@jmruthers/pace-core 0.6.4 → 0.6.5
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/dist/{DataTable-E7YQZD7D.js → DataTable-AOVNCPTX.js} +8 -8
- package/dist/{PublicPageProvider-DEMpysFR.d.ts → PublicPageProvider-QTFVrL-Z.d.ts} +65 -83
- package/dist/{UnifiedAuthProvider-QPXO24B4.js → UnifiedAuthProvider-4SBX4LU5.js} +4 -4
- package/dist/{api-6LVZTHDS.js → api-O6HTBX5Y.js} +3 -3
- package/dist/{chunk-I6DAQMWX.js → chunk-6COVEUS7.js} +130 -106
- package/dist/chunk-6COVEUS7.js.map +1 -0
- package/dist/{chunk-36LVWXB2.js → chunk-AFVQODI2.js} +37 -1
- package/dist/{chunk-36LVWXB2.js.map → chunk-AFVQODI2.js.map} +1 -1
- package/dist/{chunk-3LPHPB62.js → chunk-EFN2EIMK.js} +2 -2
- package/dist/{chunk-ATKZM7RX.js → chunk-G7QEZTYQ.js} +31 -31
- package/dist/{chunk-ATKZM7RX.js.map → chunk-G7QEZTYQ.js.map} +1 -1
- package/dist/{chunk-NN6WWZ5U.js → chunk-HU2C6SSC.js} +29 -18
- package/dist/chunk-HU2C6SSC.js.map +1 -0
- package/dist/{chunk-AVMLPIM7.js → chunk-IHB5DR3H.js} +102 -51
- package/dist/chunk-IHB5DR3H.js.map +1 -0
- package/dist/{chunk-7JPAB3T5.js → chunk-IVOFDYWT.js} +364 -208
- package/dist/chunk-IVOFDYWT.js.map +1 -0
- package/dist/{chunk-6SOIHG6Z.js → chunk-JGRYX5UX.js} +120 -20
- package/dist/chunk-JGRYX5UX.js.map +1 -0
- package/dist/{chunk-OEWDTMG7.js → chunk-NTM7ZSB6.js} +4 -4
- package/dist/chunk-NTM7ZSB6.js.map +1 -0
- package/dist/{chunk-5EC5MEWX.js → chunk-RGAWHO7N.js} +4 -4
- package/dist/chunk-RGAWHO7N.js.map +1 -0
- package/dist/{chunk-YKRAFF5K.js → chunk-UPPMRMYG.js} +3 -3
- package/dist/{chunk-YKRAFF5K.js.map → chunk-UPPMRMYG.js.map} +1 -1
- package/dist/components.d.ts +2 -3
- package/dist/components.js +24 -28
- package/dist/components.js.map +1 -1
- package/dist/{contextValidator-OOPCLPZW.js → contextValidator-5OGXSPKS.js} +2 -2
- package/dist/hooks.d.ts +3 -3
- package/dist/hooks.js +41 -139
- package/dist/hooks.js.map +1 -1
- package/dist/index.d.ts +27 -18
- package/dist/index.js +41 -50
- package/dist/index.js.map +1 -1
- package/dist/providers.js +3 -3
- package/dist/rbac/index.d.ts +16 -9
- package/dist/rbac/index.js +6 -6
- package/dist/{usePublicRouteParams-i3qtoBgg.d.ts → usePublicRouteParams-ClnV4tnv.d.ts} +8 -8
- package/dist/utils.js +1 -1
- package/docs/api/modules.md +210 -100
- package/package.json +1 -2
- package/scripts/validate-master.js +1 -1
- package/src/components/DataTable/__tests__/keyboard.test.tsx +15 -2
- package/src/components/DataTable/components/ImportModal.tsx +4 -6
- package/src/components/DataTable/components/ViewRowModal.tsx +4 -4
- package/src/components/DataTable/components/__tests__/ImportModal.test.tsx +455 -96
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +122 -58
- package/src/components/DataTable/core/DataTableContext.tsx +1 -1
- package/src/components/DateTimeField/DateTimeField.tsx +17 -19
- package/src/components/DateTimeField/README.md +5 -2
- package/src/components/Dialog/Dialog.test.tsx +248 -228
- package/src/components/Dialog/Dialog.tsx +455 -325
- package/src/components/Dialog/index.ts +3 -3
- package/src/components/FileDisplay/FileDisplay.test.tsx +41 -0
- package/src/components/FileDisplay/FileDisplay.tsx +5 -5
- package/src/components/Form/Form.test.tsx +3 -2
- package/src/components/Form/Form.tsx +4 -5
- package/src/components/InactivityWarningModal/InactivityWarningModal.test.tsx +28 -28
- package/src/components/InactivityWarningModal/InactivityWarningModal.tsx +40 -54
- package/src/components/LoginForm/LoginForm.tsx +2 -2
- package/src/components/NavigationMenu/NavigationMenu.tsx +2 -2
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +32 -39
- package/src/components/PaceAppLayout/README.md +10 -9
- package/src/components/PaceAppLayout/test-setup.tsx +40 -31
- package/src/components/PasswordChange/PasswordChangeForm.test.tsx +61 -0
- package/src/components/PasswordChange/PasswordChangeForm.tsx +20 -13
- package/src/components/PublicLayout/PublicLayout.test.tsx +7 -3
- package/src/components/PublicLayout/PublicPageLayout.tsx +5 -8
- package/src/components/UserMenu/UserMenu.test.tsx +38 -6
- package/src/components/UserMenu/UserMenu.tsx +36 -34
- package/src/components/index.ts +3 -4
- package/src/hooks/useEventTheme.ts +4 -4
- package/src/hooks/useEvents.ts +11 -7
- package/src/hooks/useKeyboardShortcuts.ts +1 -1
- package/src/hooks/useOrganisationPermissions.ts +4 -4
- package/src/hooks/useOrganisations.ts +13 -7
- package/src/index.ts +11 -1
- package/src/rbac/README.md +20 -20
- package/src/rbac/hooks/useRBAC.test.ts +21 -3
- package/src/rbac/hooks/useRBAC.ts +4 -3
- package/src/rbac/hooks/useResourcePermissions.test.ts +125 -30
- package/src/rbac/hooks/useResourcePermissions.ts +57 -29
- package/src/rbac/permissions.ts +17 -17
- package/src/rbac/utils/contextValidator.ts +36 -0
- package/src/services/AuthService.ts +2 -5
- package/src/services/InactivityService.ts +139 -58
- package/src/styles/core.css +4 -0
- package/src/utils/formatting/formatTime.test.ts +3 -2
- package/dist/chunk-5EC5MEWX.js.map +0 -1
- package/dist/chunk-6SOIHG6Z.js.map +0 -1
- package/dist/chunk-7JPAB3T5.js.map +0 -1
- package/dist/chunk-AVMLPIM7.js.map +0 -1
- package/dist/chunk-I6DAQMWX.js.map +0 -1
- package/dist/chunk-NN6WWZ5U.js.map +0 -1
- package/dist/chunk-OEWDTMG7.js.map +0 -1
- /package/dist/{DataTable-E7YQZD7D.js.map → DataTable-AOVNCPTX.js.map} +0 -0
- /package/dist/{UnifiedAuthProvider-QPXO24B4.js.map → UnifiedAuthProvider-4SBX4LU5.js.map} +0 -0
- /package/dist/{api-6LVZTHDS.js.map → api-O6HTBX5Y.js.map} +0 -0
- /package/dist/{chunk-3LPHPB62.js.map → chunk-EFN2EIMK.js.map} +0 -0
- /package/dist/{contextValidator-OOPCLPZW.js.map → contextValidator-5OGXSPKS.js.map} +0 -0
|
@@ -266,6 +266,67 @@ describe('PasswordChangeForm', () => {
|
|
|
266
266
|
|
|
267
267
|
expect(mockOnSubmit).not.toHaveBeenCalled();
|
|
268
268
|
});
|
|
269
|
+
|
|
270
|
+
it('calls onSuccess callback when password change succeeds', async () => {
|
|
271
|
+
const user = userEvent.setup();
|
|
272
|
+
const mockOnSuccess = vi.fn();
|
|
273
|
+
mockOnSubmit.mockResolvedValue({});
|
|
274
|
+
|
|
275
|
+
render(<PasswordChangeForm {...baseProps} onSuccess={mockOnSuccess} />);
|
|
276
|
+
|
|
277
|
+
const newPasswordInput = screen.getByLabelText('New Password');
|
|
278
|
+
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
279
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
280
|
+
|
|
281
|
+
await user.type(newPasswordInput, 'newpassword123');
|
|
282
|
+
await user.type(confirmPasswordInput, 'newpassword123');
|
|
283
|
+
await user.click(submitButton);
|
|
284
|
+
|
|
285
|
+
await waitFor(() => {
|
|
286
|
+
expect(mockOnSuccess).toHaveBeenCalledTimes(1);
|
|
287
|
+
});
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it('does not call onSuccess callback when password change fails', async () => {
|
|
291
|
+
const user = userEvent.setup();
|
|
292
|
+
const mockOnSuccess = vi.fn();
|
|
293
|
+
mockOnSubmit.mockResolvedValue({ error: { message: 'Password too weak' } });
|
|
294
|
+
|
|
295
|
+
render(<PasswordChangeForm {...baseProps} onSuccess={mockOnSuccess} />);
|
|
296
|
+
|
|
297
|
+
const newPasswordInput = screen.getByLabelText('New Password');
|
|
298
|
+
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
299
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
300
|
+
|
|
301
|
+
await user.type(newPasswordInput, 'newpassword123');
|
|
302
|
+
await user.type(confirmPasswordInput, 'newpassword123');
|
|
303
|
+
await user.click(submitButton);
|
|
304
|
+
|
|
305
|
+
await waitFor(() => {
|
|
306
|
+
expect(screen.getByText('Password too weak')).toBeInTheDocument();
|
|
307
|
+
expect(mockOnSuccess).not.toHaveBeenCalled();
|
|
308
|
+
});
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
it('works without onSuccess callback (backward compatibility)', async () => {
|
|
312
|
+
const user = userEvent.setup();
|
|
313
|
+
mockOnSubmit.mockResolvedValue({});
|
|
314
|
+
|
|
315
|
+
render(<PasswordChangeForm {...baseProps} />);
|
|
316
|
+
|
|
317
|
+
const newPasswordInput = screen.getByLabelText('New Password');
|
|
318
|
+
const confirmPasswordInput = screen.getByLabelText('Confirm Password');
|
|
319
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
320
|
+
|
|
321
|
+
await user.type(newPasswordInput, 'newpassword123');
|
|
322
|
+
await user.type(confirmPasswordInput, 'newpassword123');
|
|
323
|
+
await user.click(submitButton);
|
|
324
|
+
|
|
325
|
+
await waitFor(() => {
|
|
326
|
+
expect(mockOnSubmit).toHaveBeenCalled();
|
|
327
|
+
});
|
|
328
|
+
// Should not throw error when onSuccess is not provided
|
|
329
|
+
});
|
|
269
330
|
});
|
|
270
331
|
|
|
271
332
|
describe('Loading States', () => {
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
* }}
|
|
48
48
|
* />
|
|
49
49
|
*
|
|
50
|
-
* // Password change form in a modal
|
|
50
|
+
* // Password change form in a modal with onSuccess callback
|
|
51
51
|
* <Modal isOpen={showPasswordChange} onClose={() => setShowPasswordChange(false)}>
|
|
52
52
|
* <ModalContent>
|
|
53
53
|
* <ModalHeader>
|
|
@@ -58,13 +58,15 @@
|
|
|
58
58
|
* onSubmit={async (values) => {
|
|
59
59
|
* const result = await changePassword(values.newPassword);
|
|
60
60
|
* if (result.success) {
|
|
61
|
-
* setShowPasswordChange(false);
|
|
62
|
-
* toast.success('Password changed successfully');
|
|
63
61
|
* return {};
|
|
64
62
|
* } else {
|
|
65
63
|
* return { error: { message: result.error } };
|
|
66
64
|
* }
|
|
67
65
|
* }}
|
|
66
|
+
* onSuccess={() => {
|
|
67
|
+
* setShowPasswordChange(false);
|
|
68
|
+
* toast.success('Password changed successfully');
|
|
69
|
+
* }}
|
|
68
70
|
* />
|
|
69
71
|
* </ModalBody>
|
|
70
72
|
* </ModalContent>
|
|
@@ -124,10 +126,11 @@ export interface PasswordChangeFormError {
|
|
|
124
126
|
*/
|
|
125
127
|
export interface PasswordChangeFormProps {
|
|
126
128
|
onSubmit: (values: PasswordChangeFormValues) => Promise<{ error?: PasswordChangeFormError }>;
|
|
129
|
+
onSuccess?: () => void;
|
|
127
130
|
className?: string;
|
|
128
131
|
}
|
|
129
132
|
|
|
130
|
-
export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormProps) {
|
|
133
|
+
export function PasswordChangeForm({ onSubmit, onSuccess, className }: PasswordChangeFormProps) {
|
|
131
134
|
const [newPassword, setNewPassword] = useState('');
|
|
132
135
|
const [confirmPassword, setConfirmPassword] = useState('');
|
|
133
136
|
const [error, setError] = useState<string | null>(null);
|
|
@@ -151,6 +154,9 @@ export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormPr
|
|
|
151
154
|
const result = await onSubmit({ newPassword, confirmPassword });
|
|
152
155
|
if (result && result.error) {
|
|
153
156
|
setError(result.error.message || 'Failed to change password.');
|
|
157
|
+
} else {
|
|
158
|
+
// Success - call onSuccess callback
|
|
159
|
+
onSuccess?.();
|
|
154
160
|
}
|
|
155
161
|
} catch (err) {
|
|
156
162
|
const errorObj = err instanceof Error ? err : new Error('An unexpected error occurred');
|
|
@@ -163,23 +169,23 @@ export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormPr
|
|
|
163
169
|
return (
|
|
164
170
|
<form onSubmit={handleSubmit} className={cn('space-y-4', className)}>
|
|
165
171
|
{error && (
|
|
166
|
-
|
|
172
|
+
<p className="grid place-items-center text-center size-full" role="alert">
|
|
167
173
|
{error}
|
|
168
|
-
</
|
|
174
|
+
</p>
|
|
169
175
|
)}
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
<Input
|
|
176
|
+
<Label htmlFor="new-password" className="block mb-4">New Password
|
|
177
|
+
<Input
|
|
173
178
|
id="new-password"
|
|
174
179
|
type="password"
|
|
175
180
|
value={newPassword}
|
|
176
181
|
onChange={(e) => setNewPassword(e.target.value)}
|
|
177
182
|
required
|
|
178
183
|
disabled={isSubmitting}
|
|
184
|
+
className="mt-2"
|
|
179
185
|
/>
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
<Label htmlFor="confirm-password">Confirm Password
|
|
186
|
+
</Label>
|
|
187
|
+
|
|
188
|
+
<Label htmlFor="confirm-password" className="block mb-4">Confirm Password
|
|
183
189
|
<Input
|
|
184
190
|
id="confirm-password"
|
|
185
191
|
type="password"
|
|
@@ -187,8 +193,9 @@ export function PasswordChangeForm({ onSubmit, className }: PasswordChangeFormPr
|
|
|
187
193
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
|
188
194
|
required
|
|
189
195
|
disabled={isSubmitting}
|
|
196
|
+
className="mt-2"
|
|
190
197
|
/>
|
|
191
|
-
|
|
198
|
+
</Label>
|
|
192
199
|
<Button
|
|
193
200
|
type="submit"
|
|
194
201
|
className="w-full"
|
|
@@ -241,15 +241,19 @@ describe('[component] PublicPageLayout', () => {
|
|
|
241
241
|
});
|
|
242
242
|
|
|
243
243
|
it('renders loading state with proper layout classes', () => {
|
|
244
|
-
|
|
244
|
+
render(
|
|
245
245
|
<PublicPageLayout eventCode="EVENT123" isLoading={true}>
|
|
246
246
|
<div>Test Content</div>
|
|
247
247
|
</PublicPageLayout>
|
|
248
248
|
);
|
|
249
249
|
|
|
250
|
-
|
|
250
|
+
// The loading state renders a <p> tag with grid and place-items-center classes
|
|
251
|
+
// There may be multiple "Loading..." texts (from LoadingSpinner mock and actual text)
|
|
252
|
+
// So we query by the container element using getAllByText and find the one in the <p> tag
|
|
253
|
+
const loadingTexts = screen.getAllByText('Loading...');
|
|
254
|
+
const loadingContainer = loadingTexts.find(text => text.closest('p'))?.closest('p');
|
|
251
255
|
expect(loadingContainer).toBeInTheDocument();
|
|
252
|
-
expect(loadingContainer).toHaveClass('
|
|
256
|
+
expect(loadingContainer).toHaveClass('grid', 'place-items-center', 'text-center', 'size-full');
|
|
253
257
|
});
|
|
254
258
|
});
|
|
255
259
|
|
|
@@ -312,14 +312,11 @@ export function PublicPageLayout({
|
|
|
312
312
|
return <LoadingFallback />;
|
|
313
313
|
}
|
|
314
314
|
return (
|
|
315
|
-
<
|
|
316
|
-
<
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
)}
|
|
321
|
-
</div>
|
|
322
|
-
</div>
|
|
315
|
+
<p className="grid place-items-center text-center size-full">
|
|
316
|
+
<LoadingSpinner
|
|
317
|
+
size="lg"/><br />
|
|
318
|
+
{loadingMessage || 'Loading...'}
|
|
319
|
+
</p>
|
|
323
320
|
);
|
|
324
321
|
}
|
|
325
322
|
|
|
@@ -21,7 +21,7 @@ vi.mock('lucide-react', () => ({
|
|
|
21
21
|
|
|
22
22
|
// Mock the PasswordChangeForm component
|
|
23
23
|
vi.mock('../PasswordChange/PasswordChangeForm', () => ({
|
|
24
|
-
PasswordChangeForm: ({ onSubmit }: { onSubmit: (values: { newPassword: string; confirmPassword: string }) => Promise<{ error?: { message?: string; code?: string } }
|
|
24
|
+
PasswordChangeForm: ({ onSubmit, onSuccess }: { onSubmit: (values: { newPassword: string; confirmPassword: string }) => Promise<{ error?: { message?: string; code?: string } }>; onSuccess?: () => void }) => {
|
|
25
25
|
const [isSubmitting, setIsSubmitting] = React.useState(false);
|
|
26
26
|
const [error, setError] = React.useState<string | null>(null);
|
|
27
27
|
|
|
@@ -33,6 +33,9 @@ vi.mock('../PasswordChange/PasswordChangeForm', () => ({
|
|
|
33
33
|
const result = await onSubmit({ newPassword: 'newpass123', confirmPassword: 'newpass123' });
|
|
34
34
|
if (result.error) {
|
|
35
35
|
setError(result.error.message);
|
|
36
|
+
} else {
|
|
37
|
+
// Call onSuccess on successful password change
|
|
38
|
+
onSuccess?.();
|
|
36
39
|
}
|
|
37
40
|
} catch (err) {
|
|
38
41
|
const errorObj = err instanceof Error ? err : new Error('An unexpected error occurred');
|
|
@@ -92,9 +95,7 @@ vi.mock('../Select', () => ({
|
|
|
92
95
|
// Mock the Dialog components
|
|
93
96
|
vi.mock('../Dialog', () => ({
|
|
94
97
|
Dialog: ({ children, open, onOpenChange }: { children: React.ReactNode; open: boolean; onOpenChange: (open: boolean) => void }) => (
|
|
95
|
-
<div data-testid="dialog" data-open={open}>
|
|
96
|
-
{children}
|
|
97
|
-
</div>
|
|
98
|
+
open ? <div data-testid="dialog" role="dialog" data-open={open}>{children}</div> : null
|
|
98
99
|
),
|
|
99
100
|
DialogContent: ({ children, className }: { children: React.ReactNode; className?: string }) => (
|
|
100
101
|
<div data-testid="dialog-content" className={className}>
|
|
@@ -109,12 +110,17 @@ vi.mock('../Dialog', () => ({
|
|
|
109
110
|
DialogTitle: ({ children }: { children: React.ReactNode }) => (
|
|
110
111
|
<h2 data-testid="dialog-title">{children}</h2>
|
|
111
112
|
),
|
|
113
|
+
DialogDescription: ({ children }: { children: React.ReactNode }) => (
|
|
114
|
+
<p data-testid="dialog-description">{children}</p>
|
|
115
|
+
),
|
|
112
116
|
DialogTrigger: ({ children, asChild }: { children: React.ReactNode; asChild?: boolean }) => (
|
|
113
117
|
<div data-testid="dialog-trigger">
|
|
114
118
|
{asChild ? children : <button>{children}</button>}
|
|
115
119
|
</div>
|
|
116
120
|
),
|
|
117
|
-
|
|
121
|
+
DialogClose: ({ children, onClick }: { children?: React.ReactNode; onClick?: () => void }) => (
|
|
122
|
+
<button data-testid="dialog-close" onClick={onClick}>{children || 'Close'}</button>
|
|
123
|
+
),
|
|
118
124
|
}));
|
|
119
125
|
|
|
120
126
|
// Mock the Avatar component
|
|
@@ -412,6 +418,32 @@ describe('UserMenu Component', () => {
|
|
|
412
418
|
});
|
|
413
419
|
});
|
|
414
420
|
|
|
421
|
+
it('closes dialog when password change succeeds', async () => {
|
|
422
|
+
const user = createMockUser();
|
|
423
|
+
const handleChangePassword = vi.fn().mockResolvedValue({});
|
|
424
|
+
const userEventInstance = userEvent.setup();
|
|
425
|
+
|
|
426
|
+
renderWithProviders(<UserMenu user={user} onChangePassword={handleChangePassword} />);
|
|
427
|
+
|
|
428
|
+
const trigger = screen.getByRole('button', { name: 'Test User' });
|
|
429
|
+
await userEventInstance.click(trigger);
|
|
430
|
+
|
|
431
|
+
const changePasswordButton = screen.getByTestId('select-item-change-password');
|
|
432
|
+
await userEventInstance.click(changePasswordButton);
|
|
433
|
+
|
|
434
|
+
// Verify dialog is open
|
|
435
|
+
expect(screen.getByTestId('dialog-content')).toBeInTheDocument();
|
|
436
|
+
|
|
437
|
+
const submitButton = screen.getByRole('button', { name: 'Change Password' });
|
|
438
|
+
await userEventInstance.click(submitButton);
|
|
439
|
+
|
|
440
|
+
// Wait for dialog to close
|
|
441
|
+
await waitFor(() => {
|
|
442
|
+
const dialog = screen.queryByTestId('dialog-content');
|
|
443
|
+
expect(dialog).not.toBeInTheDocument();
|
|
444
|
+
}, { timeout: 3000 });
|
|
445
|
+
});
|
|
446
|
+
|
|
415
447
|
it('keeps dialog open when password change fails', async () => {
|
|
416
448
|
const user = createMockUser();
|
|
417
449
|
const handleChangePassword = vi.fn().mockResolvedValue({ error: { message: 'Password too weak' } });
|
|
@@ -434,7 +466,7 @@ describe('UserMenu Component', () => {
|
|
|
434
466
|
expect(screen.getByTestId('error-message')).toHaveTextContent('Password too weak');
|
|
435
467
|
});
|
|
436
468
|
|
|
437
|
-
// Dialog should still be open
|
|
469
|
+
// Dialog should still be open (onSuccess should not be called when there's an error)
|
|
438
470
|
expect(screen.getByTestId('dialog-content')).toBeInTheDocument();
|
|
439
471
|
});
|
|
440
472
|
|
|
@@ -117,9 +117,7 @@ import {
|
|
|
117
117
|
Dialog,
|
|
118
118
|
DialogContent,
|
|
119
119
|
DialogHeader,
|
|
120
|
-
DialogTitle
|
|
121
|
-
DialogTrigger,
|
|
122
|
-
DialogOverlay
|
|
120
|
+
DialogTitle
|
|
123
121
|
} from '../Dialog';
|
|
124
122
|
import { PasswordChangeForm, PasswordChangeFormError } from '../PasswordChange/PasswordChangeForm';
|
|
125
123
|
import { Avatar } from '../Avatar';
|
|
@@ -144,8 +142,6 @@ export const UserMenu = React.memo<UserMenuProps>(function UserMenu({
|
|
|
144
142
|
className,
|
|
145
143
|
showAvatar = true,
|
|
146
144
|
}) {
|
|
147
|
-
const [isPasswordDialogOpen, setPasswordDialogOpen] = useState(false);
|
|
148
|
-
|
|
149
145
|
const userInfo = useMemo(() => {
|
|
150
146
|
if (!user) return null;
|
|
151
147
|
return {
|
|
@@ -164,8 +160,18 @@ export const UserMenu = React.memo<UserMenuProps>(function UserMenu({
|
|
|
164
160
|
return null; // Or a loading/login button
|
|
165
161
|
}
|
|
166
162
|
|
|
163
|
+
// Password change dialog state and handlers (moved closer to Dialog usage)
|
|
164
|
+
const [isPasswordDialogOpen, setPasswordDialogOpen] = useState(false);
|
|
165
|
+
|
|
166
|
+
const handlePasswordChange = useCallback(async (newPassword: string, confirmPassword: string) => {
|
|
167
|
+
if (!onChangePassword) {
|
|
168
|
+
return { error: { message: 'Password change not configured' } };
|
|
169
|
+
}
|
|
170
|
+
return await onChangePassword(newPassword, confirmPassword);
|
|
171
|
+
}, [onChangePassword]);
|
|
172
|
+
|
|
167
173
|
return (
|
|
168
|
-
|
|
174
|
+
<>
|
|
169
175
|
<Select className={className}>
|
|
170
176
|
<SelectTrigger asChild>
|
|
171
177
|
<Button variant="outline" className="flex items-center gap-2" aria-label={userInfo.displayName}>
|
|
@@ -185,42 +191,38 @@ export const UserMenu = React.memo<UserMenuProps>(function UserMenu({
|
|
|
185
191
|
<SelectLabel className="font-normal">
|
|
186
192
|
<li className="pt-2">
|
|
187
193
|
{userInfo.displayName}<br/>
|
|
188
|
-
<
|
|
194
|
+
<small>{userInfo.email}</small>
|
|
189
195
|
</li>
|
|
190
196
|
</SelectLabel>
|
|
191
197
|
<SelectSeparator />
|
|
192
|
-
<
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
+
<SelectItem
|
|
199
|
+
value="change-password"
|
|
200
|
+
onClick={() => setPasswordDialogOpen(true)}
|
|
201
|
+
>
|
|
202
|
+
<KeyRound className="inline-block mr-2 size-4" />
|
|
203
|
+
Change Password
|
|
204
|
+
</SelectItem>
|
|
198
205
|
<SelectItem value="sign-out" onClick={handleSignOut}>
|
|
199
|
-
<LogOut className="mr-2 size-4" />
|
|
200
|
-
|
|
206
|
+
<LogOut className="inline-block mr-2 size-4" />
|
|
207
|
+
Sign out
|
|
201
208
|
</SelectItem>
|
|
202
209
|
</SelectContent>
|
|
203
210
|
</Select>
|
|
204
211
|
|
|
205
|
-
<
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
return {};
|
|
220
|
-
}}
|
|
221
|
-
/>
|
|
222
|
-
</DialogContent>
|
|
223
|
-
</Dialog>
|
|
212
|
+
<Dialog open={isPasswordDialogOpen} onOpenChange={setPasswordDialogOpen}>
|
|
213
|
+
<DialogContent>
|
|
214
|
+
<DialogHeader>
|
|
215
|
+
<DialogTitle>Change Password</DialogTitle>
|
|
216
|
+
</DialogHeader>
|
|
217
|
+
<PasswordChangeForm
|
|
218
|
+
onSubmit={async ({ newPassword, confirmPassword }) => {
|
|
219
|
+
return await handlePasswordChange(newPassword, confirmPassword);
|
|
220
|
+
}}
|
|
221
|
+
onSuccess={() => setPasswordDialogOpen(false)}
|
|
222
|
+
/>
|
|
223
|
+
</DialogContent>
|
|
224
|
+
</Dialog>
|
|
225
|
+
</>
|
|
224
226
|
);
|
|
225
227
|
});
|
|
226
228
|
|
package/src/components/index.ts
CHANGED
|
@@ -84,7 +84,6 @@ export {
|
|
|
84
84
|
export {
|
|
85
85
|
Dialog,
|
|
86
86
|
DialogPortal,
|
|
87
|
-
DialogOverlay,
|
|
88
87
|
DialogTrigger,
|
|
89
88
|
DialogClose,
|
|
90
89
|
DialogContent,
|
|
@@ -98,11 +97,11 @@ export type {
|
|
|
98
97
|
DialogProps,
|
|
99
98
|
DialogTriggerProps,
|
|
100
99
|
DialogContentProps,
|
|
101
|
-
|
|
100
|
+
DialogPortalProps,
|
|
101
|
+
DialogCloseProps,
|
|
102
102
|
DialogHeaderProps,
|
|
103
103
|
DialogFooterProps,
|
|
104
|
-
|
|
105
|
-
DialogDescriptionProps,
|
|
104
|
+
DialogBodyProps,
|
|
106
105
|
DialogSize
|
|
107
106
|
} from './Dialog/Dialog';
|
|
108
107
|
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
* // Automatically applies event colors when event is selected via EventProvider
|
|
21
21
|
* useEventTheme();
|
|
22
22
|
*
|
|
23
|
-
* return <
|
|
23
|
+
* return <main>Your app content</main>;
|
|
24
24
|
* }
|
|
25
25
|
* ```
|
|
26
26
|
*
|
|
@@ -33,7 +33,7 @@
|
|
|
33
33
|
* // Applies event colors directly from event prop
|
|
34
34
|
* useEventTheme(event);
|
|
35
35
|
*
|
|
36
|
-
* return <
|
|
36
|
+
* return <main>Public page content</main>;
|
|
37
37
|
* }
|
|
38
38
|
* ```
|
|
39
39
|
*/
|
|
@@ -79,7 +79,7 @@ const log = createLogger('useEventTheme');
|
|
|
79
79
|
* // Authenticated mode - uses EventProvider
|
|
80
80
|
* function MyApp() {
|
|
81
81
|
* useEventTheme(); // Watches selectedEvent from EventProvider
|
|
82
|
-
* return <
|
|
82
|
+
* return <main>App content</main>;
|
|
83
83
|
* }
|
|
84
84
|
* ```
|
|
85
85
|
*
|
|
@@ -88,7 +88,7 @@ const log = createLogger('useEventTheme');
|
|
|
88
88
|
* // Public page mode - uses event prop
|
|
89
89
|
* function PublicPageLayout({ event }) {
|
|
90
90
|
* useEventTheme(event); // Uses event prop directly
|
|
91
|
-
* return <
|
|
91
|
+
* return <main>Public content</main>;
|
|
92
92
|
* }
|
|
93
93
|
* ```
|
|
94
94
|
*/
|
package/src/hooks/useEvents.ts
CHANGED
|
@@ -37,13 +37,17 @@ export interface EventContextType {
|
|
|
37
37
|
* const { events, selectedEvent, setSelectedEvent } = useEvents();
|
|
38
38
|
*
|
|
39
39
|
* return (
|
|
40
|
-
* <
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
* {event.
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
40
|
+
* <nav>
|
|
41
|
+
* <ul>
|
|
42
|
+
* {events.map(event => (
|
|
43
|
+
* <li key={event.id}>
|
|
44
|
+
* <button onClick={() => setSelectedEvent(event)}>
|
|
45
|
+
* {event.event_name}
|
|
46
|
+
* </button>
|
|
47
|
+
* </li>
|
|
48
|
+
* ))}
|
|
49
|
+
* </ul>
|
|
50
|
+
* </nav>
|
|
47
51
|
* );
|
|
48
52
|
* }
|
|
49
53
|
* ```
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
* } = useOrganisationPermissions();
|
|
19
19
|
*
|
|
20
20
|
* return (
|
|
21
|
-
* <
|
|
21
|
+
* <section>
|
|
22
22
|
* {isOrgAdmin && <AdminPanel />}
|
|
23
23
|
* {canManageMembers && <MemberManagement />}
|
|
24
24
|
* <p>Your role: {userRole}</p>
|
|
25
|
-
* </
|
|
25
|
+
* </section>
|
|
26
26
|
* );
|
|
27
27
|
* }
|
|
28
28
|
*
|
|
@@ -31,10 +31,10 @@
|
|
|
31
31
|
* const permissions = useOrganisationPermissions('org-123');
|
|
32
32
|
*
|
|
33
33
|
* if (!permissions.hasOrganisationAccess) {
|
|
34
|
-
* return <
|
|
34
|
+
* return <main>No access to this organisation</main>;
|
|
35
35
|
* }
|
|
36
36
|
*
|
|
37
|
-
* return <
|
|
37
|
+
* return <main>Role in org-123: {permissions.userRole}</main>;
|
|
38
38
|
* }
|
|
39
39
|
* ```
|
|
40
40
|
*
|
|
@@ -26,14 +26,20 @@ import { Organisation, OrganisationMembership, OrganisationContextType } from '.
|
|
|
26
26
|
* const { selectedOrganisation, organisations, switchOrganisation } = useOrganisations();
|
|
27
27
|
*
|
|
28
28
|
* return (
|
|
29
|
-
* <
|
|
29
|
+
* <main>
|
|
30
30
|
* <h1>Current Organisation: {selectedOrganisation.display_name}</h1>
|
|
31
|
-
*
|
|
32
|
-
* <
|
|
33
|
-
* {org
|
|
34
|
-
*
|
|
35
|
-
*
|
|
36
|
-
*
|
|
31
|
+
* <nav>
|
|
32
|
+
* <ul>
|
|
33
|
+
* {organisations.map(org => (
|
|
34
|
+
* <li key={org.id}>
|
|
35
|
+
* <button onClick={() => switchOrganisation(org.id)}>
|
|
36
|
+
* {org.display_name}
|
|
37
|
+
* </button>
|
|
38
|
+
* </li>
|
|
39
|
+
* ))}
|
|
40
|
+
* </ul>
|
|
41
|
+
* </nav>
|
|
42
|
+
* </main>
|
|
37
43
|
* );
|
|
38
44
|
* }
|
|
39
45
|
* ```
|
package/src/index.ts
CHANGED
|
@@ -101,7 +101,6 @@ export type { ProgressProps } from './components/Progress';
|
|
|
101
101
|
export {
|
|
102
102
|
Dialog,
|
|
103
103
|
DialogPortal,
|
|
104
|
-
DialogOverlay,
|
|
105
104
|
DialogTrigger,
|
|
106
105
|
DialogClose,
|
|
107
106
|
DialogContent,
|
|
@@ -111,6 +110,17 @@ export {
|
|
|
111
110
|
DialogTitle,
|
|
112
111
|
DialogDescription,
|
|
113
112
|
} from './components/Dialog/Dialog';
|
|
113
|
+
export type {
|
|
114
|
+
DialogProps,
|
|
115
|
+
DialogTriggerProps,
|
|
116
|
+
DialogContentProps,
|
|
117
|
+
DialogPortalProps,
|
|
118
|
+
DialogCloseProps,
|
|
119
|
+
DialogHeaderProps,
|
|
120
|
+
DialogFooterProps,
|
|
121
|
+
DialogBodyProps,
|
|
122
|
+
DialogSize
|
|
123
|
+
} from './components/Dialog/Dialog';
|
|
114
124
|
|
|
115
125
|
// Dropdown Menu exports
|
|
116
126
|
// DropdownMenu components have been merged into Select components
|