@growth-labs/cms 0.5.4 → 0.5.5
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.
- package/README.md +19 -1
- package/dist/integration/options.d.ts +19 -0
- package/dist/integration/options.d.ts.map +1 -1
- package/dist/integration/options.js +6 -0
- package/dist/integration/options.js.map +1 -1
- package/dist/routes/authors.d.ts.map +1 -1
- package/dist/routes/authors.js +33 -8
- package/dist/routes/authors.js.map +1 -1
- package/dist/routes/recovery-errors.d.ts +21 -0
- package/dist/routes/recovery-errors.d.ts.map +1 -0
- package/dist/routes/recovery-errors.js +53 -0
- package/dist/routes/recovery-errors.js.map +1 -0
- package/dist/ui/client/mount.d.ts.map +1 -1
- package/dist/ui/client/mount.js +5 -2
- package/dist/ui/client/mount.js.map +1 -1
- package/dist/ui/components/ShareModal.d.ts +4 -1
- package/dist/ui/components/ShareModal.d.ts.map +1 -1
- package/dist/ui/components/ShareModal.js +39 -9
- package/dist/ui/components/ShareModal.js.map +1 -1
- package/dist/ui/components/SharePickers.d.ts +29 -2
- package/dist/ui/components/SharePickers.d.ts.map +1 -1
- package/dist/ui/components/SharePickers.js +33 -8
- package/dist/ui/components/SharePickers.js.map +1 -1
- package/dist/ui/screens/AuthorsScreen.d.ts.map +1 -1
- package/dist/ui/screens/AuthorsScreen.js +2 -1
- package/dist/ui/screens/AuthorsScreen.js.map +1 -1
- package/dist/ui/screens/ContentRoute.d.ts +3 -1
- package/dist/ui/screens/ContentRoute.d.ts.map +1 -1
- package/dist/ui/screens/ContentRoute.js +3 -3
- package/dist/ui/screens/ContentRoute.js.map +1 -1
- package/dist/ui/screens/EditorScreen.d.ts +2 -1
- package/dist/ui/screens/EditorScreen.d.ts.map +1 -1
- package/dist/ui/screens/EditorScreen.js +2 -2
- package/dist/ui/screens/EditorScreen.js.map +1 -1
- package/dist/ui/screens/LibraryScreen.d.ts +2 -1
- package/dist/ui/screens/LibraryScreen.d.ts.map +1 -1
- package/dist/ui/screens/LibraryScreen.js +2 -2
- package/dist/ui/screens/LibraryScreen.js.map +1 -1
- package/dist/ui/screens/SocialShareScreen.d.ts +3 -1
- package/dist/ui/screens/SocialShareScreen.d.ts.map +1 -1
- package/dist/ui/screens/SocialShareScreen.js +20 -8
- package/dist/ui/screens/SocialShareScreen.js.map +1 -1
- package/dist/ui/screens/author-recovery.d.ts +11 -0
- package/dist/ui/screens/author-recovery.d.ts.map +1 -0
- package/dist/ui/screens/author-recovery.js +18 -0
- package/dist/ui/screens/author-recovery.js.map +1 -0
- package/dist/ui/screens/registry.d.ts +3 -0
- package/dist/ui/screens/registry.d.ts.map +1 -1
- package/dist/ui/screens/registry.js +16 -13
- package/dist/ui/screens/registry.js.map +1 -1
- package/dist/ui/screens/social-share-data.d.ts +23 -2
- package/dist/ui/screens/social-share-data.d.ts.map +1 -1
- package/dist/ui/screens/social-share-data.js +57 -11
- package/dist/ui/screens/social-share-data.js.map +1 -1
- package/package.json +1 -1
- package/src/integration/options.ts +7 -0
- package/src/routes/authors.ts +41 -20
- package/src/routes/recovery-errors.ts +70 -0
- package/src/ui/client/mount.tsx +5 -2
- package/src/ui/components/ShareModal.tsx +102 -19
- package/src/ui/components/SharePickers.tsx +76 -27
- package/src/ui/screens/AuthorsScreen.tsx +7 -2
- package/src/ui/screens/ContentRoute.tsx +13 -3
- package/src/ui/screens/EditorScreen.tsx +7 -1
- package/src/ui/screens/LibraryScreen.tsx +3 -1
- package/src/ui/screens/SocialShareScreen.tsx +53 -6
- package/src/ui/screens/author-recovery.ts +32 -0
- package/src/ui/screens/registry.tsx +22 -12
- package/src/ui/screens/social-share-data.ts +92 -13
|
@@ -22,6 +22,13 @@ export const SHARE_MODES = ['author-social', 'owned-social', 'youtube', 'newslet
|
|
|
22
22
|
|
|
23
23
|
export type ShareMode = (typeof SHARE_MODES)[number]
|
|
24
24
|
|
|
25
|
+
export function requiresCanonicalAuthorRoster(
|
|
26
|
+
mode: ShareMode,
|
|
27
|
+
allowInlineAuthorCreation: boolean,
|
|
28
|
+
): boolean {
|
|
29
|
+
return mode === 'author-social' && !allowInlineAuthorCreation
|
|
30
|
+
}
|
|
31
|
+
|
|
25
32
|
/**
|
|
26
33
|
* Social platform options.
|
|
27
34
|
* SOURCE OF TRUTH: @growth-labs/analytics src/utils/social.ts SOCIAL_PLATFORMS
|
|
@@ -91,6 +98,75 @@ export interface AuthorRef {
|
|
|
91
98
|
create?: boolean
|
|
92
99
|
}
|
|
93
100
|
|
|
101
|
+
export interface CanonicalAuthorEntry {
|
|
102
|
+
id: string
|
|
103
|
+
slug: string
|
|
104
|
+
displayName: string
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export type CanonicalAuthorRosterStatus =
|
|
108
|
+
| { blocked: false; kind: 'not-required' | 'synchronized'; message: null }
|
|
109
|
+
| {
|
|
110
|
+
blocked: true
|
|
111
|
+
kind: 'loading' | 'missing-author' | 'out-of-sync' | 'unavailable'
|
|
112
|
+
message: string
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
export function canonicalAuthorRosterStatus({
|
|
116
|
+
enabled,
|
|
117
|
+
author,
|
|
118
|
+
authors,
|
|
119
|
+
loading,
|
|
120
|
+
error,
|
|
121
|
+
}: {
|
|
122
|
+
enabled: boolean
|
|
123
|
+
author: AuthorRef
|
|
124
|
+
authors: CanonicalAuthorEntry[]
|
|
125
|
+
loading: boolean
|
|
126
|
+
error: string | null
|
|
127
|
+
}): CanonicalAuthorRosterStatus {
|
|
128
|
+
if (!enabled) return { blocked: false, kind: 'not-required', message: null }
|
|
129
|
+
|
|
130
|
+
const id = author.id?.trim() ?? ''
|
|
131
|
+
const slug = author.slug?.trim().toLowerCase() ?? ''
|
|
132
|
+
const identity = id || slug
|
|
133
|
+
if (!identity) {
|
|
134
|
+
return {
|
|
135
|
+
blocked: true,
|
|
136
|
+
kind: 'missing-author',
|
|
137
|
+
message:
|
|
138
|
+
'No canonical author is linked to this content. Add or publish the author in the Authors screen, then retry.',
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
if (loading) {
|
|
142
|
+
return {
|
|
143
|
+
blocked: true,
|
|
144
|
+
kind: 'loading',
|
|
145
|
+
message: `Checking the synchronized author roster for canonical author ${identity}.`,
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (error) {
|
|
149
|
+
return {
|
|
150
|
+
blocked: true,
|
|
151
|
+
kind: 'unavailable',
|
|
152
|
+
message: `The author roster could not be verified for canonical author ${identity}. Refresh and retry; sharing is blocked until synchronization can be proven.`,
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
const synchronized =
|
|
157
|
+
id !== ''
|
|
158
|
+
? authors.some((candidate) => candidate.id === id)
|
|
159
|
+
: authors.some((candidate) => candidate.slug.trim().toLowerCase() === slug)
|
|
160
|
+
if (!synchronized) {
|
|
161
|
+
return {
|
|
162
|
+
blocked: true,
|
|
163
|
+
kind: 'out-of-sync',
|
|
164
|
+
message: `Author roster out of sync for canonical author ${identity}. Publish or synchronize that author in the Authors screen, refresh, and retry.`,
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
return { blocked: false, kind: 'synchronized', message: null }
|
|
168
|
+
}
|
|
169
|
+
|
|
94
170
|
/** A reference to a social handle — existing by id/username or inline-created. */
|
|
95
171
|
export interface HandleRef {
|
|
96
172
|
id?: string
|
|
@@ -175,9 +251,7 @@ export interface ShareFormPrefill {
|
|
|
175
251
|
*/
|
|
176
252
|
export function initShareForm(mode: ShareMode, prefill?: ShareFormPrefill): ShareFormState {
|
|
177
253
|
const defaultCampaign =
|
|
178
|
-
mode === '
|
|
179
|
-
? prefill?.campaign?.trim() || prefill?.contentSlug?.trim() || ''
|
|
180
|
-
: ''
|
|
254
|
+
mode === 'owned-social' ? prefill?.campaign?.trim() || prefill?.contentSlug?.trim() || '' : ''
|
|
181
255
|
const base: ShareFormState = {
|
|
182
256
|
mode,
|
|
183
257
|
destinationUrl: prefill?.destinationUrl ?? '',
|
|
@@ -281,7 +355,12 @@ export function shareFormReducer(state: ShareFormState, action: ShareFormAction)
|
|
|
281
355
|
mode,
|
|
282
356
|
// Mode-specific resets
|
|
283
357
|
platform: mode === 'youtube' ? 'youtube' : mode === 'newsletter' ? '' : '',
|
|
284
|
-
campaign:
|
|
358
|
+
campaign:
|
|
359
|
+
mode === 'owned-social'
|
|
360
|
+
? state.campaign || state.contentSlug
|
|
361
|
+
: mode === 'author-social'
|
|
362
|
+
? ''
|
|
363
|
+
: '',
|
|
285
364
|
placements: mode === 'youtube' ? [...YOUTUBE_BUNDLE_PLACEMENTS] : [],
|
|
286
365
|
youtubeVideoId: mode === 'youtube' ? state.youtubeVideoId : '',
|
|
287
366
|
issue: mode === 'newsletter' ? state.issue : '',
|
|
@@ -289,15 +368,14 @@ export function shareFormReducer(state: ShareFormState, action: ShareFormAction)
|
|
|
289
368
|
}
|
|
290
369
|
|
|
291
370
|
case 'prefill-content': {
|
|
292
|
-
const shouldDefaultCampaign =
|
|
293
|
-
(state.mode === 'author-social' || state.mode === 'owned-social') && !state.campaign.trim()
|
|
371
|
+
const shouldDefaultCampaign = state.mode === 'owned-social' && !state.campaign.trim()
|
|
294
372
|
return {
|
|
295
373
|
...state,
|
|
296
374
|
destinationUrl: action.destinationUrl,
|
|
297
375
|
contentSlug: action.contentSlug,
|
|
298
376
|
contentType: action.contentType,
|
|
299
377
|
campaign: shouldDefaultCampaign ? action.contentSlug : state.campaign,
|
|
300
|
-
|
|
378
|
+
author: action.author ?? {},
|
|
301
379
|
}
|
|
302
380
|
}
|
|
303
381
|
|
|
@@ -349,7 +427,6 @@ export interface AuthorSocialBatchPayload {
|
|
|
349
427
|
kind: 'author-social'
|
|
350
428
|
destinationUrl: string
|
|
351
429
|
platform: string
|
|
352
|
-
campaign: string
|
|
353
430
|
placement?: string
|
|
354
431
|
boostMode?: string
|
|
355
432
|
promoCode?: string
|
|
@@ -462,14 +539,16 @@ export function buildBatchPayloadFrom(state: ShareFormState): BuildPayloadResult
|
|
|
462
539
|
|
|
463
540
|
switch (state.mode) {
|
|
464
541
|
case 'author-social': {
|
|
465
|
-
if (!state.
|
|
466
|
-
return {
|
|
542
|
+
if (!refHasContent(state.author)) {
|
|
543
|
+
return {
|
|
544
|
+
ok: false,
|
|
545
|
+
error: 'A canonical author is required for Author Social. Select a synchronized author.',
|
|
546
|
+
}
|
|
467
547
|
}
|
|
468
548
|
const payload: AuthorSocialBatchPayload = {
|
|
469
549
|
kind: 'author-social',
|
|
470
550
|
...shared,
|
|
471
551
|
platform: state.platform,
|
|
472
|
-
campaign: state.campaign.trim(),
|
|
473
552
|
...(state.placement && state.placement !== 'unknown' ? { placement: state.placement } : {}),
|
|
474
553
|
...(state.boostMode && state.boostMode !== 'none' ? { boostMode: state.boostMode } : {}),
|
|
475
554
|
...(nonEmpty(state.promoCode) ? { promoCode: state.promoCode } : {}),
|
|
@@ -701,7 +780,7 @@ export function toPlainText(output: ShareOutput): string {
|
|
|
701
780
|
* given mode. The React form renders only these inputs; the request builders
|
|
702
781
|
* read only these fields.
|
|
703
782
|
*
|
|
704
|
-
* 'author-social' →
|
|
783
|
+
* 'author-social' → platform, author, destinationUrl, advanced
|
|
705
784
|
* 'owned-social' → campaign, platform, handle, (optional author), destinationUrl, advanced
|
|
706
785
|
* 'youtube' → youtubeVideoId, placements, (optional handle/author), destinationUrl (NO campaign)
|
|
707
786
|
* 'newsletter' → issue, (optional author/handle), destinationUrl (NO campaign/platform/boostMode)
|
|
@@ -711,7 +790,7 @@ export function relevantFields(mode: ShareMode): string[] {
|
|
|
711
790
|
|
|
712
791
|
switch (mode) {
|
|
713
792
|
case 'author-social':
|
|
714
|
-
return [...shared, '
|
|
793
|
+
return [...shared, 'platform', 'author', 'placement', 'boostMode', 'promoCode']
|
|
715
794
|
|
|
716
795
|
case 'owned-social':
|
|
717
796
|
return [
|