@actuate-media/cms-admin 0.36.0 → 0.37.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 (100) hide show
  1. package/dist/__tests__/views/appearance-settings.render.test.js +4 -4
  2. package/dist/__tests__/views/updates-tab.render.test.d.ts +2 -0
  3. package/dist/__tests__/views/updates-tab.render.test.d.ts.map +1 -0
  4. package/dist/__tests__/views/updates-tab.render.test.js +132 -0
  5. package/dist/__tests__/views/updates-tab.render.test.js.map +1 -0
  6. package/dist/actuate-admin.css +1 -1
  7. package/dist/views/ApiKeys.d.ts.map +1 -1
  8. package/dist/views/ApiKeys.js +14 -13
  9. package/dist/views/ApiKeys.js.map +1 -1
  10. package/dist/views/Profile.d.ts.map +1 -1
  11. package/dist/views/Profile.js +1 -1
  12. package/dist/views/Profile.js.map +1 -1
  13. package/dist/views/Settings.d.ts.map +1 -1
  14. package/dist/views/Settings.js +16 -119
  15. package/dist/views/Settings.js.map +1 -1
  16. package/dist/views/Users.js +1 -1
  17. package/dist/views/Users.js.map +1 -1
  18. package/dist/views/settings/AISettingsTab.js +40 -42
  19. package/dist/views/settings/AISettingsTab.js.map +1 -1
  20. package/dist/views/settings/AdminThemeCard.js +1 -1
  21. package/dist/views/settings/AiFeaturesCard.d.ts.map +1 -1
  22. package/dist/views/settings/AiFeaturesCard.js +2 -3
  23. package/dist/views/settings/AiFeaturesCard.js.map +1 -1
  24. package/dist/views/settings/BrandPreviewCard.js +1 -1
  25. package/dist/views/settings/BrandVoiceCard.d.ts.map +1 -1
  26. package/dist/views/settings/BrandVoiceCard.js +2 -4
  27. package/dist/views/settings/BrandVoiceCard.js.map +1 -1
  28. package/dist/views/settings/BrandingCard.d.ts.map +1 -1
  29. package/dist/views/settings/BrandingCard.js +34 -19
  30. package/dist/views/settings/BrandingCard.js.map +1 -1
  31. package/dist/views/settings/GeneralSettingsTab.js +1 -1
  32. package/dist/views/settings/LanguageRegionCard.d.ts.map +1 -1
  33. package/dist/views/settings/LanguageRegionCard.js +2 -1
  34. package/dist/views/settings/LanguageRegionCard.js.map +1 -1
  35. package/dist/views/settings/MonthlyUsageCard.d.ts.map +1 -1
  36. package/dist/views/settings/MonthlyUsageCard.js +3 -3
  37. package/dist/views/settings/MonthlyUsageCard.js.map +1 -1
  38. package/dist/views/settings/ProviderConnectionCard.js +1 -1
  39. package/dist/views/settings/ProviderConnectionCard.js.map +1 -1
  40. package/dist/views/settings/SecurityStatusCard.d.ts.map +1 -1
  41. package/dist/views/settings/SecurityStatusCard.js +2 -3
  42. package/dist/views/settings/SecurityStatusCard.js.map +1 -1
  43. package/dist/views/settings/SecurityTab.js +1 -1
  44. package/dist/views/settings/SeoRobotsDefaultsCard.d.ts.map +1 -1
  45. package/dist/views/settings/SeoRobotsDefaultsCard.js +3 -4
  46. package/dist/views/settings/SeoRobotsDefaultsCard.js.map +1 -1
  47. package/dist/views/settings/SeoSettingsTab.d.ts.map +1 -1
  48. package/dist/views/settings/SeoSettingsTab.js +5 -8
  49. package/dist/views/settings/SeoSettingsTab.js.map +1 -1
  50. package/dist/views/settings/SiteIndexStatusCard.d.ts.map +1 -1
  51. package/dist/views/settings/SiteIndexStatusCard.js +3 -2
  52. package/dist/views/settings/SiteIndexStatusCard.js.map +1 -1
  53. package/dist/views/settings/SiteInformationCard.d.ts.map +1 -1
  54. package/dist/views/settings/SiteInformationCard.js +4 -4
  55. package/dist/views/settings/SiteInformationCard.js.map +1 -1
  56. package/dist/views/settings/TypographyMotionCard.js +1 -1
  57. package/dist/views/settings/UpdatesTab.d.ts +6 -0
  58. package/dist/views/settings/UpdatesTab.d.ts.map +1 -0
  59. package/dist/views/settings/UpdatesTab.js +80 -0
  60. package/dist/views/settings/UpdatesTab.js.map +1 -0
  61. package/dist/views/settings/components.d.ts +51 -1
  62. package/dist/views/settings/components.d.ts.map +1 -1
  63. package/dist/views/settings/components.js +33 -0
  64. package/dist/views/settings/components.js.map +1 -1
  65. package/dist/views/settings/securityPrimitives.d.ts +11 -3
  66. package/dist/views/settings/securityPrimitives.d.ts.map +1 -1
  67. package/dist/views/settings/securityPrimitives.js +10 -3
  68. package/dist/views/settings/securityPrimitives.js.map +1 -1
  69. package/dist/views/settings/useUpdates.d.ts +67 -0
  70. package/dist/views/settings/useUpdates.d.ts.map +1 -0
  71. package/dist/views/settings/useUpdates.js +127 -0
  72. package/dist/views/settings/useUpdates.js.map +1 -0
  73. package/package.json +2 -2
  74. package/src/__tests__/views/appearance-settings.render.test.tsx +4 -4
  75. package/src/__tests__/views/updates-tab.render.test.tsx +161 -0
  76. package/src/views/ApiKeys.tsx +190 -154
  77. package/src/views/Profile.tsx +4 -2
  78. package/src/views/Settings.tsx +26 -451
  79. package/src/views/Users.tsx +1 -1
  80. package/src/views/settings/AISettingsTab.tsx +64 -76
  81. package/src/views/settings/AdminThemeCard.tsx +1 -1
  82. package/src/views/settings/AiFeaturesCard.tsx +6 -12
  83. package/src/views/settings/BrandPreviewCard.tsx +1 -1
  84. package/src/views/settings/BrandVoiceCard.tsx +6 -19
  85. package/src/views/settings/BrandingCard.tsx +179 -180
  86. package/src/views/settings/GeneralSettingsTab.tsx +1 -1
  87. package/src/views/settings/LanguageRegionCard.tsx +5 -7
  88. package/src/views/settings/MonthlyUsageCard.tsx +6 -10
  89. package/src/views/settings/ProviderConnectionCard.tsx +1 -1
  90. package/src/views/settings/SecurityStatusCard.tsx +8 -11
  91. package/src/views/settings/SecurityTab.tsx +1 -1
  92. package/src/views/settings/SeoRobotsDefaultsCard.tsx +7 -14
  93. package/src/views/settings/SeoSettingsTab.tsx +17 -26
  94. package/src/views/settings/SiteIndexStatusCard.tsx +10 -22
  95. package/src/views/settings/SiteInformationCard.tsx +3 -9
  96. package/src/views/settings/TypographyMotionCard.tsx +1 -1
  97. package/src/views/settings/UpdatesTab.tsx +545 -0
  98. package/src/views/settings/components.tsx +123 -1
  99. package/src/views/settings/securityPrimitives.tsx +14 -9
  100. package/src/views/settings/useUpdates.ts +183 -0
