@happyvertical/smrt-social 0.30.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 (47) hide show
  1. package/AGENTS.md +18 -0
  2. package/CLAUDE.md +1 -0
  3. package/LICENSE +7 -0
  4. package/README.md +94 -0
  5. package/dist/__smrt-register__.d.ts +2 -0
  6. package/dist/__smrt-register__.d.ts.map +1 -0
  7. package/dist/collections/index.d.ts +5 -0
  8. package/dist/collections/index.d.ts.map +1 -0
  9. package/dist/collections/oauth-state-collection.d.ts +9 -0
  10. package/dist/collections/oauth-state-collection.d.ts.map +1 -0
  11. package/dist/collections/social-account-collection.d.ts +9 -0
  12. package/dist/collections/social-account-collection.d.ts.map +1 -0
  13. package/dist/collections/social-post-analytics-snapshot-collection.d.ts +18 -0
  14. package/dist/collections/social-post-analytics-snapshot-collection.d.ts.map +1 -0
  15. package/dist/collections/social-post-collection.d.ts +32 -0
  16. package/dist/collections/social-post-collection.d.ts.map +1 -0
  17. package/dist/index.d.ts +6 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +824 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/manifest.json +3363 -0
  22. package/dist/oauth-state.d.ts +127 -0
  23. package/dist/oauth-state.d.ts.map +1 -0
  24. package/dist/server.d.ts +18 -0
  25. package/dist/server.d.ts.map +1 -0
  26. package/dist/server.js +106 -0
  27. package/dist/server.js.map +1 -0
  28. package/dist/smrt-knowledge.json +1398 -0
  29. package/dist/social-account.d.ts +270 -0
  30. package/dist/social-account.d.ts.map +1 -0
  31. package/dist/social-post-analytics-snapshot.d.ts +40 -0
  32. package/dist/social-post-analytics-snapshot.d.ts.map +1 -0
  33. package/dist/social-post.d.ts +258 -0
  34. package/dist/social-post.d.ts.map +1 -0
  35. package/dist/svelte/components/SocialAccountSettings.svelte +243 -0
  36. package/dist/svelte/components/SocialAccountSettings.svelte.d.ts +15 -0
  37. package/dist/svelte/components/SocialAccountSettings.svelte.d.ts.map +1 -0
  38. package/dist/svelte/i18n.d.ts +9 -0
  39. package/dist/svelte/i18n.d.ts.map +1 -0
  40. package/dist/svelte/i18n.js +15 -0
  41. package/dist/svelte/index.d.ts +6 -0
  42. package/dist/svelte/index.d.ts.map +1 -0
  43. package/dist/svelte/index.js +2 -0
  44. package/dist/svelte/types.d.ts +16 -0
  45. package/dist/svelte/types.d.ts.map +1 -0
  46. package/dist/svelte/types.js +1 -0
  47. package/package.json +88 -0
