@tangle-network/sandbox-ui 0.113.7 → 0.114.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (106) hide show
  1. package/dist/WorkflowGraph-B-NsdYz_.js +1961 -0
  2. package/dist/assets.d.ts +214 -206
  3. package/dist/assets.js +1057 -908
  4. package/dist/auth-CWe17Hly.js +37 -0
  5. package/dist/auth.d.ts +2 -30
  6. package/dist/auth.js +2 -15
  7. package/dist/chat-BOvFAe6p.js +903 -0
  8. package/dist/chat.d.ts +2 -230
  9. package/dist/chat.js +2 -34
  10. package/dist/connectors.d.ts +52 -49
  11. package/dist/connectors.js +595 -643
  12. package/dist/dashboard-CZ3VgNPR.js +4969 -0
  13. package/dist/dashboard.d.ts +4 -1297
  14. package/dist/dashboard.js +4 -152
  15. package/dist/editor.d.ts +2 -1
  16. package/dist/editor.js +2 -31
  17. package/dist/files.d.ts +2 -6
  18. package/dist/files.js +2 -33
  19. package/dist/globals.css +63 -3
  20. package/dist/hooks.d.ts +146 -148
  21. package/dist/hooks.js +222 -55
  22. package/dist/index-CKRaj1pM.d.ts +776 -0
  23. package/dist/index-CXX5IpH2.d.ts +2 -0
  24. package/dist/index-DPePq7c8.d.ts +1340 -0
  25. package/dist/index-DiyLLZuJ.d.ts +230 -0
  26. package/dist/index-DvmZLMT1.d.ts +30 -0
  27. package/dist/index-I8ZLVtHd.d.ts +330 -0
  28. package/dist/index-PRM636Ex.d.ts +86 -0
  29. package/dist/index.d.ts +21 -33
  30. package/dist/index.js +21 -701
  31. package/dist/info-panel-C9cqDzOL.js +594 -0
  32. package/dist/integrations.d.ts +133 -141
  33. package/dist/integrations.js +491 -673
  34. package/dist/markdown.d.ts +2 -1
  35. package/dist/markdown.js +2 -13
  36. package/dist/model-I-NIxeM-.js +1250 -0
  37. package/dist/model-brand-B9A4H9pJ.d.ts +98 -0
  38. package/dist/model-brand-hMyDTUqB.js +624 -0
  39. package/dist/motion-DkyBRJb9.js +37 -0
  40. package/dist/openui.d.ts +2 -1
  41. package/dist/openui.js +2 -7
  42. package/dist/pages.d.ts +307 -300
  43. package/dist/pages.js +4404 -4181
  44. package/dist/primitives-Dh1vG6J3.js +113 -0
  45. package/dist/primitives.d.ts +2 -74
  46. package/dist/primitives.js +2 -204
  47. package/dist/provider-logo-DjqmConF.js +311 -0
  48. package/dist/run.d.ts +2 -1
  49. package/dist/run.js +2 -23
  50. package/dist/sdk-hooks.d.ts +3 -4
  51. package/dist/sdk-hooks.js +3 -31
  52. package/dist/sidecar-DgH_vQ6m.d.ts +9 -0
  53. package/dist/stores.d.ts +14 -15
  54. package/dist/stores.js +13 -81
  55. package/dist/styles.css +63 -3
  56. package/dist/template-card-L-4gmjx2.d.ts +90 -0
  57. package/dist/terminal-CN9ZgHRt.js +197 -0
  58. package/dist/terminal.d.ts +60 -60
  59. package/dist/terminal.js +2 -10
  60. package/dist/tokens.css +51 -0
  61. package/dist/types.d.ts +4 -11
  62. package/dist/types.js +0 -1
  63. package/dist/use-pty-session-C8cE0adT.js +669 -0
  64. package/dist/use-sidecar-auth-CCTJcrq7.d.ts +145 -0
  65. package/dist/use-sidecar-auth-DkdcufOz.js +742 -0
  66. package/dist/utils-DzWbktWG.js +2 -0
  67. package/dist/utils.d.ts +2 -1
  68. package/dist/utils.js +2 -37
  69. package/dist/workbench-BCaCKl-J.js +8347 -0
  70. package/dist/workbench.d.ts +2 -310
  71. package/dist/workbench.js +2 -44
  72. package/dist/workflows.d.ts +341 -339
  73. package/dist/workflows.js +74 -109
  74. package/dist/workspace-B6AtI6ld.js +2510 -0
  75. package/dist/workspace.d.ts +2 -761
  76. package/dist/workspace.js +2 -51
  77. package/package.json +46 -40
  78. package/dist/WorkflowGraph-2QOORYIR.js +0 -1722
  79. package/dist/chunk-2BUPSB7O.js +0 -0
  80. package/dist/chunk-3KBNPQQF.js +0 -2697
  81. package/dist/chunk-3LIXVEEE.js +0 -279
  82. package/dist/chunk-72UF2H5H.js +0 -707
  83. package/dist/chunk-AI25G5MX.js +0 -944
  84. package/dist/chunk-ANSC5AFC.js +0 -314
  85. package/dist/chunk-AZ3AWMTM.js +0 -8
  86. package/dist/chunk-BGVEHENC.js +0 -10
  87. package/dist/chunk-CZ7CSFO4.js +0 -39
  88. package/dist/chunk-DYXXYGS4.js +0 -4700
  89. package/dist/chunk-EI44GEQ5.js +0 -6
  90. package/dist/chunk-IACRDXER.js +0 -219
  91. package/dist/chunk-IOB2PW5Z.js +0 -55
  92. package/dist/chunk-M4K7ZGR2.js +0 -24
  93. package/dist/chunk-MYZAW4RX.js +0 -665
  94. package/dist/chunk-OMUIPJQZ.js +0 -555
  95. package/dist/chunk-RQZRXKMR.js +0 -398
  96. package/dist/chunk-SOKKTB7W.js +0 -230
  97. package/dist/chunk-TADGS4Q7.js +0 -1010
  98. package/dist/chunk-UCQNOUN3.js +0 -7964
  99. package/dist/chunk-ULPLRBMF.js +0 -34
  100. package/dist/chunk-WID73FPH.js +0 -89
  101. package/dist/chunk-X3YXP7FG.js +0 -38
  102. package/dist/logo-DiAjq759.d.ts +0 -15
  103. package/dist/model-brand-BNnaUKKZ.d.ts +0 -99
  104. package/dist/ports-list-CDIvTNnX.d.ts +0 -17
  105. package/dist/sdk-hooks-Djkjq4cp.d.ts +0 -145
  106. package/dist/template-card-Bn-Bw5xC.d.ts +0 -87
package/dist/assets.js CHANGED
@@ -1,947 +1,1096 @@
1
- import {
2
- cn
3
- } from "./chunk-EI44GEQ5.js";
4
- import "./chunk-CZ7CSFO4.js";
5
-
6
- // src/assets/approval-queue.tsx
7
- import * as React3 from "react";
8
- import { Button as Button3, Input as Input2 } from "@tangle-network/ui/primitives";
9
- import { Search, CheckCheck } from "lucide-react";
10
-
11
- // src/assets/asset-card.tsx
12
- import { Badge } from "@tangle-network/ui/primitives";
13
- import { Check, X, Pencil, GitBranch } from "lucide-react";
14
-
15
- // src/assets/preview/image-preview.tsx
16
- import * as React from "react";
17
- import { ChevronLeft, ChevronRight } from "lucide-react";
1
+ import { t as cn } from "./utils-DzWbktWG.js";
2
+ import { Badge, Button, Input, Label, Textarea } from "@tangle-network/ui/primitives";
18
3
  import { jsx, jsxs } from "react/jsx-runtime";
19
- var ASPECT = {
20
- feed: "aspect-square",
21
- story: "aspect-[9/16]",
22
- carousel: "aspect-square"
4
+ import * as React$1 from "react";
5
+ import { Fragment as Fragment$1 } from "react";
6
+ import { Check, CheckCheck, ChevronDown, ChevronLeft, ChevronRight, Film, GitBranch, Loader2, MessageSquare, Pencil, Play, Save, Search, Trophy, X } from "lucide-react";
7
+ //#region src/assets/preview/image-preview.tsx
8
+ const ASPECT = {
9
+ feed: "aspect-square",
10
+ story: "aspect-[9/16]",
11
+ carousel: "aspect-square"
23
12
  };
24
13
  function bgStyle(bg, brand) {
25
- if (bg.type === "color") return { background: bg.value };
26
- if (bg.type === "gradient") return { background: `linear-gradient(${bg.direction ?? "to bottom right"}, ${bg.from}, ${bg.to})` };
27
- if (bg.type === "image") {
28
- return {
29
- backgroundImage: `url(${bg.url})`,
30
- backgroundSize: "cover",
31
- backgroundPosition: "center"
32
- };
33
- }
34
- return { background: brand.primaryColor };
14
+ if (bg.type === "color") return { background: bg.value };
15
+ if (bg.type === "gradient") return { background: `linear-gradient(${bg.direction ?? "to bottom right"}, ${bg.from}, ${bg.to})` };
16
+ if (bg.type === "image") return {
17
+ backgroundImage: `url(${bg.url})`,
18
+ backgroundSize: "cover",
19
+ backgroundPosition: "center"
20
+ };
21
+ return { background: brand.primaryColor };
35
22
  }
36
23
  function Layer({ layer, brand }) {
37
- const base = {
38
- position: "absolute",
39
- left: `${layer.x}%`,
40
- top: `${layer.y}%`
41
- };
42
- if (layer.type === "text") {
43
- return /* @__PURE__ */ jsx(
44
- "div",
45
- {
46
- style: {
47
- ...base,
48
- fontSize: layer.fontSize ?? 16,
49
- fontWeight: layer.fontWeight ?? "normal",
50
- color: layer.color ?? brand.textColor,
51
- width: layer.width ? `${layer.width}%` : void 0,
52
- textAlign: layer.align ?? "left"
53
- },
54
- children: layer.text
55
- }
56
- );
57
- }
58
- if (layer.type === "image") {
59
- return /* @__PURE__ */ jsx(
60
- "img",
61
- {
62
- src: layer.url,
63
- alt: "",
64
- style: {
65
- ...base,
66
- width: `${layer.width}%`,
67
- height: `${layer.height}%`,
68
- opacity: layer.opacity ?? 1,
69
- objectFit: "cover"
70
- }
71
- }
72
- );
73
- }
74
- if (layer.type === "shape") {
75
- return /* @__PURE__ */ jsx(
76
- "div",
77
- {
78
- style: {
79
- ...base,
80
- width: `${layer.width}%`,
81
- height: `${layer.height}%`,
82
- background: layer.fill ?? brand.accentColor,
83
- opacity: layer.opacity ?? 1,
84
- borderRadius: layer.shape === "circle" ? "50%" : layer.shape === "rounded-rect" ? "8px" : 0
85
- }
86
- }
87
- );
88
- }
89
- if (layer.type === "logo" && brand.logoUrl) {
90
- return /* @__PURE__ */ jsx(
91
- "img",
92
- {
93
- src: brand.logoUrl,
94
- alt: brand.businessName,
95
- style: {
96
- ...base,
97
- width: layer.width ? `${layer.width}%` : "12%"
98
- }
99
- }
100
- );
101
- }
102
- return null;
24
+ const base = {
25
+ position: "absolute",
26
+ left: `${layer.x}%`,
27
+ top: `${layer.y}%`
28
+ };
29
+ if (layer.type === "text") return /* @__PURE__ */ jsx("div", {
30
+ style: {
31
+ ...base,
32
+ fontSize: layer.fontSize ?? 16,
33
+ fontWeight: layer.fontWeight ?? "normal",
34
+ color: layer.color ?? brand.textColor,
35
+ width: layer.width ? `${layer.width}%` : void 0,
36
+ textAlign: layer.align ?? "left"
37
+ },
38
+ children: layer.text
39
+ });
40
+ if (layer.type === "image") return /* @__PURE__ */ jsx("img", {
41
+ src: layer.url,
42
+ alt: "",
43
+ style: {
44
+ ...base,
45
+ width: `${layer.width}%`,
46
+ height: `${layer.height}%`,
47
+ opacity: layer.opacity ?? 1,
48
+ objectFit: "cover"
49
+ }
50
+ });
51
+ if (layer.type === "shape") return /* @__PURE__ */ jsx("div", { style: {
52
+ ...base,
53
+ width: `${layer.width}%`,
54
+ height: `${layer.height}%`,
55
+ background: layer.fill ?? brand.accentColor,
56
+ opacity: layer.opacity ?? 1,
57
+ borderRadius: layer.shape === "circle" ? "50%" : layer.shape === "rounded-rect" ? "8px" : 0
58
+ } });
59
+ if (layer.type === "logo" && brand.logoUrl) return /* @__PURE__ */ jsx("img", {
60
+ src: brand.logoUrl,
61
+ alt: brand.businessName,
62
+ style: {
63
+ ...base,
64
+ width: layer.width ? `${layer.width}%` : "12%"
65
+ }
66
+ });
67
+ return null;
103
68
  }
104
69
  function Slide({ slide, brand }) {
105
- return /* @__PURE__ */ jsx("div", { className: "relative w-full h-full overflow-hidden", style: bgStyle(slide.background, brand), children: slide.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, { layer, brand }, i)) });
70
+ return /* @__PURE__ */ jsx("div", {
71
+ className: "relative w-full h-full overflow-hidden",
72
+ style: bgStyle(slide.background, brand),
73
+ children: slide.layers.map((layer, i) => /* @__PURE__ */ jsx(Layer, {
74
+ layer,
75
+ brand
76
+ }, i))
77
+ });
106
78
  }
