From b45edac5484b4c608490108bc57e9d6519795404 Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Wed, 22 Jan 2025 12:25:49 -0500 Subject: [PATCH 1/5] Persist link display settings to workspace --- apps/web/lib/swr/use-workspace-store.ts | 1 + apps/web/ui/links/link-display.tsx | 11 +- apps/web/ui/links/links-display-provider.tsx | 169 ++++++++----------- 3 files changed, 84 insertions(+), 97 deletions(-) diff --git a/apps/web/lib/swr/use-workspace-store.ts b/apps/web/lib/swr/use-workspace-store.ts index fa3838d24a..79342cf7d2 100644 --- a/apps/web/lib/swr/use-workspace-store.ts +++ b/apps/web/lib/swr/use-workspace-store.ts @@ -8,6 +8,7 @@ import useWorkspace from "./use-workspace"; export const STORE_KEYS = { conversionsOnboarding: "conversionsOnboarding", + linksDisplay: "linksDisplay", }; export function useWorkspaceStore( diff --git a/apps/web/ui/links/link-display.tsx b/apps/web/ui/links/link-display.tsx index eff4b5a888..b41ebec1eb 100644 --- a/apps/web/ui/links/link-display.tsx +++ b/apps/web/ui/links/link-display.tsx @@ -39,7 +39,7 @@ export default function LinkDisplay() { const [openPopover, setOpenPopover] = useState(false); const { queryParams } = useRouterStuff(); - useKeyboardShortcut("a", () => setShowArchived((o) => !o)); + useKeyboardShortcut("a", () => setShowArchived(!showArchived)); return ( { + if ( + window.confirm( + "Set this configuration as the default for everyone in this workspace?", + ) + ) + persist(); + }} /> diff --git a/apps/web/ui/links/links-display-provider.tsx b/apps/web/ui/links/links-display-provider.tsx index 4d7cdf7736..78c34c4fe1 100644 --- a/apps/web/ui/links/links-display-provider.tsx +++ b/apps/web/ui/links/links-display-provider.tsx @@ -1,13 +1,6 @@ +import { STORE_KEYS, useWorkspaceStore } from "@/lib/swr/use-workspace-store"; import { useLocalStorage } from "@dub/ui"; -import { useSearchParams } from "next/navigation"; -import { - Dispatch, - PropsWithChildren, - SetStateAction, - createContext, - useMemo, - useState, -} from "react"; +import { PropsWithChildren, createContext, useMemo } from "react"; export const linkViewModes = ["cards", "rows"] as const; @@ -65,6 +58,9 @@ export const linkDisplayProperties: { export type LinkDisplayProperty = (typeof linkDisplayPropertyIds)[number]; +const defaultViewMode = linkViewModes[0]; +const defaultSortBy = sortOptions[0].slug; +const defaultShowArchived = false; export const defaultDisplayProperties: LinkDisplayProperty[] = [ "icon", "link", @@ -75,37 +71,38 @@ export const defaultDisplayProperties: LinkDisplayProperty[] = [ "analytics", ]; +type PersistedLinksDisplay = { + viewMode: LinksViewMode; + sortBy: LinksSortSlug; + showArchived: boolean; + displayProperties: LinkDisplayProperty[]; +}; + function useLinksDisplayOption( key: string, - parsePersisted: (value: T) => T, defaultValue: T, - overrideValue?: T, + parseValue: (value: any) => T, ) { - const [valuePersisted, setValuePersisted] = useLocalStorage( + const [value, setValue] = useLocalStorage( `links-display-${key}`, defaultValue, ); - const [value, setValue] = useState(overrideValue ?? valuePersisted); return { - value, + value: parseValue(value), setValue, - valuePersisted, - setValuePersisted, - persist: () => setValuePersisted(value), - reset: () => setValue(parsePersisted(valuePersisted)), }; } export const LinksDisplayContext = createContext<{ viewMode: LinksViewMode; - setViewMode: Dispatch>; + setViewMode: (mode: LinksViewMode) => void; displayProperties: LinkDisplayProperty[]; - setDisplayProperties: Dispatch>; + setDisplayProperties: (properties: LinkDisplayProperty[]) => void; sortBy: LinksSortSlug; - setSort: Dispatch>; + setSort: (sort: LinksSortSlug) => void; showArchived: boolean; - setShowArchived: Dispatch>; + setShowArchived: (show: boolean) => void; isDirty: boolean; persist: () => void; reset: () => void; @@ -134,77 +131,64 @@ const parseDisplayProperties = (displayPropertiesRaw: string[]) => (p) => displayPropertiesRaw.findIndex((pr) => pr === p) !== -1, ); -const parseSort = (sort: string) => - sortOptions.find(({ slug }) => slug === sort)?.slug ?? sortOptions[0].slug; +const parseSortBy = (sortBy: string) => + sortOptions.find(({ slug }) => slug === sortBy)?.slug ?? sortOptions[0].slug; const parseShowArchived = (showArchived: boolean) => showArchived === true; +const parseObject = (object: any): PersistedLinksDisplay | undefined => + object + ? { + viewMode: parseViewMode(object.viewMode), + sortBy: parseSortBy(object.sortBy), + showArchived: parseShowArchived(object.showArchived), + displayProperties: parseDisplayProperties(object.displayProperties), + } + : undefined; + export function LinksDisplayProvider({ children }: PropsWithChildren) { - const searchParams = useSearchParams(); - const sortRaw = searchParams?.get("sortBy"); - const showArchivedRaw = searchParams?.get("showArchived"); + // Persisted values to workspace store + const [persistedRaw, setPersisted] = useWorkspaceStore( + STORE_KEYS.linksDisplay, + ); + const persisted = useMemo(() => parseObject(persistedRaw), [persistedRaw]); // View mode - const { - value: viewMode, - setValue: setViewMode, - valuePersisted: viewModePersisted, - persist: persistViewMode, - reset: resetViewMode, - } = useLinksDisplayOption( - "view-mode", - parseViewMode, - linkViewModes[0], - ); + const { value: viewMode, setValue: setViewMode } = + useLinksDisplayOption( + "view-mode", + defaultViewMode, + parseViewMode, + ); // Sort - const { - value: sortBy, - setValue: setSort, - valuePersisted: sortPersisted, - persist: persistSort, - reset: resetSort, - } = useLinksDisplayOption( - "sortBy", - parseSort, - sortOptions[0].slug, - sortRaw ? parseSort(sortRaw) : undefined, - ); + const { value: sortBy, setValue: setSort } = + useLinksDisplayOption("sortBy", defaultSortBy, parseSortBy); // Show archived - const { - value: showArchived, - setValue: setShowArchived, - valuePersisted: showArchivedPersisted, - persist: persistShowArchived, - reset: resetShowArchived, - } = useLinksDisplayOption( - "show-archived", - parseShowArchived, - false, - showArchivedRaw ? showArchivedRaw === "true" : undefined, - ); + const { value: showArchived, setValue: setShowArchived } = + useLinksDisplayOption( + "show-archived", + defaultShowArchived, + parseShowArchived, + ); // Display properties - const { - value: displayProperties, - setValue: setDisplayProperties, - valuePersisted: displayPropertiesPersisted, - persist: persistDisplayProperties, - reset: resetDisplayProperties, - } = useLinksDisplayOption( - "display-properties", - parseDisplayProperties, - defaultDisplayProperties, - ); + const { value: displayProperties, setValue: setDisplayProperties } = + useLinksDisplayOption( + "display-properties", + defaultDisplayProperties, + parseDisplayProperties, + ); const isDirty = useMemo(() => { - if (viewMode !== parseViewMode(viewModePersisted)) return true; - if (sortBy !== parseSort(sortPersisted)) return true; - if (showArchived !== parseShowArchived(showArchivedPersisted)) return true; + if (viewMode !== (persisted?.viewMode ?? defaultViewMode)) return true; + if (sortBy !== (persisted?.sortBy ?? defaultSortBy)) return true; + if (showArchived !== (persisted?.showArchived ?? defaultShowArchived)) + return true; if ( displayProperties.slice().sort().join(",") !== - parseDisplayProperties(displayPropertiesPersisted) + (persisted?.displayProperties ?? defaultDisplayProperties) .slice() .sort() .join(",") @@ -212,16 +196,7 @@ export function LinksDisplayProvider({ children }: PropsWithChildren) { return true; return false; - }, [ - viewModePersisted, - viewMode, - sortPersisted, - sortBy, - showArchivedPersisted, - showArchived, - displayPropertiesPersisted, - displayProperties, - ]); + }, [persisted, viewMode, sortBy, showArchived, displayProperties]); return ( { - persistViewMode(); - persistDisplayProperties(); - persistSort(); - persistShowArchived(); + setPersisted({ + viewMode: viewMode, + sortBy: sortBy, + showArchived: showArchived, + displayProperties: displayProperties, + }); }, reset: () => { - resetViewMode(); - resetDisplayProperties(); - resetSort(); - resetShowArchived(); + setViewMode(persisted?.viewMode ?? defaultViewMode); + setDisplayProperties( + persisted?.displayProperties ?? defaultDisplayProperties, + ); + setSort(persisted?.sortBy ?? defaultSortBy); + setShowArchived(persisted?.showArchived ?? defaultShowArchived); }, }} > From 683befd07038517efb8ef6d1a133937073150939 Mon Sep 17 00:00:00 2001 From: Tim Wilson Date: Wed, 22 Jan 2025 13:26:00 -0500 Subject: [PATCH 2/5] Improve persistence vs. local storage handling --- apps/web/ui/links/link-display.tsx | 4 +- apps/web/ui/links/links-display-provider.tsx | 82 ++++++++++++-------- packages/ui/src/hooks/use-local-storage.ts | 13 +++- 3 files changed, 64 insertions(+), 35 deletions(-) diff --git a/apps/web/ui/links/link-display.tsx b/apps/web/ui/links/link-display.tsx index b41ebec1eb..5bce8627f4 100644 --- a/apps/web/ui/links/link-display.tsx +++ b/apps/web/ui/links/link-display.tsx @@ -31,6 +31,7 @@ export default function LinkDisplay() { setShowArchived, displayProperties, setDisplayProperties, + isLoading, isDirty, persist, reset, @@ -190,13 +191,14 @@ export default function LinkDisplay() { >