@@ -0,0 +1,243 @@
1
+ <script lang="ts">
2
+ import { useI18n } from '@happyvertical/smrt-ui/i18n';
3
+ import type { SocialPlatformType } from '../../social-account.js';
4
+ import { M } from '../i18n.js';
5
+ import type { SocialAccountSettingsItem } from '../types.js';
6
+
7
+ const { t } = useI18n();
8
+
9
+ let {
10
+ accounts = [],
11
+ loading = false,
12
+ readonly = false,
13
+ connectHrefs = {},
14
+ onRefresh,
15
+ onTest,
16
+ onDeactivate,
17
+ }: {
18
+ accounts?: SocialAccountSettingsItem[];
19
+ loading?: boolean;
20
+ readonly?: boolean;
21
+ connectHrefs?: Partial<Record<SocialPlatformType, string>>;
22
+ onRefresh?: () => void | Promise<void>;
23
+ onTest?: (account: SocialAccountSettingsItem) => void | Promise<void>;
24
+ onDeactivate?: (account: SocialAccountSettingsItem) => void | Promise<void>;
25
+ } = $props();
26
+
27
+ const platforms: Array<{ platform: SocialPlatformType; label: string }> = [
28
+ { platform: 'youtube', label: 'YouTube' },
29
+ { platform: 'facebook', label: 'Facebook Page' },
30
+ { platform: 'threads', label: 'Threads' },
31
+ { platform: 'x', label: 'X' },
32
+ { platform: 'bluesky', label: 'Bluesky' },
33
+ ];
34
+
35
+ function statusLabel(account: SocialAccountSettingsItem): string {
36
+ if (!account.isActive) return 'inactive';
37
+ if (account.needsAttention) return 'needs attention';
38
+ return account.status.replaceAll('_', ' ');
39
+ }
40
+ </script>
41
+
42
+ <section class="social-settings">
43
+ <header class="toolbar">
44
+ <div>
45
+ <h2>{t(M['social.account_settings.heading'])}</h2>
46
+ <p>{accounts.length === 1 ? t(M['social.account_settings.configured_one'], { count: accounts.length }) : t(M['social.account_settings.configured_other'], { count: accounts.length })}</p>
47
+ </div>
48
+ {#if onRefresh}
49
+ <button type="button" class="secondary" onclick={() => onRefresh?.()} disabled={loading}>Refresh</button>
50
+ {/if}
51
+ </header>
52
+
53
+ {#if !readonly}
54
+ <div class="connect-row" aria-label={t(M['social.account_settings.connect_aria'])}>
55
+ {#each platforms as item}
56
+ {#if connectHrefs[item.platform]}
57
+ <a class="connect-button" href={connectHrefs[item.platform]}>{item.label}</a>
58
+ {:else}
59
+ <button type="button" class="connect-button" disabled>{item.label}</button>
60
+ {/if}
61
+ {/each}
62
+ </div>
63
+ {/if}
64
+
65
+ {#if loading}
66
+ <div class="empty">{t(M['social.account_settings.loading'])}</div>
67
+ {:else if accounts.length === 0}
68
+ <div class="empty">{t(M['social.account_settings.empty'])}</div>
69
+ {:else}
70
+ <div class="account-list">
71
+ {#each accounts as account (account.id)}
72
+ <article class="account-row" data-status={account.status}>
73
+ <div class="account-main">
74
+ <div class="account-title">
75
+ <h3>{account.name}</h3>
76
+ <span>{account.platform}</span>
77
+ </div>
78
+ {#if account.platformUsername}
79
+ <p>@{account.platformUsername}</p>
80
+ {/if}
81
+ {#if account.publishMode}
82
+ <p>
83
+ Mode: {account.publishMode.replaceAll('_', ' ')}
84
+ {account.publishMode === 'public' && !account.publicPublishingAllowed ? ' (blocked)' : ''}
85
+ </p>
86
+ {/if}
87
+ {#if account.missingPermissions?.length}
88
+ <p class="warning">Missing: {account.missingPermissions.join(', ')}</p>
89
+ {/if}
90
+ </div>
91
+
92
+ <div class="account-actions">
93
+ <span class:attention={account.needsAttention} class="status">{statusLabel(account)}</span>
94
+ {#if account.platformUrl}
95
+ <a class="secondary" href={account.platformUrl} target="_blank" rel="noreferrer">Open</a>
96
+ {/if}
97
+ {#if onTest}
98
+ <button type="button" class="secondary" onclick={() => onTest?.(account)}>Test</button>
99
+ {/if}
100
+ {#if !readonly && onDeactivate && account.isActive}
101
+ <button type="button" class="danger" onclick={() => onDeactivate?.(account)}>Deactivate</button>
102
+ {/if}
103
+ </div>
104
+ </article>
105
+ {/each}
106
+ </div>
107
+ {/if}
108
+ </section>
109
+
110
+ <style>
111
+ .social-settings {
112
+ display: grid;
113
+ gap: 1rem;
114
+ }
115
+
116
+ .toolbar,
117
+ .account-row {
118
+ display: flex;
119
+ align-items: flex-start;
120
+ justify-content: space-between;
121
+ gap: 1rem;
122
+ }
123
+
124
+ h2,
125
+ h3,
126
+ p {
127
+ margin: 0;
128
+ }
129
+
130
+ h2 {
131
+ font-size: var(--smrt-typography-title-medium-size, 1.1rem);
132
+ color: var(--smrt-color-on-surface, #111827);
133
+ }
134
+
135
+ h3 {
136
+ font-size: var(--smrt-typography-title-medium-size, 0.95rem);
137
+ color: var(--smrt-color-on-surface, #111827);
138
+ }
139
+
140
+ p {
141
+ color: var(--smrt-color-on-surface-variant, #6b7280);
142
+ font-size: var(--smrt-typography-body-medium-size, 0.85rem);
143
+ }
144
+
145
+ .connect-row {
146
+ display: flex;
147
+ flex-wrap: wrap;
148
+ gap: 0.5rem;
149
+ }
150
+
151
+ .account-list {
152
+ display: grid;
153
+ gap: 0.75rem;
154
+ }
155
+
156
+ .account-row {
157
+ border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
158
+ border-radius: var(--smrt-radius-md, 8px);
159
+ background: var(--smrt-color-surface, #fff);
160
+ padding: 1rem;
161
+ }
162
+
163
+ .account-title {
164
+ display: flex;
165
+ align-items: center;
166
+ gap: 0.5rem;
167
+ }
168
+
169
+ .account-title span,
170
+ .status {
171
+ border-radius: var(--smrt-radius-full, 9999px);
172
+ background: var(--smrt-color-surface-container, #f3f4f6);
173
+ color: var(--smrt-color-on-surface-variant, #4b5563);
174
+ font-size: var(--smrt-typography-label-medium-size, 0.75rem);
175
+ padding: 0.15rem 0.5rem;
176
+ text-transform: capitalize;
177
+ }
178
+
179
+ .status.attention,
180
+ .warning {
181
+ color: var(--smrt-color-error, #b91c1c);
182
+ }
183
+
184
+ .account-actions {
185
+ display: flex;
186
+ align-items: center;
187
+ justify-content: flex-end;
188
+ flex-wrap: wrap;
189
+ gap: 0.5rem;
190
+ }
191
+
192
+ button,
193
+ a {
194
+ border: 1px solid var(--smrt-color-outline-variant, #d1d5db);
195
+ border-radius: var(--smrt-radius-md, 8px);
196
+ background: var(--smrt-color-surface, #fff);
197
+ color: var(--smrt-color-on-surface, #111827);
198
+ cursor: pointer;
199
+ font: inherit;
200
+ font-size: var(--smrt-typography-label-large-size, 0.82rem);
201
+ line-height: 1;
202
+ padding: 0.55rem 0.7rem;
203
+ text-decoration: none;
204
+ }
205
+
206
+ button:disabled {
207
+ cursor: not-allowed;
208
+ opacity: 0.55;
209
+ }
210
+
211
+ .connect-button {
212
+ background: var(--smrt-color-primary, #2563eb);
213
+ border-color: var(--smrt-color-primary, #2563eb);
214
+ color: var(--smrt-color-on-primary, #fff);
215
+ }
216
+
217
+ .secondary {
218
+ background: var(--smrt-color-surface-container, #f3f4f6);
219
+ }
220
+
221
+ .danger {
222
+ color: var(--smrt-color-error, #b91c1c);
223
+ }
224
+
225
+ .empty {
226
+ border: 1px dashed var(--smrt-color-outline-variant, #d1d5db);
227
+ border-radius: var(--smrt-radius-md, 8px);
228
+ color: var(--smrt-color-on-surface-variant, #6b7280);
229
+ padding: 1.25rem;
230
+ text-align: center;
231
+ }
232
+
233
+ @media (max-width: 720px) {
234
+ .toolbar,
235
+ .account-row {
236
+ display: grid;
237
+ }
238
+
239
+ .account-actions {
240
+ justify-content: flex-start;
241
+ }
242
+ }
243
+ </style>
@@ -0,0 +1,15 @@
1
+ import type { SocialPlatformType } from '../../social-account.js';
2
+ import type { SocialAccountSettingsItem } from '../types.js';
3
+ type $$ComponentProps = {
4
+ accounts?: SocialAccountSettingsItem[];
5
+ loading?: boolean;
6
+ readonly?: boolean;
7
+ connectHrefs?: Partial<Record<SocialPlatformType, string>>;
8
+ onRefresh?: () => void | Promise<void>;
9
+ onTest?: (account: SocialAccountSettingsItem) => void | Promise<void>;
10
+ onDeactivate?: (account: SocialAccountSettingsItem) => void | Promise<void>;
11
+ };
12
+ declare const SocialAccountSettings: import("svelte").Component<$$ComponentProps, {}, "">;
13
+ type SocialAccountSettings = ReturnType<typeof SocialAccountSettings>;
14
+ export default SocialAccountSettings;
15
+ //# sourceMappingURL=SocialAccountSettings.svelte.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SocialAccountSettings.svelte.d.ts","sourceRoot":"","sources":["../../../src/svelte/components/SocialAccountSettings.svelte.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAC;AAElE,OAAO,KAAK,EAAE,yBAAyB,EAAE,MAAM,aAAa,CAAC;AAE5D,KAAK,gBAAgB,GAAI;IACxB,QAAQ,CAAC,EAAE,yBAAyB,EAAE,CAAC;IACvC,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,YAAY,CAAC,EAAE,OAAO,CAAC,MAAM,CAAC,kBAAkB,EAAE,MAAM,CAAC,CAAC,CAAC;IAC3D,SAAS,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtE,YAAY,CAAC,EAAE,CAAC,OAAO,EAAE,yBAAyB,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CAC7E,CAAC;AA0GF,QAAA,MAAM,qBAAqB,sDAAwC,CAAC;AACpE,KAAK,qBAAqB,GAAG,UAAU,CAAC,OAAO,qBAAqB,CAAC,CAAC;AACtE,eAAe,qBAAqB,CAAC"}
@@ -0,0 +1,9 @@
1
+ export declare const M: {
2
+ readonly 'social.account_settings.heading': "social.account_settings.heading";
3
+ readonly 'social.account_settings.configured_one': "social.account_settings.configured_one";
4
+ readonly 'social.account_settings.configured_other': "social.account_settings.configured_other";
5
+ readonly 'social.account_settings.connect_aria': "social.account_settings.connect_aria";
6
+ readonly 'social.account_settings.loading': "social.account_settings.loading";
7
+ readonly 'social.account_settings.empty': "social.account_settings.empty";
8
+ };
9
+ //# sourceMappingURL=i18n.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"i18n.d.ts","sourceRoot":"","sources":["../../src/svelte/i18n.ts"],"names":[],"mappings":"AAMA,eAAO,MAAM,CAAC;;;;;;;CASZ,CAAC"}
@@ -0,0 +1,15 @@
1
+ /**
2
+ * smrt-social UI message catalog (S13 #1418).
3
+ * Keys: `social.<component>.<descriptor>`.
4
+ */
5
+ import { defineMessages } from '@happyvertical/smrt-ui/i18n';
6
+ export const M = defineMessages({
7
+ 'social.account_settings.heading': 'Social Accounts',
8
+ // Separate singular/plural messages so each locale supplies its own forms
9
+ // (an English `{plural}` suffix does not translate). Selected by count below.
10
+ 'social.account_settings.configured_one': '{count} account configured',
11
+ 'social.account_settings.configured_other': '{count} accounts configured',
12
+ 'social.account_settings.connect_aria': 'Connect social account',
13
+ 'social.account_settings.loading': 'Loading accounts...',
14
+ 'social.account_settings.empty': 'No social accounts are configured yet.',
15
+ });
@@ -0,0 +1,6 @@
1
+ import type { ComponentProps } from 'svelte';
2
+ import SocialAccountSettings from './components/SocialAccountSettings.svelte';
3
+ export { SocialAccountSettings };
4
+ export type SocialAccountSettingsProps = ComponentProps<typeof SocialAccountSettings>;
5
+ export type { SocialAccountSettingsItem } from './types.js';
6
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/svelte/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,QAAQ,CAAC;AAE7C,OAAO,qBAAqB,MAAM,2CAA2C,CAAC;AAE9E,OAAO,EAAE,qBAAqB,EAAE,CAAC;AACjC,MAAM,MAAM,0BAA0B,GAAG,cAAc,CACrD,OAAO,qBAAqB,CAC7B,CAAC;AACF,YAAY,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC"}
@@ -0,0 +1,2 @@
1
+ import SocialAccountSettings from './components/SocialAccountSettings.svelte';
2
+ export { SocialAccountSettings };
@@ -0,0 +1,16 @@
1
+ import type { AccountStatus, PublishMode, SocialPlatformType } from '../social-account.js';
2
+ export interface SocialAccountSettingsItem {
3
+ id: string;
4
+ name: string;
5
+ platform: SocialPlatformType;
6
+ platformUsername?: string | null;
7
+ platformUrl?: string | null;
8
+ isActive: boolean;
9
+ status: AccountStatus;
10
+ needsAttention?: boolean;
11
+ missingPermissions?: string[];
12
+ publishMode?: PublishMode;
13
+ publicPublishingAllowed?: boolean;
14
+ tokenExpiresAt?: Date | string | null;
15
+ }
16
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/svelte/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,aAAa,EACb,WAAW,EACX,kBAAkB,EACnB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,kBAAkB,CAAC;IAC7B,gBAAgB,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,OAAO,CAAC;IAClB,MAAM,EAAE,aAAa,CAAC;IACtB,cAAc,CAAC,EAAE,OAAO,CAAC;IACzB,kBAAkB,CAAC,EAAE,MAAM,EAAE,CAAC;IAC9B,WAAW,CAAC,EAAE,WAAW,CAAC;IAC1B,uBAAuB,CAAC,EAAE,OAAO,CAAC;IAClC,cAAc,CAAC,EAAE,IAAI,GAAG,MAAM,GAAG,IAAI,CAAC;CACvC"}
@@ -0,0 +1 @@
1
+ export {};
package/package.json ADDED
@@ -0,0 +1,88 @@
1
+ {
2
+ "name": "@happyvertical/smrt-social",
3
+ "version": "0.30.0",
4
+ "type": "module",
5
+ "description": "Social media account management for multi-platform publishing in the SMRT ecosystem",
6
+ "author": "HappyVertical",
7
+ "license": "MIT",
8
+ "main": "./dist/index.js",
9
+ "types": "./dist/index.d.ts",
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ },
15
+ "./server": {
16
+ "types": "./dist/server.d.ts",
17
+ "import": "./dist/server.js"
18
+ },
19
+ "./svelte": {
20
+ "types": "./dist/svelte/index.d.ts",
21
+ "svelte": "./dist/svelte/index.js",
22
+ "import": "./dist/svelte/index.js"
23
+ },
24
+ "./manifest": "./dist/manifest.json",
25
+ "./manifest.json": "./dist/manifest.json"
26
+ },
27
+ "peerDependencies": {
28
+ "svelte": "^5.0.0"
29
+ },
30
+ "peerDependenciesMeta": {
31
+ "svelte": {
32
+ "optional": true
33
+ }
34
+ },
35
+ "files": [
36
+ "dist",
37
+ "CLAUDE.md",
38
+ "AGENTS.md"
39
+ ],
40
+ "dependencies": {
41
+ "@happyvertical/utils": "^0.74.7",
42
+ "@happyvertical/smrt-config": "0.30.0",
43
+ "@happyvertical/smrt-content": "0.30.0",
44
+ "@happyvertical/smrt-core": "0.30.0",
45
+ "@happyvertical/smrt-secrets": "0.30.0",
46
+ "@happyvertical/smrt-tenancy": "0.30.0",
47
+ "@happyvertical/smrt-ui": "0.30.0",
48
+ "@happyvertical/smrt-video": "0.30.0"
49
+ },
50
+ "devDependencies": {
51
+ "@happyvertical/logger": "^0.74.7",
52
+ "@happyvertical/sql": "^0.74.7",
53
+ "@sveltejs/package": "^2.5.7",
54
+ "@types/node": "25.0.9",
55
+ "svelte": "^5.0.0",
56
+ "svelte-check": "^4.3.5",
57
+ "typescript": "^5.9.3",
58
+ "vite": "^7.3.1",
59
+ "vitest": "^4.0.17",
60
+ "@happyvertical/smrt-vitest": "0.30.0"
61
+ },
62
+ "keywords": [
63
+ "social",
64
+ "publishing",
65
+ "youtube",
66
+ "threads",
67
+ "bluesky",
68
+ "smrt",
69
+ "framework"
70
+ ],
71
+ "publishConfig": {
72
+ "registry": "https://registry.npmjs.org",
73
+ "access": "public"
74
+ },
75
+ "repository": {
76
+ "type": "git",
77
+ "url": "https://github.com/happyvertical/smrt.git",
78
+ "directory": "packages/social"
79
+ },
80
+ "scripts": {
81
+ "build": "vite build --mode library && svelte-package -i src/svelte -o dist/svelte --tsconfig tsconfig.svelte.json",
82
+ "build:watch": "vite build --mode library --watch",
83
+ "check": "pnpm exec svelte-check --tsconfig ./tsconfig.svelte.json",
84
+ "typecheck": "tsc --noEmit && node ../../scripts/svelte-check-a11y.mjs --tsconfig ./tsconfig.svelte.json",
85
+ "test": "vitest run",
86
+ "test:watch": "vitest"
87
+ }
88
+ }