Files
selfphoneng/apps/web/src/auth/RequireAuth.tsx
T
iamdoubz 5db4914fa7 web: theme colors from the logo, readable in both themes
Pages used zinc and sky classes with every dark: variant paired by hand,
and several pairs failed WCAG AA: white on the sky-600 buttons (4.1:1),
zinc-500 secondary text on dark backgrounds (3.7-4.1:1), the 10px zinc-400
labels (2.6:1) and the dashboard's green "Connected" (3.3:1).

index.css now defines theme tokens once, light and dark side by side
(page, surface, subtle, line, line-strong, fg, muted, primary and its
soft/line/ink shades, link, focus), exposed to Tailwind with @theme
inline. Pages use those names, so a palette change is one file. Blue
#2563eb from the logo for buttons and links, the logo's navy #0b1120 for
the dark page. Every text pair is at least 4.5:1 in both themes, most 7:1.

Cards and lists sit on the surface color with a light shadow on an
off-white page, at rounded-xl; native controls follow the theme
(color-scheme). Zinc and sky stay only as data colors: status dots and
chart swatches.
2026-10-28 10:00:20 -05:00

42 lines
1.4 KiB
TypeScript

// SPDX-License-Identifier: AGPL-3.0-or-later
import { useQuery } from '@tanstack/react-query';
import type { ReactNode } from 'react';
import { Navigate, useLocation } from 'react-router';
import type { AuthUser } from '@selfphoneng/shared';
import { ApiError, authApi } from '../api/client.ts';
export const meQuery = {
queryKey: ['me'],
queryFn: authApi.me,
retry: (n: number, e: Error) => !(e instanceof ApiError && e.status === 401) && n < 2,
staleTime: 60_000,
};
export function useMe() {
return useQuery(meQuery);
}
/** Gate for signed-in pages: login first, then a forced password change, then the role check. */
export function RequireAuth({
role,
children,
}: {
role?: AuthUser['role'];
children: (user: AuthUser) => ReactNode;
}) {
const me = useMe();
const loc = useLocation();
if (me.isPending) return <p className="p-6 text-sm text-muted">Loading…</p>;
if (me.isError) {
if (me.error instanceof ApiError && me.error.status === 401)
return <Navigate to={`/login?next=${encodeURIComponent(loc.pathname)}`} replace />;
return <p className="p-6 text-sm text-red-600">Could not reach the server.</p>;
}
const user = me.data;
if (user.mustChangePassword) return <Navigate to="/change-password" replace />;
if (role && user.role !== role) return <Navigate to="/" replace />;
return <>{children(user)}</>;
}
export const homeFor = (user: AuthUser) => (user.role === 'admin' ? '/admin' : '/me');