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)
This commit is contained in:
45
src/client/components/AuthPromptModal.tsx
Normal file
45
src/client/components/AuthPromptModal.tsx
Normal file
@@ -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 (
|
||||||
|
<div className="fixed inset-0 z-50 flex items-center justify-center">
|
||||||
|
<div
|
||||||
|
className="absolute inset-0 bg-black/30"
|
||||||
|
onClick={closeAuthPrompt}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === "Escape") closeAuthPrompt();
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<div className="relative bg-white rounded-xl shadow-lg p-6 max-w-sm mx-4 w-full">
|
||||||
|
<h3 className="text-lg font-semibold text-gray-900 mb-2">
|
||||||
|
Join GearBox
|
||||||
|
</h3>
|
||||||
|
<p className="text-sm text-gray-600 mb-6">
|
||||||
|
To manage your own collection, sign in or sign up.
|
||||||
|
</p>
|
||||||
|
<div className="flex flex-col gap-3">
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="w-full text-center px-4 py-2.5 text-sm font-medium text-white bg-gray-700 hover:bg-gray-800 rounded-lg transition-colors"
|
||||||
|
onClick={closeAuthPrompt}
|
||||||
|
>
|
||||||
|
Sign in
|
||||||
|
</Link>
|
||||||
|
<Link
|
||||||
|
to="/login"
|
||||||
|
className="w-full text-center px-4 py-2.5 text-sm font-medium text-gray-700 bg-gray-100 hover:bg-gray-200 rounded-lg transition-colors"
|
||||||
|
onClick={closeAuthPrompt}
|
||||||
|
>
|
||||||
|
Create account
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -32,6 +32,20 @@ export function useUpdateSetting() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useOnboardingComplete() {
|
export function useOnboardingComplete(enabled = true) {
|
||||||
return useSetting("onboardingComplete");
|
return useQuery({
|
||||||
|
queryKey: ["settings", "onboardingComplete"],
|
||||||
|
queryFn: async () => {
|
||||||
|
try {
|
||||||
|
const result = await apiGet<Setting>(
|
||||||
|
`/api/settings/onboardingComplete`,
|
||||||
|
);
|
||||||
|
return result.value;
|
||||||
|
} catch (err: any) {
|
||||||
|
if (err?.status === 404) return null;
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
enabled,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -64,6 +64,16 @@ export function useSetup(setupId: number | null) {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function usePublicSetup(setupId: number | null) {
|
||||||
|
return useQuery({
|
||||||
|
queryKey: ["setups", setupId, "public"],
|
||||||
|
queryFn: () => apiGet<SetupWithItems>(`/api/setups/${setupId}/public`),
|
||||||
|
enabled: setupId != null,
|
||||||
|
retry: (count, error) =>
|
||||||
|
error instanceof ApiError && error.status === 404 ? false : count < 3,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
export function useCreateSetup() {
|
export function useCreateSetup() {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
return useMutation({
|
return useMutation({
|
||||||
|
|||||||
@@ -79,6 +79,11 @@ interface UIState {
|
|||||||
// Session thread tracking
|
// Session thread tracking
|
||||||
catalogSessionThreadId: number | null;
|
catalogSessionThreadId: number | null;
|
||||||
setCatalogSessionThreadId: (id: number | null) => void;
|
setCatalogSessionThreadId: (id: number | null) => void;
|
||||||
|
|
||||||
|
// Auth prompt modal
|
||||||
|
showAuthPrompt: boolean;
|
||||||
|
openAuthPrompt: () => void;
|
||||||
|
closeAuthPrompt: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useUIStore = create<UIState>((set) => ({
|
export const useUIStore = create<UIState>((set) => ({
|
||||||
@@ -184,4 +189,9 @@ export const useUIStore = create<UIState>((set) => ({
|
|||||||
// Session thread tracking
|
// Session thread tracking
|
||||||
catalogSessionThreadId: null,
|
catalogSessionThreadId: null,
|
||||||
setCatalogSessionThreadId: (id) => set({ catalogSessionThreadId: id }),
|
setCatalogSessionThreadId: (id) => set({ catalogSessionThreadId: id }),
|
||||||
|
|
||||||
|
// Auth prompt modal
|
||||||
|
showAuthPrompt: false,
|
||||||
|
openAuthPrompt: () => set({ showAuthPrompt: true }),
|
||||||
|
closeAuthPrompt: () => set({ showAuthPrompt: false }),
|
||||||
}));
|
}));
|
||||||
|
|||||||
Reference in New Issue
Block a user