107
79
  function ImagePreview({ content, brand, format = "feed", className }) {
108
- const [activeIndex, setActiveIndex] = React.useState(0);
109
- const slides = content.slides;
110
- const isMulti = slides.length > 1;
111
- return /* @__PURE__ */ jsxs("div", { className: cn("flex flex-col gap-2", className), children: [
112
- /* @__PURE__ */ jsxs("div", { className: cn("relative w-full rounded overflow-hidden", ASPECT[format]), children: [
113
- /* @__PURE__ */ jsx(Slide, { slide: slides[activeIndex], brand }),
114
- isMulti && activeIndex > 0 && /* @__PURE__ */ jsx(
115
- "button",
116
- {
117
- onClick: () => setActiveIndex((i) => i - 1),
118
- className: "absolute left-1 top-1/2 -translate-y-1/2 rounded-full p-1 bg-black/40 text-white hover:bg-black/60",
119
- children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
120
- }
121
- ),
122
- isMulti && activeIndex < slides.length - 1 && /* @__PURE__ */ jsx(
123
- "button",
124
- {
125
- onClick: () => setActiveIndex((i) => i + 1),
126
- className: "absolute right-1 top-1/2 -translate-y-1/2 rounded-full p-1 bg-black/40 text-white hover:bg-black/60",
127
- children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
128
- }
129
- ),
130
- isMulti && /* @__PURE__ */ jsx("div", { className: "absolute bottom-2 left-1/2 -translate-x-1/2 flex gap-1", children: slides.map((_, i) => /* @__PURE__ */ jsx(
131
- "button",
132
- {
133
- onClick: () => setActiveIndex(i),
134
- className: cn(
135
- "w-1.5 h-1.5 rounded-full transition-colors",
136
- i === activeIndex ? "bg-white" : "bg-white/40"
137
- )
138
- },
139
- i
140
- )) })
141
- ] }),
142
- isMulti && /* @__PURE__ */ jsxs("div", { className: "text-xs text-muted-foreground text-center", children: [
143
- activeIndex + 1,
144
- " / ",
145
- slides.length
146
- ] })
147
- ] });
80
+ const [activeIndex, setActiveIndex] = React$1.useState(0);
81
+ const slides = content.slides;
82
+ const isMulti = slides.length > 1;
83
+ return /* @__PURE__ */ jsxs("div", {
84
+ className: cn("flex flex-col gap-2", className),
85
+ children: [/* @__PURE__ */ jsxs("div", {
86
+ className: cn("relative w-full rounded overflow-hidden", ASPECT[format]),
87
+ children: [
88
+ /* @__PURE__ */ jsx(Slide, {
89
+ slide: slides[activeIndex],
90
+ brand
91
+ }),
92
+ isMulti && activeIndex > 0 && /* @__PURE__ */ jsx("button", {
93
+ onClick: () => setActiveIndex((i) => i - 1),
94
+ className: "absolute left-1 top-1/2 -translate-y-1/2 rounded-full p-1 bg-black/40 text-white hover:bg-black/60",
95
+ children: /* @__PURE__ */ jsx(ChevronLeft, { size: 16 })
96
+ }),
97
+ isMulti && activeIndex < slides.length - 1 && /* @__PURE__ */ jsx("button", {
98
+ onClick: () => setActiveIndex((i) => i + 1),
99
+ className: "absolute right-1 top-1/2 -translate-y-1/2 rounded-full p-1 bg-black/40 text-white hover:bg-black/60",
100
+ children: /* @__PURE__ */ jsx(ChevronRight, { size: 16 })
101
+ }),
102
+ isMulti && /* @__PURE__ */ jsx("div", {
103
+ className: "absolute bottom-2 left-1/2 -translate-x-1/2 flex gap-1",
104
+ children: slides.map((_, i) => /* @__PURE__ */ jsx("button", {
105
+ onClick: () => setActiveIndex(i),
106
+ className: cn("w-1.5 h-1.5 rounded-full transition-colors", i === activeIndex ? "bg-white" : "bg-white/40")
107
+ }, i))
108
+ })
109
+ ]
110
+ }), isMulti && /* @__PURE__ */ jsxs("div", {
111
+ className: "text-xs text-muted-foreground text-center",
112
+ children: [
113
+ activeIndex + 1,
114
+ " / ",
115
+ slides.length
116
+ ]
117
+ })]
118
+ });
148
119
  }
