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>
90 lines
3.5 KiB
TypeScript
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>
|
|
);
|
|
}
|