@trackany-device/components 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (159) hide show
  1. package/README.md +9 -9
  2. package/package.json +133 -4
  3. package/src/assets/index.ts +120 -0
  4. package/src/assets/media/avatars/300-1.png +0 -0
  5. package/src/assets/media/avatars/300-10.png +0 -0
  6. package/src/assets/media/avatars/300-11.png +0 -0
  7. package/src/assets/media/avatars/300-12.png +0 -0
  8. package/src/assets/media/avatars/300-13.png +0 -0
  9. package/src/assets/media/avatars/300-14.png +0 -0
  10. package/src/assets/media/avatars/300-15.png +0 -0
  11. package/src/assets/media/avatars/300-16.png +0 -0
  12. package/src/assets/media/avatars/300-17.png +0 -0
  13. package/src/assets/media/avatars/300-18.png +0 -0
  14. package/src/assets/media/avatars/300-19.png +0 -0
  15. package/src/assets/media/avatars/300-2.png +0 -0
  16. package/src/assets/media/avatars/300-20.png +0 -0
  17. package/src/assets/media/avatars/300-21.png +0 -0
  18. package/src/assets/media/avatars/300-22.png +0 -0
  19. package/src/assets/media/avatars/300-23.png +0 -0
  20. package/src/assets/media/avatars/300-24.png +0 -0
  21. package/src/assets/media/avatars/300-25.png +0 -0
  22. package/src/assets/media/avatars/300-26.png +0 -0
  23. package/src/assets/media/avatars/300-27.png +0 -0
  24. package/src/assets/media/avatars/300-28.png +0 -0
  25. package/src/assets/media/avatars/300-29.png +0 -0
  26. package/src/assets/media/avatars/300-3.png +0 -0
  27. package/src/assets/media/avatars/300-30.png +0 -0
  28. package/src/assets/media/avatars/300-31.png +0 -0
  29. package/src/assets/media/avatars/300-32.png +0 -0
  30. package/src/assets/media/avatars/300-33.png +0 -0
  31. package/src/assets/media/avatars/300-34.png +0 -0
  32. package/src/assets/media/avatars/300-4.png +0 -0
  33. package/src/assets/media/avatars/300-5.png +0 -0
  34. package/src/assets/media/avatars/300-6.png +0 -0
  35. package/src/assets/media/avatars/300-7.png +0 -0
  36. package/src/assets/media/avatars/300-8.png +0 -0
  37. package/src/assets/media/avatars/300-9.png +0 -0
  38. package/src/assets/media/avatars/blank.png +0 -0
  39. package/src/assets/media/avatars/gray/1.png +0 -0
  40. package/src/assets/media/avatars/gray/2.png +0 -0
  41. package/src/assets/media/avatars/gray/3.png +0 -0
  42. package/src/assets/media/avatars/gray/4.png +0 -0
  43. package/src/assets/media/avatars/gray/5.png +0 -0
  44. package/src/assets/media/illustrations/1-dark.svg +78 -0
  45. package/src/assets/media/illustrations/1.svg +78 -0
  46. package/src/assets/media/illustrations/10-dark.svg +148 -0
  47. package/src/assets/media/illustrations/10.svg +148 -0
  48. package/src/assets/media/illustrations/11-dark.svg +234 -0
  49. package/src/assets/media/illustrations/11.svg +234 -0
  50. package/src/assets/media/illustrations/12.svg +138 -0
  51. package/src/assets/media/illustrations/13.svg +205 -0
  52. package/src/assets/media/illustrations/14.svg +259 -0
  53. package/src/assets/media/illustrations/15.svg +242 -0
  54. package/src/assets/media/illustrations/16.svg +128 -0
  55. package/src/assets/media/illustrations/17.svg +180 -0
  56. package/src/assets/media/illustrations/18-dark.svg +6 -0
  57. package/src/assets/media/illustrations/18.svg +6 -0
  58. package/src/assets/media/illustrations/19-dark.svg +8 -0
  59. package/src/assets/media/illustrations/19.svg +8 -0
  60. package/src/assets/media/illustrations/2-dark.svg +78 -0
  61. package/src/assets/media/illustrations/2.svg +78 -0
  62. package/src/assets/media/illustrations/20-dark.svg +13 -0
  63. package/src/assets/media/illustrations/20.svg +13 -0
  64. package/src/assets/media/illustrations/21-dark.svg +9 -0
  65. package/src/assets/media/illustrations/21.svg +9 -0
  66. package/src/assets/media/illustrations/22-dark.svg +17 -0
  67. package/src/assets/media/illustrations/22.svg +17 -0
  68. package/src/assets/media/illustrations/23-dark.svg +13 -0
  69. package/src/assets/media/illustrations/23.svg +13 -0
  70. package/src/assets/media/illustrations/24.svg +6 -0
  71. package/src/assets/media/illustrations/25.svg +8 -0
  72. package/src/assets/media/illustrations/26.svg +8 -0
  73. package/src/assets/media/illustrations/27.svg +6 -0
  74. package/src/assets/media/illustrations/28-dark.svg +28 -0
  75. package/src/assets/media/illustrations/28.svg +14 -0
  76. package/src/assets/media/illustrations/29-dark.svg +6 -0
  77. package/src/assets/media/illustrations/29.svg +6 -0
  78. package/src/assets/media/illustrations/3-dark.svg +70 -0
  79. package/src/assets/media/illustrations/3.svg +70 -0
  80. package/src/assets/media/illustrations/30-dark.svg +8 -0
  81. package/src/assets/media/illustrations/30.svg +8 -0
  82. package/src/assets/media/illustrations/31-dark.svg +9 -0
  83. package/src/assets/media/illustrations/31.svg +9 -0
  84. package/src/assets/media/illustrations/32-dark.svg +10 -0
  85. package/src/assets/media/illustrations/32.svg +10 -0
  86. package/src/assets/media/illustrations/33-dark.svg +15 -0
  87. package/src/assets/media/illustrations/33.svg +15 -0
  88. package/src/assets/media/illustrations/34-dark.svg +5 -0
  89. package/src/assets/media/illustrations/34.svg +5 -0
  90. package/src/assets/media/illustrations/35-dark.svg +11 -0
  91. package/src/assets/media/illustrations/35.svg +4 -0
  92. package/src/assets/media/illustrations/4-dark.svg +51 -0
  93. package/src/assets/media/illustrations/4.svg +51 -0
  94. package/src/assets/media/illustrations/5-dark.svg +78 -0
  95. package/src/assets/media/illustrations/5.svg +78 -0
  96. package/src/assets/media/illustrations/6.svg +58 -0
  97. package/src/assets/media/illustrations/7.svg +49 -0
  98. package/src/assets/media/illustrations/8.svg +61 -0
  99. package/src/assets/media/illustrations/9.svg +57 -0
  100. package/src/assets/media/misc/placeholder.svg +15 -0
  101. package/src/components/devices/devices-mini-map.tsx +32 -26
  102. package/src/components/devices/map-marker.tsx +98 -0
  103. package/src/components/ui/checklist-item.tsx +55 -0
  104. package/src/components/ui/plan-card.tsx +68 -0
  105. package/src/components/ui/settings-row.tsx +32 -0
  106. package/src/components/ui/settings-section.tsx +22 -0
  107. package/src/components/ui/usage-meter.tsx +35 -0
  108. package/src/index.ts +12 -1
  109. package/src/layouts/LayoutSwitcher.tsx +220 -0
  110. package/src/layouts/app/MegaMenuLayout.tsx +69 -34
  111. package/src/layouts/app/MegaMenuNavbarLayout.tsx +73 -37
  112. package/src/layouts/app/NavbarCollapsibleLayout.tsx +53 -4
  113. package/src/layouts/app/NavbarSidebarLayout.tsx +74 -29
  114. package/src/layouts/app/SidebarDualMenuLayout.tsx +48 -5
  115. package/src/layouts/app/SidebarFixedLayout.tsx +15 -10
  116. package/src/layouts/app/SidebarMinimalLayout.tsx +51 -3
  117. package/src/layouts/app/SidebarTabsLayout.tsx +48 -2
  118. package/src/layouts/app/SplitSidebarLayout.tsx +91 -43
  119. package/src/layouts/app/TopNavLayout.tsx +7 -12
  120. package/src/layouts/app/WorkspaceSidebarLayout.tsx +103 -46
  121. package/src/layouts/app/partials/Navbar.tsx +61 -10
  122. package/src/layouts/app/partials/Toolbar.tsx +1 -1
  123. package/src/layouts/auth/AuthCenteredLayout.tsx +10 -4
  124. package/src/lib/map-markers.ts +21 -3
  125. package/src/pages/login/ConfirmPasswordPage.tsx +35 -0
  126. package/src/pages/login/ForgotPasswordPage.tsx +41 -0
  127. package/src/pages/login/LoginPage.tsx +50 -0
  128. package/src/pages/login/RegisterPage.tsx +41 -0
  129. package/src/pages/login/ResetPasswordPage.tsx +35 -0
  130. package/src/pages/login/TwoFactorChallengePage.tsx +41 -0
  131. package/src/pages/login/VerifyEmailPage.tsx +31 -0
  132. package/src/pages/my/ActivityPage.tsx +160 -0
  133. package/src/pages/my/GetStartedPage.tsx +221 -0
  134. package/src/pages/my/NotificationsPage.tsx +133 -0
  135. package/src/pages/my/ProfilePage.tsx +650 -0
  136. package/src/pages/my/TenantsPage.tsx +37 -0
  137. package/src/pages/tenant/AssigneesPage.tsx +155 -0
  138. package/src/pages/tenant/BeatsPage.tsx +403 -0
  139. package/src/pages/tenant/DashboardPage.tsx +195 -0
  140. package/src/pages/tenant/GeofencePage.tsx +422 -0
  141. package/src/pages/tenant/IncidentsPage.tsx +214 -0
  142. package/src/pages/tenant/IntegrationsPage.tsx +352 -0
  143. package/src/pages/tenant/InvitePage.tsx +153 -0
  144. package/src/pages/tenant/LiveStreamPage.tsx +141 -0
  145. package/src/pages/tenant/MembersPage.tsx +414 -0
  146. package/src/pages/tenant/TenantProfilePage.tsx +701 -0
  147. package/src/platform/adapters/default.tsx +1 -1
  148. package/src/platform/types.ts +2 -0
  149. package/src/styles/components/apexcharts.css +101 -0
  150. package/src/styles/components/image-input.css +51 -0
  151. package/src/styles/components/leaflet.css +25 -0
  152. package/src/styles/components/rating.css +89 -0
  153. package/src/styles/components/scrollable.css +119 -0
  154. package/src/styles/layout.css +24 -0
  155. package/src/styles/layouts/sidebar-fixed.css +93 -138
  156. package/src/styles/themes.css +5 -5
  157. package/src/vite-env.d.ts +21 -0
  158. package/src/layouts/SettingsLayout.tsx +0 -21
  159. package/src/layouts/app-layout.tsx +0 -29
