@jmruthers/pace-core 0.5.139 → 0.5.140

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 (116) hide show
  1. package/dist/utils.d.ts +1 -1
  2. package/dist/utils.js +16 -4
  3. package/dist/utils.js.map +1 -1
  4. package/docs/api/classes/ColumnFactory.md +1 -1
  5. package/docs/api/classes/ErrorBoundary.md +1 -1
  6. package/docs/api/classes/InvalidScopeError.md +1 -1
  7. package/docs/api/classes/MissingUserContextError.md +1 -1
  8. package/docs/api/classes/OrganisationContextRequiredError.md +1 -1
  9. package/docs/api/classes/PermissionDeniedError.md +1 -1
  10. package/docs/api/classes/PublicErrorBoundary.md +1 -1
  11. package/docs/api/classes/RBACAuditManager.md +1 -1
  12. package/docs/api/classes/RBACCache.md +1 -1
  13. package/docs/api/classes/RBACEngine.md +1 -1
  14. package/docs/api/classes/RBACError.md +1 -1
  15. package/docs/api/classes/RBACNotInitializedError.md +1 -1
  16. package/docs/api/classes/SecureSupabaseClient.md +1 -1
  17. package/docs/api/classes/StorageUtils.md +1 -1
  18. package/docs/api/enums/FileCategory.md +1 -1
  19. package/docs/api/interfaces/AggregateConfig.md +1 -1
  20. package/docs/api/interfaces/BadgeProps.md +1 -1
  21. package/docs/api/interfaces/ButtonProps.md +1 -1
  22. package/docs/api/interfaces/CardProps.md +1 -1
  23. package/docs/api/interfaces/ColorPalette.md +1 -1
  24. package/docs/api/interfaces/ColorShade.md +1 -1
  25. package/docs/api/interfaces/DataAccessRecord.md +1 -1
  26. package/docs/api/interfaces/DataRecord.md +1 -1
  27. package/docs/api/interfaces/DataTableAction.md +1 -1
  28. package/docs/api/interfaces/DataTableColumn.md +1 -1
  29. package/docs/api/interfaces/DataTableProps.md +1 -1
  30. package/docs/api/interfaces/DataTableToolbarButton.md +1 -1
  31. package/docs/api/interfaces/EmptyStateConfig.md +1 -1
  32. package/docs/api/interfaces/EnhancedNavigationMenuProps.md +1 -1
  33. package/docs/api/interfaces/EventAppRoleData.md +1 -1
  34. package/docs/api/interfaces/EventLogoProps.md +1 -1
  35. package/docs/api/interfaces/ExportColumn.md +1 -1
  36. package/docs/api/interfaces/ExportOptions.md +1 -1
  37. package/docs/api/interfaces/FileDisplayProps.md +1 -1
  38. package/docs/api/interfaces/FileMetadata.md +1 -1
  39. package/docs/api/interfaces/FileReference.md +1 -1
  40. package/docs/api/interfaces/FileSizeLimits.md +1 -1
  41. package/docs/api/interfaces/FileUploadOptions.md +1 -1
  42. package/docs/api/interfaces/FileUploadProps.md +1 -1
  43. package/docs/api/interfaces/FooterProps.md +1 -1
  44. package/docs/api/interfaces/GrantEventAppRoleParams.md +1 -1
  45. package/docs/api/interfaces/InactivityWarningModalProps.md +1 -1
  46. package/docs/api/interfaces/InputProps.md +1 -1
  47. package/docs/api/interfaces/LabelProps.md +1 -1
  48. package/docs/api/interfaces/LoginFormProps.md +1 -1
  49. package/docs/api/interfaces/NavigationAccessRecord.md +1 -1
  50. package/docs/api/interfaces/NavigationContextType.md +1 -1
  51. package/docs/api/interfaces/NavigationGuardProps.md +1 -1
  52. package/docs/api/interfaces/NavigationItem.md +1 -1
  53. package/docs/api/interfaces/NavigationMenuProps.md +1 -1
  54. package/docs/api/interfaces/NavigationProviderProps.md +1 -1
  55. package/docs/api/interfaces/Organisation.md +1 -1
  56. package/docs/api/interfaces/OrganisationContextType.md +1 -1
  57. package/docs/api/interfaces/OrganisationMembership.md +1 -1
  58. package/docs/api/interfaces/OrganisationProviderProps.md +1 -1
  59. package/docs/api/interfaces/OrganisationSecurityError.md +1 -1
  60. package/docs/api/interfaces/PaceAppLayoutProps.md +1 -1
  61. package/docs/api/interfaces/PaceLoginPageProps.md +1 -1
  62. package/docs/api/interfaces/PageAccessRecord.md +1 -1
  63. package/docs/api/interfaces/PagePermissionContextType.md +1 -1
  64. package/docs/api/interfaces/PagePermissionGuardProps.md +1 -1
  65. package/docs/api/interfaces/PagePermissionProviderProps.md +1 -1
  66. package/docs/api/interfaces/PaletteData.md +1 -1
  67. package/docs/api/interfaces/PermissionEnforcerProps.md +1 -1
  68. package/docs/api/interfaces/ProtectedRouteProps.md +1 -1
  69. package/docs/api/interfaces/PublicErrorBoundaryProps.md +1 -1
  70. package/docs/api/interfaces/PublicErrorBoundaryState.md +1 -1
  71. package/docs/api/interfaces/PublicLoadingSpinnerProps.md +1 -1
  72. package/docs/api/interfaces/PublicPageFooterProps.md +1 -1
  73. package/docs/api/interfaces/PublicPageHeaderProps.md +1 -1
  74. package/docs/api/interfaces/PublicPageLayoutProps.md +1 -1
  75. package/docs/api/interfaces/RBACConfig.md +1 -1
  76. package/docs/api/interfaces/RBACLogger.md +1 -1
  77. package/docs/api/interfaces/RevokeEventAppRoleParams.md +1 -1
  78. package/docs/api/interfaces/RoleBasedRouterContextType.md +1 -1
  79. package/docs/api/interfaces/RoleBasedRouterProps.md +1 -1
  80. package/docs/api/interfaces/RoleManagementResult.md +1 -1
  81. package/docs/api/interfaces/RouteAccessRecord.md +1 -1
  82. package/docs/api/interfaces/RouteConfig.md +1 -1
  83. package/docs/api/interfaces/SecureDataContextType.md +1 -1
  84. package/docs/api/interfaces/SecureDataProviderProps.md +1 -1
  85. package/docs/api/interfaces/SessionRestorationLoaderProps.md +1 -1
  86. package/docs/api/interfaces/StorageConfig.md +1 -1
  87. package/docs/api/interfaces/StorageFileInfo.md +1 -1
  88. package/docs/api/interfaces/StorageFileMetadata.md +1 -1
  89. package/docs/api/interfaces/StorageListOptions.md +1 -1
  90. package/docs/api/interfaces/StorageListResult.md +1 -1
  91. package/docs/api/interfaces/StorageUploadOptions.md +1 -1
  92. package/docs/api/interfaces/StorageUploadResult.md +1 -1
  93. package/docs/api/interfaces/StorageUrlOptions.md +1 -1
  94. package/docs/api/interfaces/StyleImport.md +1 -1
  95. package/docs/api/interfaces/SwitchProps.md +1 -1
  96. package/docs/api/interfaces/ToastActionElement.md +1 -1
  97. package/docs/api/interfaces/ToastProps.md +1 -1
  98. package/docs/api/interfaces/UnifiedAuthContextType.md +1 -1
  99. package/docs/api/interfaces/UnifiedAuthProviderProps.md +1 -1
  100. package/docs/api/interfaces/UseInactivityTrackerOptions.md +1 -1
  101. package/docs/api/interfaces/UseInactivityTrackerReturn.md +1 -1
  102. package/docs/api/interfaces/UsePublicEventOptions.md +1 -1
  103. package/docs/api/interfaces/UsePublicEventReturn.md +1 -1
  104. package/docs/api/interfaces/UsePublicFileDisplayOptions.md +1 -1
  105. package/docs/api/interfaces/UsePublicFileDisplayReturn.md +1 -1
  106. package/docs/api/interfaces/UsePublicRouteParamsReturn.md +1 -1
  107. package/docs/api/interfaces/UseResolvedScopeOptions.md +1 -1
  108. package/docs/api/interfaces/UseResolvedScopeReturn.md +1 -1
  109. package/docs/api/interfaces/UserEventAccess.md +1 -1
  110. package/docs/api/interfaces/UserMenuProps.md +1 -1
  111. package/docs/api/interfaces/UserProfile.md +1 -1
  112. package/docs/api/modules.md +2 -2
  113. package/docs/getting-started/examples/basic-auth-app.md +196 -0
  114. package/docs/getting-started/examples/full-featured-app.md +616 -0
  115. package/package.json +1 -1
  116. package/src/utils/performance/bundleAnalysis.ts +17 -3
