From 27453300782a1bade030277c021d4961061e9c69 Mon Sep 17 00:00:00 2001 From: MohamedBassem Date: Sat, 23 Nov 2024 23:21:03 +0000 Subject: feat(mobile): Add support for managing tags from mobile --- apps/mobile/app/dashboard/_layout.tsx | 9 ++ .../app/dashboard/bookmarks/[slug]/index.tsx | 15 ++- .../mobile/app/dashboard/bookmarks/[slug]/info.tsx | 20 ++- .../app/dashboard/bookmarks/[slug]/manage_tags.tsx | 139 +++++++++++++++++++++ 4 files changed, 178 insertions(+), 5 deletions(-) create mode 100644 apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx (limited to 'apps/mobile/app/dashboard') diff --git a/apps/mobile/app/dashboard/_layout.tsx b/apps/mobile/app/dashboard/_layout.tsx index bc743c7a..5717f711 100644 --- a/apps/mobile/app/dashboard/_layout.tsx +++ b/apps/mobile/app/dashboard/_layout.tsx @@ -66,6 +66,15 @@ export default function Dashboard() { presentation: "modal", }} /> + } + /> + ), + shouldRender: true, + onClick: () => + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`), + disabled: false, + }, { id: "open", icon: ( diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx index e0d87a09..e1b1bdbc 100644 --- a/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/info.tsx @@ -31,10 +31,22 @@ function TagList({ bookmark }: { bookmark: ZBookmark }) { ) : bookmark.tags.length > 0 ? ( - - {bookmark.tags.map((t) => ( - - ))} + + + {bookmark.tags.map((t) => ( + + ))} + + + + router.push(`/dashboard/bookmarks/${bookmark.id}/manage_tags`) + } + className="flex w-full flex-row justify-between gap-3 rounded-lg bg-white px-4 py-2 dark:bg-accent" + > + Manage Tags + + ) : ( No tags diff --git a/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx new file mode 100644 index 00000000..1712fdfe --- /dev/null +++ b/apps/mobile/app/dashboard/bookmarks/[slug]/manage_tags.tsx @@ -0,0 +1,139 @@ +import React, { useMemo } from "react"; +import { Pressable, SectionList, Text, View } from "react-native"; +import { useLocalSearchParams } from "expo-router"; +import { TailwindResolver } from "@/components/TailwindResolver"; +import CustomSafeAreaView from "@/components/ui/CustomSafeAreaView"; +import FullPageSpinner from "@/components/ui/FullPageSpinner"; +import { Input } from "@/components/ui/Input"; +import { useToast } from "@/components/ui/Toast"; +import { Check } from "lucide-react-native"; + +import { useUpdateBookmarkTags } from "@hoarder/shared-react/hooks/bookmarks"; +import { api } from "@hoarder/shared-react/trpc"; + +const ListPickerPage = () => { + const { slug: bookmarkId } = useLocalSearchParams(); + + const [search, setSearch] = React.useState(""); + + if (typeof bookmarkId !== "string") { + throw new Error("Unexpected param type"); + } + const { toast } = useToast(); + const onError = () => { + toast({ + message: "Something went wrong", + variant: "destructive", + showProgress: false, + }); + }; + const { data: allTags, isPending: isAllTagsPending } = + api.tags.list.useQuery(); + const { data: existingTags } = api.bookmarks.getBookmark.useQuery( + { + bookmarkId, + }, + { + select: (data) => data.tags.map((t) => ({ id: t.id, name: t.name })), + }, + ); + + const [optimisticTags, setOptimisticTags] = React.useState( + existingTags ?? [], + ); + + const { mutate: updateTags } = useUpdateBookmarkTags({ + onMutate: (req) => { + req.attach.forEach((t) => + setOptimisticTags((prev) => [ + ...prev, + { id: t.tagId!, name: t.tagName! }, + ]), + ); + req.detach.forEach((t) => + setOptimisticTags((prev) => prev.filter((p) => p.id != t.tagId!)), + ); + }, + onError, + }); + + const optimisticExistingTagIds = useMemo(() => { + return new Set(optimisticTags?.map((t) => t.id) ?? []); + }, [optimisticTags]); + + const filteredTags = useMemo(() => { + return allTags?.tags.filter( + (t) => + t.name.toLowerCase().startsWith(search.toLowerCase()) && + !optimisticExistingTagIds.has(t.id), + ); + }, [search, allTags, optimisticExistingTagIds]); + + if (isAllTagsPending) { + return ; + } + + return ( + + + + } + keyExtractor={(t) => t.id} + contentContainerStyle={{ + gap: 5, + }} + SectionSeparatorComponent={() => } + sections={[ + { + title: "Existing Tags", + data: optimisticTags ?? [], + }, + { + title: "All Tags", + data: filteredTags ?? [], + }, + ]} + renderItem={(t) => ( + + updateTags({ + bookmarkId, + detach: + t.section.title == "Existing Tags" + ? [{ tagId: t.item.id, tagName: t.item.name }] + : [], + attach: + t.section.title == "All Tags" + ? [{ tagId: t.item.id, tagName: t.item.name }] + : [], + }) + } + > + + {t.section.title == "Existing Tags" && ( + } + /> + )} + + {t.item.name} + + + + )} + /> + + + ); +}; + +export default ListPickerPage; -- cgit v1.2.3-70-g09d2