149
-
150
- // src/assets/asset-card.tsx
151
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
152
- var STATUS_VARIANT = {
153
- draft: "secondary",
154
- pending_review: "outline",
155
- approved: "default",
156
- rejected: "destructive",
157
- scheduled: "secondary",
158
- published: "default"
120
+ //#endregion
121
+ //#region src/assets/asset-card.tsx
122
+ const STATUS_VARIANT = {
123
+ draft: "secondary",
124
+ pending_review: "outline",
125
+ approved: "default",
126
+ rejected: "destructive",
127
+ scheduled: "secondary",
128
+ published: "default"
159
129
  };
160
- var STATUS_LABEL = {
161
- draft: "Draft",
162
- pending_review: "Review",
163
- approved: "Approved",
164
- rejected: "Rejected",
165
- scheduled: "Scheduled",
166
- published: "Published"
130
+ const STATUS_LABEL = {
131
+ draft: "Draft",
132
+ pending_review: "Review",
133
+ approved: "Approved",
134
+ rejected: "Rejected",
135
+ scheduled: "Scheduled",
136
+ published: "Published"
167
137
  };
168
138
  function Thumbnail({ spec }) {
169
- const { format, brand, content } = spec;
170
- if (format === "email") {
171
- const ec = content;
172
- return /* @__PURE__ */ jsxs2("div", { className: "p-2 text-xs space-y-0.5", children: [
173
- /* @__PURE__ */ jsx2("div", { className: "font-medium truncate", children: ec.subject }),
174
- /* @__PURE__ */ jsxs2("div", { className: "text-muted-foreground truncate text-[10px]", children: [
175
- ec.sections.length,
176
- " sections"
177
- ] })
178
- ] });
179
- }
180
- if (format.startsWith("image:")) {
181
- const imgFormat = format === "image:story" ? "story" : "feed";
182
- return /* @__PURE__ */ jsx2("div", { className: "overflow-hidden rounded", style: { maxHeight: 120 }, children: /* @__PURE__ */ jsx2(
183
- ImagePreview,
184
- {
185
- content,
186
- brand,
187
- format: imgFormat
188
- }
189
- ) });
190
- }
191
- if (format.startsWith("video:")) {
192
- const vc = content;
193
- return /* @__PURE__ */ jsxs2("div", { className: "p-2 text-xs space-y-0.5", children: [
194
- /* @__PURE__ */ jsxs2("div", { className: "font-medium", children: [
195
- vc.scenes.length,
196
- " scenes"
197
- ] }),
198
- /* @__PURE__ */ jsxs2("div", { className: "text-muted-foreground", children: [
199
- vc.durationSeconds,
200
- "s video"
201
- ] })
202
- ] });
203
- }
204
- if (format.startsWith("copy:")) {
205
- const cc = content;
206
- return /* @__PURE__ */ jsxs2("div", { className: "p-2 text-xs truncate space-y-0.5", children: [
207
- /* @__PURE__ */ jsx2("div", { className: "font-medium truncate", children: cc.headline }),
208
- /* @__PURE__ */ jsx2("div", { className: "text-muted-foreground line-clamp-2 text-[10px]", children: cc.body })
209
- ] });
210
- }
211
- return null;
139
+ const { format, brand, content } = spec;
140
+ if (format === "email") {
141
+ const ec = content;
142
+ return /* @__PURE__ */ jsxs("div", {
143
+ className: "p-2 text-xs space-y-0.5",
144
+ children: [/* @__PURE__ */ jsx("div", {
145
+ className: "font-medium truncate",
146
+ children: ec.subject
147
+ }), /* @__PURE__ */ jsxs("div", {
148
+ className: "text-muted-foreground truncate text-[10px]",
149
+ children: [ec.sections.length, " sections"]
150
+ })]
151
+ });
152
+ }
153
+ if (format.startsWith("image:")) return /* @__PURE__ */ jsx("div", {
154
+ className: "overflow-hidden rounded",
155
+ style: { maxHeight: 120 },
156
+ children: /* @__PURE__ */ jsx(ImagePreview, {
157
+ content,
158
+ brand,
159
+ format: format === "image:story" ? "story" : "feed"
160
+ })
161
+ });
162
+ if (format.startsWith("video:")) {
163
+ const vc = content;
164
+ return /* @__PURE__ */ jsxs("div", {
165
+ className: "p-2 text-xs space-y-0.5",
166
+ children: [/* @__PURE__ */ jsxs("div", {
167
+ className: "font-medium",
168
+ children: [vc.scenes.length, " scenes"]
169
+ }), /* @__PURE__ */ jsxs("div", {
170
+ className: "text-muted-foreground",
171
+ children: [vc.durationSeconds, "s video"]
172
+ })]
173
+ });
174
+ }
175
+ if (format.startsWith("copy:")) {
176
+ const cc = content;
177
+ return /* @__PURE__ */ jsxs("div", {
178
+ className: "p-2 text-xs truncate space-y-0.5",
179
+ children: [/* @__PURE__ */ jsx("div", {
180
+ className: "font-medium truncate",
181
+ children: cc.headline
182
+ }), /* @__PURE__ */ jsx("div", {
183
+ className: "text-muted-foreground line-clamp-2 text-[10px]",
184
+ children: cc.body
185
+ })]
186
+ });
187
+ }
188
+ return null;
212
189
  }
213
190
  function AssetCard({ spec, variantCount, onApprove, onReject, onEdit, className }) {
214
- return /* @__PURE__ */ jsxs2("div", { className: cn("rounded-lg border border-border bg-card flex flex-col overflow-hidden hover:border-border/80 transition-colors", className), children: [
215
- /* @__PURE__ */ jsx2("div", { className: "min-h-[80px] bg-muted/30", children: /* @__PURE__ */ jsx2(Thumbnail, { spec }) }),
216
- /* @__PURE__ */ jsxs2("div", { className: "px-2 py-1.5 flex items-center gap-1.5", children: [
217
- /* @__PURE__ */ jsx2("span", { className: "text-[10px] text-muted-foreground flex-1 truncate", children: spec.format }),
218
- /* @__PURE__ */ jsx2(Badge, { variant: STATUS_VARIANT[spec.status], className: "text-[10px] h-4 px-1.5", children: STATUS_LABEL[spec.status] }),
219
- variantCount !== void 0 && variantCount > 0 && /* @__PURE__ */ jsxs2("span", { className: "flex items-center gap-0.5 text-[10px] text-muted-foreground", children: [
220
- /* @__PURE__ */ jsx2(GitBranch, { size: 10 }),
221
- variantCount
222
- ] })
223
- ] }),
224
- /* @__PURE__ */ jsxs2("div", { className: "flex border-t border-border", children: [
225
- onEdit && /* @__PURE__ */ jsxs2(
226
- "button",
227
- {
228
- type: "button",
229
- onClick: () => onEdit(spec.id),
230
- className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
231
- children: [
232
- /* @__PURE__ */ jsx2(Pencil, { size: 12 }),
233
- "Edit"
234
- ]
235
- }
236
- ),
237
- onReject && spec.status === "pending_review" && /* @__PURE__ */ jsxs2(
238
- "button",
239
- {
240
- type: "button",
241
- onClick: () => onReject(spec.id),
242
- className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/5 transition-colors border-l border-border",
243
- children: [
244
- /* @__PURE__ */ jsx2(X, { size: 12 }),
245
- "Reject"
246
- ]
247
- }
248
- ),
249
- onApprove && spec.status === "pending_review" && /* @__PURE__ */ jsxs2(
250
- "button",
251
- {
252
- type: "button",
253
- onClick: () => onApprove(spec.id),
254
- className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-green-600 hover:bg-green-600/5 transition-colors border-l border-border",
255
- children: [
256
- /* @__PURE__ */ jsx2(Check, { size: 12 }),
257
- "Approve"
258
- ]
259
- }
260
- )
261
- ] })
262
- ] });
191
+ return /* @__PURE__ */ jsxs("div", {
192
+ className: cn("rounded-lg border border-border bg-card flex flex-col overflow-hidden hover:border-border/80 transition-colors", className),
193
+ children: [
194
+ /* @__PURE__ */ jsx("div", {
195
+ className: "min-h-[80px] bg-muted/30",
196
+ children: /* @__PURE__ */ jsx(Thumbnail, { spec })
197
+ }),
198
+ /* @__PURE__ */ jsxs("div", {
199
+ className: "px-2 py-1.5 flex items-center gap-1.5",
200
+ children: [
201
+ /* @__PURE__ */ jsx("span", {
202
+ className: "text-[10px] text-muted-foreground flex-1 truncate",
203
+ children: spec.format
204
+ }),
205
+ /* @__PURE__ */ jsx(Badge, {
206
+ variant: STATUS_VARIANT[spec.status],
207
+ className: "text-[10px] h-4 px-1.5",
208
+ children: STATUS_LABEL[spec.status]
209
+ }),
210
+ variantCount !== void 0 && variantCount > 0 && /* @__PURE__ */ jsxs("span", {
211
+ className: "flex items-center gap-0.5 text-[10px] text-muted-foreground",
212
+ children: [/* @__PURE__ */ jsx(GitBranch, { size: 10 }), variantCount]
213
+ })
214
+ ]
215
+ }),
216
+ /* @__PURE__ */ jsxs("div", {
217
+ className: "flex border-t border-border",
218
+ children: [
219
+ onEdit && /* @__PURE__ */ jsxs("button", {
220
+ type: "button",
221
+ onClick: () => onEdit(spec.id),
222
+ className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-foreground hover:bg-muted/50 transition-colors",
223
+ children: [/* @__PURE__ */ jsx(Pencil, { size: 12 }), "Edit"]
224
+ }),
225
+ onReject && spec.status === "pending_review" && /* @__PURE__ */ jsxs("button", {
226
+ type: "button",
227
+ onClick: () => onReject(spec.id),
228
+ className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-destructive hover:bg-destructive/5 transition-colors border-l border-border",
229
+ children: [/* @__PURE__ */ jsx(X, { size: 12 }), "Reject"]
230
+ }),
231
+ onApprove && spec.status === "pending_review" && /* @__PURE__ */ jsxs("button", {
232
+ type: "button",
233
+ onClick: () => onApprove(spec.id),
234
+ className: "flex-1 flex items-center justify-center gap-1 py-1.5 text-xs text-muted-foreground hover:text-green-600 hover:bg-green-600/5 transition-colors border-l border-border",
235
+ children: [/* @__PURE__ */ jsx(Check, { size: 12 }), "Approve"]
236
+ })
237
+ ]
238
+ })
239
+ ]
240
+ });
263
241
  }
264
-
265
- // src/assets/asset-editor.tsx
266
- import * as React2 from "react";
267
- import { Button as Button2, Input, Textarea, Label } from "@tangle-network/ui/primitives";
268
- import { MessageSquare, Save, ChevronDown, ChevronRight as ChevronRight2 } from "lucide-react";
269
-
270
- // src/assets/preview/email-preview.tsx
271
- import { jsxs as jsxs3, jsx as jsx3 } from "react/jsx-runtime";
242
+ //#endregion
243
+ //#region src/assets/preview/email-preview.tsx
272
244
  function EmailPreview({ content, brand, previewUrl, className }) {
273
- if (previewUrl) {
274
- return jsxs3("div", { className: cn("flex flex-col gap-1", className), children: [
275
- jsx3("div", { className: "text-xs text-muted-foreground font-medium truncate", children: content.subject }),
276
- content.preheader && jsx3("div", { className: "text-xs text-muted-foreground/60 truncate", children: content.preheader }),
277
- jsx3(
278
- "iframe",
279
- {
280
- src: previewUrl,
281
- className: "w-full rounded border border-border",
282
- style: { height: 480, background: "#fff" },
283
- title: "Email preview",
284
- sandbox: "allow-same-origin"
285
- }
286
- )
287
- ] });
288
- }
289
- return jsxs3("div", { className: cn("flex flex-col gap-2", className), children: [
290
- jsx3("div", { className: "text-sm font-semibold truncate", children: content.subject }),
291
- content.preheader && jsx3("div", { className: "text-xs text-muted-foreground truncate", children: content.preheader }),
292
- jsx3(
293
- "div",
294
- {
295
- className: "rounded border border-border p-4 space-y-3 overflow-y-auto",
296
- style: { maxHeight: 480, fontFamily: brand.fontFamily, color: brand.textColor },
297
- children: content.sections.map((section, i) => {
298
- if (section.type === "hero") {
299
- return jsxs3("div", { className: "text-center py-4 space-y-2", children: [
300
- section.imageUrl && jsx3("img", { src: section.imageUrl, alt: "", className: "mx-auto max-h-40 object-cover rounded" }),
301
- jsx3("div", { className: "text-xl font-bold", style: { color: brand.primaryColor }, children: section.headline }),
302
- section.subheadline && jsx3("div", { className: "text-sm text-muted-foreground", children: section.subheadline }),
303
- section.ctaLabel && jsx3(
304
- "a",
305
- {
306
- href: section.ctaUrl ?? "#",
307
- className: "inline-block px-4 py-2 rounded text-sm font-medium text-white",
308
- style: { background: brand.primaryColor },
309
- children: section.ctaLabel
310
- }
311
- )
312
- ] }, i);
313
- }
314
- if (section.type === "body") {
315
- return jsx3("p", { className: "text-sm leading-relaxed whitespace-pre-wrap", children: section.text }, i);
316
- }
317
- if (section.type === "feature") {
318
- return jsxs3("div", { className: "flex gap-3 items-start", children: [
319
- section.imageUrl && jsx3("img", { src: section.imageUrl, alt: "", className: "w-16 h-16 object-cover rounded shrink-0" }),
320
- jsxs3("div", { children: [
321
- jsx3("div", { className: "text-sm font-semibold", children: section.headline }),
322
- jsx3("div", { className: "text-xs text-muted-foreground mt-0.5", children: section.description })
323
- ] })
324
- ] }, i);
325
- }
326
- if (section.type === "testimonial") {
327
- return jsxs3("blockquote", { className: "border-l-2 pl-3 italic text-sm text-muted-foreground", style: { borderColor: brand.accentColor }, children: [
328
- jsxs3("p", { children: [
329
- '"',
330
- section.quote,
331
- '"'
332
- ] }),
333
- jsxs3("footer", { className: "mt-1 text-xs not-italic font-medium", children: [
334
- section.author,
335
- section.role ? `, ${section.role}` : ""
336
- ] })
337
- ] }, i);
338
- }
339
- if (section.type === "cta") {
340
- return jsxs3("div", { className: "text-center py-3 space-y-1", children: [
341
- jsx3(
342
- "a",
343
- {
344
- href: section.url,
345
- className: "inline-block px-5 py-2.5 rounded font-medium text-white text-sm",
346
- style: { background: brand.primaryColor },
347
- children: section.label
348
- }
349
- ),
350
- section.subtext && jsx3("p", { className: "text-xs text-muted-foreground", children: section.subtext })
351
- ] }, i);
352
- }
353
- if (section.type === "divider") {
354
- return jsx3("hr", { className: "border-border" }, i);
355
- }
356
- return null;
357
- })
358
- }
359
- )
360
- ] });
245
+ if (previewUrl) return jsxs("div", {
246
+ className: cn("flex flex-col gap-1", className),
247
+ children: [
248
+ jsx("div", {
249
+ className: "text-xs text-muted-foreground font-medium truncate",
250
+ children: content.subject
251
+ }),
252
+ content.preheader && jsx("div", {
253
+ className: "text-xs text-muted-foreground/60 truncate",
254
+ children: content.preheader
255
+ }),
256
+ jsx("iframe", {
257
+ src: previewUrl,
258
+ className: "w-full rounded border border-border",
259
+ style: {
260
+ height: 480,
261
+ background: "#fff"
262
+ },
263
+ title: "Email preview",
264
+ sandbox: "allow-same-origin"
265
+ })
266
+ ]
267
+ });
268
+ return jsxs("div", {
269
+ className: cn("flex flex-col gap-2", className),
270
+ children: [
271
+ jsx("div", {
272
+ className: "text-sm font-semibold truncate",
273
+ children: content.subject
274
+ }),
275
+ content.preheader && jsx("div", {
276
+ className: "text-xs text-muted-foreground truncate",
277
+ children: content.preheader
278
+ }),
279
+ jsx("div", {
280
+ className: "rounded border border-border p-4 space-y-3 overflow-y-auto",
281
+ style: {
282
+ maxHeight: 480,
283
+ fontFamily: brand.fontFamily,
284
+ color: brand.textColor
285
+ },
286
+ children: content.sections.map((section, i) => {
287
+ if (section.type === "hero") return jsxs("div", {
288
+ className: "text-center py-4 space-y-2",
289
+ children: [
290
+ section.imageUrl && jsx("img", {
291
+ src: section.imageUrl,
292
+ alt: "",
293
+ className: "mx-auto max-h-40 object-cover rounded"
294
+ }),
295
+ jsx("div", {
296
+ className: "text-xl font-bold",
297
+ style: { color: brand.primaryColor },
298
+ children: section.headline
299
+ }),
300
+ section.subheadline && jsx("div", {
301
+ className: "text-sm text-muted-foreground",
302
+ children: section.subheadline
303
+ }),
304
+ section.ctaLabel && jsx("a", {
305
+ href: section.ctaUrl ?? "#",
306
+ className: "inline-block px-4 py-2 rounded text-sm font-medium text-white",
307
+ style: { background: brand.primaryColor },
308
+ children: section.ctaLabel
309
+ })
310
+ ]
311
+ }, i);
312
+ if (section.type === "body") return jsx("p", {
313
+ className: "text-sm leading-relaxed whitespace-pre-wrap",
314
+ children: section.text
315
+ }, i);
316
+ if (section.type === "feature") return jsxs("div", {
317
+ className: "flex gap-3 items-start",
318
+ children: [section.imageUrl && jsx("img", {
319
+ src: section.imageUrl,
320
+ alt: "",
321
+ className: "w-16 h-16 object-cover rounded shrink-0"
322
+ }), jsxs("div", { children: [jsx("div", {
323
+ className: "text-sm font-semibold",
324
+ children: section.headline
325
+ }), jsx("div", {
326
+ className: "text-xs text-muted-foreground mt-0.5",
327
+ children: section.description
328
+ })] })]
329
+ }, i);
330
+ if (section.type === "testimonial") return jsxs("blockquote", {
331
+ className: "border-l-2 pl-3 italic text-sm text-muted-foreground",
332
+ style: { borderColor: brand.accentColor },
333
+ children: [jsxs("p", { children: [
334
+ "\"",
335
+ section.quote,
336
+ "\""
337
+ ] }), jsxs("footer", {
338
+ className: "mt-1 text-xs not-italic font-medium",
339
+ children: [section.author, section.role ? `, ${section.role}` : ""]
340
+ })]
341
+ }, i);
342
+ if (section.type === "cta") return jsxs("div", {
343
+ className: "text-center py-3 space-y-1",
344
+ children: [jsx("a", {
345
+ href: section.url,
346
+ className: "inline-block px-5 py-2.5 rounded font-medium text-white text-sm",
347
+ style: { background: brand.primaryColor },
348
+ children: section.label
349
+ }), section.subtext && jsx("p", {
350
+ className: "text-xs text-muted-foreground",
351
+ children: section.subtext
352
+ })]
353
+ }, i);
354
+ if (section.type === "divider") return jsx("hr", { className: "border-border" }, i);
355
+ return null;
356
+ })
357
+ })
358
+ ]
359
+ });
361
360
  }
362
-
363
- // src/assets/preview/video-preview.tsx
364
- import { Fragment } from "react";
365
- import { Play, Film, Loader2 } from "lucide-react";
366
- import { Button } from "@tangle-network/ui/primitives";
367
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
361
+ //#endregion
362
+ //#region src/assets/preview/video-preview.tsx
368
363
  function VideoPreview({ content, brand, onRenderRequest, isRendering, className }) {
369
- if (content.renderedUrl) {
370
- return /* @__PURE__ */ jsx4("div", { className: cn("rounded overflow-hidden aspect-[9/16] bg-black", className), children: /* @__PURE__ */ jsx4(
371
- "video",
372
- {
373
- src: content.renderedUrl,
374
- controls: true,
375
- className: "w-full h-full object-contain",
376
- playsInline: true
377
- }
378
- ) });
379
- }
380
- const sceneSummary = content.scenes.map((s) => {
381
- if (s.type === "text-animation") return s.headline;
382
- if (s.type === "image-reveal") return s.caption ?? "Image";
383
- if (s.type === "slide") return "Slide";
384
- if (s.type === "countdown") return `${s.from}\u2026`;
385
- return "Scene";
386
- });
387
- return /* @__PURE__ */ jsxs4("div", { className: cn("flex flex-col gap-3", className), children: [
388
- /* @__PURE__ */ jsxs4(
389
- "div",
390
- {
391
- className: "relative rounded overflow-hidden aspect-[9/16] flex flex-col items-center justify-center gap-3",
392
- style: { background: brand.primaryColor, color: brand.textColor },
393
- children: [
394
- /* @__PURE__ */ jsx4(Film, { size: 32, className: "opacity-60" }),
395
- /* @__PURE__ */ jsx4("div", { className: "text-sm font-medium text-center px-4", children: brand.businessName }),
396
- /* @__PURE__ */ jsxs4("div", { className: "text-xs opacity-60", children: [
397
- content.scenes.length,
398
- " scenes ",
399
- "\xB7",
400
- " ",
401
- content.durationSeconds,
402
- "s"
403
- ] })
404
- ]
405
- }
406
- ),
407
- /* @__PURE__ */ jsxs4("div", { className: "space-y-1", children: [
408
- /* @__PURE__ */ jsx4("div", { className: "text-xs font-medium text-muted-foreground", children: "Scenes" }),
409
- sceneSummary.map((label, i) => /* @__PURE__ */ jsxs4("div", { className: "text-xs text-muted-foreground flex gap-1.5 items-center", children: [
410
- /* @__PURE__ */ jsx4("span", { className: "font-mono text-[10px] w-4 text-right opacity-50", children: i + 1 }),
411
- /* @__PURE__ */ jsx4("span", { children: label }),
412
- /* @__PURE__ */ jsxs4("span", { className: "opacity-40", children: [
413
- "\xB7",
414
- " ",
415
- content.scenes[i].durationSeconds,
416
- "s"
417
- ] })
418
- ] }, i))
419
- ] }),
420
- onRenderRequest && /* @__PURE__ */ jsx4(
421
- Button,
422
- {
423
- size: "sm",
424
- variant: "outline",
425
- onClick: onRenderRequest,
426
- disabled: isRendering,
427
- className: "w-full gap-2",
428
- children: isRendering ? /* @__PURE__ */ jsxs4(Fragment, { children: [
429
- /* @__PURE__ */ jsx4(Loader2, { size: 14, className: "animate-spin", "data-motion": "essential" }),
430
- "Rendering\u2026"
431
- ] }) : /* @__PURE__ */ jsxs4(Fragment, { children: [
432
- /* @__PURE__ */ jsx4(Play, { size: 14 }),
433
- "Render in sandbox"
434
- ] })
435
- }
436
- )
437
- ] });
364
+ if (content.renderedUrl) return /* @__PURE__ */ jsx("div", {
365
+ className: cn("rounded overflow-hidden aspect-[9/16] bg-black", className),
366
+ children: /* @__PURE__ */ jsx("video", {
367
+ src: content.renderedUrl,
368
+ controls: true,
369
+ className: "w-full h-full object-contain",
370
+ playsInline: true
371
+ })
372
+ });
373
+ const sceneSummary = content.scenes.map((s) => {
374
+ if (s.type === "text-animation") return s.headline;
375
+ if (s.type === "image-reveal") return s.caption ?? "Image";
376
+ if (s.type === "slide") return "Slide";
377
+ if (s.type === "countdown") return `${s.from}…`;
378
+ return "Scene";
379
+ });
380
+ return /* @__PURE__ */ jsxs("div", {
381
+ className: cn("flex flex-col gap-3", className),
382
+ children: [
383
+ /* @__PURE__ */ jsxs("div", {
384
+ className: "relative rounded overflow-hidden aspect-[9/16] flex flex-col items-center justify-center gap-3",
385
+ style: {
386
+ background: brand.primaryColor,
387
+ color: brand.textColor
388
+ },
389
+ children: [
390
+ /* @__PURE__ */ jsx(Film, {
391
+ size: 32,
392
+ className: "opacity-60"
393
+ }),
394
+ /* @__PURE__ */ jsx("div", {
395
+ className: "text-sm font-medium text-center px-4",
396
+ children: brand.businessName
397
+ }),
398
+ /* @__PURE__ */ jsxs("div", {
399
+ className: "text-xs opacity-60",
400
+ children: [
401
+ content.scenes.length,
402
+ " scenes ",
403
+ "·",
404
+ " ",
405
+ content.durationSeconds,
406
+ "s"
407
+ ]
408
+ })
409
+ ]
410
+ }),
411
+ /* @__PURE__ */ jsxs("div", {
412
+ className: "space-y-1",
413
+ children: [/* @__PURE__ */ jsx("div", {
414
+ className: "text-xs font-medium text-muted-foreground",
415
+ children: "Scenes"
416
+ }), sceneSummary.map((label, i) => /* @__PURE__ */ jsxs("div", {
417
+ className: "text-xs text-muted-foreground flex gap-1.5 items-center",
418
+ children: [
419
+ /* @__PURE__ */ jsx("span", {
420
+ className: "font-mono text-[10px] w-4 text-right opacity-50",
421
+ children: i + 1
422
+ }),
423
+ /* @__PURE__ */ jsx("span", { children: label }),
424
+ /* @__PURE__ */ jsxs("span", {
425
+ className: "opacity-40",
426
+ children: [
427
+ "·",
428
+ " ",
429
+ content.scenes[i].durationSeconds,
430
+ "s"
431
+ ]
432
+ })
433
+ ]
434
+ }, i))]
435
+ }),
436
+ onRenderRequest && /* @__PURE__ */ jsx(Button, {
437
+ size: "sm",
438
+ variant: "outline",
439
+ onClick: onRenderRequest,
440
+ disabled: isRendering,
441
+ className: "w-full gap-2",
442
+ children: isRendering ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Loader2, {
443
+ size: 14,
444
+ className: "animate-spin",
445
+ "data-motion": "essential"
446
+ }), "Rendering…"] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Play, { size: 14 }), "Render in sandbox"] })
447
+ })
448
+ ]
449
+ });
438
450
  }
