@pattern-stack/frontend-patterns 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +310 -0
- package/dist/atoms/composed/Accordion/Accordion.d.ts +20 -0
- package/dist/atoms/composed/Accordion/Accordion.d.ts.map +1 -0
- package/dist/atoms/composed/Accordion/index.d.ts +2 -0
- package/dist/atoms/composed/Accordion/index.d.ts.map +1 -0
- package/dist/atoms/composed/Alert/Alert.d.ts +25 -0
- package/dist/atoms/composed/Alert/Alert.d.ts.map +1 -0
- package/dist/atoms/composed/Alert/index.d.ts +2 -0
- package/dist/atoms/composed/Alert/index.d.ts.map +1 -0
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts +17 -0
- package/dist/atoms/composed/Breadcrumb/Breadcrumb.d.ts.map +1 -0
- package/dist/atoms/composed/Breadcrumb/index.d.ts +2 -0
- package/dist/atoms/composed/Breadcrumb/index.d.ts.map +1 -0
- package/dist/atoms/composed/Chart/Chart.d.ts +37 -0
- package/dist/atoms/composed/Chart/Chart.d.ts.map +1 -0
- package/dist/atoms/composed/Chart/index.d.ts +3 -0
- package/dist/atoms/composed/Chart/index.d.ts.map +1 -0
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts +19 -0
- package/dist/atoms/composed/ColorSwatch/ColorSwatch.d.ts.map +1 -0
- package/dist/atoms/composed/ColorSwatch/index.d.ts +2 -0
- package/dist/atoms/composed/ColorSwatch/index.d.ts.map +1 -0
- package/dist/atoms/composed/DarkModeToggle.d.ts +4 -0
- package/dist/atoms/composed/DarkModeToggle.d.ts.map +1 -0
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts +13 -0
- package/dist/atoms/composed/DataBadge/DataBadge.d.ts.map +1 -0
- package/dist/atoms/composed/DataBadge/index.d.ts +2 -0
- package/dist/atoms/composed/DataBadge/index.d.ts.map +1 -0
- package/dist/atoms/composed/DataTable/DataTable.d.ts +28 -0
- package/dist/atoms/composed/DataTable/DataTable.d.ts.map +1 -0
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts +10 -0
- package/dist/atoms/composed/DataTable/TableCellWithTooltip.d.ts.map +1 -0
- package/dist/atoms/composed/DataTable/index.d.ts +3 -0
- package/dist/atoms/composed/DataTable/index.d.ts.map +1 -0
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts +45 -0
- package/dist/atoms/composed/DateTimePicker/DateTimePicker.d.ts.map +1 -0
- package/dist/atoms/composed/DateTimePicker/index.d.ts +3 -0
- package/dist/atoms/composed/DateTimePicker/index.d.ts.map +1 -0
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts +30 -0
- package/dist/atoms/composed/DetailedCard/DetailedCard.d.ts.map +1 -0
- package/dist/atoms/composed/DetailedCard/index.d.ts +3 -0
- package/dist/atoms/composed/DetailedCard/index.d.ts.map +1 -0
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts +18 -0
- package/dist/atoms/composed/EmptyState/EmptyState.d.ts.map +1 -0
- package/dist/atoms/composed/EmptyState/index.d.ts +2 -0
- package/dist/atoms/composed/EmptyState/index.d.ts.map +1 -0
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts +46 -0
- package/dist/atoms/composed/FileUpload/FileUpload.d.ts.map +1 -0
- package/dist/atoms/composed/FileUpload/index.d.ts +3 -0
- package/dist/atoms/composed/FileUpload/index.d.ts.map +1 -0
- package/dist/atoms/composed/FormField/FormField.d.ts +23 -0
- package/dist/atoms/composed/FormField/FormField.d.ts.map +1 -0
- package/dist/atoms/composed/FormField/index.d.ts +2 -0
- package/dist/atoms/composed/FormField/index.d.ts.map +1 -0
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts +8 -0
- package/dist/atoms/composed/GlobalSearch/GlobalSearch.d.ts.map +1 -0
- package/dist/atoms/composed/GlobalSearch/index.d.ts +2 -0
- package/dist/atoms/composed/GlobalSearch/index.d.ts.map +1 -0
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts +16 -0
- package/dist/atoms/composed/IconBadge/IconBadge.d.ts.map +1 -0
- package/dist/atoms/composed/IconBadge/index.d.ts +3 -0
- package/dist/atoms/composed/IconBadge/index.d.ts.map +1 -0
- package/dist/atoms/composed/Modal/Modal.d.ts +18 -0
- package/dist/atoms/composed/Modal/Modal.d.ts.map +1 -0
- package/dist/atoms/composed/Modal/index.d.ts +3 -0
- package/dist/atoms/composed/Modal/index.d.ts.map +1 -0
- package/dist/atoms/composed/PaletteSwitcher.d.ts +7 -0
- package/dist/atoms/composed/PaletteSwitcher.d.ts.map +1 -0
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts +25 -0
- package/dist/atoms/composed/ProgressBar/ProgressBar.d.ts.map +1 -0
- package/dist/atoms/composed/ProgressBar/index.d.ts +2 -0
- package/dist/atoms/composed/ProgressBar/index.d.ts.map +1 -0
- package/dist/atoms/composed/StatCard/StatCard.d.ts +21 -0
- package/dist/atoms/composed/StatCard/StatCard.d.ts.map +1 -0
- package/dist/atoms/composed/StatCard/index.d.ts +2 -0
- package/dist/atoms/composed/StatCard/index.d.ts.map +1 -0
- package/dist/atoms/composed/StyleGuide.d.ts +3 -0
- package/dist/atoms/composed/StyleGuide.d.ts.map +1 -0
- package/dist/atoms/composed/Toast/Toast.d.ts +40 -0
- package/dist/atoms/composed/Toast/Toast.d.ts.map +1 -0
- package/dist/atoms/composed/Toast/index.d.ts +2 -0
- package/dist/atoms/composed/Toast/index.d.ts.map +1 -0
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts +16 -0
- package/dist/atoms/composed/Tooltip/Tooltip.d.ts.map +1 -0
- package/dist/atoms/composed/Tooltip/index.d.ts +2 -0
- package/dist/atoms/composed/Tooltip/index.d.ts.map +1 -0
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts +8 -0
- package/dist/atoms/composed/UserAvatar/UserAvatar.d.ts.map +1 -0
- package/dist/atoms/composed/UserAvatar/index.d.ts +2 -0
- package/dist/atoms/composed/UserAvatar/index.d.ts.map +1 -0
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts +8 -0
- package/dist/atoms/composed/UserMenu/UserMenu.d.ts.map +1 -0
- package/dist/atoms/composed/UserMenu/index.d.ts +2 -0
- package/dist/atoms/composed/UserMenu/index.d.ts.map +1 -0
- package/dist/atoms/composed/index.d.ts +25 -0
- package/dist/atoms/composed/index.d.ts.map +1 -0
- package/dist/atoms/hooks/useApi.d.ts +25 -0
- package/dist/atoms/hooks/useApi.d.ts.map +1 -0
- package/dist/atoms/hooks/useHealth.d.ts +19 -0
- package/dist/atoms/hooks/useHealth.d.ts.map +1 -0
- package/dist/atoms/index.d.ts +9 -0
- package/dist/atoms/index.d.ts.map +1 -0
- package/dist/atoms/services/api/client.d.ts +20 -0
- package/dist/atoms/services/api/client.d.ts.map +1 -0
- package/dist/atoms/services/auth-service.d.ts +24 -0
- package/dist/atoms/services/auth-service.d.ts.map +1 -0
- package/dist/atoms/services/health.d.ts +7 -0
- package/dist/atoms/services/health.d.ts.map +1 -0
- package/dist/atoms/services/index.d.ts +4 -0
- package/dist/atoms/services/index.d.ts.map +1 -0
- package/dist/atoms/shared/config/constants.d.ts +15 -0
- package/dist/atoms/shared/config/constants.d.ts.map +1 -0
- package/dist/atoms/shared/config/dashboard-sizes.d.ts +83 -0
- package/dist/atoms/shared/config/dashboard-sizes.d.ts.map +1 -0
- package/dist/atoms/shared/config/environment.d.ts +10 -0
- package/dist/atoms/shared/config/environment.d.ts.map +1 -0
- package/dist/atoms/shared/index.d.ts +4 -0
- package/dist/atoms/shared/index.d.ts.map +1 -0
- package/dist/atoms/types/auth.d.ts +56 -0
- package/dist/atoms/types/auth.d.ts.map +1 -0
- package/dist/atoms/types/generated.d.ts +1469 -0
- package/dist/atoms/types/generated.d.ts.map +1 -0
- package/dist/atoms/types/index.d.ts +4 -0
- package/dist/atoms/types/index.d.ts.map +1 -0
- package/dist/atoms/types/loading.d.ts +26 -0
- package/dist/atoms/types/loading.d.ts.map +1 -0
- package/dist/atoms/ui/Badge.d.ts +10 -0
- package/dist/atoms/ui/Badge.d.ts.map +1 -0
- package/dist/atoms/ui/ErrorBoundary.d.ts +18 -0
- package/dist/atoms/ui/ErrorBoundary.d.ts.map +1 -0
- package/dist/atoms/ui/Select.d.ts +28 -0
- package/dist/atoms/ui/Select.d.ts.map +1 -0
- package/dist/atoms/ui/Switch.d.ts +9 -0
- package/dist/atoms/ui/Switch.d.ts.map +1 -0
- package/dist/atoms/ui/Tabs.d.ts +30 -0
- package/dist/atoms/ui/Tabs.d.ts.map +1 -0
- package/dist/atoms/ui/avatar.d.ts +7 -0
- package/dist/atoms/ui/avatar.d.ts.map +1 -0
- package/dist/atoms/ui/button.d.ts +14 -0
- package/dist/atoms/ui/button.d.ts.map +1 -0
- package/dist/atoms/ui/card.d.ts +12 -0
- package/dist/atoms/ui/card.d.ts.map +1 -0
- package/dist/atoms/ui/dropdown-menu.d.ts +28 -0
- package/dist/atoms/ui/dropdown-menu.d.ts.map +1 -0
- package/dist/atoms/ui/index.d.ts +15 -0
- package/dist/atoms/ui/index.d.ts.map +1 -0
- package/dist/atoms/ui/input.d.ts +5 -0
- package/dist/atoms/ui/input.d.ts.map +1 -0
- package/dist/atoms/ui/label.d.ts +6 -0
- package/dist/atoms/ui/label.d.ts.map +1 -0
- package/dist/atoms/ui/skeleton.d.ts +3 -0
- package/dist/atoms/ui/skeleton.d.ts.map +1 -0
- package/dist/atoms/ui/spinner.d.ts +14 -0
- package/dist/atoms/ui/spinner.d.ts.map +1 -0
- package/dist/atoms/ui/table.d.ts +11 -0
- package/dist/atoms/ui/table.d.ts.map +1 -0
- package/dist/atoms/utils/animations.d.ts +65 -0
- package/dist/atoms/utils/animations.d.ts.map +1 -0
- package/dist/atoms/utils/tooltip-helpers.d.ts +71 -0
- package/dist/atoms/utils/tooltip-helpers.d.ts.map +1 -0
- package/dist/atoms/utils/utils.d.ts +4 -0
- package/dist/atoms/utils/utils.d.ts.map +1 -0
- package/dist/features/auth/components/LoginForm.d.ts +2 -0
- package/dist/features/auth/components/LoginForm.d.ts.map +1 -0
- package/dist/features/auth/components/LogoutButton.d.ts +2 -0
- package/dist/features/auth/components/LogoutButton.d.ts.map +1 -0
- package/dist/features/auth/components/ProtectedRoute.d.ts +10 -0
- package/dist/features/auth/components/ProtectedRoute.d.ts.map +1 -0
- package/dist/features/auth/components/index.d.ts +4 -0
- package/dist/features/auth/components/index.d.ts.map +1 -0
- package/dist/features/auth/hooks/index.d.ts +3 -0
- package/dist/features/auth/hooks/index.d.ts.map +1 -0
- package/dist/features/auth/hooks/useAuth.d.ts +10 -0
- package/dist/features/auth/hooks/useAuth.d.ts.map +1 -0
- package/dist/features/auth/hooks/usePermissions.d.ts +13 -0
- package/dist/features/auth/hooks/usePermissions.d.ts.map +1 -0
- package/dist/features/auth/index.d.ts +3 -0
- package/dist/features/auth/index.d.ts.map +1 -0
- package/dist/features/index.d.ts +2 -0
- package/dist/features/index.d.ts.map +1 -0
- package/dist/frontend-patterns.css +567 -0
- package/dist/index.d.ts +10 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.es.js +10152 -0
- package/dist/index.es.js.map +1 -0
- package/dist/index.js +10170 -0
- package/dist/index.js.map +1 -0
- package/dist/molecules/forms/FormGroup.d.ts +17 -0
- package/dist/molecules/forms/FormGroup.d.ts.map +1 -0
- package/dist/molecules/forms/SearchInput.d.ts +36 -0
- package/dist/molecules/forms/SearchInput.d.ts.map +1 -0
- package/dist/molecules/forms/index.d.ts +3 -0
- package/dist/molecules/forms/index.d.ts.map +1 -0
- package/dist/molecules/index.d.ts +4 -0
- package/dist/molecules/index.d.ts.map +1 -0
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts +7 -0
- package/dist/molecules/layout/AppHeader/AppHeader.d.ts.map +1 -0
- package/dist/molecules/layout/AppHeader/index.d.ts +2 -0
- package/dist/molecules/layout/AppHeader/index.d.ts.map +1 -0
- package/dist/molecules/layout/AppLayout.d.ts +2 -0
- package/dist/molecules/layout/AppLayout.d.ts.map +1 -0
- package/dist/molecules/layout/PageTemplate.d.ts +19 -0
- package/dist/molecules/layout/PageTemplate.d.ts.map +1 -0
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts +24 -0
- package/dist/molecules/layout/SectionHeader/SectionHeader.d.ts.map +1 -0
- package/dist/molecules/layout/SectionHeader/index.d.ts +2 -0
- package/dist/molecules/layout/SectionHeader/index.d.ts.map +1 -0
- package/dist/molecules/layout/ShowcaseSection.d.ts +22 -0
- package/dist/molecules/layout/ShowcaseSection.d.ts.map +1 -0
- package/dist/molecules/layout/Sidebar.d.ts +6 -0
- package/dist/molecules/layout/Sidebar.d.ts.map +1 -0
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts +13 -0
- package/dist/molecules/layout/SidebarButton/SidebarButton.d.ts.map +1 -0
- package/dist/molecules/layout/SidebarButton/index.d.ts +2 -0
- package/dist/molecules/layout/SidebarButton/index.d.ts.map +1 -0
- package/dist/molecules/layout/SidebarContext.d.ts +12 -0
- package/dist/molecules/layout/SidebarContext.d.ts.map +1 -0
- package/dist/molecules/layout/index.d.ts +8 -0
- package/dist/molecules/layout/index.d.ts.map +1 -0
- package/dist/molecules/navigation/NavMenu.d.ts +20 -0
- package/dist/molecules/navigation/NavMenu.d.ts.map +1 -0
- package/dist/molecules/navigation/Pagination.d.ts +14 -0
- package/dist/molecules/navigation/Pagination.d.ts.map +1 -0
- package/dist/molecules/navigation/index.d.ts +3 -0
- package/dist/molecules/navigation/index.d.ts.map +1 -0
- package/dist/organisms/index.d.ts +2 -0
- package/dist/organisms/index.d.ts.map +1 -0
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts +3 -0
- package/dist/organisms/showcase/ComponentShowcasePage.d.ts.map +1 -0
- package/dist/templates/AuthTemplate.d.ts +68 -0
- package/dist/templates/AuthTemplate.d.ts.map +1 -0
- package/dist/templates/ComponentShowcaseTemplate.d.ts +53 -0
- package/dist/templates/ComponentShowcaseTemplate.d.ts.map +1 -0
- package/dist/templates/DashboardTemplate.d.ts +62 -0
- package/dist/templates/DashboardTemplate.d.ts.map +1 -0
- package/dist/templates/DataTemplate.d.ts +78 -0
- package/dist/templates/DataTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminCRUDTemplate.d.ts +105 -0
- package/dist/templates/admin/AdminCRUDTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminDashboardTemplate.d.ts +89 -0
- package/dist/templates/admin/AdminDashboardTemplate.d.ts.map +1 -0
- package/dist/templates/admin/AdminDetailTemplate.d.ts +132 -0
- package/dist/templates/admin/AdminDetailTemplate.d.ts.map +1 -0
- package/dist/templates/admin/index.d.ts +4 -0
- package/dist/templates/admin/index.d.ts.map +1 -0
- package/dist/templates/factory.d.ts +28 -0
- package/dist/templates/factory.d.ts.map +1 -0
- package/dist/templates/index.d.ts +7 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/package.json +118 -0
- package/src/App.css +42 -0
- package/src/App.tsx +54 -0
- package/src/__tests__/README.md +221 -0
- package/src/__tests__/atoms/hooks/simple-hooks.test.ts +44 -0
- package/src/__tests__/atoms/ui/button.test.tsx +68 -0
- package/src/__tests__/atoms/utils/simple.test.ts +18 -0
- package/src/__tests__/atoms/utils/utils.test.ts +77 -0
- package/src/__tests__/features/auth/simple-auth.test.tsx +40 -0
- package/src/__tests__/molecules/layout/simple-layout.test.tsx +81 -0
- package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +167 -0
- package/src/__tests__/setup.ts +51 -0
- package/src/__tests__/utils.tsx +123 -0
- package/src/atoms/composed/Accordion/Accordion.tsx +271 -0
- package/src/atoms/composed/Accordion/index.ts +1 -0
- package/src/atoms/composed/Alert/Alert.tsx +132 -0
- package/src/atoms/composed/Alert/index.ts +1 -0
- package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +83 -0
- package/src/atoms/composed/Breadcrumb/index.ts +1 -0
- package/src/atoms/composed/Chart/Chart.tsx +425 -0
- package/src/atoms/composed/Chart/index.ts +2 -0
- package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +72 -0
- package/src/atoms/composed/ColorSwatch/index.ts +1 -0
- package/src/atoms/composed/DarkModeToggle.tsx +66 -0
- package/src/atoms/composed/DataBadge/DataBadge.tsx +81 -0
- package/src/atoms/composed/DataBadge/index.ts +1 -0
- package/src/atoms/composed/DataTable/DataTable.tsx +394 -0
- package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +41 -0
- package/src/atoms/composed/DataTable/index.ts +2 -0
- package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +611 -0
- package/src/atoms/composed/DateTimePicker/index.ts +2 -0
- package/src/atoms/composed/DetailedCard/DetailedCard.tsx +181 -0
- package/src/atoms/composed/DetailedCard/index.ts +2 -0
- package/src/atoms/composed/EmptyState/EmptyState.tsx +90 -0
- package/src/atoms/composed/EmptyState/index.ts +1 -0
- package/src/atoms/composed/FileUpload/FileUpload.tsx +477 -0
- package/src/atoms/composed/FileUpload/index.ts +2 -0
- package/src/atoms/composed/FormField/FormField.tsx +92 -0
- package/src/atoms/composed/FormField/index.ts +1 -0
- package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +37 -0
- package/src/atoms/composed/GlobalSearch/index.ts +1 -0
- package/src/atoms/composed/IconBadge/IconBadge.tsx +95 -0
- package/src/atoms/composed/IconBadge/index.ts +2 -0
- package/src/atoms/composed/Modal/Modal.tsx +223 -0
- package/src/atoms/composed/Modal/index.ts +2 -0
- package/src/atoms/composed/PaletteSwitcher.tsx +386 -0
- package/src/atoms/composed/ProgressBar/ProgressBar.tsx +116 -0
- package/src/atoms/composed/ProgressBar/index.ts +1 -0
- package/src/atoms/composed/StatCard/StatCard.tsx +219 -0
- package/src/atoms/composed/StatCard/index.ts +1 -0
- package/src/atoms/composed/StyleGuide.tsx +717 -0
- package/src/atoms/composed/Toast/Toast.tsx +219 -0
- package/src/atoms/composed/Toast/index.ts +1 -0
- package/src/atoms/composed/Tooltip/Tooltip.tsx +213 -0
- package/src/atoms/composed/Tooltip/index.ts +1 -0
- package/src/atoms/composed/UserAvatar/UserAvatar.tsx +139 -0
- package/src/atoms/composed/UserAvatar/index.ts +1 -0
- package/src/atoms/composed/UserMenu/UserMenu.tsx +16 -0
- package/src/atoms/composed/UserMenu/index.ts +1 -0
- package/src/atoms/composed/index.ts +29 -0
- package/src/atoms/hooks/useApi.ts +80 -0
- package/src/atoms/hooks/useHealth.ts +17 -0
- package/src/atoms/index.ts +13 -0
- package/src/atoms/services/api/client.ts +134 -0
- package/src/atoms/services/auth-service.ts +248 -0
- package/src/atoms/services/health.ts +15 -0
- package/src/atoms/services/index.ts +3 -0
- package/src/atoms/shared/config/constants.ts +17 -0
- package/src/atoms/shared/config/dashboard-sizes.ts +111 -0
- package/src/atoms/shared/config/environment.ts +10 -0
- package/src/atoms/shared/index.ts +4 -0
- package/src/atoms/shared/styles/color-palettes.css +566 -0
- package/src/atoms/types/auth.ts +62 -0
- package/src/atoms/types/generated.ts +1469 -0
- package/src/atoms/types/index.ts +4 -0
- package/src/atoms/types/loading.ts +28 -0
- package/src/atoms/ui/Badge.tsx +30 -0
- package/src/atoms/ui/ErrorBoundary.tsx +59 -0
- package/src/atoms/ui/Select.tsx +53 -0
- package/src/atoms/ui/Switch.tsx +42 -0
- package/src/atoms/ui/Tabs.tsx +118 -0
- package/src/atoms/ui/avatar.tsx +48 -0
- package/src/atoms/ui/button.tsx +70 -0
- package/src/atoms/ui/card.tsx +76 -0
- package/src/atoms/ui/dropdown-menu.tsx +199 -0
- package/src/atoms/ui/index.ts +39 -0
- package/src/atoms/ui/input.tsx +23 -0
- package/src/atoms/ui/label.tsx +23 -0
- package/src/atoms/ui/skeleton.tsx +13 -0
- package/src/atoms/ui/spinner.tsx +49 -0
- package/src/atoms/ui/table.tsx +116 -0
- package/src/atoms/utils/animations.ts +135 -0
- package/src/atoms/utils/tooltip-helpers.ts +140 -0
- package/src/atoms/utils/utils.ts +9 -0
- package/src/features/auth/components/LoginForm.tsx +168 -0
- package/src/features/auth/components/LogoutButton.tsx +19 -0
- package/src/features/auth/components/ProtectedRoute.tsx +60 -0
- package/src/features/auth/components/index.ts +4 -0
- package/src/features/auth/hooks/index.ts +2 -0
- package/src/features/auth/hooks/useAuth.tsx +205 -0
- package/src/features/auth/hooks/usePermissions.ts +35 -0
- package/src/features/auth/index.ts +2 -0
- package/src/features/index.ts +2 -0
- package/src/index.css +704 -0
- package/src/index.ts +13 -0
- package/src/main.tsx +48 -0
- package/src/molecules/.gitkeep +0 -0
- package/src/molecules/forms/FormGroup.tsx +75 -0
- package/src/molecules/forms/SearchInput.tsx +259 -0
- package/src/molecules/forms/index.ts +4 -0
- package/src/molecules/index.ts +4 -0
- package/src/molecules/layout/AppHeader/AppHeader.tsx +42 -0
- package/src/molecules/layout/AppHeader/index.ts +1 -0
- package/src/molecules/layout/AppLayout.tsx +29 -0
- package/src/molecules/layout/PageTemplate.tsx +87 -0
- package/src/molecules/layout/SectionHeader/SectionHeader.tsx +87 -0
- package/src/molecules/layout/SectionHeader/index.ts +1 -0
- package/src/molecules/layout/ShowcaseSection.tsx +57 -0
- package/src/molecules/layout/Sidebar.tsx +144 -0
- package/src/molecules/layout/SidebarButton/SidebarButton.tsx +99 -0
- package/src/molecules/layout/SidebarButton/index.ts +1 -0
- package/src/molecules/layout/SidebarContext.tsx +31 -0
- package/src/molecules/layout/index.ts +7 -0
- package/src/molecules/navigation/NavMenu.tsx +188 -0
- package/src/molecules/navigation/Pagination.tsx +172 -0
- package/src/molecules/navigation/index.ts +4 -0
- package/src/organisms/index.ts +5 -0
- package/src/organisms/showcase/ComponentShowcasePage.tsx +2496 -0
- package/src/organisms/showcase/index.ts +1 -0
- package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +242 -0
- package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +171 -0
- package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +385 -0
- package/src/pages/AdminShowcase/index.tsx +3 -0
- package/src/pages/ComponentShowcase/BadgesShowcase.tsx +188 -0
- package/src/pages/ComponentShowcase/CardsShowcase.tsx +392 -0
- package/src/pages/ComponentShowcase/PalettesShowcase.tsx +207 -0
- package/src/pages/ComponentShowcase/StatesShowcase.tsx +485 -0
- package/src/pages/ComponentShowcase/TablesShowcase.tsx +134 -0
- package/src/pages/ComponentShowcase/TypographyShowcase.tsx +255 -0
- package/src/pages/ComponentShowcase/index.tsx +188 -0
- package/src/pages/index.ts +2 -0
- package/src/templates/AuthTemplate.tsx +216 -0
- package/src/templates/ComponentShowcaseTemplate.tsx +173 -0
- package/src/templates/DashboardTemplate.tsx +232 -0
- package/src/templates/DataTemplate.tsx +319 -0
- package/src/templates/admin/AdminCRUDTemplate.tsx +630 -0
- package/src/templates/admin/AdminDashboardTemplate.tsx +351 -0
- package/src/templates/admin/AdminDetailTemplate.tsx +563 -0
- package/src/templates/admin/index.ts +29 -0
- package/src/templates/factory.tsx +169 -0
- package/src/templates/index.ts +37 -0
- package/src/vite-env.d.ts +1 -0
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { useRef, useState, useEffect } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Hook to detect if an element's text is truncated/overflowing
|
|
5
|
+
* Useful for conditionally showing tooltips only when needed
|
|
6
|
+
*/
|
|
7
|
+
export const useTextOverflow = () => {
|
|
8
|
+
const ref = useRef<HTMLElement>(null);
|
|
9
|
+
const [isOverflowing, setIsOverflowing] = useState(false);
|
|
10
|
+
|
|
11
|
+
useEffect(() => {
|
|
12
|
+
const checkOverflow = () => {
|
|
13
|
+
const element = ref.current;
|
|
14
|
+
if (!element) return;
|
|
15
|
+
|
|
16
|
+
// Check if text is truncated
|
|
17
|
+
const isOverflow = element.scrollWidth > element.clientWidth ||
|
|
18
|
+
element.scrollHeight > element.clientHeight;
|
|
19
|
+
setIsOverflowing(isOverflow);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
checkOverflow();
|
|
23
|
+
|
|
24
|
+
// Re-check on resize
|
|
25
|
+
window.addEventListener('resize', checkOverflow);
|
|
26
|
+
|
|
27
|
+
// Use ResizeObserver if available for more accurate detection
|
|
28
|
+
if (typeof ResizeObserver !== 'undefined') {
|
|
29
|
+
const resizeObserver = new ResizeObserver(checkOverflow);
|
|
30
|
+
if (ref.current) {
|
|
31
|
+
resizeObserver.observe(ref.current);
|
|
32
|
+
}
|
|
33
|
+
return () => {
|
|
34
|
+
resizeObserver.disconnect();
|
|
35
|
+
window.removeEventListener('resize', checkOverflow);
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return () => window.removeEventListener('resize', checkOverflow);
|
|
40
|
+
}, []);
|
|
41
|
+
|
|
42
|
+
return { ref, isOverflowing };
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Formats large numbers with abbreviations
|
|
47
|
+
* Useful for StatCard values with tooltips showing full numbers
|
|
48
|
+
*/
|
|
49
|
+
export const formatNumberWithTooltip = (value: number): { display: string; tooltip: string } => {
|
|
50
|
+
const formatter = new Intl.NumberFormat('en-US');
|
|
51
|
+
const fullNumber = formatter.format(value);
|
|
52
|
+
|
|
53
|
+
if (value >= 1_000_000_000) {
|
|
54
|
+
return {
|
|
55
|
+
display: `${(value / 1_000_000_000).toFixed(1)}B`,
|
|
56
|
+
tooltip: fullNumber
|
|
57
|
+
};
|
|
58
|
+
} else if (value >= 1_000_000) {
|
|
59
|
+
return {
|
|
60
|
+
display: `${(value / 1_000_000).toFixed(1)}M`,
|
|
61
|
+
tooltip: fullNumber
|
|
62
|
+
};
|
|
63
|
+
} else if (value >= 10_000) {
|
|
64
|
+
return {
|
|
65
|
+
display: `${(value / 1_000).toFixed(1)}K`,
|
|
66
|
+
tooltip: fullNumber
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
display: fullNumber,
|
|
72
|
+
tooltip: '' // No tooltip needed for small numbers
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Standard tooltip content for common UI patterns
|
|
78
|
+
*/
|
|
79
|
+
export const tooltipContent = {
|
|
80
|
+
// Icon button tooltips
|
|
81
|
+
refresh: 'Refresh data',
|
|
82
|
+
download: 'Download',
|
|
83
|
+
upload: 'Upload file',
|
|
84
|
+
edit: 'Edit',
|
|
85
|
+
delete: 'Delete',
|
|
86
|
+
view: 'View details',
|
|
87
|
+
filter: 'Filter',
|
|
88
|
+
search: 'Search',
|
|
89
|
+
settings: 'Settings',
|
|
90
|
+
help: 'Help',
|
|
91
|
+
export: 'Export data',
|
|
92
|
+
import: 'Import data',
|
|
93
|
+
add: 'Add new',
|
|
94
|
+
create: 'Create new',
|
|
95
|
+
back: 'Go back',
|
|
96
|
+
forward: 'Go forward',
|
|
97
|
+
menu: 'Open menu',
|
|
98
|
+
more: 'More options',
|
|
99
|
+
copy: 'Copy',
|
|
100
|
+
paste: 'Paste',
|
|
101
|
+
share: 'Share',
|
|
102
|
+
print: 'Print',
|
|
103
|
+
|
|
104
|
+
// Status tooltips
|
|
105
|
+
success: 'Operation completed successfully',
|
|
106
|
+
warning: 'Requires attention',
|
|
107
|
+
error: 'Error occurred',
|
|
108
|
+
info: 'Information',
|
|
109
|
+
pending: 'Pending',
|
|
110
|
+
|
|
111
|
+
// Common actions
|
|
112
|
+
save: 'Save changes',
|
|
113
|
+
cancel: 'Cancel',
|
|
114
|
+
close: 'Close',
|
|
115
|
+
expand: 'Expand',
|
|
116
|
+
collapse: 'Collapse',
|
|
117
|
+
minimize: 'Minimize',
|
|
118
|
+
maximize: 'Maximize',
|
|
119
|
+
|
|
120
|
+
// Navigation
|
|
121
|
+
previous: 'Previous',
|
|
122
|
+
next: 'Next',
|
|
123
|
+
first: 'Go to first',
|
|
124
|
+
last: 'Go to last',
|
|
125
|
+
home: 'Home',
|
|
126
|
+
|
|
127
|
+
// Data operations
|
|
128
|
+
sort: 'Sort',
|
|
129
|
+
sortAsc: 'Sort ascending',
|
|
130
|
+
sortDesc: 'Sort descending',
|
|
131
|
+
clearSort: 'Clear sorting',
|
|
132
|
+
clearFilters: 'Clear all filters',
|
|
133
|
+
clearSearch: 'Clear search',
|
|
134
|
+
|
|
135
|
+
// Authentication
|
|
136
|
+
login: 'Sign in',
|
|
137
|
+
logout: 'Sign out',
|
|
138
|
+
profile: 'Profile',
|
|
139
|
+
account: 'Account settings',
|
|
140
|
+
};
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { useAuth } from '../hooks/useAuth';
|
|
3
|
+
import { Button } from '../../../atoms/ui/button';
|
|
4
|
+
import { Input } from '../../../atoms/ui/input';
|
|
5
|
+
import { Label } from '../../../atoms/ui/label';
|
|
6
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '../../../atoms/ui/card';
|
|
7
|
+
import { cn } from '../../../atoms/utils/utils';
|
|
8
|
+
import { Database, Shield, TrendingUp, Mail, Lock, Sparkles } from 'lucide-react';
|
|
9
|
+
|
|
10
|
+
export function LoginForm() {
|
|
11
|
+
const [email, setEmail] = useState('');
|
|
12
|
+
const [password, setPassword] = useState('');
|
|
13
|
+
const [error, setError] = useState('');
|
|
14
|
+
const { login, isLoading } = useAuth();
|
|
15
|
+
|
|
16
|
+
const handleSubmit = async (e: React.FormEvent) => {
|
|
17
|
+
e.preventDefault();
|
|
18
|
+
setError('');
|
|
19
|
+
|
|
20
|
+
try {
|
|
21
|
+
await login({ email, password });
|
|
22
|
+
} catch (err) {
|
|
23
|
+
setError(err instanceof Error ? err.message : 'Login failed');
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
return (
|
|
28
|
+
<div className="min-h-screen relative overflow-hidden bg-gradient-to-br from-muted/20 via-background to-muted/40">
|
|
29
|
+
{/* Background Pattern */}
|
|
30
|
+
<div className="absolute inset-0 bg-muted/5 opacity-5" />
|
|
31
|
+
|
|
32
|
+
{/* Floating Elements */}
|
|
33
|
+
<div className="absolute top-20 left-20 w-32 h-32 bg-primary/10 rounded-full blur-xl animate-pulse" />
|
|
34
|
+
<div className="absolute top-40 right-32 w-24 h-24 bg-secondary/10 rounded-full blur-lg animate-pulse delay-1000" />
|
|
35
|
+
<div className="absolute bottom-40 left-32 w-40 h-40 bg-accent/10 rounded-full blur-2xl animate-pulse delay-500" />
|
|
36
|
+
|
|
37
|
+
<div className="min-h-screen flex items-center justify-center p-4 relative z-10">
|
|
38
|
+
<div className="w-full max-w-md space-y-8">
|
|
39
|
+
{/* Header */}
|
|
40
|
+
<div className="text-center space-y-6 animate-slide-up">
|
|
41
|
+
<div className="flex justify-center">
|
|
42
|
+
<div className="relative">
|
|
43
|
+
<div className="w-16 h-16 bg-gradient-to-br from-primary to-secondary rounded flex items-center justify-center shadow">
|
|
44
|
+
<Database className="w-8 h-8 text-primary-foreground" />
|
|
45
|
+
</div>
|
|
46
|
+
<div className="absolute -top-1 -right-1 w-6 h-6 bg-gradient-to-br from-accent to-accent-foreground rounded-full flex items-center justify-center">
|
|
47
|
+
<Sparkles className="w-3 h-3 text-accent-foreground" />
|
|
48
|
+
</div>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<div className="space-y-2">
|
|
53
|
+
<h1 className="text-3xl font-bold bg-gradient-to-r from-foreground via-primary to-secondary bg-clip-text text-transparent">
|
|
54
|
+
Data Trust Navigator
|
|
55
|
+
</h1>
|
|
56
|
+
<p className="text-muted-foreground text-lg">
|
|
57
|
+
Explore, analyze, and trust your data ecosystem
|
|
58
|
+
</p>
|
|
59
|
+
</div>
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
{/* Login Card */}
|
|
63
|
+
<Card className="card-container animate-slide-up border-0 shadow bg-background/80 backdrop-blur-sm">
|
|
64
|
+
<CardHeader className="text-center pb-4">
|
|
65
|
+
<CardTitle className="text-xl font-semibold">Welcome Back</CardTitle>
|
|
66
|
+
<CardDescription>
|
|
67
|
+
Sign in to access your analytics dashboard
|
|
68
|
+
</CardDescription>
|
|
69
|
+
</CardHeader>
|
|
70
|
+
|
|
71
|
+
<CardContent className="space-y-6">
|
|
72
|
+
<form onSubmit={handleSubmit} className="space-y-5">
|
|
73
|
+
<div className="space-y-2">
|
|
74
|
+
<Label htmlFor="email" className="text-data-label">Email Address</Label>
|
|
75
|
+
<div className="relative">
|
|
76
|
+
<Mail className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
77
|
+
<Input
|
|
78
|
+
id="email"
|
|
79
|
+
type="email"
|
|
80
|
+
value={email}
|
|
81
|
+
onChange={(e) => setEmail(e.target.value)}
|
|
82
|
+
required
|
|
83
|
+
placeholder="Enter your email"
|
|
84
|
+
className="pl-10 h-11 transition-all duration-200 focus:ring-2 focus:ring-ring focus:border-ring"
|
|
85
|
+
/>
|
|
86
|
+
</div>
|
|
87
|
+
</div>
|
|
88
|
+
|
|
89
|
+
<div className="space-y-2">
|
|
90
|
+
<Label htmlFor="password" className="text-data-label">Password</Label>
|
|
91
|
+
<div className="relative">
|
|
92
|
+
<Lock className="absolute left-3 top-1/2 transform -translate-y-1/2 text-muted-foreground w-4 h-4" />
|
|
93
|
+
<Input
|
|
94
|
+
id="password"
|
|
95
|
+
type="password"
|
|
96
|
+
value={password}
|
|
97
|
+
onChange={(e) => setPassword(e.target.value)}
|
|
98
|
+
required
|
|
99
|
+
placeholder="Enter your password"
|
|
100
|
+
className="pl-10 h-11 transition-all duration-200 focus:ring-2 focus:ring-ring focus:border-ring"
|
|
101
|
+
/>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
{error && (
|
|
106
|
+
<div className="bg-destructive/10 border border-destructive/20 text-destructive text-sm p-3 rounded animate-shake">
|
|
107
|
+
{error}
|
|
108
|
+
</div>
|
|
109
|
+
)}
|
|
110
|
+
|
|
111
|
+
<Button
|
|
112
|
+
type="submit"
|
|
113
|
+
className={cn(
|
|
114
|
+
"w-full h-11 bg-gradient-to-r from-primary to-secondary hover:from-primary/90 hover:to-secondary/90 text-primary-foreground font-medium transition-all duration-200 hover:shadow-lg hover:scale-[1.02] active:scale-[0.98]"
|
|
115
|
+
)}
|
|
116
|
+
disabled={isLoading}
|
|
117
|
+
>
|
|
118
|
+
{isLoading ? (
|
|
119
|
+
<div className="flex items-center gap-2">
|
|
120
|
+
<div className="w-4 h-4 border-2 border-primary-foreground/30 border-t-primary-foreground rounded-full animate-spin" />
|
|
121
|
+
Signing in...
|
|
122
|
+
</div>
|
|
123
|
+
) : (
|
|
124
|
+
<div className="flex items-center gap-2">
|
|
125
|
+
<Shield className="w-4 h-4" />
|
|
126
|
+
Sign In
|
|
127
|
+
</div>
|
|
128
|
+
)}
|
|
129
|
+
</Button>
|
|
130
|
+
</form>
|
|
131
|
+
|
|
132
|
+
{/* Demo Notice */}
|
|
133
|
+
<div className="bg-muted/50 border border-border rounded p-4">
|
|
134
|
+
<div className="flex items-start gap-3">
|
|
135
|
+
<div className="w-6 h-6 bg-primary rounded-full flex items-center justify-center flex-shrink-0 mt-0.5">
|
|
136
|
+
<TrendingUp className="w-3 h-3 text-primary-foreground" />
|
|
137
|
+
</div>
|
|
138
|
+
<div className="space-y-1">
|
|
139
|
+
<p className="text-sm font-medium text-foreground">Demo Environment</p>
|
|
140
|
+
<p className="text-xs text-muted-foreground">
|
|
141
|
+
Use any email and password to explore the analytics platform
|
|
142
|
+
</p>
|
|
143
|
+
</div>
|
|
144
|
+
</div>
|
|
145
|
+
</div>
|
|
146
|
+
</CardContent>
|
|
147
|
+
</Card>
|
|
148
|
+
|
|
149
|
+
{/* Features */}
|
|
150
|
+
<div className="grid grid-cols-3 gap-4 animate-slide-up">
|
|
151
|
+
{[
|
|
152
|
+
{ icon: Database, label: 'Data Models', bgClass: 'bg-primary/10', textClass: 'text-primary' },
|
|
153
|
+
{ icon: Shield, label: 'Quality Tests', bgClass: 'bg-secondary/10', textClass: 'text-secondary-foreground' },
|
|
154
|
+
{ icon: TrendingUp, label: 'Analytics', bgClass: 'bg-accent/10', textClass: 'text-accent-foreground' }
|
|
155
|
+
].map((feature) => (
|
|
156
|
+
<div key={feature.label} className="text-center space-y-2">
|
|
157
|
+
<div className={cn('w-10 h-10 rounded-lg flex items-center justify-center mx-auto', feature.bgClass)}>
|
|
158
|
+
<feature.icon className={cn('w-5 h-5', feature.textClass)} />
|
|
159
|
+
</div>
|
|
160
|
+
<p className="text-xs text-muted-foreground font-medium">{feature.label}</p>
|
|
161
|
+
</div>
|
|
162
|
+
))}
|
|
163
|
+
</div>
|
|
164
|
+
</div>
|
|
165
|
+
</div>
|
|
166
|
+
</div>
|
|
167
|
+
);
|
|
168
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { useAuth } from '../hooks/useAuth';
|
|
2
|
+
import { Button } from '../../../atoms/ui/button';
|
|
3
|
+
|
|
4
|
+
export function LogoutButton() {
|
|
5
|
+
const { logout, user } = useAuth();
|
|
6
|
+
|
|
7
|
+
return (
|
|
8
|
+
<div className="flex items-center gap-2">
|
|
9
|
+
<span className="text-sm text-muted-foreground">Welcome, {(user as any)?.name || user?.email}</span>
|
|
10
|
+
<Button
|
|
11
|
+
variant="outline"
|
|
12
|
+
size="sm"
|
|
13
|
+
onClick={logout}
|
|
14
|
+
>
|
|
15
|
+
Logout
|
|
16
|
+
</Button>
|
|
17
|
+
</div>
|
|
18
|
+
);
|
|
19
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useAuth } from '../hooks/useAuth';
|
|
3
|
+
import { LoginForm } from './LoginForm';
|
|
4
|
+
|
|
5
|
+
interface ProtectedRouteProps {
|
|
6
|
+
children: ReactNode;
|
|
7
|
+
requirePermission?: string;
|
|
8
|
+
requireRole?: string;
|
|
9
|
+
fallback?: ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function ProtectedRoute({
|
|
13
|
+
children,
|
|
14
|
+
requirePermission,
|
|
15
|
+
requireRole,
|
|
16
|
+
fallback
|
|
17
|
+
}: ProtectedRouteProps) {
|
|
18
|
+
const { isAuthenticated, isLoading, hasPermission, hasRole } = useAuth();
|
|
19
|
+
|
|
20
|
+
if (isLoading) {
|
|
21
|
+
return (
|
|
22
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
23
|
+
<div className="text-center">
|
|
24
|
+
<div className="animate-spin rounded-full h-8 w-8 border-b-2 border-primary mx-auto"></div>
|
|
25
|
+
<p className="mt-2 text-muted-foreground">Loading...</p>
|
|
26
|
+
</div>
|
|
27
|
+
</div>
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
if (!isAuthenticated) {
|
|
32
|
+
return <LoginForm />;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Check permission if required
|
|
36
|
+
if (requirePermission && !hasPermission(requirePermission)) {
|
|
37
|
+
return fallback || (
|
|
38
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
39
|
+
<div className="text-center">
|
|
40
|
+
<h2 className="text-xl font-semibold text-destructive mb-2">Access Denied</h2>
|
|
41
|
+
<p className="text-muted-foreground">You don't have permission to access this resource.</p>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// Check role if required
|
|
48
|
+
if (requireRole && !hasRole(requireRole)) {
|
|
49
|
+
return fallback || (
|
|
50
|
+
<div className="min-h-screen flex items-center justify-center">
|
|
51
|
+
<div className="text-center">
|
|
52
|
+
<h2 className="text-xl font-semibold text-destructive mb-2">Access Denied</h2>
|
|
53
|
+
<p className="text-muted-foreground">Your role doesn't have access to this resource.</p>
|
|
54
|
+
</div>
|
|
55
|
+
</div>
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return <>{children}</>;
|
|
60
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { useState, useEffect, createContext, useContext, useMemo } from 'react';
|
|
2
|
+
import type { ReactNode } from 'react';
|
|
3
|
+
import { AuthService } from '../../../atoms/services/auth-service';
|
|
4
|
+
import { setGlobalAuthService } from '../../../atoms/services/api/client';
|
|
5
|
+
import type {
|
|
6
|
+
AuthConfig,
|
|
7
|
+
BaseUser,
|
|
8
|
+
LoginCredentials,
|
|
9
|
+
AuthContextType
|
|
10
|
+
} from '../../../atoms/types';
|
|
11
|
+
|
|
12
|
+
const AuthContext = createContext<AuthContextType | undefined>(undefined);
|
|
13
|
+
|
|
14
|
+
export function useAuth<T extends BaseUser = BaseUser>(): AuthContextType<T> {
|
|
15
|
+
const context = useContext(AuthContext);
|
|
16
|
+
if (context === undefined) {
|
|
17
|
+
throw new Error('useAuth must be used within an AuthProvider');
|
|
18
|
+
}
|
|
19
|
+
return context as AuthContextType<T>;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface AuthProviderProps {
|
|
23
|
+
children: ReactNode;
|
|
24
|
+
config?: AuthConfig;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function AuthProvider<T extends BaseUser = BaseUser>({
|
|
28
|
+
children,
|
|
29
|
+
config
|
|
30
|
+
}: AuthProviderProps) {
|
|
31
|
+
const [user, setUser] = useState<T | null>(null);
|
|
32
|
+
const [isLoading, setIsLoading] = useState(true);
|
|
33
|
+
const [permissions, setPermissions] = useState<string[]>([]);
|
|
34
|
+
|
|
35
|
+
// Create auth service with default config if none provided
|
|
36
|
+
const authService = useMemo(() => {
|
|
37
|
+
const defaultConfig: AuthConfig = {
|
|
38
|
+
apiUrl: import.meta.env.VITE_API_BASE_URL || 'http://localhost:8080',
|
|
39
|
+
endpoints: {
|
|
40
|
+
login: '/auth/login',
|
|
41
|
+
register: '/auth/register',
|
|
42
|
+
refresh: '/auth/refresh',
|
|
43
|
+
me: '/auth/me',
|
|
44
|
+
logout: '/auth/logout'
|
|
45
|
+
},
|
|
46
|
+
tokenStorage: 'localStorage',
|
|
47
|
+
tokenRefreshBuffer: 5,
|
|
48
|
+
autoRefresh: true,
|
|
49
|
+
permissions: {
|
|
50
|
+
enabled: false
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
const mergedConfig = config ? { ...defaultConfig, ...config } : defaultConfig;
|
|
55
|
+
const service = new AuthService<T>(mergedConfig);
|
|
56
|
+
|
|
57
|
+
// Set global reference for API client
|
|
58
|
+
setGlobalAuthService(service);
|
|
59
|
+
|
|
60
|
+
return service;
|
|
61
|
+
}, [config]);
|
|
62
|
+
|
|
63
|
+
const isAuthenticated = !!user;
|
|
64
|
+
|
|
65
|
+
useEffect(() => {
|
|
66
|
+
// Check for existing auth on mount
|
|
67
|
+
const initAuth = async () => {
|
|
68
|
+
setIsLoading(true);
|
|
69
|
+
|
|
70
|
+
try {
|
|
71
|
+
// Check for stored user
|
|
72
|
+
const storedUser = authService.getStoredUser();
|
|
73
|
+
const tokenData = authService.getTokenData();
|
|
74
|
+
|
|
75
|
+
if (storedUser && tokenData?.token) {
|
|
76
|
+
if (authService.isTokenExpired()) {
|
|
77
|
+
// Token expired, try to refresh
|
|
78
|
+
if (tokenData.refreshToken) {
|
|
79
|
+
try {
|
|
80
|
+
await authService.refreshToken();
|
|
81
|
+
setUser(storedUser);
|
|
82
|
+
|
|
83
|
+
// Load fresh user data if possible
|
|
84
|
+
try {
|
|
85
|
+
const freshUser = await authService.getCurrentUser();
|
|
86
|
+
if (freshUser) setUser(freshUser);
|
|
87
|
+
} catch {
|
|
88
|
+
// Keep stored user if fresh fetch fails
|
|
89
|
+
}
|
|
90
|
+
} catch {
|
|
91
|
+
// Refresh failed, clear auth
|
|
92
|
+
authService.clearAuth();
|
|
93
|
+
}
|
|
94
|
+
} else {
|
|
95
|
+
// No refresh token, clear auth
|
|
96
|
+
authService.clearAuth();
|
|
97
|
+
}
|
|
98
|
+
} else {
|
|
99
|
+
// Token still valid
|
|
100
|
+
setUser(storedUser);
|
|
101
|
+
|
|
102
|
+
// Optionally validate with server
|
|
103
|
+
try {
|
|
104
|
+
const freshUser = await authService.getCurrentUser();
|
|
105
|
+
if (freshUser) setUser(freshUser);
|
|
106
|
+
} catch {
|
|
107
|
+
// Keep stored user if validation fails
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
} catch (error) {
|
|
112
|
+
console.error('Auth initialization error:', error);
|
|
113
|
+
authService.clearAuth();
|
|
114
|
+
} finally {
|
|
115
|
+
setIsLoading(false);
|
|
116
|
+
}
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
initAuth();
|
|
120
|
+
}, [authService]);
|
|
121
|
+
|
|
122
|
+
const login = async (credentials: LoginCredentials) => {
|
|
123
|
+
setIsLoading(true);
|
|
124
|
+
try {
|
|
125
|
+
const user = await authService.login(credentials);
|
|
126
|
+
setUser(user);
|
|
127
|
+
|
|
128
|
+
// Extract permissions if user has roles/permissions
|
|
129
|
+
if (config?.permissions?.enabled && 'permissions' in user) {
|
|
130
|
+
setPermissions((user as any).permissions || []);
|
|
131
|
+
}
|
|
132
|
+
} catch (error) {
|
|
133
|
+
throw error;
|
|
134
|
+
} finally {
|
|
135
|
+
setIsLoading(false);
|
|
136
|
+
}
|
|
137
|
+
};
|
|
138
|
+
|
|
139
|
+
const logout = async () => {
|
|
140
|
+
setIsLoading(true);
|
|
141
|
+
try {
|
|
142
|
+
await authService.logout();
|
|
143
|
+
setUser(null);
|
|
144
|
+
setPermissions([]);
|
|
145
|
+
} catch (error) {
|
|
146
|
+
console.error('Logout error:', error);
|
|
147
|
+
// Clear local state anyway
|
|
148
|
+
authService.clearAuth();
|
|
149
|
+
setUser(null);
|
|
150
|
+
setPermissions([]);
|
|
151
|
+
} finally {
|
|
152
|
+
setIsLoading(false);
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
const refreshToken = async () => {
|
|
157
|
+
await authService.refreshToken();
|
|
158
|
+
};
|
|
159
|
+
|
|
160
|
+
const hasPermission = (permission: string): boolean => {
|
|
161
|
+
if (!config?.permissions?.enabled) return true;
|
|
162
|
+
return permissions.includes(permission);
|
|
163
|
+
};
|
|
164
|
+
|
|
165
|
+
const hasRole = (role: string): boolean => {
|
|
166
|
+
if (!config?.permissions?.enabled) return true;
|
|
167
|
+
|
|
168
|
+
// Check if user has role property
|
|
169
|
+
if (user && 'role' in user) {
|
|
170
|
+
return (user as any).role === role;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// Check if user has roles array
|
|
174
|
+
if (user && 'roles' in user) {
|
|
175
|
+
return ((user as any).roles || []).includes(role);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Fallback: check admin/user roles from config
|
|
179
|
+
const { adminRoles = ['admin'], userRoles = ['user'] } = config.permissions || {};
|
|
180
|
+
|
|
181
|
+
if (adminRoles.includes(role)) {
|
|
182
|
+
return permissions.includes('admin') || permissions.includes('*');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
if (userRoles.includes(role)) {
|
|
186
|
+
return permissions.length > 0; // Any permissions = user role
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
return false;
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const value: AuthContextType<T> = {
|
|
193
|
+
user,
|
|
194
|
+
isAuthenticated,
|
|
195
|
+
isLoading,
|
|
196
|
+
permissions,
|
|
197
|
+
login,
|
|
198
|
+
logout,
|
|
199
|
+
refreshToken,
|
|
200
|
+
hasPermission,
|
|
201
|
+
hasRole,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
|
|
205
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { useAuth } from './useAuth';
|
|
2
|
+
import type { BaseUser } from '../../../atoms/types';
|
|
3
|
+
|
|
4
|
+
export function usePermissions<T extends BaseUser = BaseUser>() {
|
|
5
|
+
const { hasPermission, hasRole, permissions, user } = useAuth<T>();
|
|
6
|
+
|
|
7
|
+
const can = (permission: string): boolean => hasPermission(permission);
|
|
8
|
+
|
|
9
|
+
const canAny = (permissions: string[]): boolean =>
|
|
10
|
+
permissions.some(permission => hasPermission(permission));
|
|
11
|
+
|
|
12
|
+
const canAll = (permissions: string[]): boolean =>
|
|
13
|
+
permissions.every(permission => hasPermission(permission));
|
|
14
|
+
|
|
15
|
+
const isRole = (role: string): boolean => hasRole(role);
|
|
16
|
+
|
|
17
|
+
const isAnyRole = (roles: string[]): boolean =>
|
|
18
|
+
roles.some(role => hasRole(role));
|
|
19
|
+
|
|
20
|
+
const isAdmin = (): boolean => hasRole('admin');
|
|
21
|
+
|
|
22
|
+
const isUser = (): boolean => hasRole('user');
|
|
23
|
+
|
|
24
|
+
return {
|
|
25
|
+
can,
|
|
26
|
+
canAny,
|
|
27
|
+
canAll,
|
|
28
|
+
isRole,
|
|
29
|
+
isAnyRole,
|
|
30
|
+
isAdmin,
|
|
31
|
+
isUser,
|
|
32
|
+
permissions,
|
|
33
|
+
user
|
|
34
|
+
};
|
|
35
|
+
}
|