@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,6 +3,7 @@
3
3
  import { AlertTriangle, Check, CircleHelp, X } from 'lucide-react'
4
4
  import type { ReactNode } from 'react'
5
5
  import type { CheckStatus } from '@actuate-media/cms-core/security-center'
6
+ import { SettingsCard } from './components.js'
6
7
 
7
8
  /** Small status icon used in check/summary rows. Text label always accompanies it. */
8
9
  export function CheckIcon({ status }: { status: CheckStatus }) {
@@ -51,25 +52,29 @@ export function StatusRow({
51
52
  )
52
53
  }
53
54
 
54
- /** Section wrapper matching the other settings cards. */
55
+ /**
56
+ * Section wrapper for the Security tab. Delegates to the shared
57
+ * {@link SettingsCard} so Security matches the canonical Settings design
58
+ * language (p-6, text-lg title, muted description). `headingId` is accepted
59
+ * for backward compatibility but ignored — {@link SettingsCard} generates and
60
+ * wires its own accessible heading id.
61
+ */
55
62
  export function SecurityCard({
56
- headingId,
57
63
  title,
58
64
  description,
65
+ actions,
59
66
  children,
60
67
  }: {
61
- headingId: string
68
+ /** @deprecated No longer used; SettingsCard manages the heading id. */
69
+ headingId?: string
62
70
  title: string
63
71
  description: string
72
+ actions?: ReactNode
64
73
  children: ReactNode
65
74
  }) {
66
75
  return (
67
- <section aria-labelledby={headingId} className="border-border bg-card rounded-lg border p-4">
68
- <h3 id={headingId} className="text-foreground mb-1 text-base font-medium">
69
- {title}
70
- </h3>
71
- <p className="text-muted-foreground mb-4 text-sm">{description}</p>
76
+ <SettingsCard title={title} description={description} actions={actions}>
72
77
  {children}
73
- </section>
78
+ </SettingsCard>
74
79
  )
75
80
  }
@@ -0,0 +1,183 @@
1
+ 'use client'
2
+
3
+ import { useCallback, useEffect, useState } from 'react'
4
+ import { toast } from 'sonner'
5
+
6
+ import { cmsApi } from '../../lib/api.js'
7
+
8
+ export type UpdateChangeType =
9
+ | 'feature'
10
+ | 'fix'
11
+ | 'improvement'
12
+ | 'breaking'
13
+ | 'security'
14
+ | 'deprecation'
15
+
16
+ export interface UpdateChangelogItem {
17
+ version: string
18
+ date: string
19
+ summary: string
20
+ type?: UpdateChangeType
21
+ }
22
+
23
+ export interface UpdatePluginCompatibility {
24
+ name: string
25
+ compatible: boolean
26
+ minVersion?: string
27
+ }
28
+
29
+ export interface UpdateInfo {
30
+ current: string
31
+ latest: string
32
+ updateAvailable: boolean
33
+ severity?: 'patch' | 'minor' | 'major' | 'security'
34
+ releaseDate?: string
35
+ changelog?: UpdateChangelogItem[]
36
+ updateCommand?: string
37
+ breakingChanges?: string[]
38
+ hasMigrations?: boolean
39
+ pluginCompatibility?: UpdatePluginCompatibility[]
40
+ source?: 'update-server' | 'npm'
41
+ /** Whether the current session may manage updates (admin/owner). */
42
+ canManage?: boolean
43
+ hasGithubToken?: boolean
44
+ githubRepo?: string
45
+ }
46
+
47
+ interface GithubForm {
48
+ githubRepo: string
49
+ githubToken: string
50
+ }
51
+
52
+ const REPO_PATTERN = /^[a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+$/
53
+
54
+ /** Admin/Owner may manage updates (matches the server's `requireAdminScope`). */
55
+ export function canManageUpdates(role: string | null | undefined): boolean {
56
+ const r = (role ?? '').toUpperCase()
57
+ return r === 'ADMIN' || r === 'OWNER'
58
+ }
59
+
60
+ /**
61
+ * State + actions for the Settings → Updates tab. Owns the update-status check,
62
+ * the GitHub-integration config form (with dirty tracking for the shared save
63
+ * bar), and the "Create Update PR" action. All writes are gated on `canManage`,
64
+ * which the server confirms in the check response.
65
+ */
66
+ export function useUpdates({ role }: { role?: string }) {
67
+ const [info, setInfo] = useState<UpdateInfo | null>(null)
68
+ const [checking, setChecking] = useState(false)
69
+ const [hasChecked, setHasChecked] = useState(false)
70
+ const [error, setError] = useState('')
71
+ const [applying, setApplying] = useState(false)
72
+ const [prResult, setPrResult] = useState<{ prUrl: string; prNumber: number } | null>(null)
73
+
74
+ const [form, setForm] = useState<GithubForm>({ githubRepo: '', githubToken: '' })
75
+ const [baseline, setBaseline] = useState<GithubForm>({ githubRepo: '', githubToken: '' })
76
+ const [saveState, setSaveState] = useState<'idle' | 'saving' | 'success' | 'error'>('idle')
77
+
78
+ // Optimistic until the check response confirms the server-side permission.
79
+ const canManage = info?.canManage ?? canManageUpdates(role)
80
+
81
+ const check = useCallback(async () => {
82
+ setChecking(true)
83
+ setError('')
84
+ setPrResult(null)
85
+ const res = await cmsApi<UpdateInfo>('/updates/check')
86
+ if (res.error) {
87
+ setError(res.error)
88
+ } else if (res.data) {
89
+ setInfo(res.data)
90
+ const repo = res.data.githubRepo ?? ''
91
+ setForm({ githubRepo: repo, githubToken: '' })
92
+ setBaseline({ githubRepo: repo, githubToken: '' })
93
+ }
94
+ setChecking(false)
95
+ setHasChecked(true)
96
+ }, [])
97
+
98
+ useEffect(() => {
99
+ check()
100
+ }, [check])
101
+
102
+ const repoError =
103
+ form.githubRepo.trim() && !REPO_PATTERN.test(form.githubRepo.trim())
104
+ ? 'Use the owner/repo format (e.g. actuate-media/my-site).'
105
+ : ''
106
+ const hasErrors = Boolean(repoError)
107
+ const dirty = form.githubRepo !== baseline.githubRepo || form.githubToken.trim() !== ''
108
+
109
+ const setField = useCallback((key: keyof GithubForm, value: string) => {
110
+ setForm((f) => ({ ...f, [key]: value }))
111
+ setSaveState('idle')
112
+ }, [])
113
+
114
+ const save = useCallback(async () => {
115
+ if (hasErrors || !dirty) return
116
+ setSaveState('saving')
117
+ const body: Record<string, string> = {}
118
+ if (form.githubToken.trim()) body.githubToken = form.githubToken.trim()
119
+ if (form.githubRepo.trim()) body.githubRepo = form.githubRepo.trim()
120
+
121
+ const res = await cmsApi('/updates/config', { method: 'PUT', body: JSON.stringify(body) })
122
+ if (res.error) {
123
+ setSaveState('error')
124
+ toast.error(res.error)
125
+ return
126
+ }
127
+ setSaveState('success')
128
+ toast.success('GitHub configuration saved and encrypted.')
129
+ const savedRepo = form.githubRepo.trim()
130
+ setBaseline({ githubRepo: savedRepo, githubToken: '' })
131
+ setForm({ githubRepo: savedRepo, githubToken: '' })
132
+ setInfo((prev) =>
133
+ prev
134
+ ? {
135
+ ...prev,
136
+ githubRepo: savedRepo,
137
+ hasGithubToken: prev.hasGithubToken || Boolean(body.githubToken),
138
+ }
139
+ : prev,
140
+ )
141
+ }, [dirty, form.githubRepo, form.githubToken, hasErrors])
142
+
143
+ const reset = useCallback(() => {
144
+ setForm({ ...baseline })
145
+ setSaveState('idle')
146
+ }, [baseline])
147
+
148
+ const apply = useCallback(async () => {
149
+ if (!info?.latest || !canManage) return
150
+ setApplying(true)
151
+ const res = await cmsApi<{ prUrl: string; prNumber: number }>('/updates/apply', {
152
+ method: 'POST',
153
+ body: JSON.stringify({ targetVersion: info.latest }),
154
+ })
155
+ if (res.error) {
156
+ toast.error(res.error)
157
+ } else if (res.data) {
158
+ setPrResult(res.data)
159
+ toast.success('Update PR created.')
160
+ }
161
+ setApplying(false)
162
+ }, [info, canManage])
163
+
164
+ return {
165
+ info,
166
+ checking,
167
+ hasChecked,
168
+ error,
169
+ applying,
170
+ prResult,
171
+ canManage,
172
+ form,
173
+ setField,
174
+ dirty,
175
+ repoError,
176
+ hasErrors,
177
+ saveState,
178
+ save,
179
+ reset,
180
+ check,
181
+ apply,
182
+ }
183
+ }