fix(demo): boot to dashboard, sample-data notice, robust mobile editor icon (#464)

The demo signs in as an admin on load (no login/change-password screen; /login and /change-password bounce to the dashboard), authEnabled stays true so the People/Teams/Roles/Security settings tabs remain available, the banner notes the admin data is sample data, and the mobile editor icon is an inline SVG so it always renders.
This commit is contained in:
SnapOtter
2026-07-07 23:12:37 +08:00
committed by GitHub
parent 7329bc6a13
commit a94b69b14f
6 changed files with 107 additions and 57 deletions
+2 -1
View File
@@ -8,7 +8,8 @@ export function DemoBanner() {
return (
<div className="relative z-[9999] flex items-center justify-center gap-3 bg-primary px-4 py-2 text-sm text-primary-foreground">
<span>
This is a live demo. Processing is disabled.{" "}
This is a live demo. All users, teams, and activity shown are sample data, and file
processing is disabled.{" "}
<a
href="https://github.com/snapotter-hq/SnapOtter"
target="_blank"
+26 -4
View File
@@ -736,9 +736,11 @@ export function matchDemoRoute(url: string, method: string, body?: unknown): Res
}
if (path === "/api/auth/session" && method === "GET") {
const token = localStorage.getItem("snapotter-token");
if (!token) return json({ error: "Unauthorized" }, 401);
const state = loadState();
// The demo is always signed in as an admin, so it boots straight into the
// dashboard with no login or change-password screen, and every settings
// tab (including the auth-gated People/Teams/Roles/Security ones) stays
// available. mustChangePassword is always false; the session is returned
// regardless of token so a stray logout can't drop you onto /login.
return json({
user: {
id: 1,
@@ -746,7 +748,8 @@ export function matchDemoRoute(url: string, method: string, body?: unknown): Res
displayName: "Demo User",
role: "admin",
permissions: PERMISSIONS,
mustChangePassword: !state.passwordChanged,
mustChangePassword: false,
mfaRequired: false,
loginMethod: "local",
hasLocalPassword: true,
},
@@ -1036,7 +1039,26 @@ export function matchDemoRoute(url: string, method: string, body?: unknown): Res
return null;
}
/**
* Boot the demo as an already-signed-in admin so there is no login screen.
* Runs before the app mounts: seeds an auth token and, if the URL is a login
* or change-password route (manual navigation, or the logout redirect),
* rewrites it to the dashboard before React Router reads the location.
*/
function primeDemoAuth() {
try {
localStorage.setItem("snapotter-token", "demo-token");
localStorage.setItem("snapotter-username", "demo");
} catch {}
const p = window.location.pathname;
if (p === "/login" || p === "/change-password") {
window.history.replaceState(null, "", "/");
}
}
export function installMocks() {
primeDemoAuth();
const originalFetch = window.fetch.bind(window);
window.fetch = async (input: RequestInfo | URL, init?: RequestInit): Promise<Response> => {
@@ -1,19 +1,32 @@
/**
* Image-editor glyph for the mobile bottom nav: a framed photo with an editing
* pencil. Rendered as an inline SVG (like the sibling Lucide nav icons) so it
* has no external-asset dependency. It previously used a CSS mask over
* /edit-image.png, which rendered as nothing whenever that asset failed to load
* (e.g. it was missing from the demo build), leaving the icon invisible.
*/
export function ImageEditIcon({ className }: { className?: string }) {
return (
<span
<svg
className={className}
style={{
display: "inline-block",
backgroundColor: "currentColor",
maskImage: "url(/edit-image.png)",
maskSize: "contain",
maskRepeat: "no-repeat",
maskPosition: "center",
WebkitMaskImage: "url(/edit-image.png)",
WebkitMaskSize: "contain",
WebkitMaskRepeat: "no-repeat",
WebkitMaskPosition: "center",
}}
/>
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={2}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M19 11V6a2 2 0 0 0-2-2H5a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h5" />
<circle cx="7.5" cy="8" r="1.6" />
<path d="M3.5 15.5 6.5 12.5 9 15 12 12" />
<g transform="translate(10.8 8.2) scale(0.62)">
<path
vectorEffect="non-scaling-stroke"
d="M21.174 6.812a1 1 0 0 0-3.986-3.987L3.842 16.174a2 2 0 0 0-.5.83l-1.321 4.352a.5.5 0 0 0 .623.622l4.353-1.32a2 2 0 0 0 .83-.497z"
/>
<path vectorEffect="non-scaling-stroke" d="m15 5 4 4" />
</g>
</svg>
);
}