@@ -3,16 +3,9 @@
3
3
  import * as Tabs from '@radix-ui/react-tabs'
4
4
  import {
5
5
  Bot,
6
- Eye,
7
- EyeOff,
8
6
  Loader2,
9
7
  AlertTriangle,
10
8
  Download,
11
- CheckCircle2,
12
- ArrowUpCircle,
13
- ExternalLink,
14
- RefreshCw,
15
- GitPullRequest,
16
9
  Layers,
17
10
  Users as UsersIcon,
18
11
  KeyRound,
@@ -27,6 +20,8 @@ import { SeoSettingsTab } from './settings/SeoSettingsTab.js'
27
20
  import { AISettingsTab } from './settings/AISettingsTab.js'
28
21
  import { AppearanceTab } from './settings/AppearanceTab.js'
29
22
  import { SecurityTab } from './settings/SecurityTab.js'
23
+ import { SettingsCard } from './settings/components.js'
24
+ import { UpdatesTab } from './settings/UpdatesTab.js'
30
25
  import { Users as UsersView } from './Users.js'
31
26
  import { ApiKeys as ApiKeysView } from './ApiKeys.js'
32
27
 
@@ -165,12 +160,15 @@ export function Settings({
165
160
  return (
166
161
  <div className="p-3 pr-6 sm:p-4 sm:pr-8">
167
162
  {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>
163
+ <div
164
+ role="alert"
165
+ className="border-destructive/30 bg-destructive/10 text-destructive mb-4 flex items-center gap-3 rounded-lg border p-3 text-sm"
166
+ >
167
+ <AlertTriangle size={20} className="shrink-0" aria-hidden="true" />
168
+ <span className="flex-1">{error}</span>
171
169
  <button
172
170
  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"
171
+ className="border-destructive/40 rounded-md border px-3 py-1 transition-colors hover:opacity-80"
174
172
  >
175
173
  Retry
176
174
  </button>
@@ -178,12 +176,12 @@ export function Settings({
178
176
  )}
179
177
 
180
178
  <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>
179
+ <h1 className="text-foreground mb-1 text-2xl font-medium">Settings</h1>
180
+ <p className="text-muted-foreground text-sm">Manage your CMS configuration</p>
183
181
  </div>
184
182
 
185
183
  <Tabs.Root value={activeTab} onValueChange={changeTab}>
186
- <Tabs.List className="mb-4 flex gap-1 overflow-x-auto border-b border-gray-200">
184
+ <Tabs.List className="border-border mb-4 flex gap-1 overflow-x-auto border-b">
187
185
  <Tabs.Trigger value="general" className={tabTriggerClass}>
188
186
  General
189
187
  </Tabs.Trigger>
@@ -240,12 +238,10 @@ export function Settings({
240
238
 
241
239
  {hasLayoutRegions && (
242
240
  <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>
241
+ <SettingsCard
242
+ title="Default Layout Variants"
243
+ description="Select the default header, footer, and other layout variants used site-wide. Pages can override these individually or inherit from parent pages."
244
+ >
249
245
  <div className="space-y-4">
250
246
  {layoutRegions.map((region) => (
251
247
  <RelationshipField
@@ -268,14 +264,11 @@ export function Settings({
268
264
  />
269
265
  ))}
270
266
  </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">
267
+ </SettingsCard>
268
+ <SettingsCard title="How Layout Inheritance Works">
269
+ <ul className="text-muted-foreground space-y-1.5 text-sm">
277
270
  <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">
271
+ <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
272
  1
280
273
  </span>
281
274
  <span>
@@ -284,20 +277,20 @@ export function Settings({
284
277
  </span>
285
278
  </li>
286
279
  <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">
280
+ <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
281
  2
289
282
  </span>
290
283
  <span>
291
284
  Child pages automatically inherit their parent&apos;s layout. For example,{' '}
292
- <code className="rounded bg-gray-200 px-1 font-mono">
285
+ <code className="bg-muted rounded px-1 font-mono">
293
286
  /hampton-roads/thank-you
294
287
  </code>{' '}
295
288
  inherits from{' '}
296
- <code className="rounded bg-gray-200 px-1 font-mono">/hampton-roads</code>.
289
+ <code className="bg-muted rounded px-1 font-mono">/hampton-roads</code>.
297
290
  </span>
298
291
  </li>
299
292
  <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">
293
+ <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
294
  3
302
295
  </span>
303
296
  <span>
@@ -306,7 +299,7 @@ export function Settings({
306
299
  </span>
307
300
  </li>
308
301
  </ul>
309
- </div>
302
+ </SettingsCard>
310
303
  </Tabs.Content>
311
304
  )}
312
305
 
@@ -331,7 +324,7 @@ export function Settings({
331
324
  </Tabs.Content>
332
325
 
333
326
  <Tabs.Content value="updates" className="space-y-4">
334
- <UpdatesPanel />
327
+ <UpdatesTab role={session?.user?.role} />
335
328
  </Tabs.Content>
336
329
  </Tabs.Root>
337
330
 
@@ -350,421 +343,3 @@ export function Settings({
350
343
  </div>
351
344
  )
352
345
  }
353
-
354
- // ---------------------------------------------------------------------------
355
- // Updates Panel
356
- // ---------------------------------------------------------------------------
357
-
358
- interface UpdateInfo {
359
- current: string
360
- latest: string
361
- updateAvailable: boolean
362
- severity?: 'patch' | 'minor' | 'major'
363
- releaseDate?: string
364
- changelog?: Array<{ version: string; date: string; summary: string }>
365
- updateCommand?: string
366
- hasGithubToken?: boolean
367
- githubRepo?: string
368
- }
369
-
370
- function UpdatesPanel() {
371
- const [updateInfo, setUpdateInfo] = useState<UpdateInfo | null>(null)
372
- const [checking, setChecking] = useState(false)
373
- const [checkError, setCheckError] = useState('')
374
- const [applying, setApplying] = useState(false)
375
- const [prResult, setPrResult] = useState<{ prUrl: string; prNumber: number } | null>(null)
376
- const [hasChecked, setHasChecked] = useState(false)
377
-
378
- // GitHub config
379
- const [ghToken, setGhToken] = useState('')
380
- const [ghRepo, setGhRepo] = useState('')
381
- const [showGhToken, setShowGhToken] = useState(false)
382
- const [savingConfig, setSavingConfig] = useState(false)
383
- const [configSaved, setConfigSaved] = useState(false)
384
-
385
- const checkForUpdates = async () => {
386
- setChecking(true)
387
- setCheckError('')
388
- setPrResult(null)
389
-
390
- try {
391
- const res = await cmsApi<UpdateInfo>('/updates/check')
392
- if (res.error) {
393
- setCheckError(res.error)
394
- } else if (res.data) {
395
- setUpdateInfo(res.data)
396
- if (res.data.githubRepo) setGhRepo(res.data.githubRepo)
397
- }
398
- } catch {
399
- setCheckError('Unable to check for updates. Please try again.')
400
- } finally {
401
- setChecking(false)
402
- setHasChecked(true)
403
- }
404
- }
405
-
406
- const saveGitHubConfig = async () => {
407
- setSavingConfig(true)
408
- setConfigSaved(false)
409
- try {
410
- const res = await cmsApi('/updates/config', {
411
- method: 'PUT',
412
- body: JSON.stringify({
413
- ...(ghToken ? { githubToken: ghToken } : {}),
414
- ...(ghRepo ? { githubRepo: ghRepo } : {}),
415
- }),
416
- })
417
- if (res.error) {
418
- toast.error(res.error)
419
- } else {
420
- toast.success('GitHub configuration saved and encrypted.')
421
- setConfigSaved(true)
422
- setGhToken('')
423
- if (updateInfo) {
424
- setUpdateInfo({ ...updateInfo, hasGithubToken: true, githubRepo: ghRepo })
425
- }
426
- }
427
- } catch {
428
- toast.error('Failed to save configuration.')
429
- } finally {
430
- setSavingConfig(false)
431
- }
432
- }
433
-
434
- const applyUpdate = async () => {
435
- if (!updateInfo?.latest) return
436
- setApplying(true)
437
-
438
- try {
439
- const res = await cmsApi<{ prUrl: string; prNumber: number }>('/updates/apply', {
440
- method: 'POST',
441
- body: JSON.stringify({ targetVersion: updateInfo.latest }),
442
- })
443
-
444
- if (res.error) {
445
- toast.error(res.error)
446
- } else if (res.data) {
447
- setPrResult(res.data)
448
- toast.success('Update PR created successfully!')
449
- }
450
- } catch {
451
- toast.error('Failed to create update PR. Check your GitHub configuration.')
452
- } finally {
453
- setApplying(false)
454
- }
455
- }
456
-
457
- useEffect(() => {
458
- checkForUpdates()
459
- }, [])
460
-
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',
471
- },
472
- major: { bg: 'bg-red-50', text: 'text-red-700', border: 'border-red-200', label: 'Major' },
473
- }
474
-
475
- return (
476
- <>
477
- {/* 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>
487
- <button
488
- type="button"
489
- onClick={checkForUpdates}
490
- 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"
492
- >
493
- <RefreshCw className={`h-4 w-4 ${checking ? 'animate-spin' : ''}`} />
494
- {checking ? 'Checking...' : 'Check for Updates'}
495
- </button>
496
- </div>
497
- </div>
498
-
499
- {/* Error State */}
500
- {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
- <button
505
- 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"
507
- >
508
- Retry
509
- </button>
510
- </div>
511
- )}
512
-
513
- {/* Up to Date */}
514
- {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" />
517
- <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">
520
- Actuate CMS <span className="font-mono">{updateInfo.current}</span> is the latest
521
- version.
522
- </p>
523
- </div>
524
- </div>
525
- )}
526
-
527
- {/* Update Available */}
528
- {updateInfo?.updateAvailable && (
529
- <>
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
- >
533
- <div className="flex items-start gap-3">
534
- <ArrowUpCircle
535
- className={`mt-0.5 h-6 w-6 shrink-0 ${severityColors[updateInfo.severity ?? 'patch']?.text ?? 'text-blue-700'}`}
536
- />
537
- <div className="flex-1">
538
- <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>
544
- <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'}`}
546
- >
547
- {severityColors[updateInfo.severity ?? 'patch']?.label ?? 'Update'}
548
- </span>
549
- </div>
550
- <p className="mt-1 text-sm" style={{ color: 'inherit' }}>
551
- <span className="font-mono">{updateInfo.current}</span> &rarr;{' '}
552
- <span className="font-mono font-semibold">{updateInfo.latest}</span>
553
- {updateInfo.releaseDate && (
554
- <span className="ml-2 text-xs opacity-70">
555
- Released {updateInfo.releaseDate}
556
- </span>
557
- )}
558
- </p>
559
- </div>
560
- </div>
561
-
562
- <div className="mt-4 flex items-center gap-3">
563
- <button
564
- type="button"
565
- onClick={applyUpdate}
566
- 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"
568
- >
569
- {applying ? (
570
- <>
571
- <Loader2 className="h-4 w-4 animate-spin" />
572
- Creating PR...
573
- </>
574
- ) : (
575
- <>
576
- <GitPullRequest className="h-4 w-4" />
577
- Create Update PR
578
- </>
579
- )}
580
- </button>
581
- <span className="text-xs text-gray-500">Opens a pull request on your repository</span>
582
- </div>
583
-
584
- {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">
588
- {updateInfo.updateCommand}
589
- </code>
590
- </div>
591
- )}
592
- </div>
593
-
594
- {/* Changelog */}
595
- {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>
598
- <div className="max-h-64 space-y-2 overflow-y-auto">
599
- {updateInfo.changelog.map((entry) => (
600
- <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">
602
- {entry.version}
603
- </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>
606
- </div>
607
- ))}
608
- </div>
609
- </div>
610
- )}
611
- </>
612
- )}
613
-
614
- {/* PR Created */}
615
- {prResult && (
616
- <div className="rounded-lg border border-green-200 bg-green-50 p-4">
617
- <div className="flex items-start gap-3">
618
- <GitPullRequest className="mt-0.5 h-5 w-5 shrink-0 text-green-600" />
619
- <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">
622
- PR #{prResult.prNumber} has been created on your repository. Review and merge it to
623
- apply the update, then run{' '}
624
- <code className="rounded bg-green-100 px-1 font-mono text-xs">
625
- npx prisma migrate deploy
626
- </code>
627
- .
628
- </p>
629
- <a
630
- href={prResult.prUrl}
631
- target="_blank"
632
- 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"
634
- >
635
- <ExternalLink className="h-4 w-4" />
636
- View Pull Request
637
- </a>
638
- </div>
639
- </div>
640
- </div>
641
- )}
642
-
643
- {/* 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
-
651
- <div className="space-y-4">
652
- <div>
653
- <label className="mb-1 block text-sm font-medium text-gray-700">Repository</label>
654
- <input
655
- type="text"
656
- value={ghRepo}
657
- onChange={(e) => setGhRepo(e.target.value)}
658
- 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"
660
- />
661
- <p className="mt-1 text-xs text-gray-500">
662
- e.g.{' '}
663
- <code className="rounded bg-gray-100 px-1 font-mono">
664
- actuate-media/my-client-site
665
- </code>
666
- </p>
667
- </div>
668
- <div>
669
- <label className="mb-1 block text-sm font-medium text-gray-700">
670
- GitHub Token
671
- {updateInfo?.hasGithubToken && !ghToken && (
672
- <span className="ml-2 text-xs font-normal text-green-600">Saved</span>
673
- )}
674
- </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>
694
- </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{' '}
698
- <a
699
- href="https://github.com/settings/tokens"
700
- target="_blank"
701
- rel="noopener noreferrer"
702
- className="text-blue-600 hover:underline"
703
- >
704
- github.com/settings/tokens
705
- </a>
706
- </p>
707
- </div>
708
-
709
- <button
710
- type="button"
711
- onClick={saveGitHubConfig}
712
- 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"
714
- >
715
- {savingConfig ? (
716
- <>
717
- <Loader2 className="h-4 w-4 animate-spin" />
718
- Encrypting &amp; Saving...
719
- </>
720
- ) : (
721
- 'Save Configuration'
722
- )}
723
- </button>
724
- </div>
725
- </div>
726
-
727
- {/* 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">
731
- <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">
733
- 1
734
- </span>
735
- <span>
736
- Click &quot;Check for Updates&quot; to see if a new version is available on npm.
737
- </span>
738
- </li>
739
- <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">
741
- 2
742
- </span>
743
- <span>
744
- Add your GitHub token and repository above. They&apos;re encrypted at rest — never
745
- stored in plaintext.
746
- </span>
747
- </li>
748
- <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">
750
- 3
751
- </span>
752
- <span>
753
- Click &quot;Create Update PR&quot; to open a pull request that bumps your CMS packages
754
- automatically.
755
- </span>
756
- </li>
757
- <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">
759
- 4
760
- </span>
761
- <span>
762
- 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>.
764
- </span>
765
- </li>
766
- </ul>
767
- </div>
768
- </>
769
- )
770
- }
@@ -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