Tau.Acuvim/portal/frontend/src/App.tsx
Diseri Pearson e9143f8c27 Portal: self-service My profile + secondaryColor on the sidebar
Closes the last two outstanding items from FrontEndPrompt.txt's
original phase list.

Frontend
- New MyProfilePage at /profile, any authenticated user. Two cards:
  read-only email + roles + editable displayName; current/new/confirm
  password change with client-side AntD policy validation matching
  Identity's backend rules.
- AppLayout: replace the inline displayName + Sign out with a Dropdown
  on a user-icon button. Items are "My profile" → /profile and
  "Sign out". Header is cleaner; logout still one click.
- AppLayout: Sider now uses branding.secondaryColor with Menu
  theme="dark" + transparent background so the brand colour shows
  through. Sidebar finally reflects the secondary brand colour like
  the header reflects primary.
- useAuth exposes setUser so a successful profile save updates the
  header name without a /auth/me round-trip (PUT already returns
  the updated CurrentUserResponse).

Backend
- PUT /api/auth/me — update own displayName. Email and roles stay
  immutable here (admin-only for role changes). Returns the updated
  CurrentUserResponse.
- POST /api/auth/me/change-password — requires both current and new
  password. Wraps UserManager.ChangePasswordAsync so Identity's
  password policy (8+, upper, lower, digit) runs server-side and the
  current password is verified before any change.

Curl-verified
- PUT /me with new displayName → 200 with updated user.
- change-password with wrong current → 400 "Incorrect password."
- change-password with weak new → 400 listing each policy violation.
- change-password valid + revert → 204 / 204.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-19 20:04:18 +02:00

90 lines
3.5 KiB
TypeScript

import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { AuthProvider } from './hooks/useAuth';
import { BrandingProvider } from './hooks/useBranding';
import { AppInfoProvider } from './hooks/useAppInfo';
import { ThemedRoot } from './components/ThemedRoot';
import { RequireAuth } from './components/RequireAuth';
import { RequireRole } from './components/RequireRole';
import { AppLayout } from './components/layout/AppLayout';
import { LoginPage } from './pages/LoginPage';
import { DashboardPage } from './pages/DashboardPage';
import { DashboardsPage } from './pages/DashboardsPage';
import { MeasurementsPage } from './pages/MeasurementsPage';
import { MyProfilePage } from './pages/MyProfilePage';
import { AdminSitesPage } from './pages/AdminSitesPage';
import { AdminCustomersPage } from './pages/AdminCustomersPage';
import { AdminCustomerDetailPage } from './pages/AdminCustomerDetailPage';
import { SettingsPage } from './pages/SettingsPage';
const queryClient = new QueryClient({
defaultOptions: { queries: { retry: false, refetchOnWindowFocus: false } },
});
export default function App() {
return (
<QueryClientProvider client={queryClient}>
<AppInfoProvider>
<BrandingProvider>
<ThemedRoot>
<AuthProvider>
<BrowserRouter>
<Routes>
<Route path="/login" element={<LoginPage />} />
<Route
path="/"
element={
<RequireAuth>
<AppLayout />
</RequireAuth>
}
>
<Route index element={<DashboardPage />} />
<Route path="dashboards" element={<DashboardsPage />} />
<Route path="measurements" element={<MeasurementsPage />} />
<Route path="profile" element={<MyProfilePage />} />
<Route
path="admin/sites"
element={
<RequireRole role="Admin">
<AdminSitesPage />
</RequireRole>
}
/>
<Route
path="admin/customers"
element={
<RequireRole role="Admin">
<AdminCustomersPage />
</RequireRole>
}
/>
<Route
path="admin/customers/:id"
element={
<RequireRole role="Admin">
<AdminCustomerDetailPage />
</RequireRole>
}
/>
<Route
path="settings"
element={
<RequireRole role="Admin">
<SettingsPage />
</RequireRole>
}
/>
<Route path="admin/users" element={<Navigate to="/settings" replace />} />
</Route>
<Route path="*" element={<Navigate to="/" replace />} />
</Routes>
</BrowserRouter>
</AuthProvider>
</ThemedRoot>
</BrandingProvider>
</AppInfoProvider>
</QueryClientProvider>
);
}