@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
@@ -1,6 +1,6 @@
1
1
  'use client'
2
2
 
3
- import { AlertTriangle, Bot, Info, Loader2, Plus } from 'lucide-react'
3
+ import { AlertTriangle, Info, Loader2, Plus } from 'lucide-react'
4
4
  import { useId, useMemo, useState } from 'react'
5
5
  import { toast } from 'sonner'
6
6
  import { AiFeaturesCard } from './AiFeaturesCard.js'
@@ -8,7 +8,7 @@ import { BrandVoiceCard } from './BrandVoiceCard.js'
8
8
  import { ModelSelector } from './ModelSelector.js'
9
9
  import { MonthlyUsageCard } from './MonthlyUsageCard.js'
10
10
  import { ProviderConnectionCard } from './ProviderConnectionCard.js'
11
- import { SettingsSaveBar } from './components.js'
11
+ import { SettingsCard, SettingsSaveBar } from './components.js'
12
12
  import {
13
13
  useAiSettings,
14
14
  type AiProviderKey,
@@ -41,7 +41,7 @@ export function AISettingsTab({ role }: AISettingsTabProps) {
41
41
  <div className="space-y-4" aria-busy="true" aria-live="polite">
42
42
  <span className="sr-only">Loading AI settings…</span>
43
43
  {[0, 1, 2].map((i) => (
44
- <div key={i} className="border-border bg-card rounded-lg border p-4">
44
+ <div key={i} className="border-border bg-card rounded-lg border p-6">
45
45
  <div className="bg-muted mb-4 h-4 w-40 animate-pulse rounded" />
46
46
  <div className="space-y-3">
47
47
  <div className="bg-muted h-9 w-full animate-pulse rounded" />
@@ -125,7 +125,6 @@ export function AISettingsTab({ role }: AISettingsTabProps) {
125
125
  // ---------------------------------------------------------------------------
126
126
 
127
127
  function AssistantCard({ ai, canEdit }: { ai: UseAiSettings; canEdit: boolean }) {
128
- const headingId = useId()
129
128
  const providerSelectId = useId()
130
129
  const [refreshing, setRefreshing] = useState(false)
131
130
 
@@ -150,21 +149,10 @@ function AssistantCard({ ai, canEdit }: { ai: UseAiSettings; canEdit: boolean })
150
149
  }
151
150
 
152
151
  return (
153
- <section aria-labelledby={headingId} className="border-border bg-card rounded-lg border p-4">
154
- <div className="mb-4 flex items-center gap-2">
155
- <span className="bg-brand/10 text-brand flex h-9 w-9 items-center justify-center rounded-lg">
156
- <Bot size={18} aria-hidden="true" />
157
- </span>
158
- <div>
159
- <h3 id={headingId} className="text-foreground text-base font-medium">
160
- AI Assistant
161
- </h3>
162
- <p className="text-muted-foreground text-sm">
163
- The default provider and model used across AI features.
164
- </p>
165
- </div>
166
- </div>
167
-
152
+ <SettingsCard
153
+ title="AI Assistant"
154
+ description="The default provider and model used across AI features."
155
+ >
168
156
  {ai.providers.length === 0 ? (
169
157
  <div className="border-border bg-muted/40 text-muted-foreground rounded-md border border-dashed p-4 text-sm">
170
158
  No AI providers are connected yet. Add one below to enable AI features.
@@ -206,7 +194,7 @@ function AssistantCard({ ai, canEdit }: { ai: UseAiSettings; canEdit: boolean })
206
194
  />
207
195
  </div>
208
196
  )}
209
- </section>
197
+ </SettingsCard>
210
198
  )
211
199
  }
212
200
 
@@ -215,16 +203,14 @@ function AssistantCard({ ai, canEdit }: { ai: UseAiSettings; canEdit: boolean })
215
203
  // ---------------------------------------------------------------------------
216
204
 
217
205
  function ProviderConnectionsSection({ ai, canEdit }: { ai: UseAiSettings; canEdit: boolean }) {
218
- const headingId = useId()
219
206
  const [adding, setAdding] = useState(false)
220
207
 
221
208
  return (
222
- <section aria-labelledby={headingId} className="space-y-3">
223
- <div className="flex items-center justify-between">
224
- <h3 id={headingId} className="text-foreground text-base font-medium">
225
- Provider connections
226
- </h3>
227
- {canEdit && !adding && (
209
+ <SettingsCard
210
+ title="Provider connections"
211
+ description="Connect Anthropic, OpenAI, xAI, or a custom OpenAI-compatible endpoint."
212
+ actions={
213
+ canEdit && !adding ? (
228
214
  <button
229
215
  type="button"
230
216
  onClick={() => setAdding(true)}
@@ -233,57 +219,59 @@ function ProviderConnectionsSection({ ai, canEdit }: { ai: UseAiSettings; canEdi
233
219
  <Plus size={14} aria-hidden="true" />
234
220
  Add provider
235
221
  </button>
222
+ ) : undefined
223
+ }
224
+ >
225
+ <div className="space-y-3">
226
+ {adding && (
227
+ <AddProviderForm
228
+ ai={ai}
229
+ onDone={() => setAdding(false)}
230
+ onCancel={() => setAdding(false)}
231
+ />
236
232
  )}
237
- </div>
238
233
 
239
- {adding && (
240
- <AddProviderForm
241
- ai={ai}
242
- onDone={() => setAdding(false)}
243
- onCancel={() => setAdding(false)}
244
- />
245
- )}
246
-
247
- {ai.providers.map((p) => (
248
- <ProviderConnectionCard
249
- key={p.id}
250
- provider={p}
251
- canEdit={canEdit}
252
- busy={ai.busyProviderId === p.id}
253
- onTest={async () => {
254
- const res = await ai.testProvider(p.id)
255
- if (!res.ok) toast.error(res.error ?? res.message ?? 'Connection test failed.')
256
- else toast.success(res.message ?? 'Connection successful.')
257
- }}
258
- onSyncModels={async () => {
259
- const res = await ai.syncModels(p.id)
260
- if (!res.ok) toast.error(res.error ?? 'Failed to refresh models.')
261
- else if (res.fromFallback)
262
- toast.warning(res.warning ?? 'Showing the curated model list.')
263
- else toast.success('Model list refreshed.')
264
- }}
265
- onUpdateKey={async (apiKey) => {
266
- const res = await ai.updateProvider(p.id, { apiKey })
267
- if (!res.ok) toast.error(res.error ?? 'Failed to update key.')
268
- else toast.success('API key updated.')
269
- }}
270
- onUpdateBaseUrl={async (baseUrl) => {
271
- const res = await ai.updateProvider(p.id, { baseUrl })
272
- if (!res.ok) toast.error(res.error ?? 'Failed to update base URL.')
273
- else toast.success('Base URL updated.')
274
- }}
275
- onDisconnect={async () => {
276
- const res = await ai.deleteProvider(p.id)
277
- if (!res.ok) toast.error(res.error ?? 'Failed to disconnect.')
278
- else toast.success(`${p.displayName} disconnected.`)
279
- }}
280
- />
281
- ))}
234
+ {ai.providers.map((p) => (
235
+ <ProviderConnectionCard
236
+ key={p.id}
237
+ provider={p}
238
+ canEdit={canEdit}
239
+ busy={ai.busyProviderId === p.id}
240
+ onTest={async () => {
241
+ const res = await ai.testProvider(p.id)
242
+ if (!res.ok) toast.error(res.error ?? res.message ?? 'Connection test failed.')
243
+ else toast.success(res.message ?? 'Connection successful.')
244
+ }}
245
+ onSyncModels={async () => {
246
+ const res = await ai.syncModels(p.id)
247
+ if (!res.ok) toast.error(res.error ?? 'Failed to refresh models.')
248
+ else if (res.fromFallback)
249
+ toast.warning(res.warning ?? 'Showing the curated model list.')
250
+ else toast.success('Model list refreshed.')
251
+ }}
252
+ onUpdateKey={async (apiKey) => {
253
+ const res = await ai.updateProvider(p.id, { apiKey })
254
+ if (!res.ok) toast.error(res.error ?? 'Failed to update key.')
255
+ else toast.success('API key updated.')
256
+ }}
257
+ onUpdateBaseUrl={async (baseUrl) => {
258
+ const res = await ai.updateProvider(p.id, { baseUrl })
259
+ if (!res.ok) toast.error(res.error ?? 'Failed to update base URL.')
260
+ else toast.success('Base URL updated.')
261
+ }}
262
+ onDisconnect={async () => {
263
+ const res = await ai.deleteProvider(p.id)
264
+ if (!res.ok) toast.error(res.error ?? 'Failed to disconnect.')
265
+ else toast.success(`${p.displayName} disconnected.`)
266
+ }}
267
+ />
268
+ ))}
282
269
 
283
- {ai.providers.length === 0 && !adding && (
284
- <p className="text-muted-foreground text-sm">No providers connected.</p>
285
- )}
286
- </section>
270
+ {ai.providers.length === 0 && !adding && (
271
+ <p className="text-muted-foreground text-sm">No providers connected.</p>
272
+ )}
273
+ </div>
274
+ </SettingsCard>
287
275
  )
288
276
  }
289
277
 
@@ -332,7 +320,7 @@ function AddProviderForm({
332
320
  }
333
321
 
334
322
  return (
335
- <div className="border-border bg-card rounded-lg border p-4">
323
+ <div className="border-border bg-background rounded-lg border p-4">
336
324
  <h4 className="text-foreground mb-3 text-base font-medium">Add a provider</h4>
337
325
  <div className="space-y-3">
338
326
  <div>
@@ -66,7 +66,7 @@ export function AdminThemeCard({ s, canEdit }: { s: UseAppearanceSettings; canEd
66
66
  return (
67
67
  <section className="border-border bg-card rounded-lg border p-6">
68
68
  <header className="mb-4">
69
- <h3 className="text-foreground text-lg font-medium">Admin Theme</h3>
69
+ <h3 className="text-foreground text-lg font-medium">Admin theme</h3>
70
70
  <p className="text-muted-foreground mt-0.5 text-sm">
71
71
  Controls the default look of the admin. Individual users can override the color mode.
72
72
  </p>
@@ -2,7 +2,7 @@
2
2
 
3
3
  import { AlertTriangle } from 'lucide-react'
4
4
  import { useId } from 'react'
5
- import { SettingsToggleRow } from './components.js'
5
+ import { SettingsCard, SettingsToggleRow } from './components.js'
6
6
  import { capabilityLabel, effectiveFeatureModel, isModelCompatible } from './featureModel.js'
7
7
  import type { AiFeatureView, AiModelView } from './useAiSettings.js'
8
8
 
@@ -31,17 +31,11 @@ export function AiFeaturesCard({
31
31
  onToggle: (key: string, enabled: boolean) => void
32
32
  onSetFeatureModel: (key: string, modelId: string) => void
33
33
  }) {
34
- const headingId = useId()
35
34
  return (
36
- <section aria-labelledby={headingId} className="border-border bg-card rounded-lg border p-4">
37
- <h3 id={headingId} className="text-foreground mb-1 text-base font-medium">
38
- AI Features
39
- </h3>
40
- <p className="text-muted-foreground mb-4 text-sm">
41
- Turn individual AI-powered features on or off, and optionally route each to a specific
42
- model.
43
- </p>
44
-
35
+ <SettingsCard
36
+ title="AI Features"
37
+ description="Turn individual AI-powered features on or off, and optionally route each to a specific model."
38
+ >
45
39
  {features.length === 0 ? (
46
40
  <p className="text-muted-foreground text-sm">No AI features are available.</p>
47
41
  ) : (
@@ -59,7 +53,7 @@ export function AiFeaturesCard({
59
53
  ))}
60
54
  </div>
61
55
  )}
62
- </section>
56
+ </SettingsCard>
63
57
  )
64
58
  }
65
59
 
@@ -60,7 +60,7 @@ export function BrandPreviewCard({ s }: { s: UseAppearanceSettings }) {
60
60
  <section className="border-border bg-card rounded-lg border p-6">
61
61
  <header className="mb-4 flex items-center justify-between gap-2">
62
62
  <div>
63
- <h3 className="text-foreground text-lg font-medium">Brand Preview</h3>
63
+ <h3 className="text-foreground text-lg font-medium">Brand preview</h3>
64
64
  <p className="text-muted-foreground mt-0.5 text-sm">
65
65
  How your brand renders across the admin and public site.
66
66
  </p>
@@ -1,8 +1,7 @@
1
1
  'use client'
2
2
 
3
- import { Sparkles } from 'lucide-react'
4
3
  import { useId } from 'react'
5
- import { SettingsToggleRow } from './components.js'
4
+ import { SettingsCard, SettingsToggleRow } from './components.js'
6
5
  import type { BrandVoiceForm } from './useAiSettings.js'
7
6
 
8
7
  const INPUT_CLASS =
@@ -28,7 +27,6 @@ export function BrandVoiceCard({
28
27
  canEdit: boolean
29
28
  onChange: (patch: Partial<BrandVoiceForm>) => void
30
29
  }) {
31
- const headingId = useId()
32
30
  const nameId = useId()
33
31
  const toneId = useId()
34
32
  const audienceId = useId()
@@ -37,21 +35,10 @@ export function BrandVoiceCard({
37
35
  const disabled = !canEdit
38
36
 
39
37
  return (
40
- <section aria-labelledby={headingId} className="border-border bg-card rounded-lg border p-4">
41
- <div className="mb-4 flex items-center gap-2">
42
- <span className="bg-brand/10 text-brand flex h-9 w-9 items-center justify-center rounded-lg">
43
- <Sparkles size={18} aria-hidden="true" />
44
- </span>
45
- <div>
46
- <h3 id={headingId} className="text-foreground text-base font-medium">
47
- Brand voice
48
- </h3>
49
- <p className="text-muted-foreground text-sm">
50
- Teach the AI how your brand sounds. Applied to content writing and SEO copy.
51
- </p>
52
- </div>
53
- </div>
54
-
38
+ <SettingsCard
39
+ title="Brand voice"
40
+ description="Teach the AI how your brand sounds. Applied to content writing and SEO copy."
41
+ >
55
42
  <div className="space-y-4">
56
43
  <SettingsToggleRow
57
44
  label="Apply brand voice"
@@ -147,6 +134,6 @@ export function BrandVoiceCard({
147
134
  </div>
148
135
  </div>
149
136
  </div>
150
- </section>
137
+ </SettingsCard>
151
138
  )
152
139
  }