@pattern-stack/frontend-patterns 0.0.1 → 0.0.3

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.
Files changed (153) hide show
  1. package/README.md +6 -6
  2. package/package.json +3 -5
  3. package/src/App.css +0 -42
  4. package/src/App.tsx +0 -54
  5. package/src/__tests__/README.md +0 -221
  6. package/src/__tests__/atoms/hooks/simple-hooks.test.ts +0 -44
  7. package/src/__tests__/atoms/ui/button.test.tsx +0 -68
  8. package/src/__tests__/atoms/utils/simple.test.ts +0 -18
  9. package/src/__tests__/atoms/utils/utils.test.ts +0 -77
  10. package/src/__tests__/features/auth/simple-auth.test.tsx +0 -40
  11. package/src/__tests__/molecules/layout/simple-layout.test.tsx +0 -81
  12. package/src/__tests__/organisms/showcase/simple-showcase.test.tsx +0 -167
  13. package/src/__tests__/setup.ts +0 -51
  14. package/src/__tests__/utils.tsx +0 -123
  15. package/src/atoms/composed/Accordion/Accordion.tsx +0 -271
  16. package/src/atoms/composed/Accordion/index.ts +0 -1
  17. package/src/atoms/composed/Alert/Alert.tsx +0 -132
  18. package/src/atoms/composed/Alert/index.ts +0 -1
  19. package/src/atoms/composed/Breadcrumb/Breadcrumb.tsx +0 -83
  20. package/src/atoms/composed/Breadcrumb/index.ts +0 -1
  21. package/src/atoms/composed/Chart/Chart.tsx +0 -425
  22. package/src/atoms/composed/Chart/index.ts +0 -2
  23. package/src/atoms/composed/ColorSwatch/ColorSwatch.tsx +0 -72
  24. package/src/atoms/composed/ColorSwatch/index.ts +0 -1
  25. package/src/atoms/composed/DarkModeToggle.tsx +0 -66
  26. package/src/atoms/composed/DataBadge/DataBadge.tsx +0 -81
  27. package/src/atoms/composed/DataBadge/index.ts +0 -1
  28. package/src/atoms/composed/DataTable/DataTable.tsx +0 -394
  29. package/src/atoms/composed/DataTable/TableCellWithTooltip.tsx +0 -41
  30. package/src/atoms/composed/DataTable/index.ts +0 -2
  31. package/src/atoms/composed/DateTimePicker/DateTimePicker.tsx +0 -611
  32. package/src/atoms/composed/DateTimePicker/index.ts +0 -2
  33. package/src/atoms/composed/DetailedCard/DetailedCard.tsx +0 -181
  34. package/src/atoms/composed/DetailedCard/index.ts +0 -2
  35. package/src/atoms/composed/EmptyState/EmptyState.tsx +0 -90
  36. package/src/atoms/composed/EmptyState/index.ts +0 -1
  37. package/src/atoms/composed/FileUpload/FileUpload.tsx +0 -477
  38. package/src/atoms/composed/FileUpload/index.ts +0 -2
  39. package/src/atoms/composed/FormField/FormField.tsx +0 -92
  40. package/src/atoms/composed/FormField/index.ts +0 -1
  41. package/src/atoms/composed/GlobalSearch/GlobalSearch.tsx +0 -37
  42. package/src/atoms/composed/GlobalSearch/index.ts +0 -1
  43. package/src/atoms/composed/IconBadge/IconBadge.tsx +0 -95
  44. package/src/atoms/composed/IconBadge/index.ts +0 -2
  45. package/src/atoms/composed/Modal/Modal.tsx +0 -223
  46. package/src/atoms/composed/Modal/index.ts +0 -2
  47. package/src/atoms/composed/PaletteSwitcher.tsx +0 -386
  48. package/src/atoms/composed/ProgressBar/ProgressBar.tsx +0 -116
  49. package/src/atoms/composed/ProgressBar/index.ts +0 -1
  50. package/src/atoms/composed/StatCard/StatCard.tsx +0 -219
  51. package/src/atoms/composed/StatCard/index.ts +0 -1
  52. package/src/atoms/composed/StyleGuide.tsx +0 -717
  53. package/src/atoms/composed/Toast/Toast.tsx +0 -219
  54. package/src/atoms/composed/Toast/index.ts +0 -1
  55. package/src/atoms/composed/Tooltip/Tooltip.tsx +0 -213
  56. package/src/atoms/composed/Tooltip/index.ts +0 -1
  57. package/src/atoms/composed/UserAvatar/UserAvatar.tsx +0 -139
  58. package/src/atoms/composed/UserAvatar/index.ts +0 -1
  59. package/src/atoms/composed/UserMenu/UserMenu.tsx +0 -16
  60. package/src/atoms/composed/UserMenu/index.ts +0 -1
  61. package/src/atoms/composed/index.ts +0 -29
  62. package/src/atoms/hooks/useApi.ts +0 -80
  63. package/src/atoms/hooks/useHealth.ts +0 -17
  64. package/src/atoms/index.ts +0 -13
  65. package/src/atoms/services/api/client.ts +0 -134
  66. package/src/atoms/services/auth-service.ts +0 -248
  67. package/src/atoms/services/health.ts +0 -15
  68. package/src/atoms/services/index.ts +0 -3
  69. package/src/atoms/shared/config/constants.ts +0 -17
  70. package/src/atoms/shared/config/dashboard-sizes.ts +0 -111
  71. package/src/atoms/shared/config/environment.ts +0 -10
  72. package/src/atoms/shared/index.ts +0 -4
  73. package/src/atoms/shared/styles/color-palettes.css +0 -566
  74. package/src/atoms/types/auth.ts +0 -62
  75. package/src/atoms/types/generated.ts +0 -1469
  76. package/src/atoms/types/index.ts +0 -4
  77. package/src/atoms/types/loading.ts +0 -28
  78. package/src/atoms/ui/Badge.tsx +0 -30
  79. package/src/atoms/ui/ErrorBoundary.tsx +0 -59
  80. package/src/atoms/ui/Select.tsx +0 -53
  81. package/src/atoms/ui/Switch.tsx +0 -42
  82. package/src/atoms/ui/Tabs.tsx +0 -118
  83. package/src/atoms/ui/avatar.tsx +0 -48
  84. package/src/atoms/ui/button.tsx +0 -70
  85. package/src/atoms/ui/card.tsx +0 -76
  86. package/src/atoms/ui/dropdown-menu.tsx +0 -199
  87. package/src/atoms/ui/index.ts +0 -39
  88. package/src/atoms/ui/input.tsx +0 -23
  89. package/src/atoms/ui/label.tsx +0 -23
  90. package/src/atoms/ui/skeleton.tsx +0 -13
  91. package/src/atoms/ui/spinner.tsx +0 -49
  92. package/src/atoms/ui/table.tsx +0 -116
  93. package/src/atoms/utils/animations.ts +0 -135
  94. package/src/atoms/utils/tooltip-helpers.ts +0 -140
  95. package/src/atoms/utils/utils.ts +0 -9
  96. package/src/features/auth/components/LoginForm.tsx +0 -168
  97. package/src/features/auth/components/LogoutButton.tsx +0 -19
  98. package/src/features/auth/components/ProtectedRoute.tsx +0 -60
  99. package/src/features/auth/components/index.ts +0 -4
  100. package/src/features/auth/hooks/index.ts +0 -2
  101. package/src/features/auth/hooks/useAuth.tsx +0 -205
  102. package/src/features/auth/hooks/usePermissions.ts +0 -35
  103. package/src/features/auth/index.ts +0 -2
  104. package/src/features/index.ts +0 -2
  105. package/src/index.css +0 -704
  106. package/src/index.ts +0 -13
  107. package/src/main.tsx +0 -48
  108. package/src/molecules/.gitkeep +0 -0
  109. package/src/molecules/forms/FormGroup.tsx +0 -75
  110. package/src/molecules/forms/SearchInput.tsx +0 -259
  111. package/src/molecules/forms/index.ts +0 -4
  112. package/src/molecules/index.ts +0 -4
  113. package/src/molecules/layout/AppHeader/AppHeader.tsx +0 -42
  114. package/src/molecules/layout/AppHeader/index.ts +0 -1
  115. package/src/molecules/layout/AppLayout.tsx +0 -29
  116. package/src/molecules/layout/PageTemplate.tsx +0 -87
  117. package/src/molecules/layout/SectionHeader/SectionHeader.tsx +0 -87
  118. package/src/molecules/layout/SectionHeader/index.ts +0 -1
  119. package/src/molecules/layout/ShowcaseSection.tsx +0 -57
  120. package/src/molecules/layout/Sidebar.tsx +0 -144
  121. package/src/molecules/layout/SidebarButton/SidebarButton.tsx +0 -99
  122. package/src/molecules/layout/SidebarButton/index.ts +0 -1
  123. package/src/molecules/layout/SidebarContext.tsx +0 -31
  124. package/src/molecules/layout/index.ts +0 -7
  125. package/src/molecules/navigation/NavMenu.tsx +0 -188
  126. package/src/molecules/navigation/Pagination.tsx +0 -172
  127. package/src/molecules/navigation/index.ts +0 -4
  128. package/src/organisms/index.ts +0 -5
  129. package/src/organisms/showcase/ComponentShowcasePage.tsx +0 -2496
  130. package/src/organisms/showcase/index.ts +0 -1
  131. package/src/pages/AdminShowcase/AdminCRUDShowcase.tsx +0 -242
  132. package/src/pages/AdminShowcase/AdminDashboardShowcase.tsx +0 -171
  133. package/src/pages/AdminShowcase/AdminDetailShowcase.tsx +0 -385
  134. package/src/pages/AdminShowcase/index.tsx +0 -3
  135. package/src/pages/ComponentShowcase/BadgesShowcase.tsx +0 -188
  136. package/src/pages/ComponentShowcase/CardsShowcase.tsx +0 -392
  137. package/src/pages/ComponentShowcase/PalettesShowcase.tsx +0 -207
  138. package/src/pages/ComponentShowcase/StatesShowcase.tsx +0 -485
  139. package/src/pages/ComponentShowcase/TablesShowcase.tsx +0 -134
  140. package/src/pages/ComponentShowcase/TypographyShowcase.tsx +0 -255
  141. package/src/pages/ComponentShowcase/index.tsx +0 -188
  142. package/src/pages/index.ts +0 -2
  143. package/src/templates/AuthTemplate.tsx +0 -216
  144. package/src/templates/ComponentShowcaseTemplate.tsx +0 -173
  145. package/src/templates/DashboardTemplate.tsx +0 -232
  146. package/src/templates/DataTemplate.tsx +0 -319
  147. package/src/templates/admin/AdminCRUDTemplate.tsx +0 -630
  148. package/src/templates/admin/AdminDashboardTemplate.tsx +0 -351
  149. package/src/templates/admin/AdminDetailTemplate.tsx +0 -563
  150. package/src/templates/admin/index.ts +0 -29
  151. package/src/templates/factory.tsx +0 -169
  152. package/src/templates/index.ts +0 -37
  153. package/src/vite-env.d.ts +0 -1
