diff --git a/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx b/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx index fcbfc361..62391309 100644 --- a/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx +++ b/app/(loggedInRoutes)/admin/checklist/[uuid]/page.tsx @@ -58,7 +58,7 @@ export default async function AdminChecklistPage(props: AdminChecklistPageProps) : []; const metadata = { - id: checklist.id, + id: checklist.slug, uuid: checklist.uuid, title: checklist.title, category: checklist.category || "Uncategorized", diff --git a/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx b/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx index 666eaf92..3c07bc76 100644 --- a/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx +++ b/app/(loggedInRoutes)/admin/note/[uuid]/page.tsx @@ -59,7 +59,7 @@ export default async function AdminNotePage(props: AdminNotePageProps) { : []; const metadata = { - id: note.id, + id: note.slug, uuid: note.uuid, title: note.title, category: note.category || "Uncategorized", diff --git a/app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx b/app/(loggedInRoutes)/checklist/[user]/[uuid]/page.tsx similarity index 72% rename from app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx rename to app/(loggedInRoutes)/checklist/[user]/[uuid]/page.tsx index 09d1fee8..31adeee2 100644 --- a/app/(loggedInRoutes)/checklist/[...categoryPath]/page.tsx +++ b/app/(loggedInRoutes)/checklist/[user]/[uuid]/page.tsx @@ -13,44 +13,42 @@ import { sanitizeUserForClient } from "@/app/_utils/user-sanitize-utils"; interface ChecklistPageProps { params: Promise<{ - categoryPath: string[]; + user: string; + uuid: string; }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; } export const dynamic = "force-dynamic"; export async function generateMetadata(props: ChecklistPageProps): Promise { const params = await props.params; - const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); - - return getMedatadaTitle(Modes.CHECKLISTS, id, category); + const { user, uuid } = params; + return getMedatadaTitle(Modes.CHECKLISTS, decodeId(uuid), decodeURIComponent(user)); } export default async function ChecklistPage(props: ChecklistPageProps) { const params = await props.params; - const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); + const searchParams = await props.searchParams; + const { user: ownerUsername, uuid } = params; + const id = decodeId(uuid); + const owner = decodeURIComponent(ownerUsername); + + const categoryFallbackRaw = searchParams?.c; + const categoryFallback = Array.isArray(categoryFallbackRaw) + ? categoryFallbackRaw[0] + : categoryFallbackRaw; + const userRecord = await getCurrentUser(); const username = userRecord?.username || ""; const hasContentAccess = await canAccessAllContent(); const categoriesResult = await getCategories(Modes.CHECKLISTS); - let checklist = await getListById(id, username, category); + let checklist = await getListById(id, owner, username, false, categoryFallback); if (!checklist && hasContentAccess) { - checklist = await getListById(id, undefined, category); + checklist = await getListById(id, owner, undefined, false, categoryFallback); } if (!checklist) { @@ -63,7 +61,7 @@ export default async function ChecklistPage(props: ChecklistPageProps) { : []; const metadata = { - id: checklist.id, + id: checklist.slug, uuid: checklist.uuid, title: checklist.title, category: checklist.category || "Uncategorized", diff --git a/app/(loggedInRoutes)/note/[...categoryPath]/page.tsx b/app/(loggedInRoutes)/note/[user]/[uuid]/page.tsx similarity index 71% rename from app/(loggedInRoutes)/note/[...categoryPath]/page.tsx rename to app/(loggedInRoutes)/note/[user]/[uuid]/page.tsx index 46ccab8e..30412586 100644 --- a/app/(loggedInRoutes)/note/[...categoryPath]/page.tsx +++ b/app/(loggedInRoutes)/note/[user]/[uuid]/page.tsx @@ -18,34 +18,32 @@ import { MetadataProvider } from "@/app/_providers/MetadataProvider"; interface NotePageProps { params: Promise<{ - categoryPath: string[]; + user: string; + uuid: string; }>; + searchParams: Promise<{ [key: string]: string | string[] | undefined }>; } export const dynamic = "force-dynamic"; export async function generateMetadata(props: NotePageProps): Promise { const params = await props.params; - const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); - - return getMedatadaTitle(Modes.NOTES, id, category); + const { user, uuid } = params; + return getMedatadaTitle(Modes.NOTES, decodeId(uuid), decodeURIComponent(user)); } export default async function NotePage(props: NotePageProps) { const params = await props.params; - const { categoryPath } = params; - const id = decodeId(categoryPath[categoryPath.length - 1]); - const encodedCategoryPath = categoryPath.slice(0, -1).join("/"); - const category = - categoryPath.length === 1 - ? "Uncategorized" - : decodeCategoryPath(encodedCategoryPath); + const searchParams = await props.searchParams; + const { user: ownerUsername, uuid } = params; + const id = decodeId(uuid); + const owner = decodeURIComponent(ownerUsername); + + const categoryFallbackRaw = searchParams?.c; + const categoryFallback = Array.isArray(categoryFallbackRaw) + ? categoryFallbackRaw[0] + : categoryFallbackRaw; + const user = await getCurrentUser(); const username = user?.username || ""; const hasContentAccess = await canAccessAllContent(); @@ -54,10 +52,10 @@ export default async function NotePage(props: NotePageProps) { const categoriesResult = await getCategories(Modes.NOTES); - let note = await getNoteById(id, category, username); + let note = await getNoteById(id, owner, username, false, categoryFallback); if (!note && hasContentAccess) { - note = await getNoteById(id, category); + note = await getNoteById(id, owner, undefined, false, categoryFallback); } if (!note) { @@ -70,7 +68,7 @@ export default async function NotePage(props: NotePageProps) { : []; const metadata = { - id: note.id, + id: note.slug, uuid: note.uuid, title: note.title, category: note.category || "Uncategorized", diff --git a/app/(loggedInRoutes)/page.tsx b/app/(loggedInRoutes)/page.tsx index 711f6cc1..bb65c90d 100644 --- a/app/(loggedInRoutes)/page.tsx +++ b/app/(loggedInRoutes)/page.tsx @@ -52,8 +52,8 @@ export default async function HomePage() { ); diff --git a/app/(loggedInRoutes)/settings/admin/content/page.tsx b/app/(loggedInRoutes)/settings/admin/content/page.tsx index cc8aa28c..cd4e49b5 100644 --- a/app/(loggedInRoutes)/settings/admin/content/page.tsx +++ b/app/(loggedInRoutes)/settings/admin/content/page.tsx @@ -7,21 +7,21 @@ import { canAccessAllContent } from "@/app/_server/actions/users"; import { notFound } from "next/navigation"; export default async function AdminContentPage() { - const hasAccess = await canAccessAllContent(); + const hasAccess = await canAccessAllContent(); - if (!hasAccess) { - return notFound(); - } + if (!hasAccess) { + return notFound(); + } - const [usersData, listsData, docsData] = await Promise.all([ - readJsonFile(USERS_FILE), - getAllLists(), - getAllNotes(), - ]); + const [usersData, listsData, docsData] = await Promise.all([ + readJsonFile(USERS_FILE), + getAllLists(), + getAllNotes(), + ]); - const users = usersData; - const allLists = listsData.success && listsData.data ? listsData.data : []; - const allDocs = docsData.success && docsData.data ? docsData.data : []; + const users = usersData; + const allLists = listsData.success && listsData.data ? listsData.data : []; + const allNotes = docsData.success && docsData.data ? docsData.data : []; - return ; + return ; } diff --git a/app/(loggedInRoutes)/settings/admin/overview/page.tsx b/app/(loggedInRoutes)/settings/admin/overview/page.tsx index 7c67fb84..ff9d58db 100644 --- a/app/(loggedInRoutes)/settings/admin/overview/page.tsx +++ b/app/(loggedInRoutes)/settings/admin/overview/page.tsx @@ -7,28 +7,28 @@ import { getAllLists } from "@/app/_server/actions/checklist"; import { getAllNotes } from "@/app/_server/actions/note"; export default async function AdminOverviewPage() { - const admin = await isAdmin(); + const admin = await isAdmin(); - if (!admin) { - return notFound(); - } + if (!admin) { + return notFound(); + } - const [usersData, listsData, docsData] = await Promise.all([ - readJsonFile(USERS_FILE), - getAllLists(), - getAllNotes(), - ]); + const [usersData, listsData, docsData] = await Promise.all([ + readJsonFile(USERS_FILE), + getAllLists(), + getAllNotes(), + ]); - const users = usersData; - const allLists = listsData.success && listsData.data ? listsData.data : []; - const allDocs = docsData.success && docsData.data ? docsData.data : []; + const users = usersData; + const allLists = listsData.success && listsData.data ? listsData.data : []; + const allNotes = docsData.success && docsData.data ? docsData.data : []; - const stats = { - totalUsers: users.length, - totalChecklists: allLists.length, - totalNotes: allDocs.length, - adminUsers: users.filter((u: any) => u.isAdmin).length, - }; + const stats = { + totalUsers: users.length, + totalChecklists: allLists.length, + totalNotes: allNotes.length, + adminUsers: users.filter((u: any) => u.isAdmin).length, + }; - return ; + return ; } diff --git a/app/(loggedInRoutes)/settings/admin/users/page.tsx b/app/(loggedInRoutes)/settings/admin/users/page.tsx index 5fe3573a..e4e6cad6 100644 --- a/app/(loggedInRoutes)/settings/admin/users/page.tsx +++ b/app/(loggedInRoutes)/settings/admin/users/page.tsx @@ -7,29 +7,29 @@ import { isAdmin, getUsername } from "@/app/_server/actions/users"; import { notFound } from "next/navigation"; export default async function AdminUsersPage() { - const admin = await isAdmin(); - const username = await getUsername(); + const admin = await isAdmin(); + const username = await getUsername(); - if (!admin) { - return notFound(); - } + if (!admin) { + return notFound(); + } - const [usersData, listsData, docsData] = await Promise.all([ - readJsonFile(USERS_FILE), - getAllLists(), - getAllNotes(), - ]); + const [usersData, listsData, docsData] = await Promise.all([ + readJsonFile(USERS_FILE), + getAllLists(), + getAllNotes(), + ]); - const users = usersData; - const allLists = listsData.success && listsData.data ? listsData.data : []; - const allDocs = docsData.success && docsData.data ? docsData.data : []; + const users = usersData; + const allLists = listsData.success && listsData.data ? listsData.data : []; + const allNotes = docsData.success && docsData.data ? docsData.data : []; - return ( - - ); + return ( + + ); } diff --git a/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx b/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx index 0d3ab6e0..a3106dda 100644 --- a/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx +++ b/app/_components/FeatureComponents/Admin/Parts/AdminContent.tsx @@ -23,13 +23,13 @@ import { useAppMode } from "@/app/_providers/AppModeProvider"; interface AdminContentProps { allLists: Checklist[]; - allDocs: Note[]; + allNotes: Note[]; users: UserType[]; } export const AdminContent = ({ allLists, - allDocs, + allNotes, users, }: AdminContentProps) => { const t = useTranslations(); @@ -50,11 +50,11 @@ export const AdminContent = ({ }); const docsByOwner = new Map(); - allDocs.forEach((doc) => { + allNotes.forEach((doc) => { if (doc.owner) { - const ownerDocs = docsByOwner.get(doc.owner) || []; - ownerDocs.push(doc); - docsByOwner.set(doc.owner, ownerDocs); + const ownerNotes = docsByOwner.get(doc.owner) || []; + ownerNotes.push(doc); + docsByOwner.set(doc.owner, ownerNotes); } }); @@ -70,12 +70,12 @@ export const AdminContent = ({ }; }) .sort((a, b) => b.totalItems - a.totalItems); - }, [users, allLists, allDocs]); + }, [users, allLists, allNotes]); useEffect(() => { if (expandedUsers === null && sortedUserContent.length > 0) { setExpandedUsers( - new Set(sortedUserContent.map((uc) => uc.user.username)) + new Set(sortedUserContent.map((uc) => uc.user.username)), ); } }, [sortedUserContent, expandedUsers]); @@ -97,7 +97,7 @@ export const AdminContent = ({ setExpandedUsers(new Set()); } else { setExpandedUsers( - new Set(sortedUserContent.map((uc) => uc.user.username)) + new Set(sortedUserContent.map((uc) => uc.user.username)), ); } }; @@ -110,15 +110,15 @@ export const AdminContent = ({ await rebuildLinkIndex(username); showToast({ type: "success", - title: t('common.success'), - message: t('admin.successfullyRebuiltIndex', { username }), + title: t("common.success"), + message: t("admin.successfullyRebuiltIndex", { username }), }); } catch (error) { console.error("Failed to rebuild index:", error); showToast({ type: "error", - title: t('common.error'), - message: t('admin.failedToRebuildIndex', { username }), + title: t("common.error"), + message: t("admin.failedToRebuildIndex", { username }), }); } finally { setRebuildingIndex(null); @@ -133,7 +133,10 @@ export const AdminContent = ({ showToast({ type: "success", title: t("common.success"), - message: t("admin.tagsUpdated", { processed: result.data?.processed || 0, updated: result.data?.updated || 0 }), + message: t("admin.tagsUpdated", { + processed: result.data?.processed || 0, + updated: result.data?.updated || 0, + }), }); } else { throw new Error(result.error); @@ -151,20 +154,27 @@ export const AdminContent = ({ return (
- +
- {t('admin.totalItems', { items: allLists.length + allDocs.length, userCount: users.length })} + {t("admin.totalItems", { + items: allLists.length + allNotes.length, + userCount: users.length, + })}
{tagsEnabled && ( @@ -212,7 +222,10 @@ export const AdminContent = ({ )}

- {t('admin.userContent', { checklistsLength: checklists.length, notesLength: notes.length })} + {t("admin.userContent", { + checklistsLength: checklists.length, + notesLength: notes.length, + })}

@@ -225,15 +238,15 @@ export const AdminContent = ({ handleRebuildIndex(user.username); }} disabled={rebuildingIndex === user.username} - title={t('admin.rebuildLinkIndexesTitle')} + title={t("admin.rebuildLinkIndexesTitle")} > {rebuildingIndex === user.username - ? t('admin.rebuilding') - : t('admin.rebuildIndexes')} + ? t("admin.rebuilding") + : t("admin.rebuildIndexes")} {hasContent && ( - {t('common.itemCount', { count: totalItems })} + {t("common.itemCount", { count: totalItems })} )} {isExpanded ? ( @@ -249,21 +262,21 @@ export const AdminContent = ({ {hasContent ? (
} items={checklists.map((list) => ({ ...list, link: `/admin/checklist/${list.uuid}`, - details: `${list.owner} • ${list.category} • ${t('common.itemCount', { count: list.items.length })}`, + details: `${list.owner} • ${list.category} • ${t("common.itemCount", { count: list.items.length })}`, }))} /> } items={notes.map((doc) => ({ ...doc, link: `/admin/note/${doc.uuid}`, - details: `${doc.owner} • ${doc.category} • ${t('common.characterCount', { count: doc.content.length })}`, + details: `${doc.owner} • ${doc.category} • ${t("common.characterCount", { count: doc.content.length })}`, }))} />
@@ -273,10 +286,10 @@ export const AdminContent = ({

- {t('admin.noContentYet')} + {t("admin.noContentYet")}

- {t('admin.userHasNoContent')} + {t("admin.userHasNoContent")}

)} diff --git a/app/_components/FeatureComponents/Admin/Parts/AdminContentColumn.tsx b/app/_components/FeatureComponents/Admin/Parts/AdminContentColumn.tsx index 2611bc8b..92d70c18 100644 --- a/app/_components/FeatureComponents/Admin/Parts/AdminContentColumn.tsx +++ b/app/_components/FeatureComponents/Admin/Parts/AdminContentColumn.tsx @@ -28,7 +28,7 @@ export const AdminContentColumn = ({
{items.map((item) => ( diff --git a/app/_components/FeatureComponents/Admin/Parts/AdminUsers.tsx b/app/_components/FeatureComponents/Admin/Parts/AdminUsers.tsx index bdf37453..a0814c85 100644 --- a/app/_components/FeatureComponents/Admin/Parts/AdminUsers.tsx +++ b/app/_components/FeatureComponents/Admin/Parts/AdminUsers.tsx @@ -1,12 +1,12 @@ "use client"; import { - Search01Icon, - Add01Icon, - UserEdit01Icon, - Delete03Icon, - ShieldUserIcon, - UserIcon, + Search01Icon, + Add01Icon, + UserEdit01Icon, + Delete03Icon, + ShieldUserIcon, + UserIcon, } from "hugeicons-react"; import { Button } from "@/app/_components/GlobalComponents/Buttons/Button"; import { User as UserType, Checklist, Note } from "@/app/_types"; @@ -16,160 +16,173 @@ import { useTranslations } from "next-intl"; import { useAppMode } from "@/app/_providers/AppModeProvider"; interface AdminUsersProps { - users: UserType[]; - searchQuery: string; - onSearchChange: (query: string) => void; - onAddUser: () => void; - onEditUser: (user: UserType) => void; - onDeleteUser: (user: UserType) => void; - allLists: Checklist[]; - allDocs: Note[]; - username: string; - deletingUser: string | null; + users: UserType[]; + searchQuery: string; + onSearchChange: (query: string) => void; + onAddUser: () => void; + onEditUser: (user: UserType) => void; + onDeleteUser: (user: UserType) => void; + allLists: Checklist[]; + allNotes: Note[]; + username: string; + deletingUser: string | null; } export const AdminUsers = ({ - users, - searchQuery, - onSearchChange, - onAddUser, - onEditUser, - onDeleteUser, - allLists, - allDocs, - username, - deletingUser, + users, + searchQuery, + onSearchChange, + onAddUser, + onEditUser, + onDeleteUser, + allLists, + allNotes, + username, + deletingUser, }: AdminUsersProps) => { - const t = useTranslations(); - const { user: currentUser } = useAppMode(); - const isSuperAdmin = currentUser?.isSuperAdmin || false; + const t = useTranslations(); + const { user: currentUser } = useAppMode(); + const isSuperAdmin = currentUser?.isSuperAdmin || false; - const filteredUsers = users.filter((user) => - user.username.toLowerCase().includes(searchQuery.toLowerCase()) - ); + const filteredUsers = users.filter((user) => + user.username.toLowerCase().includes(searchQuery.toLowerCase()), + ); - return ( -
-
- -
- -
- - onSearchChange(e.target.value)} - className="pl-10" - /> -
+ return ( +
+
+ +
-
- {filteredUsers.map((user) => { - const userChecklists = allLists.filter( - (list) => list.owner === user.username - ).length; - const userDocs = allDocs.filter( - (doc) => doc.owner === user.username - ).length; +
+ + onSearchChange(e.target.value)} + className="pl-10" + /> +
- return ( -
-
-
-
- -
-
-
-

- {user.username} -

- {user.username === username && ( - - {t("admin.you")} - - )} - {user.isAdmin && ( - - )} - {user.isSuperAdmin && ( - - {t("admin.systemOwner")} - - )} -
-

- {t("admin.userRole", { - role: user.isAdmin ? t("common.admin") : t("common.user"), - checklists: userChecklists, - notes: userDocs, - })} -

-
-
-
- - {user.username !== username && ( - - )} -
-
-
- ); - })} +
+ {filteredUsers.map((user) => { + const userChecklists = allLists.filter( + (list) => list.owner === user.username, + ).length; + const userNotes = allNotes.filter( + (doc) => doc.owner === user.username, + ).length; - {filteredUsers.length === 0 && ( -
-
- -
-

- {searchQuery ? t("admin.noUsersFound") : t("admin.noUsersYet")} -

-

- {searchQuery - ? t("admin.noUsersMatchSearch") - : t("admin.usersWillAppear")} -

+ return ( +
+
+
+
+ +
+
+
+

+ {user.username} +

+ {user.username === username && ( + + {t("admin.you")} + + )} + {user.isAdmin && ( + + )} + {user.isSuperAdmin && ( + + {t("admin.systemOwner")} + + )}
- )} +

+ {t("admin.userRole", { + role: user.isAdmin + ? t("common.admin") + : t("common.user"), + checklists: userChecklists, + notes: userNotes, + })} +

+
+
+
+ + {user.username !== username && ( + + )} +
+
+
+ ); + })} + + {filteredUsers.length === 0 && ( +
+
+
-
- ); +

+ {searchQuery ? t("admin.noUsersFound") : t("admin.noUsersYet")} +

+

+ {searchQuery + ? t("admin.noUsersMatchSearch") + : t("admin.usersWillAppear")} +

+
+ )} +
+
+ ); }; diff --git a/app/_components/FeatureComponents/Admin/Parts/AdminUsersClient.tsx b/app/_components/FeatureComponents/Admin/Parts/AdminUsersClient.tsx index 73ab538e..3dcb9a3e 100644 --- a/app/_components/FeatureComponents/Admin/Parts/AdminUsersClient.tsx +++ b/app/_components/FeatureComponents/Admin/Parts/AdminUsersClient.tsx @@ -14,11 +14,16 @@ import { ConfirmModal } from "@/app/_components/GlobalComponents/Modals/Confirma interface AdminUsersClientProps { initialUsers: User[]; initialLists: Checklist[]; - initialDocs: Note[]; + initialNotes: Note[]; username: string; } -export function AdminUsersClient({ initialUsers, initialLists, initialDocs, username }: AdminUsersClientProps) { +export function AdminUsersClient({ + initialUsers, + initialLists, + initialNotes, + username, +}: AdminUsersClientProps) { const t = useTranslations(); const { showToast } = useToast(); const [users, setUsers] = useState(initialUsers); @@ -32,9 +37,7 @@ export function AdminUsersClient({ initialUsers, initialLists, initialDocs, user const loadData = async () => { try { - const [usersData] = await Promise.all([ - readJsonFile(USERS_FILE), - ]); + const [usersData] = await Promise.all([readJsonFile(USERS_FILE)]); setUsers(usersData); } catch (error) { @@ -71,20 +74,22 @@ export function AdminUsersClient({ initialUsers, initialLists, initialDocs, user const result = await deleteUser(formData); if (result.success) { - setUsers((prev) => prev.filter((u) => u.username !== userToDelete.username)); + setUsers((prev) => + prev.filter((u) => u.username !== userToDelete.username), + ); } else { showToast({ type: "error", - title: t('common.error'), - message: result.error || t('errors.failedToDeleteUser'), + title: t("common.error"), + message: result.error || t("errors.failedToDeleteUser"), }); } } catch (error) { console.error("Error deleting user:", error); showToast({ type: "error", - title: t('common.error'), - message: t('errors.failedToDeleteUser'), + title: t("common.error"), + message: t("errors.failedToDeleteUser"), }); } finally { setDeletingUser(null); @@ -102,7 +107,7 @@ export function AdminUsersClient({ initialUsers, initialLists, initialDocs, user onEditUser={handleEditUser} onDeleteUser={handleDeleteUser} allLists={initialLists} - allDocs={initialDocs} + allNotes={initialNotes} username={username} deletingUser={deletingUser} /> @@ -122,7 +127,9 @@ export function AdminUsersClient({ initialUsers, initialLists, initialDocs, user }} onConfirm={confirmDeleteUser} title={t("common.delete")} - message={t('admin.deleteUserConfirmation', { username: userToDelete?.username || "" })} + message={t("admin.deleteUserConfirmation", { + username: userToDelete?.username || "", + })} confirmText={t("common.delete")} variant="destructive" /> diff --git a/app/_components/FeatureComponents/Admin/Parts/ThemePreview.tsx b/app/_components/FeatureComponents/Admin/Parts/ThemePreview.tsx index da13628c..f1eb783f 100644 --- a/app/_components/FeatureComponents/Admin/Parts/ThemePreview.tsx +++ b/app/_components/FeatureComponents/Admin/Parts/ThemePreview.tsx @@ -24,7 +24,8 @@ export const ThemePreview: React.FC = ({ const t = useTranslations(); const sampleNote = useMemo(() => ({ - id: "preview-note", + slug: "preview-note", + uuid: "preview-uuid", title: t('settings.customTheme.sampleNoteTitle'), content: t('settings.customTheme.sampleNoteContent'), category: t('settings.customTheme.sampleNoteCategory'), diff --git a/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx b/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx index dc037ddf..f45f87af 100644 --- a/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx +++ b/app/_components/FeatureComponents/Checklists/ChecklistsPageClient.tsx @@ -56,8 +56,8 @@ export const ChecklistsPageClient = ({ if (checklistFilter === "pinned") { const pinnedPaths = user?.pinnedLists || []; filtered = filtered.filter((list) => { - const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.id}`; - const idPath = `${list.category || "Uncategorized"}/${list.id}`; + const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.slug}`; + const idPath = `${list.category || "Uncategorized"}/${list.slug}`; return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); }); } else if (checklistFilter === "completed") { @@ -135,12 +135,12 @@ export const ChecklistsPageClient = ({ ]); const handleTogglePin = async (list: Checklist) => { - if (!user || isTogglingPin === list.id) return; + if (!user || isTogglingPin === list.slug) return; - setIsTogglingPin(list.id); + setIsTogglingPin(list.slug); try { const result = await togglePin( - list.id, + list.slug, list.category || "Uncategorized", ItemTypes.CHECKLIST, ); @@ -268,14 +268,14 @@ export const ChecklistsPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.uuid}`; router.push(`/checklist/${categoryPath}`); }} isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}`, + `${list.category || "Uncategorized"}/${list.uuid}`, )} onTogglePin={() => handleTogglePin(list)} /> @@ -287,14 +287,14 @@ export const ChecklistsPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.uuid}`; router.push(`/checklist/${categoryPath}`); }} isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}`, + `${list.category || "Uncategorized"}/${list.uuid}`, )} onTogglePin={() => handleTogglePin(list)} /> @@ -306,14 +306,14 @@ export const ChecklistsPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.uuid}`; router.push(`/checklist/${categoryPath}`); }} isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}`, + `${list.category || "Uncategorized"}/${list.uuid}`, )} onTogglePin={() => handleTogglePin(list)} /> diff --git a/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx b/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx index e6e1fa05..9e47c4c3 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/ChecklistClient.tsx @@ -18,7 +18,10 @@ import { useChecklist } from "@/app/_hooks/useChecklist"; import { isKanbanType, Modes } from "@/app/_types/enums"; import { useShortcut } from "@/app/_providers/ShortcutsProvider"; import { toggleArchive } from "@/app/_server/actions/dashboard"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { + buildCategoryPath, + encodeCategoryPath, +} from "@/app/_utils/global-utils"; import { useTranslations } from "next-intl"; interface ChecklistClientProps { @@ -47,16 +50,16 @@ export const ChecklistClient = ({ useState(""); const { openCreateChecklistModal, openCreateCategoryModal, openSettings } = useShortcut(); - const prevChecklistId = useRef(checklist.id); + const prevChecklistId = useRef(checklist.slug); const prevUpdatedAt = useRef(checklist.updatedAt); useEffect(() => { if ( - checklist.id !== prevChecklistId.current || + checklist.slug !== prevChecklistId.current || checklist.updatedAt !== prevUpdatedAt.current ) { setLocalChecklist(checklist); - prevChecklistId.current = checklist.id; + prevChecklistId.current = checklist.slug; prevUpdatedAt.current = checklist.updatedAt; } }, [checklist]); @@ -84,7 +87,7 @@ export const ChecklistClient = ({ const handleCloneConfirm = async (targetCategory: string) => { const formData = new FormData(); - formData.append("id", localChecklist.id); + formData.append("id", localChecklist.slug); formData.append( "originalCategory", localChecklist.category || "Uncategorized", @@ -98,12 +101,16 @@ export const ChecklistClient = ({ const result = await cloneChecklist(formData); if (result.success && result.data) { - router.push( - `/checklist/${buildCategoryPath( - result.data.category || "Uncategorized", - result.data.id, - )}`, + const userSegment = encodeURIComponent( + result.data.owner || user?.username || "unknown", ); + const uuidSegment = encodeURIComponent( + result.data.pending ? result.data.slug : result.data.uuid, + ); + const categoryQuery = result.data.pending + ? `?c=${encodeCategoryPath(result.data.category || "Uncategorized")}` + : ""; + router.push(`/checklist/${userSegment}/${uuidSegment}${categoryQuery}`); router.refresh(); } }; @@ -220,7 +227,13 @@ export const ChecklistClient = ({ onClose={() => setShowCreateModal(false)} onCreated={(newChecklist) => { if (newChecklist) { - router.push(`/checklist/${newChecklist.id}`); + const userSegment = encodeURIComponent( + newChecklist.owner || user?.username || "unknown", + ); + const uuidSegment = encodeURIComponent( + newChecklist.uuid || newChecklist.slug, + ); + router.push(`/checklist/${userSegment}/${uuidSegment}`); } setShowCreateModal(false); router.refresh(); diff --git a/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx b/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx index 69017b6c..431c47bd 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/Common/ChecklistHeader.tsx @@ -91,7 +91,7 @@ export const ChecklistHeader = ({ size="sm" onClick={handleCopyId} className="h-6 w-6 p-0" - title={`Copy ID: ${checklist?.uuid || checklist?.id}`} + title={`Copy ID: ${checklist?.uuid || checklist?.slug}`} > {copied ? ( diff --git a/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx b/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx index 3997c70b..622365c6 100644 --- a/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx +++ b/app/_components/FeatureComponents/Checklists/Parts/Common/LastModifiedCreatedInfo.tsx @@ -24,7 +24,7 @@ const LastModifiedCreatedInfo = ({ const isShared = allSharedItems?.checklists.some( (sharedChecklist) => - sharedChecklist.id === checklist.id && + sharedChecklist.id === checklist.slug && sharedChecklist.category === encodedCategory ); return ( diff --git a/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx b/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx index 7aa4369a..79ca4e3c 100644 --- a/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx +++ b/app/_components/FeatureComponents/Checklists/TasksPageClient.tsx @@ -54,8 +54,8 @@ export const TasksPageClient = ({ if (taskFilter === "pinned") { const pinnedPaths = user?.pinnedLists || []; filtered = filtered.filter((list) => { - const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.id}`; - const idPath = `${list.category || "Uncategorized"}/${list.id}`; + const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.slug}`; + const idPath = `${list.category || "Uncategorized"}/${list.slug}`; return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); }); } else if (taskFilter === "completed") { @@ -278,14 +278,14 @@ export const TasksPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.uuid}`; router.push(`/checklist/${categoryPath}`); }} isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}` + `${list.category || "Uncategorized"}/${list.uuid}` )} onTogglePin={() => { }} /> @@ -297,14 +297,14 @@ export const TasksPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.uuid}`; router.push(`/checklist/${categoryPath}`); }} isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}` + `${list.category || "Uncategorized"}/${list.uuid}` )} onTogglePin={() => { }} /> @@ -316,14 +316,14 @@ export const TasksPageClient = ({
{paginatedItems.map((list) => ( { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.uuid}`; router.push(`/checklist/${categoryPath}`); }} isPinned={user?.pinnedLists?.includes( - `${list.category || "Uncategorized"}/${list.id}` + `${list.category || "Uncategorized"}/${list.uuid}` )} onTogglePin={() => { }} /> diff --git a/app/_components/FeatureComponents/Home/HomeClient.tsx b/app/_components/FeatureComponents/Home/HomeClient.tsx index 1d820d81..d661cfd4 100644 --- a/app/_components/FeatureComponents/Home/HomeClient.tsx +++ b/app/_components/FeatureComponents/Home/HomeClient.tsx @@ -9,7 +9,7 @@ import { Checklist, Category, Note, SanitisedUser } from "@/app/_types"; import { useAppMode } from "@/app/_providers/AppModeProvider"; import { useShortcut } from "@/app/_providers/ShortcutsProvider"; import { Modes } from "@/app/_types/enums"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { encodeCategoryPath } from "@/app/_utils/global-utils"; import { MobileHeader } from "@/app/_components/GlobalComponents/Layout/MobileHeader"; interface SharingStatus { @@ -21,16 +21,16 @@ interface SharingStatus { interface HomeClientProps { initialLists: Checklist[]; initialCategories: Category[]; - initialDocs: Note[]; - initialDocsCategories: Category[]; + initialNotes: Note[]; + initialNotesCategories: Category[]; user: SanitisedUser | null; } export const HomeClient = ({ initialLists, initialCategories, - initialDocs, - initialDocsCategories, + initialNotes, + initialNotesCategories, user, }: HomeClientProps) => { const router = useRouter(); @@ -53,7 +53,11 @@ export const HomeClient = ({ return ( router.refresh()} onCategoryRenamed={() => router.refresh()} > - + {mode === Modes.CHECKLISTS && ( { - const categoryPath = buildCategoryPath( - list.category || "Uncategorized", - list.id + const userSegment = encodeURIComponent( + list.owner || user?.username || "unknown", + ); + const uuidSegment = encodeURIComponent( + list.pending ? list.slug || "" : list.uuid || list.slug || "", + ); + const categoryQuery = list.pending + ? `?c=${encodeCategoryPath(list.category || "Uncategorized")}` + : ""; + router.push( + `/checklist/${userSegment}/${uuidSegment}${categoryQuery}`, ); - router.push(`/checklist/${categoryPath}`); }} /> )} {mode === Modes.NOTES && ( { - const categoryPath = buildCategoryPath( - note.category || "Uncategorized", - note.id + const userSegment = encodeURIComponent( + note.owner || user?.username || "unknown", + ); + const uuidSegment = encodeURIComponent( + note.pending ? note.slug : note.uuid || note.slug, ); - router.push(`/note/${categoryPath}`); + const categoryQuery = note.pending + ? `?c=${encodeCategoryPath(note.category || "Uncategorized")}` + : ""; + router.push(`/note/${userSegment}/${uuidSegment}${categoryQuery}`); }} /> )} {mode === Modes.TAGS && ( isKanbanType(list.type)) - .filter((list) => !pinned.some((p) => p.id === list.id)); + .filter((list) => !pinned.some((p) => p.slug === list.slug)); }, [taskLists, displayLists, selectedCategory, pinned]); const filteredSimpleLists = useMemo(() => { if (!selectedCategory) return simpleLists; return displayLists .filter((list) => !isKanbanType(list.type)) - .filter((list) => !pinned.some((p) => p.id === list.id)); + .filter((list) => !pinned.some((p) => p.slug === list.slug)); }, [simpleLists, displayLists, selectedCategory, pinned]); const categoryDisplayName = @@ -139,7 +139,7 @@ export const ChecklistHome = ({ list.category || "Uncategorized", ); const sharedItem = userSharedItems?.checklists?.find( - (item) => item.id === list.id && item.category === encodedCategory, + (item) => item.id === list.slug && item.category === encodedCategory, ); return sharedItem?.sharer; }; @@ -229,14 +229,14 @@ export const ChecklistHome = ({ onDragEnd={handleDragEnd} > list.uuid || list.id)} + items={pinned.map((list) => list.uuid || list.slug)} strategy={verticalListSortingStrategy} > {viewMode === "card" && (
{pinned.map((list) => ( {pinned.map((list) => ( {pinned.map((list) => ( {filteredTaskLists.map((list) => ( {filteredTaskLists.map((list) => ( {filteredTaskLists.map((list) => ( {filteredSimpleLists.map((list) => ( {filteredSimpleLists.map((list) => ( {filteredSimpleLists.map((list) => ( { if (!selectedFilter) return recent; - return displayNotes.filter((note) => !pinned.some((p) => p.id === note.id)); + return displayNotes.filter((note) => !pinned.some((p) => p.slug === note.slug)); }, [displayNotes, recent, selectedFilter, pinned]); const filterDisplayName = useMemo(() => { @@ -137,7 +137,7 @@ export const NotesHome = ({ note.category || "Uncategorized", ); const sharedItem = userSharedItems?.notes?.find( - (item) => item.id === note.id && item.category === encodedCategory, + (item) => item.id === note.slug && item.category === encodedCategory, ); return sharedItem?.sharer; }; @@ -220,7 +220,7 @@ export const NotesHome = ({ onDragEnd={handleDragEnd} > note.uuid || note.id)} + items={pinned.map((note) => note.uuid || note.slug)} strategy={verticalListSortingStrategy} > {viewMode === "card" && ( @@ -231,7 +231,7 @@ export const NotesHome = ({ > {pinned.map((note) => (
{pinned.map((note) => ( {pinned.map((note) => ( {filteredRecent.map((note) => (
{filteredRecent.map((note) => ( {filteredRecent.map((note) => ( { const t = useTranslations(); @@ -127,7 +125,7 @@ export const TagsHome = ({ note.category || "Uncategorized", ); const sharedItem = userSharedItems?.notes?.find( - (item) => item.id === note.id && item.category === encodedCategory, + (item) => item.id === note.slug && item.category === encodedCategory, ); return sharedItem?.sharer; }; @@ -137,25 +135,35 @@ export const TagsHome = ({ list.category || "Uncategorized", ); const sharedItem = userSharedItems?.checklists?.find( - (item) => item.id === list.id && item.category === encodedCategory, + (item) => item.id === list.slug && item.category === encodedCategory, ); return sharedItem?.sharer; }; const handleSelectNote = (note: Note) => { - const categoryPath = buildCategoryPath( - note.category || "Uncategorized", - note.id, + const userSegment = encodeURIComponent( + note.owner || user?.username || "unknown", + ); + const uuidSegment = encodeURIComponent( + note.pending ? note.slug || "" : note.uuid || note.slug || "", ); - router.push(`/note/${categoryPath}`); + const categoryQuery = note.pending + ? `?c=${encodeCategoryPath(note.category || "Uncategorized")}` + : ""; + router.push(`/note/${userSegment}/${uuidSegment}${categoryQuery}`); }; const handleSelectChecklist = (list: Checklist) => { - const categoryPath = buildCategoryPath( - list.category || "Uncategorized", - list.id, + const userSegment = encodeURIComponent( + list.owner || user?.username || "unknown", + ); + const uuidSegment = encodeURIComponent( + list.pending ? list.slug || "" : list.uuid || list.slug || "", ); - router.push(`/checklist/${categoryPath}`); + const categoryQuery = list.pending + ? `?c=${encodeCategoryPath(list.category || "Uncategorized")}` + : ""; + router.push(`/checklist/${userSegment}/${uuidSegment}${categoryQuery}`); }; if (combinedItems.length === 0 && !selectedFilter) { @@ -178,7 +186,7 @@ export const TagsHome = ({ if (viewMode === "card") { return (
{ const isShared = allSharedItems?.checklists.some( (sharedChecklist) => - sharedChecklist.id === checklist.id && + sharedChecklist.id === checklist.slug && sharedChecklist.category === encodedCategory ) || false; const { permissions } = usePermissions(); @@ -85,7 +85,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { useKanbanReminders({ checklist: localChecklist, - checklistId: localChecklist.uuid || localChecklist.id, + checklistId: localChecklist.uuid || localChecklist.slug, category: localChecklist.category || "Uncategorized", onUpdate: handleItemUpdate, }); @@ -131,7 +131,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { const handleUnarchive = async (itemId: string) => { const formData = new FormData(); - formData.append("listId", localChecklist.id); + formData.append("listId", localChecklist.slug); formData.append("itemId", itemId); formData.append("category", localChecklist.category || "Uncategorized"); @@ -192,7 +192,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { title={column.title} items={items} status={column.status} - checklistId={localChecklist.id} + checklistId={localChecklist.slug} category={localChecklist.category || "Uncategorized"} onUpdate={handleItemUpdate} isShared={isShared} @@ -376,7 +376,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { checklist={localChecklist} item={activeItem} isDragging - checklistId={localChecklist.id} + checklistId={localChecklist.slug} category={localChecklist.category || "Uncategorized"} onUpdate={refreshChecklist} isShared={isShared} @@ -404,7 +404,7 @@ export const Kanban = ({ checklist, onUpdate }: KanbanBoardProps) => { isOpen={!!calendarSelectedItem} onClose={() => setCalendarSelectedItem(null)} onUpdate={handleItemUpdate} - checklistId={localChecklist.id} + checklistId={localChecklist.slug} category={localChecklist.category || "Uncategorized"} /> )} diff --git a/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx b/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx index b7c10cf0..7e69e283 100644 --- a/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx +++ b/app/_components/FeatureComponents/Kanban/KanbanCardDetail.tsx @@ -108,7 +108,7 @@ export const KanbanCardDetail = ({ useEffect(() => { if (!isOpen) return; const _loadUsers = async () => { - const sharedWithUsers = await getUsersWithAccess(checklistId, checklist.uuid); + const sharedWithUsers = await getUsersWithAccess(checklist.uuid); if (sharedWithUsers.length === 0) { setBoardIsShared(false); return; diff --git a/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx b/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx index 8b9d8dd1..c6d05cf9 100644 --- a/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx +++ b/app/_components/FeatureComponents/Kanban/KanbanPageClient.tsx @@ -55,8 +55,8 @@ export const KanbanPageClient = ({ if (taskFilter === "pinned") { const pinnedPaths = user?.pinnedLists || []; filtered = filtered.filter((list) => { - const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.id}`; - const idPath = `${list.category || "Uncategorized"}/${list.id}`; + const uuidPath = `${list.category || "Uncategorized"}/${list.uuid || list.slug}`; + const idPath = `${list.category || "Uncategorized"}/${list.slug}`; return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); }); } else if (taskFilter === "completed") { @@ -175,13 +175,13 @@ export const KanbanPageClient = ({ } const renderList = (list: Checklist) => { - const categoryPath = `${list.category || "Uncategorized"}/${list.id}`; + const categoryPath = `${list.category || "Uncategorized"}/${list.slug}`; const isPinned = user?.pinnedLists?.includes(categoryPath); if (viewMode === 'list') { return ( router.push(`/checklist/${categoryPath}`)} isPinned={isPinned} @@ -193,7 +193,7 @@ export const KanbanPageClient = ({ if (viewMode === 'grid') { return ( router.push(`/checklist/${categoryPath}`)} isPinned={isPinned} @@ -204,7 +204,7 @@ export const KanbanPageClient = ({ return ( router.push(`/checklist/${categoryPath}`)} isPinned={isPinned} diff --git a/app/_components/FeatureComponents/Migration/Parts/YamlMetadataMigrationView.tsx b/app/_components/FeatureComponents/Migration/Parts/YamlMetadataMigrationView.tsx index 49689a8b..b8e8ddb1 100644 --- a/app/_components/FeatureComponents/Migration/Parts/YamlMetadataMigrationView.tsx +++ b/app/_components/FeatureComponents/Migration/Parts/YamlMetadataMigrationView.tsx @@ -86,7 +86,9 @@ export const YamlMetadataMigrationView = ({ diff --git a/app/_components/FeatureComponents/Notes/NoteClient.tsx b/app/_components/FeatureComponents/Notes/NoteClient.tsx index 61bfd73f..31f73fa7 100644 --- a/app/_components/FeatureComponents/Notes/NoteClient.tsx +++ b/app/_components/FeatureComponents/Notes/NoteClient.tsx @@ -10,7 +10,7 @@ import { useShortcut } from "@/app/_providers/ShortcutsProvider"; import { useShortcuts } from "@/app/_hooks/useShortcuts"; import { useNoteEditor } from "@/app/_hooks/useNoteEditor"; import { useAppMode } from "@/app/_providers/AppModeProvider"; -import { buildCategoryPath } from "@/app/_utils/global-utils"; +import { encodeCategoryPath } from "@/app/_utils/global-utils"; import { CloneCategoryModal } from "@/app/_components/GlobalComponents/Modals/ConfirmationModals/CloneCategoryModal"; import { SwipeNavigationWrapper } from "@/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper"; @@ -27,16 +27,16 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { const { user } = useAppMode(); const [localNote, setLocalNote] = useState(note); const [showCloneModal, setShowCloneModal] = useState(false); - const prevNoteId = useRef(note.id); + const prevNoteId = useRef(note.slug); const prevUpdatedAt = useRef(note.updatedAt); useEffect(() => { if ( - note.id !== prevNoteId.current || + note.slug !== prevNoteId.current || note.updatedAt !== prevUpdatedAt.current ) { setLocalNote(note); - prevNoteId.current = note.id; + prevNoteId.current = note.slug; prevUpdatedAt.current = note.updatedAt; } }, [note]); @@ -57,7 +57,7 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { const handleCloneConfirm = async (targetCategory: string) => { const formData = new FormData(); - formData.append("id", localNote.id); + formData.append("id", localNote.slug); formData.append("uuid", localNote.uuid || ""); formData.append("originalCategory", localNote.category || "Uncategorized"); formData.append("category", targetCategory || "Uncategorized"); @@ -69,12 +69,18 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { const result = await cloneNote(formData); if (result.success && result.data) { - router.push( - `/note/${buildCategoryPath( - result.data.category || "Uncategorized", - result.data.id, - )}`, + const userSegment = encodeURIComponent( + result.data.owner || user?.username || "unknown", ); + const uuidSegment = encodeURIComponent( + result.data.pending + ? result.data.slug || "" + : result.data.uuid || result.data.slug || "", + ); + const categoryQuery = result.data.pending + ? `?c=${encodeCategoryPath(result.data.category || "Uncategorized")}` + : ""; + router.push(`/note/${userSegment}/${uuidSegment}${categoryQuery}`); router.refresh(); } }; @@ -117,7 +123,7 @@ export const NoteClient = ({ note, categories }: NoteClientProps) => { isEditorInEditMode={viewModel.isEditing} > diff --git a/app/_components/FeatureComponents/Notes/NotesPageClient.tsx b/app/_components/FeatureComponents/Notes/NotesPageClient.tsx index d1641a47..ce3346ef 100644 --- a/app/_components/FeatureComponents/Notes/NotesPageClient.tsx +++ b/app/_components/FeatureComponents/Notes/NotesPageClient.tsx @@ -49,8 +49,8 @@ export const NotesPageClient = ({ if (noteFilter === "pinned") { const pinnedPaths = user?.pinnedNotes || []; filtered = filtered.filter((note) => { - const uuidPath = `${note.category || "Uncategorized"}/${note.uuid || note.id}`; - const idPath = `${note.category || "Uncategorized"}/${note.id}`; + const uuidPath = `${note.category || "Uncategorized"}/${note.uuid || note.slug}`; + const idPath = `${note.category || "Uncategorized"}/${note.slug}`; return pinnedPaths.includes(uuidPath) || pinnedPaths.includes(idPath); }); } else if (noteFilter === "recent") { @@ -102,12 +102,12 @@ export const NotesPageClient = ({ }, [currentPage, totalPages, totalItems, goToPage, handleItemsPerPageChange, setPaginationInfo]); const handleTogglePin = async (note: Note) => { - if (!user || isTogglingPin === note.id) return; + if (!user || isTogglingPin === note.slug) return; - setIsTogglingPin(note.id); + setIsTogglingPin(note.slug); try { const result = await togglePin( - note.id, + note.slug, note.category || "Uncategorized", ItemTypes.NOTE ); @@ -164,15 +164,15 @@ export const NotesPageClient = ({ columnClassName="pl-4 bg-clip-padding" > {paginatedItems.map((note) => ( -
+
{ - const categoryPath = `${note.category || "Uncategorized"}/${note.id}`; + const categoryPath = `${note.category || "Uncategorized"}/${note.uuid}`; router.push(`/note/${categoryPath}`); }} isPinned={user?.pinnedNotes?.includes( - `${note.category || "Uncategorized"}/${note.id}` + `${note.category || "Uncategorized"}/${note.uuid}` )} onTogglePin={() => handleTogglePin(note)} /> @@ -185,14 +185,14 @@ export const NotesPageClient = ({
{paginatedItems.map((note) => ( { - const categoryPath = `${note.category || "Uncategorized"}/${note.id}`; + const categoryPath = `${note.category || "Uncategorized"}/${note.uuid}`; router.push(`/note/${categoryPath}`); }} isPinned={user?.pinnedNotes?.includes( - `${note.category || "Uncategorized"}/${note.id}` + `${note.category || "Uncategorized"}/${note.uuid}` )} onTogglePin={() => handleTogglePin(note)} /> @@ -204,14 +204,14 @@ export const NotesPageClient = ({
{paginatedItems.map((note) => ( { - const categoryPath = `${note.category || "Uncategorized"}/${note.id}`; + const categoryPath = `${note.category || "Uncategorized"}/${note.uuid}`; router.push(`/note/${categoryPath}`); }} isPinned={user?.pinnedNotes?.includes( - `${note.category || "Uncategorized"}/${note.id}` + `${note.category || "Uncategorized"}/${note.uuid}` )} onTogglePin={() => handleTogglePin(note)} /> diff --git a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx index 1609ce6c..d1bd79a4 100644 --- a/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/NoteEditor/NoteEditorHeader.tsx @@ -116,7 +116,7 @@ export const NoteEditorHeader = ({ useEffect(() => { setHasPromptedForDecryption(false); - }, [note?.id]); + }, [note?.slug]); useEffect(() => { if (onOpenDecryptModal) { @@ -166,7 +166,7 @@ export const NoteEditorHeader = ({ const success = await copyTextToClipboard( `${note?.uuid ? note?.uuid - : `${encodeCategoryPath(note?.category || "Uncategorized")}/${note?.id + : `${encodeCategoryPath(note?.category || "Uncategorized")}/${note?.slug }` }` ); @@ -206,7 +206,7 @@ export const NoteEditorHeader = ({ const handlePermanentDecryption = async (newContent: string) => { const formData = new FormData(); - formData.append("id", note.id); + formData.append("slug", note.slug); formData.append("title", title); formData.append("content", newContent); formData.append("category", category); @@ -218,11 +218,7 @@ export const NoteEditorHeader = ({ const result = await updateNote(formData); if (result.success && result.data) { - const categoryPath = buildCategoryPath( - result.data.category || t("notes.uncategorized"), - result.data.id - ); - const newPath = `/note/${categoryPath}`; + const newPath = `/note/${note.owner}/${result.data.uuid || note.uuid}`; const currentPath = window.location.pathname; if (newPath === currentPath) { @@ -235,7 +231,7 @@ export const NoteEditorHeader = ({ const handleEncryptionSuccess = async (newContent: string) => { const formData = new FormData(); - formData.append("id", note.id); + formData.append("slug", note.slug); formData.append("title", title); formData.append("content", newContent); formData.append("category", category); @@ -247,11 +243,7 @@ export const NoteEditorHeader = ({ const result = await updateNote(formData); if (result.success && result.data) { - const categoryPath = buildCategoryPath( - result.data.category || t("notes.uncategorized"), - result.data.id - ); - const newPath = `/note/${categoryPath}`; + const newPath = `/note/${note.owner}/${result.data.uuid || note.uuid}`; const currentPath = window.location.pathname; if (newPath === currentPath) { @@ -266,7 +258,7 @@ export const NoteEditorHeader = ({ const encodedCategory = encodeCategoryPath(metadata.category); const itemDetails = sharingInfo( globalSharing, - metadata.uuid || metadata.id, + metadata.uuid || "", encodedCategory ); const isShared = itemDetails.exists && itemDetails.sharedWith.length > 0; @@ -321,7 +313,7 @@ export const NoteEditorHeader = ({ ? note?.uuid : `${encodeCategoryPath( note?.category || t("notes.uncategorized") - )}/${note?.id}` + )}/${note?.slug}` }`} > {copied ? ( @@ -736,7 +728,7 @@ export const NoteEditorHeader = ({ isOpen={showHistoryModal} onClose={() => setShowHistoryModal(false)} noteUuid={note.uuid || ""} - noteId={note.id} + noteId={note.slug} noteCategory={note.category || "Uncategorized"} noteOwner={note.owner || ""} noteTitle={note.title} diff --git a/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx b/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx index 3262443a..ee6bb168 100644 --- a/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/SwipeNavigationWrapper.tsx @@ -16,9 +16,12 @@ interface SwipeNavigationWrapperProps { } const getNoteUrl = (note: Partial | null, embed = false): string | null => { - if (!note?.id) return null; - const base = `/note/${buildCategoryPath(note.category || "Uncategorized", note.id)}`; - return embed ? `${base}?embed=true` : base; + if (!note?.uuid && !note?.slug) return null; + const userSegment = encodeURIComponent(note.owner || "unknown"); + const uuidSegment = encodeURIComponent(note.uuid || note.slug || ""); + const categoryQuery = !note.uuid ? `&c=${encodeURIComponent(note.category || "Uncategorized")}` : ""; + const base = `/note/${userSegment}/${uuidSegment}`; + return embed ? `${base}?embed=true${categoryQuery}` : `${base}${categoryQuery.replace('&', '?')}`; }; export const SwipeNavigationWrapper = ({ diff --git a/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx b/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx index 79bf16a9..f5bea59f 100644 --- a/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx +++ b/app/_components/FeatureComponents/Notes/Parts/TipTap/CustomExtensions/InternalLinkComponent.tsx @@ -46,12 +46,16 @@ const _returnNote = async (uuid: string, router: any, note?: Note) => { const finalNote = note || (await getNoteById(uuid)); if (finalNote) { - router.push( - `/note/${buildCategoryPath( - finalNote.category || "Uncategorized", - finalNote.id, - )}`, + const userSegment = encodeURIComponent(finalNote.owner || "unknown"); + const uuidSegment = encodeURIComponent( + finalNote.pending + ? finalNote.slug || "" + : finalNote.uuid || finalNote.slug || "", ); + const categoryQuery = finalNote.pending + ? `?c=${encodeCategoryPath(finalNote.category || "Uncategorized")}` + : ""; + router.push(`/note/${userSegment}/${uuidSegment}${categoryQuery}`); return; } @@ -66,12 +70,16 @@ const _returnChecklist = async ( const finalChecklist = checklist || (await getListById(uuid)); if (finalChecklist) { - router.push( - `/checklist/${buildCategoryPath( - finalChecklist.category || "Uncategorized", - finalChecklist.id, - )}`, + const userSegment = encodeURIComponent(finalChecklist.owner || "unknown"); + const uuidSegment = encodeURIComponent( + finalChecklist.pending + ? finalChecklist.slug || "" + : finalChecklist.uuid || finalChecklist.slug || "", ); + const categoryQuery = finalChecklist.pending + ? `?c=${encodeCategoryPath(finalChecklist.category || "Uncategorized")}` + : ""; + router.push(`/checklist/${userSegment}/${uuidSegment}${categoryQuery}`); return; } return undefined; @@ -147,16 +155,22 @@ export const InternalLinkComponent = ({ if (href.startsWith("/jotty/")) { const uuidFromPath = href.replace("/jotty/", ""); - if (fullItem && fullItem.id) { + if (fullItem && fullItem.uuid) { + const itemType = + fullItem && "type" in fullItem && fullItem.type + ? ItemTypes.CHECKLIST + : ItemTypes.NOTE; + const userSegment = encodeURIComponent(fullItem.owner || "unknown"); + const uuidSegment = encodeURIComponent( + fullItem.pending + ? fullItem.slug || "" + : fullItem.uuid || fullItem.slug || "", + ); + const categoryQuery = fullItem.pending + ? `?c=${encodeCategoryPath(fullItem.category || "Uncategorized")}` + : ""; router.push( - `/${ - fullItem && "type" in fullItem && fullItem.type - ? ItemTypes.CHECKLIST - : ItemTypes.NOTE - }/${buildCategoryPath( - fullItem.category || "Uncategorized", - fullItem.id, - )}`, + `/${itemType}/${userSegment}/${uuidSegment}${categoryQuery}`, ); return; } @@ -184,14 +198,14 @@ export const InternalLinkComponent = ({ e.stopPropagation(); if (isJottyLink) { - if (fullItem && fullItem.id) { + if (fullItem && fullItem.slug) { const pathPrefix = fullItem && "type" in fullItem && fullItem.type ? "/checklist/" : "/note/"; const newHref = `${pathPrefix}${buildCategoryPath( fullItem.category || "Uncategorized", - fullItem.id, + fullItem.uuid || "", )}`; updateAttributes({ href: newHref, @@ -200,7 +214,7 @@ export const InternalLinkComponent = ({ ? "checklist" : "note", category: fullItem.category || "Uncategorized", - itemId: fullItem.id, + itemId: fullItem.slug, convertToBidirectional: false, }); } else if (itemId && category) { @@ -223,13 +237,13 @@ export const InternalLinkComponent = ({ const foundItem = notes.find( (n) => - encodeId(n.id || "") === encodeId(itemId) && + encodeId(n.slug || "") === encodeId(itemId) && encodeCategoryPath(n?.category || "") === encodeCategoryPath(category), ) || checklists.find( (c) => - encodeId(c.id || "") === encodeId(itemId) && + encodeId(c.slug || "") === encodeId(itemId) && encodeCategoryPath(c?.category || "") === encodeCategoryPath(category), ); diff --git a/app/_components/FeatureComponents/Profile/Parts/LinksTab.tsx b/app/_components/FeatureComponents/Profile/Parts/LinksTab.tsx index 3ff3a75f..17d00b68 100644 --- a/app/_components/FeatureComponents/Profile/Parts/LinksTab.tsx +++ b/app/_components/FeatureComponents/Profile/Parts/LinksTab.tsx @@ -36,7 +36,7 @@ const getLabel = ( const fullItem = (notes.find((n) => n.uuid === node.data.id) as Note | undefined) || (checklists.find((c) => c.uuid === node.data.id) as Checklist | undefined); - return `${fullItem?.id}.md`; + return `${fullItem?.slug}.md`; }; const CustomNode = ({ node, onHover, onLeave, notes, checklists }: any) => { diff --git a/app/_components/FeatureComponents/Sidebar/Parts/CategoryList.tsx b/app/_components/FeatureComponents/Sidebar/Parts/CategoryList.tsx index ad66b5aa..a901bae0 100644 --- a/app/_components/FeatureComponents/Sidebar/Parts/CategoryList.tsx +++ b/app/_components/FeatureComponents/Sidebar/Parts/CategoryList.tsx @@ -117,15 +117,7 @@ export const CategoryList = (props: CategoryListProps) => { return; } - let currentItemPath: string | null = null; - if (activeNode.type === "item") { - const routePrefix = mode === Modes.CHECKLISTS ? "/checklist" : "/note"; - const currentCategoryPath = buildCategoryPath( - activeNode.category, - activeNode.id, - ); - currentItemPath = `${routePrefix}/${currentCategoryPath}`; - } + const formData = new FormData(); formData.append("mode", mode); @@ -152,70 +144,7 @@ export const CategoryList = (props: CategoryListProps) => { await moveNode(formData); - if ( - activeNode.type === "item" && - currentItemPath && - pathname === currentItemPath - ) { - let newCategory = ""; - if (overNode.type === "category") { - newCategory = overNode.categoryPath; - } else if (overNode.type === "drop-indicator") { - newCategory = overNode.parentPath || "Uncategorized"; - } - - if (newCategory === "") { - newCategory = "Uncategorized"; - } - - const routePrefix = mode === Modes.CHECKLISTS ? "/checklist" : "/note"; - const newItemPath = `${routePrefix}/${buildCategoryPath( - newCategory, - activeNode.id, - )}`; - - router.push(newItemPath); - } else if (activeNode.type === "category" && pathname) { - const routePrefix = mode === Modes.CHECKLISTS ? "/checklist" : "/note"; - const oldCategoryPath = activeNode.categoryPath; - const categoryName = activeNode.categoryPath.split("/").pop() || ""; - - let newCategoryPath = ""; - if (overNode.type === "category") { - newCategoryPath = `${overNode.categoryPath}/${categoryName}`; - } else if (overNode.type === "drop-indicator") { - const parentPath = overNode.parentPath || ""; - newCategoryPath = - parentPath === "" ? categoryName : `${parentPath}/${categoryName}`; - } - - const oldCategoryUrl = `${routePrefix}/${encodeCategoryPath(oldCategoryPath)}/`; - const pathnameParts = pathname.split("/"); - - let itemPart = ""; - let matched = false; - - if (pathname.startsWith(oldCategoryUrl) && pathnameParts.length > 3) { - const categoryPathParts = - encodeCategoryPath(oldCategoryPath).split("/"); - const startIndex = - routePrefix.split("/").length + categoryPathParts.length; - itemPart = pathnameParts.slice(startIndex).join("/"); - matched = true; - } - - if (matched) { - const newPath = `${routePrefix}/${buildCategoryPath( - newCategoryPath, - decodeURIComponent(itemPart), - )}`; - router.push(newPath); - } else { - router.refresh(); - } - } else { - router.refresh(); - } + router.refresh(); }; return ( diff --git a/app/_components/FeatureComponents/Sidebar/Parts/CategoryRenderer.tsx b/app/_components/FeatureComponents/Sidebar/Parts/CategoryRenderer.tsx index ff0719de..5bb1b757 100644 --- a/app/_components/FeatureComponents/Sidebar/Parts/CategoryRenderer.tsx +++ b/app/_components/FeatureComponents/Sidebar/Parts/CategoryRenderer.tsx @@ -116,7 +116,7 @@ export const CategoryRenderer = (props: CategoryRendererProps) => { const firstChildId = subCategories[0] ? `category::${subCategories[0].path}` : categoryItems[0] - ? `item::${categoryItems[0].category || "Uncategorized"}::${categoryItems[0].id + ? `item::${categoryItems[0].category || "Uncategorized"}::${categoryItems[0].slug }` : undefined; @@ -242,13 +242,13 @@ export const CategoryRenderer = (props: CategoryRendererProps) => { ))} {categoryItems.map((item) => ( -
+
{ /> { - const sharer = item.sharer || "Unknown"; - if (!acc[sharer]) { - acc[sharer] = []; - } - if (item.id) { - acc[sharer].push(item as SharedItemEntry); - } - return acc; - }, {} as Record); + const groupedBySharer = modeItems.reduce( + (acc, item) => { + const sharer = item.sharer || "Unknown"; + if (!acc[sharer]) { + acc[sharer] = []; + } + if (item.id) { + acc[sharer].push(item as SharedItemEntry); + } + return acc; + }, + {} as Record, + ); const toggleUserCollapsed = (sharer: string) => { setCollapsedUsers((prev) => { @@ -82,7 +85,16 @@ export const SharedItemsList = ({ }; const getItemHref = (item: Checklist | Note) => { - return `/${mode === Modes.NOTES ? ItemTypes.NOTE : ItemTypes.CHECKLIST}/${buildCategoryPath(item.category || 'Uncategorized', item.id)}`; + const userSegment = encodeURIComponent( + item.owner || "unknown", + ).toLowerCase(); + const uuidSegment = encodeURIComponent( + item.pending ? item.slug : item.uuid || item.slug, + ).toLowerCase(); + const categoryQuery = item.pending + ? `?c=${encodeCategoryPath(item.category || "Uncategorized")}` + : ""; + return `/${mode === Modes.NOTES ? ItemTypes.NOTE : ItemTypes.CHECKLIST}/${userSegment}/${uuidSegment}${categoryQuery}`; }; const handleItemClick = (e: React.MouseEvent, item: Checklist | Note) => { @@ -104,7 +116,7 @@ export const SharedItemsList = ({ onClick={onToggleCollapsed} className={cn( "flex items-center gap-2 py-2 pr-2 text-md lg:text-sm rounded-jotty transition-colors w-full text-left", - "hover:bg-muted/50 cursor-pointer" + "hover:bg-muted/50 cursor-pointer", )} > {collapsed ? ( @@ -133,7 +145,7 @@ export const SharedItemsList = ({ onClick={() => toggleUserCollapsed(sharer)} className={cn( "flex items-center gap-2 py-2 pr-2 text-md lg:text-sm rounded-jotty transition-colors w-full text-left", - "hover:bg-muted/50 cursor-pointer" + "hover:bg-muted/50 cursor-pointer", )} > {isUserCollapsed ? ( @@ -155,11 +167,13 @@ export const SharedItemsList = ({ {sharerItems.map((sharedItem) => { const fullItem = fullItems.find( (item) => - (item.uuid === sharedItem.uuid || item.id === sharedItem.id) && - item.isShared + (item.uuid === sharedItem.uuid || + item.slug === sharedItem.id) && + item.isShared, ) as (Checklist | Note) | undefined; - if (!fullItem || !fullItem.id || !fullItem.title) return null; + if (!fullItem || !fullItem.slug || !fullItem.title) + return null; const isSelected = isItemSelected(fullItem); @@ -174,7 +188,7 @@ export const SharedItemsList = ({ "flex items-center gap-2 py-2 px-3 text-md lg:text-sm rounded-jotty transition-colors w-full text-left", isSelected ? "bg-primary/60 text-primary-foreground" - : "hover:bg-muted/50 text-foreground" + : "hover:bg-muted/50 text-foreground", )} > {mode === "checklists" ? ( diff --git a/app/_components/FeatureComponents/Sidebar/Parts/SidebarItem.tsx b/app/_components/FeatureComponents/Sidebar/Parts/SidebarItem.tsx index 97c13b99..17961d1a 100644 --- a/app/_components/FeatureComponents/Sidebar/Parts/SidebarItem.tsx +++ b/app/_components/FeatureComponents/Sidebar/Parts/SidebarItem.tsx @@ -17,7 +17,7 @@ import { Share08Icon, } from "hugeicons-react"; import { Button } from "@/app/_components/GlobalComponents/Buttons/Button"; -import { cn, buildCategoryPath } from "@/app/_utils/global-utils"; +import { cn } from "@/app/_utils/global-utils"; import { DropdownMenu } from "@/app/_components/GlobalComponents/Dropdowns/DropdownMenu"; import { AppMode, Checklist, Note } from "@/app/_types"; import { isKanbanType, ItemTypes, Modes } from "@/app/_types/enums"; @@ -66,7 +66,7 @@ export const SidebarItem = ({ const encodedCategory = encodeCategoryPath(item.category || "Uncategorized"); const itemDetails = sharingInfo( globalSharing, - item.uuid || item.id, + item.uuid || item.slug, encodedCategory, ); @@ -78,13 +78,22 @@ export const SidebarItem = ({ const [isTogglingPin, setIsTogglingPin] = useState(null); - const itemHref = `/${mode === Modes.NOTES ? ItemTypes.NOTE : ItemTypes.CHECKLIST}/${buildCategoryPath(item.category || "Uncategorized", item.id)}`; + const userSegment = encodeURIComponent( + item.owner || user?.username || "unknown", + ).toLowerCase(); + const uuidSegment = encodeURIComponent( + item.pending ? item.slug : item.uuid || item.slug, + ).toLowerCase(); + const categoryQuery = item.pending + ? `?c=${encodeCategoryPath(item.category || "Uncategorized")}` + : ""; + const itemHref = `/${mode === Modes.NOTES ? ItemTypes.NOTE : ItemTypes.CHECKLIST}/${userSegment}/${uuidSegment}${categoryQuery}`; const handleDeleteItem = async () => { const formData = new FormData(); if (mode === Modes.CHECKLISTS) { - formData.append("id", item.id); + formData.append("id", item.slug); formData.append("category", item.category || "Uncategorized"); if (item.uuid) formData.append("uuid", item.uuid); const result = await deleteList(formData); @@ -92,7 +101,7 @@ export const SidebarItem = ({ router.refresh(); } } else { - formData.append("id", item.id); + formData.append("id", item.slug); formData.append("category", item.category || "Uncategorized"); if (item.uuid) formData.append("uuid", item.uuid); const result = await deleteNote(formData); @@ -106,10 +115,10 @@ export const SidebarItem = ({ const handleTogglePin = async () => { if (!user || isTogglingPin) return; - setIsTogglingPin(item.id); + setIsTogglingPin(item.slug); try { const result = await togglePin( - item.uuid || item.id, + item.uuid || item.slug, item.category || "Uncategorized", mode === Modes.CHECKLISTS ? ItemTypes.CHECKLIST : ItemTypes.NOTE, ); @@ -130,7 +139,7 @@ export const SidebarItem = ({ if (!pinnedItems) return false; const itemPath = `${item.category || "Uncategorized"}/${ - item.uuid || item.id + item.uuid || item.slug }`; return pinnedItems.includes(itemPath); }; @@ -163,7 +172,7 @@ export const SidebarItem = ({ ) : ( ), - disabled: isTogglingPin === item.id, + disabled: isTogglingPin === item.slug, }, ...(item.category !== ARCHIVED_DIR_NAME ? [ @@ -293,7 +302,7 @@ export const SidebarItem = ({ {showShareModal && ( { const handleNoteClick = (e: React.MouseEvent, note: Note) => { e.preventDefault(); e.stopPropagation(); - router.push( - `/note/${buildCategoryPath(note.category || "Uncategorized", note.id)}`, + const userSegment = encodeURIComponent(note.owner || "unknown"); + const uuidSegment = encodeURIComponent( + note.pending ? note.slug : note.uuid || note.slug, ); + const categoryQuery = note.pending + ? `?c=${encodeCategoryPath(note.category || "Uncategorized")}` + : ""; + router.push(`/note/${userSegment}/${uuidSegment}${categoryQuery}`); }; const handleChecklistClick = ( @@ -32,9 +40,14 @@ export const TagHoverCard = ({ notes, checklists }: TagHoverCardProps) => { ) => { e.preventDefault(); e.stopPropagation(); - router.push( - `/checklist/${buildCategoryPath(list.category || "Uncategorized", list.id!)}`, + const userSegment = encodeURIComponent(list.owner || "unknown"); + const uuidSegment = encodeURIComponent( + list.pending ? list.slug || "" : list.uuid || list.slug || "", ); + const categoryQuery = list.pending + ? `?c=${encodeCategoryPath(list.category || "Uncategorized")}` + : ""; + router.push(`/checklist/${userSegment}/${uuidSegment}${categoryQuery}`); }; const showSectionLabels = notes.length > 0 && checklists.length > 0; @@ -76,7 +89,7 @@ export const TagHoverCard = ({ notes, checklists }: TagHoverCardProps) => { )} {checklists.map((list) => ( + > + {t("common.cancel")} + + > + {t("common.cancel")} +