@djangocfg/layouts 2.1.356 → 2.1.358
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/package.json +21 -19
- package/src/configurator/private/schema.ts +12 -0
- package/src/layouts/AdminLayout/AdminLayout.tsx +2 -1
- package/src/layouts/AppLayout/AppLayout.tsx +35 -15
- package/src/layouts/AppLayout/BaseApp.tsx +2 -2
- package/src/layouts/AuthLayout/AuthLayout.tsx +26 -19
- package/src/layouts/AuthLayout/components/oauth/OAuthCallback.tsx +10 -4
- package/src/layouts/AuthLayout/components/shared/AuthButton.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthContainer.tsx +10 -10
- package/src/layouts/AuthLayout/components/shared/AuthDivider.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthError.tsx +10 -5
- package/src/layouts/AuthLayout/components/shared/AuthFooter.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthHeader.tsx +10 -10
- package/src/layouts/AuthLayout/components/shared/AuthLink.tsx +11 -5
- package/src/layouts/AuthLayout/components/shared/AuthOTPInput.tsx +28 -20
- package/src/layouts/AuthLayout/components/shared/TermsCheckbox.tsx +11 -5
- package/src/layouts/AuthLayout/components/steps/IdentifierStep.tsx +12 -4
- package/src/layouts/AuthLayout/components/steps/OTPStep.tsx +9 -4
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupComplete.tsx +12 -5
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupLoading.tsx +9 -4
- package/src/layouts/AuthLayout/components/steps/SetupStep/SetupQRCode.tsx +11 -5
- package/src/layouts/AuthLayout/components/steps/SetupStep/index.tsx +15 -5
- package/src/layouts/AuthLayout/components/steps/TwoFactorStep.tsx +9 -4
- package/src/layouts/AuthLayout/context.tsx +35 -13
- package/src/layouts/AuthLayout/shells/AuthShell.tsx +11 -4
- package/src/layouts/AuthLayout/shells/CenteredShell.tsx +10 -4
- package/src/layouts/AuthLayout/shells/SplitShell.tsx +10 -4
- package/src/layouts/AuthLayout/shells/context.tsx +16 -5
- package/src/layouts/PrivateLayout/PrivateLayout.tsx +45 -248
- package/src/layouts/PrivateLayout/components/PrivateSidebar.tsx +113 -430
- package/src/layouts/{_components → PrivateLayout/components}/PrivateSidebarAccount.tsx +82 -105
- package/src/layouts/PrivateLayout/components/SidebarBrand.tsx +168 -0
- package/src/layouts/{_components → PrivateLayout/components}/SidebarFeatured.tsx +2 -2
- package/src/layouts/PrivateLayout/components/SidebarNavGroup.tsx +189 -0
- package/src/layouts/PrivateLayout/components/SidebarNavItem.tsx +137 -0
- package/src/layouts/PrivateLayout/components/SidebarSlots.tsx +71 -0
- package/src/layouts/PrivateLayout/components/index.ts +4 -0
- package/src/layouts/PrivateLayout/context.tsx +211 -0
- package/src/layouts/PrivateLayout/density.ts +48 -0
- package/src/layouts/PrivateLayout/hooks/index.ts +14 -0
- package/src/layouts/PrivateLayout/hooks/useAuthGuard.ts +54 -0
- package/src/layouts/PrivateLayout/hooks/useHoverExpand.ts +110 -0
- package/src/layouts/PrivateLayout/hooks/useLayoutVisual.ts +113 -0
- package/src/layouts/PrivateLayout/hooks/useShellVisualState.ts +207 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarDefaultOpen.ts +21 -0
- package/src/layouts/PrivateLayout/hooks/useSidebarKeyboard.ts +115 -0
- package/src/layouts/PrivateLayout/index.ts +2 -2
- package/src/layouts/PrivateLayout/types.ts +193 -0
- package/src/layouts/ProfileLayout/ProfileDialog/ProfileDialog.tsx +32 -0
- package/src/layouts/ProfileLayout/ProfileDialog/index.ts +2 -0
- package/src/layouts/ProfileLayout/ProfileDialog/store.ts +19 -0
- package/src/layouts/ProfileLayout/{context.tsx → ProfileForm/context.tsx} +8 -8
- package/src/layouts/ProfileLayout/ProfileForm/index.tsx +148 -0
- package/src/layouts/ProfileLayout/README.md +118 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/ApiKeySection.tsx +197 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/context.tsx +159 -0
- package/src/layouts/ProfileLayout/components/ApiKeySection/index.ts +3 -0
- package/src/layouts/ProfileLayout/components/EditableField.tsx +1 -1
- package/src/layouts/ProfileLayout/components/PreferencesSection.tsx +56 -0
- package/src/layouts/ProfileLayout/components/ProfileHeader.tsx +110 -0
- package/src/layouts/ProfileLayout/components/ProfileTab.tsx +35 -0
- package/src/layouts/ProfileLayout/components/{TwoFactorSection.tsx → TwoFactorSection/TwoFactorSection.tsx} +1 -1
- package/src/layouts/ProfileLayout/components/TwoFactorSection/index.ts +1 -0
- package/src/layouts/ProfileLayout/components/index.ts +5 -2
- package/src/layouts/ProfileLayout/hooks/index.ts +2 -0
- package/src/layouts/ProfileLayout/hooks/useProfileTabs.ts +48 -0
- package/src/layouts/ProfileLayout/index.ts +7 -3
- package/src/layouts/ProfileLayout/types.ts +47 -0
- package/src/layouts/{_components → PublicLayout/components}/UserMenu.tsx +3 -3
- package/src/layouts/PublicLayout/components/index.ts +4 -0
- package/src/layouts/PublicLayout/footers/DefaultFooter/DefaultFooter.tsx +12 -2
- package/src/layouts/PublicLayout/navbars/MinimalNavbar/MinimalNavbar.tsx +1 -1
- package/src/layouts/PublicLayout/primitives/NavActions.tsx +44 -3
- package/src/layouts/PublicLayout/primitives/NavBrand.tsx +4 -2
- package/src/layouts/PublicLayout/primitives/NavDesktopItems.tsx +42 -2
- package/src/layouts/PublicLayout/shared/MobileDrawerShell.tsx +1 -1
- package/src/layouts/PublicLayout/shared/NavbarShell.tsx +60 -1
- package/src/layouts/_components/index.ts +2 -6
- package/src/layouts/index.ts +9 -4
- package/src/layouts/ProfileLayout/ProfileLayout.tsx +0 -284
- package/src/layouts/ProfileLayout/__tests__/TwoFactorSection.test.tsx +0 -234
- package/src/layouts/ProfileLayout/components/ProfileForm.tsx +0 -198
- /package/src/layouts/{_components → PublicLayout/components}/UserAvatar.tsx +0 -0
|
@@ -1,234 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for TwoFactorSection component
|
|
3
|
-
*
|
|
4
|
-
* Tests the 2FA management section in ProfileLayout.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import { fireEvent, render, screen, waitFor } from '@testing-library/react';
|
|
8
|
-
import React from 'react';
|
|
9
|
-
|
|
10
|
-
import { TwoFactorSection } from '../components/TwoFactorSection';
|
|
11
|
-
|
|
12
|
-
// Mock hooks
|
|
13
|
-
const mockFetchStatus = jest.fn();
|
|
14
|
-
const mockDisable2FA = jest.fn();
|
|
15
|
-
const mockResetSetup = jest.fn();
|
|
16
|
-
const mockClearError = jest.fn();
|
|
17
|
-
|
|
18
|
-
jest.mock('@djangocfg/api/auth', () => ({
|
|
19
|
-
useTwoFactorStatus: () => ({
|
|
20
|
-
isLoading: false,
|
|
21
|
-
error: null,
|
|
22
|
-
has2FAEnabled: false,
|
|
23
|
-
devices: [],
|
|
24
|
-
fetchStatus: mockFetchStatus,
|
|
25
|
-
disable2FA: mockDisable2FA,
|
|
26
|
-
clearError: mockClearError,
|
|
27
|
-
}),
|
|
28
|
-
useTwoFactorSetup: () => ({
|
|
29
|
-
resetSetup: mockResetSetup,
|
|
30
|
-
}),
|
|
31
|
-
}));
|
|
32
|
-
|
|
33
|
-
// Mock TwoFactorSetup component
|
|
34
|
-
jest.mock('../../AuthLayout/components/TwoFactorSetup', () => ({
|
|
35
|
-
TwoFactorSetup: ({ onComplete, onSkip }: any) => (
|
|
36
|
-
<div data-testid="two-factor-setup">
|
|
37
|
-
<button onClick={onComplete}>Complete Setup</button>
|
|
38
|
-
<button onClick={onSkip}>Skip Setup</button>
|
|
39
|
-
</div>
|
|
40
|
-
),
|
|
41
|
-
}));
|
|
42
|
-
|
|
43
|
-
// Mock UI components
|
|
44
|
-
jest.mock('@djangocfg/ui-core/components', () => ({
|
|
45
|
-
Alert: ({ children, variant }: any) => (
|
|
46
|
-
<div data-testid="alert" data-variant={variant}>
|
|
47
|
-
{children}
|
|
48
|
-
</div>
|
|
49
|
-
),
|
|
50
|
-
AlertDescription: ({ children }: any) => <span>{children}</span>,
|
|
51
|
-
Button: ({ children, onClick, disabled, variant }: any) => (
|
|
52
|
-
<button onClick={onClick} disabled={disabled} data-variant={variant}>
|
|
53
|
-
{children}
|
|
54
|
-
</button>
|
|
55
|
-
),
|
|
56
|
-
Card: ({ children, className }: any) => (
|
|
57
|
-
<div data-testid="card" className={className}>
|
|
58
|
-
{children}
|
|
59
|
-
</div>
|
|
60
|
-
),
|
|
61
|
-
CardContent: ({ children }: any) => <div>{children}</div>,
|
|
62
|
-
CardDescription: ({ children }: any) => <p>{children}</p>,
|
|
63
|
-
CardHeader: ({ children }: any) => <div>{children}</div>,
|
|
64
|
-
CardTitle: ({ children }: any) => <h3>{children}</h3>,
|
|
65
|
-
Dialog: ({ children, open }: any) => (open ? <div data-testid="dialog">{children}</div> : null),
|
|
66
|
-
DialogContent: ({ children }: any) => <div>{children}</div>,
|
|
67
|
-
DialogDescription: ({ children }: any) => <p>{children}</p>,
|
|
68
|
-
DialogFooter: ({ children }: any) => <div>{children}</div>,
|
|
69
|
-
DialogHeader: ({ children }: any) => <div>{children}</div>,
|
|
70
|
-
DialogTitle: ({ children }: any) => <h4>{children}</h4>,
|
|
71
|
-
OTPInput: ({ value, onChange, disabled }: any) => (
|
|
72
|
-
<input
|
|
73
|
-
data-testid="otp-input"
|
|
74
|
-
value={value}
|
|
75
|
-
onChange={(e) => onChange(e.target.value)}
|
|
76
|
-
disabled={disabled}
|
|
77
|
-
/>
|
|
78
|
-
),
|
|
79
|
-
}));
|
|
80
|
-
|
|
81
|
-
describe('TwoFactorSection', () => {
|
|
82
|
-
beforeEach(() => {
|
|
83
|
-
jest.clearAllMocks();
|
|
84
|
-
});
|
|
85
|
-
|
|
86
|
-
describe('when 2FA is disabled', () => {
|
|
87
|
-
it('should render disabled state', () => {
|
|
88
|
-
render(<TwoFactorSection />);
|
|
89
|
-
|
|
90
|
-
expect(screen.getByText('Two-Factor Authentication')).toBeInTheDocument();
|
|
91
|
-
expect(screen.getByText('2FA is not enabled')).toBeInTheDocument();
|
|
92
|
-
expect(screen.getByText('Enable 2FA')).toBeInTheDocument();
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it('should fetch status on mount', () => {
|
|
96
|
-
render(<TwoFactorSection />);
|
|
97
|
-
|
|
98
|
-
expect(mockFetchStatus).toHaveBeenCalledTimes(1);
|
|
99
|
-
});
|
|
100
|
-
|
|
101
|
-
it('should show security recommendation', () => {
|
|
102
|
-
render(<TwoFactorSection />);
|
|
103
|
-
|
|
104
|
-
expect(
|
|
105
|
-
screen.getByText(/Two-factor authentication adds an extra layer of security/)
|
|
106
|
-
).toBeInTheDocument();
|
|
107
|
-
});
|
|
108
|
-
});
|
|
109
|
-
|
|
110
|
-
describe('when 2FA is enabled', () => {
|
|
111
|
-
beforeEach(() => {
|
|
112
|
-
jest.doMock('@djangocfg/api/auth', () => ({
|
|
113
|
-
useTwoFactorStatus: () => ({
|
|
114
|
-
isLoading: false,
|
|
115
|
-
error: null,
|
|
116
|
-
has2FAEnabled: true,
|
|
117
|
-
devices: [
|
|
118
|
-
{
|
|
119
|
-
id: '123',
|
|
120
|
-
name: 'My Authenticator',
|
|
121
|
-
createdAt: '2024-01-01T00:00:00Z',
|
|
122
|
-
lastUsedAt: null,
|
|
123
|
-
isPrimary: true,
|
|
124
|
-
},
|
|
125
|
-
],
|
|
126
|
-
fetchStatus: mockFetchStatus,
|
|
127
|
-
disable2FA: mockDisable2FA,
|
|
128
|
-
clearError: mockClearError,
|
|
129
|
-
}),
|
|
130
|
-
useTwoFactorSetup: () => ({
|
|
131
|
-
resetSetup: mockResetSetup,
|
|
132
|
-
}),
|
|
133
|
-
}));
|
|
134
|
-
});
|
|
135
|
-
|
|
136
|
-
it('should render enabled state with device info', async () => {
|
|
137
|
-
// Note: Due to jest module caching, this test shows expected behavior
|
|
138
|
-
// In actual implementation, mock would need to be reset properly
|
|
139
|
-
});
|
|
140
|
-
});
|
|
141
|
-
|
|
142
|
-
describe('enable 2FA flow', () => {
|
|
143
|
-
it('should show setup view when Enable 2FA is clicked', async () => {
|
|
144
|
-
render(<TwoFactorSection />);
|
|
145
|
-
|
|
146
|
-
fireEvent.click(screen.getByText('Enable 2FA'));
|
|
147
|
-
|
|
148
|
-
await waitFor(() => {
|
|
149
|
-
expect(screen.getByTestId('two-factor-setup')).toBeInTheDocument();
|
|
150
|
-
});
|
|
151
|
-
|
|
152
|
-
expect(mockResetSetup).toHaveBeenCalled();
|
|
153
|
-
});
|
|
154
|
-
|
|
155
|
-
it('should return to status view when setup is completed', async () => {
|
|
156
|
-
render(<TwoFactorSection />);
|
|
157
|
-
|
|
158
|
-
// Click Enable 2FA
|
|
159
|
-
fireEvent.click(screen.getByText('Enable 2FA'));
|
|
160
|
-
|
|
161
|
-
await waitFor(() => {
|
|
162
|
-
expect(screen.getByTestId('two-factor-setup')).toBeInTheDocument();
|
|
163
|
-
});
|
|
164
|
-
|
|
165
|
-
// Complete setup
|
|
166
|
-
fireEvent.click(screen.getByText('Complete Setup'));
|
|
167
|
-
|
|
168
|
-
await waitFor(() => {
|
|
169
|
-
expect(mockFetchStatus).toHaveBeenCalled();
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
it('should return to status view when setup is skipped', async () => {
|
|
174
|
-
render(<TwoFactorSection />);
|
|
175
|
-
|
|
176
|
-
// Click Enable 2FA
|
|
177
|
-
fireEvent.click(screen.getByText('Enable 2FA'));
|
|
178
|
-
|
|
179
|
-
await waitFor(() => {
|
|
180
|
-
expect(screen.getByTestId('two-factor-setup')).toBeInTheDocument();
|
|
181
|
-
});
|
|
182
|
-
|
|
183
|
-
// Skip setup
|
|
184
|
-
fireEvent.click(screen.getByText('Skip Setup'));
|
|
185
|
-
|
|
186
|
-
// Should go back to status view
|
|
187
|
-
await waitFor(() => {
|
|
188
|
-
expect(screen.queryByTestId('two-factor-setup')).not.toBeInTheDocument();
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
});
|
|
192
|
-
|
|
193
|
-
describe('error handling', () => {
|
|
194
|
-
it('should display error when present', () => {
|
|
195
|
-
jest.doMock('@djangocfg/api/auth', () => ({
|
|
196
|
-
useTwoFactorStatus: () => ({
|
|
197
|
-
isLoading: false,
|
|
198
|
-
error: 'Failed to fetch status',
|
|
199
|
-
has2FAEnabled: null,
|
|
200
|
-
devices: [],
|
|
201
|
-
fetchStatus: mockFetchStatus,
|
|
202
|
-
disable2FA: mockDisable2FA,
|
|
203
|
-
clearError: mockClearError,
|
|
204
|
-
}),
|
|
205
|
-
useTwoFactorSetup: () => ({
|
|
206
|
-
resetSetup: mockResetSetup,
|
|
207
|
-
}),
|
|
208
|
-
}));
|
|
209
|
-
|
|
210
|
-
// Note: Due to jest module caching, mock would need proper reset
|
|
211
|
-
});
|
|
212
|
-
});
|
|
213
|
-
|
|
214
|
-
describe('loading state', () => {
|
|
215
|
-
it('should show loading indicator when fetching initial status', () => {
|
|
216
|
-
jest.doMock('@djangocfg/api/auth', () => ({
|
|
217
|
-
useTwoFactorStatus: () => ({
|
|
218
|
-
isLoading: true,
|
|
219
|
-
error: null,
|
|
220
|
-
has2FAEnabled: null,
|
|
221
|
-
devices: [],
|
|
222
|
-
fetchStatus: mockFetchStatus,
|
|
223
|
-
disable2FA: mockDisable2FA,
|
|
224
|
-
clearError: mockClearError,
|
|
225
|
-
}),
|
|
226
|
-
useTwoFactorSetup: () => ({
|
|
227
|
-
resetSetup: mockResetSetup,
|
|
228
|
-
}),
|
|
229
|
-
}));
|
|
230
|
-
|
|
231
|
-
// Note: Due to jest module caching, mock would need proper reset
|
|
232
|
-
});
|
|
233
|
-
});
|
|
234
|
-
});
|
|
@@ -1,198 +0,0 @@
|
|
|
1
|
-
// @ts-nocheck
|
|
2
|
-
'use client';
|
|
3
|
-
|
|
4
|
-
import React, { useEffect, useState } from 'react';
|
|
5
|
-
import { useForm } from 'react-hook-form';
|
|
6
|
-
import { toast } from '@djangocfg/ui-core/hooks';
|
|
7
|
-
|
|
8
|
-
import {
|
|
9
|
-
PatchedCfgUserUpdateRequest, PatchedCfgUserUpdateRequestSchema,
|
|
10
|
-
useAuth
|
|
11
|
-
} from '@djangocfg/api/auth';
|
|
12
|
-
import {
|
|
13
|
-
Button, Form, FormControl, FormField, FormItem, FormLabel, FormMessage, Input, Label, PhoneInput
|
|
14
|
-
} from '@djangocfg/ui-core/components';
|
|
15
|
-
import { zodResolver } from '@hookform/resolvers/zod';
|
|
16
|
-
|
|
17
|
-
import { profileLogger } from '../../../utils/logger';
|
|
18
|
-
|
|
19
|
-
export const ProfileForm = () => {
|
|
20
|
-
const { user, updateProfile } = useAuth();
|
|
21
|
-
const [isEditing, setIsEditing] = useState(false);
|
|
22
|
-
const [isSaving, setIsSaving] = useState(false);
|
|
23
|
-
|
|
24
|
-
const form = useForm<PatchedCfgUserUpdateRequest>({
|
|
25
|
-
resolver: zodResolver(PatchedCfgUserUpdateRequestSchema),
|
|
26
|
-
defaultValues: {
|
|
27
|
-
first_name: '',
|
|
28
|
-
last_name: '',
|
|
29
|
-
company: '',
|
|
30
|
-
position: '',
|
|
31
|
-
phone: '',
|
|
32
|
-
},
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
// Load user data
|
|
36
|
-
useEffect(() => {
|
|
37
|
-
if (user) {
|
|
38
|
-
form.reset({
|
|
39
|
-
first_name: user.first_name || '',
|
|
40
|
-
last_name: user.last_name || '',
|
|
41
|
-
company: user.company || '',
|
|
42
|
-
position: user.position || '',
|
|
43
|
-
phone: user.phone || '',
|
|
44
|
-
});
|
|
45
|
-
}
|
|
46
|
-
}, [user, form]);
|
|
47
|
-
|
|
48
|
-
const handleSubmit = async (data: PatchedCfgUserUpdateRequest) => {
|
|
49
|
-
setIsSaving(true);
|
|
50
|
-
try {
|
|
51
|
-
await updateProfile(data);
|
|
52
|
-
toast.success('Profile updated successfully');
|
|
53
|
-
setIsEditing(false);
|
|
54
|
-
} catch (error: any) {
|
|
55
|
-
profileLogger.error('Profile update error:', error);
|
|
56
|
-
if (error?.response?.data) {
|
|
57
|
-
const fieldErrors = error.response.data;
|
|
58
|
-
Object.entries(fieldErrors).forEach(([field, messages]) => {
|
|
59
|
-
if (Array.isArray(messages) && messages.length > 0) {
|
|
60
|
-
form.setError(field as keyof PatchedCfgUserUpdateRequest, {
|
|
61
|
-
type: 'server',
|
|
62
|
-
message: messages[0],
|
|
63
|
-
});
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
toast.error('Please fix the validation errors');
|
|
67
|
-
} else {
|
|
68
|
-
toast.error('Failed to update profile');
|
|
69
|
-
}
|
|
70
|
-
} finally {
|
|
71
|
-
setIsSaving(false);
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
const handleCancel = () => {
|
|
76
|
-
setIsEditing(false);
|
|
77
|
-
form.clearErrors();
|
|
78
|
-
if (user) {
|
|
79
|
-
form.reset({
|
|
80
|
-
first_name: user.first_name || '',
|
|
81
|
-
last_name: user.last_name || '',
|
|
82
|
-
company: user.company || '',
|
|
83
|
-
position: user.position || '',
|
|
84
|
-
phone: user.phone || '',
|
|
85
|
-
});
|
|
86
|
-
}
|
|
87
|
-
};
|
|
88
|
-
|
|
89
|
-
const onSubmit = form.handleSubmit(handleSubmit);
|
|
90
|
-
|
|
91
|
-
return (
|
|
92
|
-
<Form {...form}>
|
|
93
|
-
<form onSubmit={onSubmit} className="space-y-4">
|
|
94
|
-
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
|
|
95
|
-
<div className="space-y-2 md:col-span-2">
|
|
96
|
-
<Label htmlFor="email">Email</Label>
|
|
97
|
-
<Input id="email" value={user?.email || ''} disabled className="bg-muted" />
|
|
98
|
-
</div>
|
|
99
|
-
|
|
100
|
-
<FormField
|
|
101
|
-
control={form.control}
|
|
102
|
-
name="first_name"
|
|
103
|
-
render={({ field }) => (
|
|
104
|
-
<FormItem>
|
|
105
|
-
<FormLabel>First Name</FormLabel>
|
|
106
|
-
<FormControl>
|
|
107
|
-
<Input {...field} disabled={!isEditing} placeholder="Enter first name" />
|
|
108
|
-
</FormControl>
|
|
109
|
-
<FormMessage />
|
|
110
|
-
</FormItem>
|
|
111
|
-
)}
|
|
112
|
-
/>
|
|
113
|
-
|
|
114
|
-
<FormField
|
|
115
|
-
control={form.control}
|
|
116
|
-
name="last_name"
|
|
117
|
-
render={({ field }) => (
|
|
118
|
-
<FormItem>
|
|
119
|
-
<FormLabel>Last Name</FormLabel>
|
|
120
|
-
<FormControl>
|
|
121
|
-
<Input {...field} disabled={!isEditing} placeholder="Enter last name" />
|
|
122
|
-
</FormControl>
|
|
123
|
-
<FormMessage />
|
|
124
|
-
</FormItem>
|
|
125
|
-
)}
|
|
126
|
-
/>
|
|
127
|
-
|
|
128
|
-
<FormField
|
|
129
|
-
control={form.control}
|
|
130
|
-
name="company"
|
|
131
|
-
render={({ field }) => (
|
|
132
|
-
<FormItem>
|
|
133
|
-
<FormLabel>Company</FormLabel>
|
|
134
|
-
<FormControl>
|
|
135
|
-
<Input {...field} disabled={!isEditing} placeholder="Enter company name" />
|
|
136
|
-
</FormControl>
|
|
137
|
-
<FormMessage />
|
|
138
|
-
</FormItem>
|
|
139
|
-
)}
|
|
140
|
-
/>
|
|
141
|
-
|
|
142
|
-
<FormField
|
|
143
|
-
control={form.control}
|
|
144
|
-
name="position"
|
|
145
|
-
render={({ field }) => (
|
|
146
|
-
<FormItem>
|
|
147
|
-
<FormLabel>Position</FormLabel>
|
|
148
|
-
<FormControl>
|
|
149
|
-
<Input {...field} disabled={!isEditing} placeholder="Enter position" />
|
|
150
|
-
</FormControl>
|
|
151
|
-
<FormMessage />
|
|
152
|
-
</FormItem>
|
|
153
|
-
)}
|
|
154
|
-
/>
|
|
155
|
-
|
|
156
|
-
<FormField
|
|
157
|
-
control={form.control}
|
|
158
|
-
name="phone"
|
|
159
|
-
render={({ field }) => (
|
|
160
|
-
<FormItem className="md:col-span-2">
|
|
161
|
-
<FormLabel>Phone</FormLabel>
|
|
162
|
-
<FormControl>
|
|
163
|
-
<PhoneInput
|
|
164
|
-
value={field.value}
|
|
165
|
-
onChange={field.onChange}
|
|
166
|
-
disabled={!isEditing}
|
|
167
|
-
placeholder="Enter phone number"
|
|
168
|
-
defaultCountry="US"
|
|
169
|
-
/>
|
|
170
|
-
</FormControl>
|
|
171
|
-
<FormMessage />
|
|
172
|
-
</FormItem>
|
|
173
|
-
)}
|
|
174
|
-
/>
|
|
175
|
-
</div>
|
|
176
|
-
|
|
177
|
-
{/* Action Buttons */}
|
|
178
|
-
<div className="flex items-center justify-between pt-4">
|
|
179
|
-
{isEditing ? (
|
|
180
|
-
<div className="flex items-center gap-2">
|
|
181
|
-
<Button type="submit" disabled={isSaving}>
|
|
182
|
-
{isSaving ? 'Saving...' : 'Save Changes'}
|
|
183
|
-
</Button>
|
|
184
|
-
<Button type="button" variant="outline" onClick={handleCancel}>
|
|
185
|
-
Cancel
|
|
186
|
-
</Button>
|
|
187
|
-
</div>
|
|
188
|
-
) : (
|
|
189
|
-
<Button type="button" onClick={() => setIsEditing(true)}>
|
|
190
|
-
Edit Profile
|
|
191
|
-
</Button>
|
|
192
|
-
)}
|
|
193
|
-
</div>
|
|
194
|
-
</form>
|
|
195
|
-
</Form>
|
|
196
|
-
);
|
|
197
|
-
};
|
|
198
|
-
|
|
File without changes
|