T07a: frontend core SPA — shell + auth + routing + i18n + UI library + a11y

- React 18 + Vite + TypeScript + Tailwind CSS setup
- AppShell with Sidebar (plugin menu) + TopBar (tenant switcher, search, notifications, user menu)
- Auth pages: Login, PasswordResetRequest, PasswordResetConfirm
- Protected routes with auth guard
- API client (axios with interceptors: session cookie, 401 redirect, 422 validation)
- TanStack Query hooks for auth, users, companies, contacts, notifications
- Zustand stores: authStore, uiStore
- i18n setup (de/en locales) with react-i18next
- UI component library: Button, Input, Select, Modal, Toast, Table, Card, Badge, Avatar, Pagination, EmptyState, Skeleton, ConfirmDialog
- Accessibility: ARIA labels, 44px touch targets, keyboard nav, reduced-motion, sr-only
- Design tokens from prototype as CSS custom properties
- 111 tests passing across 20 test files
- tsc --noEmit: 0 errors
- npm run build: success (471KB JS, 24KB CSS)
This commit is contained in:
leocrm-bot
2026-06-29 07:55:47 +02:00
parent f8193a6ab5
commit 22976abe92
66 changed files with 8598 additions and 0 deletions
+22
View File
@@ -0,0 +1,22 @@
import { useEffect } from 'react';
import { useAuthStore } from '@/store/authStore';
import { useCurrentUser } from '@/api/hooks';
export function useAuth() {
const store = useAuthStore();
const { data, isLoading, isError } = useCurrentUser();
useEffect(() => {
if (isError) {
store.setAuthenticated(false);
store.setUser(null);
}
}, [isError, store]);
return {
user: store.user,
isAuthenticated: store.isAuthenticated,
isLoading: isLoading && !store.user,
currentTenant: store.currentTenant,
};
}
+24
View File
@@ -0,0 +1,24 @@
import { useAuthStore } from '@/store/authStore';
import { useSwitchTenant } from '@/api/hooks';
export function useTenant() {
const { user, currentTenant, setTenant } = useAuthStore();
const switchTenantMutation = useSwitchTenant();
const availableTenants = user?.tenants ?? [];
const switchTenant = async (tenantId: string) => {
try {
await switchTenantMutation.mutateAsync(tenantId);
} catch (error) {
console.error('Failed to switch tenant:', error);
}
};
return {
currentTenant,
availableTenants,
switchTenant,
isSwitching: switchTenantMutation.isPending,
};
}