439
-
440
- // src/assets/preview/copy-preview.tsx
441
- import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
442
- var PLATFORM_LIMITS = {
443
- instagram: 2200,
444
- tiktok: 2200,
445
- x: 280,
446
- linkedin: 3e3,
447
- sms: 160,
448
- "email-subject": 60
451
+ //#endregion
452
+ //#region src/assets/preview/copy-preview.tsx
453
+ const PLATFORM_LIMITS = {
454
+ instagram: 2200,
455
+ tiktok: 2200,
456
+ x: 280,
457
+ linkedin: 3e3,
458
+ sms: 160,
459
+ "email-subject": 60
449
460
  };
450
- var PLATFORM_LABELS = {
451
- instagram: "Instagram",
452
- tiktok: "TikTok",
453
- x: "X / Twitter",
454
- linkedin: "LinkedIn",
455
- sms: "SMS",
456
- "email-subject": "Email Subject"
461
+ const PLATFORM_LABELS = {
462
+ instagram: "Instagram",
463
+ tiktok: "TikTok",
464
+ x: "X / Twitter",
465
+ linkedin: "LinkedIn",
466
+ sms: "SMS",
467
+ "email-subject": "Email Subject"
457
468
  };
458
469
  function CopyPreview({ content, className }) {
459
- const limit = PLATFORM_LIMITS[content.platform];
460
- const bodyLen = content.body.length;
461
- const isOverLimit = limit !== null && bodyLen > limit;
462
- const warningThreshold = limit !== null ? limit * 0.9 : null;
463
- const isNearLimit = !isOverLimit && warningThreshold !== null && bodyLen >= warningThreshold;
464
- return /* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col gap-2", className), children: [
465
- /* @__PURE__ */ jsxs5("div", { className: "flex items-center justify-between", children: [
466
- /* @__PURE__ */ jsx5("span", { className: "text-xs font-medium text-muted-foreground", children: PLATFORM_LABELS[content.platform] }),
467
- limit !== null && /* @__PURE__ */ jsxs5("span", { className: cn("text-xs tabular-nums", isOverLimit ? "text-destructive font-medium" : isNearLimit ? "text-warning" : "text-muted-foreground"), children: [
468
- bodyLen,
469
- " / ",
470
- limit
471
- ] })
472
- ] }),
473
- /* @__PURE__ */ jsxs5("div", { className: "rounded border border-border p-3 space-y-2 bg-card", children: [
474
- content.headline && /* @__PURE__ */ jsx5("div", { className: "text-sm font-semibold", children: content.headline }),
475
- /* @__PURE__ */ jsx5("p", { className: "text-sm whitespace-pre-wrap leading-relaxed", children: content.body }),
476
- content.hashtags && content.hashtags.length > 0 && /* @__PURE__ */ jsx5("div", { className: "text-sm text-blue-500 flex flex-wrap gap-1", children: content.hashtags.map((tag) => /* @__PURE__ */ jsxs5("span", { children: [
477
- "#",
478
- tag
479
- ] }, tag)) })
480
- ] })
481
- ] });
470
+ const limit = PLATFORM_LIMITS[content.platform];
471
+ const bodyLen = content.body.length;
472
+ const isOverLimit = limit !== null && bodyLen > limit;
473
+ const warningThreshold = limit !== null ? limit * .9 : null;
474
+ const isNearLimit = !isOverLimit && warningThreshold !== null && bodyLen >= warningThreshold;
475
+ return /* @__PURE__ */ jsxs("div", {
476
+ className: cn("flex flex-col gap-2", className),
477
+ children: [/* @__PURE__ */ jsxs("div", {
478
+ className: "flex items-center justify-between",
479
+ children: [/* @__PURE__ */ jsx("span", {
480
+ className: "text-xs font-medium text-muted-foreground",
481
+ children: PLATFORM_LABELS[content.platform]
482
+ }), limit !== null && /* @__PURE__ */ jsxs("span", {
483
+ className: cn("text-xs tabular-nums", isOverLimit ? "text-destructive font-medium" : isNearLimit ? "text-warning" : "text-muted-foreground"),
484
+ children: [
485
+ bodyLen,
486
+ " / ",
487
+ limit
488
+ ]
489
+ })]
490
+ }), /* @__PURE__ */ jsxs("div", {
491
+ className: "rounded border border-border p-3 space-y-2 bg-card",
492
+ children: [
493
+ content.headline && /* @__PURE__ */ jsx("div", {
494
+ className: "text-sm font-semibold",
495
+ children: content.headline
496
+ }),
497
+ /* @__PURE__ */ jsx("p", {
498
+ className: "text-sm whitespace-pre-wrap leading-relaxed",
499
+ children: content.body
500
+ }),
501
+ content.hashtags && content.hashtags.length > 0 && /* @__PURE__ */ jsx("div", {
502
+ className: "text-sm text-blue-500 flex flex-wrap gap-1",
503
+ children: content.hashtags.map((tag) => /* @__PURE__ */ jsxs("span", { children: ["#", tag] }, tag))
504
+ })
505
+ ]
506
+ })]
507
+ });
482
508
  }