@@ -0,0 +1,616 @@
1
+ # Full-Featured Application
2
+
3
+ A complete meal management application demonstrating all PACE Core features including authentication, RBAC, data tables, forms, and permission enforcement.
4
+
5
+ ## 🎯 What This Example Shows
6
+
7
+ - Complete authentication flow with RBAC
8
+ - Multi-tenant organisation support
9
+ - Event management and selection
10
+ - Advanced DataTable with CRUD operations
11
+ - Form validation and error handling
12
+ - Permission enforcement at page and component level
13
+ - Responsive layout with navigation
14
+ - Production-ready patterns
15
+
16
+ ## 📁 Project Structure
17
+
18
+ ```
19
+ src/
20
+ ├── App.tsx # Main app with all providers
21
+ ├── lib/
22
+ │ └── supabase.ts # Supabase client
23
+ ├── components/
24
+ │ ├── auth/
25
+ │ │ ├── LoginPage.tsx # Login form
26
+ │ │ └── ProtectedRoute.tsx # Route protection
27
+ │ ├── layout/
28
+ │ │ ├── AppLayout.tsx # Main layout
29
+ │ │ └── Navigation.tsx # Navigation menu
30
+ │ ├── dashboard/
31
+ │ │ └── DashboardPage.tsx # Dashboard with metrics
32
+ │ ├── meals/
33
+ │ │ ├── MealsPage.tsx # Meals list with DataTable
34
+ │ │ ├── MealForm.tsx # Add/edit meal form
35
+ │ │ └── MealModal.tsx # Modal for meal operations
36
+ │ ├── users/
37
+ │ │ ├── UsersPage.tsx # User management
38
+ │ │ └── UserForm.tsx # User creation/editing
39
+ │ └── shared/
40
+ │ ├── LoadingSpinner.tsx # Loading states
41
+ │ └── ErrorBoundary.tsx # Error handling
42
+ ├── hooks/
43
+ │ ├── useMeals.ts # Meals data management
44
+ │ └── useUsers.ts # Users data management
45
+ ├── types/
46
+ │ ├── meal.ts # Meal type definitions
47
+ │ └── user.ts # User type definitions
48
+ └── utils/
49
+ ├── permissions.ts # Permission checking
50
+ └── validation.ts # Form validation schemas
51
+ ```
52
+
53
+ ## 🚀 Implementation
54
+
55
+ ### 1. App.tsx - Complete Application Setup
56
+
57
+ ```tsx
58
+ import {
59
+ UnifiedAuthProvider,
60
+ OrganisationProvider,
61
+ EventProvider,
62
+ ErrorBoundary
63
+ } from '@jmruthers/pace-core';
64
+ import { BrowserRouter } from 'react-router-dom';
65
+ import { supabase } from './lib/supabase';
66
+ import { AppRoutes } from './components/AppRoutes';
67
+
68
+ function App() {
69
+ return (
70
+ <ErrorBoundary>
71
+ <UnifiedAuthProvider
72
+ supabaseClient={supabase}
73
+ appName="meal-manager"
74
+ enableRBAC={true}
75
+ requireOrganisationContext={true}
76
+ persistState={true}
77
+ >
78
+ <OrganisationProvider>
79
+ <EventProvider>
80
+ <BrowserRouter>
81
+ <AppRoutes />
82
+ </BrowserRouter>
83
+ </EventProvider>
84
+ </OrganisationProvider>
85
+ </UnifiedAuthProvider>
86
+ </ErrorBoundary>
87
+ );
88
+ }
89
+
90
+ export default App;
91
+ ```
92
+
93
+ ### 2. AppRoutes.tsx - Route Configuration
94
+
95
+ ```tsx
96
+ import { Routes, Route, Navigate } from 'react-router-dom';
97
+ import { ProtectedRoute } from './auth/ProtectedRoute';
98
+ import { AppLayout } from './layout/AppLayout';
99
+ import { LoginPage } from './auth/LoginPage';
100
+ import { DashboardPage } from './dashboard/DashboardPage';
101
+ import { MealsPage } from './meals/MealsPage';
102
+ import { UsersPage } from './users/UsersPage';
103
+
104
+ export function AppRoutes() {
105
+ return (
106
+ <Routes>
107
+ <Route path="/login" element={<LoginPage />} />
108
+
109
+ <Route path="/" element={
110
+ <ProtectedRoute>
111
+ <AppLayout />
112
+ </ProtectedRoute>
113
+ }>
114
+ <Route index element={<DashboardPage />} />
115
+ <Route path="meals" element={<MealsPage />} />
116
+ <Route path="users" element={<UsersPage />} />
117
+ </Route>
118
+
119
+ <Route path="*" element={<Navigate to="/" replace />} />
120
+ </Routes>
121
+ );
122
+ }
123
+ ```
124
+
125
+ ### 3. ProtectedRoute.tsx - Route Protection
126
+
127
+ ```tsx
128
+ import { useUnifiedAuth } from '@jmruthers/pace-core';
129
+ import { Navigate, useLocation } from 'react-router-dom';
130
+ import { LoadingSpinner } from '../shared/LoadingSpinner';
131
+
132
+ interface ProtectedRouteProps {
133
+ children: React.ReactNode;
134
+ requiredPermission?: string;
135
+ }
136
+
137
+ export function ProtectedRoute({ children, requiredPermission }: ProtectedRouteProps) {
138
+ const { user, loading, hasPermission } = useUnifiedAuth();
139
+ const location = useLocation();
140
+
141
+ if (loading) {
142
+ return <LoadingSpinner />;
143
+ }
144
+
145
+ if (!user) {
146
+ return <Navigate to="/login" state={{ from: location }} replace />;
147
+ }
148
+
149
+ if (requiredPermission && !hasPermission(requiredPermission)) {
150
+ return <Navigate to="/unauthorized" replace />;
151
+ }
152
+
153
+ return <>{children}</>;
154
+ }
155
+ ```
156
+
157
+ ### 4. AppLayout.tsx - Main Layout
158
+
159
+ ```tsx
160
+ import { PaceAppLayout, OrganisationSelector, EventSelector } from '@jmruthers/pace-core';
161
+ import { Outlet } from 'react-router-dom';
162
+
163
+ const navItems = [
164
+ {
165
+ title: 'Dashboard',
166
+ href: '/',
167
+ icon: 'HomeIcon',
168
+ permission: 'read:dashboard'
169
+ },
170
+ {
171
+ title: 'Meals',
172
+ href: '/meals',
173
+ icon: 'UtensilsIcon',
174
+ permission: 'read:meals'
175
+ },
176
+ {
177
+ title: 'Users',
178
+ href: '/users',
179
+ icon: 'UsersIcon',
180
+ permission: 'read:users'
181
+ }
182
+ ];
183
+
184
+ export function AppLayout() {
185
+ return (
186
+ <PaceAppLayout
187
+ appName="Meal Manager"
188
+ navItems={navItems}
189
+ enforcePermissions={true}
190
+ defaultPermission="read"
191
+ headerActions={
192
+ <>
193
+ <OrganisationSelector />
194
+ <EventSelector />
195
+ </>
196
+ }
197
+ >
198
+ <Outlet />
199
+ </PaceAppLayout>
200
+ );
201
+ }
202
+ ```
203
+
204
+ ### 5. DashboardPage.tsx - Dashboard with Metrics
205
+
206
+ ```tsx
207
+ import { Card, CardHeader, CardTitle, CardContent } from '@jmruthers/pace-core';
208
+ import { useUnifiedAuth } from '@jmruthers/pace-core';
209
+ import { useMeals } from '../../hooks/useMeals';
210
+ import { useUsers } from '../../hooks/useUsers';
211
+
212
+ export function DashboardPage() {
213
+ const { user } = useUnifiedAuth();
214
+ const { meals, loading: mealsLoading } = useMeals();
215
+ const { users, loading: usersLoading } = useUsers();
216
+
217
+ const totalMeals = meals?.length || 0;
218
+ const totalUsers = users?.length || 0;
219
+ const thisWeekMeals = meals?.filter(meal => {
220
+ const weekAgo = new Date();
221
+ weekAgo.setDate(weekAgo.getDate() - 7);
222
+ return new Date(meal.date) >= weekAgo;
223
+ }).length || 0;
224
+
225
+ return (
226
+ <div className="p-6">
227
+ <div className="mb-8">
228
+ <h1 className="text-3xl font-bold text-sec-900">Dashboard</h1>
229
+ <p className="text-sec-600 mt-2">
230
+ Welcome back, {user?.email}. Here's what's happening with your meals.
231
+ </p>
232
+ </div>
233
+
234
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-6 mb-8">
235
+ <Card>
236
+ <CardHeader>
237
+ <CardTitle>Total Meals</CardTitle>
238
+ </CardHeader>
239
+ <CardContent>
240
+ <p className="text-3xl font-bold">{mealsLoading ? '...' : totalMeals}</p>
241
+ <p className="text-sm text-sec-600">All time</p>
242
+ </CardContent>
243
+ </Card>
244
+
245
+ <Card>
246
+ <CardHeader>
247
+ <CardTitle>This Week</CardTitle>
248
+ </CardHeader>
249
+ <CardContent>
250
+ <p className="text-3xl font-bold">{mealsLoading ? '...' : thisWeekMeals}</p>
251
+ <p className="text-sm text-sec-600">Last 7 days</p>
252
+ </CardContent>
253
+ </Card>
254
+
255
+ <Card>
256
+ <CardHeader>
257
+ <CardTitle>Total Users</CardTitle>
258
+ </CardHeader>
259
+ <CardContent>
260
+ <p className="text-3xl font-bold">{usersLoading ? '...' : totalUsers}</p>
261
+ <p className="text-sm text-sec-600">Active users</p>
262
+ </CardContent>
263
+ </Card>
264
+ </div>
265
+
266
+ {/* Recent Activity */}
267
+ <Card>
268
+ <CardHeader>
269
+ <CardTitle>Recent Activity</CardTitle>
270
+ </CardHeader>
271
+ <CardContent>
272
+ <p className="text-sec-600">
273
+ Your meal tracking activity will appear here.
274
+ </p>
275
+ </CardContent>
276
+ </Card>
277
+ </div>
278
+ );
279
+ }
280
+ ```
281
+
282
+ ### 6. MealsPage.tsx - Advanced DataTable
283
+
284
+ ```tsx
285
+ import { useState } from 'react';
286
+ import {
287
+ DataTable,
288
+ Button,
289
+ useToast,
290
+ useUnifiedAuth
291
+ } from '@jmruthers/pace-core';
292
+ import { MealForm } from './MealForm';
293
+ import { useMeals } from '../../hooks/useMeals';
294
+ import { Meal } from '../../types/meal';
295
+
296
+ export function MealsPage() {
297
+ const [showForm, setShowForm] = useState(false);
298
+ const [editingMeal, setEditingMeal] = useState<Meal | null>(null);
299
+ const { meals, loading, createMeal, updateMeal, deleteMeal } = useMeals();
300
+ const { hasPermission } = useUnifiedAuth();
301
+ const { toast } = useToast();
302
+
303
+ const canCreate = hasPermission('create:meals');
304
+ const canEdit = hasPermission('update:meals');
305
+ const canDelete = hasPermission('delete:meals');
306
+
307
+ const columns = [
308
+ {
309
+ accessorKey: 'name',
310
+ header: 'Meal Name',
311
+ },
312
+ {
313
+ accessorKey: 'category',
314
+ header: 'Category',
315
+ },
316
+ {
317
+ accessorKey: 'calories',
318
+ header: 'Calories',
319
+ },
320
+ {
321
+ accessorKey: 'date',
322
+ header: 'Date',
323
+ },
324
+ {
325
+ id: 'actions',
326
+ header: 'Actions',
327
+ cell: ({ row }: any) => (
328
+ <div className="flex gap-2">
329
+ {canEdit && (
330
+ <Button
331
+ variant="outline"
332
+ size="sm"
333
+ onClick={() => setEditingMeal(row.original)}
334
+ >
335
+ Edit
336
+ </Button>
337
+ )}
338
+ {canDelete && (
339
+ <Button
340
+ variant="destructive"
341
+ size="sm"
342
+ onClick={() => handleDelete(row.original.id)}
343
+ >
344
+ Delete
345
+ </Button>
346
+ )}
347
+ </div>
348
+ ),
349
+ },
350
+ ];
351
+
352
+ const handleDelete = async (id: string) => {
353
+ try {
354
+ await deleteMeal(id);
355
+ toast({
356
+ title: 'Success',
357
+ description: 'Meal deleted successfully',
358
+ });
359
+ } catch (error) {
360
+ toast({
361
+ title: 'Error',
362
+ description: 'Failed to delete meal',
363
+ variant: 'destructive',
364
+ });
365
+ }
366
+ };
367
+
368
+ const handleSubmit = async (mealData: Partial<Meal>) => {
369
+ try {
370
+ if (editingMeal) {
371
+ await updateMeal(editingMeal.id, mealData);
372
+ toast({
373
+ title: 'Success',
374
+ description: 'Meal updated successfully',
375
+ });
376
+ } else {
377
+ await createMeal(mealData);
378
+ toast({
379
+ title: 'Success',
380
+ description: 'Meal created successfully',
381
+ });
382
+ }
383
+ setShowForm(false);
384
+ setEditingMeal(null);
385
+ } catch (error) {
386
+ toast({
387
+ title: 'Error',
388
+ description: 'Failed to save meal',
389
+ variant: 'destructive',
390
+ });
391
+ }
392
+ };
393
+
394
+ return (
395
+ <div className="p-6">
396
+ <div className="flex justify-between items-center mb-6">
397
+ <h1 className="text-2xl font-bold">Meals</h1>
398
+ {canCreate && (
399
+ <Button onClick={() => setShowForm(true)}>
400
+ Add Meal
401
+ </Button>
402
+ )}
403
+ </div>
404
+
405
+ <DataTable
406
+ data={meals || []}
407
+ columns={columns}
408
+ isLoading={loading}
409
+ features={{
410
+ search: true,
411
+ pagination: true,
412
+ }}
413
+ onRowClick={(meal) => canEdit && setEditingMeal(meal)}
414
+ />
415
+
416
+ {showForm && (
417
+ <MealForm
418
+ meal={editingMeal}
419
+ onSubmit={handleSubmit}
420
+ onCancel={() => {
421
+ setShowForm(false);
422
+ setEditingMeal(null);
423
+ }}
424
+ />
425
+ )}
426
+ </div>
427
+ );
428
+ }
429
+ ```
430
+
431
+ ### 7. MealForm.tsx - Form with Validation
432
+
433
+ ```tsx
434
+ import { useForm } from 'react-hook-form';
435
+ import { zodResolver } from '@hookform/resolvers/zod';
436
+ import { z } from 'zod';
437
+ import {
438
+ Form,
439
+ FormField,
440
+ FormItem,
441
+ FormLabel,
442
+ FormControl,
443
+ FormMessage,
444
+ Button,
445
+ Input,
446
+ Select,
447
+ SelectContent,
448
+ SelectItem,
449
+ SelectTrigger,
450
+ SelectValue,
451
+ Card,
452
+ CardHeader,
453
+ CardTitle,
454
+ CardContent
455
+ } from '@jmruthers/pace-core';
456
+ import { Meal } from '../../types/meal';
457
+
458
+ const mealSchema = z.object({
459
+ name: z.string().min(1, 'Meal name is required'),
460
+ category: z.string().min(1, 'Category is required'),
461
+ calories: z.number().min(1, 'Calories must be greater than 0'),
462
+ date: z.string().min(1, 'Date is required'),
463
+ });
464
+
465
+ interface MealFormProps {
466
+ meal?: Meal | null;
467
+ onSubmit: (data: Partial<Meal>) => Promise<void>;
468
+ onCancel: () => void;
469
+ }
470
+
471
+ export function MealForm({ meal, onSubmit, onCancel }: MealFormProps) {
472
+ const form = useForm({
473
+ resolver: zodResolver(mealSchema),
474
+ defaultValues: {
475
+ name: meal?.name || '',
476
+ category: meal?.category || '',
477
+ calories: meal?.calories || 0,
478
+ date: meal?.date || new Date().toISOString().split('T')[0],
479
+ },
480
+ });
481
+
482
+ const handleSubmit = async (data: any) => {
483
+ await onSubmit(data);
484
+ };
485
+
486
+ return (
487
+ <Card className="mt-6">
488
+ <CardHeader>
489
+ <CardTitle>{meal ? 'Edit Meal' : 'Add New Meal'}</CardTitle>
490
+ </CardHeader>
491
+ <CardContent>
492
+ <Form {...form}>
493
+ <form onSubmit={form.handleSubmit(handleSubmit)} className="space-y-4">
494
+ <FormField
495
+ control={form.control}
496
+ name="name"
497
+ render={({ field }) => (
498
+ <FormItem>
499
+ <FormLabel>Meal Name</FormLabel>
500
+ <FormControl>
501
+ <Input placeholder="Enter meal name" {...field} />
502
+ </FormControl>
503
+ <FormMessage />
504
+ </FormItem>
505
+ )}
506
+ />
507
+
508
+ <FormField
509
+ control={form.control}
510
+ name="category"
511
+ render={({ field }) => (
512
+ <FormItem>
513
+ <FormLabel>Category</FormLabel>
514
+ <Select onValueChange={field.onChange} defaultValue={field.value}>
515
+ <FormControl>
516
+ <SelectTrigger>
517
+ <SelectValue placeholder="Select category" />
518
+ </SelectTrigger>
519
+ </FormControl>
520
+ <SelectContent>
521
+ <SelectItem value="breakfast">Breakfast</SelectItem>
522
+ <SelectItem value="lunch">Lunch</SelectItem>
523
+ <SelectItem value="dinner">Dinner</SelectItem>
524
+ <SelectItem value="snack">Snack</SelectItem>
525
+ </SelectContent>
526
+ </Select>
527
+ <FormMessage />
528
+ </FormItem>
529
+ )}
530
+ />
531
+
532
+ <FormField
533
+ control={form.control}
534
+ name="calories"
535
+ render={({ field }) => (
536
+ <FormItem>
537
+ <FormLabel>Calories</FormLabel>
538
+ <FormControl>
539
+ <Input
540
+ type="number"
541
+ placeholder="Enter calories"
542
+ {...field}
543
+ onChange={(e) => field.onChange(parseInt(e.target.value) || 0)}
544
+ />
545
+ </FormControl>
546
+ <FormMessage />
547
+ </FormItem>
548
+ )}
549
+ />
550
+
551
+ <FormField
552
+ control={form.control}
553
+ name="date"
554
+ render={({ field }) => (
555
+ <FormItem>
556
+ <FormLabel>Date</FormLabel>
557
+ <FormControl>
558
+ <Input type="date" {...field} />
559
+ </FormControl>
560
+ <FormMessage />
561
+ </FormItem>
562
+ )}
563
+ />
564
+
565
+ <div className="flex gap-2 pt-4">
566
+ <Button type="submit">
567
+ {meal ? 'Update Meal' : 'Add Meal'}
568
+ </Button>
569
+ <Button type="button" variant="outline" onClick={onCancel}>
570
+ Cancel
571
+ </Button>
572
+ </div>
573
+ </form>
574
+ </Form>
575
+ </CardContent>
576
+ </Card>
577
+ );
578
+ }
579
+ ```
580
+
581
+ ## 🔧 Environment Setup
582
+
583
+ Create `.env.local`:
584
+
585
+ ```bash
586
+ REACT_APP_SUPABASE_URL=https://your-project.supabase.co
587
+ REACT_APP_SUPABASE_ANON_KEY=your-anon-key-here
588
+ ```
589
+
590
+ ## 🎉 What You Get
591
+
592
+ - ✅ Complete authentication with RBAC
593
+ - ✅ Multi-tenant organisation support
594
+ - ✅ Event management
595
+ - ✅ Advanced DataTable with CRUD
596
+ - ✅ Form validation with Zod
597
+ - ✅ Permission enforcement
598
+ - ✅ Responsive layout
599
+ - ✅ Error handling and loading states
600
+ - ✅ Toast notifications
601
+ - ✅ Production-ready patterns
602
+
603
+ ## 🚀 Next Steps
604
+
605
+ - **Customize the design** - Adapt to your brand
606
+ - **Add more features** - Reporting, analytics, integrations
607
+ - **Implement real data** - Connect to your database
608
+ - **Add tests** - Unit and integration testing
609
+ - **Deploy** - Production deployment
610
+
611
+ ## 📚 Related Documentation
612
+
613
+ - **[Core Concepts](../core-concepts/)** - Understand authentication and RBAC
614
+ - **[Implementation Guides](../implementation-guides/)** - Advanced patterns
615
+ - **[API Reference](../api-reference/)** - All components and hooks
616
+ - **[Best Practices](../best-practices/)** - Production deployment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jmruthers/pace-core",
3
- "version": "0.5.139",
3
+ "version": "0.5.140",
4
4
  "description": "Clean, modern React component library with Tailwind v4 styling and native utilities",
