feat(24-02): render-first root layout, guarded write actions, public setup viewing
- Remove authLoading spinner gate — app renders immediately for all visitors - Expand isPublicRoute to include /, /global-items/*, /setups/*, /users/, /login - Replace hard window.location.href redirect with soft navigate() after auth resolves - Remove onboarding loading spinner — pass isAuthenticated as enabled to guard query - Add AuthPromptModal to root JSX for global availability - Guard Add to Collection and Add to Thread buttons with isAuthenticated check - Rework setup detail page to use usePublicSetup for anonymous visitors - Wrap all write action UI (Add Items, Delete, Public toggle, remove/classify) in isAuthenticated guards
This commit is contained in:
@@ -12,6 +12,7 @@ import { Toaster } from "sonner";
|
||||
import "../app.css";
|
||||
import { AddToCollectionModal } from "../components/AddToCollectionModal";
|
||||
import { AddToThreadModal } from "../components/AddToThreadModal";
|
||||
import { AuthPromptModal } from "../components/AuthPromptModal";
|
||||
import { CatalogSearchOverlay } from "../components/CatalogSearchOverlay";
|
||||
import { ConfirmDialog } from "../components/ConfirmDialog";
|
||||
import { ExternalLinkDialog } from "../components/ExternalLinkDialog";
|
||||
@@ -94,7 +95,7 @@ function RootLayout() {
|
||||
|
||||
// Onboarding — only check when authenticated (endpoint requires auth)
|
||||
const { data: onboardingComplete, isLoading: onboardingLoading } =
|
||||
useOnboardingComplete();
|
||||
useOnboardingComplete(isAuthenticated);
|
||||
const [wizardDismissed, setWizardDismissed] = useState(false);
|
||||
|
||||
// Don't show onboarding wizard until user has created an account
|
||||
@@ -117,40 +118,21 @@ function RootLayout() {
|
||||
|
||||
const totalsBarProps = isDashboard ? {} : { linkTo: "/" };
|
||||
|
||||
// Show loading while checking auth
|
||||
if (authLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="w-6 h-6 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Redirect unauthenticated users to login (server-side OIDC route)
|
||||
// Allow public routes through without auth
|
||||
const isPublicRoute =
|
||||
location.pathname.startsWith("/users/") || location.pathname === "/login";
|
||||
location.pathname === "/" ||
|
||||
location.pathname.startsWith("/users/") ||
|
||||
location.pathname.startsWith("/global-items") ||
|
||||
location.pathname.startsWith("/setups/") ||
|
||||
location.pathname === "/login";
|
||||
|
||||
// FAB visibility: show on all authenticated, non-public routes
|
||||
const isSetupsPage = !!matchRoute({ to: "/setups", fuzzy: true });
|
||||
const showFab = isAuthenticated && !isPublicRoute;
|
||||
|
||||
if (!isAuthenticated && !isPublicRoute) {
|
||||
window.location.href = "/login";
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<p className="text-sm text-gray-500">Redirecting to login...</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Show loading while checking onboarding status
|
||||
if (onboardingLoading) {
|
||||
return (
|
||||
<div className="min-h-screen bg-gray-50 flex items-center justify-center">
|
||||
<div className="w-6 h-6 border-2 border-gray-600 border-t-transparent rounded-full animate-spin" />
|
||||
</div>
|
||||
);
|
||||
if (!isAuthenticated && !isPublicRoute && !authLoading) {
|
||||
navigate({ to: "/login" });
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -198,6 +180,9 @@ function RootLayout() {
|
||||
<AddToThreadModal />
|
||||
<Toaster position="bottom-right" richColors />
|
||||
|
||||
{/* Auth Prompt Modal */}
|
||||
<AuthPromptModal />
|
||||
|
||||
{/* Onboarding Wizard */}
|
||||
{showWizard && (
|
||||
<OnboardingWizard onComplete={() => setWizardDismissed(true)} />
|
||||
|
||||
Reference in New Issue
Block a user