@@ -0,0 +1,650 @@
1
+ import {
2
+ Avatar, AvatarFallback, AvatarImage,
3
+ Badge,
4
+ Button,
5
+ Card, CardContent, CardDescription, CardHeader, CardTitle,
6
+ SettingsSection,
7
+ Checkbox,
8
+ Input, Label, Textarea,
9
+ PasswordInput,
10
+ PlanCard,
11
+ RadioGroup,
12
+ Select,
13
+ Separator,
14
+ Switch,
15
+ Table, TableBody, TableCell, TableHead, TableHeader, TableRow,
16
+ AppearanceTabs,
17
+ } from '@trackany-device/components';
18
+ import { LayoutResolved } from '../../layouts/LayoutSwitcher';
19
+ import type { LayoutName } from '../../layouts/LayoutSwitcher';
20
+ import {
21
+ AlertTriangle, Bell, Boxes, Camera, CheckCircle2,
22
+ Copy, CreditCard, Download, Globe, KeyRound, Laptop,
23
+ Link2, Lock, Mail, Palette,
24
+ Plus, ShieldCheck, Smartphone,
25
+ Trash2, UserCircle, UserCog, Wifi,
26
+ } from 'lucide-react';
27
+ import { useState } from 'react';
28
+
29
+ export interface UserProfile {
30
+ name: string;
31
+ email: string;
32
+ phone: string;
33
+ role: string;
34
+ org: string;
35
+ initials: string;
36
+ }
37
+
38
+ export interface Session {
39
+ device: string;
40
+ location: string;
41
+ time: string;
42
+ current: boolean;
43
+ }
44
+
45
+ export interface LoginEvent {
46
+ event: string;
47
+ device: string;
48
+ date: string;
49
+ ok: boolean;
50
+ }
51
+
52
+ export interface Invoice {
53
+ id: string;
54
+ date: string;
55
+ amount: string;
56
+ status: string;
57
+ }
58
+
59
+ const SIDEBAR_SECTIONS = [
60
+ { id: 'basic', label: 'Basic info', icon: UserCircle },
61
+ { id: 'email', label: 'Email', icon: Mail },
62
+ { id: 'password', label: 'Password', icon: Lock },
63
+ { id: 'twofa', label: 'Two-factor auth', icon: ShieldCheck },
64
+ { id: 'social', label: 'Social sign-in', icon: Link2 },
65
+ { id: 'notifications', label: 'Notifications', icon: Bell },
66
+ { id: 'appearance', label: 'Appearance', icon: Palette },
67
+ { id: 'preferences', label: 'Preferences', icon: UserCog },
68
+ { id: 'api', label: 'API keys', icon: KeyRound },
69
+ { id: 'delete', label: 'Delete account', icon: Trash2 },
70
+ ];
71
+
72
+ export function UserProfilePage({ layout, user }: { layout: LayoutName; user: UserProfile }) {
73
+ return (
74
+ <LayoutResolved layout={layout} title="Profile" currentUrl="/settings/profile">
75
+ <div className="p-6 space-y-6">
76
+ <Card>
77
+ <CardContent className="p-6">
78
+ <div className="flex flex-col sm:flex-row items-start sm:items-center gap-4">
79
+ <div className="relative shrink-0">
80
+ <Avatar className="h-20 w-20">
81
+ <AvatarImage src="" alt={user.name} />
82
+ <AvatarFallback className="text-2xl">{user.initials}</AvatarFallback>
83
+ </Avatar>
84
+ <button className="absolute -bottom-1 -right-1 flex h-6 w-6 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm">
85
+ <Camera className="h-3.5 w-3.5" />
86
+ </button>
87
+ </div>
88
+ <div className="flex-1 min-w-0">
89
+ <div className="flex flex-wrap items-center gap-2">
90
+ <h1 className="text-xl font-semibold">{user.name}</h1>
91
+ <Badge variant="outline">{user.role}</Badge>
92
+ <Badge variant="outline" className="text-green-600 border-green-200 bg-green-50">Active</Badge>
93
+ </div>
94
+ <p className="text-sm text-muted-foreground mt-0.5">{user.email} · {user.org}</p>
95
+ <p className="text-xs text-muted-foreground mt-1">Member since March 2024 · Punjab, Pakistan</p>
96
+ </div>
97
+ <Button variant="outline" size="sm"><UserCog className="h-4 w-4 mr-1.5" />Edit profile</Button>
98
+ </div>
99
+ </CardContent>
100
+ </Card>
101
+
102
+ <div className="grid gap-6 lg:grid-cols-2">
103
+ <Card>
104
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
105
+ <CardTitle className="text-base">Personal information</CardTitle>
106
+ <Button variant="ghost" size="sm" className="text-primary h-7 px-2">Edit</Button>
107
+ </CardHeader>
108
+ <CardContent className="p-0">
109
+ <table className="w-full text-sm">
110
+ <tbody>
111
+ {[
112
+ ['Full name', user.name],
113
+ ['Phone', user.phone],
114
+ ['Date of birth', '12 Apr 1990'],
115
+ ['Gender', 'Male'],
116
+ ['Address', 'House 14, Street 7, Gulberg III, Lahore'],
117
+ ['City', 'Lahore, Punjab'],
118
+ ].map(([label, value]) => (
119
+ <tr key={label} className="border-b border-border last:border-0">
120
+ <td className="px-5 py-2.5 text-muted-foreground w-36">{label}</td>
121
+ <td className="px-5 py-2.5 font-medium">{value}</td>
122
+ </tr>
123
+ ))}
124
+ </tbody>
125
+ </table>
126
+ </CardContent>
127
+ </Card>
128
+
129
+ <Card>
130
+ <CardHeader className="flex flex-row items-center justify-between pb-2">
131
+ <CardTitle className="text-base">Work &amp; skills</CardTitle>
132
+ <Button variant="ghost" size="sm" className="text-primary h-7 px-2">Edit</Button>
133
+ </CardHeader>
134
+ <CardContent className="space-y-3 text-sm">
135
+ {[
136
+ ['Organisation', user.org],
137
+ ['Role', user.role],
138
+ ['Language', 'Urdu, English'],
139
+ ['Timezone', 'PKT (UTC+5)'],
140
+ ].map(([label, value]) => (
141
+ <div key={label} className="flex items-center justify-between border-b border-border pb-2 last:border-0 last:pb-0">
142
+ <span className="text-muted-foreground">{label}</span>
143
+ <span className="font-medium">{value}</span>
144
+ </div>
145
+ ))}
146
+ <div className="pt-1">
147
+ <p className="text-muted-foreground mb-2">Skills</p>
148
+ <div className="flex flex-wrap gap-1.5">
149
+ {['Fleet Management', 'GPS Tracking', 'Incident Response', 'Team Supervision', 'Route Planning'].map((s) => (
150
+ <Badge key={s} variant="secondary" className="text-xs">{s}</Badge>
151
+ ))}
152
+ </div>
153
+ </div>
154
+ </CardContent>
155
+ </Card>
156
+
157
+ <Card>
158
+ <CardHeader className="pb-2">
159
+ <CardTitle className="text-base">Account settings</CardTitle>
160
+ </CardHeader>
161
+ <CardContent className="p-0">
162
+ <table className="w-full text-sm">
163
+ <tbody>
164
+ {[
165
+ { label: 'Email', value: user.email, action: 'Change' },
166
+ { label: 'Password', value: '••••••••••', action: 'Change' },
167
+ { label: 'Two-factor auth', value: 'Not enabled', action: 'Enable' },
168
+ { label: 'Notifications', value: 'Email + Push', action: 'Manage' },
169
+ ].map(({ label, value, action }) => (
170
+ <tr key={label} className="border-b border-border last:border-0">
171
+ <td className="px-5 py-2.5 text-muted-foreground w-40">{label}</td>
172
+ <td className="px-5 py-2.5">{value}</td>
173
+ <td className="px-5 py-2.5 text-right"><Button variant="ghost" size="sm" className="text-primary h-6 px-1.5 text-xs">{action}</Button></td>
174
+ </tr>
175
+ ))}
176
+ </tbody>
177
+ </table>
178
+ </CardContent>
179
+ </Card>
180
+
181
+ <Card>
182
+ <CardHeader className="pb-2">
183
+ <CardTitle className="text-base">Achievements</CardTitle>
184
+ </CardHeader>
185
+ <CardContent>
186
+ <div className="grid grid-cols-2 gap-3">
187
+ {[
188
+ { icon: ShieldCheck, label: 'Safety Champion', desc: 'Zero incidents Q1', color: 'text-green-600 bg-green-50' },
189
+ { icon: Boxes, label: 'Fleet Expert', desc: '300+ vehicles', color: 'text-blue-600 bg-blue-50' },
190
+ { icon: Globe, label: 'Province Coverage', desc: 'All Punjab districts', color: 'text-purple-600 bg-purple-50' },
191
+ { icon: Wifi, label: 'Always Connected', desc: '99.2% uptime', color: 'text-amber-600 bg-amber-50' },
192
+ ].map(({ icon: Icon, label, desc, color }) => (
193
+ <div key={label} className="rounded-lg border border-border p-3 flex items-start gap-2.5">
194
+ <div className={`rounded-lg p-2 shrink-0 ${color}`}><Icon className="h-4 w-4" /></div>
195
+ <div>
196
+ <p className="text-xs font-semibold">{label}</p>
197
+ <p className="text-xs text-muted-foreground">{desc}</p>
198
+ </div>
199
+ </div>
200
+ ))}
201
+ </div>
202
+ </CardContent>
203
+ </Card>
204
+ </div>
205
+ </div>
206
+ </LayoutResolved>
207
+ );
208
+ }
209
+
210
+ export function SettingsPlainPage({ layout, user }: { layout: LayoutName; user: UserProfile }) {
211
+ const [name, setName] = useState(user.name);
212
+ const [phone, setPhone] = useState(user.phone);
213
+ const [bio, setBio] = useState(`Fleet supervisor at ${user.org}, managing 300+ GPS-tracked vehicles across Punjab province.`);
214
+ const [saved, setSaved] = useState(false);
215
+ return (
216
+ <LayoutResolved layout={layout} title="Account Settings" currentUrl="/settings/profile">
217
+ <div className="p-6 max-w-xl mx-auto space-y-6">
218
+ <h1 className="text-xl font-semibold">Account settings</h1>
219
+
220
+ {saved && (
221
+ <div className="flex items-center gap-2 rounded-lg border border-green-200 bg-green-50 px-4 py-2.5 text-sm text-green-700">
222
+ <CheckCircle2 className="h-4 w-4 shrink-0" />Profile updated successfully.
223
+ </div>
224
+ )}
225
+
226
+ <Card>
227
+ <CardHeader><CardTitle>Profile photo</CardTitle></CardHeader>
228
+ <CardContent>
229
+ <div className="flex items-center gap-4">
230
+ <Avatar className="h-16 w-16">
231
+ <AvatarFallback className="text-xl">{user.initials}</AvatarFallback>
232
+ </Avatar>
233
+ <div className="space-y-1.5">
234
+ <Button variant="outline" size="sm"><Camera className="h-3.5 w-3.5 mr-1.5" />Upload photo</Button>
235
+ <p className="text-xs text-muted-foreground">JPG or PNG, max 2 MB</p>
236
+ </div>
237
+ </div>
238
+ </CardContent>
239
+ </Card>
240
+
241
+ <Card>
242
+ <CardHeader><CardTitle>Personal details</CardTitle></CardHeader>
243
+ <CardContent className="space-y-4">
244
+ <div className="grid gap-4 sm:grid-cols-2">
245
+ <div className="space-y-1.5">
246
+ <Label htmlFor="sp-name">Full name</Label>
247
+ <Input id="sp-name" value={name} onChange={(e) => setName(e.target.value)} />
248
+ </div>
249
+ <div className="space-y-1.5">
250
+ <Label htmlFor="sp-email">Email address</Label>
251
+ <Input id="sp-email" value={user.email} disabled className="opacity-60" />
252
+ </div>
253
+ <div className="space-y-1.5">
254
+ <Label htmlFor="sp-phone">Phone number</Label>
255
+ <Input id="sp-phone" type="tel" value={phone} onChange={(e) => setPhone(e.target.value)} />
256
+ </div>
257
+ <div className="space-y-1.5">
258
+ <Label htmlFor="sp-dob">Date of birth</Label>
259
+ <Input id="sp-dob" type="date" defaultValue="1990-04-12" />
260
+ </div>
261
+ <div className="space-y-1.5">
262
+ <Label htmlFor="sp-country">Country</Label>
263
+ <Select id="sp-country" defaultValue="pk">
264
+ <option value="pk">Pakistan</option>
265
+ <option value="ae">UAE</option>
266
+ <option value="gb">United Kingdom</option>
267
+ </Select>
268
+ </div>
269
+ <div className="space-y-1.5">
270
+ <Label htmlFor="sp-city">City</Label>
271
+ <Input id="sp-city" defaultValue="Lahore" />
272
+ </div>
273
+ </div>
274
+ <div className="space-y-1.5">
275
+ <Label htmlFor="sp-bio">Bio</Label>
276
+ <Textarea id="sp-bio" rows={3} value={bio} onChange={(e) => setBio(e.target.value)} />
277
+ </div>
278
+ </CardContent>
279
+ </Card>
280
+
281
+ <Card>
282
+ <CardHeader><CardTitle>Change password</CardTitle></CardHeader>
283
+ <CardContent className="space-y-4">
284
+ <div className="space-y-1.5">
285
+ <Label htmlFor="sp-cur">Current password</Label>
286
+ <PasswordInput id="sp-cur" />
287
+ </div>
288
+ <div className="grid gap-4 sm:grid-cols-2">
289
+ <div className="space-y-1.5">
290
+ <Label htmlFor="sp-new">New password</Label>
291
+ <PasswordInput id="sp-new" />
292
+ </div>
293
+ <div className="space-y-1.5">
294
+ <Label htmlFor="sp-cnf">Confirm password</Label>
295
+ <PasswordInput id="sp-cnf" />
296
+ </div>
297
+ </div>
298
+ </CardContent>
299
+ </Card>
300
+
301
+ <div className="flex justify-end gap-3">
302
+ <Button variant="outline">Cancel</Button>
303
+ <Button onClick={() => setSaved(true)}>Save changes</Button>
304
+ </div>
305
+ </div>
306
+ </LayoutResolved>
307
+ );
308
+ }
309
+
310
+ export function SettingsSidebarPage({ layout, user }: { layout: LayoutName; user: UserProfile }) {
311
+ const [active, setActive] = useState('basic');
312
+ return (
313
+ <LayoutResolved layout={layout} title="Settings" currentUrl="/settings/profile">
314
+ <div className="flex min-h-full">
315
+ <aside className="hidden lg:flex flex-col gap-1 w-56 shrink-0 sticky top-0 self-start h-screen overflow-y-auto border-r border-border p-4">
316
+ <p className="px-3 py-1 text-xs font-semibold text-muted-foreground uppercase tracking-wider mb-1">Settings</p>
317
+ {SIDEBAR_SECTIONS.map(({ id, label, icon: Icon }) => (
318
+ <button
319
+ key={id}
320
+ onClick={() => setActive(id)}
321
+ className={`flex items-center gap-2.5 rounded-lg px-3 py-2 text-sm text-left transition-colors ${active === id ? 'bg-primary/10 text-primary font-medium' : 'text-foreground hover:bg-muted'}`}
322
+ >
323
+ <Icon className="h-4 w-4 shrink-0" />{label}
324
+ </button>
325
+ ))}
326
+ </aside>
327
+
328
+ <div className="flex-1 p-6 space-y-6 max-w-2xl">
329
+ <SettingsSection id="basic" title="Basic information" description="Update your name, photo and contact details.">
330
+ <div className="space-y-4">
331
+ <div className="flex items-center gap-4">
332
+ <Avatar className="h-14 w-14"><AvatarFallback>{user.initials}</AvatarFallback></Avatar>
333
+ <Button variant="outline" size="sm"><Camera className="h-3.5 w-3.5 mr-1.5" />Change photo</Button>
334
+ </div>
335
+ <div className="grid gap-4 sm:grid-cols-2">
336
+ <div className="space-y-1.5"><Label>Full name</Label><Input defaultValue={user.name} /></div>
337
+ <div className="space-y-1.5"><Label>Phone</Label><Input defaultValue={user.phone} /></div>
338
+ <div className="space-y-1.5"><Label>Country</Label><Select defaultValue="pk"><option value="pk">Pakistan</option></Select></div>
339
+ <div className="space-y-1.5"><Label>Timezone</Label><Select defaultValue="pkt"><option value="pkt">PKT (UTC+5)</option></Select></div>
340
+ </div>
341
+ <div className="space-y-1.5"><Label>Bio</Label><Textarea rows={2} defaultValue={`Fleet supervisor at ${user.org}.`} /></div>
342
+ <div className="flex justify-end"><Button size="sm">Save</Button></div>
343
+ </div>
344
+ </SettingsSection>
345
+
346
+ <SettingsSection id="email" title="Email address" description="Your email is used for login and notifications.">
347
+ <div className="space-y-4">
348
+ <div className="space-y-1.5"><Label>Email address</Label><Input type="email" defaultValue={user.email} /></div>
349
+ <div className="flex items-center justify-between">
350
+ <div><p className="text-sm font-medium">Primary email</p><p className="text-xs text-muted-foreground">Used for all account notifications</p></div>
351
+ <Switch defaultChecked />
352
+ </div>
353
+ <Separator />
354
+ <div className="flex justify-end"><Button size="sm">Update email</Button></div>
355
+ </div>
356
+ </SettingsSection>
357
+
358
+ <SettingsSection id="password" title="Change password" description="Use a strong password of at least 8 characters.">
359
+ <div className="space-y-4">
360
+ <div className="space-y-1.5"><Label>Current password</Label><PasswordInput /></div>
361
+ <div className="grid gap-4 sm:grid-cols-2">
362
+ <div className="space-y-1.5"><Label>New password</Label><PasswordInput /></div>
363
+ <div className="space-y-1.5"><Label>Confirm password</Label><PasswordInput /></div>
364
+ </div>
365
+ <div className="flex justify-end"><Button size="sm">Update password</Button></div>
366
+ </div>
367
+ </SettingsSection>
368
+
369
+ <SettingsSection id="twofa" title="Two-factor authentication" description="Add a second layer of security to your account.">
370
+ <div className="space-y-4">
371
+ <div className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 flex items-start gap-3">
372
+ <AlertTriangle className="h-4 w-4 text-amber-600 mt-0.5 shrink-0" />
373
+ <p className="text-sm text-amber-800">Two-factor authentication is currently disabled. Enable it to better protect your account.</p>
374
+ </div>
375
+ <RadioGroup
376
+ name="2fa-method"
377
+ defaultValue="app"
378
+ options={[
379
+ { value: 'app', label: 'Authenticator app', description: 'Use Google Authenticator or similar' },
380
+ { value: 'sms', label: 'SMS verification', description: 'Receive a code to your phone number' },
381
+ ]}
382
+ />
383
+ <div className="flex justify-end"><Button size="sm">Enable 2FA</Button></div>
384
+ </div>
385
+ </SettingsSection>
386
+
387
+ <SettingsSection id="social" title="Social sign-in" description="Connect external accounts for faster login.">
388
+ <div className="space-y-3">
389
+ {[
390
+ { name: 'Google', connected: true, email: 'ahmad@gmail.com' },
391
+ { name: 'Microsoft', connected: false, email: null },
392
+ { name: 'Apple', connected: false, email: null },
393
+ ].map(({ name, connected, email }) => (
394
+ <div key={name} className="flex items-center justify-between py-2 border-b border-border last:border-0">
395
+ <div>
396
+ <p className="text-sm font-medium">{name}</p>
397
+ {email && <p className="text-xs text-muted-foreground">{email}</p>}
398
+ </div>
399
+ {connected
400
+ ? <Button variant="outline" size="sm" className="text-destructive border-destructive/30 hover:bg-destructive/5">Disconnect</Button>
401
+ : <Button variant="outline" size="sm"><Plus className="h-3.5 w-3.5 mr-1" />Connect</Button>
402
+ }
403
+ </div>
404
+ ))}
405
+ </div>
406
+ </SettingsSection>
407
+
408
+ <SettingsSection id="notifications" title="Notifications" description="Choose what alerts you want to receive.">
409
+ <div className="space-y-1">
410
+ {[
411
+ { label: 'Critical incidents', desc: 'SOS and high-priority rule violations', on: true },
412
+ { label: 'Device offline', desc: 'When a device stops reporting', on: true },
413
+ { label: 'Low battery alerts', desc: 'Battery below 20%', on: false },
414
+ { label: 'Geofence violations', desc: 'Zone entry/exit events', on: true },
415
+ { label: 'Weekly email digest', desc: 'Fleet summary every Monday', on: true },
416
+ { label: 'Trip completed', desc: 'When assignees finish trips', on: false },
417
+ ].map(({ label, desc, on }) => (
418
+ <div key={label} className="flex items-start justify-between py-2.5 border-b border-border last:border-0">
419
+ <div><p className="text-sm font-medium">{label}</p><p className="text-xs text-muted-foreground mt-0.5">{desc}</p></div>
420
+ <Switch defaultChecked={on} />
421
+ </div>
422
+ ))}
423
+ </div>
424
+ </SettingsSection>
425
+
426
+ <SettingsSection id="appearance" title="Appearance" description="Personalise how the app looks for you.">
427
+ <div className="space-y-4">
428
+ <div><p className="text-sm font-medium mb-2">Theme</p><AppearanceTabs /></div>
429
+ <Separator />
430
+ <div className="flex items-center justify-between">
431
+ <div><p className="text-sm font-medium">Compact sidebar</p><p className="text-xs text-muted-foreground">Show only icons in the sidebar</p></div>
432
+ <Switch />
433
+ </div>
434
+ </div>
435
+ </SettingsSection>
436
+
437
+ <SettingsSection id="preferences" title="Preferences" description="Language, timezone and display preferences.">
438
+ <div className="space-y-4">
439
+ <div className="grid gap-4 sm:grid-cols-2">
440
+ <div className="space-y-1.5"><Label>Language</Label><Select defaultValue="en"><option value="en">English</option><option value="ur">Urdu</option></Select></div>
441
+ <div className="space-y-1.5"><Label>Date format</Label><Select defaultValue="dmy"><option value="dmy">DD/MM/YYYY</option><option value="mdy">MM/DD/YYYY</option></Select></div>
442
+ </div>
443
+ <div className="space-y-2">
444
+ <p className="text-sm font-medium">Email visibility</p>
445
+ <RadioGroup name="email-vis" defaultValue="team" options={[
446
+ { value: 'public', label: 'Public — visible to everyone' },
447
+ { value: 'team', label: 'Team only — visible to my organisation' },
448
+ { value: 'private', label: 'Private — hidden from everyone' },
449
+ ]} />
450
+ </div>
451
+ <div className="flex justify-end"><Button size="sm">Save preferences</Button></div>
452
+ </div>
453
+ </SettingsSection>
454
+
455
+ <SettingsSection id="api" title="API keys" description="Use these keys to access the TAD API programmatically.">
456
+ <div className="space-y-4">
457
+ <div className="space-y-1.5">
458
+ <Label>Personal access token</Label>
459
+ <div className="flex gap-2">
460
+ <Input value="tad_live_••••••••••••••••••••••••••••••••" readOnly className="font-mono text-xs" />
461
+ <Button variant="outline" size="icon"><Copy className="h-4 w-4" /></Button>
462
+ </div>
463
+ <p className="text-xs text-muted-foreground">Expires 15 Jan 2027 · Full access</p>
464
+ </div>
465
+ <Separator />
466
+ <div className="flex items-center justify-between">
467
+ <div><p className="text-sm font-medium">Webhook endpoint</p><p className="text-xs text-muted-foreground font-mono">https://api.tad.io/webhooks/tad</p></div>
468
+ <Button variant="outline" size="sm"><Copy className="h-3.5 w-3.5 mr-1" />Copy</Button>
469
+ </div>
470
+ </div>
471
+ </SettingsSection>
472
+
473
+ <SettingsSection id="delete" title="Delete account" description="Permanently remove your account and all its data.">
474
+ <div className="space-y-4">
475
+ <div className="rounded-lg border border-destructive/40 bg-destructive/5 p-4 space-y-2">
476
+ <p className="text-sm font-semibold text-destructive flex items-center gap-1.5"><AlertTriangle className="h-4 w-4" />Warning — this action is irreversible</p>
477
+ <p className="text-xs text-muted-foreground">Deleting your account will remove all your personal data, trip history, and preferences. You will lose access to all connected organisations.</p>
478
+ </div>
479
+ <Checkbox id="del-confirm" label="I understand this action cannot be undone." />
480
+ <div className="flex gap-2">
481
+ <Button variant="outline" size="sm">Deactivate account</Button>
482
+ <Button variant="destructive" size="sm"><Trash2 className="h-3.5 w-3.5 mr-1.5" />Delete account</Button>
483
+ </div>
484
+ </div>
485
+ </SettingsSection>
486
+ </div>
487
+ </div>
488
+ </LayoutResolved>
489
+ );
490
+ }
491
+
492
+ export function SecurityPage({ layout, user, sessions, loginActivity }: {
493
+ layout: LayoutName;
494
+ user: UserProfile;
495
+ sessions: Session[];
496
+ loginActivity: LoginEvent[];
497
+ }) {
498
+ return (
499
+ <LayoutResolved layout={layout} title="Security" currentUrl="/settings/security">
500
+ <div className="p-6 max-w-2xl mx-auto space-y-6">
501
+ <h1 className="text-xl font-semibold">Security</h1>
502
+
503
+ <Card>
504
+ <CardHeader><CardTitle>Two-factor authentication</CardTitle><CardDescription>Protect your account with an additional verification step.</CardDescription></CardHeader>
505
+ <CardContent className="space-y-4">
506
+ <div className="flex items-center justify-between rounded-lg border border-border p-4">
507
+ <div className="flex items-center gap-3">
508
+ <div className="rounded-full bg-amber-100 p-2"><ShieldCheck className="h-5 w-5 text-amber-600" /></div>
509
+ <div>
510
+ <p className="text-sm font-medium">Two-factor auth is disabled</p>
511
+ <p className="text-xs text-muted-foreground">Enable to add extra security to your account</p>
512
+ </div>
513
+ </div>
514
+ <Button size="sm">Enable</Button>
515
+ </div>
516
+ <RadioGroup name="2fa-type" defaultValue="app" options={[
517
+ { value: 'app', label: 'Authenticator app', description: 'Scan a QR code with Google Authenticator' },
518
+ { value: 'sms', label: 'SMS code', description: `Text message to ${user.phone}` },
519
+ ]} />
520
+ </CardContent>
521
+ </Card>
522
+
523
+ <Card>
524
+ <CardHeader className="flex flex-row items-center justify-between">
525
+ <div><CardTitle>Active sessions</CardTitle><CardDescription>Devices currently signed in to your account.</CardDescription></div>
526
+ <Button variant="outline" size="sm" className="text-destructive border-destructive/30"><Laptop className="h-3.5 w-3.5 mr-1.5" />Sign out all</Button>
527
+ </CardHeader>
528
+ <CardContent className="p-0">
529
+ <Table>
530
+ <TableHeader><TableRow><TableHead>Device</TableHead><TableHead>Location</TableHead><TableHead>Last active</TableHead><TableHead /></TableRow></TableHeader>
531
+ <TableBody>
532
+ {sessions.map((s) => (
533
+ <TableRow key={s.device}>
534
+ <TableCell>
535
+ <div className="flex items-center gap-2">
536
+ {s.device.includes('iPhone') ? <Smartphone className="h-4 w-4 text-muted-foreground shrink-0" /> : <Laptop className="h-4 w-4 text-muted-foreground shrink-0" />}
537
+ <span className="text-sm">{s.device}</span>
538
+ </div>
539
+ </TableCell>
540
+ <TableCell className="text-sm text-muted-foreground">{s.location}</TableCell>
541
+ <TableCell className="text-sm text-muted-foreground">{s.time}</TableCell>
542
+ <TableCell className="text-right">
543
+ {s.current
544
+ ? <Badge variant="outline" className="text-green-600 border-green-200 bg-green-50 text-xs">This device</Badge>
545
+ : <Button variant="ghost" size="sm" className="text-destructive h-7 px-2 text-xs">Revoke</Button>
546
+ }
547
+ </TableCell>
548
+ </TableRow>
549
+ ))}
550
+ </TableBody>
551
+ </Table>
552
+ </CardContent>
553
+ </Card>
554
+
555
+ <Card>
556
+ <CardHeader><CardTitle>Recent login activity</CardTitle></CardHeader>
557
+ <CardContent className="p-0">
558
+ <Table>
559
+ <TableHeader><TableRow><TableHead>Event</TableHead><TableHead>Device</TableHead><TableHead>Date</TableHead><TableHead>Status</TableHead></TableRow></TableHeader>
560
+ <TableBody>
561
+ {loginActivity.map((row, i) => (
562
+ <TableRow key={i}>
563
+ <TableCell className="text-sm font-medium">{row.event}</TableCell>
564
+ <TableCell className="text-sm text-muted-foreground">{row.device}</TableCell>
565
+ <TableCell className="text-sm text-muted-foreground">{row.date}</TableCell>
566
+ <TableCell>
567
+ <Badge variant="outline" className={row.ok ? 'text-green-600 border-green-200 bg-green-50 text-xs' : 'text-red-600 border-red-200 bg-red-50 text-xs'}>
568
+ {row.ok ? 'Success' : 'Failed'}
569
+ </Badge>
570
+ </TableCell>
571
+ </TableRow>
572
+ ))}
573
+ </TableBody>
574
+ </Table>
575
+ </CardContent>
576
+ </Card>
577
+ </div>
578
+ </LayoutResolved>
579
+ );
580
+ }
581
+
582
+ export function BillingPage({ layout, invoices }: { layout: LayoutName; invoices: Invoice[] }) {
583
+ return (
584
+ <LayoutResolved layout={layout} title="Billing" currentUrl="/settings/billing">
585
+ <div className="p-6 max-w-2xl mx-auto space-y-6">
586
+ <h1 className="text-xl font-semibold">Billing &amp; plan</h1>
587
+
588
+ <PlanCard
589
+ name="Professional Plan"
590
+ status="Active"
591
+ price="PKR 8,500 / month · Up to 500 devices · 10 users"
592
+ renewal="Renews on 1 June 2026 · Auto-renewal enabled"
593
+ action={<Button variant="outline" size="sm">Upgrade plan</Button>}
594
+ usage={[
595
+ { label: 'Devices', used: 312, limit: 500 },
596
+ { label: 'Users', used: 8, limit: 10 },
597
+ { label: 'Storage', used: 12, limit: 50 },
598
+ ]}
599
+ />
600
+
601
+ <Card>
602
+ <CardHeader className="flex flex-row items-center justify-between">
603
+ <CardTitle>Payment method</CardTitle>
604
+ <Button variant="outline" size="sm"><Plus className="h-3.5 w-3.5 mr-1" />Add method</Button>
605
+ </CardHeader>
606
+ <CardContent>
607
+ <div className="flex items-center justify-between rounded-lg border border-border p-4">
608
+ <div className="flex items-center gap-3">
609
+ <div className="rounded-lg bg-muted p-2"><CreditCard className="h-5 w-5 text-muted-foreground" /></div>
610
+ <div>
611
+ <p className="text-sm font-medium">Meezan Bank — •••• 4242</p>
612
+ <p className="text-xs text-muted-foreground">Expires 09/2028</p>
613
+ </div>
614
+ </div>
615
+ <Badge variant="outline" className="text-green-600 border-green-200 bg-green-50">Default</Badge>
616
+ </div>
617
+ </CardContent>
618
+ </Card>
619
+
620
+ <Card>
621
+ <CardHeader><CardTitle>Invoice history</CardTitle></CardHeader>
622
+ <CardContent className="p-0">
623
+ <Table>
624
+ <TableHeader><TableRow><TableHead>Invoice</TableHead><TableHead>Date</TableHead><TableHead>Amount</TableHead><TableHead>Status</TableHead><TableHead /></TableRow></TableHeader>
625
+ <TableBody>
626
+ {invoices.map((inv) => (
627
+ <TableRow key={inv.id}>
628
+ <TableCell className="font-mono text-xs font-medium">{inv.id}</TableCell>
629
+ <TableCell className="text-sm text-muted-foreground">{inv.date}</TableCell>
630
+ <TableCell className="text-sm font-medium">{inv.amount}</TableCell>
631
+ <TableCell><Badge variant="outline" className="text-green-600 border-green-200 bg-green-50 text-xs">{inv.status}</Badge></TableCell>
632
+ <TableCell className="text-right"><Button variant="ghost" size="sm" className="h-7 px-2"><Download className="h-3.5 w-3.5" /></Button></TableCell>
633
+ </TableRow>
634
+ ))}
635
+ </TableBody>
636
+ </Table>
637
+ </CardContent>
638
+ </Card>
639
+
640
+ <Card className="border-destructive/30">
641
+ <CardHeader><CardTitle className="text-destructive">Cancel subscription</CardTitle></CardHeader>
642
+ <CardContent>
643
+ <p className="text-sm text-muted-foreground mb-4">Cancelling your plan will disable device tracking at the end of the current billing period. You will retain read-only access to historical data for 30 days.</p>
644
+ <Button variant="outline" size="sm" className="text-destructive border-destructive/40">Cancel subscription</Button>
645
+ </CardContent>
646
+ </Card>
647
+ </div>
648
+ </LayoutResolved>
649
+ );
650
+ }