483
-
484
- // src/assets/asset-editor.tsx
485
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
509
+ //#endregion
510
+ //#region src/assets/asset-editor.tsx
486
511
  function BrandPanel({ brand, onChange }) {
487
- const [open, setOpen] = React2.useState(false);
488
- return /* @__PURE__ */ jsxs6("div", { className: "border border-border rounded", children: [
489
- /* @__PURE__ */ jsxs6(
490
- "button",
491
- {
492
- type: "button",
493
- className: "flex w-full items-center justify-between px-3 py-2 text-xs font-medium text-muted-foreground hover:text-foreground",
494
- onClick: () => setOpen((o) => !o),
495
- children: [
496
- "Brand tokens",
497
- open ? /* @__PURE__ */ jsx6(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsx6(ChevronRight2, { size: 14 })
498
- ]
499
- }
500
- ),
501
- open && /* @__PURE__ */ jsxs6("div", { className: "px-3 pb-3 space-y-2 border-t border-border pt-2", children: [
502
- ["primaryColor", "accentColor", "textColor"].map((key) => /* @__PURE__ */ jsxs6("div", { className: "flex items-center gap-2", children: [
503
- /* @__PURE__ */ jsx6(
504
- "input",
505
- {
506
- type: "color",
507
- value: brand[key],
508
- onChange: (e) => onChange({ ...brand, [key]: e.target.value }),
509
- className: "w-6 h-6 rounded cursor-pointer border-0"
510
- }
511
- ),
512
- /* @__PURE__ */ jsx6(Label, { className: "text-xs capitalize", children: key.replace(/Color/, " color") })
513
- ] }, key)),
514
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
515
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Font family" }),
516
- /* @__PURE__ */ jsx6(
517
- Input,
518
- {
519
- value: brand.fontFamily,
520
- onChange: (e) => onChange({ ...brand, fontFamily: e.target.value }),
521
- className: "h-7 text-xs"
522
- }
523
- )
524
- ] }),
525
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
526
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Logo URL" }),
527
- /* @__PURE__ */ jsx6(
528
- Input,
529
- {
530
- value: brand.logoUrl ?? "",
531
- onChange: (e) => onChange({ ...brand, logoUrl: e.target.value || void 0 }),
532
- className: "h-7 text-xs",
533
- placeholder: "https://\u2026"
534
- }
535
- )
536
- ] })
537
- ] })
538
- ] });
512
+ const [open, setOpen] = React$1.useState(false);
513
+ return /* @__PURE__ */ jsxs("div", {
514
+ className: "border border-border rounded",
515
+ children: [/* @__PURE__ */ jsxs("button", {
516
+ type: "button",
517
+ className: "flex w-full items-center justify-between px-3 py-2 text-xs font-medium text-muted-foreground hover:text-foreground",
518
+ onClick: () => setOpen((o) => !o),
519
+ children: ["Brand tokens", open ? /* @__PURE__ */ jsx(ChevronDown, { size: 14 }) : /* @__PURE__ */ jsx(ChevronRight, { size: 14 })]
520
+ }), open && /* @__PURE__ */ jsxs("div", {
521
+ className: "px-3 pb-3 space-y-2 border-t border-border pt-2",
522
+ children: [
523
+ [
524
+ "primaryColor",
525
+ "accentColor",
526
+ "textColor"
527
+ ].map((key) => /* @__PURE__ */ jsxs("div", {
528
+ className: "flex items-center gap-2",
529
+ children: [/* @__PURE__ */ jsx("input", {
530
+ type: "color",
531
+ value: brand[key],
532
+ onChange: (e) => onChange({
533
+ ...brand,
534
+ [key]: e.target.value
535
+ }),
536
+ className: "w-6 h-6 rounded cursor-pointer border-0"
537
+ }), /* @__PURE__ */ jsx(Label, {
538
+ className: "text-xs capitalize",
539
+ children: key.replace(/Color/, " color")
540
+ })]
541
+ }, key)),
542
+ /* @__PURE__ */ jsxs("div", {
543
+ className: "space-y-1",
544
+ children: [/* @__PURE__ */ jsx(Label, {
545
+ className: "text-xs",
546
+ children: "Font family"
547
+ }), /* @__PURE__ */ jsx(Input, {
548
+ value: brand.fontFamily,
549
+ onChange: (e) => onChange({
550
+ ...brand,
551
+ fontFamily: e.target.value
552
+ }),
553
+ className: "h-7 text-xs"
554
+ })]
555
+ }),
556
+ /* @__PURE__ */ jsxs("div", {
557
+ className: "space-y-1",
558
+ children: [/* @__PURE__ */ jsx(Label, {
559
+ className: "text-xs",
560
+ children: "Logo URL"
561
+ }), /* @__PURE__ */ jsx(Input, {
562
+ value: brand.logoUrl ?? "",
563
+ onChange: (e) => onChange({
564
+ ...brand,
565
+ logoUrl: e.target.value || void 0
566
+ }),
567
+ className: "h-7 text-xs",
568
+ placeholder: "https://…"
569
+ })]
570
+ })
571
+ ]
572
+ })]
573
+ });
539
574
  }
540
575
  function EmailEditor({ spec, onChange }) {
541
- const content = spec.content;
542
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
543
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
544
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Subject" }),
545
- /* @__PURE__ */ jsx6(
546
- Input,
547
- {
548
- value: content.subject,
549
- onChange: (e) => onChange({ ...spec, content: { ...content, subject: e.target.value } }),
550
- className: "h-8 text-sm"
551
- }
552
- )
553
- ] }),
554
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
555
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Preheader" }),
556
- /* @__PURE__ */ jsx6(
557
- Input,
558
- {
559
- value: content.preheader ?? "",
560
- onChange: (e) => onChange({ ...spec, content: { ...content, preheader: e.target.value || void 0 } }),
561
- className: "h-8 text-sm",
562
- placeholder: "Preview text\u2026"
563
- }
564
- )
565
- ] }),
566
- content.sections.map((section, i) => {
567
- if (section.type === "hero" || section.type === "body" || section.type === "cta") {
568
- const textField = section.type === "body" ? "text" : section.type === "cta" ? "label" : "headline";
569
- const val = section[textField] ?? "";
570
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
571
- /* @__PURE__ */ jsxs6(Label, { className: "text-xs capitalize", children: [
572
- section.type,
573
- " \u2014 ",
574
- textField
575
- ] }),
576
- /* @__PURE__ */ jsx6(
577
- Textarea,
578
- {
579
- value: val,
580
- onChange: (e) => {
581
- const updated = [...content.sections];
582
- updated[i] = { ...updated[i], [textField]: e.target.value };
583
- onChange({ ...spec, content: { ...content, sections: updated } });
584
- },
585
- className: "text-sm min-h-[60px]"
586
- }
587
- )
588
- ] }, i);
589
- }
590
- return null;
591
- })
592
- ] });
576
+ const content = spec.content;
577
+ return /* @__PURE__ */ jsxs("div", {
578
+ className: "space-y-3",
579
+ children: [
580
+ /* @__PURE__ */ jsxs("div", {
581
+ className: "space-y-1",
582
+ children: [/* @__PURE__ */ jsx(Label, {
583
+ className: "text-xs",
584
+ children: "Subject"
585
+ }), /* @__PURE__ */ jsx(Input, {
586
+ value: content.subject,
587
+ onChange: (e) => onChange({
588
+ ...spec,
589
+ content: {
590
+ ...content,
591
+ subject: e.target.value
592
+ }
593
+ }),
594
+ className: "h-8 text-sm"
595
+ })]
596
+ }),
597
+ /* @__PURE__ */ jsxs("div", {
598
+ className: "space-y-1",
599
+ children: [/* @__PURE__ */ jsx(Label, {
600
+ className: "text-xs",
601
+ children: "Preheader"
602
+ }), /* @__PURE__ */ jsx(Input, {
603
+ value: content.preheader ?? "",
604
+ onChange: (e) => onChange({
605
+ ...spec,
606
+ content: {
607
+ ...content,
608
+ preheader: e.target.value || void 0
609
+ }
610
+ }),
611
+ className: "h-8 text-sm",
612
+ placeholder: "Preview text…"
613
+ })]
614
+ }),
615
+ content.sections.map((section, i) => {
616
+ if (section.type === "hero" || section.type === "body" || section.type === "cta") {
617
+ const textField = section.type === "body" ? "text" : section.type === "cta" ? "label" : "headline";
618
+ const val = section[textField] ?? "";
619
+ return /* @__PURE__ */ jsxs("div", {
620
+ className: "space-y-1",
621
+ children: [/* @__PURE__ */ jsxs(Label, {
622
+ className: "text-xs capitalize",
623
+ children: [
624
+ section.type,
625
+ " — ",
626
+ textField
627
+ ]
628
+ }), /* @__PURE__ */ jsx(Textarea, {
629
+ value: val,
630
+ onChange: (e) => {
631
+ const updated = [...content.sections];
632
+ updated[i] = {
633
+ ...updated[i],
634
+ [textField]: e.target.value
635
+ };
636
+ onChange({
637
+ ...spec,
638
+ content: {
639
+ ...content,
640
+ sections: updated
641
+ }
642
+ });
643
+ },
644
+ className: "text-sm min-h-[60px]"
645
+ })]
646
+ }, i);
647
+ }
648
+ return null;
649
+ })
650
+ ]
651
+ });
593
652
  }
