| 1 | import { useAuth, useClerk } from "@clerk/react"; |
| 2 | import { ServerIcon, SmartphoneIcon } from "lucide-react"; |
| 3 | import { type ReactNode, useCallback, useState } from "react"; |
| 4 | import { createPortal } from "react-dom"; |
| 5 | |
| 6 | import { MobileClientsUserProfilePage } from "./MobileClientsUserProfilePage"; |
| 7 | import { T3ConnectUserProfilePage } from "./T3ConnectUserProfilePage"; |
| 8 | |
| 9 | /** Custom pages in the Clerk account modal, in menu order. */ |
| 10 | export const T3_CONNECT_ACCOUNT_PAGES = [ |
| 11 | { |
| 12 | label: "Mobile clients", |
| 13 | url: "mobile-clients", |
| 14 | icon: <SmartphoneIcon className="size-4" />, |
| 15 | content: <MobileClientsUserProfilePage />, |
| 16 | }, |
| 17 | { |
| 18 | label: "T3 Connect", |
| 19 | url: "t3-connect", |
| 20 | icon: <ServerIcon className="size-4" />, |
| 21 | content: <T3ConnectUserProfilePage />, |
| 22 | }, |
| 23 | ] as const; |
| 24 | |
| 25 | type PortalTargets = Readonly<Record<string, HTMLDivElement | undefined>>; |
| 26 | |
| 27 | /** |
| 28 | * Opens the Clerk account modal on the T3 Connect page from outside the |
| 29 | * UserButton. Clerk mounts custom pages into DOM nodes it owns, so the caller |
| 30 | * must keep `portals` rendered for as long as the modal can be open. |
| 31 | */ |
| 32 | export function useT3ConnectAccountPage(): { |
| 33 | readonly open: (() => void) | null; |
| 34 | readonly portals: ReactNode; |
| 35 | } { |
| 36 | const clerk = useClerk(); |
| 37 | const { isSignedIn } = useAuth(); |
| 38 | const [targets, setTargets] = useState<PortalTargets>({}); |
| 39 | |
| 40 | const open = useCallback(() => { |
| 41 | const setTarget = (key: string, element: HTMLDivElement | undefined) => |
| 42 | setTargets((current) => ({ ...current, [key]: element })); |
| 43 | clerk.openUserProfile({ |
| 44 | __experimental_startPath: "/t3-connect", |
| 45 | customPages: T3_CONNECT_ACCOUNT_PAGES.map((page) => ({ |
| 46 | label: page.label, |
| 47 | url: page.url, |
| 48 | mount: (element: HTMLDivElement) => setTarget(`content:${page.url}`, element), |
| 49 | unmount: () => setTarget(`content:${page.url}`, undefined), |
| 50 | mountIcon: (element: HTMLDivElement) => setTarget(`icon:${page.url}`, element), |
| 51 | unmountIcon: () => setTarget(`icon:${page.url}`, undefined), |
| 52 | })), |
| 53 | }); |
| 54 | }, [clerk]); |
| 55 | |
| 56 | const portals = T3_CONNECT_ACCOUNT_PAGES.flatMap((page) => { |
| 57 | const content = targets[`content:${page.url}`]; |
| 58 | const icon = targets[`icon:${page.url}`]; |
| 59 | return [ |
| 60 | content ? createPortal(page.content, content, `content:${page.url}`) : null, |
| 61 | icon ? createPortal(page.icon, icon, `icon:${page.url}`) : null, |
| 62 | ]; |
| 63 | }); |
| 64 | |
| 65 | return { open: isSignedIn ? open : null, portals }; |
| 66 | } |