@actuate-media/cms-admin 0.36.0 → 0.36.1

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 (73) hide show
  1. package/dist/actuate-admin.css +1 -1
  2. package/dist/views/ApiKeys.d.ts.map +1 -1
  3. package/dist/views/ApiKeys.js +14 -13
  4. package/dist/views/ApiKeys.js.map +1 -1
  5. package/dist/views/Profile.d.ts.map +1 -1
  6. package/dist/views/Profile.js +1 -1
  7. package/dist/views/Profile.js.map +1 -1
  8. package/dist/views/Settings.d.ts.map +1 -1
  9. package/dist/views/Settings.js +25 -20
  10. package/dist/views/Settings.js.map +1 -1
  11. package/dist/views/Users.js +1 -1
  12. package/dist/views/Users.js.map +1 -1
  13. package/dist/views/settings/AISettingsTab.js +40 -42
  14. package/dist/views/settings/AISettingsTab.js.map +1 -1
  15. package/dist/views/settings/AiFeaturesCard.d.ts.map +1 -1
  16. package/dist/views/settings/AiFeaturesCard.js +2 -3
  17. package/dist/views/settings/AiFeaturesCard.js.map +1 -1
  18. package/dist/views/settings/BrandVoiceCard.d.ts.map +1 -1
  19. package/dist/views/settings/BrandVoiceCard.js +2 -4
  20. package/dist/views/settings/BrandVoiceCard.js.map +1 -1
  21. package/dist/views/settings/GeneralSettingsTab.js +1 -1
  22. package/dist/views/settings/LanguageRegionCard.d.ts.map +1 -1
  23. package/dist/views/settings/LanguageRegionCard.js +2 -1
  24. package/dist/views/settings/LanguageRegionCard.js.map +1 -1
  25. package/dist/views/settings/MonthlyUsageCard.d.ts.map +1 -1
  26. package/dist/views/settings/MonthlyUsageCard.js +3 -3
  27. package/dist/views/settings/MonthlyUsageCard.js.map +1 -1
  28. package/dist/views/settings/ProviderConnectionCard.js +1 -1
  29. package/dist/views/settings/ProviderConnectionCard.js.map +1 -1
  30. package/dist/views/settings/SecurityStatusCard.d.ts.map +1 -1
  31. package/dist/views/settings/SecurityStatusCard.js +2 -3
  32. package/dist/views/settings/SecurityStatusCard.js.map +1 -1
  33. package/dist/views/settings/SecurityTab.js +1 -1
  34. package/dist/views/settings/SeoRobotsDefaultsCard.d.ts.map +1 -1
  35. package/dist/views/settings/SeoRobotsDefaultsCard.js +3 -4
  36. package/dist/views/settings/SeoRobotsDefaultsCard.js.map +1 -1
  37. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  38. package/dist/views/settings/SeoSettingsTab.js +5 -8
  39. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  40. package/dist/views/settings/SiteIndexStatusCard.d.ts.map +1 -1
  41. package/dist/views/settings/SiteIndexStatusCard.js +3 -2
  42. package/dist/views/settings/SiteIndexStatusCard.js.map +1 -1
  43. package/dist/views/settings/SiteInformationCard.d.ts.map +1 -1
  44. package/dist/views/settings/SiteInformationCard.js +4 -4
  45. package/dist/views/settings/SiteInformationCard.js.map +1 -1
  46. package/dist/views/settings/components.d.ts +31 -1
  47. package/dist/views/settings/components.d.ts.map +1 -1
  48. package/dist/views/settings/components.js +22 -0
  49. package/dist/views/settings/components.js.map +1 -1
  50. package/dist/views/settings/securityPrimitives.d.ts +11 -3
  51. package/dist/views/settings/securityPrimitives.d.ts.map +1 -1
  52. package/dist/views/settings/securityPrimitives.js +10 -3
  53. package/dist/views/settings/securityPrimitives.js.map +1 -1
  54. package/package.json +1 -1
  55. package/src/views/ApiKeys.tsx +190 -154
  56. package/src/views/Profile.tsx +4 -2
  57. package/src/views/Settings.tsx +154 -143
  58. package/src/views/Users.tsx +1 -1
  59. package/src/views/settings/AISettingsTab.tsx +64 -76
  60. package/src/views/settings/AiFeaturesCard.tsx +6 -12
  61. package/src/views/settings/BrandVoiceCard.tsx +6 -19
  62. package/src/views/settings/GeneralSettingsTab.tsx +1 -1
  63. package/src/views/settings/LanguageRegionCard.tsx +5 -7
  64. package/src/views/settings/MonthlyUsageCard.tsx +6 -10
  65. package/src/views/settings/ProviderConnectionCard.tsx +1 -1
  66. package/src/views/settings/SecurityStatusCard.tsx +8 -11
  67. package/src/views/settings/SecurityTab.tsx +1 -1
  68. package/src/views/settings/SeoRobotsDefaultsCard.tsx +7 -14
  69. package/src/views/settings/SeoSettingsTab.tsx +17 -26
  70. package/src/views/settings/SiteIndexStatusCard.tsx +10 -22
  71. package/src/views/settings/SiteInformationCard.tsx +3 -9
  72. package/src/views/settings/components.tsx +72 -1
  73. package/src/views/settings/securityPrimitives.tsx +14 -9