@@ -1,140 +0,0 @@
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
- };
@@ -1,9 +0,0 @@
1
- import { clsx, type ClassValue } from "clsx"
2
- import { twMerge } from "tailwind-merge"
3
-
4
- export function cn(...inputs: ClassValue[]) {
5
- return twMerge(clsx(inputs))
6
- }
7
-
8
- // Re-export tooltip helpers
9
- export * from './tooltip-helpers';
@@ -1,168 +0,0 @@
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
- }
@@ -1,19 +0,0 @@
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
- }
@@ -1,60 +0,0 @@
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
- }
@@ -1,4 +0,0 @@
1
- export { LoginForm } from './LoginForm';
2
- export { ProtectedRoute } from './ProtectedRoute';
3
- export { LogoutButton } from './LogoutButton';
4
- // AuthProvider is exported from ../hooks/useAuth
@@ -1,2 +0,0 @@
1
- export { useAuth, AuthProvider } from './useAuth';
2
- export { usePermissions } from './usePermissions';
@@ -1,205 +0,0 @@
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
- }
@@ -1,35 +0,0 @@
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
- }
@@ -1,2 +0,0 @@
1
- export * from './components';
2
- export * from './hooks';
@@ -1,2 +0,0 @@
1
- // Features - Domain-specific functionality modules
2
- export * from './auth';