"use client"; import { useState, createContext, useContext, useEffect } from "react"; import { useSearchParams } from "next/navigation"; import { Header } from "../PageHeader"; import { Footer } from "components/ActionBar/Footer"; import { Sidebar } from "components/ActionBar/Sidebar"; import { DesktopNavigation } from "components/ActionBar/DesktopNavigation"; import { MobileNavigation } from "components/ActionBar/MobileNavigation"; import { navPages, NotificationButton, } from "components/ActionBar/NavigationButtons"; import { create } from "zustand"; import { Popover } from "components/Popover"; import { Checkbox } from "components/Checkbox"; import { Separator } from "components/Layout"; import { CloseTiny } from "components/Icons/CloseTiny"; import { MediaContents } from "components/Media"; import { SortSmall } from "components/Icons/SortSmall"; import { TabsSmall } from "components/Icons/TabsSmall"; import { Input } from "components/Input"; import { SearchTiny } from "components/Icons/SearchTiny"; import { InterfaceState, useIdentityData } from "components/IdentityProvider"; import { updateIdentityInterfaceState } from "actions/updateIdentityInterfaceState"; import Link from "next/link"; import { ExternalLinkTiny } from "components/Icons/ExternalLinkTiny"; import { usePreserveScroll } from "src/hooks/usePreserveScroll"; import { Tab } from "components/Tab"; import { PubIcon, PublicationButtons } from "components/ActionBar/Publications"; export type DashboardState = { display?: "grid" | "list"; sort?: "created" | "alphabetical"; filter: { drafts: boolean; published: boolean; docs: boolean; archived: boolean; }; }; type DashboardStore = { dashboards: { [id: string]: DashboardState }; setDashboard: (id: string, partial: Partial) => void; }; const defaultDashboardState: DashboardState = { display: undefined, sort: undefined, filter: { drafts: false, published: false, docs: false, archived: false, }, }; export const useDashboardStore = create((set, get) => ({ dashboards: {}, setDashboard: (id: string, partial: Partial) => { set((state) => ({ dashboards: { ...state.dashboards, [id]: { ...(state.dashboards[id] || defaultDashboardState), ...partial, }, }, })); }, })); export const DashboardIdContext = createContext(null); export const useDashboardId = () => { const id = useContext(DashboardIdContext); if (!id) { throw new Error("useDashboardId must be used within a DashboardLayout"); } return id; }; export const useDashboardState = () => { const id = useDashboardId(); let { identity } = useIdentityData(); let localState = useDashboardStore( (state) => state.dashboards[id] || defaultDashboardState, ); if (!identity) return localState; let metadata = identity.interface_state as InterfaceState; return metadata?.dashboards?.[id] || defaultDashboardState; }; export const useSetDashboardState = () => { const id = useDashboardId(); let { identity, mutate } = useIdentityData(); const setDashboard = useDashboardStore((state) => state.setDashboard); return async (partial: Partial) => { if (!identity) return setDashboard(id, partial); let interface_state = (identity.interface_state as InterfaceState) || {}; let newDashboardState = { ...defaultDashboardState, ...interface_state.dashboards?.[id], ...partial, }; mutate( { ...identity, interface_state: { ...interface_state, dashboards: { ...interface_state.dashboards, [id]: newDashboardState, }, }, }, { revalidate: false }, ); await updateIdentityInterfaceState({ ...interface_state, dashboards: { [id]: newDashboardState, }, }); }; }; export function DashboardLayout< T extends { [name: string]: { content: React.ReactNode; controls: React.ReactNode; }; }, >(props: { id: string; tabs: T; defaultTab: keyof T; currentPage: navPages; publication?: string; profileDid?: string; actions?: React.ReactNode; pageTitle?: string; onTabHover?: (tabName: string) => void; }) { const searchParams = useSearchParams(); const tabParam = searchParams.get("tab"); // Initialize tab from search param if valid, otherwise use default const initialTab = tabParam && props.tabs[tabParam] ? tabParam : props.defaultTab; let [tab, setTab] = useState(initialTab); // Custom setter that updates both state and URL const setTabWithUrl = (newTab: keyof T) => { setTab(newTab); const params = new URLSearchParams(searchParams.toString()); params.set("tab", newTab as string); const newUrl = `${window.location.pathname}?${params.toString()}`; window.history.replaceState(null, "", newUrl); }; let { content, controls } = props.tabs[tab]; let { ref } = usePreserveScroll( `dashboard-${props.id}-${tab as string}`, ); let [headerState, setHeaderState] = useState<"default" | "controls">( "default", ); return (
{props.actions && {props.actions}}
{props.pageTitle && ( )} {Object.keys(props.tabs).length <= 1 && !controls ? null : ( <>
{headerState === "default" ? ( <> {Object.keys(props.tabs).length > 1 && (
{Object.keys(props.tabs).map((t) => { return ( setTabWithUrl(t)} onMouseEnter={() => props.onTabHover?.(t)} onPointerDown={() => props.onTabHover?.(t)} /> ); })}
)} {props.publication && ( )}
{controls}
) : ( <> {controls} )}
)} {content}
); } export const PageTitle = (props: { pageTitle: string; actions: React.ReactNode; }) => { return (

{props.pageTitle}

{props.actions}
{/*
{props.controls}
*/}
); }; export const HomeDashboardControls = (props: { searchValue: string; setSearchValueAction: (searchValue: string) => void; hasBackgroundImage: boolean; defaultDisplay: Exclude; hasPubs: boolean; hasArchived: boolean; }) => { let { display, sort } = useDashboardState(); display = display || props.defaultDisplay; let setState = useSetDashboardState(); let { identity } = useIdentityData(); return (
{identity && ( )}
{props.hasPubs ? ( <> {" "} ) : null}
); }; export const PublicationDashboardControls = (props: { searchValue: string; setSearchValueAction: (searchValue: string) => void; hasBackgroundImage: boolean; defaultDisplay: Exclude; }) => { let { display, sort } = useDashboardState(); display = display || props.defaultDisplay; let setState = useSetDashboardState(); return (
); }; const SortToggle = (props: { setState: (partial: Partial) => Promise; sort: string | undefined; }) => { return ( ); }; const DisplayToggle = (props: { setState: (partial: Partial) => Promise; display: string | undefined; }) => { return ( ); }; const FilterOptions = (props: { hasPubs: boolean; hasArchived: boolean }) => { let { filter } = useDashboardState(); let setState = useSetDashboardState(); let filterCount = Object.values(filter).filter(Boolean).length; return ( Filter {filterCount > 0 && `(${filterCount})`}} > {props.hasPubs && ( <> setState({ filter: { ...filter, drafts: !!e.target.checked }, }) } > Drafts setState({ filter: { ...filter, published: !!e.target.checked }, }) } > Published )} {props.hasArchived && ( setState({ filter: { ...filter, archived: !!e.target.checked }, }) } > Archived )} setState({ filter: { ...filter, docs: !!e.target.checked }, }) } > Docs
); }; const SearchInput = (props: { searchValue: string; setSearchValue: (searchValue: string) => void; hasBackgroundImage: boolean; }) => { return (
{ props.setSearchValue(e.currentTarget.value); }} />
); };