From cd85715d05f8294d7adf020419f8ba6a6d1a1497 Mon Sep 17 00:00:00 2001 From: Jean-Luc Makiola Date: Fri, 10 Apr 2026 10:06:59 +0200 Subject: [PATCH] feat(24-02): add auth prompt state, modal, usePublicSetup hook, guard onboarding - Extend uiStore with showAuthPrompt/openAuthPrompt/closeAuthPrompt state - Create AuthPromptModal component with sign in/sign up CTAs pointing to /login - Add usePublicSetup hook to useSetups for anonymous setup viewing via public API - Rework useOnboardingComplete to accept enabled param (guards auth-gated call) --- src/client/components/AuthPromptModal.tsx | 45 +++++++++++++++++++++++ src/client/hooks/useSettings.ts | 18 ++++++++- src/client/hooks/useSetups.ts | 10 +++++ src/client/stores/uiStore.ts | 10 +++++ 4 files changed, 81 insertions(+), 2 deletions(-) create mode 100644 src/client/components/AuthPromptModal.tsx diff --git a/src/client/components/AuthPromptModal.tsx b/src/client/components/AuthPromptModal.tsx new file mode 100644 index 0000000..d2eca84 --- /dev/null +++ b/src/client/components/AuthPromptModal.tsx @@ -0,0 +1,45 @@ +import { Link } from "@tanstack/react-router"; +import { useUIStore } from "../stores/uiStore"; + +export function AuthPromptModal() { + const showAuthPrompt = useUIStore((s) => s.showAuthPrompt); + const closeAuthPrompt = useUIStore((s) => s.closeAuthPrompt); + + if (!showAuthPrompt) return null; + + return ( +
+
{ + if (e.key === "Escape") closeAuthPrompt(); + }} + /> +
+

+ Join GearBox +

+

+ To manage your own collection, sign in or sign up. +

+
+ + Sign in + + + Create account + +
+
+
+ ); +} diff --git a/src/client/hooks/useSettings.ts b/src/client/hooks/useSettings.ts index b26f905..474029e 100644 --- a/src/client/hooks/useSettings.ts +++ b/src/client/hooks/useSettings.ts @@ -32,6 +32,20 @@ export function useUpdateSetting() { }); } -export function useOnboardingComplete() { - return useSetting("onboardingComplete"); +export function useOnboardingComplete(enabled = true) { + return useQuery({ + queryKey: ["settings", "onboardingComplete"], + queryFn: async () => { + try { + const result = await apiGet( + `/api/settings/onboardingComplete`, + ); + return result.value; + } catch (err: any) { + if (err?.status === 404) return null; + throw err; + } + }, + enabled, + }); } diff --git a/src/client/hooks/useSetups.ts b/src/client/hooks/useSetups.ts index 8e0b9da..33591e8 100644 --- a/src/client/hooks/useSetups.ts +++ b/src/client/hooks/useSetups.ts @@ -64,6 +64,16 @@ export function useSetup(setupId: number | null) { }); } +export function usePublicSetup(setupId: number | null) { + return useQuery({ + queryKey: ["setups", setupId, "public"], + queryFn: () => apiGet(`/api/setups/${setupId}/public`), + enabled: setupId != null, + retry: (count, error) => + error instanceof ApiError && error.status === 404 ? false : count < 3, + }); +} + export function useCreateSetup() { const queryClient = useQueryClient(); return useMutation({ diff --git a/src/client/stores/uiStore.ts b/src/client/stores/uiStore.ts index b325622..147922a 100644 --- a/src/client/stores/uiStore.ts +++ b/src/client/stores/uiStore.ts @@ -79,6 +79,11 @@ interface UIState { // Session thread tracking catalogSessionThreadId: number | null; setCatalogSessionThreadId: (id: number | null) => void; + + // Auth prompt modal + showAuthPrompt: boolean; + openAuthPrompt: () => void; + closeAuthPrompt: () => void; } export const useUIStore = create((set) => ({ @@ -184,4 +189,9 @@ export const useUIStore = create((set) => ({ // Session thread tracking catalogSessionThreadId: null, setCatalogSessionThreadId: (id) => set({ catalogSessionThreadId: id }), + + // Auth prompt modal + showAuthPrompt: false, + openAuthPrompt: () => set({ showAuthPrompt: true }), + closeAuthPrompt: () => set({ showAuthPrompt: false }), }));