5
5
  "private": false,
6
6
  "publishConfig": {
@@ -14,7 +14,13 @@ interface ChunkInfo {
14
14
  }
15
15
 
16
16
  class BundleAnalyzer {
17
- private enabled = import.meta.env.MODE === 'development';
17
+ private get enabled(): boolean {
18
+ try {
19
+ return import.meta.env?.MODE === 'development';
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
18
24
 
19
25
  analyzeBundle(): BundleStats | null {
20
26
  if (!this.enabled) return null;
@@ -104,7 +110,11 @@ export const bundleAnalyzer = new BundleAnalyzer();
104
110
 
105
111
  // Helper to check if imports are optimized
106
112
  export function validateImportPattern(moduleId: string, importedItems: string[]): void {
107
- if (import.meta.env.MODE !== 'development') return;
113
+ try {
114
+ if (import.meta.env?.MODE !== 'development') return;
115
+ } catch {
116
+ return; // Silently skip in test environments
117
+ }
108
118
 
109
119
  const largeModules = ['lodash', 'moment', 'rxjs'];
110
120
  const isLargeModule = largeModules.some(mod => moduleId.includes(mod));
@@ -121,7 +131,11 @@ export function validateImportPattern(moduleId: string, importedItems: string[])
121
131
 
122
132
  // Monitor dynamic imports
123
133
  export function trackDynamicImport(moduleName: string): void {
124
- if (import.meta.env.MODE !== 'development') return;
134
+ try {
135
+ if (import.meta.env?.MODE !== 'development') return;
136
+ } catch {
137
+ return; // Silently skip in test environments
138
+ }
125
139
 
126
140
  // TODO: Replace with proper logging service integration
127
141
  // For now, we'll log to console for testing purposes