diff options
| author | sergio <33748103+colado@users.noreply.github.com> | 2025-12-29 23:45:36 +0200 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2025-12-29 21:45:36 +0000 |
| commit | 30fa06feac15609fe99657e02d171e024e825322 (patch) | |
| tree | 0992aaa3a1fd3f82f38d9fe0f6d84aa990feedfc /apps | |
| parent | 5537fe85ed65444359bfd066707760d6395fc7a4 (diff) | |
| download | karakeep-30fa06feac15609fe99657e02d171e024e825322.tar.zst | |
feat(mobile): create new list edit screen (#2310)
* feat(mobile): create new edit screen and path
* refactor(mobile): use correct import for back navigation
* refactor(mobile): remove set state for list type
* feat(mobile): handle loading state
* feat(mobile): add error handling
* feat(mobile): add local validation for empty list name
* refactor(mobile): use correct param name in edit path
* feat(mobile): handle all pending state cases
* refactor(mobile): remove unnecessary return
* refactor(mobile): move type validation to top of the file
* refactor(mobile): revert validation order
* refactor(mobile): clean up submit values
* fix(mobile): fix button views
Diffstat (limited to 'apps')
| -rw-r--r-- | apps/mobile/app/dashboard/_layout.tsx | 9 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/lists/[slug]/edit.tsx | 152 | ||||
| -rw-r--r-- | apps/mobile/app/dashboard/lists/[slug]/index.tsx (renamed from apps/mobile/app/dashboard/lists/[slug].tsx) | 19 |
3 files changed, 178 insertions, 2 deletions
diff --git a/apps/mobile/app/dashboard/_layout.tsx b/apps/mobile/app/dashboard/_layout.tsx index 260071f0..60fbc4fc 100644 --- a/apps/mobile/app/dashboard/_layout.tsx +++ b/apps/mobile/app/dashboard/_layout.tsx @@ -110,6 +110,15 @@ export default function Dashboard() { }} /> <Stack.Screen + name="lists/[slug]/edit" + options={{ + headerTitle: "Edit List", + headerBackTitle: "Back", + headerTransparent: true, + presentation: "modal", + }} + /> + <Stack.Screen name="archive" options={{ headerTitle: "", 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..6ccc2f26 --- /dev/null +++ b/apps/mobile/app/dashboard/lists/[slug]/edit.tsx @@ -0,0 +1,152 @@ +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 { api } from "@/lib/trpc"; + +import { useEditBookmarkList } from "@karakeep/shared-react/hooks/lists"; + +const EditListPage = () => { + const { slug: listId } = useLocalSearchParams<{ slug?: string | string[] }>(); + const [text, setText] = useState(""); + const [query, setQuery] = useState(""); + const { toast } = useToast(); + 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 } = api.lists.get.useQuery({ + 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..11379588 100644 --- a/apps/mobile/app/dashboard/lists/[slug].tsx +++ b/apps/mobile/app/dashboard/lists/[slug]/index.tsx @@ -96,10 +96,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 +136,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} |