594
653
  function CopyEditor({ spec, onChange }) {
595
- const content = spec.content;
596
- return /* @__PURE__ */ jsxs6("div", { className: "space-y-3", children: [
597
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
598
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Headline" }),
599
- /* @__PURE__ */ jsx6(
600
- Input,
601
- {
602
- value: content.headline,
603
- onChange: (e) => onChange({ ...spec, content: { ...content, headline: e.target.value } }),
604
- className: "h-8 text-sm"
605
- }
606
- )
607
- ] }),
608
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
609
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Body" }),
610
- /* @__PURE__ */ jsx6(
611
- Textarea,
612
- {
613
- value: content.body,
614
- onChange: (e) => onChange({ ...spec, content: { ...content, body: e.target.value } }),
615
- className: "text-sm min-h-[80px]"
616
- }
617
- )
618
- ] }),
619
- /* @__PURE__ */ jsxs6("div", { className: "space-y-1", children: [
620
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Hashtags (comma-separated)" }),
621
- /* @__PURE__ */ jsx6(
622
- Input,
623
- {
624
- value: (content.hashtags ?? []).join(", "),
625
- onChange: (e) => onChange({ ...spec, content: { ...content, hashtags: e.target.value.split(",").map((t) => t.trim()).filter(Boolean) } }),
626
- className: "h-8 text-sm",
627
- placeholder: "marketing, growth, saas"
628
- }
629
- )
630
- ] })
631
- ] });
654
+ const content = spec.content;
655
+ return /* @__PURE__ */ jsxs("div", {
656
+ className: "space-y-3",
657
+ children: [
658
+ /* @__PURE__ */ jsxs("div", {
659
+ className: "space-y-1",
660
+ children: [/* @__PURE__ */ jsx(Label, {
661
+ className: "text-xs",
662
+ children: "Headline"
663
+ }), /* @__PURE__ */ jsx(Input, {
664
+ value: content.headline,
665
+ onChange: (e) => onChange({
666
+ ...spec,
667
+ content: {
668
+ ...content,
669
+ headline: e.target.value
670
+ }
671
+ }),
672
+ className: "h-8 text-sm"
673
+ })]
674
+ }),
675
+ /* @__PURE__ */ jsxs("div", {
676
+ className: "space-y-1",
677
+ children: [/* @__PURE__ */ jsx(Label, {
678
+ className: "text-xs",
679
+ children: "Body"
680
+ }), /* @__PURE__ */ jsx(Textarea, {
681
+ value: content.body,
682
+ onChange: (e) => onChange({
683
+ ...spec,
684
+ content: {
685
+ ...content,
686
+ body: e.target.value
687
+ }
688
+ }),
689
+ className: "text-sm min-h-[80px]"
690
+ })]
691
+ }),
692
+ /* @__PURE__ */ jsxs("div", {
693
+ className: "space-y-1",
694
+ children: [/* @__PURE__ */ jsx(Label, {
695
+ className: "text-xs",
696
+ children: "Hashtags (comma-separated)"
697
+ }), /* @__PURE__ */ jsx(Input, {
698
+ value: (content.hashtags ?? []).join(", "),
699
+ onChange: (e) => onChange({
700
+ ...spec,
701
+ content: {
702
+ ...content,
703
+ hashtags: e.target.value.split(",").map((t) => t.trim()).filter(Boolean)
704
+ }
705
+ }),
706
+ className: "h-8 text-sm",
707
+ placeholder: "marketing, growth, saas"
708
+ })]
709
+ })
710
+ ]
711
+ });
632
712
  }
633
713
  function Preview({ spec, previewUrl, isRendering, onRenderRequest }) {
634
- const { format, brand, content } = spec;
635
- if (format === "email") return /* @__PURE__ */ jsx6(EmailPreview, { content, brand, previewUrl });
636
- if (format.startsWith("image:")) {
637
- const imgFormat = format === "image:story" ? "story" : format === "image:carousel" ? "carousel" : "feed";
638
- return /* @__PURE__ */ jsx6(ImagePreview, { content, brand, format: imgFormat });
639
- }
640
- if (format.startsWith("video:")) {
641
- return /* @__PURE__ */ jsx6(VideoPreview, { content, brand, onRenderRequest, isRendering });
642
- }
643
- if (format.startsWith("copy:")) {
644
- return /* @__PURE__ */ jsx6(CopyPreview, { content });
645
- }
646
- return null;
714
+ const { format, brand, content } = spec;
715
+ if (format === "email") return /* @__PURE__ */ jsx(EmailPreview, {
716
+ content,
717
+ brand,
718
+ previewUrl
719
+ });
720
+ if (format.startsWith("image:")) return /* @__PURE__ */ jsx(ImagePreview, {
721
+ content,
722
+ brand,
723
+ format: format === "image:story" ? "story" : format === "image:carousel" ? "carousel" : "feed"
724
+ });
725
+ if (format.startsWith("video:")) return /* @__PURE__ */ jsx(VideoPreview, {
726
+ content,
727
+ brand,
728
+ onRenderRequest,
729
+ isRendering
730
+ });
731
+ if (format.startsWith("copy:")) return /* @__PURE__ */ jsx(CopyPreview, { content });
732
+ return null;
647
733
  }
