@jiggai/kitchen-plugin-marketing 0.2.9 → 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.
- package/dist/tabs/accounts.js +197 -157
- package/dist/tabs/content-library.js +226 -92
- package/package.json +1 -1
package/dist/tabs/accounts.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
80
|
-
|
|
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 [
|
|
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("
|
|
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
|
-
|
|
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
|
|
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
|
|
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}/
|
|
129
|
+
const res = await fetch(`${apiBase}/drivers?team=${encodeURIComponent(teamId)}`, { headers });
|
|
139
130
|
const json = await res.json();
|
|
140
|
-
|
|
131
|
+
setDrivers(Array.isArray(json.drivers) ? json.drivers : []);
|
|
141
132
|
} catch (e) {
|
|
142
|
-
setError(e?.message || "Failed to
|
|
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([
|
|
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
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
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 }, "
|
|
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
|
-
`${
|
|
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:
|
|
245
|
-
|
|
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 }, "+
|
|
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
|
-
"
|
|
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 },
|
|
261
|
+
h("button", { type: "button", onClick: savePostizConfig, style: t.btnPrimary }, "Save & Detect")
|
|
299
262
|
)
|
|
300
263
|
),
|
|
301
|
-
// ---- Manual
|
|
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
|
|
306
|
-
h("div", { className: "text-xs mb-3", style: t.faint }, "For
|
|
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:
|
|
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
|
|
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
|
-
// ----
|
|
351
|
-
|
|
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: "
|
|
363
|
-
h("div", { className: "text-sm", style: t.
|
|
364
|
-
h(
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
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
|
-
|
|
372
|
-
|
|
369
|
+
// ---- Disconnected platforms ----
|
|
370
|
+
disconnectedDrivers.length > 0 && h(
|
|
371
|
+
"div",
|
|
372
|
+
{ style: t.card },
|
|
373
|
+
h(
|
|
373
374
|
"div",
|
|
374
|
-
{
|
|
375
|
-
h(
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
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
|
-
{
|
|
388
|
-
|
|
389
|
-
|
|
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:
|
|
392
|
-
height:
|
|
415
|
+
width: 8,
|
|
416
|
+
height: 8,
|
|
393
417
|
borderRadius: "50%",
|
|
394
|
-
background: "rgba(
|
|
395
|
-
display: "flex",
|
|
396
|
-
alignItems: "center",
|
|
397
|
-
justifyContent: "center",
|
|
398
|
-
fontSize: "1rem"
|
|
418
|
+
background: "rgba(100,100,100,0.4)"
|
|
399
419
|
}
|
|
400
|
-
}
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
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: "
|
|
416
|
-
|
|
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
|
}
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
const useMemo = R.useMemo;
|
|
9
9
|
const useState = R.useState;
|
|
10
10
|
const useCallback = R.useCallback;
|
|
11
|
+
const useRef = R.useRef;
|
|
11
12
|
const t = {
|
|
12
13
|
text: { color: "var(--ck-text-primary)" },
|
|
13
14
|
muted: { color: "var(--ck-text-secondary)" },
|
|
@@ -53,15 +54,16 @@
|
|
|
53
54
|
fontWeight: 700,
|
|
54
55
|
cursor: "pointer"
|
|
55
56
|
},
|
|
56
|
-
pill: (active) => ({
|
|
57
|
+
pill: (active, connected) => ({
|
|
57
58
|
background: active ? "rgba(99,179,237,0.16)" : "rgba(255,255,255,0.03)",
|
|
58
59
|
border: `1px solid ${active ? "rgba(99,179,237,0.45)" : "var(--ck-border-subtle)"}`,
|
|
59
60
|
borderRadius: "999px",
|
|
60
61
|
padding: "0.25rem 0.55rem",
|
|
61
62
|
fontSize: "0.8rem",
|
|
62
|
-
color: active ? "rgba(210,235,255,0.95)" : "var(--ck-text-secondary)",
|
|
63
|
-
cursor: "pointer",
|
|
64
|
-
userSelect: "none"
|
|
63
|
+
color: active ? "rgba(210,235,255,0.95)" : connected ? "var(--ck-text-secondary)" : "var(--ck-text-tertiary)",
|
|
64
|
+
cursor: connected ? "pointer" : "default",
|
|
65
|
+
userSelect: "none",
|
|
66
|
+
opacity: connected ? 1 : 0.5
|
|
65
67
|
}),
|
|
66
68
|
statusBadge: (status) => {
|
|
67
69
|
const colors = {
|
|
@@ -79,21 +81,46 @@
|
|
|
79
81
|
fontWeight: 600,
|
|
80
82
|
color: "white"
|
|
81
83
|
};
|
|
82
|
-
}
|
|
84
|
+
},
|
|
85
|
+
backendBadge: (backend) => {
|
|
86
|
+
const colors = {
|
|
87
|
+
postiz: "rgba(99,179,237,0.5)",
|
|
88
|
+
gateway: "rgba(134,239,172,0.5)",
|
|
89
|
+
direct: "rgba(251,191,36,0.5)"
|
|
90
|
+
};
|
|
91
|
+
return {
|
|
92
|
+
display: "inline-block",
|
|
93
|
+
background: colors[backend] || "rgba(100,100,100,0.3)",
|
|
94
|
+
borderRadius: "999px",
|
|
95
|
+
padding: "0.05rem 0.35rem",
|
|
96
|
+
fontSize: "0.6rem",
|
|
97
|
+
fontWeight: 600,
|
|
98
|
+
color: "white",
|
|
99
|
+
marginLeft: "0.25rem"
|
|
100
|
+
};
|
|
101
|
+
},
|
|
102
|
+
charWarn: (pct) => ({
|
|
103
|
+
color: pct > 100 ? "rgba(248,113,113,0.95)" : pct > 90 ? "rgba(251,191,36,0.9)" : "var(--ck-text-tertiary)",
|
|
104
|
+
fontSize: "0.75rem"
|
|
105
|
+
})
|
|
83
106
|
};
|
|
84
107
|
function ContentLibrary(props) {
|
|
85
108
|
const teamId = String(props?.teamId || "default");
|
|
86
109
|
const apiBase = useMemo(() => `/api/plugins/marketing`, []);
|
|
110
|
+
const [drivers, setDrivers] = useState([]);
|
|
87
111
|
const [posts, setPosts] = useState([]);
|
|
88
|
-
const [providers, setProviders] = useState([]);
|
|
89
112
|
const [loading, setLoading] = useState(true);
|
|
90
113
|
const [saving, setSaving] = useState(false);
|
|
91
114
|
const [publishing, setPublishing] = useState(false);
|
|
92
115
|
const [error, setError] = useState(null);
|
|
93
116
|
const [success, setSuccess] = useState(null);
|
|
117
|
+
const [filterStatus, setFilterStatus] = useState("all");
|
|
94
118
|
const [content, setContent] = useState("");
|
|
95
|
-
const [
|
|
119
|
+
const [selectedPlatforms, setSelectedPlatforms] = useState([]);
|
|
96
120
|
const [scheduledAt, setScheduledAt] = useState("");
|
|
121
|
+
const [mediaUrl, setMediaUrl] = useState("");
|
|
122
|
+
const [showMedia, setShowMedia] = useState(false);
|
|
123
|
+
const successTimeout = useRef(null);
|
|
97
124
|
const postizHeaders = useMemo(() => {
|
|
98
125
|
try {
|
|
99
126
|
const stored = localStorage.getItem(`ck-postiz-${teamId}`);
|
|
@@ -110,44 +137,61 @@
|
|
|
110
137
|
}
|
|
111
138
|
return {};
|
|
112
139
|
}, [teamId]);
|
|
113
|
-
const
|
|
140
|
+
const loadDrivers = useCallback(async () => {
|
|
114
141
|
try {
|
|
115
|
-
const res = await fetch(`${apiBase}/
|
|
142
|
+
const res = await fetch(`${apiBase}/drivers?team=${encodeURIComponent(teamId)}`, { headers: postizHeaders });
|
|
116
143
|
const json = await res.json();
|
|
117
|
-
|
|
144
|
+
setDrivers(Array.isArray(json.drivers) ? json.drivers : []);
|
|
118
145
|
} catch {
|
|
119
146
|
}
|
|
120
|
-
}, [apiBase, teamId]);
|
|
121
|
-
const
|
|
147
|
+
}, [apiBase, teamId, postizHeaders]);
|
|
148
|
+
const loadPosts = useCallback(async () => {
|
|
122
149
|
try {
|
|
123
|
-
const
|
|
150
|
+
const url = `${apiBase}/posts?team=${encodeURIComponent(teamId)}&limit=50`;
|
|
151
|
+
const res = await fetch(url);
|
|
124
152
|
const json = await res.json();
|
|
125
|
-
|
|
126
|
-
setProviders(detected);
|
|
153
|
+
setPosts(Array.isArray(json.data) ? json.data : []);
|
|
127
154
|
} catch {
|
|
128
155
|
}
|
|
129
|
-
}, [apiBase, teamId
|
|
156
|
+
}, [apiBase, teamId]);
|
|
130
157
|
useEffect(() => {
|
|
131
158
|
setLoading(true);
|
|
132
|
-
Promise.all([
|
|
133
|
-
}, [
|
|
134
|
-
const
|
|
135
|
-
|
|
136
|
-
|
|
159
|
+
Promise.all([loadDrivers(), loadPosts()]).finally(() => setLoading(false));
|
|
160
|
+
}, [loadDrivers, loadPosts]);
|
|
161
|
+
const connectedDrivers = useMemo(() => drivers.filter((d) => d.connected), [drivers]);
|
|
162
|
+
const disconnectedDrivers = useMemo(() => drivers.filter((d) => !d.connected), [drivers]);
|
|
163
|
+
const togglePlatform = (platform) => {
|
|
164
|
+
const driver = drivers.find((d) => d.platform === platform);
|
|
165
|
+
if (!driver?.connected) return;
|
|
166
|
+
setSelectedPlatforms(
|
|
167
|
+
(prev) => prev.includes(platform) ? prev.filter((x) => x !== platform) : [...prev, platform]
|
|
137
168
|
);
|
|
138
169
|
};
|
|
170
|
+
const charLimit = useMemo(() => {
|
|
171
|
+
if (selectedPlatforms.length === 0) return void 0;
|
|
172
|
+
const limits = selectedPlatforms.map((p) => drivers.find((d) => d.platform === p)?.capabilities?.maxLength).filter((l) => l !== void 0);
|
|
173
|
+
return limits.length > 0 ? Math.min(...limits) : void 0;
|
|
174
|
+
}, [selectedPlatforms, drivers]);
|
|
175
|
+
const canSchedule = useMemo(() => {
|
|
176
|
+
return selectedPlatforms.some((p) => drivers.find((d) => d.platform === p)?.capabilities?.canSchedule);
|
|
177
|
+
}, [selectedPlatforms, drivers]);
|
|
178
|
+
const showSuccess = (msg) => {
|
|
179
|
+
setSuccess(msg);
|
|
180
|
+
if (successTimeout.current) clearTimeout(successTimeout.current);
|
|
181
|
+
successTimeout.current = setTimeout(() => setSuccess(null), 5e3);
|
|
182
|
+
};
|
|
139
183
|
const onSaveDraft = async () => {
|
|
140
184
|
if (!content.trim()) return;
|
|
141
185
|
setSaving(true);
|
|
142
186
|
setError(null);
|
|
143
187
|
try {
|
|
144
|
-
const platforms =
|
|
188
|
+
const platforms = selectedPlatforms.length > 0 ? selectedPlatforms : ["draft"];
|
|
145
189
|
const res = await fetch(`${apiBase}/posts?team=${encodeURIComponent(teamId)}`, {
|
|
146
190
|
method: "POST",
|
|
147
191
|
headers: { "content-type": "application/json" },
|
|
148
192
|
body: JSON.stringify({
|
|
149
193
|
content,
|
|
150
|
-
platforms
|
|
194
|
+
platforms,
|
|
151
195
|
status: scheduledAt ? "scheduled" : "draft",
|
|
152
196
|
scheduledAt: scheduledAt || void 0
|
|
153
197
|
})
|
|
@@ -155,7 +199,9 @@
|
|
|
155
199
|
if (!res.ok) throw new Error(`Save failed (${res.status})`);
|
|
156
200
|
setContent("");
|
|
157
201
|
setScheduledAt("");
|
|
158
|
-
|
|
202
|
+
setSelectedPlatforms([]);
|
|
203
|
+
setMediaUrl("");
|
|
204
|
+
showSuccess("Draft saved!");
|
|
159
205
|
await loadPosts();
|
|
160
206
|
} catch (e) {
|
|
161
207
|
setError(e?.message || "Failed to save");
|
|
@@ -164,40 +210,54 @@
|
|
|
164
210
|
}
|
|
165
211
|
};
|
|
166
212
|
const onPublish = async () => {
|
|
167
|
-
if (!content.trim() ||
|
|
213
|
+
if (!content.trim() || selectedPlatforms.length === 0) return;
|
|
168
214
|
setPublishing(true);
|
|
169
215
|
setError(null);
|
|
170
216
|
setSuccess(null);
|
|
171
|
-
const postizProviders = selectedProviders.filter((id) => id.startsWith("postiz:"));
|
|
172
|
-
const gatewayProviders = selectedProviders.filter((id) => id.startsWith("gateway:"));
|
|
173
217
|
try {
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
if (
|
|
189
|
-
|
|
190
|
-
throw new Error(err?.message || `Postiz publish failed (${res.status})`);
|
|
218
|
+
const res = await fetch(`${apiBase}/publish?team=${encodeURIComponent(teamId)}`, {
|
|
219
|
+
method: "POST",
|
|
220
|
+
headers: { "content-type": "application/json", ...postizHeaders },
|
|
221
|
+
body: JSON.stringify({
|
|
222
|
+
content,
|
|
223
|
+
platforms: selectedPlatforms,
|
|
224
|
+
scheduledAt: scheduledAt || void 0,
|
|
225
|
+
mediaUrls: mediaUrl ? [mediaUrl] : void 0
|
|
226
|
+
})
|
|
227
|
+
});
|
|
228
|
+
const json = await res.json();
|
|
229
|
+
if (json.results) {
|
|
230
|
+
const succeeded = json.results.filter((r) => r.success);
|
|
231
|
+
const failed = json.results.filter((r) => !r.success);
|
|
232
|
+
if (failed.length > 0 && succeeded.length === 0) {
|
|
233
|
+
throw new Error(failed.map((f) => `${f.platform}: ${f.error}`).join("; "));
|
|
191
234
|
}
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
235
|
+
const parts = [];
|
|
236
|
+
if (succeeded.length > 0) {
|
|
237
|
+
parts.push(`${scheduledAt ? "Scheduled" : "Published"} to ${succeeded.map((s) => s.platform).join(", ")}`);
|
|
238
|
+
}
|
|
239
|
+
if (failed.length > 0) {
|
|
240
|
+
parts.push(`Failed: ${failed.map((f) => `${f.platform} (${f.error})`).join(", ")}`);
|
|
241
|
+
}
|
|
242
|
+
showSuccess(parts.join(" \xB7 "));
|
|
195
243
|
} else {
|
|
196
|
-
|
|
244
|
+
showSuccess(scheduledAt ? "Scheduled!" : "Published!");
|
|
197
245
|
}
|
|
246
|
+
await fetch(`${apiBase}/posts?team=${encodeURIComponent(teamId)}`, {
|
|
247
|
+
method: "POST",
|
|
248
|
+
headers: { "content-type": "application/json" },
|
|
249
|
+
body: JSON.stringify({
|
|
250
|
+
content,
|
|
251
|
+
platforms: selectedPlatforms,
|
|
252
|
+
status: scheduledAt ? "scheduled" : "published",
|
|
253
|
+
scheduledAt: scheduledAt || void 0
|
|
254
|
+
})
|
|
255
|
+
}).catch(() => {
|
|
256
|
+
});
|
|
198
257
|
setContent("");
|
|
199
258
|
setScheduledAt("");
|
|
200
|
-
|
|
259
|
+
setSelectedPlatforms([]);
|
|
260
|
+
setMediaUrl("");
|
|
201
261
|
await loadPosts();
|
|
202
262
|
} catch (e) {
|
|
203
263
|
setError(e?.message || "Publish failed");
|
|
@@ -205,8 +265,12 @@
|
|
|
205
265
|
setPublishing(false);
|
|
206
266
|
}
|
|
207
267
|
};
|
|
208
|
-
const
|
|
209
|
-
const hasSelection =
|
|
268
|
+
const hasConnected = connectedDrivers.length > 0;
|
|
269
|
+
const hasSelection = selectedPlatforms.length > 0;
|
|
270
|
+
const filteredPosts = useMemo(() => {
|
|
271
|
+
if (filterStatus === "all") return posts;
|
|
272
|
+
return posts.filter((p) => p.status === filterStatus);
|
|
273
|
+
}, [posts, filterStatus]);
|
|
210
274
|
return h(
|
|
211
275
|
"div",
|
|
212
276
|
{ className: "space-y-3" },
|
|
@@ -223,60 +287,103 @@
|
|
|
223
287
|
onChange: (e) => setContent(e.target.value),
|
|
224
288
|
placeholder: "Write your post\u2026",
|
|
225
289
|
rows: 5,
|
|
226
|
-
style: { ...t.input, resize: "vertical", minHeight: "110px" }
|
|
290
|
+
style: { ...t.input, resize: "vertical", minHeight: "110px", fontFamily: "inherit" }
|
|
227
291
|
}),
|
|
228
|
-
//
|
|
229
|
-
|
|
292
|
+
// Character count
|
|
293
|
+
charLimit && content.length > 0 && h(
|
|
294
|
+
"div",
|
|
295
|
+
{ style: t.charWarn(content.length / charLimit * 100) },
|
|
296
|
+
`${content.length} / ${charLimit} characters`,
|
|
297
|
+
content.length > charLimit && " \u26A0 over limit"
|
|
298
|
+
),
|
|
299
|
+
!charLimit && content.length > 0 && h("div", { className: "text-xs", style: t.faint }, `${content.length} chars`),
|
|
300
|
+
// Platform selector — connected
|
|
301
|
+
h(
|
|
230
302
|
"div",
|
|
231
303
|
null,
|
|
232
304
|
h("div", { className: "text-xs font-medium mb-2", style: t.faint }, "Publish to"),
|
|
233
|
-
h(
|
|
305
|
+
connectedDrivers.length > 0 ? h(
|
|
234
306
|
"div",
|
|
235
307
|
{ className: "flex flex-wrap gap-2" },
|
|
236
|
-
...
|
|
237
|
-
(
|
|
308
|
+
...connectedDrivers.map(
|
|
309
|
+
(d) => h(
|
|
238
310
|
"span",
|
|
239
311
|
{
|
|
240
|
-
key:
|
|
241
|
-
onClick: () =>
|
|
242
|
-
style: t.pill(
|
|
312
|
+
key: d.platform,
|
|
313
|
+
onClick: () => togglePlatform(d.platform),
|
|
314
|
+
style: t.pill(selectedPlatforms.includes(d.platform), true),
|
|
243
315
|
role: "button",
|
|
244
|
-
tabIndex: 0
|
|
316
|
+
tabIndex: 0,
|
|
317
|
+
title: `${d.displayName} via ${d.backend}`
|
|
245
318
|
},
|
|
246
|
-
`${
|
|
319
|
+
`${d.icon} ${d.label}`,
|
|
320
|
+
h("span", { style: t.backendBadge(d.backend) }, d.backend)
|
|
321
|
+
)
|
|
322
|
+
),
|
|
323
|
+
...disconnectedDrivers.map(
|
|
324
|
+
(d) => h("span", {
|
|
325
|
+
key: d.platform,
|
|
326
|
+
style: t.pill(false, false),
|
|
327
|
+
title: `${d.label} \u2014 not connected`
|
|
328
|
+
}, `${d.icon} ${d.label}`)
|
|
329
|
+
)
|
|
330
|
+
) : h(
|
|
331
|
+
"div",
|
|
332
|
+
{ className: "flex flex-wrap gap-2" },
|
|
333
|
+
...drivers.map(
|
|
334
|
+
(d) => h(
|
|
335
|
+
"span",
|
|
336
|
+
{ key: d.platform, style: t.pill(false, false), title: "Not connected" },
|
|
337
|
+
`${d.icon} ${d.label}`
|
|
247
338
|
)
|
|
339
|
+
),
|
|
340
|
+
h(
|
|
341
|
+
"div",
|
|
342
|
+
{ className: "text-xs mt-1", style: t.faint },
|
|
343
|
+
"No platforms connected. Go to Accounts tab to set up Postiz or add accounts."
|
|
248
344
|
)
|
|
249
345
|
)
|
|
250
346
|
),
|
|
251
|
-
//
|
|
252
|
-
|
|
347
|
+
// Media URL (collapsible)
|
|
348
|
+
h(
|
|
253
349
|
"div",
|
|
254
|
-
|
|
255
|
-
"
|
|
350
|
+
null,
|
|
351
|
+
h("button", {
|
|
352
|
+
type: "button",
|
|
353
|
+
onClick: () => setShowMedia(!showMedia),
|
|
354
|
+
style: { ...t.btnGhost, padding: "0.3rem 0.55rem", fontSize: "0.8rem" }
|
|
355
|
+
}, showMedia ? "\u2212 Media" : "+ Media"),
|
|
356
|
+
showMedia && h(
|
|
357
|
+
"div",
|
|
358
|
+
{ className: "mt-2" },
|
|
359
|
+
h("input", {
|
|
360
|
+
type: "url",
|
|
361
|
+
value: mediaUrl,
|
|
362
|
+
onChange: (e) => setMediaUrl(e.target.value),
|
|
363
|
+
placeholder: "Paste image or video URL\u2026",
|
|
364
|
+
style: t.input
|
|
365
|
+
})
|
|
366
|
+
)
|
|
256
367
|
),
|
|
257
|
-
// Schedule
|
|
258
|
-
h(
|
|
368
|
+
// Schedule (only if any selected platform supports it)
|
|
369
|
+
(canSchedule || !hasSelection) && h(
|
|
259
370
|
"div",
|
|
260
371
|
{ className: "grid grid-cols-1 gap-2 sm:grid-cols-2" },
|
|
261
372
|
h(
|
|
262
373
|
"div",
|
|
263
374
|
null,
|
|
264
|
-
h(
|
|
375
|
+
h(
|
|
376
|
+
"div",
|
|
377
|
+
{ className: "text-xs font-medium mb-1", style: t.faint },
|
|
378
|
+
canSchedule ? "Schedule (optional)" : "Schedule (connect Postiz for scheduling)"
|
|
379
|
+
),
|
|
265
380
|
h("input", {
|
|
266
381
|
type: "datetime-local",
|
|
267
382
|
value: scheduledAt,
|
|
268
383
|
onChange: (e) => setScheduledAt(e.target.value),
|
|
269
|
-
style: t.input
|
|
384
|
+
style: { ...t.input, opacity: canSchedule || !hasSelection ? 1 : 0.5 },
|
|
385
|
+
disabled: hasSelection && !canSchedule
|
|
270
386
|
})
|
|
271
|
-
),
|
|
272
|
-
h(
|
|
273
|
-
"div",
|
|
274
|
-
{ className: "flex items-end" },
|
|
275
|
-
h(
|
|
276
|
-
"div",
|
|
277
|
-
{ className: "text-xs", style: t.faint },
|
|
278
|
-
content.length > 0 ? `${content.length} chars` : ""
|
|
279
|
-
)
|
|
280
387
|
)
|
|
281
388
|
),
|
|
282
389
|
// Actions
|
|
@@ -289,17 +396,12 @@
|
|
|
289
396
|
style: { ...t.btnGhost, opacity: saving ? 0.7 : 1 },
|
|
290
397
|
disabled: saving || !content.trim()
|
|
291
398
|
}, saving ? "Saving\u2026" : "Save draft"),
|
|
292
|
-
|
|
399
|
+
hasConnected && hasSelection && h("button", {
|
|
293
400
|
type: "button",
|
|
294
401
|
onClick: () => void onPublish(),
|
|
295
402
|
style: { ...t.btnPublish, opacity: publishing ? 0.7 : 1 },
|
|
296
403
|
disabled: publishing || !content.trim()
|
|
297
|
-
}, publishing ? "Publishing\u2026" : scheduledAt ? "\u23F1 Schedule" : "\u{1F4E4} Publish")
|
|
298
|
-
!postizAvailable && hasSelection && h(
|
|
299
|
-
"div",
|
|
300
|
-
{ className: "text-xs", style: t.faint },
|
|
301
|
-
"Connect Postiz on Accounts tab to publish directly."
|
|
302
|
-
)
|
|
404
|
+
}, publishing ? "Publishing\u2026" : scheduledAt ? "\u23F1 Schedule" : "\u{1F4E4} Publish now")
|
|
303
405
|
),
|
|
304
406
|
error && h("div", { className: "text-xs", style: { color: "rgba(248,113,113,0.95)" } }, error),
|
|
305
407
|
success && h("div", { className: "text-xs", style: { color: "rgba(74,222,128,0.9)" } }, success)
|
|
@@ -311,14 +413,36 @@
|
|
|
311
413
|
{ style: t.card },
|
|
312
414
|
h(
|
|
313
415
|
"div",
|
|
314
|
-
{ className: "flex items-center justify-between mb-
|
|
416
|
+
{ className: "flex items-center justify-between mb-3" },
|
|
315
417
|
h("div", { className: "text-sm font-medium", style: t.text }, "Posts"),
|
|
316
|
-
h(
|
|
418
|
+
h(
|
|
419
|
+
"div",
|
|
420
|
+
{ className: "flex items-center gap-2" },
|
|
421
|
+
...["all", "draft", "scheduled", "published", "failed"].map(
|
|
422
|
+
(s) => h("button", {
|
|
423
|
+
key: s,
|
|
424
|
+
type: "button",
|
|
425
|
+
onClick: () => setFilterStatus(s),
|
|
426
|
+
style: {
|
|
427
|
+
...t.btnGhost,
|
|
428
|
+
padding: "0.2rem 0.45rem",
|
|
429
|
+
fontSize: "0.7rem",
|
|
430
|
+
background: filterStatus === s ? "rgba(99,179,237,0.12)" : void 0,
|
|
431
|
+
borderColor: filterStatus === s ? "rgba(99,179,237,0.35)" : void 0
|
|
432
|
+
}
|
|
433
|
+
}, s)
|
|
434
|
+
),
|
|
435
|
+
h("button", { type: "button", onClick: () => void loadPosts(), style: { ...t.btnGhost, padding: "0.2rem 0.45rem", fontSize: "0.7rem" } }, "\u21BB")
|
|
436
|
+
)
|
|
317
437
|
),
|
|
318
|
-
loading ? h("div", { className: "py-6 text-center text-sm", style: t.faint }, "Loading\u2026") :
|
|
438
|
+
loading ? h("div", { className: "py-6 text-center text-sm", style: t.faint }, "Loading\u2026") : filteredPosts.length === 0 ? h(
|
|
439
|
+
"div",
|
|
440
|
+
{ className: "py-6 text-center text-sm", style: t.faint },
|
|
441
|
+
filterStatus === "all" ? "No posts yet. Compose your first post above!" : `No ${filterStatus} posts.`
|
|
442
|
+
) : h(
|
|
319
443
|
"div",
|
|
320
444
|
{ className: "space-y-2" },
|
|
321
|
-
...
|
|
445
|
+
...filteredPosts.map(
|
|
322
446
|
(p) => h(
|
|
323
447
|
"div",
|
|
324
448
|
{ key: p.id, style: { ...t.card, padding: "0.75rem" } },
|
|
@@ -333,11 +457,21 @@
|
|
|
333
457
|
),
|
|
334
458
|
p.scheduledAt && h("div", { className: "text-xs", style: t.muted }, `\u23F1 ${new Date(p.scheduledAt).toLocaleString()}`)
|
|
335
459
|
),
|
|
336
|
-
h("div", {
|
|
460
|
+
h("div", {
|
|
461
|
+
className: "mt-2 whitespace-pre-wrap text-sm",
|
|
462
|
+
style: { ...t.text, maxHeight: "120px", overflow: "hidden", textOverflow: "ellipsis" }
|
|
463
|
+
}, p.content),
|
|
337
464
|
p.platforms?.length > 0 && h(
|
|
338
465
|
"div",
|
|
339
466
|
{ className: "mt-2 flex flex-wrap gap-1" },
|
|
340
|
-
...p.platforms.map((pl) =>
|
|
467
|
+
...p.platforms.map((pl) => {
|
|
468
|
+
const driver = drivers.find((d) => d.platform === pl);
|
|
469
|
+
return h(
|
|
470
|
+
"span",
|
|
471
|
+
{ key: pl, style: t.pill(true, true) },
|
|
472
|
+
driver ? `${driver.icon} ${pl}` : pl
|
|
473
|
+
);
|
|
474
|
+
})
|
|
341
475
|
)
|
|
342
476
|
)
|
|
343
477
|
)
|