@jiggai/kitchen-plugin-marketing 0.2.8 → 0.2.10

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.
@@ -53,59 +53,59 @@
53
53
  fontSize: "0.7rem",
54
54
  fontWeight: 600,
55
55
  color: "white"
56
+ }),
57
+ capPill: (active) => ({
58
+ display: "inline-block",
59
+ background: active ? "rgba(99,179,237,0.15)" : "rgba(255,255,255,0.03)",
60
+ border: `1px solid ${active ? "rgba(99,179,237,0.3)" : "var(--ck-border-subtle)"}`,
61
+ borderRadius: "999px",
62
+ padding: "0.1rem 0.4rem",
63
+ fontSize: "0.6rem",
64
+ color: active ? "rgba(210,235,255,0.9)" : "var(--ck-text-tertiary)"
56
65
  })
57
66
  };
58
- const PLATFORM_ICONS = {
59
- x: "\u{1D54F}",
60
- twitter: "\u{1D54F}",
61
- instagram: "\u{1F4F7}",
62
- linkedin: "\u{1F4BC}",
63
- facebook: "\u{1F4D8}",
64
- youtube: "\u25B6\uFE0F",
65
- tiktok: "\u{1F3B5}",
66
- bluesky: "\u{1F98B}",
67
- mastodon: "\u{1F418}",
68
- reddit: "\u{1F916}",
69
- discord: "\u{1F4AC}",
70
- telegram: "\u2708\uFE0F",
71
- pinterest: "\u{1F4CC}",
72
- threads: "\u{1F9F5}",
73
- medium: "\u270D\uFE0F",
74
- wordpress: "\u{1F4DD}"
75
- };
76
- const TYPE_COLORS = {
67
+ const BACKEND_COLORS = {
77
68
  postiz: "rgba(99,179,237,0.7)",
78
69
  gateway: "rgba(134,239,172,0.7)",
79
- skill: "rgba(251,191,36,0.7)",
80
- manual: "rgba(167,139,250,0.7)"
70
+ direct: "rgba(251,191,36,0.7)",
71
+ none: "rgba(100,100,100,0.5)"
72
+ };
73
+ const BACKEND_LABELS = {
74
+ postiz: "Postiz",
75
+ gateway: "OpenClaw",
76
+ direct: "Direct API",
77
+ none: "Not connected"
81
78
  };
82
79
  function Accounts(props) {
83
80
  const teamId = String(props?.teamId || "default");
84
81
  const apiBase = useMemo(() => `/api/plugins/marketing`, []);
85
- const [providers, setProviders] = useState([]);
82
+ const [drivers, setDrivers] = useState([]);
86
83
  const [manualAccounts, setManualAccounts] = useState([]);
87
84
  const [loading, setLoading] = useState(true);
88
- const [detecting, setDetecting] = useState(false);
89
85
  const [error, setError] = useState(null);
90
86
  const [postizKey, setPostizKey] = useState("");
91
87
  const [postizUrl, setPostizUrl] = useState("https://api.postiz.com/public/v1");
92
88
  const [showPostizSetup, setShowPostizSetup] = useState(false);
93
89
  const [showManual, setShowManual] = useState(false);
94
- const [manPlatform, setManPlatform] = useState("twitter");
90
+ const [manPlatform, setManPlatform] = useState("x");
95
91
  const [manName, setManName] = useState("");
96
92
  const [manUser, setManUser] = useState("");
97
93
  const [manToken, setManToken] = useState("");
98
94
  const [saving, setSaving] = useState(false);
99
- useEffect(() => {
95
+ const getStoredPostiz = () => {
100
96
  try {
101
97
  const stored = localStorage.getItem(`ck-postiz-${teamId}`);
102
- if (stored) {
103
- const parsed = JSON.parse(stored);
104
- setPostizKey(parsed.apiKey || "");
105
- setPostizUrl(parsed.baseUrl || "https://api.postiz.com/public/v1");
106
- }
98
+ if (stored) return JSON.parse(stored);
107
99
  } catch {
108
100
  }
101
+ return null;
102
+ };
103
+ useEffect(() => {
104
+ const stored = getStoredPostiz();
105
+ if (stored) {
106
+ setPostizKey(stored.apiKey || "");
107
+ setPostizUrl(stored.baseUrl || "https://api.postiz.com/public/v1");
108
+ }
109
109
  }, [teamId]);
110
110
  const savePostizConfig = () => {
111
111
  try {
@@ -113,18 +113,9 @@
113
113
  } catch {
114
114
  }
115
115
  setShowPostizSetup(false);
116
- void detectAll();
117
- };
118
- const getStoredPostiz = () => {
119
- try {
120
- const stored = localStorage.getItem(`ck-postiz-${teamId}`);
121
- if (stored) return JSON.parse(stored);
122
- } catch {
123
- }
124
- return null;
116
+ void loadDrivers();
125
117
  };
126
- const detectAll = async () => {
127
- setDetecting(true);
118
+ const loadDrivers = async () => {
128
119
  setError(null);
129
120
  try {
130
121
  const stored = getStoredPostiz();
@@ -135,13 +126,11 @@
135
126
  headers["x-postiz-api-key"] = key;
136
127
  headers["x-postiz-base-url"] = url;
137
128
  }
138
- const res = await fetch(`${apiBase}/providers?team=${encodeURIComponent(teamId)}`, { headers });
129
+ const res = await fetch(`${apiBase}/drivers?team=${encodeURIComponent(teamId)}`, { headers });
139
130
  const json = await res.json();
140
- setProviders(Array.isArray(json.providers) ? json.providers : []);
131
+ setDrivers(Array.isArray(json.drivers) ? json.drivers : []);
141
132
  } catch (e) {
142
- setError(e?.message || "Failed to detect providers");
143
- } finally {
144
- setDetecting(false);
133
+ setError(e?.message || "Failed to load drivers");
145
134
  }
146
135
  };
147
136
  const loadManual = async () => {
@@ -154,7 +143,7 @@
154
143
  };
155
144
  const refresh = async () => {
156
145
  setLoading(true);
157
- await Promise.all([detectAll(), loadManual()]);
146
+ await Promise.all([loadDrivers(), loadManual()]);
158
147
  setLoading(false);
159
148
  };
160
149
  useEffect(() => {
@@ -179,43 +168,17 @@
179
168
  setManName("");
180
169
  setManUser("");
181
170
  setManToken("");
182
- await loadManual();
171
+ await Promise.all([loadDrivers(), loadManual()]);
183
172
  } catch (e) {
184
173
  setError(e?.message || "Failed to connect");
185
174
  } finally {
186
175
  setSaving(false);
187
176
  }
188
177
  };
189
- const allProviders = useMemo(() => {
190
- const combined = [...providers];
191
- for (const ma of manualAccounts) {
192
- combined.push({
193
- id: `manual:${ma.id}`,
194
- type: "manual",
195
- platform: ma.platform,
196
- displayName: ma.displayName,
197
- username: ma.username,
198
- isActive: ma.isActive,
199
- capabilities: ["post"]
200
- });
201
- }
202
- return combined;
203
- }, [providers, manualAccounts]);
204
- const grouped = useMemo(() => {
205
- const g = {};
206
- for (const p of allProviders) {
207
- const key = p.type;
208
- if (!g[key]) g[key] = [];
209
- g[key].push(p);
210
- }
211
- return g;
212
- }, [allProviders]);
213
- const typeLabels = {
214
- postiz: "Postiz",
215
- gateway: "OpenClaw Channels",
216
- skill: "Skills",
217
- manual: "Manual"
218
- };
178
+ const connectedDrivers = useMemo(() => drivers.filter((d) => d.connected), [drivers]);
179
+ const disconnectedDrivers = useMemo(() => drivers.filter((d) => !d.connected), [drivers]);
180
+ const connectedCount = connectedDrivers.length;
181
+ const totalCount = drivers.length;
219
182
  return h(
220
183
  "div",
221
184
  { className: "space-y-3" },
@@ -229,11 +192,11 @@
229
192
  h(
230
193
  "div",
231
194
  null,
232
- h("div", { className: "text-sm font-medium", style: t.text }, "Connected Accounts"),
195
+ h("div", { className: "text-sm font-medium", style: t.text }, "Platform Drivers"),
233
196
  h(
234
197
  "div",
235
198
  { className: "mt-1 text-xs", style: t.faint },
236
- `${allProviders.length} provider${allProviders.length !== 1 ? "s" : ""} detected`
199
+ `${connectedCount}/${totalCount} platforms connected`
237
200
  )
238
201
  ),
239
202
  h(
@@ -241,15 +204,15 @@
241
204
  { className: "flex flex-wrap gap-2" },
242
205
  h(
243
206
  "button",
244
- { type: "button", onClick: () => void refresh(), style: t.btnGhost, disabled: detecting },
245
- detecting ? "Detecting\u2026" : "\u21BB Refresh"
207
+ { type: "button", onClick: () => void refresh(), style: t.btnGhost, disabled: loading },
208
+ loading ? "Loading\u2026" : "\u21BB Refresh"
246
209
  ),
247
210
  h(
248
211
  "button",
249
212
  { type: "button", onClick: () => setShowPostizSetup(!showPostizSetup), style: t.btnGhost },
250
213
  postizKey ? "\u2699 Postiz" : "+ Postiz"
251
214
  ),
252
- h("button", { type: "button", onClick: () => setShowManual(!showManual), style: t.btnGhost }, "+ Manual")
215
+ h("button", { type: "button", onClick: () => setShowManual(!showManual), style: t.btnGhost }, "+ Direct token")
253
216
  )
254
217
  ),
255
218
  error && h("div", { className: "mt-2 text-xs", style: { color: "rgba(248,113,113,0.95)" } }, error)
@@ -262,7 +225,7 @@
262
225
  h(
263
226
  "div",
264
227
  { className: "text-xs mb-3", style: t.faint },
265
- "Connect Postiz to manage social accounts via their platform. Get your API key from Postiz Settings \u2192 Developers \u2192 Public API."
228
+ "Postiz manages OAuth connections to social platforms. Get your API key from Postiz Settings \u2192 Developers \u2192 Public API."
266
229
  ),
267
230
  h(
268
231
  "div",
@@ -295,15 +258,15 @@
295
258
  "div",
296
259
  { className: "mt-3 flex gap-2" },
297
260
  h("button", { type: "button", onClick: () => setShowPostizSetup(false), style: t.btnGhost }, "Cancel"),
298
- h("button", { type: "button", onClick: savePostizConfig, style: t.btnPrimary }, postizKey ? "Save & Detect" : "Save")
261
+ h("button", { type: "button", onClick: savePostizConfig, style: t.btnPrimary }, "Save & Detect")
299
262
  )
300
263
  ),
301
- // ---- Manual account form ----
264
+ // ---- Manual token form ----
302
265
  showManual && h(
303
266
  "div",
304
267
  { style: t.card },
305
- h("div", { className: "text-sm font-medium mb-2", style: t.text }, "Add manual account"),
306
- h("div", { className: "text-xs mb-3", style: t.faint }, "For direct API access without Postiz. You provide the token."),
268
+ h("div", { className: "text-sm font-medium mb-2", style: t.text }, "Add direct API token"),
269
+ h("div", { className: "text-xs mb-3", style: t.faint }, "For platforms where you have your own API credentials. Token is encrypted at rest."),
307
270
  h(
308
271
  "div",
309
272
  { className: "grid grid-cols-1 gap-2 sm:grid-cols-2" },
@@ -314,18 +277,14 @@
314
277
  h(
315
278
  "select",
316
279
  { value: manPlatform, onChange: (e) => setManPlatform(e.target.value), style: t.input },
317
- h("option", { value: "twitter" }, "Twitter / X"),
318
- h("option", { value: "instagram" }, "Instagram"),
319
- h("option", { value: "linkedin" }, "LinkedIn"),
320
- h("option", { value: "bluesky" }, "Bluesky"),
321
- h("option", { value: "mastodon" }, "Mastodon")
280
+ ...drivers.map((d) => h("option", { key: d.platform, value: d.platform }, d.label))
322
281
  )
323
282
  ),
324
283
  h(
325
284
  "div",
326
285
  null,
327
286
  h("div", { className: "text-xs font-medium mb-1", style: t.faint }, "Display name"),
328
- h("input", { value: manName, onChange: (e) => setManName(e.target.value), placeholder: "My X account", style: t.input })
287
+ h("input", { value: manName, onChange: (e) => setManName(e.target.value), placeholder: "My account", style: t.input })
329
288
  ),
330
289
  h(
331
290
  "div",
@@ -347,87 +306,168 @@
347
306
  h("button", { type: "button", onClick: () => void onManualConnect(), style: t.btnPrimary, disabled: saving }, saving ? "Saving\u2026" : "Connect")
348
307
  )
349
308
  ),
350
- // ---- Loading ----
351
- loading && h(
352
- "div",
353
- { style: t.card },
354
- h("div", { className: "py-6 text-center text-sm", style: t.faint }, "Detecting providers\u2026")
355
- ),
356
- // ---- Provider groups ----
357
- !loading && allProviders.length === 0 && h(
309
+ // ---- Connected platforms ----
310
+ connectedDrivers.length > 0 && h(
358
311
  "div",
359
312
  { style: t.card },
360
313
  h(
361
314
  "div",
362
- { className: "py-6 text-center space-y-2" },
363
- h("div", { className: "text-sm", style: t.faint }, "No providers detected"),
364
- h(
365
- "div",
366
- { className: "text-xs", style: t.faint },
367
- "Connect Postiz for full social media management, or add accounts manually."
315
+ { className: "flex items-center gap-2 mb-3" },
316
+ h("div", { className: "text-sm font-medium", style: t.text }, "Connected"),
317
+ h("span", { style: t.badge("rgba(74,222,128,0.7)") }, `${connectedCount}`)
318
+ ),
319
+ h(
320
+ "div",
321
+ { className: "space-y-2" },
322
+ ...connectedDrivers.map(
323
+ (d) => h(
324
+ "div",
325
+ { key: d.platform, style: { ...t.card, padding: "0.75rem", display: "flex", alignItems: "center", gap: "0.75rem" } },
326
+ d.avatar ? h("img", { src: d.avatar, alt: "", style: { width: 36, height: 36, borderRadius: "50%", objectFit: "cover" } }) : h("div", {
327
+ style: {
328
+ width: 36,
329
+ height: 36,
330
+ borderRadius: "50%",
331
+ background: "rgba(255,255,255,0.06)",
332
+ display: "flex",
333
+ alignItems: "center",
334
+ justifyContent: "center",
335
+ fontSize: "1.1rem"
336
+ }
337
+ }, d.icon),
338
+ h(
339
+ "div",
340
+ { style: { flex: 1, minWidth: 0 } },
341
+ h("div", { className: "text-sm font-medium", style: t.text }, d.displayName),
342
+ h(
343
+ "div",
344
+ { className: "text-xs", style: t.faint },
345
+ [d.username, d.platform].filter(Boolean).join(" \xB7 ")
346
+ )
347
+ ),
348
+ h(
349
+ "div",
350
+ { className: "flex items-center gap-2 shrink-0 flex-wrap" },
351
+ h("span", { style: t.badge(BACKEND_COLORS[d.backend] || BACKEND_COLORS.none) }, BACKEND_LABELS[d.backend] || d.backend),
352
+ d.capabilities.canPost && h("span", { style: t.capPill(true) }, "post"),
353
+ d.capabilities.canSchedule && h("span", { style: t.capPill(true) }, "schedule"),
354
+ d.capabilities.canUploadMedia && h("span", { style: t.capPill(true) }, "media"),
355
+ d.capabilities.maxLength && h("span", { style: t.capPill(false) }, `${d.capabilities.maxLength} chars`),
356
+ h("div", {
357
+ style: {
358
+ width: 8,
359
+ height: 8,
360
+ borderRadius: "50%",
361
+ background: "rgba(74,222,128,0.8)"
362
+ }
363
+ })
364
+ )
365
+ )
368
366
  )
369
367
  )
370
368
  ),
371
- !loading && Object.entries(grouped).map(
372
- ([type, items]) => h(
369
+ // ---- Disconnected platforms ----
370
+ disconnectedDrivers.length > 0 && h(
371
+ "div",
372
+ { style: t.card },
373
+ h(
373
374
  "div",
374
- { key: type, style: t.card },
375
- h(
376
- "div",
377
- { className: "flex items-center gap-2 mb-3" },
378
- h("div", { className: "text-sm font-medium", style: t.text }, typeLabels[type] || type),
379
- h("span", { style: t.badge(TYPE_COLORS[type] || "rgba(100,100,100,0.6)") }, `${items.length}`)
380
- ),
381
- h(
382
- "div",
383
- { className: "space-y-2" },
384
- ...items.map(
385
- (p) => h(
375
+ { className: "flex items-center gap-2 mb-3" },
376
+ h("div", { className: "text-sm font-medium", style: t.text }, "Available"),
377
+ h("span", { style: t.badge("rgba(100,100,100,0.5)") }, `${disconnectedDrivers.length}`)
378
+ ),
379
+ h(
380
+ "div",
381
+ { className: "text-xs mb-3", style: t.faint },
382
+ "Connect these via Postiz or by adding a direct API token above."
383
+ ),
384
+ h(
385
+ "div",
386
+ { className: "space-y-2" },
387
+ ...disconnectedDrivers.map(
388
+ (d) => h(
389
+ "div",
390
+ { key: d.platform, style: { ...t.card, padding: "0.75rem", display: "flex", alignItems: "center", gap: "0.75rem", opacity: 0.6 } },
391
+ h("div", {
392
+ style: {
393
+ width: 36,
394
+ height: 36,
395
+ borderRadius: "50%",
396
+ background: "rgba(255,255,255,0.04)",
397
+ display: "flex",
398
+ alignItems: "center",
399
+ justifyContent: "center",
400
+ fontSize: "1.1rem"
401
+ }
402
+ }, d.icon),
403
+ h(
386
404
  "div",
387
- { key: p.id, style: { ...t.card, padding: "0.75rem", display: "flex", alignItems: "center", gap: "0.75rem" } },
388
- // Avatar or platform icon
389
- p.avatar ? h("img", { src: p.avatar, alt: "", style: { width: 32, height: 32, borderRadius: "50%", objectFit: "cover" } }) : h("div", {
405
+ { style: { flex: 1, minWidth: 0 } },
406
+ h("div", { className: "text-sm font-medium", style: t.text }, d.label),
407
+ h("div", { className: "text-xs", style: t.faint }, "Not connected")
408
+ ),
409
+ h(
410
+ "div",
411
+ { className: "flex items-center gap-2 shrink-0" },
412
+ d.capabilities.maxLength && h("span", { style: t.capPill(false) }, `${d.capabilities.maxLength} chars`),
413
+ h("div", {
390
414
  style: {
391
- width: 32,
392
- height: 32,
415
+ width: 8,
416
+ height: 8,
393
417
  borderRadius: "50%",
394
- background: "rgba(255,255,255,0.06)",
395
- display: "flex",
396
- alignItems: "center",
397
- justifyContent: "center",
398
- fontSize: "1rem"
418
+ background: "rgba(100,100,100,0.4)"
399
419
  }
400
- }, PLATFORM_ICONS[p.platform] || "\u{1F517}"),
401
- // Info
402
- h(
403
- "div",
404
- { style: { flex: 1, minWidth: 0 } },
405
- h("div", { className: "text-sm font-medium", style: t.text }, p.displayName),
406
- h(
407
- "div",
408
- { className: "text-xs", style: t.faint },
409
- [p.platform, p.username].filter(Boolean).join(" \xB7 ")
410
- )
411
- ),
412
- // Status + capabilities
420
+ })
421
+ )
422
+ )
423
+ )
424
+ )
425
+ ),
426
+ // ---- Manual accounts (if any exist beyond drivers) ----
427
+ manualAccounts.length > 0 && h(
428
+ "div",
429
+ { style: t.card },
430
+ h(
431
+ "div",
432
+ { className: "flex items-center gap-2 mb-3" },
433
+ h("div", { className: "text-sm font-medium", style: t.text }, "Stored tokens"),
434
+ h("span", { style: t.badge("rgba(251,191,36,0.7)") }, `${manualAccounts.length}`)
435
+ ),
436
+ h("div", { className: "text-xs mb-3", style: t.faint }, "Tokens stored locally, encrypted at rest. These feed into the direct backend for their platform driver."),
437
+ h(
438
+ "div",
439
+ { className: "space-y-2" },
440
+ ...manualAccounts.map(
441
+ (a) => h(
442
+ "div",
443
+ { key: a.id, style: { ...t.card, padding: "0.75rem", display: "flex", alignItems: "center", gap: "0.75rem" } },
444
+ h(
445
+ "div",
446
+ { style: { flex: 1, minWidth: 0 } },
447
+ h("div", { className: "text-sm", style: t.text }, a.displayName),
413
448
  h(
414
449
  "div",
415
- { className: "flex items-center gap-2 shrink-0" },
416
- p.capabilities?.includes("schedule") && h("span", { className: "text-xs", style: t.faint }, "\u23F1"),
417
- p.capabilities?.includes("post") && h("span", { className: "text-xs", style: t.faint }, "\u{1F4E4}"),
418
- h("div", {
419
- style: {
420
- width: 8,
421
- height: 8,
422
- borderRadius: "50%",
423
- background: p.isActive ? "rgba(74,222,128,0.8)" : "rgba(248,113,113,0.6)"
424
- }
425
- })
450
+ { className: "text-xs", style: t.faint },
451
+ [a.platform, a.username].filter(Boolean).join(" \xB7 ")
426
452
  )
427
- )
453
+ ),
454
+ h("div", {
455
+ style: {
456
+ width: 8,
457
+ height: 8,
458
+ borderRadius: "50%",
459
+ background: a.isActive ? "rgba(74,222,128,0.8)" : "rgba(248,113,113,0.6)"
460
+ }
461
+ })
428
462
  )
429
463
  )
430
464
  )
465
+ ),
466
+ // ---- Loading ----
467
+ loading && drivers.length === 0 && h(
468
+ "div",
469
+ { style: t.card },
470
+ h("div", { className: "py-6 text-center text-sm", style: t.faint }, "Detecting platform drivers\u2026")
431
471
  )
432
472
  );
433
473
  }