@@ -27,6 +27,7 @@ import { SeoSettingsTab } from './settings/SeoSettingsTab.js'
27
27
  import { AISettingsTab } from './settings/AISettingsTab.js'
28
28
  import { AppearanceTab } from './settings/AppearanceTab.js'
29
29
  import { SecurityTab } from './settings/SecurityTab.js'
30
+ import { SettingsCard } from './settings/components.js'
30
31
  import { Users as UsersView } from './Users.js'
31
32
  import { ApiKeys as ApiKeysView } from './ApiKeys.js'
32
33
 
@@ -165,12 +166,15 @@ export function Settings({
165
166
  return (
166
167
  <div className="p-3 pr-6 sm:p-4 sm:pr-8">
167
168
  {error && (
168
- <div className="mb-4 flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
169
- <AlertTriangle className="h-5 w-5 shrink-0 text-red-600" />
170
- <span className="flex-1 text-sm text-red-800">{error}</span>
169
+ <div
170
+ role="alert"
171
+ className="border-destructive/30 bg-destructive/10 text-destructive mb-4 flex items-center gap-3 rounded-lg border p-3 text-sm"
172
+ >
173
+ <AlertTriangle size={20} className="shrink-0" aria-hidden="true" />
174
+ <span className="flex-1">{error}</span>
171
175
  <button
172
176
  onClick={refetch}
173
- className="rounded-lg border border-red-300 px-3 py-1 text-sm text-red-700 transition-colors hover:bg-red-100"
177
+ className="border-destructive/40 rounded-md border px-3 py-1 transition-colors hover:opacity-80"
174
178
  >
175
179
  Retry
176
180
  </button>
@@ -178,12 +182,12 @@ export function Settings({
178
182
  )}
179
183
 
180
184
  <div className="mb-4">
181
- <h1 className="mb-1 text-2xl font-semibold text-gray-900">Settings</h1>
182
- <p className="text-sm text-gray-600">Manage your CMS configuration</p>
185
+ <h1 className="text-foreground mb-1 text-2xl font-medium">Settings</h1>
186
+ <p className="text-muted-foreground text-sm">Manage your CMS configuration</p>
183
187
  </div>
184
188
 
185
189
  <Tabs.Root value={activeTab} onValueChange={changeTab}>
186
- <Tabs.List className="mb-4 flex gap-1 overflow-x-auto border-b border-gray-200">
190
+ <Tabs.List className="border-border mb-4 flex gap-1 overflow-x-auto border-b">
187
191
  <Tabs.Trigger value="general" className={tabTriggerClass}>
188
192
  General
189
193
  </Tabs.Trigger>
@@ -240,12 +244,10 @@ export function Settings({
240
244
 
241
245
  {hasLayoutRegions && (
242
246
  <Tabs.Content value="layout" className="space-y-4">
243
- <div className="rounded-lg border border-gray-200 bg-white p-4">
244
- <h3 className="mb-1 text-sm font-semibold text-gray-900">Default Layout Variants</h3>
245
- <p className="mb-4 text-xs text-gray-500">
246
- Select the default header, footer, and other layout variants used site-wide. Pages
247
- can override these individually or inherit from parent pages.
248
- </p>
247
+ <SettingsCard
248
+ title="Default Layout Variants"
249
+ description="Select the default header, footer, and other layout variants used site-wide. Pages can override these individually or inherit from parent pages."
250
+ >
249
251
  <div className="space-y-4">
250
252
  {layoutRegions.map((region) => (
251
253
  <RelationshipField
@@ -268,14 +270,11 @@ export function Settings({
268
270
  />
269
271
  ))}
270
272
  </div>
271
- </div>
272
- <div className="rounded-lg border border-gray-200 bg-gray-50 p-4">
273
- <h3 className="mb-2 text-sm font-semibold text-gray-700">
274
- How Layout Inheritance Works
275
- </h3>
276
- <ul className="space-y-1.5 text-xs text-gray-600">
273
+ </SettingsCard>
274
+ <SettingsCard title="How Layout Inheritance Works">
275
+ <ul className="text-muted-foreground space-y-1.5 text-sm">
277
276
  <li className="flex items-start gap-2">
278
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
277
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
279
278
  1
280
279
  </span>
281
280
  <span>
@@ -284,20 +283,20 @@ export function Settings({
284
283
  </span>
285
284
  </li>
286
285
  <li className="flex items-start gap-2">
287
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
286
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
288
287
  2
289
288
  </span>
290
289
  <span>
291
290
  Child pages automatically inherit their parent&apos;s layout. For example,{' '}
292
- <code className="rounded bg-gray-200 px-1 font-mono">
291
+ <code className="bg-muted rounded px-1 font-mono">
293
292
  /hampton-roads/thank-you
294
293
  </code>{' '}
295
294
  inherits from{' '}
296
- <code className="rounded bg-gray-200 px-1 font-mono">/hampton-roads</code>.
295
+ <code className="bg-muted rounded px-1 font-mono">/hampton-roads</code>.
297
296
  </span>
298
297
  </li>
299
298
  <li className="flex items-start gap-2">
300
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
299
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
301
300
  3
302
301
  </span>
303
302
  <span>
@@ -306,7 +305,7 @@ export function Settings({
306
305
  </span>
307
306
  </li>
308
307
  </ul>
309
- </div>
308
+ </SettingsCard>
310
309
  </Tabs.Content>
311
310
  )}
312
311
 
@@ -458,52 +457,59 @@ function UpdatesPanel() {
458
457
  checkForUpdates()
459
458
  }, [])
460
459
 
461
- const severityColors: Record<
462
- string,
463
- { bg: string; text: string; border: string; label: string }
464
- > = {
465
- patch: { bg: 'bg-blue-50', text: 'text-blue-700', border: 'border-blue-200', label: 'Patch' },
466
- minor: {
467
- bg: 'bg-yellow-50',
468
- text: 'text-yellow-700',
469
- border: 'border-yellow-200',
470
- label: 'Minor',
460
+ // Semantic severity tokens so update banners match the rest of the admin in
461
+ // both light and dark mode (no raw palette colors). Concrete keys (not a
462
+ // string-index Record) keep the lookup non-nullable.
463
+ const severityColors = {
464
+ patch: { banner: 'border-brand/30 bg-brand/10', text: 'text-brand', label: 'Patch' },
465
+ minor: { banner: 'border-warning/30 bg-warning/10', text: 'text-warning', label: 'Minor' },
466
+ major: {
467
+ banner: 'border-destructive/30 bg-destructive/10',
468
+ text: 'text-destructive',
469
+ label: 'Major',
471
470
  },
472
- major: { bg: 'bg-red-50', text: 'text-red-700', border: 'border-red-200', label: 'Major' },
473
471
  }
472
+ const sev = severityColors[updateInfo?.severity ?? 'patch']
473
+ const ghInputClass =
474
+ 'w-full rounded-md border border-border bg-input-background px-3 py-2 text-base text-foreground focus-visible:ring-2 focus-visible:ring-ring focus-visible:outline-none'
474
475
 
475
476
  return (
476
477
  <>
477
478
  {/* Current Version Card */}
478
- <div className="rounded-lg border border-gray-200 bg-white p-4">
479
- <div className="flex items-center justify-between">
480
- <div>
481
- <h3 className="text-sm font-semibold text-gray-900">Actuate CMS</h3>
482
- <p className="mt-1 text-sm text-gray-600">
483
- Current version:{' '}
484
- <span className="font-mono font-medium">{updateInfo?.current ?? '...'}</span>
485
- </p>
486
- </div>
479
+ <SettingsCard
480
+ title="Actuate CMS"
481
+ description="Keep your CMS up to date with the latest features and security patches."
482
+ actions={
487
483
  <button
488
484
  type="button"
489
485
  onClick={checkForUpdates}
490
486
  disabled={checking}
491
- className="flex items-center gap-2 rounded-lg border border-gray-300 px-4 py-2 text-sm font-medium text-gray-700 transition-colors hover:bg-gray-50 disabled:opacity-50"
487
+ className="border-border text-foreground hover:bg-accent inline-flex items-center gap-2 rounded-md border px-4 py-2 text-sm font-medium transition-colors disabled:opacity-50"
492
488
  >
493
- <RefreshCw className={`h-4 w-4 ${checking ? 'animate-spin' : ''}`} />
494
- {checking ? 'Checking...' : 'Check for Updates'}
489
+ <RefreshCw size={16} className={checking ? 'animate-spin' : ''} aria-hidden="true" />
490
+ {checking ? 'Checking' : 'Check for Updates'}
495
491
  </button>
496
- </div>
497
- </div>
492
+ }
493
+ >
494
+ <p className="text-muted-foreground text-sm">
495
+ Current version:{' '}
496
+ <span className="text-foreground font-mono font-medium">
497
+ {updateInfo?.current ?? '…'}
498
+ </span>
499
+ </p>
500
+ </SettingsCard>
498
501
 
499
502
  {/* Error State */}
500
503
  {checkError && (
501
- <div className="flex items-center gap-3 rounded-lg border border-red-200 bg-red-50 p-3">
502
- <AlertTriangle className="h-5 w-5 shrink-0 text-red-600" />
503
- <span className="flex-1 text-sm text-red-800">{checkError}</span>
504
+ <div
505
+ role="alert"
506
+ className="border-destructive/30 bg-destructive/10 text-destructive flex items-center gap-3 rounded-lg border p-3 text-sm"
507
+ >
508
+ <AlertTriangle size={20} className="shrink-0" aria-hidden="true" />
509
+ <span className="flex-1">{checkError}</span>
504
510
  <button
505
511
  onClick={checkForUpdates}
506
- className="rounded-lg border border-red-300 px-3 py-1 text-sm text-red-700 transition-colors hover:bg-red-100"
512
+ className="border-destructive/40 rounded-md border px-3 py-1 transition-colors hover:opacity-80"
507
513
  >
508
514
  Retry
509
515
  </button>
@@ -512,11 +518,11 @@ function UpdatesPanel() {
512
518
 
513
519
  {/* Up to Date */}
514
520
  {hasChecked && updateInfo && !updateInfo.updateAvailable && !checkError && (
515
- <div className="flex items-center gap-3 rounded-lg border border-green-200 bg-green-50 p-4">
516
- <CheckCircle2 className="h-6 w-6 shrink-0 text-green-600" />
521
+ <div className="border-success/30 bg-success/10 text-success flex items-center gap-3 rounded-lg border p-4">
522
+ <CheckCircle2 size={24} className="shrink-0" aria-hidden="true" />
517
523
  <div>
518
- <h3 className="text-sm font-semibold text-green-900">You&apos;re up to date!</h3>
519
- <p className="mt-0.5 text-sm text-green-700">
524
+ <h3 className="text-foreground font-medium">You&apos;re up to date!</h3>
525
+ <p className="text-muted-foreground mt-0.5 text-sm">
520
526
  Actuate CMS <span className="font-mono">{updateInfo.current}</span> is the latest
521
527
  version.
522
528
  </p>
@@ -527,29 +533,25 @@ function UpdatesPanel() {
527
533
  {/* Update Available */}
528
534
  {updateInfo?.updateAvailable && (
529
535
  <>
530
- <div
531
- className={`rounded-lg border p-4 ${severityColors[updateInfo.severity ?? 'patch']?.border ?? 'border-blue-200'} ${severityColors[updateInfo.severity ?? 'patch']?.bg ?? 'bg-blue-50'}`}
532
- >
536
+ <div className={`rounded-lg border p-4 ${sev.banner}`}>
533
537
  <div className="flex items-start gap-3">
534
538
  <ArrowUpCircle
535
- className={`mt-0.5 h-6 w-6 shrink-0 ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'}`}
539
+ size={24}
540
+ className={`mt-0.5 shrink-0 ${sev.text}`}
541
+ aria-hidden="true"
536
542
  />
537
543
  <div className="flex-1">
538
544
  <div className="flex items-center gap-2">
539
- <h3
540
- className={`text-sm font-semibold ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'}`}
541
- >
542
- Update Available
543
- </h3>
545
+ <h3 className={`font-medium ${sev.text}`}>Update Available</h3>
544
546
  <span
545
- className={`inline-flex items-center rounded px-2 py-0.5 text-xs font-medium ${severityColors[updateInfo.severity ?? 'patch']?.bg ?? 'bg-blue-50'} ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'} border ${severityColors[updateInfo.severity ?? 'patch']?.border ?? 'border-blue-200'}`}
547
+ className={`inline-flex items-center rounded border px-2 py-0.5 text-xs font-medium ${sev.banner} ${sev.text}`}
546
548
  >
547
- {severityColors[updateInfo.severity ?? 'patch']?.label ?? 'Update'}
549
+ {sev.label}
548
550
  </span>
549
551
  </div>
550
- <p className="mt-1 text-sm" style={{ color: 'inherit' }}>
552
+ <p className="text-muted-foreground mt-1 text-sm">
551
553
  <span className="font-mono">{updateInfo.current}</span> &rarr;{' '}
552
- <span className="font-mono font-semibold">{updateInfo.latest}</span>
554
+ <span className="text-foreground font-mono font-medium">{updateInfo.latest}</span>
553
555
  {updateInfo.releaseDate && (
554
556
  <span className="ml-2 text-xs opacity-70">
555
557
  Released {updateInfo.releaseDate}
@@ -564,27 +566,29 @@ function UpdatesPanel() {
564
566
  type="button"
565
567
  onClick={applyUpdate}
566
568
  disabled={applying}
567
- className="flex items-center gap-2 rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:opacity-50"
569
+ className="bg-brand text-brand-foreground inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-opacity hover:opacity-90 disabled:opacity-50"
568
570
  >
569
571
  {applying ? (
570
572
  <>
571
- <Loader2 className="h-4 w-4 animate-spin" />
572
- Creating PR...
573
+ <Loader2 size={16} className="animate-spin" aria-hidden="true" />
574
+ Creating PR
573
575
  </>
574
576
  ) : (
575
577
  <>
576
- <GitPullRequest className="h-4 w-4" />
578
+ <GitPullRequest size={16} aria-hidden="true" />
577
579
  Create Update PR
578
580
  </>
579
581
  )}
580
582
  </button>
581
- <span className="text-xs text-gray-500">Opens a pull request on your repository</span>
583
+ <span className="text-muted-foreground text-xs">
584
+ Opens a pull request on your repository
585
+ </span>
582
586
  </div>
583
587
 
584
588
  {updateInfo.updateCommand && (
585
- <div className="mt-3 rounded border border-gray-200 bg-white p-3">
586
- <p className="mb-1 text-xs text-gray-500">Or update manually:</p>
587
- <code className="block rounded bg-gray-50 px-2 py-1.5 font-mono text-xs text-gray-800 select-all">
589
+ <div className="border-border bg-card mt-3 rounded-md border p-3">
590
+ <p className="text-muted-foreground mb-1 text-xs">Or update manually:</p>
591
+ <code className="bg-muted text-foreground block rounded px-2 py-1.5 font-mono text-xs select-all">
588
592
  {updateInfo.updateCommand}
589
593
  </code>
590
594
  </div>
@@ -593,35 +597,36 @@ function UpdatesPanel() {
593
597
 
594
598
  {/* Changelog */}
595
599
  {updateInfo.changelog && updateInfo.changelog.length > 0 && (
596
- <div className="rounded-lg border border-gray-200 bg-white p-4">
597
- <h3 className="mb-3 text-sm font-semibold text-gray-900">Changelog</h3>
600
+ <SettingsCard title="Changelog">
598
601
  <div className="max-h-64 space-y-2 overflow-y-auto">
599
602
  {updateInfo.changelog.map((entry) => (
600
603
  <div key={entry.version} className="flex items-baseline gap-3 text-sm">
601
- <span className="w-14 shrink-0 font-mono text-xs text-gray-500">
604
+ <span className="text-muted-foreground w-14 shrink-0 font-mono text-xs">
602
605
  {entry.version}
603
606
  </span>
604
- <span className="w-20 shrink-0 text-xs text-gray-400">{entry.date}</span>
605
- <span className="text-gray-700">{entry.summary}</span>
607
+ <span className="text-muted-foreground w-20 shrink-0 text-xs">
608
+ {entry.date}
609
+ </span>
610
+ <span className="text-foreground">{entry.summary}</span>
606
611
  </div>
607
612
  ))}
608
613
  </div>
609
- </div>
614
+ </SettingsCard>
610
615
  )}
611
616
  </>
612
617
  )}
613
618
 
614
619
  {/* PR Created */}
615
620
  {prResult && (
616
- <div className="rounded-lg border border-green-200 bg-green-50 p-4">
621
+ <div className="border-success/30 bg-success/10 rounded-lg border p-4">
617
622
  <div className="flex items-start gap-3">
618
- <GitPullRequest className="mt-0.5 h-5 w-5 shrink-0 text-green-600" />
623
+ <GitPullRequest size={20} className="text-success mt-0.5 shrink-0" aria-hidden="true" />
619
624
  <div>
620
- <h3 className="text-sm font-semibold text-green-900">Pull Request Created</h3>
621
- <p className="mt-1 text-sm text-green-700">
625
+ <h3 className="text-foreground font-medium">Pull Request Created</h3>
626
+ <p className="text-muted-foreground mt-1 text-sm">
622
627
  PR #{prResult.prNumber} has been created on your repository. Review and merge it to
623
628
  apply the update, then run{' '}
624
- <code className="rounded bg-green-100 px-1 font-mono text-xs">
629
+ <code className="bg-muted rounded px-1 font-mono text-xs">
625
630
  npx prisma migrate deploy
626
631
  </code>
627
632
  .
@@ -630,9 +635,9 @@ function UpdatesPanel() {
630
635
  href={prResult.prUrl}
631
636
  target="_blank"
632
637
  rel="noopener noreferrer"
633
- className="mt-2 inline-flex items-center gap-1.5 text-sm font-medium text-green-700 hover:text-green-800"
638
+ className="text-brand mt-2 inline-flex items-center gap-1.5 text-sm font-medium hover:underline"
634
639
  >
635
- <ExternalLink className="h-4 w-4" />
640
+ <ExternalLink size={16} aria-hidden="true" />
636
641
  View Pull Request
637
642
  </a>
638
643
  </div>
@@ -641,65 +646,72 @@ function UpdatesPanel() {
641
646
  )}
642
647
 
643
648
  {/* GitHub Configuration */}
644
- <div className="rounded-lg border border-gray-200 bg-white p-4">
645
- <h3 className="mb-1 text-sm font-semibold text-gray-900">GitHub Integration</h3>
646
- <p className="mb-4 text-xs text-gray-500">
647
- Connect your repository to enable one-click update PRs. Credentials are encrypted at rest
648
- (AES-256-GCM).
649
- </p>
650
-
649
+ <SettingsCard
650
+ title="GitHub Integration"
651
+ description="Connect your repository to enable one-click update PRs. Credentials are encrypted at rest (AES-256-GCM)."
652
+ >
651
653
  <div className="space-y-4">
652
654
  <div>
653
- <label className="mb-1 block text-sm font-medium text-gray-700">Repository</label>
655
+ <label
656
+ htmlFor="updates-gh-repo"
657
+ className="text-foreground mb-1 block text-sm font-medium"
658
+ >
659
+ Repository
660
+ </label>
654
661
  <input
662
+ id="updates-gh-repo"
655
663
  type="text"
656
664
  value={ghRepo}
657
665
  onChange={(e) => setGhRepo(e.target.value)}
658
666
  placeholder="owner/repo"
659
- className="w-full rounded-lg border border-gray-300 px-3 py-2 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
667
+ className={ghInputClass}
660
668
  />
661
- <p className="mt-1 text-xs text-gray-500">
669
+ <p className="text-muted-foreground mt-1 text-sm">
662
670
  e.g.{' '}
663
- <code className="rounded bg-gray-100 px-1 font-mono">
664
- actuate-media/my-client-site
665
- </code>
671
+ <code className="bg-muted rounded px-1 font-mono">actuate-media/my-client-site</code>
666
672
  </p>
667
673
  </div>
668
674
  <div>
669
- <label className="mb-1 block text-sm font-medium text-gray-700">
675
+ <label
676
+ htmlFor="updates-gh-token"
677
+ className="text-foreground mb-1 block text-sm font-medium"
678
+ >
670
679
  GitHub Token
671
680
  {updateInfo?.hasGithubToken && !ghToken && (
672
- <span className="ml-2 text-xs font-normal text-green-600">Saved</span>
681
+ <span className="text-success ml-2 text-xs font-normal">Saved</span>
673
682
  )}
674
683
  </label>
675
- <div className="flex gap-2">
676
- <div className="relative flex-1">
677
- <input
678
- type={showGhToken ? 'text' : 'password'}
679
- value={ghToken}
680
- onChange={(e) => setGhToken(e.target.value)}
681
- placeholder={
682
- updateInfo?.hasGithubToken ? '••••••••••••••••' : 'ghp_... or github_pat_...'
683
- }
684
- className="w-full rounded-lg border border-gray-300 px-3 py-2 pr-10 text-sm focus:ring-2 focus:ring-blue-500 focus:outline-none"
685
- />
686
- <button
687
- type="button"
688
- onClick={() => setShowGhToken(!showGhToken)}
689
- className="absolute top-1/2 right-2 -translate-y-1/2 p-1 text-gray-400 hover:text-gray-600"
690
- >
691
- {showGhToken ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
692
- </button>
693
- </div>
684
+ <div className="relative">
685
+ <input
686
+ id="updates-gh-token"
687
+ type={showGhToken ? 'text' : 'password'}
688
+ value={ghToken}
689
+ onChange={(e) => setGhToken(e.target.value)}
690
+ placeholder={
691
+ updateInfo?.hasGithubToken ? '••••••••••••••••' : 'ghp_... or github_pat_...'
692
+ }
693
+ className={`${ghInputClass} pr-10`}
694
+ />
695
+ <button
696
+ type="button"
697
+ onClick={() => setShowGhToken(!showGhToken)}
698
+ aria-label={showGhToken ? 'Hide token' : 'Show token'}
699
+ className="text-muted-foreground hover:text-foreground absolute top-1/2 right-2 -translate-y-1/2 p-1"
700
+ >
701
+ {showGhToken ? (
702
+ <EyeOff size={16} aria-hidden="true" />
703
+ ) : (
704
+ <Eye size={16} aria-hidden="true" />
705
+ )}
706
+ </button>
694
707
  </div>
695
- <p className="mt-1 text-xs text-gray-500">
696
- Needs <code className="rounded bg-gray-100 px-1 font-mono">repo</code> scope. Create
697
- at{' '}
708
+ <p className="text-muted-foreground mt-1 text-sm">
709
+ Needs <code className="bg-muted rounded px-1 font-mono">repo</code> scope. Create at{' '}
698
710
  <a
699
711
  href="https://github.com/settings/tokens"
700
712
  target="_blank"
701
713
  rel="noopener noreferrer"
702
- className="text-blue-600 hover:underline"
714
+ className="text-brand hover:underline"
703
715
  >
704
716
  github.com/settings/tokens
705
717
  </a>
@@ -710,26 +722,25 @@ function UpdatesPanel() {
710
722
  type="button"
711
723
  onClick={saveGitHubConfig}
712
724
  disabled={savingConfig || (!ghToken && !ghRepo)}
713
- className="flex items-center gap-2 rounded-lg bg-gray-900 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-gray-800 disabled:opacity-50"
725
+ className="bg-brand text-brand-foreground inline-flex items-center gap-2 rounded-md px-4 py-2 text-sm font-medium transition-opacity hover:opacity-90 disabled:opacity-50"
714
726
  >
715
727
  {savingConfig ? (
716
728
  <>
717
- <Loader2 className="h-4 w-4 animate-spin" />
718
- Encrypting &amp; Saving...
729
+ <Loader2 size={16} className="animate-spin" aria-hidden="true" />
730
+ Encrypting &amp; Saving
719
731
  </>
720
732
  ) : (
721
733
  'Save Configuration'
722
734
  )}
723
735
  </button>
724
736
  </div>
725
- </div>
737
+ </SettingsCard>
726
738
 
727
739
  {/* Help Text */}
728
- <div className="rounded-lg border border-gray-200 bg-gray-50 p-4">
729
- <h3 className="mb-2 text-sm font-semibold text-gray-700">How Updates Work</h3>
730
- <ul className="space-y-1.5 text-xs text-gray-600">
740
+ <SettingsCard title="How Updates Work">
741
+ <ul className="text-muted-foreground space-y-1.5 text-sm">
731
742
  <li className="flex items-start gap-2">
732
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
743
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
733
744
  1
734
745
  </span>
735
746
  <span>
@@ -737,7 +748,7 @@ function UpdatesPanel() {
737
748
  </span>
738
749
  </li>
739
750
  <li className="flex items-start gap-2">
740
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
751
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
741
752
  2
742
753
  </span>
743
754
  <span>
@@ -746,7 +757,7 @@ function UpdatesPanel() {
746
757
  </span>
747
758
  </li>
748
759
  <li className="flex items-start gap-2">
749
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
760
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
750
761
  3
751
762
  </span>
752
763
  <span>
@@ -755,16 +766,16 @@ function UpdatesPanel() {
755
766
  </span>
756
767
  </li>
757
768
  <li className="flex items-start gap-2">
758
- <span className="mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full bg-blue-100 text-[10px] font-bold text-blue-600">
769
+ <span className="bg-brand/10 text-brand mt-0.5 flex h-4 w-4 shrink-0 items-center justify-center rounded-full text-[10px] font-medium">
759
770
  4
760
771
  </span>
761
772
  <span>
762
773
  Review and merge the PR, then deploy. Database migrations run automatically via{' '}
763
- <code className="rounded bg-gray-200 px-1 font-mono">prisma migrate deploy</code>.
774
+ <code className="bg-muted rounded px-1 font-mono">prisma migrate deploy</code>.
764
775
  </span>
765
776
  </li>
766
777
  </ul>
767
- </div>
778
+ </SettingsCard>
768
779
  </>
769
780
  )
770
781
  }
@@ -923,7 +923,7 @@ export function Users({ embedded = false }: UsersProps = {}) {
923
923
  {/* Header */}
924
924
  <div className="border-border flex flex-col gap-3 border-b p-4 lg:flex-row lg:items-center lg:justify-between">
925
925
  <div>
926
- <h2 className="text-card-foreground text-base font-medium">Team members</h2>
926
+ <h2 className="text-card-foreground text-lg font-medium">Team members</h2>
927
927
  <p className="text-muted-foreground text-sm">
928
928
  {peopleCount} {peopleCount === 1 ? 'person has' : 'people have'} access to this
929
929
  workspace