@outstand-so/ui 0.1.9 → 0.1.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/index.cjs ADDED
@@ -0,0 +1,2302 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __export = (target, all) => {
9
+ for (var name in all)
10
+ __defProp(target, name, { get: all[name], enumerable: true });
11
+ };
12
+ var __copyProps = (to, from, except, desc) => {
13
+ if (from && typeof from === "object" || typeof from === "function") {
14
+ for (let key of __getOwnPropNames(from))
15
+ if (!__hasOwnProp.call(to, key) && key !== except)
16
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
17
+ }
18
+ return to;
19
+ };
20
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
21
+ // If the importer is in node compatibility mode or this is not an ESM
22
+ // file that has been converted to a CommonJS file using a Babel-
23
+ // compatible transform (i.e. "__esModule" has not been set), then set
24
+ // "default" to the CommonJS "module.exports" for node compatibility.
25
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
26
+ mod
27
+ ));
28
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
29
+
30
+ // src/index.ts
31
+ var index_exports = {};
32
+ __export(index_exports, {
33
+ AccountCard: () => AccountCard,
34
+ AccountsList: () => AccountsList,
35
+ ConnectAccountButton: () => ConnectAccountButton,
36
+ ConnectAccountButtonGroup: () => ConnectAccountButtonGroup,
37
+ CreatePostForm: () => CreatePostForm,
38
+ MediaGallery: () => MediaGallery,
39
+ MediaList: () => MediaList,
40
+ MediaPreview: () => MediaPreview,
41
+ MediaUploader: () => MediaUploader,
42
+ NetworkConfigPanel: () => NetworkConfigPanel,
43
+ NetworkSelector: () => NetworkSelector,
44
+ OAuthCallback: () => OAuthCallback,
45
+ OutstandProvider: () => OutstandProvider,
46
+ PageSelector: () => PageSelector,
47
+ PostComposer: () => PostComposer,
48
+ PostMetrics: () => PostMetrics,
49
+ PostMetricsCard: () => PostMetricsCard,
50
+ PostScheduler: () => PostScheduler,
51
+ PostsWithMetrics: () => PostsWithMetrics,
52
+ useAccounts: () => useAccounts,
53
+ useMediaList: () => useMediaList,
54
+ useMediaUpload: () => useMediaUpload,
55
+ useOAuthFlow: () => useOAuthFlow,
56
+ useOutstand: () => useOutstand,
57
+ useOutstandApi: () => useOutstandApi,
58
+ usePostMetrics: () => usePostMetrics,
59
+ usePosts: () => usePosts
60
+ });
61
+ module.exports = __toCommonJS(index_exports);
62
+
63
+ // src/context/outstand-provider.tsx
64
+ var React = __toESM(require("react"), 1);
65
+ var import_jsx_runtime = require("react/jsx-runtime");
66
+ var OutstandContext = React.createContext(null);
67
+ function OutstandProvider({
68
+ children,
69
+ apiKey,
70
+ baseUrl = "https://api.outstand.so/v1",
71
+ tenantId,
72
+ initialAccounts = []
73
+ }) {
74
+ const [accounts, setAccounts] = React.useState(initialAccounts);
75
+ const [selectedAccounts, setSelectedAccounts] = React.useState([]);
76
+ const [isLoading, setIsLoading] = React.useState(false);
77
+ const value = React.useMemo(
78
+ () => ({
79
+ apiKey,
80
+ baseUrl,
81
+ tenantId,
82
+ accounts,
83
+ setAccounts,
84
+ selectedAccounts,
85
+ setSelectedAccounts,
86
+ isLoading,
87
+ setIsLoading
88
+ }),
89
+ [apiKey, baseUrl, tenantId, accounts, selectedAccounts, isLoading]
90
+ );
91
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(OutstandContext.Provider, { value, children });
92
+ }
93
+ function useOutstand() {
94
+ const context = React.useContext(OutstandContext);
95
+ if (!context) {
96
+ throw new Error("useOutstand must be used within an OutstandProvider");
97
+ }
98
+ return context;
99
+ }
100
+
101
+ // src/hooks/use-outstand-api.ts
102
+ var React2 = __toESM(require("react"), 1);
103
+ function useOutstandApi({ apiKey, baseUrl = "https://api.outstand.so/v1" }) {
104
+ const headers = React2.useMemo(
105
+ () => ({
106
+ "Content-Type": "application/json",
107
+ Authorization: `Bearer ${apiKey}`
108
+ }),
109
+ [apiKey]
110
+ );
111
+ const request = React2.useCallback(
112
+ async function makeRequest(endpoint, options = {}) {
113
+ const url = `${baseUrl}${endpoint}`;
114
+ try {
115
+ const response = await fetch(url, {
116
+ ...options,
117
+ headers: {
118
+ ...headers,
119
+ ...options.headers
120
+ }
121
+ });
122
+ const data = await response.json();
123
+ if (!response.ok) {
124
+ return {
125
+ success: false,
126
+ error: data.error || "Request failed",
127
+ message: data.message,
128
+ details: data.details
129
+ };
130
+ }
131
+ return data;
132
+ } catch (error) {
133
+ return {
134
+ success: false,
135
+ error: error instanceof Error ? error.message : "Network error"
136
+ };
137
+ }
138
+ },
139
+ [baseUrl, headers]
140
+ );
141
+ const get = React2.useCallback(
142
+ function getRequest(endpoint) {
143
+ return request(endpoint, { method: "GET" });
144
+ },
145
+ [request]
146
+ );
147
+ const post = React2.useCallback(
148
+ function postRequest(endpoint, body) {
149
+ return request(endpoint, {
150
+ method: "POST",
151
+ body: JSON.stringify(body)
152
+ });
153
+ },
154
+ [request]
155
+ );
156
+ const patch = React2.useCallback(
157
+ function patchRequest(endpoint, body) {
158
+ return request(endpoint, {
159
+ method: "PATCH",
160
+ body: JSON.stringify(body)
161
+ });
162
+ },
163
+ [request]
164
+ );
165
+ const del = React2.useCallback(
166
+ function deleteRequest(endpoint) {
167
+ return request(endpoint, { method: "DELETE" });
168
+ },
169
+ [request]
170
+ );
171
+ return { get, post, patch, delete: del, request };
172
+ }
173
+
174
+ // src/hooks/use-accounts.ts
175
+ var React3 = __toESM(require("react"), 1);
176
+ var import_swr = __toESM(require("swr"), 1);
177
+ function useAccounts({
178
+ apiKey,
179
+ baseUrl = "https://api.outstand.so/v1",
180
+ tenantId,
181
+ limit = 50,
182
+ offset = 0
183
+ }) {
184
+ const api = useOutstandApi({ apiKey, baseUrl });
185
+ const queryParams = new URLSearchParams({
186
+ limit: limit.toString(),
187
+ offset: offset.toString()
188
+ });
189
+ if (tenantId) {
190
+ queryParams.set("tenantId", tenantId);
191
+ }
192
+ const { data, error, isLoading, mutate } = (0, import_swr.default)(
193
+ `/social-accounts?${queryParams.toString()}`,
194
+ async () => {
195
+ const response = await api.get(`/social-accounts?${queryParams.toString()}`);
196
+ return {
197
+ accounts: response.data || [],
198
+ count: response.data?.length || 0,
199
+ limit,
200
+ offset
201
+ };
202
+ },
203
+ { revalidateOnFocus: false }
204
+ );
205
+ const accounts = data?.accounts || [];
206
+ const total = data?.count || 0;
207
+ const disconnectAccount = React3.useCallback(
208
+ async (accountId) => {
209
+ const response = await api.delete(`/social-accounts/${accountId}`);
210
+ if (response.success) {
211
+ mutate();
212
+ }
213
+ return response;
214
+ },
215
+ [api, mutate]
216
+ );
217
+ return {
218
+ accounts,
219
+ total,
220
+ isLoading,
221
+ error,
222
+ refetch: mutate,
223
+ disconnectAccount,
224
+ hasNextPage: offset + limit < total,
225
+ hasPrevPage: offset > 0
226
+ };
227
+ }
228
+
229
+ // src/hooks/use-posts.ts
230
+ var React4 = __toESM(require("react"), 1);
231
+ var import_swr2 = __toESM(require("swr"), 1);
232
+ function usePosts({
233
+ apiKey,
234
+ baseUrl = "https://api.outstand.so/v1",
235
+ socialAccountId,
236
+ limit = 50,
237
+ offset = 0
238
+ }) {
239
+ const api = useOutstandApi({ apiKey, baseUrl });
240
+ const queryParams = new URLSearchParams({
241
+ limit: limit.toString(),
242
+ offset: offset.toString()
243
+ });
244
+ if (socialAccountId) {
245
+ queryParams.set("social_account_id", socialAccountId);
246
+ }
247
+ const { data, error, isLoading, mutate } = (0, import_swr2.default)(
248
+ `/posts?${queryParams.toString()}`,
249
+ async () => {
250
+ const response = await api.get(`/posts?${queryParams.toString()}`);
251
+ return {
252
+ posts: response.data || [],
253
+ pagination: {
254
+ limit,
255
+ offset,
256
+ total: response.count || 0
257
+ }
258
+ };
259
+ },
260
+ { revalidateOnFocus: false }
261
+ );
262
+ const posts = data?.posts || [];
263
+ const total = data?.pagination?.total || 0;
264
+ const createPost = React4.useCallback(
265
+ async (postData) => {
266
+ const response = await api.post("/posts/", postData);
267
+ if (response.success) {
268
+ mutate();
269
+ }
270
+ return response;
271
+ },
272
+ [api, mutate]
273
+ );
274
+ const deletePost = React4.useCallback(
275
+ async (postId) => {
276
+ const response = await api.delete(`/posts/${postId}`);
277
+ if (response.success) {
278
+ mutate();
279
+ }
280
+ return response;
281
+ },
282
+ [api, mutate]
283
+ );
284
+ return {
285
+ posts,
286
+ total,
287
+ isLoading,
288
+ error,
289
+ refetch: mutate,
290
+ createPost,
291
+ deletePost,
292
+ hasNextPage: offset + limit < total,
293
+ hasPrevPage: offset > 0
294
+ };
295
+ }
296
+
297
+ // src/hooks/use-post-metrics.ts
298
+ var import_swr3 = __toESM(require("swr"), 1);
299
+ function usePostMetrics({
300
+ apiKey,
301
+ baseUrl = "https://api.outstand.so/v1",
302
+ postId,
303
+ refreshInterval
304
+ }) {
305
+ const api = useOutstandApi({ apiKey, baseUrl });
306
+ const { data, error, isLoading, mutate } = (0, import_swr3.default)(
307
+ postId ? `post-metrics-${postId}` : null,
308
+ async () => {
309
+ const response = await api.get(`/posts/${postId}/analytics`);
310
+ return response;
311
+ },
312
+ {
313
+ revalidateOnFocus: false,
314
+ refreshInterval,
315
+ keepPreviousData: false
316
+ }
317
+ );
318
+ const analytics = data?.success ? {
319
+ post: data.post,
320
+ metrics_by_account: data.metrics_by_account ?? [],
321
+ aggregated_metrics: data.aggregated_metrics ?? {
322
+ total_likes: 0,
323
+ total_comments: 0,
324
+ total_shares: 0,
325
+ total_reach: 0,
326
+ total_views: 0,
327
+ total_impressions: 0,
328
+ average_engagement_rate: 0
329
+ }
330
+ } : null;
331
+ return {
332
+ analytics,
333
+ isLoading,
334
+ error,
335
+ refetch: mutate
336
+ };
337
+ }
338
+
339
+ // src/hooks/use-media-upload.ts
340
+ var React5 = __toESM(require("react"), 1);
341
+ function useMediaUpload({ apiKey, baseUrl = "https://api.outstand.so/v1" }) {
342
+ const [isUploading, setIsUploading] = React5.useState(false);
343
+ const [uploadProgress, setUploadProgress] = React5.useState({});
344
+ const api = useOutstandApi({ apiKey, baseUrl });
345
+ const getUploadUrl = React5.useCallback(
346
+ async (filename, contentType) => {
347
+ return api.post("/media/upload", { filename, content_type: contentType });
348
+ },
349
+ [api]
350
+ );
351
+ const confirmUpload = React5.useCallback(
352
+ async (mediaId, size) => {
353
+ return api.post(`/media/${mediaId}/confirm`, size ? { size } : {});
354
+ },
355
+ [api]
356
+ );
357
+ const uploadFile = React5.useCallback(
358
+ async (file) => {
359
+ setIsUploading(true);
360
+ setUploadProgress((prev) => ({ ...prev, [file.name]: 0 }));
361
+ try {
362
+ const urlResponse = await getUploadUrl(file.name, file.type);
363
+ if (!urlResponse.success || !urlResponse.data) {
364
+ throw new Error(urlResponse.error || "Failed to get upload URL");
365
+ }
366
+ const { id, upload_url } = urlResponse.data;
367
+ setUploadProgress((prev) => ({ ...prev, [file.name]: 50 }));
368
+ const uploadResponse = await fetch(upload_url, {
369
+ method: "PUT",
370
+ body: file,
371
+ headers: {
372
+ "Content-Type": file.type
373
+ }
374
+ });
375
+ if (!uploadResponse.ok) {
376
+ throw new Error("Failed to upload file");
377
+ }
378
+ setUploadProgress((prev) => ({ ...prev, [file.name]: 75 }));
379
+ const confirmResponse = await confirmUpload(id, file.size);
380
+ if (!confirmResponse.success || !confirmResponse.data) {
381
+ throw new Error(confirmResponse.error || "Failed to confirm upload");
382
+ }
383
+ setUploadProgress((prev) => ({ ...prev, [file.name]: 100 }));
384
+ const confirmedMedia = confirmResponse.data;
385
+ return {
386
+ id: confirmedMedia.id,
387
+ url: confirmedMedia.url,
388
+ filename: confirmedMedia.filename,
389
+ contentType: confirmedMedia.content_type,
390
+ size: confirmedMedia.size,
391
+ status: confirmedMedia.status,
392
+ created_at: confirmedMedia.created_at,
393
+ expires_at: confirmedMedia.expires_at
394
+ };
395
+ } catch (error) {
396
+ console.error("Upload error:", error);
397
+ return null;
398
+ } finally {
399
+ setIsUploading(false);
400
+ }
401
+ },
402
+ [getUploadUrl, confirmUpload]
403
+ );
404
+ const uploadFiles = React5.useCallback(
405
+ async (files) => {
406
+ const results = await Promise.all(files.map(uploadFile));
407
+ return results.filter((r) => r !== null);
408
+ },
409
+ [uploadFile]
410
+ );
411
+ return {
412
+ uploadFile,
413
+ uploadFiles,
414
+ getUploadUrl,
415
+ confirmUpload,
416
+ // Exported confirmUpload in case users need it directly
417
+ isUploading,
418
+ uploadProgress
419
+ };
420
+ }
421
+
422
+ // src/hooks/use-media-list.ts
423
+ var import_swr4 = __toESM(require("swr"), 1);
424
+ function useMediaList({
425
+ apiKey,
426
+ baseUrl,
427
+ limit = 50,
428
+ offset = 0,
429
+ enabled = true
430
+ }) {
431
+ const api = useOutstandApi({ apiKey, baseUrl });
432
+ const { data, error, isLoading, mutate } = (0, import_swr4.default)(
433
+ enabled ? `media-list-${limit}-${offset}` : null,
434
+ async () => {
435
+ const response = await api.get(`/media?limit=${limit}&offset=${offset}`);
436
+ return response;
437
+ },
438
+ {
439
+ revalidateOnFocus: false,
440
+ keepPreviousData: false
441
+ }
442
+ );
443
+ const deleteMedia = async (mediaId) => {
444
+ await api.delete(`/media/${mediaId}`);
445
+ mutate();
446
+ };
447
+ return {
448
+ media: data?.data ?? [],
449
+ pagination: data?.pagination ?? null,
450
+ isLoading,
451
+ error: error ?? null,
452
+ refresh: () => mutate(),
453
+ deleteMedia
454
+ };
455
+ }
456
+
457
+ // src/hooks/use-oauth-flow.ts
458
+ var React6 = __toESM(require("react"), 1);
459
+ function useOAuthFlow({ apiKey, baseUrl = "https://api.outstand.so/v1" }) {
460
+ const [isLoading, setIsLoading] = React6.useState(false);
461
+ const api = useOutstandApi({ apiKey, baseUrl });
462
+ const getAuthUrl = React6.useCallback(
463
+ async (network, redirectUri, tenantId) => {
464
+ setIsLoading(true);
465
+ try {
466
+ const response = await api.post(`/social-networks/${network}/auth-url`, {
467
+ redirect_uri: redirectUri,
468
+ tenant_id: tenantId
469
+ });
470
+ return response;
471
+ } finally {
472
+ setIsLoading(false);
473
+ }
474
+ },
475
+ [api]
476
+ );
477
+ const getPendingConnection = React6.useCallback(
478
+ async (sessionToken) => {
479
+ setIsLoading(true);
480
+ try {
481
+ return api.get(`/social-accounts/pending/${sessionToken}`);
482
+ } finally {
483
+ setIsLoading(false);
484
+ }
485
+ },
486
+ [api]
487
+ );
488
+ const finalizeConnection = React6.useCallback(
489
+ async (sessionToken, selectedPageIds) => {
490
+ setIsLoading(true);
491
+ try {
492
+ return api.post(`/social-accounts/pending/${sessionToken}/finalize`, {
493
+ selectedPageIds
494
+ });
495
+ } finally {
496
+ setIsLoading(false);
497
+ }
498
+ },
499
+ [api]
500
+ );
501
+ return {
502
+ getAuthUrl,
503
+ getPendingConnection,
504
+ finalizeConnection,
505
+ isLoading
506
+ };
507
+ }
508
+
509
+ // src/lib/utils.ts
510
+ var import_clsx = require("clsx");
511
+ var import_tailwind_merge = require("tailwind-merge");
512
+ function cn(...inputs) {
513
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
514
+ }
515
+ function formatNumber(num) {
516
+ if (num >= 1e6) {
517
+ return (num / 1e6).toFixed(1) + "M";
518
+ }
519
+ if (num >= 1e3) {
520
+ return (num / 1e3).toFixed(1) + "K";
521
+ }
522
+ return num.toString();
523
+ }
524
+ function formatDate(date) {
525
+ return new Intl.DateTimeFormat("en-US", {
526
+ month: "short",
527
+ day: "numeric",
528
+ year: "numeric",
529
+ hour: "numeric",
530
+ minute: "2-digit"
531
+ }).format(new Date(date));
532
+ }
533
+ function getNetworkColor(network) {
534
+ const colors = {
535
+ x: "#000000",
536
+ twitter: "#1DA1F2",
537
+ facebook: "#1877F2",
538
+ instagram: "#E4405F",
539
+ linkedin: "#0A66C2",
540
+ youtube: "#FF0000",
541
+ tiktok: "#000000",
542
+ threads: "#000000",
543
+ bluesky: "#0085FF",
544
+ pinterest: "#E60023"
545
+ };
546
+ return colors[network.toLowerCase()] || "#6B7280";
547
+ }
548
+ function getNetworkDisplayName(network) {
549
+ const names = {
550
+ x: "X (Twitter)",
551
+ twitter: "Twitter",
552
+ facebook: "Facebook",
553
+ instagram: "Instagram",
554
+ linkedin: "LinkedIn",
555
+ youtube: "YouTube",
556
+ tiktok: "TikTok",
557
+ threads: "Threads",
558
+ bluesky: "Bluesky",
559
+ pinterest: "Pinterest"
560
+ };
561
+ return names[network.toLowerCase()] || network;
562
+ }
563
+
564
+ // src/components/icons/network-icons.tsx
565
+ var import_jsx_runtime2 = require("react/jsx-runtime");
566
+ function NetworkIcon({ network, className }) {
567
+ const iconClass = cn("shrink-0", className);
568
+ switch (network.toLowerCase()) {
569
+ case "x":
570
+ case "twitter":
571
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M18.244 2.25h3.308l-7.227 8.26 8.502 11.24H16.17l-5.214-6.817L4.99 21.75H1.68l7.73-8.835L1.254 2.25H8.08l4.713 6.231zm-1.161 17.52h1.833L7.084 4.126H5.117z" }) });
572
+ case "facebook":
573
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.47h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.47h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z" }) });
574
+ case "instagram":
575
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2.163c3.204 0 3.584.012 4.85.07 3.252.148 4.771 1.691 4.919 4.919.058 1.265.069 1.645.069 4.849 0 3.205-.012 3.584-.069 4.849-.149 3.225-1.664 4.771-4.919 4.919-1.266.058-1.644.07-4.85.07-3.204 0-3.584-.012-4.849-.07-3.26-.149-4.771-1.699-4.919-4.92-.058-1.265-.07-1.644-.07-4.849 0-3.204.013-3.583.07-4.849.149-3.227 1.664-4.771 4.919-4.919 1.266-.057 1.645-.069 4.849-.069zM12 0C8.741 0 8.333.014 7.053.072 2.695.272.273 2.69.073 7.052.014 8.333 0 8.741 0 12c0 3.259.014 3.668.072 4.948.2 4.358 2.618 6.78 6.98 6.98C8.333 23.986 8.741 24 12 24c3.259 0 3.668-.014 4.948-.072 4.354-.2 6.782-2.618 6.979-6.98.059-1.28.073-1.689.073-4.948 0-3.259-.014-3.667-.072-4.947-.196-4.354-2.617-6.78-6.979-6.98C15.668.014 15.259 0 12 0zm0 5.838a6.162 6.162 0 100 12.324 6.162 6.162 0 000-12.324zM12 16a4 4 0 110-8 4 4 0 010 8zm6.406-11.845a1.44 1.44 0 100 2.881 1.44 1.44 0 000-2.881z" }) });
576
+ case "linkedin":
577
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M20.447 20.452h-3.554v-5.569c0-1.328-.027-3.037-1.852-3.037-1.853 0-2.136 1.445-2.136 2.939v5.667H9.351V9h3.414v1.561h.046c.477-.9 1.637-1.85 3.37-1.85 3.601 0 4.267 2.37 4.267 5.455v6.286zM5.337 7.433a2.062 2.062 0 01-2.063-2.065 2.064 2.064 0 112.063 2.065zm1.782 13.019H3.555V9h3.564v11.452zM22.225 0H1.771C.792 0 0 .774 0 1.729v20.542C0 23.227.792 24 1.771 24h20.451C23.2 24 24 23.227 24 22.271V1.729C24 .774 23.2 0 22.222 0h.003z" }) });
578
+ case "youtube":
579
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" }) });
580
+ case "tiktok":
581
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12.525.02c1.31-.02 2.61-.01 3.91-.02.08 1.53.63 3.09 1.75 4.17 1.12 1.11 2.7 1.62 4.24 1.79v4.03c-1.44-.05-2.89-.35-4.2-.97-.57-.26-1.1-.59-1.62-.93-.01 2.92.01 5.84-.02 8.75-.08 1.4-.54 2.79-1.35 3.94-1.31 1.92-3.58 3.17-5.91 3.21-1.43.08-2.86-.31-4.08-1.03-2.02-1.19-3.44-3.37-3.65-5.71-.02-.5-.03-1-.01-1.49.18-1.9 1.12-3.72 2.58-4.96 1.66-1.44 3.98-2.13 6.15-1.72.02 1.48-.04 2.96-.04 4.44-.99-.32-2.15-.23-3.02.37-.63.41-1.11 1.04-1.36 1.75-.21.51-.15 1.07-.14 1.61.24 1.64 1.82 3.02 3.5 2.87 1.12-.01 2.19-.66 2.77-1.61.19-.33.4-.67.41-1.06.1-1.79.06-3.57.07-5.36.01-4.03-.01-8.05.02-12.07z" }) });
582
+ case "threads":
583
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12.186 24h-.007c-3.581-.024-6.334-1.205-8.184-3.509C2.35 18.44 1.5 15.586 1.472 12.01v-.017c.03-3.579.879-6.43 2.525-8.482C5.845 1.205 8.6.024 12.18 0h.014c2.746.02 5.043.725 6.826 2.098 1.677 1.29 2.858 3.13 3.509 5.467l-2.04.569c-1.104-3.96-3.898-5.984-8.304-6.015-2.91.022-5.11.936-6.54 2.717C4.307 6.504 3.616 8.914 3.589 12c.027 3.086.718 5.496 2.057 7.164 1.43 1.783 3.631 2.698 6.54 2.717 2.623-.02 4.358-.631 5.8-2.045 1.647-1.613 1.618-3.593 1.09-4.798-.31-.71-.873-1.3-1.634-1.75-.192 1.352-.622 2.446-1.284 3.272-.886 1.102-2.14 1.704-3.73 1.79-1.202.065-2.361-.218-3.259-.801-1.063-.689-1.685-1.74-1.752-2.96-.065-1.182.408-2.256 1.332-3.023.88-.73 2.132-1.13 3.628-1.154 1.041-.017 2.004.098 2.877.343-.058-.663-.22-1.207-.485-1.63-.396-.635-1.028-.96-1.878-.967h-.061c-.696 0-1.548.196-2.011.627l-1.378-1.56c.769-.68 1.965-1.185 3.328-1.185h.09c1.49.016 2.6.551 3.297 1.59.507.756.792 1.73.853 2.895.634.235 1.19.54 1.658.916 1.09.878 1.796 2.087 2.047 3.5.306 1.73.003 3.603-1.413 5.013-1.884 1.877-4.326 2.715-7.691 2.739z" }) });
584
+ case "bluesky":
585
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 10.8c-1.087-2.114-4.046-6.053-6.798-7.995C2.566.944 1.561 1.266.902 1.565.139 1.908 0 3.08 0 3.768c0 .69.378 5.65.624 6.479.815 2.736 3.713 3.66 6.383 3.364.136-.02.275-.039.415-.056-.138.022-.276.04-.415.056-3.912.58-7.387 2.005-2.83 7.078 5.013 5.19 6.87-1.113 7.823-4.308.953 3.195 2.05 9.271 7.733 4.308 4.267-4.308 1.172-6.498-2.74-7.078a8.741 8.741 0 01-.415-.056c.14.017.279.036.415.056 2.67.297 5.568-.628 6.383-3.364.246-.828.624-5.79.624-6.478 0-.69-.139-1.861-.902-2.206-.659-.298-1.664-.62-4.3 1.24C16.046 4.748 13.087 8.687 12 10.8z" }) });
586
+ case "pinterest":
587
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 0C5.373 0 0 5.372 0 12c0 5.084 3.163 9.426 7.627 11.174-.105-.949-.2-2.405.042-3.441.218-.937 1.407-5.965 1.407-5.965s-.359-.719-.359-1.782c0-1.668.967-2.914 2.171-2.914 1.023 0 1.518.769 1.518 1.69 0 1.029-.655 2.568-.994 3.995-.283 1.194.599 2.169 1.777 2.169 2.133 0 3.772-2.249 3.772-5.495 0-2.873-2.064-4.882-5.012-4.882-3.414 0-5.418 2.561-5.418 5.207 0 1.031.397 2.138.893 2.738a.36.36 0 01.083.345l-.333 1.36c-.053.22-.174.267-.402.161-1.499-.698-2.436-2.889-2.436-4.649 0-3.785 2.75-7.262 7.929-7.262 4.163 0 7.398 2.967 7.398 6.931 0 4.136-2.607 7.464-6.227 7.464-1.216 0-2.359-.631-2.75-1.378l-.748 2.853c-.271 1.043-1.002 2.35-1.492 3.146C9.57 23.812 10.763 24 12 24c6.627 0 12-5.373 12-12 0-6.628-5.373-12-12-12z" }) });
588
+ default:
589
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("svg", { className: iconClass, viewBox: "0 0 24 24", fill: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("path", { d: "M12 2C6.48 2 2 6.48 2 12s4.48 10 10 10 10-4.48 10-10S17.52 2 12 2zm-1 17.93c-3.95-.49-7-3.85-7-7.93 0-.62.08-1.21.21-1.79L9 15v1c0 1.1.9 2 2 2v1.93zm6.9-2.54c-.26-.81-1-1.39-1.9-1.39h-1v-3c0-.55-.45-1-1-1H8v-2h2c.55 0 1-.45 1-1V7h2c1.1 0 2-.9 2-2v-.41c2.93 1.19 5 4.06 5 7.41 0 2.08-.8 3.97-2.1 5.39z" }) });
590
+ }
591
+ }
592
+
593
+ // src/components/connect-account/connect-account-button.tsx
594
+ var import_jsx_runtime3 = require("react/jsx-runtime");
595
+ function ConnectAccountButton({
596
+ network,
597
+ redirectUri,
598
+ tenantId,
599
+ apiKey,
600
+ baseUrl = "https://api.outstand.so/v1",
601
+ onSuccess,
602
+ onError,
603
+ className,
604
+ children,
605
+ variant = "default",
606
+ size = "md"
607
+ }) {
608
+ const { getAuthUrl, isLoading } = useOAuthFlow({ apiKey, baseUrl });
609
+ const handleConnect = async () => {
610
+ try {
611
+ const response = await getAuthUrl(network, redirectUri, tenantId);
612
+ if (response.success && response.data?.auth_url) {
613
+ onSuccess?.(response.data.auth_url);
614
+ window.location.href = response.data.auth_url;
615
+ } else {
616
+ throw new Error(response.error || "Failed to get authentication URL");
617
+ }
618
+ } catch (error) {
619
+ onError?.(error instanceof Error ? error : new Error("Connection failed"));
620
+ }
621
+ };
622
+ const networkColor = getNetworkColor(network);
623
+ const displayName = getNetworkDisplayName(network);
624
+ const sizeClasses = {
625
+ sm: "px-3 py-1.5 text-sm gap-1.5",
626
+ md: "px-4 py-2 text-sm gap-2",
627
+ lg: "px-6 py-3 text-base gap-2.5"
628
+ };
629
+ const variantClasses = {
630
+ default: "text-white hover:opacity-90",
631
+ outline: "bg-transparent border-2 hover:opacity-80",
632
+ ghost: "bg-transparent hover:bg-muted"
633
+ };
634
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
635
+ "button",
636
+ {
637
+ onClick: handleConnect,
638
+ disabled: isLoading,
639
+ className: cn(
640
+ "inline-flex items-center justify-center rounded-lg font-medium transition-all duration-200",
641
+ "focus:outline-none focus:ring-2 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed",
642
+ sizeClasses[size],
643
+ variantClasses[variant],
644
+ className
645
+ ),
646
+ style: {
647
+ backgroundColor: variant === "default" ? networkColor : "transparent",
648
+ borderColor: variant === "outline" ? networkColor : "transparent",
649
+ color: variant === "default" ? "white" : networkColor
650
+ },
651
+ children: [
652
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(NetworkIcon, { network, className: size === "sm" ? "h-4 w-4" : size === "lg" ? "h-6 w-6" : "h-5 w-5" }),
653
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "animate-pulse", children: "Connecting..." }) : children || /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { children: [
654
+ "Connect ",
655
+ displayName
656
+ ] })
657
+ ]
658
+ }
659
+ );
660
+ }
661
+
662
+ // src/components/connect-account/connect-account-button-group.tsx
663
+ var import_jsx_runtime4 = require("react/jsx-runtime");
664
+ function ConnectAccountButtonGroup({
665
+ networks,
666
+ redirectUri,
667
+ tenantId,
668
+ apiKey,
669
+ baseUrl,
670
+ onSuccess,
671
+ onError,
672
+ className,
673
+ variant = "default",
674
+ size = "md",
675
+ layout = "vertical"
676
+ }) {
677
+ const layoutClasses = {
678
+ horizontal: "flex flex-row flex-wrap gap-3",
679
+ vertical: "flex flex-col gap-3",
680
+ grid: "grid grid-cols-2 sm:grid-cols-3 gap-3"
681
+ };
682
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: cn(layoutClasses[layout], className), children: networks.map((network) => /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
683
+ ConnectAccountButton,
684
+ {
685
+ network,
686
+ redirectUri,
687
+ tenantId,
688
+ apiKey,
689
+ baseUrl,
690
+ variant,
691
+ size,
692
+ onSuccess: (authUrl) => onSuccess?.(network, authUrl),
693
+ onError: (error) => onError?.(network, error)
694
+ },
695
+ network
696
+ )) });
697
+ }
698
+
699
+ // src/components/media/media-uploader.tsx
700
+ var React7 = __toESM(require("react"), 1);
701
+
702
+ // src/components/media/media-preview.tsx
703
+ var import_jsx_runtime5 = require("react/jsx-runtime");
704
+ function MediaPreview({ media, onRemove, progress, className }) {
705
+ const isVideo = media.contentType?.startsWith("video/") || media.filename.match(/\.(mp4|mov|webm|avi)$/i);
706
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("relative group rounded-lg overflow-hidden bg-muted aspect-square", className), children: [
707
+ isVideo ? /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("video", { src: media.url, className: "w-full h-full object-cover", muted: true, playsInline: true }) : /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("img", { src: media.url || "/placeholder.svg", alt: media.filename, className: "w-full h-full object-cover" }),
708
+ progress !== void 0 && progress < 100 && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute inset-0 bg-background/80 flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: "w-16 h-16 relative", children: [
709
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("svg", { className: "w-full h-full transform -rotate-90", children: [
710
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("circle", { cx: "32", cy: "32", r: "28", fill: "none", stroke: "currentColor", strokeWidth: "4", className: "text-muted" }),
711
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
712
+ "circle",
713
+ {
714
+ cx: "32",
715
+ cy: "32",
716
+ r: "28",
717
+ fill: "none",
718
+ stroke: "currentColor",
719
+ strokeWidth: "4",
720
+ strokeDasharray: `${progress / 100 * 176} 176`,
721
+ className: "text-primary"
722
+ }
723
+ )
724
+ ] }),
725
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { className: "absolute inset-0 flex items-center justify-center text-xs font-medium", children: [
726
+ progress,
727
+ "%"
728
+ ] })
729
+ ] }) }),
730
+ onRemove && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)(
731
+ "button",
732
+ {
733
+ onClick: onRemove,
734
+ className: "absolute top-2 right-2 p-1.5 rounded-full bg-background/80 text-foreground opacity-0 group-hover:opacity-100 transition-opacity hover:bg-background",
735
+ children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("svg", { className: "h-4 w-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M6 18L18 6M6 6l12 12" }) })
736
+ }
737
+ ),
738
+ isVideo && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "absolute bottom-2 left-2 px-2 py-0.5 rounded bg-background/80 text-xs", children: "Video" })
739
+ ] });
740
+ }
741
+
742
+ // src/components/media/media-uploader.tsx
743
+ var import_jsx_runtime6 = require("react/jsx-runtime");
744
+ function MediaUploader({
745
+ apiKey,
746
+ baseUrl = "https://api.outstand.so/v1",
747
+ onUploadComplete,
748
+ onUploadError,
749
+ maxFiles = 4,
750
+ acceptedTypes = ["image/*", "video/*"],
751
+ className
752
+ }) {
753
+ const [files, setFiles] = React7.useState([]);
754
+ const [isDragging, setIsDragging] = React7.useState(false);
755
+ const fileInputRef = React7.useRef(null);
756
+ const { uploadFiles, isUploading, uploadProgress } = useMediaUpload({ apiKey, baseUrl });
757
+ const handleFileSelect = async (selectedFiles) => {
758
+ if (!selectedFiles) return;
759
+ const fileArray = Array.from(selectedFiles).slice(0, maxFiles - files.length);
760
+ try {
761
+ const uploadedFiles = await uploadFiles(fileArray);
762
+ const newFiles = [...files, ...uploadedFiles];
763
+ setFiles(newFiles);
764
+ onUploadComplete?.(newFiles);
765
+ } catch (error) {
766
+ onUploadError?.(error instanceof Error ? error : new Error("Upload failed"));
767
+ }
768
+ };
769
+ const handleDrop = (e) => {
770
+ e.preventDefault();
771
+ setIsDragging(false);
772
+ handleFileSelect(e.dataTransfer.files);
773
+ };
774
+ const handleDragOver = (e) => {
775
+ e.preventDefault();
776
+ setIsDragging(true);
777
+ };
778
+ const handleDragLeave = (e) => {
779
+ e.preventDefault();
780
+ setIsDragging(false);
781
+ };
782
+ const handleRemove = (index) => {
783
+ const newFiles = files.filter((_, i) => i !== index);
784
+ setFiles(newFiles);
785
+ onUploadComplete?.(newFiles);
786
+ };
787
+ const canAddMore = files.length < maxFiles;
788
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cn("space-y-4", className), children: [
789
+ canAddMore && /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)(
790
+ "div",
791
+ {
792
+ onDrop: handleDrop,
793
+ onDragOver: handleDragOver,
794
+ onDragLeave: handleDragLeave,
795
+ onClick: () => fileInputRef.current?.click(),
796
+ className: cn(
797
+ "border-2 border-dashed rounded-lg p-8 text-center cursor-pointer transition-colors",
798
+ isDragging ? "border-primary bg-primary/5" : "border-muted-foreground/25 hover:border-muted-foreground/50",
799
+ isUploading && "opacity-50 pointer-events-none"
800
+ ),
801
+ children: [
802
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
803
+ "input",
804
+ {
805
+ ref: fileInputRef,
806
+ type: "file",
807
+ multiple: true,
808
+ accept: acceptedTypes.join(","),
809
+ onChange: (e) => handleFileSelect(e.target.files),
810
+ className: "hidden"
811
+ }
812
+ ),
813
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: "flex flex-col items-center gap-2", children: [
814
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("svg", { className: "h-10 w-10 text-muted-foreground", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
815
+ "path",
816
+ {
817
+ strokeLinecap: "round",
818
+ strokeLinejoin: "round",
819
+ strokeWidth: 1.5,
820
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
821
+ }
822
+ ) }),
823
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-muted-foreground", children: isUploading ? "Uploading..." : "Drag & drop or click to upload" }),
824
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("p", { className: "text-xs text-muted-foreground/70", children: [
825
+ maxFiles - files.length,
826
+ " of ",
827
+ maxFiles,
828
+ " slots available"
829
+ ] })
830
+ ] })
831
+ ]
832
+ }
833
+ ),
834
+ files.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-3", children: files.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
835
+ MediaPreview,
836
+ {
837
+ media: file,
838
+ onRemove: () => handleRemove(index),
839
+ progress: uploadProgress[file.filename]
840
+ },
841
+ file.id
842
+ )) })
843
+ ] });
844
+ }
845
+
846
+ // src/components/media/media-gallery.tsx
847
+ var import_jsx_runtime7 = require("react/jsx-runtime");
848
+ function MediaGallery({ media, onRemove, className, columns = 4 }) {
849
+ const gridCols = {
850
+ 2: "grid-cols-2",
851
+ 3: "grid-cols-3",
852
+ 4: "grid-cols-2 sm:grid-cols-4"
853
+ };
854
+ if (media.length === 0) {
855
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn("border border-dashed rounded-lg p-8 text-center", className), children: /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { className: "text-sm text-muted-foreground", children: "No media uploaded" }) });
856
+ }
857
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { className: cn("grid gap-3", gridCols[columns], className), children: media.map((file, index) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(MediaPreview, { media: file, onRemove: onRemove ? () => onRemove(index) : void 0 }, file.id)) });
858
+ }
859
+
860
+ // src/components/media/media-list.tsx
861
+ var import_react = require("react");
862
+ var import_jsx_runtime8 = require("react/jsx-runtime");
863
+ function MediaList({
864
+ apiKey,
865
+ baseUrl,
866
+ pageSize = 20,
867
+ offset: initialOffset = 0,
868
+ onMediaSelect,
869
+ onMediaDelete,
870
+ className,
871
+ columns = 4,
872
+ selectable = false
873
+ }) {
874
+ const [offset, setOffset] = (0, import_react.useState)(initialOffset);
875
+ const [selectedMediaIds, setSelectedMediaIds] = (0, import_react.useState)(/* @__PURE__ */ new Set());
876
+ const { media, pagination, isLoading, error, refresh, deleteMedia } = useMediaList({
877
+ apiKey,
878
+ baseUrl,
879
+ limit: pageSize,
880
+ offset
881
+ });
882
+ const gridCols = {
883
+ 2: "grid-cols-2",
884
+ 3: "grid-cols-3",
885
+ 4: "grid-cols-2 sm:grid-cols-4"
886
+ };
887
+ const handleDelete = async (mediaId) => {
888
+ try {
889
+ await deleteMedia(mediaId);
890
+ onMediaDelete?.(mediaId);
891
+ } catch (err) {
892
+ console.error("Failed to delete media:", err);
893
+ }
894
+ };
895
+ const handleSelect = (mediaFile) => {
896
+ if (!selectable) {
897
+ onMediaSelect?.(mediaFile);
898
+ return;
899
+ }
900
+ const newSelected = new Set(selectedMediaIds);
901
+ if (newSelected.has(mediaFile.id)) {
902
+ newSelected.delete(mediaFile.id);
903
+ } else {
904
+ newSelected.add(mediaFile.id);
905
+ }
906
+ setSelectedMediaIds(newSelected);
907
+ onMediaSelect?.(mediaFile);
908
+ };
909
+ const totalCount = pagination?.count ?? 0;
910
+ const currentPage = Math.floor(offset / pageSize) + 1;
911
+ const totalPages = Math.ceil(totalCount / pageSize);
912
+ const goToPage = (page) => {
913
+ const newOffset = (page - 1) * pageSize;
914
+ setOffset(newOffset);
915
+ };
916
+ if (error) {
917
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("border border-destructive/50 rounded-lg p-6 text-center", className), children: [
918
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-sm text-destructive", children: "Failed to load media files" }),
919
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: refresh, className: "mt-2 text-sm text-primary hover:underline", children: "Try again" })
920
+ ] });
921
+ }
922
+ if (isLoading) {
923
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("space-y-4", className), children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("grid gap-3", gridCols[columns]), children: Array.from({ length: pageSize > 8 ? 8 : pageSize }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "aspect-square bg-muted animate-pulse rounded-lg" }, i)) }) });
924
+ }
925
+ if (media.length === 0) {
926
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("border border-dashed rounded-lg p-8 text-center", className), children: [
927
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
928
+ "svg",
929
+ {
930
+ className: "mx-auto h-12 w-12 text-muted-foreground/50",
931
+ fill: "none",
932
+ stroke: "currentColor",
933
+ viewBox: "0 0 24 24",
934
+ children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
935
+ "path",
936
+ {
937
+ strokeLinecap: "round",
938
+ strokeLinejoin: "round",
939
+ strokeWidth: 1.5,
940
+ d: "M4 16l4.586-4.586a2 2 0 012.828 0L16 16m-2-2l1.586-1.586a2 2 0 012.828 0L20 14m-6-6h.01M6 20h12a2 2 0 002-2V6a2 2 0 00-2-2H6a2 2 0 00-2 2v12a2 2 0 002 2z"
941
+ }
942
+ )
943
+ }
944
+ ),
945
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-2 text-sm text-muted-foreground", children: "No media files found" }),
946
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "mt-1 text-xs text-muted-foreground/70", children: "Upload media files to see them here" })
947
+ ] });
948
+ }
949
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: cn("space-y-4", className), children: [
950
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-between", children: [
951
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
952
+ totalCount,
953
+ " ",
954
+ totalCount === 1 ? "file" : "files"
955
+ ] }),
956
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { onClick: refresh, className: "text-sm text-primary hover:underline", children: "Refresh" })
957
+ ] }),
958
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: cn("grid gap-3", gridCols[columns]), children: media.map((file) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
959
+ "div",
960
+ {
961
+ className: cn(
962
+ "relative group",
963
+ selectable && "cursor-pointer",
964
+ selectable && selectedMediaIds.has(file.id) && "ring-2 ring-primary rounded-lg"
965
+ ),
966
+ onClick: () => handleSelect(file),
967
+ children: [
968
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(MediaPreview, { media: file, onRemove: onMediaDelete ? () => handleDelete(file.id) : void 0 }),
969
+ selectable && selectedMediaIds.has(file.id) && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "absolute top-2 left-2 w-5 h-5 bg-primary rounded-full flex items-center justify-center", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("svg", { className: "w-3 h-3 text-primary-foreground", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 3, d: "M5 13l4 4L19 7" }) }) }),
970
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "absolute bottom-0 left-0 right-0 p-2 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity rounded-b-lg", children: [
971
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-white truncate", children: file.filename }),
972
+ file.size && /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { className: "text-xs text-white/70", children: formatFileSize(file.size) })
973
+ ] })
974
+ ]
975
+ },
976
+ file.id
977
+ )) }),
978
+ totalPages > 1 && /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { className: "flex items-center justify-center gap-2 pt-4", children: [
979
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
980
+ "button",
981
+ {
982
+ onClick: () => goToPage(currentPage - 1),
983
+ disabled: currentPage === 1,
984
+ className: "px-3 py-1.5 text-sm border rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors",
985
+ children: "Previous"
986
+ }
987
+ ),
988
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className: "flex items-center gap-1", children: generatePageNumbers(currentPage, totalPages).map(
989
+ (page, idx) => page === "..." ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { className: "px-2 text-muted-foreground", children: "..." }, `ellipsis-${idx}`) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
990
+ "button",
991
+ {
992
+ onClick: () => goToPage(page),
993
+ className: cn(
994
+ "w-8 h-8 text-sm rounded-md transition-colors",
995
+ currentPage === page ? "bg-primary text-primary-foreground" : "hover:bg-muted"
996
+ ),
997
+ children: page
998
+ },
999
+ page
1000
+ )
1001
+ ) }),
1002
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
1003
+ "button",
1004
+ {
1005
+ onClick: () => goToPage(currentPage + 1),
1006
+ disabled: currentPage === totalPages,
1007
+ className: "px-3 py-1.5 text-sm border rounded-md disabled:opacity-50 disabled:cursor-not-allowed hover:bg-muted transition-colors",
1008
+ children: "Next"
1009
+ }
1010
+ )
1011
+ ] })
1012
+ ] });
1013
+ }
1014
+ function formatFileSize(bytes) {
1015
+ if (bytes < 1024) return `${bytes} B`;
1016
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)} KB`;
1017
+ return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;
1018
+ }
1019
+ function generatePageNumbers(current, total) {
1020
+ if (total <= 7) {
1021
+ return Array.from({ length: total }, (_, i) => i + 1);
1022
+ }
1023
+ const pages = [];
1024
+ if (current <= 4) {
1025
+ pages.push(1, 2, 3, 4, 5, "...", total);
1026
+ } else if (current >= total - 3) {
1027
+ pages.push(1, "...", total - 4, total - 3, total - 2, total - 1, total);
1028
+ } else {
1029
+ pages.push(1, "...", current - 1, current, current + 1, "...", total);
1030
+ }
1031
+ return pages;
1032
+ }
1033
+
1034
+ // src/components/posts/create-post-form.tsx
1035
+ var React10 = __toESM(require("react"), 1);
1036
+
1037
+ // src/components/posts/post-composer.tsx
1038
+ var import_jsx_runtime9 = require("react/jsx-runtime");
1039
+ var CHARACTER_LIMITS = {
1040
+ x: 280,
1041
+ twitter: 280,
1042
+ threads: 500,
1043
+ linkedin: 3e3,
1044
+ facebook: 63206,
1045
+ instagram: 2200,
1046
+ youtube: 5e3,
1047
+ tiktok: 2200,
1048
+ bluesky: 300,
1049
+ pinterest: 500
1050
+ };
1051
+ function PostComposer({
1052
+ value,
1053
+ onChange,
1054
+ selectedAccounts = [],
1055
+ placeholder = "What's happening?",
1056
+ maxLength,
1057
+ className
1058
+ }) {
1059
+ const networks = [...new Set(selectedAccounts.map((a) => a.network))];
1060
+ const effectiveMaxLength = maxLength || (networks.length > 0 ? Math.min(...networks.map((n) => CHARACTER_LIMITS[n] || 5e3)) : 5e3);
1061
+ const charCount = value.length;
1062
+ const isOverLimit = charCount > effectiveMaxLength;
1063
+ const warningThreshold = effectiveMaxLength * 0.9;
1064
+ const limitingNetworks = networks.filter((n) => CHARACTER_LIMITS[n] && value.length > CHARACTER_LIMITS[n]);
1065
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: cn("space-y-2", className), children: [
1066
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
1067
+ "textarea",
1068
+ {
1069
+ value,
1070
+ onChange: (e) => onChange(e.target.value),
1071
+ placeholder,
1072
+ rows: 4,
1073
+ className: cn(
1074
+ "w-full px-4 py-3 rounded-lg border resize-none transition-colors",
1075
+ "bg-background text-foreground placeholder:text-muted-foreground",
1076
+ "focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
1077
+ isOverLimit ? "border-destructive" : "border-input"
1078
+ )
1079
+ }
1080
+ ),
1081
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { className: "flex items-center justify-between text-sm", children: [
1082
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { className: "flex flex-wrap gap-2", children: limitingNetworks.map((network) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("span", { className: "text-destructive text-xs", children: [
1083
+ "Exceeds ",
1084
+ getNetworkDisplayName(network),
1085
+ " limit (",
1086
+ CHARACTER_LIMITS[network],
1087
+ ")"
1088
+ ] }, network)) }),
1089
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(
1090
+ "span",
1091
+ {
1092
+ className: cn(
1093
+ "font-mono",
1094
+ isOverLimit ? "text-destructive" : charCount >= warningThreshold ? "text-amber-500" : "text-muted-foreground"
1095
+ ),
1096
+ children: [
1097
+ charCount,
1098
+ "/",
1099
+ effectiveMaxLength
1100
+ ]
1101
+ }
1102
+ )
1103
+ ] })
1104
+ ] });
1105
+ }
1106
+
1107
+ // src/components/posts/network-selector.tsx
1108
+ var import_jsx_runtime10 = require("react/jsx-runtime");
1109
+ function NetworkSelector({ accounts, selectedIds, onChange, className }) {
1110
+ const toggleAccount = (id) => {
1111
+ if (selectedIds.includes(id)) {
1112
+ onChange(selectedIds.filter((i) => i !== id));
1113
+ } else {
1114
+ onChange([...selectedIds, id]);
1115
+ }
1116
+ };
1117
+ const selectAll = () => {
1118
+ onChange(accounts.map((a) => a.id));
1119
+ };
1120
+ const deselectAll = () => {
1121
+ onChange([]);
1122
+ };
1123
+ if (accounts.length === 0) {
1124
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: cn("border border-dashed rounded-lg p-6 text-center", className), children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "text-sm text-muted-foreground", children: "No accounts connected" }) });
1125
+ }
1126
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: cn("space-y-3", className), children: [
1127
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex gap-2 text-sm", children: [
1128
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: selectAll, className: "text-primary hover:underline", children: "Select all" }),
1129
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { className: "text-muted-foreground", children: "\xB7" }),
1130
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "button", onClick: deselectAll, className: "text-primary hover:underline", children: "Deselect all" })
1131
+ ] }),
1132
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className: "grid grid-cols-1 sm:grid-cols-2 gap-2", children: accounts.map((account) => {
1133
+ const isSelected = selectedIds.includes(account.id);
1134
+ const networkColor = getNetworkColor(account.network);
1135
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
1136
+ "button",
1137
+ {
1138
+ type: "button",
1139
+ onClick: () => toggleAccount(account.id),
1140
+ className: cn(
1141
+ "flex items-center gap-3 p-3 rounded-lg border transition-all",
1142
+ isSelected ? "border-primary bg-primary/5" : "border-input hover:border-muted-foreground/50"
1143
+ ),
1144
+ children: [
1145
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1146
+ "div",
1147
+ {
1148
+ className: "w-10 h-10 rounded-full flex items-center justify-center text-white",
1149
+ style: { backgroundColor: networkColor },
1150
+ children: account.profile_picture_url ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1151
+ "img",
1152
+ {
1153
+ src: account.profile_picture_url || "/placeholder.svg",
1154
+ alt: account.nickname,
1155
+ className: "w-full h-full rounded-full object-cover"
1156
+ }
1157
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(NetworkIcon, { network: account.network, className: "h-5 w-5" })
1158
+ }
1159
+ ),
1160
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { className: "flex-1 text-left min-w-0", children: [
1161
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { className: "font-medium truncate", children: account.nickname }),
1162
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { className: "text-xs text-muted-foreground truncate", children: [
1163
+ "@",
1164
+ account.username
1165
+ ] })
1166
+ ] }),
1167
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
1168
+ "div",
1169
+ {
1170
+ className: cn(
1171
+ "w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors",
1172
+ isSelected ? "border-primary bg-primary" : "border-muted-foreground/30"
1173
+ ),
1174
+ children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("svg", { className: "w-3 h-3 text-primary-foreground", fill: "currentColor", viewBox: "0 0 12 12", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("path", { d: "M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" }) })
1175
+ }
1176
+ )
1177
+ ]
1178
+ },
1179
+ account.id
1180
+ );
1181
+ }) })
1182
+ ] });
1183
+ }
1184
+
1185
+ // src/components/posts/network-config-panel.tsx
1186
+ var React8 = __toESM(require("react"), 1);
1187
+ var import_jsx_runtime11 = require("react/jsx-runtime");
1188
+ function NetworkConfigPanel({ networks, configs, onChange, className }) {
1189
+ const [activeTab, setActiveTab] = React8.useState(networks[0]);
1190
+ React8.useEffect(() => {
1191
+ if (!networks.includes(activeTab)) {
1192
+ setActiveTab(networks[0]);
1193
+ }
1194
+ }, [networks, activeTab]);
1195
+ const updateConfig = (network, key, value) => {
1196
+ const currentNetworkConfig = configs[network] || {};
1197
+ const newNetworkConfig = { ...currentNetworkConfig, [key]: value };
1198
+ onChange({
1199
+ ...configs,
1200
+ [network]: newNetworkConfig
1201
+ });
1202
+ };
1203
+ const renderConfig = (network) => {
1204
+ const networkConfig = configs[network] || {};
1205
+ switch (network) {
1206
+ case "threads":
1207
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "space-y-4", children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1208
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "block text-sm font-medium mb-2", children: "Reply Control" }),
1209
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1210
+ "select",
1211
+ {
1212
+ value: networkConfig.replyControl || "everyone",
1213
+ onChange: (e) => updateConfig(network, "replyControl", e.target.value),
1214
+ className: "w-full px-3 py-2 rounded-lg border border-input bg-background",
1215
+ children: [
1216
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "everyone", children: "Everyone" }),
1217
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "accounts_you_follow", children: "Accounts You Follow" }),
1218
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "mentioned_only", children: "Mentioned Only" })
1219
+ ]
1220
+ }
1221
+ )
1222
+ ] }) });
1223
+ case "instagram":
1224
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1225
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1226
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "block text-sm font-medium mb-2", children: "Media Type" }),
1227
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1228
+ "select",
1229
+ {
1230
+ value: networkConfig.mediaType || "FEED",
1231
+ onChange: (e) => updateConfig(network, "mediaType", e.target.value),
1232
+ className: "w-full px-3 py-2 rounded-lg border border-input bg-background",
1233
+ children: [
1234
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "FEED", children: "Feed Post" }),
1235
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "REELS", children: "Reels" }),
1236
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "STORIES", children: "Story" })
1237
+ ]
1238
+ }
1239
+ )
1240
+ ] }),
1241
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center gap-2", children: [
1242
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1243
+ "input",
1244
+ {
1245
+ type: "checkbox",
1246
+ checked: networkConfig.shareToFeed ?? true,
1247
+ onChange: (e) => updateConfig(network, "shareToFeed", e.target.checked),
1248
+ className: "rounded border-input"
1249
+ }
1250
+ ),
1251
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-sm", children: "Share to Feed" })
1252
+ ] })
1253
+ ] });
1254
+ case "youtube":
1255
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1256
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1257
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "block text-sm font-medium mb-2", children: "Video Title" }),
1258
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1259
+ "input",
1260
+ {
1261
+ type: "text",
1262
+ value: networkConfig.title || "",
1263
+ onChange: (e) => updateConfig(network, "title", e.target.value),
1264
+ placeholder: "Enter video title",
1265
+ className: "w-full px-3 py-2 rounded-lg border border-input bg-background"
1266
+ }
1267
+ )
1268
+ ] }),
1269
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1270
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "block text-sm font-medium mb-2", children: "Privacy" }),
1271
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1272
+ "select",
1273
+ {
1274
+ value: networkConfig.privacyStatus || "public",
1275
+ onChange: (e) => updateConfig(network, "privacyStatus", e.target.value),
1276
+ className: "w-full px-3 py-2 rounded-lg border border-input bg-background",
1277
+ children: [
1278
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "public", children: "Public" }),
1279
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "unlisted", children: "Unlisted" }),
1280
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "private", children: "Private" })
1281
+ ]
1282
+ }
1283
+ )
1284
+ ] })
1285
+ ] });
1286
+ case "tiktok":
1287
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-4", children: [
1288
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { children: [
1289
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("label", { className: "block text-sm font-medium mb-2", children: "Privacy Level" }),
1290
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1291
+ "select",
1292
+ {
1293
+ value: networkConfig.privacyLevel || "PUBLIC_TO_EVERYONE",
1294
+ onChange: (e) => updateConfig(network, "privacyLevel", e.target.value),
1295
+ className: "w-full px-3 py-2 rounded-lg border border-input bg-background",
1296
+ children: [
1297
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "PUBLIC_TO_EVERYONE", children: "Public" }),
1298
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "MUTUAL_FOLLOW_FRIENDS", children: "Friends" }),
1299
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("option", { value: "SELF_ONLY", children: "Private" })
1300
+ ]
1301
+ }
1302
+ )
1303
+ ] }),
1304
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: "space-y-2", children: [
1305
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center gap-2", children: [
1306
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1307
+ "input",
1308
+ {
1309
+ type: "checkbox",
1310
+ checked: networkConfig.disableDuet ?? false,
1311
+ onChange: (e) => updateConfig(network, "disableDuet", e.target.checked),
1312
+ className: "rounded border-input"
1313
+ }
1314
+ ),
1315
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-sm", children: "Disable Duet" })
1316
+ ] }),
1317
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center gap-2", children: [
1318
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1319
+ "input",
1320
+ {
1321
+ type: "checkbox",
1322
+ checked: networkConfig.disableStitch ?? false,
1323
+ onChange: (e) => updateConfig(network, "disableStitch", e.target.checked),
1324
+ className: "rounded border-input"
1325
+ }
1326
+ ),
1327
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-sm", children: "Disable Stitch" })
1328
+ ] }),
1329
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("label", { className: "flex items-center gap-2", children: [
1330
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1331
+ "input",
1332
+ {
1333
+ type: "checkbox",
1334
+ checked: networkConfig.disableComment ?? false,
1335
+ onChange: (e) => updateConfig(network, "disableComment", e.target.checked),
1336
+ className: "rounded border-input"
1337
+ }
1338
+ ),
1339
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { className: "text-sm", children: "Disable Comments" })
1340
+ ] })
1341
+ ] })
1342
+ ] });
1343
+ default:
1344
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1345
+ "No additional configuration available for ",
1346
+ getNetworkDisplayName(network)
1347
+ ] });
1348
+ }
1349
+ };
1350
+ if (networks.length === 0) return null;
1351
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { className: cn("border rounded-lg overflow-hidden", className), children: [
1352
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "flex border-b overflow-x-auto", children: networks.map((network) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1353
+ "button",
1354
+ {
1355
+ type: "button",
1356
+ onClick: () => setActiveTab(network),
1357
+ className: cn(
1358
+ "flex items-center gap-2 px-4 py-2.5 text-sm font-medium border-b-2 transition-colors whitespace-nowrap",
1359
+ activeTab === network ? "border-primary text-primary" : "border-transparent text-muted-foreground hover:text-foreground"
1360
+ ),
1361
+ children: [
1362
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(NetworkIcon, { network, className: "h-4 w-4" }),
1363
+ getNetworkDisplayName(network)
1364
+ ]
1365
+ },
1366
+ network
1367
+ )) }),
1368
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className: "p-4", children: renderConfig(activeTab) })
1369
+ ] });
1370
+ }
1371
+
1372
+ // src/components/posts/post-scheduler.tsx
1373
+ var React9 = __toESM(require("react"), 1);
1374
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1375
+ function PostScheduler({ value, onChange, className, minDate = /* @__PURE__ */ new Date(), maxDays = 30 }) {
1376
+ const [isScheduled, setIsScheduled] = React9.useState(!!value);
1377
+ const maxDate = /* @__PURE__ */ new Date();
1378
+ maxDate.setDate(maxDate.getDate() + maxDays);
1379
+ const handleToggle = () => {
1380
+ if (isScheduled) {
1381
+ setIsScheduled(false);
1382
+ onChange(null);
1383
+ } else {
1384
+ setIsScheduled(true);
1385
+ const defaultDate = /* @__PURE__ */ new Date();
1386
+ defaultDate.setHours(defaultDate.getHours() + 1, 0, 0, 0);
1387
+ onChange(defaultDate);
1388
+ }
1389
+ };
1390
+ const handleDateChange = (e) => {
1391
+ const date = new Date(e.target.value);
1392
+ onChange(date);
1393
+ };
1394
+ const formatDateTimeLocal = (date) => {
1395
+ const offset = date.getTimezoneOffset();
1396
+ const adjustedDate = new Date(date.getTime() - offset * 60 * 1e3);
1397
+ return adjustedDate.toISOString().slice(0, 16);
1398
+ };
1399
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { className: cn("space-y-3", className), children: [
1400
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("label", { className: "flex items-center gap-3", children: [
1401
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1402
+ "button",
1403
+ {
1404
+ type: "button",
1405
+ onClick: handleToggle,
1406
+ className: cn("relative w-11 h-6 rounded-full transition-colors", isScheduled ? "bg-primary" : "bg-muted"),
1407
+ children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1408
+ "span",
1409
+ {
1410
+ className: cn(
1411
+ "absolute top-0.5 left-0.5 w-5 h-5 rounded-full bg-white shadow transition-transform",
1412
+ isScheduled && "translate-x-5"
1413
+ )
1414
+ }
1415
+ )
1416
+ }
1417
+ ),
1418
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { className: "text-sm font-medium", children: "Schedule for later" })
1419
+ ] }),
1420
+ isScheduled && value && /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { className: "flex gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1421
+ "input",
1422
+ {
1423
+ type: "datetime-local",
1424
+ value: formatDateTimeLocal(value),
1425
+ onChange: handleDateChange,
1426
+ min: formatDateTimeLocal(minDate),
1427
+ max: formatDateTimeLocal(maxDate),
1428
+ className: "flex-1 px-3 py-2 rounded-lg border border-input bg-background text-sm"
1429
+ }
1430
+ ) }),
1431
+ isScheduled && /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
1432
+ "Posts can be scheduled up to ",
1433
+ maxDays,
1434
+ " days in advance"
1435
+ ] })
1436
+ ] });
1437
+ }
1438
+
1439
+ // src/components/posts/create-post-form.tsx
1440
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1441
+ function CreatePostForm({
1442
+ apiKey,
1443
+ baseUrl = "https://api.outstand.so/v1",
1444
+ accounts: providedAccounts,
1445
+ onPostCreated,
1446
+ onError,
1447
+ className,
1448
+ enableScheduling = true,
1449
+ enableDrafts = false
1450
+ }) {
1451
+ const [content, setContent] = React10.useState("");
1452
+ const [selectedAccountIds, setSelectedAccountIds] = React10.useState([]);
1453
+ const [media, setMedia] = React10.useState([]);
1454
+ const [scheduledAt, setScheduledAt] = React10.useState(null);
1455
+ const [networkConfigs, setNetworkConfigs] = React10.useState({});
1456
+ const [isSubmitting, setIsSubmitting] = React10.useState(false);
1457
+ const { accounts: fetchedAccounts } = useAccounts({
1458
+ apiKey,
1459
+ baseUrl,
1460
+ limit: 100
1461
+ });
1462
+ const { createPost } = usePosts({ apiKey, baseUrl });
1463
+ const accounts = providedAccounts || fetchedAccounts;
1464
+ const selectedAccounts = accounts.filter((a) => selectedAccountIds.includes(a.id));
1465
+ const uniqueNetworks = [...new Set(selectedAccounts.map((a) => a.network))];
1466
+ const handleSubmit = async (e) => {
1467
+ e.preventDefault();
1468
+ if (!content.trim() && media.length === 0) {
1469
+ onError?.(new Error("Please enter content or upload media"));
1470
+ return;
1471
+ }
1472
+ if (selectedAccountIds.length === 0) {
1473
+ onError?.(new Error("Please select at least one account"));
1474
+ return;
1475
+ }
1476
+ setIsSubmitting(true);
1477
+ try {
1478
+ const accountIdentifiers = selectedAccounts.map((account) => account.username);
1479
+ const postData = {
1480
+ accounts: accountIdentifiers,
1481
+ ...scheduledAt && { scheduledAt: scheduledAt.toISOString() }
1482
+ };
1483
+ if (networkConfigs.threads && Object.keys(networkConfigs.threads).length > 0) {
1484
+ postData.threads = networkConfigs.threads;
1485
+ }
1486
+ if (networkConfigs.instagram && Object.keys(networkConfigs.instagram).length > 0) {
1487
+ postData.instagram = networkConfigs.instagram;
1488
+ }
1489
+ if (networkConfigs.youtube && Object.keys(networkConfigs.youtube).length > 0) {
1490
+ postData.youtube = networkConfigs.youtube;
1491
+ }
1492
+ if (networkConfigs.tiktok && Object.keys(networkConfigs.tiktok).length > 0) {
1493
+ postData.tiktok = networkConfigs.tiktok;
1494
+ }
1495
+ if (media.length > 0) {
1496
+ postData.containers = [
1497
+ {
1498
+ content,
1499
+ media: media.map((m) => ({
1500
+ id: m.id,
1501
+ url: m.url,
1502
+ filename: m.filename
1503
+ }))
1504
+ }
1505
+ ];
1506
+ } else {
1507
+ postData.content = content;
1508
+ }
1509
+ const response = await createPost(postData);
1510
+ if (response.success && response.data?.post) {
1511
+ onPostCreated?.(response.data.post);
1512
+ setContent("");
1513
+ setSelectedAccountIds([]);
1514
+ setMedia([]);
1515
+ setScheduledAt(null);
1516
+ setNetworkConfigs({});
1517
+ } else {
1518
+ throw new Error(response.error || "Failed to create post");
1519
+ }
1520
+ } catch (error) {
1521
+ onError?.(error instanceof Error ? error : new Error("Failed to create post"));
1522
+ } finally {
1523
+ setIsSubmitting(false);
1524
+ }
1525
+ };
1526
+ const handleNetworkConfigChange = (configs) => {
1527
+ setNetworkConfigs(configs);
1528
+ };
1529
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("form", { onSubmit: handleSubmit, className: cn("space-y-6", className), children: [
1530
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4", children: [
1531
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { className: "block text-sm font-medium", children: "Select Accounts" }),
1532
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NetworkSelector, { accounts, selectedIds: selectedAccountIds, onChange: setSelectedAccountIds })
1533
+ ] }),
1534
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4", children: [
1535
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { className: "block text-sm font-medium", children: "Content" }),
1536
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1537
+ PostComposer,
1538
+ {
1539
+ value: content,
1540
+ onChange: setContent,
1541
+ selectedAccounts,
1542
+ placeholder: "What's on your mind?"
1543
+ }
1544
+ )
1545
+ ] }),
1546
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4", children: [
1547
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { className: "block text-sm font-medium", children: "Media" }),
1548
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1549
+ MediaUploader,
1550
+ {
1551
+ apiKey,
1552
+ baseUrl,
1553
+ onUploadComplete: setMedia,
1554
+ onUploadError: (error) => onError?.(error),
1555
+ maxFiles: 4
1556
+ }
1557
+ )
1558
+ ] }),
1559
+ uniqueNetworks.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4", children: [
1560
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { className: "block text-sm font-medium", children: "Network-Specific Options" }),
1561
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(NetworkConfigPanel, { networks: uniqueNetworks, configs: networkConfigs, onChange: handleNetworkConfigChange })
1562
+ ] }),
1563
+ enableScheduling && /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { className: "space-y-4", children: [
1564
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("label", { className: "block text-sm font-medium", children: "Schedule" }),
1565
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(PostScheduler, { value: scheduledAt, onChange: setScheduledAt })
1566
+ ] }),
1567
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { className: "flex gap-3", children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1568
+ "button",
1569
+ {
1570
+ type: "submit",
1571
+ disabled: isSubmitting || !content.trim() && media.length === 0 || selectedAccountIds.length === 0,
1572
+ className: cn(
1573
+ "flex-1 px-4 py-2.5 rounded-lg font-medium transition-colors",
1574
+ "bg-primary text-primary-foreground hover:bg-primary/90",
1575
+ "disabled:opacity-50 disabled:cursor-not-allowed"
1576
+ ),
1577
+ children: isSubmitting ? "Posting..." : scheduledAt ? "Schedule Post" : "Post Now"
1578
+ }
1579
+ ) })
1580
+ ] });
1581
+ }
1582
+
1583
+ // src/components/accounts/accounts-list.tsx
1584
+ var React12 = __toESM(require("react"), 1);
1585
+
1586
+ // src/components/accounts/account-card.tsx
1587
+ var React11 = __toESM(require("react"), 1);
1588
+ var import_jsx_runtime14 = require("react/jsx-runtime");
1589
+ function AccountCard({ account, onSelect, onDisconnect, className }) {
1590
+ const [showConfirm, setShowConfirm] = React11.useState(false);
1591
+ const networkColor = getNetworkColor(account.network);
1592
+ const handleDisconnect = () => {
1593
+ if (showConfirm) {
1594
+ onDisconnect?.();
1595
+ setShowConfirm(false);
1596
+ } else {
1597
+ setShowConfirm(true);
1598
+ }
1599
+ };
1600
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1601
+ "div",
1602
+ {
1603
+ className: cn(
1604
+ "flex items-center gap-4 p-4 rounded-lg border border-input bg-card transition-colors",
1605
+ onSelect && "cursor-pointer hover:border-muted-foreground/50",
1606
+ className
1607
+ ),
1608
+ onClick: onSelect,
1609
+ children: [
1610
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1611
+ "div",
1612
+ {
1613
+ className: "w-12 h-12 rounded-full flex items-center justify-center shrink-0",
1614
+ style: { backgroundColor: networkColor },
1615
+ children: account.profile_picture_url ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1616
+ "img",
1617
+ {
1618
+ src: account.profile_picture_url || "/placeholder.svg",
1619
+ alt: account.nickname,
1620
+ className: "w-full h-full rounded-full object-cover"
1621
+ }
1622
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(NetworkIcon, { network: account.network, className: "h-6 w-6 text-white" })
1623
+ }
1624
+ ),
1625
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex-1 min-w-0", children: [
1626
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex items-center gap-2", children: [
1627
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("h3", { className: "font-medium truncate", children: account.nickname }),
1628
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { className: "px-2 py-0.5 text-xs rounded-full text-white", style: { backgroundColor: networkColor }, children: getNetworkDisplayName(account.network) })
1629
+ ] }),
1630
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-sm text-muted-foreground truncate", children: [
1631
+ "@",
1632
+ account.username
1633
+ ] }),
1634
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("p", { className: "text-xs text-muted-foreground mt-1", children: [
1635
+ "Connected ",
1636
+ formatDate(account.createdAt)
1637
+ ] })
1638
+ ] }),
1639
+ onDisconnect && /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("div", { onClick: (e) => e.stopPropagation(), children: showConfirm ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { className: "flex gap-2", children: [
1640
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1641
+ "button",
1642
+ {
1643
+ onClick: handleDisconnect,
1644
+ className: "px-3 py-1.5 text-xs font-medium rounded bg-destructive text-destructive-foreground hover:bg-destructive/90",
1645
+ children: "Confirm"
1646
+ }
1647
+ ),
1648
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1649
+ "button",
1650
+ {
1651
+ onClick: () => setShowConfirm(false),
1652
+ className: "px-3 py-1.5 text-xs font-medium rounded border border-input hover:bg-accent",
1653
+ children: "Cancel"
1654
+ }
1655
+ )
1656
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1657
+ "button",
1658
+ {
1659
+ onClick: handleDisconnect,
1660
+ className: "px-3 py-1.5 text-xs font-medium rounded border border-input text-muted-foreground hover:text-destructive hover:border-destructive",
1661
+ children: "Disconnect"
1662
+ }
1663
+ ) })
1664
+ ]
1665
+ }
1666
+ );
1667
+ }
1668
+
1669
+ // src/components/accounts/accounts-list.tsx
1670
+ var import_jsx_runtime15 = require("react/jsx-runtime");
1671
+ function AccountsList({
1672
+ apiKey,
1673
+ baseUrl = "https://api.outstand.so/v1",
1674
+ tenantId,
1675
+ onAccountSelect,
1676
+ onAccountDisconnect,
1677
+ className,
1678
+ pageSize = 10
1679
+ }) {
1680
+ const [offset, setOffset] = React12.useState(0);
1681
+ const { accounts, total, isLoading, error, disconnectAccount, hasNextPage, hasPrevPage } = useAccounts({
1682
+ apiKey,
1683
+ baseUrl,
1684
+ tenantId,
1685
+ limit: pageSize,
1686
+ offset
1687
+ });
1688
+ const handleDisconnect = async (accountId) => {
1689
+ const result = await disconnectAccount(accountId);
1690
+ if (result.success) {
1691
+ onAccountDisconnect?.(accountId);
1692
+ }
1693
+ };
1694
+ const handleNextPage = () => {
1695
+ setOffset((prev) => prev + pageSize);
1696
+ };
1697
+ const handlePrevPage = () => {
1698
+ setOffset((prev) => Math.max(0, prev - pageSize));
1699
+ };
1700
+ if (isLoading && accounts.length === 0) {
1701
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn("space-y-3", className), children: [...Array(3)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "h-20 rounded-lg bg-muted animate-pulse" }, i)) });
1702
+ }
1703
+ if (error) {
1704
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn("border border-destructive/50 rounded-lg p-6 text-center", className), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-destructive", children: "Failed to load accounts" }) });
1705
+ }
1706
+ if (accounts.length === 0) {
1707
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: cn("border border-dashed rounded-lg p-8 text-center", className), children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { className: "text-muted-foreground", children: "No accounts connected" }) });
1708
+ }
1709
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: cn("space-y-4", className), children: [
1710
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { className: "space-y-3", children: accounts.map((account) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1711
+ AccountCard,
1712
+ {
1713
+ account,
1714
+ onSelect: () => onAccountSelect?.(account),
1715
+ onDisconnect: () => handleDisconnect(account.id)
1716
+ },
1717
+ account.id
1718
+ )) }),
1719
+ total > pageSize && /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex items-center justify-between pt-4 border-t", children: [
1720
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
1721
+ "Showing ",
1722
+ offset + 1,
1723
+ "-",
1724
+ Math.min(offset + pageSize, total),
1725
+ " of ",
1726
+ total
1727
+ ] }),
1728
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { className: "flex gap-2", children: [
1729
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1730
+ "button",
1731
+ {
1732
+ onClick: handlePrevPage,
1733
+ disabled: !hasPrevPage,
1734
+ className: "px-3 py-1.5 text-sm rounded-lg border border-input hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed",
1735
+ children: "Previous"
1736
+ }
1737
+ ),
1738
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
1739
+ "button",
1740
+ {
1741
+ onClick: handleNextPage,
1742
+ disabled: !hasNextPage,
1743
+ className: "px-3 py-1.5 text-sm rounded-lg border border-input hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed",
1744
+ children: "Next"
1745
+ }
1746
+ )
1747
+ ] })
1748
+ ] })
1749
+ ] });
1750
+ }
1751
+
1752
+ // src/components/oauth/oauth-callback.tsx
1753
+ var React14 = __toESM(require("react"), 1);
1754
+
1755
+ // src/components/oauth/page-selector.tsx
1756
+ var React13 = __toESM(require("react"), 1);
1757
+ var import_jsx_runtime16 = require("react/jsx-runtime");
1758
+ function PageSelector({ network, pages, onSelect, isLoading, className }) {
1759
+ const [selectedIds, setSelectedIds] = React13.useState([]);
1760
+ const networkColor = getNetworkColor(network);
1761
+ const togglePage = (id) => {
1762
+ setSelectedIds((prev) => prev.includes(id) ? prev.filter((i) => i !== id) : [...prev, id]);
1763
+ };
1764
+ const handleConfirm = () => {
1765
+ onSelect(selectedIds);
1766
+ };
1767
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: cn("space-y-6", className), children: [
1768
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "text-center", children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1770
+ "div",
1771
+ {
1772
+ className: "w-16 h-16 mx-auto rounded-full flex items-center justify-center text-white mb-4",
1773
+ style: { backgroundColor: networkColor },
1774
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NetworkIcon, { network, className: "h-8 w-8" })
1775
+ }
1776
+ ),
1777
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("h2", { className: "text-xl font-semibold", children: [
1778
+ "Connect ",
1779
+ getNetworkDisplayName(network)
1780
+ ] }),
1781
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "text-muted-foreground mt-1", children: "Select the pages or accounts you want to connect" })
1782
+ ] }),
1783
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "space-y-2", children: pages.map((page) => {
1784
+ const isSelected = selectedIds.includes(page.id);
1785
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
1786
+ "button",
1787
+ {
1788
+ type: "button",
1789
+ onClick: () => togglePage(page.id),
1790
+ className: cn(
1791
+ "w-full flex items-center gap-3 p-4 rounded-lg border transition-all text-left",
1792
+ isSelected ? "border-primary bg-primary/5" : "border-input hover:border-muted-foreground/50"
1793
+ ),
1794
+ children: [
1795
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "w-10 h-10 rounded-full bg-muted overflow-hidden shrink-0", children: page.profilePictureUrl ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1796
+ "img",
1797
+ {
1798
+ src: page.profilePictureUrl || "/placeholder.svg",
1799
+ alt: page.name,
1800
+ className: "w-full h-full object-cover"
1801
+ }
1802
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { className: "w-full h-full flex items-center justify-center text-muted-foreground", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(NetworkIcon, { network, className: "h-5 w-5" }) }) }),
1803
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { className: "flex-1 min-w-0", children: [
1804
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { className: "font-medium truncate", children: page.name }),
1805
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("p", { className: "text-sm text-muted-foreground truncate", children: [
1806
+ "@",
1807
+ page.username,
1808
+ " \xB7 ",
1809
+ page.type
1810
+ ] })
1811
+ ] }),
1812
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1813
+ "div",
1814
+ {
1815
+ className: cn(
1816
+ "w-5 h-5 rounded-full border-2 flex items-center justify-center transition-colors shrink-0",
1817
+ isSelected ? "border-primary bg-primary" : "border-muted-foreground/30"
1818
+ ),
1819
+ children: isSelected && /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("svg", { className: "w-3 h-3 text-primary-foreground", fill: "currentColor", viewBox: "0 0 12 12", children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("path", { d: "M10.28 2.28L3.989 8.575 1.695 6.28A1 1 0 00.28 7.695l3 3a1 1 0 001.414 0l7-7A1 1 0 0010.28 2.28z" }) })
1820
+ }
1821
+ )
1822
+ ]
1823
+ },
1824
+ page.id
1825
+ );
1826
+ }) }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
1828
+ "button",
1829
+ {
1830
+ onClick: handleConfirm,
1831
+ disabled: selectedIds.length === 0 || isLoading,
1832
+ className: cn(
1833
+ "w-full py-3 rounded-lg font-medium text-white transition-colors",
1834
+ "disabled:opacity-50 disabled:cursor-not-allowed"
1835
+ ),
1836
+ style: { backgroundColor: networkColor },
1837
+ children: isLoading ? "Connecting..." : `Connect ${selectedIds.length} Account${selectedIds.length !== 1 ? "s" : ""}`
1838
+ }
1839
+ )
1840
+ ] });
1841
+ }
1842
+
1843
+ // src/components/oauth/oauth-callback.tsx
1844
+ var import_jsx_runtime17 = require("react/jsx-runtime");
1845
+ function OAuthCallback({
1846
+ apiKey,
1847
+ baseUrl = "https://api.outstand.so/v1",
1848
+ onSuccess,
1849
+ onError,
1850
+ className
1851
+ }) {
1852
+ const [sessionToken, setSessionToken] = React14.useState(null);
1853
+ const [pendingData, setPendingData] = React14.useState(null);
1854
+ const [isLoading, setIsLoading] = React14.useState(true);
1855
+ const [error, setError] = React14.useState(null);
1856
+ const { getPendingConnection, finalizeConnection } = useOAuthFlow({ apiKey, baseUrl });
1857
+ React14.useEffect(() => {
1858
+ const params = new URLSearchParams(window.location.search);
1859
+ const session = params.get("session");
1860
+ if (!session) {
1861
+ setError("No session token found in URL");
1862
+ setIsLoading(false);
1863
+ return;
1864
+ }
1865
+ setSessionToken(session);
1866
+ loadPendingConnection(session);
1867
+ }, []);
1868
+ const loadPendingConnection = async (token) => {
1869
+ try {
1870
+ const response = await getPendingConnection(token);
1871
+ if (response.success && response.data) {
1872
+ setPendingData(response.data);
1873
+ } else {
1874
+ throw new Error(response.error || "Failed to load connection details");
1875
+ }
1876
+ } catch (err) {
1877
+ const errorMessage = err instanceof Error ? err.message : "Connection failed";
1878
+ setError(errorMessage);
1879
+ onError?.(new Error(errorMessage));
1880
+ } finally {
1881
+ setIsLoading(false);
1882
+ }
1883
+ };
1884
+ const handleFinalize = async (selectedPageIds) => {
1885
+ if (!sessionToken) return;
1886
+ setIsLoading(true);
1887
+ try {
1888
+ const response = await finalizeConnection(sessionToken, selectedPageIds);
1889
+ if (response.success && response.data?.connectedAccounts) {
1890
+ onSuccess?.(response.data.connectedAccounts);
1891
+ } else {
1892
+ throw new Error(response.error || "Failed to connect accounts");
1893
+ }
1894
+ } catch (err) {
1895
+ const errorMessage = err instanceof Error ? err.message : "Connection failed";
1896
+ setError(errorMessage);
1897
+ onError?.(new Error(errorMessage));
1898
+ } finally {
1899
+ setIsLoading(false);
1900
+ }
1901
+ };
1902
+ if (isLoading && !pendingData) {
1903
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("flex flex-col items-center justify-center p-8", className), children: [
1904
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("div", { className: "w-8 h-8 border-2 border-primary border-t-transparent rounded-full animate-spin" }),
1905
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "mt-4 text-muted-foreground", children: "Loading connection details..." })
1906
+ ] });
1907
+ }
1908
+ if (error) {
1909
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsxs)("div", { className: cn("border border-destructive/50 rounded-lg p-6 text-center", className), children: [
1910
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("svg", { className: "w-12 h-12 mx-auto text-destructive mb-4", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1911
+ "path",
1912
+ {
1913
+ strokeLinecap: "round",
1914
+ strokeLinejoin: "round",
1915
+ strokeWidth: 2,
1916
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
1917
+ }
1918
+ ) }),
1919
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("h3", { className: "text-lg font-semibold text-destructive mb-2", children: "Connection Failed" }),
1920
+ /* @__PURE__ */ (0, import_jsx_runtime17.jsx)("p", { className: "text-muted-foreground", children: error })
1921
+ ] });
1922
+ }
1923
+ if (pendingData) {
1924
+ return /* @__PURE__ */ (0, import_jsx_runtime17.jsx)(
1925
+ PageSelector,
1926
+ {
1927
+ network: pendingData.network,
1928
+ pages: pendingData.availablePages,
1929
+ onSelect: handleFinalize,
1930
+ isLoading,
1931
+ className
1932
+ }
1933
+ );
1934
+ }
1935
+ return null;
1936
+ }
1937
+
1938
+ // src/components/metrics/post-metrics-card.tsx
1939
+ var import_jsx_runtime18 = require("react/jsx-runtime");
1940
+ function PostMetricsCard({ account, metrics, platformPostId, publishedAt, className }) {
1941
+ if (!account || !metrics) {
1942
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: cn("p-4 rounded-lg border bg-card text-center text-muted-foreground", className), children: "No metrics data available" });
1943
+ }
1944
+ const networkColor = getNetworkColor(account.network);
1945
+ const likes = metrics.likes ?? 0;
1946
+ const comments = metrics.comments ?? 0;
1947
+ const shares = metrics.shares ?? 0;
1948
+ const reach = metrics.reach ?? 0;
1949
+ const saves = metrics.saves ?? 0;
1950
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: cn("p-4 rounded-lg border bg-card", className), children: [
1951
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex items-center gap-3 mb-4", children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1953
+ "div",
1954
+ {
1955
+ className: "w-10 h-10 rounded-full flex items-center justify-center text-white",
1956
+ style: { backgroundColor: networkColor },
1957
+ children: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(NetworkIcon, { network: account.network, className: "h-5 w-5" })
1958
+ }
1959
+ ),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "flex-1 min-w-0", children: [
1961
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "font-medium truncate", children: account.nickname || "Unknown" }),
1962
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("p", { className: "text-xs text-muted-foreground", children: [
1963
+ "@",
1964
+ account.username || "unknown",
1965
+ " \xB7 ",
1966
+ getNetworkDisplayName(account.network)
1967
+ ] })
1968
+ ] }),
1969
+ publishedAt && /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-xs text-muted-foreground", children: formatDate(publishedAt) })
1970
+ ] }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "grid grid-cols-3 sm:grid-cols-5 gap-3", children: [
1972
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MetricItem, { icon: "heart", label: "Likes", value: likes }),
1973
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MetricItem, { icon: "chat", label: "Comments", value: comments }),
1974
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MetricItem, { icon: "share", label: "Shares", value: shares }),
1975
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MetricItem, { icon: "users", label: "Reach", value: reach }),
1976
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(MetricItem, { icon: "bookmark", label: "Saves", value: saves })
1977
+ ] }),
1978
+ metrics.platform_specific && Object.keys(metrics.platform_specific).length > 0 && /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "mt-4 pt-4 border-t", children: [
1979
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-xs text-muted-foreground mb-2", children: "Platform Specific" }),
1980
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("div", { className: "flex flex-wrap gap-3", children: Object.entries(metrics.platform_specific).map(([key, value]) => /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "text-sm", children: [
1981
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("span", { className: "text-muted-foreground capitalize", children: [
1982
+ key.replace(/_/g, " "),
1983
+ ":"
1984
+ ] }),
1985
+ " ",
1986
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("span", { className: "font-medium", children: typeof value === "number" ? formatNumber(value) : String(value ?? "N/A") })
1987
+ ] }, key)) })
1988
+ ] })
1989
+ ] });
1990
+ }
1991
+ function MetricItem({
1992
+ icon,
1993
+ label,
1994
+ value
1995
+ }) {
1996
+ const icons = {
1997
+ heart: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
1998
+ "path",
1999
+ {
2000
+ strokeLinecap: "round",
2001
+ strokeLinejoin: "round",
2002
+ strokeWidth: 2,
2003
+ d: "M4.318 6.318a4.5 4.5 0 000 6.364L12 20.364l7.682-7.682a4.5 4.5 0 00-6.364-6.364L12 7.636l-1.318-1.318a4.5 4.5 0 00-6.364 0z"
2004
+ }
2005
+ ),
2006
+ chat: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2007
+ "path",
2008
+ {
2009
+ strokeLinecap: "round",
2010
+ strokeLinejoin: "round",
2011
+ strokeWidth: 2,
2012
+ d: "M8 12h.01M12 12h.01M16 12h.01M21 12c0 4.418-4.03 8-9 8a9.863 9.863 0 01-4.255-.949L3 20l1.395-3.72C3.512 15.042 3 13.574 3 12c0-4.418 4.03-8 9-8s9 3.582 9 8z"
2013
+ }
2014
+ ),
2015
+ share: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2016
+ "path",
2017
+ {
2018
+ strokeLinecap: "round",
2019
+ strokeLinejoin: "round",
2020
+ strokeWidth: 2,
2021
+ d: "M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z"
2022
+ }
2023
+ ),
2024
+ eye: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2025
+ "path",
2026
+ {
2027
+ strokeLinecap: "round",
2028
+ strokeLinejoin: "round",
2029
+ strokeWidth: 2,
2030
+ d: "M15 12a3 3 0 11-6 0 3 3 0 016 0z M2.458 12C3.732 7.943 7.523 5 12 5c4.478 0 8.268 2.943 9.542 7-1.274 4.057-5.064 7-9.542 7-4.477 0-8.268-2.943-9.542-7z"
2031
+ }
2032
+ ),
2033
+ chart: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2034
+ "path",
2035
+ {
2036
+ strokeLinecap: "round",
2037
+ strokeLinejoin: "round",
2038
+ strokeWidth: 2,
2039
+ d: "M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
2040
+ }
2041
+ ),
2042
+ users: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2043
+ "path",
2044
+ {
2045
+ strokeLinecap: "round",
2046
+ strokeLinejoin: "round",
2047
+ strokeWidth: 2,
2048
+ d: "M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z"
2049
+ }
2050
+ ),
2051
+ bookmark: /* @__PURE__ */ (0, import_jsx_runtime18.jsx)(
2052
+ "path",
2053
+ {
2054
+ strokeLinecap: "round",
2055
+ strokeLinejoin: "round",
2056
+ strokeWidth: 2,
2057
+ d: "M5 5a2 2 0 012-2h10a2 2 0 012 2v16l-7-3.5L5 21V5z"
2058
+ }
2059
+ )
2060
+ };
2061
+ return /* @__PURE__ */ (0, import_jsx_runtime18.jsxs)("div", { className: "text-center", children: [
2062
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("svg", { className: "w-5 h-5 mx-auto text-muted-foreground mb-1", fill: "none", viewBox: "0 0 24 24", stroke: "currentColor", children: icons[icon] }),
2063
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "font-semibold", children: formatNumber(value ?? 0) }),
2064
+ /* @__PURE__ */ (0, import_jsx_runtime18.jsx)("p", { className: "text-xs text-muted-foreground", children: label })
2065
+ ] });
2066
+ }
2067
+
2068
+ // src/components/metrics/post-metrics.tsx
2069
+ var import_jsx_runtime19 = require("react/jsx-runtime");
2070
+ function PostMetrics({
2071
+ apiKey,
2072
+ baseUrl = "https://api.outstand.so/v1",
2073
+ postId,
2074
+ className,
2075
+ refreshInterval
2076
+ }) {
2077
+ const { analytics, isLoading, error } = usePostMetrics({
2078
+ apiKey,
2079
+ baseUrl,
2080
+ postId,
2081
+ refreshInterval
2082
+ });
2083
+ if (isLoading) {
2084
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: cn("space-y-4", className), children: [
2085
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "h-32 rounded-lg bg-muted animate-pulse" }),
2086
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "grid grid-cols-2 gap-4", children: [...Array(4)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "h-20 rounded-lg bg-muted animate-pulse" }, i)) })
2087
+ ] });
2088
+ }
2089
+ if (error || !analytics) {
2090
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: cn("border border-destructive/50 rounded-lg p-6 text-center", className), children: /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-destructive", children: "Failed to load metrics" }) });
2091
+ }
2092
+ const { aggregated_metrics, metrics_by_account = [] } = analytics;
2093
+ const {
2094
+ total_likes = 0,
2095
+ total_comments = 0,
2096
+ total_shares = 0,
2097
+ total_reach = 0,
2098
+ total_views = 0,
2099
+ total_impressions = 0,
2100
+ average_engagement_rate = 0
2101
+ } = aggregated_metrics ?? {};
2102
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: cn("space-y-6", className), children: [
2103
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "p-6 rounded-lg border bg-card", children: [
2104
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "text-lg font-semibold mb-4", children: "Overall Performance" }),
2105
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "grid grid-cols-2 sm:grid-cols-4 gap-4", children: [
2106
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(MetricBox, { label: "Total Likes", value: total_likes }),
2107
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(MetricBox, { label: "Total Comments", value: total_comments }),
2108
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(MetricBox, { label: "Total Shares", value: total_shares }),
2109
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(MetricBox, { label: "Total Reach", value: total_reach }),
2110
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(MetricBox, { label: "Total Views", value: total_views }),
2111
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(MetricBox, { label: "Impressions", value: total_impressions }),
2112
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2113
+ MetricBox,
2114
+ {
2115
+ label: "Avg. Engagement",
2116
+ value: `${((average_engagement_rate ?? 0) * 100).toFixed(2)}%`,
2117
+ isPercentage: true
2118
+ }
2119
+ )
2120
+ ] })
2121
+ ] }),
2122
+ metrics_by_account.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "space-y-4", children: [
2123
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("h3", { className: "text-lg font-semibold", children: "By Platform" }),
2124
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("div", { className: "grid gap-4", children: metrics_by_account.map((item, index) => /* @__PURE__ */ (0, import_jsx_runtime19.jsx)(
2125
+ PostMetricsCard,
2126
+ {
2127
+ account: item?.social_account,
2128
+ metrics: item?.metrics,
2129
+ platformPostId: item?.platform_post_id,
2130
+ publishedAt: item?.published_at
2131
+ },
2132
+ `${item?.social_account?.id ?? index}-${index}`
2133
+ )) })
2134
+ ] })
2135
+ ] });
2136
+ }
2137
+ function MetricBox({
2138
+ label,
2139
+ value,
2140
+ isPercentage = false
2141
+ }) {
2142
+ const displayValue = typeof value === "number" ? formatNumber(value) : value;
2143
+ return /* @__PURE__ */ (0, import_jsx_runtime19.jsxs)("div", { className: "p-4 rounded-lg bg-muted/50", children: [
2144
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-xs text-muted-foreground uppercase tracking-wider", children: label }),
2145
+ /* @__PURE__ */ (0, import_jsx_runtime19.jsx)("p", { className: "text-2xl font-bold mt-1", children: displayValue })
2146
+ ] });
2147
+ }
2148
+
2149
+ // src/components/metrics/posts-with-metrics.tsx
2150
+ var React15 = __toESM(require("react"), 1);
2151
+ var import_jsx_runtime20 = require("react/jsx-runtime");
2152
+ function PostsWithMetrics({
2153
+ apiKey,
2154
+ baseUrl = "https://api.outstand.so/v1",
2155
+ socialAccountId,
2156
+ pageSize = 10,
2157
+ className
2158
+ }) {
2159
+ const [offset, setOffset] = React15.useState(0);
2160
+ const [expandedPostId, setExpandedPostId] = React15.useState(null);
2161
+ const { posts, total, isLoading, hasNextPage, hasPrevPage } = usePosts({
2162
+ apiKey,
2163
+ baseUrl,
2164
+ socialAccountId,
2165
+ limit: pageSize,
2166
+ offset
2167
+ });
2168
+ React15.useEffect(() => {
2169
+ setExpandedPostId(null);
2170
+ }, [offset]);
2171
+ const handleToggleExpand = React15.useCallback((postId) => {
2172
+ setExpandedPostId((prev) => prev === postId ? null : postId);
2173
+ }, []);
2174
+ if (isLoading && posts.length === 0) {
2175
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("space-y-4", className), children: [...Array(3)].map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "h-24 rounded-lg bg-muted animate-pulse" }, i)) });
2176
+ }
2177
+ if (posts.length === 0) {
2178
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: cn("border border-dashed rounded-lg p-8 text-center", className), children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-muted-foreground", children: "No posts found" }) });
2179
+ }
2180
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: cn("space-y-4", className), children: [
2181
+ posts.map((post) => {
2182
+ if (!post?.id) return null;
2183
+ const isExpanded = expandedPostId === post.id;
2184
+ const socialAccounts = post.socialAccounts ?? [];
2185
+ const containers = post.containers ?? [];
2186
+ const content = containers[0]?.content || "No content";
2187
+ return /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "border rounded-lg overflow-hidden", children: [
2188
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2189
+ "button",
2190
+ {
2191
+ onClick: () => handleToggleExpand(post.id),
2192
+ className: "w-full p-4 text-left hover:bg-accent/50 transition-colors",
2193
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-start gap-4", children: [
2194
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex -space-x-2", children: [
2195
+ socialAccounts.slice(0, 3).map((account) => /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2196
+ "div",
2197
+ {
2198
+ className: "w-8 h-8 rounded-full border-2 border-background flex items-center justify-center bg-muted",
2199
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(NetworkIcon, { network: account?.network ?? "unknown", className: "h-4 w-4" })
2200
+ },
2201
+ account?.id ?? Math.random()
2202
+ )),
2203
+ socialAccounts.length > 3 && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "w-8 h-8 rounded-full border-2 border-background flex items-center justify-center bg-muted text-xs font-medium", children: [
2204
+ "+",
2205
+ socialAccounts.length - 3
2206
+ ] })
2207
+ ] }),
2208
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex-1 min-w-0", children: [
2209
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("p", { className: "text-sm line-clamp-2", children: content }),
2210
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center gap-2 mt-2 text-xs text-muted-foreground", children: [
2211
+ post.publishedAt ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { children: [
2212
+ "Published ",
2213
+ formatDate(post.publishedAt)
2214
+ ] }) : post.scheduledAt ? /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { children: [
2215
+ "Scheduled for ",
2216
+ formatDate(post.scheduledAt)
2217
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: "Draft" }),
2218
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("span", { children: "\xB7" }),
2219
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("span", { children: [
2220
+ socialAccounts.length,
2221
+ " accounts"
2222
+ ] })
2223
+ ] })
2224
+ ] }),
2225
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2226
+ "svg",
2227
+ {
2228
+ className: cn("w-5 h-5 text-muted-foreground transition-transform", isExpanded && "rotate-180"),
2229
+ fill: "none",
2230
+ viewBox: "0 0 24 24",
2231
+ stroke: "currentColor",
2232
+ children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M19 9l-7 7-7-7" })
2233
+ }
2234
+ )
2235
+ ] })
2236
+ }
2237
+ ),
2238
+ isExpanded && post.publishedAt && /* @__PURE__ */ (0, import_jsx_runtime20.jsx)("div", { className: "border-t p-4 bg-muted/30", children: /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(PostMetrics, { apiKey, baseUrl, postId: post.id }, `metrics-${post.id}`) })
2239
+ ] }, post.id);
2240
+ }),
2241
+ total > pageSize && /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex items-center justify-between pt-4", children: [
2242
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("p", { className: "text-sm text-muted-foreground", children: [
2243
+ "Showing ",
2244
+ offset + 1,
2245
+ "-",
2246
+ Math.min(offset + pageSize, total),
2247
+ " of ",
2248
+ total
2249
+ ] }),
2250
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsxs)("div", { className: "flex gap-2", children: [
2251
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2252
+ "button",
2253
+ {
2254
+ onClick: () => setOffset((prev) => Math.max(0, prev - pageSize)),
2255
+ disabled: !hasPrevPage,
2256
+ className: "px-3 py-1.5 text-sm rounded-lg border border-input hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed",
2257
+ children: "Previous"
2258
+ }
2259
+ ),
2260
+ /* @__PURE__ */ (0, import_jsx_runtime20.jsx)(
2261
+ "button",
2262
+ {
2263
+ onClick: () => setOffset((prev) => prev + pageSize),
2264
+ disabled: !hasNextPage,
2265
+ className: "px-3 py-1.5 text-sm rounded-lg border border-input hover:bg-accent disabled:opacity-50 disabled:cursor-not-allowed",
2266
+ children: "Next"
2267
+ }
2268
+ )
2269
+ ] })
2270
+ ] })
2271
+ ] });
2272
+ }
2273
+ // Annotate the CommonJS export names for ESM import in node:
2274
+ 0 && (module.exports = {
2275
+ AccountCard,
2276
+ AccountsList,
2277
+ ConnectAccountButton,
2278
+ ConnectAccountButtonGroup,
2279
+ CreatePostForm,
2280
+ MediaGallery,
2281
+ MediaList,
2282
+ MediaPreview,
2283
+ MediaUploader,
2284
+ NetworkConfigPanel,
2285
+ NetworkSelector,
2286
+ OAuthCallback,
2287
+ OutstandProvider,
2288
+ PageSelector,
2289
+ PostComposer,
2290
+ PostMetrics,
2291
+ PostMetricsCard,
2292
+ PostScheduler,
2293
+ PostsWithMetrics,
2294
+ useAccounts,
2295
+ useMediaList,
2296
+ useMediaUpload,
2297
+ useOAuthFlow,
2298
+ useOutstand,
2299
+ useOutstandApi,
2300
+ usePostMetrics,
2301
+ usePosts
2302
+ });