diff --git a/src/client/routes/__root.tsx b/src/client/routes/__root.tsx
index f8c79cd..cc4554b 100644
--- a/src/client/routes/__root.tsx
+++ b/src/client/routes/__root.tsx
@@ -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 (
-
- );
- }
-
- // 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 (
-
-
Redirecting to login...
-
- );
- }
-
- // Show loading while checking onboarding status
- if (onboardingLoading) {
- return (
-
- );
+ if (!isAuthenticated && !isPublicRoute && !authLoading) {
+ navigate({ to: "/login" });
+ return null;
}
return (
@@ -198,6 +180,9 @@ function RootLayout() {
+ {/* Auth Prompt Modal */}
+
+
{/* Onboarding Wizard */}
{showWizard && (
setWizardDismissed(true)} />
diff --git a/src/client/routes/global-items/$globalItemId.tsx b/src/client/routes/global-items/$globalItemId.tsx
index 18d6724..80b2af7 100644
--- a/src/client/routes/global-items/$globalItemId.tsx
+++ b/src/client/routes/global-items/$globalItemId.tsx
@@ -1,4 +1,5 @@
import { createFileRoute, Link } from "@tanstack/react-router";
+import { useAuth } from "../../hooks/useAuth";
import { useFormatters } from "../../hooks/useFormatters";
import { useGlobalItem } from "../../hooks/useGlobalItems";
import { useUIStore } from "../../stores/uiStore";
@@ -11,8 +12,11 @@ function GlobalItemDetail() {
const { globalItemId } = Route.useParams();
const { data: item, isLoading, error } = useGlobalItem(Number(globalItemId));
const { weight, price } = useFormatters();
+ const { data: auth } = useAuth();
+ const isAuthenticated = !!auth?.user;
const openAddToCollection = useUIStore((s) => s.openAddToCollection);
const openAddToThread = useUIStore((s) => s.openAddToThread);
+ const openAuthPrompt = useUIStore((s) => s.openAuthPrompt);
if (isLoading) {
return (
@@ -133,18 +137,26 @@ function GlobalItemDetail() {
- {/* Actions */}
-
-
+
+ Add Items
+
- {/* Public toggle */}
-
+
+ {setup.isPublic ? "Public" : "Private"}
+
-
-
-
+
+
+
+ )}
{/* Empty state */}
{itemCount === 0 && (
@@ -214,13 +227,15 @@ function SetupDetailPage() {
Add items from your collection to build this loadout.
-
+ {isAuthenticated && (
+
+ )}
)}
@@ -268,15 +283,22 @@ function SetupDetailPage() {
imageFilename={item.imageFilename}
imageUrl={item.imageUrl}
productUrl={item.productUrl}
- onRemove={() => removeItem.mutate(item.id)}
+ onRemove={
+ isAuthenticated
+ ? () => removeItem.mutate(item.id)
+ : undefined
+ }
classification={item.classification}
- onClassificationCycle={() =>
- updateClassification.mutate({
- itemId: item.id,
- classification: nextClassification(
- item.classification,
- ),
- })
+ onClassificationCycle={
+ isAuthenticated
+ ? () =>
+ updateClassification.mutate({
+ itemId: item.id,
+ classification: nextClassification(
+ item.classification,
+ ),
+ })
+ : undefined
}
/>
))}
@@ -288,16 +310,18 @@ function SetupDetailPage() {
)}
- {/* Item Picker */}
- setPickerOpen(false)}
- />
+ {/* Item Picker — only for authenticated users */}
+ {isAuthenticated && (
+ setPickerOpen(false)}
+ />
+ )}
- {/* Delete Confirmation Dialog */}
- {confirmDelete && (
+ {/* Delete Confirmation Dialog — only for authenticated users */}
+ {isAuthenticated && confirmDelete && (