648
734
  function AssetEditor({ spec, previewUrl, isRendering, onSave, onRenderRequest, onRevisionRequest, className }) {
649
- const [draft, setDraft] = React2.useState(spec);
650
- const [revisionInput, setRevisionInput] = React2.useState("");
651
- const isDirty = JSON.stringify(draft) !== JSON.stringify(spec);
652
- React2.useEffect(() => {
653
- setDraft(spec);
654
- }, [spec]);
655
- const handleRevision = () => {
656
- if (!revisionInput.trim()) return;
657
- onRevisionRequest?.(revisionInput.trim());
658
- setRevisionInput("");
659
- };
660
- return /* @__PURE__ */ jsxs6("div", { className: cn("grid grid-cols-2 gap-4 h-full min-h-0", className), children: [
661
- /* @__PURE__ */ jsxs6("div", { className: "flex flex-col gap-3 overflow-y-auto pr-1", children: [
662
- /* @__PURE__ */ jsxs6("div", { className: "flex items-center justify-between", children: [
663
- /* @__PURE__ */ jsx6("span", { className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider", children: draft.format }),
664
- isDirty && onSave && /* @__PURE__ */ jsxs6(Button2, { size: "sm", variant: "default", onClick: () => onSave(draft), className: "h-7 gap-1.5 text-xs", children: [
665
- /* @__PURE__ */ jsx6(Save, { size: 12 }),
666
- "Save"
667
- ] })
668
- ] }),
669
- draft.format === "email" && /* @__PURE__ */ jsx6(EmailEditor, { spec: draft, onChange: (s) => setDraft(s) }),
670
- (draft.format === "copy:caption" || draft.format === "copy:headline" || draft.format === "copy:sms") && /* @__PURE__ */ jsx6(CopyEditor, { spec: draft, onChange: (s) => setDraft(s) }),
671
- /* @__PURE__ */ jsx6(BrandPanel, { brand: draft.brand, onChange: (b) => setDraft((d) => ({ ...d, brand: b })) }),
672
- onRevisionRequest && /* @__PURE__ */ jsxs6("div", { className: "space-y-1.5", children: [
673
- /* @__PURE__ */ jsx6(Label, { className: "text-xs", children: "Ask agent to revise" }),
674
- /* @__PURE__ */ jsxs6("div", { className: "flex gap-1.5", children: [
675
- /* @__PURE__ */ jsx6(
676
- Input,
677
- {
678
- value: revisionInput,
679
- onChange: (e) => setRevisionInput(e.target.value),
680
- placeholder: "Make the CTA more urgent\u2026",
681
- className: "h-8 text-sm",
682
- onKeyDown: (e) => {
683
- if (e.key === "Enter" && !e.shiftKey) {
684
- e.preventDefault();
685
- handleRevision();
686
- }
687
- }
688
- }
689
- ),
690
- /* @__PURE__ */ jsx6(
691
- Button2,
692
- {
693
- size: "sm",
694
- variant: "outline",
695
- onClick: handleRevision,
696
- disabled: !revisionInput.trim(),
697
- className: "h-8 px-2",
698
- children: /* @__PURE__ */ jsx6(MessageSquare, { size: 14 })
699
- }
700
- )
701
- ] })
702
- ] })
703
- ] }),
704
- /* @__PURE__ */ jsx6("div", { className: "overflow-y-auto", children: /* @__PURE__ */ jsx6(
705
- Preview,
706
- {
707
- spec: draft,
708
- previewUrl,
709
- isRendering,
710
- onRenderRequest
711
- }
712
- ) })
713
- ] });
735
+ const [draft, setDraft] = React$1.useState(spec);
736
+ const [revisionInput, setRevisionInput] = React$1.useState("");
737
+ const isDirty = JSON.stringify(draft) !== JSON.stringify(spec);
738
+ React$1.useEffect(() => {
739
+ setDraft(spec);
740
+ }, [spec]);
741
+ const handleRevision = () => {
742
+ if (!revisionInput.trim()) return;
743
+ onRevisionRequest?.(revisionInput.trim());
744
+ setRevisionInput("");
745
+ };
746
+ return /* @__PURE__ */ jsxs("div", {
747
+ className: cn("grid grid-cols-2 gap-4 h-full min-h-0", className),
748
+ children: [/* @__PURE__ */ jsxs("div", {
749
+ className: "flex flex-col gap-3 overflow-y-auto pr-1",
750
+ children: [
751
+ /* @__PURE__ */ jsxs("div", {
752
+ className: "flex items-center justify-between",
753
+ children: [/* @__PURE__ */ jsx("span", {
754
+ className: "text-xs font-semibold text-muted-foreground uppercase tracking-wider",
755
+ children: draft.format
756
+ }), isDirty && onSave && /* @__PURE__ */ jsxs(Button, {
757
+ size: "sm",
758
+ variant: "default",
759
+ onClick: () => onSave(draft),
760
+ className: "h-7 gap-1.5 text-xs",
761
+ children: [/* @__PURE__ */ jsx(Save, { size: 12 }), "Save"]
762
+ })]
763
+ }),
764
+ draft.format === "email" && /* @__PURE__ */ jsx(EmailEditor, {
765
+ spec: draft,
766
+ onChange: (s) => setDraft(s)
767
+ }),
768
+ (draft.format === "copy:caption" || draft.format === "copy:headline" || draft.format === "copy:sms") && /* @__PURE__ */ jsx(CopyEditor, {
769
+ spec: draft,
770
+ onChange: (s) => setDraft(s)
771
+ }),
772
+ /* @__PURE__ */ jsx(BrandPanel, {
773
+ brand: draft.brand,
774
+ onChange: (b) => setDraft((d) => ({
775
+ ...d,
776
+ brand: b
777
+ }))
778
+ }),
779
+ onRevisionRequest && /* @__PURE__ */ jsxs("div", {
780
+ className: "space-y-1.5",
781
+ children: [/* @__PURE__ */ jsx(Label, {
782
+ className: "text-xs",
783
+ children: "Ask agent to revise"
784
+ }), /* @__PURE__ */ jsxs("div", {
785
+ className: "flex gap-1.5",
786
+ children: [/* @__PURE__ */ jsx(Input, {
787
+ value: revisionInput,
788
+ onChange: (e) => setRevisionInput(e.target.value),
789
+ placeholder: "Make the CTA more urgent…",
790
+ className: "h-8 text-sm",
791
+ onKeyDown: (e) => {
792
+ if (e.key === "Enter" && !e.shiftKey) {
793
+ e.preventDefault();
794
+ handleRevision();
795
+ }
796
+ }
797
+ }), /* @__PURE__ */ jsx(Button, {
798
+ size: "sm",
799
+ variant: "outline",
800
+ onClick: handleRevision,
801
+ disabled: !revisionInput.trim(),
802
+ className: "h-8 px-2",
803
+ children: /* @__PURE__ */ jsx(MessageSquare, { size: 14 })
804
+ })]
805
+ })]
806
+ })
807
+ ]
808
+ }), /* @__PURE__ */ jsx("div", {
809
+ className: "overflow-y-auto",
810
+ children: /* @__PURE__ */ jsx(Preview, {
811
+ spec: draft,
812
+ previewUrl,
813
+ isRendering,
814
+ onRenderRequest
815
+ })
816
+ })]
817
+ });
714
818
  }
715
-
716
- // src/assets/approval-queue.tsx
717
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
718
- var FORMAT_OPTIONS = [
719
- { value: "all", label: "All formats" },
720
- { value: "email", label: "Email" },
721
- { value: "image:feed", label: "Feed image" },
722
- { value: "image:story", label: "Story" },
723
- { value: "image:carousel", label: "Carousel" },
724
- { value: "video:reel", label: "Reel" },
725
- { value: "video:feed", label: "Video" },
726
- { value: "copy:caption", label: "Caption" },
727
- { value: "copy:headline", label: "Headline" },
728
- { value: "copy:sms", label: "SMS" }
819
+ //#endregion
820
+ //#region src/assets/approval-queue.tsx
821
+ const FORMAT_OPTIONS = [
822
+ {
823
+ value: "all",
824
+ label: "All formats"
825
+ },
826
+ {
827
+ value: "email",
828
+ label: "Email"
829
+ },
830
+ {
831
+ value: "image:feed",
832
+ label: "Feed image"
833
+ },
834
+ {
835
+ value: "image:story",
836
+ label: "Story"
837
+ },
838
+ {
839
+ value: "image:carousel",
840
+ label: "Carousel"
841
+ },
842
+ {
843
+ value: "video:reel",
844
+ label: "Reel"
845
+ },
846
+ {
847
+ value: "video:feed",
848
+ label: "Video"
849
+ },
850
+ {
851
+ value: "copy:caption",
852
+ label: "Caption"
853
+ },
854
+ {
855
+ value: "copy:headline",
856
+ label: "Headline"
857
+ },
858
+ {
859
+ value: "copy:sms",
860
+ label: "SMS"
861
+ }
729
862
  ];
730
- var STATUS_OPTIONS = [
731
- { value: "all", label: "All statuses" },
732
- { value: "pending_review", label: "Needs review" },
733
- { value: "approved", label: "Approved" },
734
- { value: "rejected", label: "Rejected" },
735
- { value: "scheduled", label: "Scheduled" },
736
- { value: "published", label: "Published" }
863
+ const STATUS_OPTIONS = [
864
+ {
865
+ value: "all",
866
+ label: "All statuses"
867
+ },
868
+ {
869
+ value: "pending_review",
870
+ label: "Needs review"
871
+ },
872
+ {
873
+ value: "approved",
874
+ label: "Approved"
875
+ },
876
+ {
877
+ value: "rejected",
878
+ label: "Rejected"
879
+ },
880
+ {
881
+ value: "scheduled",
882
+ label: "Scheduled"
883
+ },
884
+ {
885
+ value: "published",
886
+ label: "Published"
887
+ }
737
888
  ];
738
- function ApprovalQueue({
739
- assets,
740
- variantCounts = {},
741
- onApprove,
742
- onReject,
743
- onEdit,
744
- onSave,
745
- onRevisionRequest,
746
- onRenderRequest,
747
- renderingIds = /* @__PURE__ */ new Set(),
748
- previewUrls = {},
749
- className
750
- }) {
751
- const [search, setSearch] = React3.useState("");
752
- const [formatFilter, setFormatFilter] = React3.useState("all");
753
- const [statusFilter, setStatusFilter] = React3.useState("pending_review");
754
- const [editingId, setEditingId] = React3.useState(null);
755
- const [scheduleDate, setScheduleDate] = React3.useState("");
756
- const filtered = assets.filter((a) => {
757
- if (formatFilter !== "all" && a.format !== formatFilter) return false;
758
- if (statusFilter !== "all" && a.status !== statusFilter) return false;
759
- if (search) {
760
- const q = search.toLowerCase();
761
- if (!a.id.includes(q) && !a.format.includes(q) && !a.brand.businessName.toLowerCase().includes(q)) return false;
762
- }
763
- return true;
764
- });
765
- const pendingCount = assets.filter((a) => a.status === "pending_review").length;
766
- const handleBulkApprove = () => {
767
- filtered.filter((a) => a.status === "pending_review").forEach((a) => onApprove?.(a.id, scheduleDate || void 0));
768
- };
769
- const editingSpec = editingId ? assets.find((a) => a.id === editingId) : null;
770
- if (editingSpec) {
771
- return /* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col gap-3 h-full", className), children: [
772
- /* @__PURE__ */ jsxs7("div", { className: "flex items-center justify-between", children: [
773
- /* @__PURE__ */ jsx7(
774
- "button",
775
- {
776
- type: "button",
777
- onClick: () => setEditingId(null),
778
- className: "text-xs text-muted-foreground hover:text-foreground",
779
- children: "\u2190 Back to queue"
780
- }
781
- ),
782
- /* @__PURE__ */ jsxs7("div", { className: "flex gap-2", children: [
783
- onReject && editingSpec.status === "pending_review" && /* @__PURE__ */ jsx7(Button3, { size: "sm", variant: "outline", onClick: () => {
784
- onReject(editingSpec.id);
785
- setEditingId(null);
786
- }, children: "Reject" }),
787
- onApprove && editingSpec.status === "pending_review" && /* @__PURE__ */ jsx7(Button3, { size: "sm", onClick: () => {
788
- onApprove(editingSpec.id, scheduleDate || void 0);
789
- setEditingId(null);
790
- }, children: "Approve" })
791
- ] })
792
- ] }),
793
- /* @__PURE__ */ jsx7(
794
- AssetEditor,
795
- {
796
- spec: editingSpec,
797
- previewUrl: previewUrls[editingSpec.id],
798
- isRendering: renderingIds.has(editingSpec.id),
799
- onSave: (s) => {
800
- onSave?.(s);
801
- setEditingId(null);
802
- },
803
- onRenderRequest: () => onRenderRequest?.(editingSpec.id),
804
- onRevisionRequest: (instr) => onRevisionRequest?.(editingSpec.id, instr),
805
- className: "flex-1"
806
- }
807
- )
808
- ] });
809
- }
810
- return /* @__PURE__ */ jsxs7("div", { className: cn("flex flex-col gap-3", className), children: [
811
- /* @__PURE__ */ jsxs7("div", { className: "flex flex-wrap gap-2 items-center", children: [
812
- /* @__PURE__ */ jsxs7("div", { className: "relative flex-1 min-w-40", children: [
813
- /* @__PURE__ */ jsx7(Search, { size: 12, className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground" }),
814
- /* @__PURE__ */ jsx7(
815
- Input2,
816
- {
817
- value: search,
818
- onChange: (e) => setSearch(e.target.value),
819
- placeholder: "Filter assets\u2026",
820
- className: "h-8 pl-7 text-sm"
821
- }
822
- )
823
- ] }),
824
- /* @__PURE__ */ jsx7(
825
- "select",
826
- {
827
- value: formatFilter,
828
- onChange: (e) => setFormatFilter(e.target.value),
829
- className: "h-8 px-2 text-xs rounded border border-input bg-background",
830
- children: FORMAT_OPTIONS.map((o) => /* @__PURE__ */ jsx7("option", { value: o.value, children: o.label }, o.value))
831
- }
832
- ),
833
- /* @__PURE__ */ jsx7(
834
- "select",
835
- {
836
- value: statusFilter,
837
- onChange: (e) => setStatusFilter(e.target.value),
838
- className: "h-8 px-2 text-xs rounded border border-input bg-background",
839
- children: STATUS_OPTIONS.map((o) => /* @__PURE__ */ jsx7("option", { value: o.value, children: o.label }, o.value))
840
- }
841
- ),
842
- pendingCount > 0 && onApprove && /* @__PURE__ */ jsxs7("div", { className: "flex items-center gap-1.5", children: [
843
- /* @__PURE__ */ jsx7(
844
- "input",
845
- {
846
- type: "date",
847
- value: scheduleDate,
848
- onChange: (e) => setScheduleDate(e.target.value),
849
- className: "h-8 px-2 text-xs rounded border border-input bg-background w-32"
850
- }
851
- ),
852
- /* @__PURE__ */ jsxs7(Button3, { size: "sm", variant: "outline", onClick: handleBulkApprove, className: "h-8 gap-1.5 text-xs", children: [
853
- /* @__PURE__ */ jsx7(CheckCheck, { size: 13 }),
854
- "Approve all (",
855
- pendingCount,
856
- ")"
857
- ] })
858
- ] })
859
- ] }),
860
- filtered.length === 0 ? /* @__PURE__ */ jsx7("div", { className: "py-12 text-center text-sm text-muted-foreground", children: "No assets match your filters." }) : /* @__PURE__ */ jsx7("div", { className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3", children: filtered.map((a) => /* @__PURE__ */ jsx7(
861
- AssetCard,
862
- {
863
- spec: a,
864
- variantCount: variantCounts[a.id],
865
- onApprove,
866
- onReject,
867
- onEdit: () => setEditingId(a.id)
868
- },
869
- a.id
870
- )) })
871
- ] });
889
+ function ApprovalQueue({ assets, variantCounts = {}, onApprove, onReject, onEdit, onSave, onRevisionRequest, onRenderRequest, renderingIds = /* @__PURE__ */ new Set(), previewUrls = {}, className }) {
890
+ const [search, setSearch] = React$1.useState("");
891
+ const [formatFilter, setFormatFilter] = React$1.useState("all");
892
+ const [statusFilter, setStatusFilter] = React$1.useState("pending_review");
893
+ const [editingId, setEditingId] = React$1.useState(null);
894
+ const [scheduleDate, setScheduleDate] = React$1.useState("");
895
+ const filtered = assets.filter((a) => {
896
+ if (formatFilter !== "all" && a.format !== formatFilter) return false;
897
+ if (statusFilter !== "all" && a.status !== statusFilter) return false;
898
+ if (search) {
899
+ const q = search.toLowerCase();
900
+ if (!a.id.includes(q) && !a.format.includes(q) && !a.brand.businessName.toLowerCase().includes(q)) return false;
901
+ }
902
+ return true;
903
+ });
904
+ const pendingCount = assets.filter((a) => a.status === "pending_review").length;
905
+ const handleBulkApprove = () => {
906
+ filtered.filter((a) => a.status === "pending_review").forEach((a) => onApprove?.(a.id, scheduleDate || void 0));
907
+ };
908
+ const editingSpec = editingId ? assets.find((a) => a.id === editingId) : null;
909
+ if (editingSpec) return /* @__PURE__ */ jsxs("div", {
910
+ className: cn("flex flex-col gap-3 h-full", className),
911
+ children: [/* @__PURE__ */ jsxs("div", {
912
+ className: "flex items-center justify-between",
913
+ children: [/* @__PURE__ */ jsx("button", {
914
+ type: "button",
915
+ onClick: () => setEditingId(null),
916
+ className: "text-xs text-muted-foreground hover:text-foreground",
917
+ children: "← Back to queue"
918
+ }), /* @__PURE__ */ jsxs("div", {
919
+ className: "flex gap-2",
920
+ children: [onReject && editingSpec.status === "pending_review" && /* @__PURE__ */ jsx(Button, {
921
+ size: "sm",
922
+ variant: "outline",
923
+ onClick: () => {
924
+ onReject(editingSpec.id);
925
+ setEditingId(null);
926
+ },
927
+ children: "Reject"
928
+ }), onApprove && editingSpec.status === "pending_review" && /* @__PURE__ */ jsx(Button, {
929
+ size: "sm",
930
+ onClick: () => {
931
+ onApprove(editingSpec.id, scheduleDate || void 0);
932
+ setEditingId(null);
933
+ },
934
+ children: "Approve"
935
+ })]
936
+ })]
937
+ }), /* @__PURE__ */ jsx(AssetEditor, {
938
+ spec: editingSpec,
939
+ previewUrl: previewUrls[editingSpec.id],
940
+ isRendering: renderingIds.has(editingSpec.id),
941
+ onSave: (s) => {
942
+ onSave?.(s);
943
+ setEditingId(null);
944
+ },
945
+ onRenderRequest: () => onRenderRequest?.(editingSpec.id),
946
+ onRevisionRequest: (instr) => onRevisionRequest?.(editingSpec.id, instr),
947
+ className: "flex-1"
948
+ })]
949
+ });
950
+ return /* @__PURE__ */ jsxs("div", {
951
+ className: cn("flex flex-col gap-3", className),
952
+ children: [/* @__PURE__ */ jsxs("div", {
953
+ className: "flex flex-wrap gap-2 items-center",
954
+ children: [
955
+ /* @__PURE__ */ jsxs("div", {
956
+ className: "relative flex-1 min-w-40",
957
+ children: [/* @__PURE__ */ jsx(Search, {
958
+ size: 12,
959
+ className: "absolute left-2.5 top-1/2 -translate-y-1/2 text-muted-foreground"
960
+ }), /* @__PURE__ */ jsx(Input, {
961
+ value: search,
962
+ onChange: (e) => setSearch(e.target.value),
963
+ placeholder: "Filter assets…",
964
+ className: "h-8 pl-7 text-sm"
965
+ })]
966
+ }),
967
+ /* @__PURE__ */ jsx("select", {
968
+ value: formatFilter,
969
+ onChange: (e) => setFormatFilter(e.target.value),
970
+ className: "h-8 px-2 text-xs rounded border border-input bg-background",
971
+ children: FORMAT_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", {
972
+ value: o.value,
973
+ children: o.label
974
+ }, o.value))
975
+ }),
976
+ /* @__PURE__ */ jsx("select", {
977
+ value: statusFilter,
978
+ onChange: (e) => setStatusFilter(e.target.value),
979
+ className: "h-8 px-2 text-xs rounded border border-input bg-background",
980
+ children: STATUS_OPTIONS.map((o) => /* @__PURE__ */ jsx("option", {
981
+ value: o.value,
982
+ children: o.label
983
+ }, o.value))
984
+ }),
985
+ pendingCount > 0 && onApprove && /* @__PURE__ */ jsxs("div", {
986
+ className: "flex items-center gap-1.5",
987
+ children: [/* @__PURE__ */ jsx("input", {
988
+ type: "date",
989
+ value: scheduleDate,
990
+ onChange: (e) => setScheduleDate(e.target.value),
991
+ className: "h-8 px-2 text-xs rounded border border-input bg-background w-32"
992
+ }), /* @__PURE__ */ jsxs(Button, {
993
+ size: "sm",
994
+ variant: "outline",
995
+ onClick: handleBulkApprove,
996
+ className: "h-8 gap-1.5 text-xs",
997
+ children: [
998
+ /* @__PURE__ */ jsx(CheckCheck, { size: 13 }),
999
+ "Approve all (",
1000
+ pendingCount,
1001
+ ")"
1002
+ ]
1003
+ })]
1004
+ })
1005
+ ]
1006
+ }), filtered.length === 0 ? /* @__PURE__ */ jsx("div", {
1007
+ className: "py-12 text-center text-sm text-muted-foreground",
1008
+ children: "No assets match your filters."
1009
+ }) : /* @__PURE__ */ jsx("div", {
1010
+ className: "grid grid-cols-2 sm:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-3",
1011
+ children: filtered.map((a) => /* @__PURE__ */ jsx(AssetCard, {
1012
+ spec: a,
1013
+ variantCount: variantCounts[a.id],
1014
+ onApprove,
1015
+ onReject,
1016
+ onEdit: () => setEditingId(a.id)
1017
+ }, a.id))
1018
+ })]
1019
+ });
872
1020
  }
