aboutsummaryrefslogtreecommitdiffstats
path: root/apps/mobile/app/dashboard/lists
diff options
context:
space:
mode:
Diffstat (limited to 'apps/mobile/app/dashboard/lists')
-rw-r--r--apps/mobile/app/dashboard/lists/[slug]/edit.tsx156
-rw-r--r--apps/mobile/app/dashboard/lists/[slug]/index.tsx (renamed from apps/mobile/app/dashboard/lists/[slug].tsx)50
-rw-r--r--apps/mobile/app/dashboard/lists/new.tsx30
3 files changed, 208 insertions, 28 deletions
diff --git a/apps/mobile/app/dashboard/lists/[slug]/edit.tsx b/apps/mobile/app/dashboard/lists/[slug]/edit.tsx
new file mode 100644
index 00000000..c1103b4d
--- /dev/null
+++ b/apps/mobile/app/dashboard/lists/[slug]/edit.tsx
@@ -0,0 +1,156 @@
+import { useEffect, useState } from "react";
+import { View } from "react-native";
+import { router, useLocalSearchParams } from "expo-router";
+import { Button } from "@/components/ui/Button";
+import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
+import FullPageSpinner from "@/components/ui/FullPageSpinner";
+import { Input } from "@/components/ui/Input";
+import { Text } from "@/components/ui/Text";
+import { useToast } from "@/components/ui/Toast";
+import { useQuery } from "@tanstack/react-query";
+
+import { useEditBookmarkList } from "@karakeep/shared-react/hooks/lists";
+import { useTRPC } from "@karakeep/shared-react/trpc";
+
+const EditListPage = () => {
+ const { slug: listId } = useLocalSearchParams<{ slug?: string | string[] }>();
+ const [text, setText] = useState("");
+ const [query, setQuery] = useState("");
+ const { toast } = useToast();
+ const api = useTRPC();
+ const { mutate, isPending: editIsPending } = useEditBookmarkList({
+ onSuccess: () => {
+ dismiss();
+ },
+ onError: (error) => {
+ // Extract error message from the error object
+ let errorMessage = "Something went wrong";
+ if (error.data?.zodError) {
+ errorMessage = Object.values(error.data.zodError.fieldErrors)
+ .flat()
+ .join("\n");
+ } else if (error.message) {
+ errorMessage = error.message;
+ }
+ toast({
+ message: errorMessage,
+ variant: "destructive",
+ });
+ },
+ });
+
+ if (typeof listId !== "string") {
+ throw new Error("Unexpected param type");
+ }
+
+ const { data: list, isLoading: fetchIsPending } = useQuery(
+ api.lists.get.queryOptions({
+ listId,
+ }),
+ );
+
+ const dismiss = () => {
+ router.back();
+ };
+
+ useEffect(() => {
+ if (!list) return;
+ setText(list.name ?? "");
+ setQuery(list.query ?? "");
+ }, [list?.id, list?.query, list?.name]);
+
+ const onSubmit = () => {
+ if (!text.trim()) {
+ toast({ message: "List name can't be empty", variant: "destructive" });
+ return;
+ }
+
+ if (list?.type === "smart" && !query.trim()) {
+ toast({
+ message: "Smart lists must have a search query",
+ variant: "destructive",
+ });
+ return;
+ }
+
+ mutate({
+ listId,
+ name: text.trim(),
+ query: list?.type === "smart" ? query.trim() : undefined,
+ });
+ };
+
+ const isPending = fetchIsPending || editIsPending;
+
+ return (
+ <CustomSafeAreaView>
+ {isPending ? (
+ <FullPageSpinner />
+ ) : (
+ <View className="gap-3 px-4">
+ {/* List Type Info - not editable */}
+ <View className="gap-2">
+ <Text className="text-sm text-muted-foreground">List Type</Text>
+ <View className="flex flex-row gap-2">
+ <View className="flex-1">
+ <Button
+ variant={list?.type === "manual" ? "primary" : "secondary"}
+ disabled
+ >
+ <Text>Manual</Text>
+ </Button>
+ </View>
+ <View className="flex-1">
+ <Button
+ variant={list?.type === "smart" ? "primary" : "secondary"}
+ disabled
+ >
+ <Text>Smart</Text>
+ </Button>
+ </View>
+ </View>
+ </View>
+
+ {/* List Name */}
+ <View className="flex flex-row items-center gap-1">
+ <Text className="shrink p-2">{list?.icon || "🚀"}</Text>
+ <Input
+ className="flex-1 bg-card"
+ onChangeText={setText}
+ value={text}
+ placeholder="List Name"
+ autoFocus
+ autoCapitalize={"none"}
+ />
+ </View>
+
+ {/* Smart List Query Input */}
+ {list?.type === "smart" && (
+ <View className="gap-2">
+ <Text className="text-sm text-muted-foreground">
+ Search Query
+ </Text>
+ <Input
+ className="bg-card"
+ onChangeText={setQuery}
+ value={query}
+ placeholder="e.g., #important OR list:work"
+ autoCapitalize={"none"}
+ />
+ <Text className="text-xs italic text-muted-foreground">
+ Smart lists automatically show bookmarks matching your search
+ query
+ </Text>
+ </View>
+ )}
+
+ <Button disabled={isPending} onPress={onSubmit}>
+ <Text>Save</Text>
+ </Button>
+ </View>
+ )}
+ </CustomSafeAreaView>
+ );
+};
+
+export default EditListPage;
diff --git a/apps/mobile/app/dashboard/lists/[slug].tsx b/apps/mobile/app/dashboard/lists/[slug]/index.tsx
index e7aab443..763df65e 100644
--- a/apps/mobile/app/dashboard/lists/[slug].tsx
+++ b/apps/mobile/app/dashboard/lists/[slug]/index.tsx
@@ -5,14 +5,16 @@ import UpdatingBookmarkList from "@/components/bookmarks/UpdatingBookmarkList";
import FullPageError from "@/components/FullPageError";
import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView";
import FullPageSpinner from "@/components/ui/FullPageSpinner";
-import { api } from "@/lib/trpc";
import { MenuView } from "@react-native-menu/menu";
+import { useMutation, useQuery } from "@tanstack/react-query";
import { Ellipsis } from "lucide-react-native";
+import { useTRPC } from "@karakeep/shared-react/trpc";
import { ZBookmarkList } from "@karakeep/shared/types/lists";
export default function ListView() {
const { slug } = useLocalSearchParams();
+ const api = useTRPC();
if (typeof slug !== "string") {
throw new Error("Unexpected param type");
}
@@ -20,7 +22,7 @@ export default function ListView() {
data: list,
error,
refetch,
- } = api.lists.get.useQuery({ listId: slug });
+ } = useQuery(api.lists.get.queryOptions({ listId: slug }));
return (
<CustomSafeAreaView>
@@ -58,17 +60,22 @@ function ListActionsMenu({
listId: string;
role: ZBookmarkList["userRole"];
}) {
- const { mutate: deleteList } = api.lists.delete.useMutation({
- onSuccess: () => {
- router.replace("/dashboard/lists");
- },
- });
+ const api = useTRPC();
+ const { mutate: deleteList } = useMutation(
+ api.lists.delete.mutationOptions({
+ onSuccess: () => {
+ router.replace("/dashboard/lists");
+ },
+ }),
+ );
- const { mutate: leaveList } = api.lists.leaveList.useMutation({
- onSuccess: () => {
- router.replace("/dashboard/lists");
- },
- });
+ const { mutate: leaveList } = useMutation(
+ api.lists.leaveList.mutationOptions({
+ onSuccess: () => {
+ router.replace("/dashboard/lists");
+ },
+ }),
+ );
const handleDelete = () => {
Alert.alert("Delete List", "Are you sure you want to delete this list?", [
@@ -96,10 +103,24 @@ function ListActionsMenu({
]);
};
+ const handleEdit = () => {
+ router.push({
+ pathname: "/dashboard/lists/[slug]/edit",
+ params: { slug: listId },
+ });
+ };
+
return (
<MenuView
actions={[
{
+ id: "edit",
+ title: "Edit List",
+ attributes: {
+ hidden: role !== "owner",
+ },
+ },
+ {
id: "delete",
title: "Delete List",
attributes: {
@@ -122,9 +143,10 @@ function ListActionsMenu({
onPressAction={({ nativeEvent }) => {
if (nativeEvent.event === "delete") {
handleDelete();
- }
- if (nativeEvent.event === "leave") {
+ } else if (nativeEvent.event === "leave") {
handleLeave();
+ } else if (nativeEvent.event === "edit") {
+ handleEdit();
}
}}
shouldOpenOnLongPress={false}
diff --git a/apps/mobile/app/dashboard/lists/new.tsx b/apps/mobile/app/dashboard/lists/new.tsx
index af51ed15..bada46f2 100644
--- a/apps/mobile/app/dashboard/lists/new.tsx
+++ b/apps/mobile/app/dashboard/lists/new.tsx
@@ -66,20 +66,22 @@ const NewListPage = () => {
<View className="gap-2">
<Text className="text-sm text-muted-foreground">List Type</Text>
<View className="flex flex-row gap-2">
- <Button
- variant={listType === "manual" ? "primary" : "secondary"}
- onPress={() => setListType("manual")}
- className="flex-1"
- >
- <Text>Manual</Text>
- </Button>
- <Button
- variant={listType === "smart" ? "primary" : "secondary"}
- onPress={() => setListType("smart")}
- className="flex-1"
- >
- <Text>Smart</Text>
- </Button>
+ <View className="flex-1">
+ <Button
+ variant={listType === "manual" ? "primary" : "secondary"}
+ onPress={() => setListType("manual")}
+ >
+ <Text>Manual</Text>
+ </Button>
+ </View>
+ <View className="flex-1">
+ <Button
+ variant={listType === "smart" ? "primary" : "secondary"}
+ onPress={() => setListType("smart")}
+ >
+ <Text>Smart</Text>
+ </Button>
+ </View>
</View>
</View>