873
-
874
- // src/assets/variant-compare.tsx
875
- import { Button as Button4, Badge as Badge2 } from "@tangle-network/ui/primitives";
876
- import { Trophy } from "lucide-react";
877
- import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1021
+ //#endregion
1022
+ //#region src/assets/variant-compare.tsx
878
1023
  function VariantCompare({ variants, onPromote, onSave, className }) {
879
- const cols = Math.min(variants.length, 3);
880
- if (variants.length === 0) {
881
- return /* @__PURE__ */ jsx8("div", { className: cn("py-12 text-center text-sm text-muted-foreground", className), children: "No variants to compare." });
882
- }
883
- return /* @__PURE__ */ jsxs8("div", { className: cn("flex flex-col gap-3", className), children: [
884
- /* @__PURE__ */ jsxs8("div", { className: "text-xs text-muted-foreground", children: [
885
- "Comparing ",
886
- variants.length,
887
- " variant",
888
- variants.length !== 1 ? "s" : "",
889
- ".",
890
- onPromote && " Promote one to mark it approved and archive the rest."
891
- ] }),
892
- /* @__PURE__ */ jsx8(
893
- "div",
894
- {
895
- className: "grid gap-3",
896
- style: { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` },
897
- children: variants.slice(0, 3).map((variant) => /* @__PURE__ */ jsxs8("div", { className: "flex flex-col gap-2 rounded-lg border border-border p-3", children: [
898
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center justify-between", children: [
899
- /* @__PURE__ */ jsxs8("div", { className: "flex items-center gap-1.5", children: [
900
- /* @__PURE__ */ jsx8("span", { className: "text-xs font-medium", children: variant.label }),
901
- variant.approvedAt && /* @__PURE__ */ jsx8(Badge2, { variant: "default", className: "text-[10px] h-4 px-1.5", children: "Approved" }),
902
- variant.rejectedAt && /* @__PURE__ */ jsx8(Badge2, { variant: "destructive", className: "text-[10px] h-4 px-1.5", children: "Rejected" })
903
- ] }),
904
- onPromote && !variant.approvedAt && /* @__PURE__ */ jsxs8(
905
- Button4,
906
- {
907
- size: "sm",
908
- variant: "outline",
909
- onClick: () => onPromote(variant.id),
910
- className: "h-6 gap-1 text-[11px] px-2",
911
- children: [
912
- /* @__PURE__ */ jsx8(Trophy, { size: 11 }),
913
- "Pick this"
914
- ]
915
- }
916
- )
917
- ] }),
918
- /* @__PURE__ */ jsx8(
919
- AssetEditor,
920
- {
921
- spec: variant.spec,
922
- onSave: (spec) => onSave?.({ ...variant, spec }),
923
- className: "min-h-[300px]"
924
- }
925
- ),
926
- variant.editLog.length > 0 && /* @__PURE__ */ jsxs8("div", { className: "text-[10px] text-muted-foreground space-y-0.5", children: [
927
- /* @__PURE__ */ jsx8("span", { className: "font-medium", children: "Edit history" }),
928
- variant.editLog.slice(-3).map((ev, i) => /* @__PURE__ */ jsxs8("div", { children: [
929
- ev.action,
930
- ev.editedFields?.length ? ` (${ev.editedFields.join(", ")})` : ""
931
- ] }, i))
932
- ] })
933
- ] }, variant.id))
934
- }
935
- )
936
- ] });
1024
+ const cols = Math.min(variants.length, 3);
1025
+ if (variants.length === 0) return /* @__PURE__ */ jsx("div", {
1026
+ className: cn("py-12 text-center text-sm text-muted-foreground", className),
1027
+ children: "No variants to compare."
1028
+ });
1029
+ return /* @__PURE__ */ jsxs("div", {
1030
+ className: cn("flex flex-col gap-3", className),
1031
+ children: [/* @__PURE__ */ jsxs("div", {
1032
+ className: "text-xs text-muted-foreground",
1033
+ children: [
1034
+ "Comparing ",
1035
+ variants.length,
1036
+ " variant",
1037
+ variants.length !== 1 ? "s" : "",
1038
+ ".",
1039
+ onPromote && " Promote one to mark it approved and archive the rest."
1040
+ ]
1041
+ }), /* @__PURE__ */ jsx("div", {
1042
+ className: "grid gap-3",
1043
+ style: { gridTemplateColumns: `repeat(${cols}, minmax(0, 1fr))` },
1044
+ children: variants.slice(0, 3).map((variant) => /* @__PURE__ */ jsxs("div", {
1045
+ className: "flex flex-col gap-2 rounded-lg border border-border p-3",
1046
+ children: [
1047
+ /* @__PURE__ */ jsxs("div", {
1048
+ className: "flex items-center justify-between",
1049
+ children: [/* @__PURE__ */ jsxs("div", {
1050
+ className: "flex items-center gap-1.5",
1051
+ children: [
1052
+ /* @__PURE__ */ jsx("span", {
1053
+ className: "text-xs font-medium",
1054
+ children: variant.label
1055
+ }),
1056
+ variant.approvedAt && /* @__PURE__ */ jsx(Badge, {
1057
+ variant: "default",
1058
+ className: "text-[10px] h-4 px-1.5",
1059
+ children: "Approved"
1060
+ }),
1061
+ variant.rejectedAt && /* @__PURE__ */ jsx(Badge, {
1062
+ variant: "destructive",
1063
+ className: "text-[10px] h-4 px-1.5",
1064
+ children: "Rejected"
1065
+ })
1066
+ ]
1067
+ }), onPromote && !variant.approvedAt && /* @__PURE__ */ jsxs(Button, {
1068
+ size: "sm",
1069
+ variant: "outline",
1070
+ onClick: () => onPromote(variant.id),
1071
+ className: "h-6 gap-1 text-[11px] px-2",
1072
+ children: [/* @__PURE__ */ jsx(Trophy, { size: 11 }), "Pick this"]
1073
+ })]
1074
+ }),
1075
+ /* @__PURE__ */ jsx(AssetEditor, {
1076
+ spec: variant.spec,
1077
+ onSave: (spec) => onSave?.({
1078
+ ...variant,
1079
+ spec
1080
+ }),
1081
+ className: "min-h-[300px]"
1082
+ }),
1083
+ variant.editLog.length > 0 && /* @__PURE__ */ jsxs("div", {
1084
+ className: "text-[10px] text-muted-foreground space-y-0.5",
1085
+ children: [/* @__PURE__ */ jsx("span", {
1086
+ className: "font-medium",
1087
+ children: "Edit history"
1088
+ }), variant.editLog.slice(-3).map((ev, i) => /* @__PURE__ */ jsxs("div", { children: [ev.action, ev.editedFields?.length ? ` (${ev.editedFields.join(", ")})` : ""] }, i))]
1089
+ })
1090
+ ]
1091
+ }, variant.id))
1092
+ })]
1093
+ });
937
1094
  }
938
- export {
939
- ApprovalQueue,
940
- AssetCard,
941
- AssetEditor,
942
- CopyPreview,
943
- EmailPreview,
944
- ImagePreview,
945
- VariantCompare,
946
- VideoPreview
947
- };
1095
+ //#endregion
1096
+ export { ApprovalQueue, AssetCard, AssetEditor, CopyPreview, EmailPreview, ImagePreview, VariantCompare, VideoPreview };