@fayz-ai/plugin-marketing 0.13.17 → 0.13.19

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.js CHANGED
@@ -1,9 +1,9 @@
1
- import React9, { useState, useEffect, useMemo, useCallback } from 'react';
2
- import { connectionSettings, connectionStatus, settingsStatus, createConnectionStore, getActiveTenantId, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, countByTenant, useTranslation, useDataChanged, resolveDataProvider } from '@fayz-ai/core';
1
+ import React12, { useState, useEffect, useMemo, useCallback } from 'react';
2
+ import { connectionSettings, connectionStatus, settingsStatus, createConnectionStore, getActiveTenantId, registerTranslations, createSafeDataProvider, getSupabaseClientOptional, countByTenant, useTranslation, useDataChanged, getCurrentLocale, resolveDataProvider } from '@fayz-ai/core';
3
3
  import { createPluginContext, fayzConnectorOAuth, PluginSettingsPanel, useModuleNavigation, createViewRouter, ModuleActionBar, SettingsGroup, SelectRow, ToggleRow, askAI, PageActions, useLimitGuard, useOrganizationStore, CrudFormPage, RightRailPage, invalidateLimit, CrudListView } from '@fayz-ai/admin';
4
- import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, PageToolbar, DataTable, SubpageHeader, RowActions, PageHeaderActions, Button, Skeleton, ConfirmDialog, ContentSplit, ContentSplitTrigger, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, MarkdownEditor, ComingSoon, DashboardCanvas, RangeControl, cn, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, SegmentedControl, ModalFooter, SheetBody, Badge, SheetFooter, SelectSeparator, Checkbox, toast, KpiCard as KpiCard$1, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
4
+ import { defineKpiWidget, defineCustomWidget, defineTableWidget, ModulePage, PageToolbar, DataTable, SubpageHeader, RowActions, PageHeaderActions, Button, Skeleton, ConfirmDialog, ComingSoon, DashboardCanvas, RangeControl, cn, Modal, ModalContent, ModalHeader, ModalTitle, ModalBody, SegmentedControl, Input, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, ModalFooter, SheetBody, Textarea, Badge, SheetFooter, SelectSeparator, Checkbox, MarkdownEditor, toast, KpiCard as KpiCard$1, Card, CardHeader, CardTitle, CardContent, TableWidget } from '@fayz-ai/ui';
5
5
  import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
6
- import { History, CheckCircle2, AlertCircle, ChevronRight, Users, Target, Percent, DollarSign, Trash2, WandSparkles, SlidersHorizontal, Workflow, ExternalLink, Sparkles, Pencil, Plus, Layers, MousePointerClick, NotebookPen, ArrowUpRight, ArrowDownRight, Send, Megaphone, Settings2, Columns3, CalendarDays, List, ChevronLeft, Loader2, ImagePlus, X, Bike, Utensils, DoorOpen, MessageCircle, Instagram, Mail, Globe, Search, Link2, AtSign, Pin, Twitter, Linkedin, Facebook, Youtube, Music2, Repeat2, Heart, BarChart2, Share2, ThumbsUp, MoreHorizontal, Bookmark, GripVertical, Image, Trophy } from 'lucide-react';
6
+ import { History, CheckCircle2, AlertCircle, ChevronRight, Users, Target, Percent, DollarSign, Trash2, WandSparkles, SlidersHorizontal, Workflow, ExternalLink, Sparkles, Pencil, Plus, Layers, MousePointerClick, NotebookPen, ArrowUpRight, ArrowDownRight, Send, Megaphone, Settings2, Columns3, CalendarDays, List, ChevronLeft, X, Bike, Utensils, DoorOpen, MessageCircle, Instagram, Mail, Globe, Search, Link2, AtSign, Pin, Twitter, Linkedin, Facebook, Youtube, Music2, GripVertical, Loader2, ImagePlus, Repeat2, Heart, BarChart2, Share2, ThumbsUp, MoreHorizontal, Bookmark, Image, Trophy } from 'lucide-react';
7
7
  import { createStore } from 'zustand/vanilla';
8
8
 
9
9
  // src/index.ts
@@ -14,7 +14,7 @@ var useMarketingProvider = ctx.useProvider;
14
14
  var useMarketingStore = ctx.useStore;
15
15
  function useChannelLookup() {
16
16
  const { channels } = useMarketingConfig();
17
- return React9.useMemo(() => new Map(channels.map((c) => [c.id, c])), [channels]);
17
+ return React12.useMemo(() => new Map(channels.map((c) => [c.id, c])), [channels]);
18
18
  }
19
19
  function OverviewView() {
20
20
  const setRange = useMarketingStore((s) => s.setRange);
@@ -270,7 +270,7 @@ function ChannelsView({ onOpen }) {
270
270
  const { conversion, currency } = useMarketingConfig();
271
271
  const channels = useMarketingStore((s) => s.channels);
272
272
  const sorted = [...channels].sort((a, b) => b.conversions - a.conversions);
273
- const columns = React9.useMemo(() => [
273
+ const columns = React12.useMemo(() => [
274
274
  { accessorKey: "channelId", header: t("marketing.col.channel"), cell: ({ getValue }) => /* @__PURE__ */ jsx(ChannelCell, { channelId: getValue() }) },
275
275
  { accessorKey: "reach", header: t("marketing.col.reach"), cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatNumber(getValue()) }) },
276
276
  { accessorKey: "leads", header: t("marketing.col.leads"), cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatNumber(getValue()) }) },
@@ -307,13 +307,13 @@ function ChannelDetailView({ channelId, onBack }) {
307
307
  const channels = useMarketingStore((s) => s.channels);
308
308
  const lookup = useChannelLookup();
309
309
  const provider2 = useMarketingProvider();
310
- const [campaigns, setCampaigns] = React9.useState([]);
311
- React9.useEffect(() => {
310
+ const [campaigns, setCampaigns] = React12.useState([]);
311
+ React12.useEffect(() => {
312
312
  void provider2.listCampaigns({ range, channelId }).then(setCampaigns);
313
313
  }, [provider2, range, channelId]);
314
314
  const perf = channels.find((c) => c.channelId === channelId);
315
315
  const channel = lookup.get(channelId);
316
- const columns = React9.useMemo(() => [
316
+ const columns = React12.useMemo(() => [
317
317
  { accessorKey: "name", header: t("marketing.col.campaign"), cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: getValue() }) },
318
318
  { accessorKey: "conversions", header: conversion.labelPlural, cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatNumber(getValue()) }) },
319
319
  { id: "cvr", accessorFn: (c) => c.reach > 0 ? c.conversions / c.reach * 100 : 0, header: t("marketing.col.cvr"), cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatPercent(getValue()) }) },
@@ -352,17 +352,17 @@ function CampaignComposer({ open, onClose, initialSegmentId }) {
352
352
  const { channels } = useMarketingConfig();
353
353
  const provider2 = useMarketingProvider();
354
354
  const saveCampaign = useMarketingStore((s) => s.saveCampaign);
355
- const [kind, setKind] = React9.useState("ad_spend");
356
- const [name, setName] = React9.useState("");
357
- const [channelId, setChannelId] = React9.useState(channels[0]?.id ?? "");
358
- const [status, setStatus] = React9.useState("draft");
359
- const [spend, setSpend] = React9.useState("");
360
- const [purpose, setPurpose] = React9.useState("promotion");
361
- const [segmentId, setSegmentId] = React9.useState("");
362
- const [templateKey, setTemplateKey] = React9.useState("");
363
- const [segments, setSegments] = React9.useState([]);
364
- const [saving, setSaving] = React9.useState(false);
365
- React9.useEffect(() => {
355
+ const [kind, setKind] = React12.useState("ad_spend");
356
+ const [name, setName] = React12.useState("");
357
+ const [channelId, setChannelId] = React12.useState(channels[0]?.id ?? "");
358
+ const [status, setStatus] = React12.useState("draft");
359
+ const [spend, setSpend] = React12.useState("");
360
+ const [purpose, setPurpose] = React12.useState("promotion");
361
+ const [segmentId, setSegmentId] = React12.useState("");
362
+ const [templateKey, setTemplateKey] = React12.useState("");
363
+ const [segments, setSegments] = React12.useState([]);
364
+ const [saving, setSaving] = React12.useState(false);
365
+ React12.useEffect(() => {
366
366
  if (!open) return;
367
367
  setName("");
368
368
  setChannelId(channels[0]?.id ?? "");
@@ -480,8 +480,8 @@ function CampaignsView() {
480
480
  const { conversion, currency } = useMarketingConfig();
481
481
  const campaigns = useMarketingStore((s) => s.campaigns);
482
482
  const deleteCampaign = useMarketingStore((s) => s.deleteCampaign);
483
- const [composerOpen, setComposerOpen] = React9.useState(false);
484
- const columns = React9.useMemo(() => [
483
+ const [composerOpen, setComposerOpen] = React12.useState(false);
484
+ const columns = React12.useMemo(() => [
485
485
  { accessorKey: "name", header: t("marketing.col.campaign"), cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "font-medium text-foreground", children: getValue() }) },
486
486
  { accessorKey: "channelId", header: t("marketing.col.channel"), cell: ({ getValue }) => /* @__PURE__ */ jsx(ChannelCell, { channelId: getValue() }) },
487
487
  { accessorKey: "reach", header: t("marketing.col.reach"), cell: ({ getValue }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: formatNumber(getValue()) }) },
@@ -512,7 +512,7 @@ function CampaignsView() {
512
512
  )
513
513
  }
514
514
  ], [t, conversion.labelPlural, currency, deleteCampaign]);
515
- const createActions = React9.useMemo(() => [
515
+ const createActions = React12.useMemo(() => [
516
516
  {
517
517
  id: "with-ai",
518
518
  label: t("assistant.askAI"),
@@ -618,19 +618,19 @@ function LandingPageComposer({
618
618
  const sites = useMarketingStore((s) => s.sites);
619
619
  const leadTags = useMarketingStore((s) => s.leadTags);
620
620
  const saveLandingPage = useMarketingStore((s) => s.saveLandingPage);
621
- const [siteId, setSiteId] = React9.useState("");
622
- const [name, setName] = React9.useState("");
623
- const [route, setRoute] = React9.useState("");
624
- const [status, setStatus] = React9.useState("live");
625
- const [prompt, setPrompt] = React9.useState("");
626
- const [tags, setTags] = React9.useState([]);
627
- const [newTag, setNewTag] = React9.useState("");
628
- const [destination, setDestination] = React9.useState("lead");
629
- const [required, setRequired] = React9.useState(["name"]);
630
- const [minH, setMinH] = React9.useState("");
631
- const [maxD, setMaxD] = React9.useState("");
632
- const [saving, setSaving] = React9.useState(false);
633
- React9.useEffect(() => {
621
+ const [siteId, setSiteId] = React12.useState("");
622
+ const [name, setName] = React12.useState("");
623
+ const [route, setRoute] = React12.useState("");
624
+ const [status, setStatus] = React12.useState("live");
625
+ const [prompt, setPrompt] = React12.useState("");
626
+ const [tags, setTags] = React12.useState([]);
627
+ const [newTag, setNewTag] = React12.useState("");
628
+ const [destination, setDestination] = React12.useState("lead");
629
+ const [required, setRequired] = React12.useState(["name"]);
630
+ const [minH, setMinH] = React12.useState("");
631
+ const [maxD, setMaxD] = React12.useState("");
632
+ const [saving, setSaving] = React12.useState(false);
633
+ React12.useEffect(() => {
634
634
  if (!open) return;
635
635
  setSiteId(page?.siteId ?? sites[0]?.id ?? "");
636
636
  setName(page?.name ?? "");
@@ -646,7 +646,7 @@ function LandingPageComposer({
646
646
  }, [open, page, sites]);
647
647
  const site = sites.find((s) => s.id === siteId);
648
648
  const previewPath = route.trim() ? route.trim().startsWith("/") ? route.trim() : `/${route.trim()}` : "";
649
- const options = React9.useMemo(() => {
649
+ const options = React12.useMemo(() => {
650
650
  const byslug = new Map(leadTags.map((tag) => [tag.slug, tag.name]));
651
651
  for (const slug of tags) if (!byslug.has(slug)) byslug.set(slug, slug);
652
652
  return [...byslug].map(([slug, label]) => ({ slug, label }));
@@ -731,8 +731,10 @@ function LandingPageComposer({
731
731
  /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between gap-2", children: [
732
732
  /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-foreground", children: t("marketing.landing.prompt") }),
733
733
  site?.editorUrl ? /* @__PURE__ */ jsxs(
734
- "button",
734
+ Button,
735
735
  {
736
+ variant: "bare",
737
+ size: "bare",
736
738
  type: "button",
737
739
  onClick: () => window.open(site.editorUrl, "_blank", "noopener,noreferrer"),
738
740
  className: "inline-flex items-center gap-1 text-xs font-medium text-primary hover:underline",
@@ -745,13 +747,13 @@ function LandingPageComposer({
745
747
  ) : null
746
748
  ] }),
747
749
  /* @__PURE__ */ jsx(
748
- "textarea",
750
+ Textarea,
749
751
  {
750
752
  value: prompt,
751
753
  onChange: (e) => setPrompt(e.target.value),
752
754
  rows: 3,
753
755
  placeholder: t("marketing.landing.promptPlaceholder"),
754
- className: "w-full rounded-md border border-border bg-background px-3 py-2 text-sm text-foreground outline-none focus:ring-2 focus:ring-ring"
756
+ className: "rounded-md border-border bg-background shadow-none focus-visible:ring-offset-0"
755
757
  }
756
758
  )
757
759
  ] }) : null,
@@ -771,8 +773,8 @@ function LandingPageComposer({
771
773
  opt.slug
772
774
  );
773
775
  }),
774
- /* @__PURE__ */ jsx(
775
- "input",
776
+ /* @__PURE__ */ jsx("div", { className: "min-w-[8rem] flex-1", children: /* @__PURE__ */ jsx(
777
+ Input,
776
778
  {
777
779
  value: newTag,
778
780
  onChange: (e) => setNewTag(e.target.value),
@@ -784,9 +786,9 @@ function LandingPageComposer({
784
786
  },
785
787
  onBlur: addTag,
786
788
  placeholder: t("marketing.landing.leadTagPlaceholder"),
787
- className: "min-w-[8rem] flex-1 border-0 bg-transparent px-1 text-xs text-foreground outline-none placeholder:text-muted-foreground"
789
+ className: "h-auto rounded-none border-0 bg-transparent px-1 py-0 text-xs shadow-none focus-visible:ring-0 focus-visible:ring-offset-0"
788
790
  }
789
- )
791
+ ) })
790
792
  ] }),
791
793
  /* @__PURE__ */ jsx("span", { className: "block text-xs text-muted-foreground", children: t("marketing.landing.leadTagHint") })
792
794
  ] }),
@@ -812,10 +814,12 @@ function LandingPageComposer({
812
814
  /* @__PURE__ */ jsx("div", { className: "mt-2 flex flex-wrap gap-1.5", children: CAPTURE_FIELDS.map((f) => {
813
815
  const on = required.includes(f);
814
816
  return /* @__PURE__ */ jsx(
815
- "button",
817
+ Button,
816
818
  {
819
+ variant: "bare",
820
+ size: "bare",
817
821
  type: "button",
818
- className: `rounded-full border px-2.5 py-1 text-xs ${on ? "border-primary bg-primary/10 text-primary" : "text-muted-foreground"}`,
822
+ className: `font-normal rounded-full border px-2.5 py-1 text-xs ${on ? "border-primary bg-primary/10 text-primary" : "text-muted-foreground"}`,
819
823
  onClick: () => setRequired((prev) => on ? prev.filter((x) => x !== f) : [...prev, f]),
820
824
  children: t(`marketing.landing.field.${f}`)
821
825
  },
@@ -856,8 +860,8 @@ function LandingPagesView() {
856
860
  const t = useTranslation();
857
861
  const pages = useMarketingStore((s) => s.landingPages);
858
862
  const deleteLandingPage = useMarketingStore((s) => s.deleteLandingPage);
859
- const [editing, setEditing] = React9.useState(null);
860
- const [composerOpen, setComposerOpen] = React9.useState(false);
863
+ const [editing, setEditing] = React12.useState(null);
864
+ const [composerOpen, setComposerOpen] = React12.useState(false);
861
865
  const measured = pages.filter((p) => p.visits !== null);
862
866
  const totalVisits = measured.reduce((s, p) => s + (p.visits ?? 0), 0);
863
867
  const totalLeads = pages.reduce((s, p) => s + p.conversions, 0);
@@ -869,7 +873,7 @@ function LandingPagesView() {
869
873
  setEditing(page);
870
874
  setComposerOpen(true);
871
875
  }
872
- const columns = React9.useMemo(() => [
876
+ const columns = React12.useMemo(() => [
873
877
  {
874
878
  accessorKey: "name",
875
879
  header: t("marketing.col.page"),
@@ -958,7 +962,7 @@ function LandingPagesView() {
958
962
  )
959
963
  }
960
964
  ], [t, deleteLandingPage]);
961
- const createActions = React9.useMemo(() => [{
965
+ const createActions = React12.useMemo(() => [{
962
966
  id: "new-page",
963
967
  label: t("marketing.landing.new"),
964
968
  icon: Plus,
@@ -1059,8 +1063,10 @@ function PlatformPicker({ value, onChange }) {
1059
1063
  return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap items-center gap-1.5", children: PLATFORMS.map((platform) => {
1060
1064
  const selected = value.includes(platform);
1061
1065
  return /* @__PURE__ */ jsxs(
1062
- "button",
1066
+ Button,
1063
1067
  {
1068
+ variant: "bare",
1069
+ size: "bare",
1064
1070
  type: "button",
1065
1071
  "aria-pressed": selected,
1066
1072
  onClick: () => onChange(selected ? value.filter((p) => p !== platform) : [...value, platform]),
@@ -1098,9 +1104,9 @@ function Field({ label, children }) {
1098
1104
  function useBriefDraft() {
1099
1105
  const plan = useContentPlannerStore((s) => s.activePlan);
1100
1106
  const savePlan = useContentPlannerStore((s) => s.savePlan);
1101
- const [draft, setDraft] = React9.useState(plan ? toDraft(plan) : null);
1102
- const [saving, setSaving] = React9.useState(false);
1103
- React9.useEffect(() => {
1107
+ const [draft, setDraft] = React12.useState(plan ? toDraft(plan) : null);
1108
+ const [saving, setSaving] = React12.useState(false);
1109
+ React12.useEffect(() => {
1104
1110
  if (plan) setDraft(toDraft(plan));
1105
1111
  }, [plan?.id]);
1106
1112
  const patch = (partial) => setDraft((d) => d ? { ...d, ...partial } : d);
@@ -1128,7 +1134,7 @@ function useBriefDraft() {
1128
1134
  }
1129
1135
  function BriefFields({ draft, patch }) {
1130
1136
  const t = useTranslation();
1131
- const [pillarInput, setPillarInput] = React9.useState("");
1137
+ const [pillarInput, setPillarInput] = React12.useState("");
1132
1138
  const addPillar = () => {
1133
1139
  const value = pillarInput.trim();
1134
1140
  if (!value || draft.pillars.includes(value)) return;
@@ -1190,8 +1196,10 @@ function BriefFields({ draft, patch }) {
1190
1196
  children: [
1191
1197
  pillar,
1192
1198
  /* @__PURE__ */ jsx(
1193
- "button",
1199
+ Button,
1194
1200
  {
1201
+ variant: "bare",
1202
+ size: "bare",
1195
1203
  type: "button",
1196
1204
  onClick: () => patch({ pillars: draft.pillars.filter((p) => p !== pillar) }),
1197
1205
  className: "text-muted-foreground hover:text-foreground",
@@ -1265,1458 +1273,1451 @@ function PlanBriefView({ onBack }) {
1265
1273
  function PlanBriefSheet({ open, onClose }) {
1266
1274
  const t = useTranslation();
1267
1275
  const { plan, draft, patch, dirty, saving, save } = useBriefDraft();
1268
- return /* @__PURE__ */ jsxs(
1269
- RightRailPage,
1270
- {
1271
- title: plan?.name ?? t("marketing.content.brief"),
1272
- open,
1273
- onClose,
1274
- width: "max-w-2xl",
1275
- children: [
1276
- /* @__PURE__ */ jsx(SheetBody, { children: draft ? /* @__PURE__ */ jsx(BriefFields, { draft, patch }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" }) }),
1277
- /* @__PURE__ */ jsx(SheetFooter, { children: /* @__PURE__ */ jsx(Button, { className: "flex-1", onClick: () => void save(), disabled: !dirty || saving, children: t("marketing.content.save") }) })
1278
- ]
1279
- }
1280
- );
1276
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
1277
+ if (!o) onClose();
1278
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "4xl", children: [
1279
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: plan?.name ?? t("marketing.content.brief") }) }),
1280
+ /* @__PURE__ */ jsx(ModalBody, { children: draft ? /* @__PURE__ */ jsx(BriefFields, { draft, patch }) : /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: "\u2026" }) }),
1281
+ /* @__PURE__ */ jsxs(ModalFooter, { children: [
1282
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: t("common.cancel") }),
1283
+ /* @__PURE__ */ jsx(Button, { onClick: () => void save().then(onClose), disabled: !dirty || saving, children: t("marketing.content.save") })
1284
+ ] })
1285
+ ] }) });
1281
1286
  }
1282
- var DEFAULT_WEEKS = 8;
1283
- var ADD_ACCOUNT_VALUE = "__add-account__";
1284
- var POST_DRAG_MIME = "application/x-mkt-post";
1285
- function PostCard({ post, onOpen, onDelete }) {
1286
- const t = useTranslation();
1287
- const [dragging, setDragging] = React9.useState(false);
1288
- return /* @__PURE__ */ jsxs(
1289
- "div",
1290
- {
1291
- role: "button",
1292
- tabIndex: 0,
1293
- onClick: onOpen,
1294
- onKeyDown: (e) => {
1295
- if (e.key === "Enter") onOpen();
1296
- },
1297
- draggable: true,
1298
- onDragStart: (e) => {
1299
- e.dataTransfer.setData(POST_DRAG_MIME, post.id);
1300
- e.dataTransfer.effectAllowed = "move";
1301
- setDragging(true);
1302
- },
1303
- onDragEnd: () => setDragging(false),
1304
- className: cn(
1305
- "group/post relative w-full cursor-pointer rounded-card border border-border bg-card p-3 pl-7 text-left transition-colors hover:bg-muted/40",
1306
- dragging && "opacity-50"
1307
- ),
1308
- children: [
1309
- /* @__PURE__ */ jsx(
1310
- GripVertical,
1311
- {
1312
- className: "absolute left-1.5 top-3 h-4 w-4 cursor-grab text-muted-foreground/50 opacity-0 transition-opacity group-hover/post:opacity-100",
1313
- "aria-hidden": true
1314
- }
1315
- ),
1316
- onDelete && /* @__PURE__ */ jsx(
1317
- "button",
1318
- {
1319
- type: "button",
1320
- "aria-label": t("marketing.content.deletePost"),
1321
- onClick: (e) => {
1322
- e.stopPropagation();
1323
- onDelete();
1324
- },
1325
- className: "absolute right-1.5 top-2 rounded p-1 text-muted-foreground/60 opacity-0 transition-opacity hover:bg-destructive/10 hover:text-destructive group-hover/post:opacity-100",
1326
- children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
1327
- }
1328
- ),
1329
- /* @__PURE__ */ jsx("p", { className: "pr-5 text-sm font-medium text-foreground", children: post.title || t("marketing.content.untitled") }),
1330
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
1331
- /* @__PURE__ */ jsx(FormatBadge, { format: post.format }),
1332
- /* @__PURE__ */ jsx(PostStatusBadge, { status: post.status }),
1333
- post.scheduledDate && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: post.scheduledDate }),
1334
- /* @__PURE__ */ jsx(PlatformBadges, { platforms: post.platforms })
1335
- ] })
1336
- ]
1337
- }
1338
- );
1287
+ function canUploadMedia() {
1288
+ return !!getSupabaseClientOptional();
1339
1289
  }
1340
- function WeekColumn({ week, rangeLabel, posts, onOpenPost, onNewPost, onDeletePost, onDropPost, disabled }) {
1290
+ async function uploadPostMedia(postId, file) {
1291
+ const supabase = getSupabaseClientOptional();
1292
+ if (!supabase) throw new Error("Supabase not initialized");
1293
+ const tenantId = getActiveTenantId();
1294
+ if (!tenantId) throw new Error("No active tenant");
1295
+ const ext = file.name.split(".").pop()?.toLowerCase() ?? "bin";
1296
+ const path = `content/${tenantId}/${postId}-${Date.now()}.${ext}`;
1297
+ const { error } = await supabase.storage.from("mkt-media").upload(path, file, { upsert: true });
1298
+ if (error) throw error;
1299
+ const { data } = supabase.storage.from("mkt-media").getPublicUrl(path);
1300
+ return data.publicUrl;
1301
+ }
1302
+ function newId() {
1303
+ return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ck-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
1304
+ }
1305
+ function RecordingChecklist({ post }) {
1341
1306
  const t = useTranslation();
1342
- const [isDropTarget, setIsDropTarget] = React9.useState(false);
1343
- return /* @__PURE__ */ jsxs(
1344
- "div",
1345
- {
1346
- className: cn(
1347
- "flex flex-col gap-2 rounded-card border border-border bg-muted/20 p-2.5 transition-colors",
1348
- isDropTarget && "border-primary bg-primary/5 ring-1 ring-primary/40"
1349
- ),
1350
- onDragOver: (e) => {
1351
- if (!onDropPost || !e.dataTransfer.types.includes(POST_DRAG_MIME)) return;
1352
- e.preventDefault();
1353
- e.dataTransfer.dropEffect = "move";
1354
- setIsDropTarget(true);
1355
- },
1356
- onDragLeave: () => setIsDropTarget(false),
1357
- onDrop: (e) => {
1358
- if (!onDropPost) return;
1359
- e.preventDefault();
1360
- setIsDropTarget(false);
1361
- const postId = e.dataTransfer.getData(POST_DRAG_MIME);
1362
- if (postId) onDropPost(postId, week);
1363
- },
1364
- children: [
1365
- /* @__PURE__ */ jsxs("div", { className: "flex items-baseline justify-between px-1", children: [
1366
- /* @__PURE__ */ jsxs("p", { className: "text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
1367
- t("marketing.content.week"),
1368
- " ",
1369
- week
1370
- ] }),
1371
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground/80", children: rangeLabel })
1307
+ const savePost = useContentPlannerStore((s) => s.savePost);
1308
+ const [items, setItems] = React12.useState(post.checklist);
1309
+ const [text, setText] = React12.useState("");
1310
+ React12.useEffect(() => {
1311
+ setItems(post.checklist);
1312
+ }, [post.id]);
1313
+ const persist = (next) => {
1314
+ setItems(next);
1315
+ void savePost({ id: post.id, checklist: next });
1316
+ };
1317
+ const toggle = (id) => persist(items.map((i) => i.id === id ? { ...i, done: !i.done } : i));
1318
+ const remove = (id) => persist(items.filter((i) => i.id !== id));
1319
+ const add = () => {
1320
+ const value = text.trim();
1321
+ if (!value) return;
1322
+ persist([...items, { id: newId(), text: value, done: false }]);
1323
+ setText("");
1324
+ };
1325
+ const generateFromScript = () => {
1326
+ const takes = [...post.contentMd.matchAll(/^##\s+(TAKE\s+.+)$/gim)].map(
1327
+ (m) => m[1].trim()
1328
+ );
1329
+ const existing = new Set(items.map((i) => i.text));
1330
+ const fresh = takes.filter((take) => !existing.has(take)).map((take) => ({ id: newId(), text: take, done: false }));
1331
+ if (fresh.length > 0) persist([...items, ...fresh]);
1332
+ };
1333
+ const hasTakes = /^##\s+TAKE\s+/im.test(post.contentMd);
1334
+ const done = items.filter((i) => i.done).length;
1335
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
1336
+ items.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
1337
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
1338
+ /* @__PURE__ */ jsxs("span", { children: [
1339
+ done,
1340
+ "/",
1341
+ items.length
1372
1342
  ] }),
1373
- posts.map((post) => /* @__PURE__ */ jsx(
1374
- PostCard,
1375
- {
1376
- post,
1377
- onOpen: () => onOpenPost(post.id),
1378
- onDelete: onDeletePost ? () => onDeletePost(post.id) : void 0
1379
- },
1380
- post.id
1381
- )),
1382
- /* @__PURE__ */ jsxs(
1383
- "button",
1343
+ hasTakes && /* @__PURE__ */ jsxs(
1344
+ Button,
1384
1345
  {
1346
+ variant: "bare",
1347
+ size: "bare",
1385
1348
  type: "button",
1386
- onClick: () => onNewPost(week),
1387
- disabled,
1388
- className: "flex items-center justify-center gap-1 rounded-card border border-dashed border-border p-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
1349
+ onClick: generateFromScript,
1350
+ className: "flex items-center gap-1 font-medium text-muted-foreground transition-colors hover:text-foreground",
1389
1351
  children: [
1390
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1391
- t("marketing.content.newPost")
1352
+ /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
1353
+ t("marketing.content.checklistGenerate")
1392
1354
  ]
1393
1355
  }
1394
1356
  )
1395
- ]
1396
- }
1397
- );
1398
- }
1399
- function AccountModal({ open, account, onClose }) {
1400
- const t = useTranslation();
1401
- const saveAccount = useContentPlannerStore((s) => s.saveAccount);
1402
- const deleteAccount = useContentPlannerStore((s) => s.deleteAccount);
1403
- const isEdit = !!account;
1404
- const [name, setName] = React9.useState("");
1405
- const [handle, setHandle] = React9.useState("");
1406
- const [platforms, setPlatforms] = React9.useState(["instagram"]);
1407
- const [saving, setSaving] = React9.useState(false);
1408
- const [confirmDelete, setConfirmDelete] = React9.useState(false);
1409
- React9.useEffect(() => {
1410
- if (open) {
1411
- setName(account?.name ?? "");
1412
- setHandle(account?.handle ?? "");
1413
- setPlatforms(account?.platforms?.length ? account.platforms : ["instagram"]);
1414
- setSaving(false);
1415
- setConfirmDelete(false);
1416
- }
1417
- }, [open, account]);
1418
- const create = async () => {
1419
- if (!name.trim() || platforms.length === 0 || saving) return;
1420
- setSaving(true);
1421
- try {
1422
- await saveAccount({
1423
- ...isEdit ? { id: account.id } : {},
1424
- name: name.trim(),
1425
- handle: handle.trim() || void 0,
1426
- platforms
1427
- });
1428
- onClose();
1429
- } finally {
1430
- setSaving(false);
1431
- }
1432
- };
1433
- const remove = async () => {
1434
- if (!account || saving) return;
1435
- setSaving(true);
1436
- try {
1437
- await deleteAccount(account.id);
1438
- onClose();
1439
- } finally {
1440
- setSaving(false);
1441
- }
1442
- };
1443
- return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
1444
- if (!o) onClose();
1445
- }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
1446
- /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: isEdit ? t("marketing.content.editAccountTitle") : t("marketing.content.addAccountTitle") }) }),
1447
- /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-4", children: [
1448
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1449
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.nameLabel") }),
1450
- /* @__PURE__ */ jsx(
1451
- Input,
1452
- {
1453
- autoFocus: true,
1454
- value: name,
1455
- onChange: (e) => setName(e.target.value),
1456
- placeholder: t("marketing.content.accountNamePlaceholder"),
1457
- onKeyDown: (e) => {
1458
- if (e.key === "Enter") void create();
1459
- }
1460
- }
1461
- )
1462
1357
  ] }),
1463
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1464
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.handleLabel") }),
1358
+ /* @__PURE__ */ jsx("div", { className: "h-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
1359
+ "div",
1360
+ {
1361
+ className: "h-full rounded-full bg-primary transition-[width]",
1362
+ style: { width: items.length ? `${done / items.length * 100}%` : 0 }
1363
+ }
1364
+ ) })
1365
+ ] }),
1366
+ items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-card border border-dashed border-border p-3 text-center", children: [
1367
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.checklistEmpty") }),
1368
+ hasTakes && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: generateFromScript, children: [
1369
+ /* @__PURE__ */ jsx(Sparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
1370
+ t("marketing.content.checklistGenerate")
1371
+ ] })
1372
+ ] }),
1373
+ /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: items.map((item) => /* @__PURE__ */ jsxs("li", { className: "group/item flex items-start gap-2.5 rounded-button px-1 py-2 transition-colors hover:bg-muted/40", children: [
1374
+ /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-1 cursor-pointer items-start gap-2.5", children: [
1375
+ /* @__PURE__ */ jsx(Checkbox, { checked: item.done, onChange: () => toggle(item.id), className: "mt-0.5" }),
1465
1376
  /* @__PURE__ */ jsx(
1466
- Input,
1377
+ "span",
1467
1378
  {
1468
- value: handle,
1469
- onChange: (e) => setHandle(e.target.value),
1470
- placeholder: t("marketing.content.handlePlaceholder")
1379
+ className: cn(
1380
+ "min-w-0 flex-1 text-sm leading-snug",
1381
+ item.done ? "text-muted-foreground line-through" : "text-foreground"
1382
+ ),
1383
+ children: item.text
1471
1384
  }
1472
1385
  )
1473
1386
  ] }),
1474
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1475
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.platformsLabel") }),
1476
- /* @__PURE__ */ jsx(PlatformPicker, { value: platforms, onChange: setPlatforms })
1477
- ] })
1478
- ] }),
1479
- /* @__PURE__ */ jsx(ModalFooter, { children: confirmDelete && isEdit ? /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-3 rounded-card border border-destructive/30 bg-destructive/5 p-3", children: [
1480
- /* @__PURE__ */ jsx("p", { className: "flex-1 text-xs text-destructive", children: t("marketing.content.deleteAccountConfirm") }),
1481
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmDelete(false), disabled: saving, children: t("marketing.content.cancel") }),
1482
- /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: () => void remove(), disabled: saving, children: t("marketing.content.deleteAccount") })
1483
- ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1484
- isEdit && /* @__PURE__ */ jsxs(
1387
+ /* @__PURE__ */ jsx(
1485
1388
  Button,
1486
1389
  {
1487
- variant: "outline",
1488
- onClick: () => setConfirmDelete(true),
1489
- disabled: saving,
1490
- className: "mr-auto text-destructive hover:text-destructive",
1491
- children: [
1492
- /* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 h-4 w-4" }),
1493
- t("marketing.content.deleteAccount")
1494
- ]
1390
+ variant: "bare",
1391
+ size: "bare",
1392
+ type: "button",
1393
+ "aria-label": t("marketing.content.cancel"),
1394
+ onClick: () => remove(item.id),
1395
+ className: "rounded p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-destructive group-hover/item:opacity-100 focus:opacity-100",
1396
+ children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
1495
1397
  }
1496
- ),
1497
- /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: t("marketing.content.cancel") }),
1498
- /* @__PURE__ */ jsx(Button, { onClick: () => void create(), disabled: saving || !name.trim() || platforms.length === 0, children: isEdit ? t("marketing.content.save") : t("marketing.content.createAccount") })
1499
- ] }) })
1500
- ] }) });
1501
- }
1502
- function AccountSelector() {
1503
- const t = useTranslation();
1504
- const loaded = useContentPlannerStore((s) => s.loaded);
1505
- const accounts = useContentPlannerStore((s) => s.accounts);
1506
- const activeAccountId = useContentPlannerStore((s) => s.activeAccountId);
1507
- const setAccount = useContentPlannerStore((s) => s.setAccount);
1508
- const [open, setOpen] = React9.useState(false);
1509
- const [modal, setModal] = React9.useState(null);
1510
- const activeAccount = accounts.find((a) => a.id === activeAccountId) ?? null;
1511
- if (!loaded) {
1512
- return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1513
- /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-64" }),
1514
- /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9" })
1515
- ] });
1516
- }
1517
- const openSettings = (account) => {
1518
- setOpen(false);
1519
- setModal({ mode: "edit", account });
1520
- };
1521
- return /* @__PURE__ */ jsxs(Fragment, { children: [
1522
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1523
- /* @__PURE__ */ jsxs(
1524
- Select,
1398
+ )
1399
+ ] }, item.id)) }),
1400
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1401
+ /* @__PURE__ */ jsx(
1402
+ Input,
1525
1403
  {
1526
- open,
1527
- onOpenChange: setOpen,
1528
- value: activeAccountId ?? void 0,
1529
- onValueChange: (id) => {
1530
- if (id === ADD_ACCOUNT_VALUE) {
1531
- setModal({ mode: "create" });
1532
- return;
1533
- }
1534
- void setAccount(id);
1404
+ value: text,
1405
+ onChange: (e) => setText(e.target.value),
1406
+ placeholder: t("marketing.content.checklistAddPlaceholder"),
1407
+ onKeyDown: (e) => {
1408
+ if (e.key === "Enter") add();
1535
1409
  },
1536
- children: [
1537
- /* @__PURE__ */ jsx(SelectTrigger, { className: "w-64", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("marketing.content.account") }) }),
1538
- /* @__PURE__ */ jsxs(SelectContent, { children: [
1539
- accounts.length === 0 ? /* @__PURE__ */ jsx(SelectItem, { value: "__none__", disabled: true, children: t("marketing.content.noAccountsAvailable") }) : accounts.map((acc) => /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1540
- /* @__PURE__ */ jsx(SelectItem, { value: acc.id, className: "pr-9", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1541
- acc.name,
1542
- /* @__PURE__ */ jsx(PlatformBadges, { platforms: acc.platforms })
1543
- ] }) }),
1544
- /* @__PURE__ */ jsx(
1545
- "button",
1546
- {
1547
- type: "button",
1548
- "aria-label": t("marketing.content.accountSettings"),
1549
- onPointerDown: (e) => {
1550
- e.stopPropagation();
1551
- e.preventDefault();
1552
- },
1553
- onPointerUp: (e) => {
1554
- e.stopPropagation();
1555
- e.preventDefault();
1556
- },
1557
- onClick: (e) => {
1558
- e.stopPropagation();
1559
- openSettings(acc);
1560
- },
1561
- className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground/70 transition-colors hover:bg-muted/60 hover:text-foreground",
1562
- children: /* @__PURE__ */ jsx(Settings2, { className: "h-3.5 w-3.5" })
1563
- }
1564
- )
1565
- ] }, acc.id)),
1566
- /* @__PURE__ */ jsx(SelectSeparator, {}),
1567
- /* @__PURE__ */ jsx(SelectItem, { value: ADD_ACCOUNT_VALUE, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
1568
- /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
1569
- t("marketing.content.addAccount")
1570
- ] }) })
1571
- ] })
1572
- ]
1410
+ className: "h-8 text-sm"
1573
1411
  }
1574
1412
  ),
1575
- activeAccount && /* @__PURE__ */ jsx(
1576
- Button,
1577
- {
1578
- variant: "ghost",
1579
- size: "sm",
1580
- className: "h-9 w-9 p-0 text-muted-foreground",
1581
- "aria-label": t("marketing.content.accountSettings"),
1582
- onClick: () => setModal({ mode: "edit", account: activeAccount }),
1583
- children: /* @__PURE__ */ jsx(Settings2, { className: "h-4 w-4" })
1584
- }
1585
- )
1586
- ] }),
1587
- /* @__PURE__ */ jsx(
1588
- AccountModal,
1589
- {
1590
- open: !!modal,
1591
- account: modal?.mode === "edit" ? modal.account : null,
1592
- onClose: () => setModal(null)
1593
- }
1594
- )
1413
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: add, disabled: !text.trim(), "aria-label": t("marketing.content.checklistAdd"), children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }) })
1414
+ ] })
1595
1415
  ] });
1596
1416
  }
1597
- function ViewSwitcher({ mode, onChange }) {
1598
- const t = useTranslation();
1599
- return /* @__PURE__ */ jsx(
1600
- SegmentedControl,
1601
- {
1602
- size: "md",
1603
- "aria-label": t("marketing.content.viewBoard"),
1604
- value: mode,
1605
- onChange,
1606
- options: [
1607
- { value: "board", label: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
1608
- /* @__PURE__ */ jsx(Columns3, { className: "h-3.5 w-3.5" }),
1609
- t("marketing.content.viewBoard")
1610
- ] }) },
1611
- { value: "calendar", label: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
1612
- /* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5" }),
1613
- t("marketing.content.viewCalendar")
1614
- ] }) },
1615
- { value: "list", label: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
1616
- /* @__PURE__ */ jsx(List, { className: "h-3.5 w-3.5" }),
1617
- t("marketing.content.viewList")
1618
- ] }) }
1619
- ]
1620
- }
1621
- );
1622
- }
1623
- function shortDay(d) {
1624
- return d.toLocaleDateString(void 0, { day: "numeric", month: "short" });
1417
+ function toCaption(md) {
1418
+ return md.replace(/^#+\s*/gm, "").replace(/\*\*/g, "").replace(/^[-*]\s+/gm, "").trim();
1625
1419
  }
1626
- function buildBoardWeeks(startDate, weeksCount) {
1627
- return Array.from({ length: weeksCount }, (_, i) => {
1628
- const start = new Date(startDate);
1629
- start.setDate(start.getDate() + i * 7);
1630
- const end = new Date(start);
1631
- end.setDate(start.getDate() + 6);
1632
- return {
1633
- week: i + 1,
1634
- rangeLabel: `${shortDay(start)} \u2013 ${shortDay(end)}`,
1635
- monthLabel: start.toLocaleDateString(void 0, { month: "long", year: "numeric" })
1636
- };
1637
- });
1420
+ function isVideo(url) {
1421
+ return !!url && /\.(mp4|webm|mov)($|\?)/i.test(url);
1638
1422
  }
1639
- function BoardView({ startDate, weeksCount, postsByWeek, onOpenPost, onNewPost, onDeletePost, onDropPost, disabled }) {
1640
- const months = [];
1641
- for (const bw of buildBoardWeeks(startDate, weeksCount)) {
1642
- const current = months[months.length - 1];
1643
- if (current && current.label === bw.monthLabel) current.weeks.push(bw);
1644
- else months.push({ label: bw.monthLabel, weeks: [bw] });
1423
+ function Media({ mediaUrl, className }) {
1424
+ if (!mediaUrl) {
1425
+ return /* @__PURE__ */ jsx("div", { className: cn("flex aspect-square w-full items-center justify-center bg-muted/40", className), children: /* @__PURE__ */ jsx(Image, { className: "h-8 w-8 text-muted-foreground/40" }) });
1645
1426
  }
1646
- return /* @__PURE__ */ jsx("div", { className: "space-y-8", children: months.map(({ label, weeks }) => /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
1647
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
1648
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: weeks.map(({ week, rangeLabel }) => /* @__PURE__ */ jsx(
1649
- WeekColumn,
1650
- {
1651
- week,
1652
- rangeLabel,
1653
- posts: postsByWeek.get(week) ?? [],
1654
- onOpenPost,
1655
- onNewPost,
1656
- onDeletePost,
1657
- onDropPost,
1658
- disabled
1659
- },
1660
- week
1661
- )) })
1662
- ] }, label)) });
1427
+ return isVideo(mediaUrl) ? /* @__PURE__ */ jsx("video", { src: mediaUrl, controls: true, className: cn("w-full bg-black object-contain", className) }) : /* @__PURE__ */ jsx("img", { src: mediaUrl, alt: "", className: cn("w-full object-cover", className) });
1663
1428
  }
1664
- function toISODate(d) {
1665
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
1429
+ function Avatar({ name, ring }) {
1430
+ return /* @__PURE__ */ jsx(
1431
+ "span",
1432
+ {
1433
+ className: cn(
1434
+ "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold text-foreground",
1435
+ ring && "ring-2 ring-offset-2 ring-pink-500/60"
1436
+ ),
1437
+ children: (name.replace(/^@/, "")[0] ?? "?").toUpperCase()
1438
+ }
1439
+ );
1666
1440
  }
1667
- function mondayOfCurrentWeek() {
1668
- const now = /* @__PURE__ */ new Date();
1669
- const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
1670
- monday.setDate(monday.getDate() - (monday.getDay() + 6) % 7);
1671
- return monday;
1441
+ function InstagramFrame({ caption, mediaUrl, accountName, accountHandle }) {
1442
+ const t = useTranslation();
1443
+ const handle = (accountHandle || accountName).replace(/^@/, "");
1444
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-card border border-border bg-card", children: [
1445
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
1446
+ /* @__PURE__ */ jsx(Avatar, { name: handle, ring: true }),
1447
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-sm font-semibold text-foreground", children: handle }),
1448
+ /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4 text-muted-foreground" })
1449
+ ] }),
1450
+ /* @__PURE__ */ jsx(Media, { mediaUrl }),
1451
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 px-3 py-2.5", children: [
1452
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-foreground", children: [
1453
+ /* @__PURE__ */ jsx(Heart, { className: "h-6 w-6" }),
1454
+ /* @__PURE__ */ jsx(MessageCircle, { className: "h-6 w-6" }),
1455
+ /* @__PURE__ */ jsx(Send, { className: "h-6 w-6" }),
1456
+ /* @__PURE__ */ jsx(Bookmark, { className: "ml-auto h-6 w-6" })
1457
+ ] }),
1458
+ /* @__PURE__ */ jsxs("p", { className: "whitespace-pre-wrap text-sm leading-snug text-foreground", children: [
1459
+ /* @__PURE__ */ jsx("span", { className: "mr-1.5 font-semibold", children: handle }),
1460
+ toCaption(caption)
1461
+ ] }),
1462
+ /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: t("marketing.content.previewNow") })
1463
+ ] })
1464
+ ] });
1672
1465
  }
1673
- function CalendarView({ posts, onOpenPost, onNewPostAt, disabled }) {
1466
+ function FacebookFrame({ caption, mediaUrl, accountName }) {
1674
1467
  const t = useTranslation();
1675
- const [cursor, setCursor] = React9.useState(() => {
1676
- const first = posts.find((p) => p.scheduledDate)?.scheduledDate;
1677
- const base = first ? /* @__PURE__ */ new Date(`${first}T00:00:00`) : /* @__PURE__ */ new Date();
1678
- return new Date(base.getFullYear(), base.getMonth(), 1);
1679
- });
1680
- const [expandedDay, setExpandedDay] = React9.useState(null);
1681
- const MAX_VISIBLE = 3;
1682
- const postsByDate = React9.useMemo(() => {
1683
- const map = /* @__PURE__ */ new Map();
1684
- for (const post of posts) {
1685
- if (!post.scheduledDate) continue;
1686
- const list = map.get(post.scheduledDate) ?? [];
1687
- list.push(post);
1688
- map.set(post.scheduledDate, list);
1689
- }
1690
- return map;
1691
- }, [posts]);
1692
- const unscheduled = posts.filter((p) => !p.scheduledDate);
1693
- const days = React9.useMemo(() => {
1694
- const firstOfMonth = new Date(cursor.getFullYear(), cursor.getMonth(), 1);
1695
- const start = new Date(firstOfMonth);
1696
- start.setDate(start.getDate() - (firstOfMonth.getDay() + 6) % 7);
1697
- const cells = [];
1698
- for (let i = 0; i < 42; i++) {
1699
- const d = new Date(start);
1700
- d.setDate(start.getDate() + i);
1701
- cells.push(d);
1702
- }
1703
- while (cells.length > 7 && cells[cells.length - 7].getMonth() !== cursor.getMonth()) {
1704
- cells.splice(cells.length - 7, 7);
1705
- }
1706
- return cells;
1707
- }, [cursor]);
1708
- const monthLabel = cursor.toLocaleDateString(void 0, { month: "long", year: "numeric" });
1709
- const weekdayLabels = days.slice(0, 7).map(
1710
- (d) => d.toLocaleDateString(void 0, { weekday: "short" })
1711
- );
1712
- const todayISO = toISODate(/* @__PURE__ */ new Date());
1713
- return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
1714
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1715
- /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold capitalize text-foreground", children: monthLabel }),
1716
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
1717
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() - 1, 1)), children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
1718
- /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1)), children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
1719
- ] })
1468
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-card border border-border bg-card", children: [
1469
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
1470
+ /* @__PURE__ */ jsx(Avatar, { name: accountName }),
1471
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1472
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: accountName.replace(/^@/, "") }),
1473
+ /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
1474
+ t("marketing.content.previewNow"),
1475
+ " \xB7 ",
1476
+ /* @__PURE__ */ jsx(Globe, { className: "h-3 w-3" })
1477
+ ] })
1478
+ ] }),
1479
+ /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4 text-muted-foreground" })
1720
1480
  ] }),
1721
- unscheduled.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-card border border-dashed border-border bg-muted/20 p-2.5", children: [
1722
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("marketing.content.unscheduled") }),
1723
- unscheduled.map((post) => /* @__PURE__ */ jsxs(
1724
- "button",
1725
- {
1726
- type: "button",
1727
- onClick: () => onOpenPost(post.id),
1728
- className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted/40",
1729
- children: [
1730
- post.title || t("marketing.content.untitled"),
1731
- /* @__PURE__ */ jsx(FormatBadge, { format: post.format })
1732
- ]
1733
- },
1734
- post.id
1735
- ))
1481
+ /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap px-3 pb-2.5 text-sm leading-snug text-foreground", children: toCaption(caption) }),
1482
+ /* @__PURE__ */ jsx(Media, { mediaUrl, className: "aspect-auto" }),
1483
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-around border-t border-border px-3 py-2 text-muted-foreground", children: [
1484
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium", children: [
1485
+ /* @__PURE__ */ jsx(ThumbsUp, { className: "h-4 w-4" }),
1486
+ " ",
1487
+ t("marketing.content.previewLike")
1488
+ ] }),
1489
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium", children: [
1490
+ /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
1491
+ " ",
1492
+ t("marketing.content.previewComment")
1493
+ ] }),
1494
+ /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium", children: [
1495
+ /* @__PURE__ */ jsx(Share2, { className: "h-4 w-4" }),
1496
+ " ",
1497
+ t("marketing.content.previewShare")
1498
+ ] })
1499
+ ] })
1500
+ ] });
1501
+ }
1502
+ function LinkedinFrame({ caption, mediaUrl, accountName }) {
1503
+ const t = useTranslation();
1504
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-card border border-border bg-card", children: [
1505
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
1506
+ /* @__PURE__ */ jsx(Avatar, { name: accountName }),
1507
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1508
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: accountName.replace(/^@/, "") }),
1509
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.previewNow") })
1510
+ ] })
1736
1511
  ] }),
1737
- /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-card border border-border", children: [
1738
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 border-b border-border bg-muted/30", children: weekdayLabels.map((label, i) => /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-center text-xs font-medium capitalize text-muted-foreground", children: label }, i)) }),
1739
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((day, i) => {
1740
- const iso = toISODate(day);
1741
- const inMonth = day.getMonth() === cursor.getMonth();
1742
- const dayPosts = postsByDate.get(iso) ?? [];
1743
- return /* @__PURE__ */ jsxs(
1744
- "div",
1745
- {
1746
- className: cn(
1747
- "group min-h-24 border-b border-r border-border p-1.5 [&:nth-child(7n)]:border-r-0",
1748
- !inMonth && "bg-muted/20"
1749
- ),
1750
- children: [
1751
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1752
- /* @__PURE__ */ jsx(
1753
- "span",
1754
- {
1755
- className: cn(
1756
- "text-xs",
1757
- inMonth ? "text-foreground" : "text-muted-foreground/60",
1758
- iso === todayISO && "flex h-5 w-5 items-center justify-center rounded-full bg-primary font-semibold text-primary-foreground"
1759
- ),
1760
- children: day.getDate()
1761
- }
1762
- ),
1763
- !disabled && /* @__PURE__ */ jsx(
1764
- "button",
1765
- {
1766
- type: "button",
1767
- onClick: () => onNewPostAt(iso),
1768
- className: "rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted/60 hover:text-foreground group-hover:opacity-100",
1769
- "aria-label": t("marketing.content.newPost"),
1770
- children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
1771
- }
1772
- )
1773
- ] }),
1774
- /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-1", children: [
1775
- (expandedDay === iso ? dayPosts : dayPosts.slice(0, MAX_VISIBLE)).map((post) => /* @__PURE__ */ jsx(
1776
- "button",
1777
- {
1778
- type: "button",
1779
- onClick: () => onOpenPost(post.id),
1780
- className: "block w-full truncate rounded border border-border bg-card px-1.5 py-1 text-left text-xs text-foreground transition-colors hover:bg-muted/40",
1781
- children: post.title || t("marketing.content.untitled")
1782
- },
1783
- post.id
1784
- )),
1785
- dayPosts.length > MAX_VISIBLE && /* @__PURE__ */ jsx(
1786
- "button",
1787
- {
1788
- type: "button",
1789
- onClick: () => setExpandedDay(expandedDay === iso ? null : iso),
1790
- className: "block w-full px-1.5 py-0.5 text-left text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
1791
- children: expandedDay === iso ? t("marketing.content.showLess") : `+${dayPosts.length - MAX_VISIBLE}`
1792
- }
1793
- )
1794
- ] })
1795
- ]
1796
- },
1797
- i
1798
- );
1799
- }) })
1512
+ /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap px-3 pb-2.5 text-sm leading-snug text-foreground", children: toCaption(caption) }),
1513
+ /* @__PURE__ */ jsx(Media, { mediaUrl, className: "aspect-auto" }),
1514
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-around border-t border-border px-3 py-2 text-muted-foreground", children: [
1515
+ /* @__PURE__ */ jsx(ThumbsUp, { className: "h-4 w-4" }),
1516
+ /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
1517
+ /* @__PURE__ */ jsx(Repeat2, { className: "h-4 w-4" }),
1518
+ /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
1800
1519
  ] })
1801
1520
  ] });
1802
1521
  }
1803
- function ListView({ posts, onOpenPost }) {
1522
+ function XFrame({ caption, mediaUrl, accountName, accountHandle }) {
1804
1523
  const t = useTranslation();
1805
- if (posts.length === 0) {
1806
- return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("marketing.content.noPosts") });
1807
- }
1808
- return /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-card border border-border", children: /* @__PURE__ */ jsxs("table", { className: "w-full text-sm", children: [
1809
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { className: "border-b border-border bg-muted/30 text-left text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [
1810
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2", children: t("marketing.content.listWeek") }),
1811
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2", children: t("marketing.content.listTitle") }),
1812
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2", children: t("marketing.content.listFormat") }),
1813
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2", children: t("marketing.content.listStatus") }),
1814
- /* @__PURE__ */ jsx("th", { className: "px-3 py-2", children: t("marketing.content.listDate") })
1815
- ] }) }),
1816
- /* @__PURE__ */ jsx("tbody", { children: posts.map((post) => /* @__PURE__ */ jsxs(
1817
- "tr",
1524
+ const handle = (accountHandle || accountName).replace(/^@/, "");
1525
+ return /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-sm rounded-card border border-border bg-card p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5", children: [
1526
+ /* @__PURE__ */ jsx(Avatar, { name: handle }),
1527
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1528
+ /* @__PURE__ */ jsxs("p", { className: "truncate text-sm text-foreground", children: [
1529
+ /* @__PURE__ */ jsx("span", { className: "font-semibold", children: accountName.replace(/^@/, "") }),
1530
+ /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
1531
+ " @",
1532
+ handle,
1533
+ " \xB7 ",
1534
+ t("marketing.content.previewNow")
1535
+ ] })
1536
+ ] }),
1537
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 whitespace-pre-wrap text-sm leading-snug text-foreground", children: toCaption(caption) }),
1538
+ /* @__PURE__ */ jsx("div", { className: "mt-2 overflow-hidden rounded-xl border border-border", children: /* @__PURE__ */ jsx(Media, { mediaUrl, className: "aspect-auto" }) }),
1539
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between pr-6 text-muted-foreground", children: [
1540
+ /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
1541
+ /* @__PURE__ */ jsx(Repeat2, { className: "h-4 w-4" }),
1542
+ /* @__PURE__ */ jsx(Heart, { className: "h-4 w-4" }),
1543
+ /* @__PURE__ */ jsx(BarChart2, { className: "h-4 w-4" }),
1544
+ /* @__PURE__ */ jsx(Share2, { className: "h-4 w-4" })
1545
+ ] })
1546
+ ] })
1547
+ ] }) });
1548
+ }
1549
+ var FRAMES = {
1550
+ instagram: InstagramFrame,
1551
+ facebook: FacebookFrame,
1552
+ linkedin: LinkedinFrame,
1553
+ x: XFrame
1554
+ };
1555
+ function PostSocialPreview(props) {
1556
+ const platforms = props.platforms.length > 0 ? props.platforms : ["instagram"];
1557
+ const [selected, setSelected] = React12.useState(platforms[0]);
1558
+ const active = platforms.includes(selected) ? selected : platforms[0];
1559
+ const Frame = FRAMES[active] ?? InstagramFrame;
1560
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-border bg-muted/20 p-4", children: [
1561
+ platforms.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center gap-1", children: platforms.map((platform) => /* @__PURE__ */ jsxs(
1562
+ Button,
1818
1563
  {
1819
- onClick: () => onOpenPost(post.id),
1820
- className: "cursor-pointer border-b border-border last:border-b-0 transition-colors hover:bg-muted/30",
1564
+ variant: "bare",
1565
+ size: "bare",
1566
+ type: "button",
1567
+ onClick: () => setSelected(platform),
1568
+ "aria-pressed": active === platform,
1569
+ className: cn(
1570
+ "flex items-center gap-1.5 rounded-button px-2.5 py-1.5 text-xs font-medium capitalize transition-colors",
1571
+ active === platform ? "bg-card text-foreground shadow-button" : "text-muted-foreground hover:text-foreground"
1572
+ ),
1821
1573
  children: [
1822
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-muted-foreground", children: post.weekNumber }),
1823
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 font-medium text-foreground", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1824
- post.title || t("marketing.content.untitled"),
1825
- /* @__PURE__ */ jsx(PlatformBadges, { platforms: post.platforms })
1826
- ] }) }),
1827
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx(FormatBadge, { format: post.format }) }),
1828
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2", children: /* @__PURE__ */ jsx(PostStatusBadge, { status: post.status }) }),
1829
- /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-muted-foreground", children: post.scheduledDate ?? "\u2014" })
1574
+ /* @__PURE__ */ jsx(PlatformIcon, { platform, className: "h-3.5 w-3.5" }),
1575
+ platform
1830
1576
  ]
1831
1577
  },
1832
- post.id
1833
- )) })
1834
- ] }) });
1578
+ platform
1579
+ )) }),
1580
+ /* @__PURE__ */ jsx(Frame, { ...props })
1581
+ ] });
1835
1582
  }
1836
- function ContentView({ onOpenPost }) {
1837
- const t = useTranslation();
1838
- const loaded = useContentPlannerStore((s) => s.loaded);
1839
- const loading = useContentPlannerStore((s) => s.loading);
1840
- const accounts = useContentPlannerStore((s) => s.accounts);
1841
- const activeAccountId = useContentPlannerStore((s) => s.activeAccountId);
1842
- const plan = useContentPlannerStore((s) => s.activePlan);
1843
- const posts = useContentPlannerStore((s) => s.posts);
1844
- const load = useContentPlannerStore((s) => s.load);
1845
- const savePlan = useContentPlannerStore((s) => s.savePlan);
1846
- const savePost = useContentPlannerStore((s) => s.savePost);
1847
- const deletePost = useContentPlannerStore((s) => s.deletePost);
1848
- const viewMode = useContentPlannerStore((s) => s.viewMode);
1849
- const setViewMode = useContentPlannerStore((s) => s.setViewMode);
1850
- const loadedTenantId = useContentPlannerStore((s) => s.loadedTenantId);
1851
- const guardContentPosts = useLimitGuard("content_posts_month");
1852
- const [briefOpen, setBriefOpen] = React9.useState(false);
1853
- const [pendingDeleteId, setPendingDeleteId] = React9.useState(null);
1854
- const tenantId = useOrganizationStore((state) => state.currentOrg?.id);
1855
- React9.useEffect(() => {
1856
- if (!loading && (!loaded || tenantId && tenantId !== loadedTenantId)) void load();
1857
- }, [tenantId, loadedTenantId, loaded, loading, load]);
1858
- const postsByWeek = React9.useMemo(() => {
1859
- const map = /* @__PURE__ */ new Map();
1860
- for (const post of posts) {
1861
- const list = map.get(post.weekNumber) ?? [];
1862
- list.push(post);
1863
- map.set(post.weekNumber, list);
1864
- }
1865
- return map;
1866
- }, [posts]);
1867
- const ensurePlan = async () => {
1868
- if (plan) return plan;
1869
- if (!activeAccountId) return null;
1870
- return savePlan({
1871
- accountId: activeAccountId,
1872
- name: t("marketing.content.defaultPlanName"),
1873
- status: "active",
1874
- weeksCount: DEFAULT_WEEKS,
1875
- startDate: toISODate(mondayOfCurrentWeek())
1876
- });
1877
- };
1878
- const createPost = async (week) => {
1879
- if (await guardContentPosts() === "blocked") return;
1880
- const targetPlan = await ensurePlan();
1881
- if (!targetPlan) return;
1882
- const start = targetPlan.startDate ? /* @__PURE__ */ new Date(`${targetPlan.startDate}T00:00:00`) : mondayOfCurrentWeek();
1883
- start.setDate(start.getDate() + (week - 1) * 7);
1884
- const post = await savePost({
1885
- planId: targetPlan.id,
1886
- weekNumber: week,
1887
- title: "",
1888
- format: "reel",
1889
- status: "idea",
1890
- scheduledDate: toISODate(start)
1891
- });
1892
- invalidateLimit("content_posts_month");
1893
- onOpenPost(post.id);
1894
- };
1895
- const movePost = async (postId, week) => {
1896
- const post = posts.find((p) => p.id === postId);
1897
- if (!post || post.weekNumber === week) return;
1898
- await savePost({ id: postId, weekNumber: week });
1899
- };
1900
- const createPostAt = async (isoDate) => {
1901
- if (await guardContentPosts() === "blocked") return;
1902
- const targetPlan = await ensurePlan();
1903
- if (!targetPlan) return;
1904
- let week = 1;
1905
- if (targetPlan.startDate) {
1906
- const start = /* @__PURE__ */ new Date(`${targetPlan.startDate}T00:00:00`);
1907
- const day = /* @__PURE__ */ new Date(`${isoDate}T00:00:00`);
1908
- const diff = Math.floor((day.getTime() - start.getTime()) / (7 * 24 * 3600 * 1e3)) + 1;
1909
- week = Math.min(Math.max(diff, 1), targetPlan.weeksCount);
1583
+
1584
+ // src/views/content/templates.ts
1585
+ var reel = (title) => `# ${title}
1586
+
1587
+ **TEMA:** ${title.toUpperCase()}
1588
+ **Formato:** Reel \xB7 **Dura\xE7\xE3o:** 45 a 60 segundos
1589
+
1590
+ ---
1591
+
1592
+ ## TAKE 1 \u2013 GANCHO (0s a 5s)
1593
+
1594
+ *Dire\xE7\xE3o de cena: olhando para a c\xE2mera, energia alta.*
1595
+
1596
+ **Texto na tela:** \u2026
1597
+
1598
+ **Fala:** "\u2026"
1599
+
1600
+ ---
1601
+
1602
+ ## TAKE 2 \u2013 DESENVOLVIMENTO (5s a 40s)
1603
+
1604
+ *Dire\xE7\xE3o de cena: \u2026*
1605
+
1606
+ **Texto na tela:** \u2026
1607
+
1608
+ **Fala:** "\u2026"
1609
+
1610
+ ---
1611
+
1612
+ ## TAKE 3 \u2013 CTA (40s a 60s)
1613
+
1614
+ **Fala:** "\u2026"
1615
+
1616
+ **CTA:** \u2026
1617
+ `;
1618
+ var staticPost = (title) => `# ${title}
1619
+
1620
+ **TEMA:** ${title.toUpperCase()}
1621
+ **Formato:** Est\xE1tico
1622
+
1623
+ ## Texto da arte
1624
+
1625
+ \u2026
1626
+
1627
+ ## Legenda
1628
+
1629
+ \u2026
1630
+
1631
+ **CTA:** \u2026
1632
+ `;
1633
+ var carousel = (title) => `# ${title}
1634
+
1635
+ **TEMA:** ${title.toUpperCase()}
1636
+ **Formato:** Carrossel
1637
+
1638
+ ## Slide 1 \u2014 Capa (gancho)
1639
+
1640
+ \u2026
1641
+
1642
+ ## Slide 2
1643
+
1644
+ \u2026
1645
+
1646
+ ## Slide 3
1647
+
1648
+ \u2026
1649
+
1650
+ ## \xDAltimo slide \u2014 CTA
1651
+
1652
+ \u2026
1653
+ `;
1654
+ var story = (title) => `# ${title}
1655
+
1656
+ **TEMA:** ${title.toUpperCase()}
1657
+ **Formato:** Story
1658
+
1659
+ ## Frame 1
1660
+
1661
+ \u2026
1662
+
1663
+ ## Frame 2
1664
+
1665
+ \u2026
1666
+
1667
+ ## Frame final \u2014 CTA
1668
+
1669
+ *Sticker de link / caixa de pergunta / enquete.*
1670
+
1671
+ \u2026
1672
+ `;
1673
+ var video = (title) => `# ${title}
1674
+
1675
+ **TEMA:** ${title.toUpperCase()}
1676
+ **Formato:** V\xEDdeo longo (YouTube) \xB7 **Dura\xE7\xE3o:** 8 a 12 minutos
1677
+
1678
+ ## Gancho (0s a 30s)
1679
+
1680
+ \u2026
1681
+
1682
+ ## Roteiro por blocos
1683
+
1684
+ ### Bloco 1 \u2014 Contexto
1685
+
1686
+ \u2026
1687
+
1688
+ ### Bloco 2 \u2014 Desenvolvimento
1689
+
1690
+ \u2026
1691
+
1692
+ ### Bloco 3 \u2014 Fechamento
1693
+
1694
+ \u2026
1695
+
1696
+ **CTA:** inscreva-se / comente / link na descri\xE7\xE3o
1697
+ `;
1698
+ var live = (title) => `# ${title}
1699
+
1700
+ **TEMA:** ${title.toUpperCase()}
1701
+ **Formato:** Live
1702
+
1703
+ ## Pauta
1704
+
1705
+ 1. Abertura e aquecimento (5 min)
1706
+ 2. \u2026
1707
+ 3. Perguntas e respostas (15 min)
1708
+
1709
+ ## Avisos / CTAs durante a live
1710
+
1711
+ \u2026
1712
+ `;
1713
+ var article = (title) => `# ${title}
1714
+
1715
+ **TEMA:** ${title.toUpperCase()}
1716
+ **Formato:** Artigo / Newsletter
1717
+
1718
+ ## T\xEDtulo de trabalho
1719
+
1720
+ \u2026
1721
+
1722
+ ## Estrutura
1723
+
1724
+ - Abertura (dor / promessa)
1725
+ - Desenvolvimento em 3 pontos
1726
+ - Conclus\xE3o + CTA
1727
+
1728
+ ## Rascunho
1729
+
1730
+ \u2026
1731
+ `;
1732
+ var POST_TEMPLATES = {
1733
+ reel,
1734
+ static: staticPost,
1735
+ carousel,
1736
+ story,
1737
+ video,
1738
+ live,
1739
+ article
1740
+ };
1741
+ function MediaAsset({ post, onChanged }) {
1742
+ const t = useTranslation();
1743
+ const inputRef = React12.useRef(null);
1744
+ const [busy, setBusy] = React12.useState(false);
1745
+ const isVideo2 = post.mediaUrl ? /\.(mp4|webm|mov)($|\?)/i.test(post.mediaUrl) : false;
1746
+ const pick = () => inputRef.current?.click();
1747
+ const upload = async (file) => {
1748
+ setBusy(true);
1749
+ try {
1750
+ const url = await uploadPostMedia(post.id, file);
1751
+ onChanged(url);
1752
+ } finally {
1753
+ setBusy(false);
1910
1754
  }
1911
- const post = await savePost({
1912
- planId: targetPlan.id,
1913
- weekNumber: week,
1914
- title: "",
1915
- format: "reel",
1916
- status: "idea",
1917
- scheduledDate: isoDate
1918
- });
1919
- invalidateLimit("content_posts_month");
1920
- onOpenPost(post.id);
1921
1755
  };
1922
- const busy = !loaded || loading;
1923
- const hasAccount = accounts.length > 0;
1924
- const weeksCount = plan?.weeksCount ?? DEFAULT_WEEKS;
1925
- const boardStart = plan?.startDate ? /* @__PURE__ */ new Date(`${plan.startDate}T00:00:00`) : mondayOfCurrentWeek();
1926
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-6", children: [
1927
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
1928
- /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
1929
- /* @__PURE__ */ jsx(AccountSelector, {}),
1930
- plan && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: plan.name })
1931
- ] }),
1932
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
1933
- /* @__PURE__ */ jsx(ViewSwitcher, { mode: viewMode, onChange: setViewMode }),
1934
- plan && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: () => setBriefOpen(true), children: [
1935
- /* @__PURE__ */ jsx(NotebookPen, { className: "mr-1.5 h-4 w-4" }),
1936
- t("marketing.content.brief")
1937
- ] })
1938
- ] })
1939
- ] }),
1940
- !busy && !hasAccount && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("marketing.content.noAccountsHint") }),
1941
- busy && viewMode === "board" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-44" }, i)) }),
1942
- busy && viewMode === "calendar" && /* @__PURE__ */ jsx(Skeleton, { className: "h-96 w-full" }),
1943
- busy && viewMode === "list" && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: Array.from({ length: 6 }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }, i)) }),
1944
- !busy && viewMode === "board" && /* @__PURE__ */ jsx(
1945
- BoardView,
1756
+ if (!canUploadMedia()) {
1757
+ return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.mediaUnavailable") });
1758
+ }
1759
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1760
+ /* @__PURE__ */ jsx(
1761
+ "input",
1946
1762
  {
1947
- startDate: boardStart,
1948
- weeksCount,
1949
- postsByWeek,
1950
- onOpenPost,
1951
- onNewPost: (w) => void createPost(w),
1952
- onDeletePost: setPendingDeleteId,
1953
- onDropPost: (id, w) => void movePost(id, w),
1954
- disabled: !hasAccount
1763
+ ref: inputRef,
1764
+ type: "file",
1765
+ accept: "image/*,video/*",
1766
+ className: "hidden",
1767
+ onChange: (e) => {
1768
+ const file = e.target.files?.[0];
1769
+ if (file) void upload(file);
1770
+ e.target.value = "";
1771
+ }
1955
1772
  }
1956
1773
  ),
1957
- !busy && viewMode === "calendar" && /* @__PURE__ */ jsx(
1958
- CalendarView,
1774
+ post.mediaUrl ? /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-card border border-border bg-muted/20", children: [
1775
+ isVideo2 ? /* @__PURE__ */ jsx("video", { src: post.mediaUrl, controls: true, className: "max-h-72 w-full object-contain" }) : /* @__PURE__ */ jsx("img", { src: post.mediaUrl, alt: "", className: "max-h-72 w-full object-contain" }),
1776
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-border p-2", children: [
1777
+ /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: pick, disabled: busy, children: [
1778
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(ImagePlus, { className: "mr-1.5 h-3.5 w-3.5" }),
1779
+ t("marketing.content.mediaReplace")
1780
+ ] }),
1781
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => onChanged(null), disabled: busy, children: [
1782
+ /* @__PURE__ */ jsx(X, { className: "mr-1 h-3.5 w-3.5" }),
1783
+ t("marketing.content.mediaRemove")
1784
+ ] })
1785
+ ] })
1786
+ ] }) : /* @__PURE__ */ jsxs(
1787
+ Button,
1959
1788
  {
1960
- posts,
1961
- onOpenPost,
1962
- onNewPostAt: (iso) => void createPostAt(iso),
1963
- disabled: !hasAccount
1964
- }
1965
- ),
1966
- !busy && viewMode === "list" && /* @__PURE__ */ jsx(ListView, { posts, onOpenPost }),
1967
- /* @__PURE__ */ jsx(PlanBriefSheet, { open: briefOpen, onClose: () => setBriefOpen(false) }),
1968
- /* @__PURE__ */ jsx(
1969
- ConfirmDialog,
1970
- {
1971
- open: !!pendingDeleteId,
1972
- variant: "destructive",
1973
- title: t("marketing.content.deletePost"),
1974
- description: t("marketing.content.deletePostConfirm"),
1975
- confirmLabel: t("marketing.content.deletePost"),
1976
- cancelLabel: t("marketing.content.cancel"),
1977
- onCancel: () => setPendingDeleteId(null),
1978
- onConfirm: () => {
1979
- const id = pendingDeleteId;
1980
- setPendingDeleteId(null);
1981
- if (id) void deletePost(id);
1982
- }
1789
+ variant: "bare",
1790
+ size: "bare",
1791
+ type: "button",
1792
+ onClick: pick,
1793
+ disabled: busy,
1794
+ className: "flex w-full flex-col items-center justify-center gap-2 rounded-card border border-dashed border-border p-6 text-muted-foreground transition-colors hover:bg-muted/30 hover:text-foreground font-normal disabled:opacity-100",
1795
+ children: [
1796
+ busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin" }) : /* @__PURE__ */ jsx(ImagePlus, { className: "h-5 w-5" }),
1797
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("marketing.content.mediaUpload") }),
1798
+ /* @__PURE__ */ jsx("span", { className: "text-xs", children: t("marketing.content.mediaHint") })
1799
+ ]
1983
1800
  }
1984
1801
  )
1985
1802
  ] });
1986
1803
  }
1987
- function canUploadMedia() {
1988
- return !!getSupabaseClientOptional();
1989
- }
1990
- async function uploadPostMedia(postId, file) {
1991
- const supabase = getSupabaseClientOptional();
1992
- if (!supabase) throw new Error("Supabase not initialized");
1993
- const tenantId = getActiveTenantId();
1994
- if (!tenantId) throw new Error("No active tenant");
1995
- const ext = file.name.split(".").pop()?.toLowerCase() ?? "bin";
1996
- const path = `content/${tenantId}/${postId}-${Date.now()}.${ext}`;
1997
- const { error } = await supabase.storage.from("mkt-media").upload(path, file, { upsert: true });
1998
- if (error) throw error;
1999
- const { data } = supabase.storage.from("mkt-media").getPublicUrl(path);
2000
- return data.publicUrl;
1804
+ function toDraft2(post) {
1805
+ return {
1806
+ title: post.title,
1807
+ format: post.format,
1808
+ status: post.status,
1809
+ scheduledDate: post.scheduledDate ?? "",
1810
+ platforms: post.platforms,
1811
+ hook: post.hook ?? "",
1812
+ cta: post.cta ?? "",
1813
+ contentMd: post.contentMd
1814
+ };
2001
1815
  }
2002
- function newId() {
2003
- return typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `ck-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
1816
+ function PostModal({ postId, onClose }) {
1817
+ return /* @__PURE__ */ jsx(Modal, { open: !!postId, onOpenChange: (o) => {
1818
+ if (!o) onClose();
1819
+ }, children: /* @__PURE__ */ jsx(ModalContent, { size: "5xl", children: postId && /* @__PURE__ */ jsx(PostEditor, { postId, onClose }) }) });
2004
1820
  }
2005
- function RecordingChecklist({ post }) {
1821
+ function PostEditor({ postId, onClose }) {
2006
1822
  const t = useTranslation();
1823
+ const provider2 = useContentPlannerProvider();
1824
+ const storePost = useContentPlannerStore((s) => s.posts.find((p) => p.id === postId));
2007
1825
  const savePost = useContentPlannerStore((s) => s.savePost);
2008
- const [items, setItems] = React9.useState(post.checklist);
2009
- const [text, setText] = React9.useState("");
2010
- React9.useEffect(() => {
2011
- setItems(post.checklist);
2012
- }, [post.id]);
2013
- const persist = (next) => {
2014
- setItems(next);
2015
- void savePost({ id: post.id, checklist: next });
1826
+ const deletePost = useContentPlannerStore((s) => s.deletePost);
1827
+ const accounts = useContentPlannerStore((s) => s.accounts);
1828
+ const activeAccountId = useContentPlannerStore((s) => s.activeAccountId);
1829
+ const [post, setPost] = React12.useState(storePost ?? null);
1830
+ const [draft, setDraft] = React12.useState(storePost ? toDraft2(storePost) : null);
1831
+ const [saving, setSaving] = React12.useState(false);
1832
+ const [confirmDelete, setConfirmDelete] = React12.useState(false);
1833
+ React12.useEffect(() => {
1834
+ if (post?.id === postId) return;
1835
+ if (storePost) {
1836
+ setPost(storePost);
1837
+ setDraft(toDraft2(storePost));
1838
+ return;
1839
+ }
1840
+ void provider2.getPost(postId).then((fetched) => {
1841
+ if (fetched) {
1842
+ setPost(fetched);
1843
+ setDraft(toDraft2(fetched));
1844
+ }
1845
+ });
1846
+ }, [postId, storePost, post?.id, provider2]);
1847
+ if (!post || !draft) {
1848
+ return /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-5", children: [
1849
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-56" }),
1850
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }),
1851
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: Array.from({ length: 3 }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-14" }, i)) }),
1852
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-64 w-full" })
1853
+ ] });
1854
+ }
1855
+ const dirty = draft.title !== post.title || draft.format !== post.format || draft.status !== post.status || draft.scheduledDate !== (post.scheduledDate ?? "") || draft.platforms.join(",") !== post.platforms.join(",") || draft.hook !== (post.hook ?? "") || draft.cta !== (post.cta ?? "") || draft.contentMd !== post.contentMd;
1856
+ const patch = (partial) => setDraft((d) => d ? { ...d, ...partial } : d);
1857
+ const save = async () => {
1858
+ setSaving(true);
1859
+ try {
1860
+ const updated = await savePost({
1861
+ id: post.id,
1862
+ title: draft.title,
1863
+ format: draft.format,
1864
+ status: draft.status,
1865
+ scheduledDate: draft.scheduledDate || null,
1866
+ platforms: draft.platforms,
1867
+ hook: draft.hook,
1868
+ cta: draft.cta,
1869
+ contentMd: draft.contentMd
1870
+ });
1871
+ setPost(updated);
1872
+ setDraft(toDraft2(updated));
1873
+ } finally {
1874
+ setSaving(false);
1875
+ }
2016
1876
  };
2017
- const toggle = (id) => persist(items.map((i) => i.id === id ? { ...i, done: !i.done } : i));
2018
- const remove = (id) => persist(items.filter((i) => i.id !== id));
2019
- const add = () => {
2020
- const value = text.trim();
2021
- if (!value) return;
2022
- persist([...items, { id: newId(), text: value, done: false }]);
2023
- setText("");
1877
+ const insertTemplate = () => {
1878
+ patch({ contentMd: POST_TEMPLATES[draft.format](draft.title || t("marketing.content.untitled")) });
2024
1879
  };
2025
- const generateFromScript = () => {
2026
- const takes = [...post.contentMd.matchAll(/^##\s+(TAKE\s+.+)$/gim)].map(
2027
- (m) => m[1].trim()
2028
- );
2029
- const existing = new Set(items.map((i) => i.text));
2030
- const fresh = takes.filter((take) => !existing.has(take)).map((take) => ({ id: newId(), text: take, done: false }));
2031
- if (fresh.length > 0) persist([...items, ...fresh]);
1880
+ const isStatic = draft.format === "static";
1881
+ const account = accounts.find((a) => a.id === activeAccountId) ?? null;
1882
+ const handleMediaChanged = (mediaUrl) => {
1883
+ void savePost({ id: post.id, mediaUrl }).then((updated) => {
1884
+ setPost(updated);
1885
+ setDraft((d) => d ? { ...d } : d);
1886
+ });
2032
1887
  };
2033
- const hasTakes = /^##\s+TAKE\s+/im.test(post.contentMd);
2034
- const done = items.filter((i) => i.done).length;
2035
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3", children: [
2036
- items.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-1.5", children: [
2037
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-muted-foreground", children: [
2038
- /* @__PURE__ */ jsxs("span", { children: [
2039
- done,
2040
- "/",
2041
- items.length
2042
- ] }),
2043
- hasTakes && /* @__PURE__ */ jsxs(
2044
- "button",
2045
- {
2046
- type: "button",
2047
- onClick: generateFromScript,
2048
- className: "flex items-center gap-1 font-medium text-muted-foreground transition-colors hover:text-foreground",
2049
- children: [
2050
- /* @__PURE__ */ jsx(Sparkles, { className: "h-3 w-3" }),
2051
- t("marketing.content.checklistGenerate")
2052
- ]
2053
- }
2054
- )
2055
- ] }),
2056
- /* @__PURE__ */ jsx("div", { className: "h-1 overflow-hidden rounded-full bg-muted", children: /* @__PURE__ */ jsx(
2057
- "div",
2058
- {
2059
- className: "h-full rounded-full bg-primary transition-[width]",
2060
- style: { width: items.length ? `${done / items.length * 100}%` : 0 }
2061
- }
2062
- ) })
2063
- ] }),
2064
- items.length === 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-card border border-dashed border-border p-3 text-center", children: [
2065
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.checklistEmpty") }),
2066
- hasTakes && /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: generateFromScript, children: [
2067
- /* @__PURE__ */ jsx(Sparkles, { className: "mr-1.5 h-3.5 w-3.5" }),
2068
- t("marketing.content.checklistGenerate")
2069
- ] })
2070
- ] }),
2071
- /* @__PURE__ */ jsx("ul", { className: "space-y-1", children: items.map((item) => /* @__PURE__ */ jsxs("li", { className: "group/item flex items-start gap-2.5 rounded-button px-1 py-2 transition-colors hover:bg-muted/40", children: [
2072
- /* @__PURE__ */ jsxs("label", { className: "flex min-w-0 flex-1 cursor-pointer items-start gap-2.5", children: [
2073
- /* @__PURE__ */ jsx(Checkbox, { checked: item.done, onChange: () => toggle(item.id), className: "mt-0.5" }),
1888
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1889
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: draft.title || t("marketing.content.untitled") }) }),
1890
+ /* @__PURE__ */ jsx(ModalBody, { children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-6 lg:grid-cols-[minmax(0,1fr)_18rem]", children: [
1891
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 space-y-5", children: [
2074
1892
  /* @__PURE__ */ jsx(
2075
- "span",
1893
+ Input,
2076
1894
  {
2077
- className: cn(
2078
- "min-w-0 flex-1 text-sm leading-snug",
2079
- item.done ? "text-muted-foreground line-through" : "text-foreground"
2080
- ),
2081
- children: item.text
1895
+ value: draft.title,
1896
+ onChange: (e) => patch({ title: e.target.value }),
1897
+ placeholder: t("marketing.content.titlePlaceholder"),
1898
+ className: "text-base font-medium"
2082
1899
  }
2083
- )
2084
- ] }),
2085
- /* @__PURE__ */ jsx(
2086
- "button",
2087
- {
2088
- type: "button",
2089
- "aria-label": t("marketing.content.cancel"),
2090
- onClick: () => remove(item.id),
2091
- className: "rounded p-0.5 text-muted-foreground/50 opacity-0 transition-opacity hover:text-destructive group-hover/item:opacity-100 focus:opacity-100",
2092
- children: /* @__PURE__ */ jsx(X, { className: "h-3.5 w-3.5" })
2093
- }
2094
- )
2095
- ] }, item.id)) }),
2096
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2097
- /* @__PURE__ */ jsx(
2098
- Input,
2099
- {
2100
- value: text,
2101
- onChange: (e) => setText(e.target.value),
2102
- placeholder: t("marketing.content.checklistAddPlaceholder"),
2103
- onKeyDown: (e) => {
2104
- if (e.key === "Enter") add();
2105
- },
2106
- className: "h-8 text-sm"
2107
- }
2108
- ),
2109
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: add, disabled: !text.trim(), "aria-label": t("marketing.content.checklistAdd"), children: /* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }) })
2110
- ] })
2111
- ] });
2112
- }
2113
- function toCaption(md) {
2114
- return md.replace(/^#+\s*/gm, "").replace(/\*\*/g, "").replace(/^[-*]\s+/gm, "").trim();
2115
- }
2116
- function isVideo(url) {
2117
- return !!url && /\.(mp4|webm|mov)($|\?)/i.test(url);
2118
- }
2119
- function Media({ mediaUrl, className }) {
2120
- if (!mediaUrl) {
2121
- return /* @__PURE__ */ jsx("div", { className: cn("flex aspect-square w-full items-center justify-center bg-muted/40", className), children: /* @__PURE__ */ jsx(Image, { className: "h-8 w-8 text-muted-foreground/40" }) });
2122
- }
2123
- return isVideo(mediaUrl) ? /* @__PURE__ */ jsx("video", { src: mediaUrl, controls: true, className: cn("w-full bg-black object-contain", className) }) : /* @__PURE__ */ jsx("img", { src: mediaUrl, alt: "", className: cn("w-full object-cover", className) });
2124
- }
2125
- function Avatar({ name, ring }) {
2126
- return /* @__PURE__ */ jsx(
2127
- "span",
2128
- {
2129
- className: cn(
2130
- "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold text-foreground",
2131
- ring && "ring-2 ring-offset-2 ring-pink-500/60"
2132
- ),
2133
- children: (name.replace(/^@/, "")[0] ?? "?").toUpperCase()
2134
- }
2135
- );
2136
- }
2137
- function InstagramFrame({ caption, mediaUrl, accountName, accountHandle }) {
2138
- const t = useTranslation();
2139
- const handle = (accountHandle || accountName).replace(/^@/, "");
2140
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-card border border-border bg-card", children: [
2141
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
2142
- /* @__PURE__ */ jsx(Avatar, { name: handle, ring: true }),
2143
- /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-sm font-semibold text-foreground", children: handle }),
2144
- /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4 text-muted-foreground" })
2145
- ] }),
2146
- /* @__PURE__ */ jsx(Media, { mediaUrl }),
2147
- /* @__PURE__ */ jsxs("div", { className: "space-y-2 px-3 py-2.5", children: [
2148
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-4 text-foreground", children: [
2149
- /* @__PURE__ */ jsx(Heart, { className: "h-6 w-6" }),
2150
- /* @__PURE__ */ jsx(MessageCircle, { className: "h-6 w-6" }),
2151
- /* @__PURE__ */ jsx(Send, { className: "h-6 w-6" }),
2152
- /* @__PURE__ */ jsx(Bookmark, { className: "ml-auto h-6 w-6" })
2153
- ] }),
2154
- /* @__PURE__ */ jsxs("p", { className: "whitespace-pre-wrap text-sm leading-snug text-foreground", children: [
2155
- /* @__PURE__ */ jsx("span", { className: "mr-1.5 font-semibold", children: handle }),
2156
- toCaption(caption)
2157
- ] }),
2158
- /* @__PURE__ */ jsx("p", { className: "text-[11px] uppercase tracking-wide text-muted-foreground", children: t("marketing.content.previewNow") })
2159
- ] })
2160
- ] });
2161
- }
2162
- function FacebookFrame({ caption, mediaUrl, accountName }) {
2163
- const t = useTranslation();
2164
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-card border border-border bg-card", children: [
2165
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
2166
- /* @__PURE__ */ jsx(Avatar, { name: accountName }),
2167
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2168
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: accountName.replace(/^@/, "") }),
2169
- /* @__PURE__ */ jsxs("p", { className: "flex items-center gap-1 text-xs text-muted-foreground", children: [
2170
- t("marketing.content.previewNow"),
2171
- " \xB7 ",
2172
- /* @__PURE__ */ jsx(Globe, { className: "h-3 w-3" })
1900
+ ),
1901
+ /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
1902
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1903
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.formatLabel") }),
1904
+ /* @__PURE__ */ jsxs(Select, { value: draft.format, onValueChange: (v) => patch({ format: v }), children: [
1905
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1906
+ /* @__PURE__ */ jsx(SelectContent, { children: POST_FORMATS.map((f) => /* @__PURE__ */ jsx(SelectItem, { value: f, children: t(`marketing.content.format.${f}`) }, f)) })
1907
+ ] })
1908
+ ] }),
1909
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1910
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.statusLabel") }),
1911
+ /* @__PURE__ */ jsxs(Select, { value: draft.status, onValueChange: (v) => patch({ status: v }), children: [
1912
+ /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
1913
+ /* @__PURE__ */ jsx(SelectContent, { children: POST_STATUS_ORDER.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
1914
+ /* @__PURE__ */ jsx(StatusDot, { status: s }),
1915
+ t(`marketing.content.status.${s}`)
1916
+ ] }) }, s)) })
1917
+ ] })
1918
+ ] }),
1919
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1920
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.scheduledDate") }),
1921
+ /* @__PURE__ */ jsx(
1922
+ Input,
1923
+ {
1924
+ type: "date",
1925
+ value: draft.scheduledDate,
1926
+ onChange: (e) => patch({ scheduledDate: e.target.value })
1927
+ }
1928
+ )
1929
+ ] }),
1930
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 sm:col-span-2", children: [
1931
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.hook") }),
1932
+ /* @__PURE__ */ jsx(
1933
+ Input,
1934
+ {
1935
+ value: draft.hook,
1936
+ onChange: (e) => patch({ hook: e.target.value }),
1937
+ placeholder: t("marketing.content.hookPlaceholder")
1938
+ }
1939
+ )
1940
+ ] }),
1941
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1942
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.cta") }),
1943
+ /* @__PURE__ */ jsx(
1944
+ Input,
1945
+ {
1946
+ value: draft.cta,
1947
+ onChange: (e) => patch({ cta: e.target.value }),
1948
+ placeholder: t("marketing.content.ctaPlaceholder")
1949
+ }
1950
+ )
1951
+ ] }),
1952
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1 sm:col-span-3", children: [
1953
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.platformsLabel") }),
1954
+ /* @__PURE__ */ jsx(PlatformPicker, { value: draft.platforms, onChange: (platforms) => patch({ platforms }) })
1955
+ ] })
1956
+ ] }),
1957
+ isStatic && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1958
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.mediaLabel") }),
1959
+ /* @__PURE__ */ jsx(MediaAsset, { post, onChanged: handleMediaChanged })
1960
+ ] }),
1961
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1962
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
1963
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: isStatic ? t("marketing.content.captionLabel") : t("marketing.content.scriptLabel") }),
1964
+ draft.contentMd.trim() === "" && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: insertTemplate, children: t("marketing.content.insertTemplate") })
1965
+ ] }),
1966
+ /* @__PURE__ */ jsx(
1967
+ MarkdownEditor,
1968
+ {
1969
+ value: draft.contentMd,
1970
+ onChange: (contentMd) => patch({ contentMd }),
1971
+ placeholder: isStatic ? t("marketing.content.captionPlaceholder") : t("marketing.content.bodyPlaceholder"),
1972
+ editLabel: t("marketing.content.edit"),
1973
+ previewLabel: t("marketing.content.preview"),
1974
+ renderPreview: isStatic ? (value) => /* @__PURE__ */ jsx(
1975
+ PostSocialPreview,
1976
+ {
1977
+ caption: value,
1978
+ mediaUrl: post.mediaUrl,
1979
+ accountName: account?.name ?? "@conta",
1980
+ accountHandle: account?.handle,
1981
+ platforms: draft.platforms.length > 0 ? draft.platforms : account?.platforms ?? []
1982
+ }
1983
+ ) : void 0
1984
+ }
1985
+ )
2173
1986
  ] })
2174
1987
  ] }),
2175
- /* @__PURE__ */ jsx(MoreHorizontal, { className: "h-4 w-4 text-muted-foreground" })
2176
- ] }),
2177
- /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap px-3 pb-2.5 text-sm leading-snug text-foreground", children: toCaption(caption) }),
2178
- /* @__PURE__ */ jsx(Media, { mediaUrl, className: "aspect-auto" }),
2179
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-around border-t border-border px-3 py-2 text-muted-foreground", children: [
2180
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium", children: [
2181
- /* @__PURE__ */ jsx(ThumbsUp, { className: "h-4 w-4" }),
2182
- " ",
2183
- t("marketing.content.previewLike")
2184
- ] }),
2185
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium", children: [
2186
- /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
2187
- " ",
2188
- t("marketing.content.previewComment")
2189
- ] }),
2190
- /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5 text-xs font-medium", children: [
2191
- /* @__PURE__ */ jsx(Share2, { className: "h-4 w-4" }),
2192
- " ",
2193
- t("marketing.content.previewShare")
1988
+ /* @__PURE__ */ jsxs("aside", { className: "space-y-2 lg:border-l lg:border-border lg:pl-6", children: [
1989
+ /* @__PURE__ */ jsx("p", { className: "text-sm font-medium text-foreground", children: t("marketing.content.checklistTitle") }),
1990
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.checklistDescription") }),
1991
+ /* @__PURE__ */ jsx(RecordingChecklist, { post })
2194
1992
  ] })
2195
- ] })
2196
- ] });
2197
- }
2198
- function LinkedinFrame({ caption, mediaUrl, accountName }) {
2199
- const t = useTranslation();
2200
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto w-full max-w-sm overflow-hidden rounded-card border border-border bg-card", children: [
2201
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5 px-3 py-2.5", children: [
2202
- /* @__PURE__ */ jsx(Avatar, { name: accountName }),
2203
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2204
- /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-semibold text-foreground", children: accountName.replace(/^@/, "") }),
2205
- /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.previewNow") })
1993
+ ] }) }),
1994
+ /* @__PURE__ */ jsxs(ModalFooter, { className: "sm:justify-between", children: [
1995
+ confirmDelete ? /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2", children: [
1996
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: t("marketing.content.deletePostConfirm") }),
1997
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmDelete(false), children: t("marketing.content.cancel") }),
1998
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: () => void deletePost(post.id).then(onClose), children: t("marketing.content.deletePost") })
1999
+ ] }) : /* @__PURE__ */ jsx(Button, { variant: "ghost", className: "text-destructive", onClick: () => setConfirmDelete(true), children: t("marketing.content.deletePost") }),
2000
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2001
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, children: t("marketing.content.cancel") }),
2002
+ /* @__PURE__ */ jsx(Button, { onClick: () => void save().then(onClose), disabled: !dirty || saving, children: t("marketing.content.save") })
2206
2003
  ] })
2207
- ] }),
2208
- /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap px-3 pb-2.5 text-sm leading-snug text-foreground", children: toCaption(caption) }),
2209
- /* @__PURE__ */ jsx(Media, { mediaUrl, className: "aspect-auto" }),
2210
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-around border-t border-border px-3 py-2 text-muted-foreground", children: [
2211
- /* @__PURE__ */ jsx(ThumbsUp, { className: "h-4 w-4" }),
2212
- /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
2213
- /* @__PURE__ */ jsx(Repeat2, { className: "h-4 w-4" }),
2214
- /* @__PURE__ */ jsx(Send, { className: "h-4 w-4" })
2215
2004
  ] })
2216
2005
  ] });
2217
2006
  }
2218
- function XFrame({ caption, mediaUrl, accountName, accountHandle }) {
2007
+ var DEFAULT_WEEKS = 8;
2008
+ var ADD_ACCOUNT_VALUE = "__add-account__";
2009
+ var POST_DRAG_MIME = "application/x-mkt-post";
2010
+ function PostCard({ post, onOpen, onDelete }) {
2219
2011
  const t = useTranslation();
2220
- const handle = (accountHandle || accountName).replace(/^@/, "");
2221
- return /* @__PURE__ */ jsx("div", { className: "mx-auto w-full max-w-sm rounded-card border border-border bg-card p-3", children: /* @__PURE__ */ jsxs("div", { className: "flex gap-2.5", children: [
2222
- /* @__PURE__ */ jsx(Avatar, { name: handle }),
2223
- /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2224
- /* @__PURE__ */ jsxs("p", { className: "truncate text-sm text-foreground", children: [
2225
- /* @__PURE__ */ jsx("span", { className: "font-semibold", children: accountName.replace(/^@/, "") }),
2226
- /* @__PURE__ */ jsxs("span", { className: "text-muted-foreground", children: [
2227
- " @",
2228
- handle,
2229
- " \xB7 ",
2230
- t("marketing.content.previewNow")
2231
- ] })
2232
- ] }),
2233
- /* @__PURE__ */ jsx("p", { className: "mt-0.5 whitespace-pre-wrap text-sm leading-snug text-foreground", children: toCaption(caption) }),
2234
- /* @__PURE__ */ jsx("div", { className: "mt-2 overflow-hidden rounded-xl border border-border", children: /* @__PURE__ */ jsx(Media, { mediaUrl, className: "aspect-auto" }) }),
2235
- /* @__PURE__ */ jsxs("div", { className: "mt-2 flex items-center justify-between pr-6 text-muted-foreground", children: [
2236
- /* @__PURE__ */ jsx(MessageCircle, { className: "h-4 w-4" }),
2237
- /* @__PURE__ */ jsx(Repeat2, { className: "h-4 w-4" }),
2238
- /* @__PURE__ */ jsx(Heart, { className: "h-4 w-4" }),
2239
- /* @__PURE__ */ jsx(BarChart2, { className: "h-4 w-4" }),
2240
- /* @__PURE__ */ jsx(Share2, { className: "h-4 w-4" })
2241
- ] })
2242
- ] })
2243
- ] }) });
2244
- }
2245
- var FRAMES = {
2246
- instagram: InstagramFrame,
2247
- facebook: FacebookFrame,
2248
- linkedin: LinkedinFrame,
2249
- x: XFrame
2250
- };
2251
- function PostSocialPreview(props) {
2252
- const platforms = props.platforms.length > 0 ? props.platforms : ["instagram"];
2253
- const [selected, setSelected] = React9.useState(platforms[0]);
2254
- const active = platforms.includes(selected) ? selected : platforms[0];
2255
- const Frame = FRAMES[active] ?? InstagramFrame;
2256
- return /* @__PURE__ */ jsxs("div", { className: "space-y-3 rounded-md border border-border bg-muted/20 p-4", children: [
2257
- platforms.length > 1 && /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center gap-1", children: platforms.map((platform) => /* @__PURE__ */ jsxs(
2258
- "button",
2259
- {
2260
- type: "button",
2261
- onClick: () => setSelected(platform),
2262
- "aria-pressed": active === platform,
2263
- className: cn(
2264
- "flex items-center gap-1.5 rounded-button px-2.5 py-1.5 text-xs font-medium capitalize transition-colors",
2265
- active === platform ? "bg-card text-foreground shadow-button" : "text-muted-foreground hover:text-foreground"
2266
- ),
2267
- children: [
2268
- /* @__PURE__ */ jsx(PlatformIcon, { platform, className: "h-3.5 w-3.5" }),
2269
- platform
2270
- ]
2012
+ const [dragging, setDragging] = React12.useState(false);
2013
+ return /* @__PURE__ */ jsxs(
2014
+ "div",
2015
+ {
2016
+ role: "button",
2017
+ tabIndex: 0,
2018
+ onClick: onOpen,
2019
+ onKeyDown: (e) => {
2020
+ if (e.key === "Enter") onOpen();
2271
2021
  },
2272
- platform
2273
- )) }),
2274
- /* @__PURE__ */ jsx(Frame, { ...props })
2275
- ] });
2022
+ draggable: true,
2023
+ onDragStart: (e) => {
2024
+ e.dataTransfer.setData(POST_DRAG_MIME, post.id);
2025
+ e.dataTransfer.effectAllowed = "move";
2026
+ setDragging(true);
2027
+ },
2028
+ onDragEnd: () => setDragging(false),
2029
+ className: cn(
2030
+ "group/post relative w-full cursor-pointer rounded-card border border-border bg-card p-3 pl-7 text-left transition-colors hover:bg-muted/40",
2031
+ dragging && "opacity-50"
2032
+ ),
2033
+ children: [
2034
+ /* @__PURE__ */ jsx(
2035
+ GripVertical,
2036
+ {
2037
+ className: "absolute left-1.5 top-3 h-4 w-4 cursor-grab text-muted-foreground/50 opacity-0 transition-opacity group-hover/post:opacity-100",
2038
+ "aria-hidden": true
2039
+ }
2040
+ ),
2041
+ onDelete && /* @__PURE__ */ jsx(
2042
+ Button,
2043
+ {
2044
+ variant: "bare",
2045
+ size: "bare",
2046
+ type: "button",
2047
+ "aria-label": t("marketing.content.deletePost"),
2048
+ onClick: (e) => {
2049
+ e.stopPropagation();
2050
+ onDelete();
2051
+ },
2052
+ className: "absolute right-1.5 top-2 rounded p-1 text-muted-foreground/60 opacity-0 transition-opacity hover:bg-destructive/10 hover:text-destructive group-hover/post:opacity-100",
2053
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
2054
+ }
2055
+ ),
2056
+ /* @__PURE__ */ jsx("p", { className: "pr-5 text-sm font-medium text-foreground", children: post.title || t("marketing.content.untitled") }),
2057
+ /* @__PURE__ */ jsxs("div", { className: "mt-2 flex flex-wrap items-center gap-1.5", children: [
2058
+ /* @__PURE__ */ jsx(FormatBadge, { format: post.format }),
2059
+ /* @__PURE__ */ jsx(PostStatusBadge, { status: post.status }),
2060
+ post.scheduledDate && /* @__PURE__ */ jsx("span", { className: "text-xs text-muted-foreground", children: shortDay(/* @__PURE__ */ new Date(`${post.scheduledDate}T00:00:00`)) }),
2061
+ /* @__PURE__ */ jsx(PlatformBadges, { platforms: post.platforms })
2062
+ ] })
2063
+ ]
2064
+ }
2065
+ );
2276
2066
  }
2277
-
2278
- // src/views/content/templates.ts
2279
- var reel = (title) => `# ${title}
2280
-
2281
- **TEMA:** ${title.toUpperCase()}
2282
- **Formato:** Reel \xB7 **Dura\xE7\xE3o:** 45 a 60 segundos
2283
-
2284
- ---
2285
-
2286
- ## TAKE 1 \u2013 GANCHO (0s a 5s)
2287
-
2288
- *Dire\xE7\xE3o de cena: olhando para a c\xE2mera, energia alta.*
2289
-
2290
- **Texto na tela:** \u2026
2291
-
2292
- **Fala:** "\u2026"
2293
-
2294
- ---
2295
-
2296
- ## TAKE 2 \u2013 DESENVOLVIMENTO (5s a 40s)
2297
-
2298
- *Dire\xE7\xE3o de cena: \u2026*
2299
-
2300
- **Texto na tela:** \u2026
2301
-
2302
- **Fala:** "\u2026"
2303
-
2304
- ---
2305
-
2306
- ## TAKE 3 \u2013 CTA (40s a 60s)
2307
-
2308
- **Fala:** "\u2026"
2309
-
2310
- **CTA:** \u2026
2311
- `;
2312
- var staticPost = (title) => `# ${title}
2313
-
2314
- **TEMA:** ${title.toUpperCase()}
2315
- **Formato:** Est\xE1tico
2316
-
2317
- ## Texto da arte
2318
-
2319
- \u2026
2320
-
2321
- ## Legenda
2322
-
2323
- \u2026
2324
-
2325
- **CTA:** \u2026
2326
- `;
2327
- var carousel = (title) => `# ${title}
2328
-
2329
- **TEMA:** ${title.toUpperCase()}
2330
- **Formato:** Carrossel
2331
-
2332
- ## Slide 1 \u2014 Capa (gancho)
2333
-
2334
- \u2026
2335
-
2336
- ## Slide 2
2337
-
2338
- \u2026
2339
-
2340
- ## Slide 3
2341
-
2342
- \u2026
2343
-
2344
- ## \xDAltimo slide \u2014 CTA
2345
-
2346
- \u2026
2347
- `;
2348
- var story = (title) => `# ${title}
2349
-
2350
- **TEMA:** ${title.toUpperCase()}
2351
- **Formato:** Story
2352
-
2353
- ## Frame 1
2354
-
2355
- \u2026
2356
-
2357
- ## Frame 2
2358
-
2359
- \u2026
2360
-
2361
- ## Frame final \u2014 CTA
2362
-
2363
- *Sticker de link / caixa de pergunta / enquete.*
2364
-
2365
- \u2026
2366
- `;
2367
- var video = (title) => `# ${title}
2368
-
2369
- **TEMA:** ${title.toUpperCase()}
2370
- **Formato:** V\xEDdeo longo (YouTube) \xB7 **Dura\xE7\xE3o:** 8 a 12 minutos
2371
-
2372
- ## Gancho (0s a 30s)
2373
-
2374
- \u2026
2375
-
2376
- ## Roteiro por blocos
2377
-
2378
- ### Bloco 1 \u2014 Contexto
2379
-
2380
- \u2026
2381
-
2382
- ### Bloco 2 \u2014 Desenvolvimento
2383
-
2384
- \u2026
2385
-
2386
- ### Bloco 3 \u2014 Fechamento
2387
-
2388
- \u2026
2389
-
2390
- **CTA:** inscreva-se / comente / link na descri\xE7\xE3o
2391
- `;
2392
- var live = (title) => `# ${title}
2393
-
2394
- **TEMA:** ${title.toUpperCase()}
2395
- **Formato:** Live
2396
-
2397
- ## Pauta
2398
-
2399
- 1. Abertura e aquecimento (5 min)
2400
- 2. \u2026
2401
- 3. Perguntas e respostas (15 min)
2402
-
2403
- ## Avisos / CTAs durante a live
2404
-
2405
- \u2026
2406
- `;
2407
- var article = (title) => `# ${title}
2408
-
2409
- **TEMA:** ${title.toUpperCase()}
2410
- **Formato:** Artigo / Newsletter
2411
-
2412
- ## T\xEDtulo de trabalho
2413
-
2414
- \u2026
2415
-
2416
- ## Estrutura
2417
-
2418
- - Abertura (dor / promessa)
2419
- - Desenvolvimento em 3 pontos
2420
- - Conclus\xE3o + CTA
2421
-
2422
- ## Rascunho
2423
-
2424
- \u2026
2425
- `;
2426
- var POST_TEMPLATES = {
2427
- reel,
2428
- static: staticPost,
2429
- carousel,
2430
- story,
2431
- video,
2432
- live,
2433
- article
2434
- };
2435
- function MediaAsset({ post, onChanged }) {
2067
+ function WeekColumn({ week, rangeLabel, posts, onOpenPost, onNewPost, onDeletePost, onDropPost, disabled }) {
2436
2068
  const t = useTranslation();
2437
- const inputRef = React9.useRef(null);
2438
- const [busy, setBusy] = React9.useState(false);
2439
- const isVideo2 = post.mediaUrl ? /\.(mp4|webm|mov)($|\?)/i.test(post.mediaUrl) : false;
2440
- const pick = () => inputRef.current?.click();
2441
- const upload = async (file) => {
2442
- setBusy(true);
2069
+ const [isDropTarget, setIsDropTarget] = React12.useState(false);
2070
+ return /* @__PURE__ */ jsxs(
2071
+ "div",
2072
+ {
2073
+ className: cn(
2074
+ "flex flex-col gap-2 rounded-card border border-border bg-muted/20 p-2.5 transition-colors",
2075
+ isDropTarget && "border-primary bg-primary/5 ring-1 ring-primary/40"
2076
+ ),
2077
+ onDragOver: (e) => {
2078
+ if (!onDropPost || !e.dataTransfer.types.includes(POST_DRAG_MIME)) return;
2079
+ e.preventDefault();
2080
+ e.dataTransfer.dropEffect = "move";
2081
+ setIsDropTarget(true);
2082
+ },
2083
+ onDragLeave: () => setIsDropTarget(false),
2084
+ onDrop: (e) => {
2085
+ if (!onDropPost) return;
2086
+ e.preventDefault();
2087
+ setIsDropTarget(false);
2088
+ const postId = e.dataTransfer.getData(POST_DRAG_MIME);
2089
+ if (postId) onDropPost(postId, week);
2090
+ },
2091
+ children: [
2092
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-baseline justify-between gap-x-2 gap-y-0.5 px-1", children: [
2093
+ /* @__PURE__ */ jsxs("p", { className: "whitespace-nowrap text-xs font-semibold uppercase tracking-wide text-muted-foreground", children: [
2094
+ t("marketing.content.week"),
2095
+ " ",
2096
+ week
2097
+ ] }),
2098
+ /* @__PURE__ */ jsx("p", { className: "whitespace-nowrap text-xs text-muted-foreground/80", children: rangeLabel })
2099
+ ] }),
2100
+ posts.map((post) => /* @__PURE__ */ jsx(
2101
+ PostCard,
2102
+ {
2103
+ post,
2104
+ onOpen: () => onOpenPost(post.id),
2105
+ onDelete: onDeletePost ? () => onDeletePost(post.id) : void 0
2106
+ },
2107
+ post.id
2108
+ )),
2109
+ /* @__PURE__ */ jsxs(
2110
+ Button,
2111
+ {
2112
+ variant: "bare",
2113
+ size: "bare",
2114
+ type: "button",
2115
+ onClick: () => onNewPost(week),
2116
+ disabled,
2117
+ className: "flex items-center justify-center gap-1 rounded-card border border-dashed border-border p-2 text-xs font-medium text-muted-foreground transition-colors hover:bg-muted/40 hover:text-foreground disabled:pointer-events-none disabled:opacity-50",
2118
+ children: [
2119
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
2120
+ t("marketing.content.newPost")
2121
+ ]
2122
+ }
2123
+ )
2124
+ ]
2125
+ }
2126
+ );
2127
+ }
2128
+ function AccountModal({ open, account, onClose }) {
2129
+ const t = useTranslation();
2130
+ const saveAccount = useContentPlannerStore((s) => s.saveAccount);
2131
+ const deleteAccount = useContentPlannerStore((s) => s.deleteAccount);
2132
+ const isEdit = !!account;
2133
+ const [name, setName] = React12.useState("");
2134
+ const [handle, setHandle] = React12.useState("");
2135
+ const [platforms, setPlatforms] = React12.useState(["instagram"]);
2136
+ const [saving, setSaving] = React12.useState(false);
2137
+ const [confirmDelete, setConfirmDelete] = React12.useState(false);
2138
+ React12.useEffect(() => {
2139
+ if (open) {
2140
+ setName(account?.name ?? "");
2141
+ setHandle(account?.handle ?? "");
2142
+ setPlatforms(account?.platforms?.length ? account.platforms : ["instagram"]);
2143
+ setSaving(false);
2144
+ setConfirmDelete(false);
2145
+ }
2146
+ }, [open, account]);
2147
+ const create = async () => {
2148
+ if (!name.trim() || platforms.length === 0 || saving) return;
2149
+ setSaving(true);
2443
2150
  try {
2444
- const url = await uploadPostMedia(post.id, file);
2445
- onChanged(url);
2151
+ await saveAccount({
2152
+ ...isEdit ? { id: account.id } : {},
2153
+ name: name.trim(),
2154
+ handle: handle.trim() || void 0,
2155
+ platforms
2156
+ });
2157
+ onClose();
2446
2158
  } finally {
2447
- setBusy(false);
2159
+ setSaving(false);
2448
2160
  }
2449
2161
  };
2450
- if (!canUploadMedia()) {
2451
- return /* @__PURE__ */ jsx("p", { className: "text-xs text-muted-foreground", children: t("marketing.content.mediaUnavailable") });
2452
- }
2453
- return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2454
- /* @__PURE__ */ jsx(
2455
- "input",
2162
+ const remove = async () => {
2163
+ if (!account || saving) return;
2164
+ setSaving(true);
2165
+ try {
2166
+ await deleteAccount(account.id);
2167
+ onClose();
2168
+ } finally {
2169
+ setSaving(false);
2170
+ }
2171
+ };
2172
+ return /* @__PURE__ */ jsx(Modal, { open, onOpenChange: (o) => {
2173
+ if (!o) onClose();
2174
+ }, children: /* @__PURE__ */ jsxs(ModalContent, { size: "sm", children: [
2175
+ /* @__PURE__ */ jsx(ModalHeader, { children: /* @__PURE__ */ jsx(ModalTitle, { children: isEdit ? t("marketing.content.editAccountTitle") : t("marketing.content.addAccountTitle") }) }),
2176
+ /* @__PURE__ */ jsxs(ModalBody, { className: "space-y-4", children: [
2177
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2178
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.nameLabel") }),
2179
+ /* @__PURE__ */ jsx(
2180
+ Input,
2181
+ {
2182
+ autoFocus: true,
2183
+ value: name,
2184
+ onChange: (e) => setName(e.target.value),
2185
+ placeholder: t("marketing.content.accountNamePlaceholder"),
2186
+ onKeyDown: (e) => {
2187
+ if (e.key === "Enter") void create();
2188
+ }
2189
+ }
2190
+ )
2191
+ ] }),
2192
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2193
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.handleLabel") }),
2194
+ /* @__PURE__ */ jsx(
2195
+ Input,
2196
+ {
2197
+ value: handle,
2198
+ onChange: (e) => setHandle(e.target.value),
2199
+ placeholder: t("marketing.content.handlePlaceholder")
2200
+ }
2201
+ )
2202
+ ] }),
2203
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2204
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.platformsLabel") }),
2205
+ /* @__PURE__ */ jsx(PlatformPicker, { value: platforms, onChange: setPlatforms })
2206
+ ] })
2207
+ ] }),
2208
+ /* @__PURE__ */ jsx(ModalFooter, { children: confirmDelete && isEdit ? /* @__PURE__ */ jsxs("div", { className: "flex w-full items-center gap-3 rounded-card border border-destructive/30 bg-destructive/5 p-3", children: [
2209
+ /* @__PURE__ */ jsx("p", { className: "flex-1 text-xs text-destructive", children: t("marketing.content.deleteAccountConfirm") }),
2210
+ /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmDelete(false), disabled: saving, children: t("marketing.content.cancel") }),
2211
+ /* @__PURE__ */ jsx(Button, { variant: "destructive", size: "sm", onClick: () => void remove(), disabled: saving, children: t("marketing.content.deleteAccount") })
2212
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
2213
+ isEdit && /* @__PURE__ */ jsxs(
2214
+ Button,
2215
+ {
2216
+ variant: "outline",
2217
+ onClick: () => setConfirmDelete(true),
2218
+ disabled: saving,
2219
+ className: "mr-auto text-destructive hover:text-destructive",
2220
+ children: [
2221
+ /* @__PURE__ */ jsx(Trash2, { className: "mr-1.5 h-4 w-4" }),
2222
+ t("marketing.content.deleteAccount")
2223
+ ]
2224
+ }
2225
+ ),
2226
+ /* @__PURE__ */ jsx(Button, { variant: "outline", onClick: onClose, disabled: saving, children: t("marketing.content.cancel") }),
2227
+ /* @__PURE__ */ jsx(Button, { onClick: () => void create(), disabled: saving || !name.trim() || platforms.length === 0, children: isEdit ? t("marketing.content.save") : t("marketing.content.createAccount") })
2228
+ ] }) })
2229
+ ] }) });
2230
+ }
2231
+ function AccountSelector() {
2232
+ const t = useTranslation();
2233
+ const loaded = useContentPlannerStore((s) => s.loaded);
2234
+ const accounts = useContentPlannerStore((s) => s.accounts);
2235
+ const activeAccountId = useContentPlannerStore((s) => s.activeAccountId);
2236
+ const setAccount = useContentPlannerStore((s) => s.setAccount);
2237
+ const [open, setOpen] = React12.useState(false);
2238
+ const [modal, setModal] = React12.useState(null);
2239
+ const activeAccount = accounts.find((a) => a.id === activeAccountId) ?? null;
2240
+ if (!loaded) {
2241
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2242
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-64" }),
2243
+ /* @__PURE__ */ jsx(Skeleton, { className: "h-9 w-9" })
2244
+ ] });
2245
+ }
2246
+ const openSettings = (account) => {
2247
+ setOpen(false);
2248
+ setModal({ mode: "edit", account });
2249
+ };
2250
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
2251
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2252
+ /* @__PURE__ */ jsxs(
2253
+ Select,
2254
+ {
2255
+ open,
2256
+ onOpenChange: setOpen,
2257
+ value: activeAccountId ?? void 0,
2258
+ onValueChange: (id) => {
2259
+ if (id === ADD_ACCOUNT_VALUE) {
2260
+ setModal({ mode: "create" });
2261
+ return;
2262
+ }
2263
+ void setAccount(id);
2264
+ },
2265
+ children: [
2266
+ /* @__PURE__ */ jsx(SelectTrigger, { className: "w-64", children: /* @__PURE__ */ jsx(SelectValue, { placeholder: t("marketing.content.account") }) }),
2267
+ /* @__PURE__ */ jsxs(SelectContent, { children: [
2268
+ accounts.length === 0 ? /* @__PURE__ */ jsx(SelectItem, { value: "__none__", disabled: true, children: t("marketing.content.noAccountsAvailable") }) : accounts.map((acc) => /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2269
+ /* @__PURE__ */ jsx(SelectItem, { value: acc.id, className: "pr-9", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
2270
+ acc.name,
2271
+ /* @__PURE__ */ jsx(PlatformBadges, { platforms: acc.platforms })
2272
+ ] }) }),
2273
+ /* @__PURE__ */ jsx(
2274
+ Button,
2275
+ {
2276
+ variant: "bare",
2277
+ size: "bare",
2278
+ type: "button",
2279
+ "aria-label": t("marketing.content.accountSettings"),
2280
+ onPointerDown: (e) => {
2281
+ e.stopPropagation();
2282
+ e.preventDefault();
2283
+ },
2284
+ onPointerUp: (e) => {
2285
+ e.stopPropagation();
2286
+ e.preventDefault();
2287
+ },
2288
+ onClick: (e) => {
2289
+ e.stopPropagation();
2290
+ openSettings(acc);
2291
+ },
2292
+ className: "absolute right-2 top-1/2 -translate-y-1/2 rounded p-1 text-muted-foreground/70 transition-colors hover:bg-muted/60 hover:text-foreground",
2293
+ children: /* @__PURE__ */ jsx(Settings2, { className: "h-3.5 w-3.5" })
2294
+ }
2295
+ )
2296
+ ] }, acc.id)),
2297
+ /* @__PURE__ */ jsx(SelectSeparator, {}),
2298
+ /* @__PURE__ */ jsx(SelectItem, { value: ADD_ACCOUNT_VALUE, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
2299
+ /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" }),
2300
+ t("marketing.content.addAccount")
2301
+ ] }) })
2302
+ ] })
2303
+ ]
2304
+ }
2305
+ ),
2306
+ activeAccount && /* @__PURE__ */ jsx(
2307
+ Button,
2308
+ {
2309
+ variant: "ghost",
2310
+ size: "sm",
2311
+ className: "h-9 w-9 p-0 text-muted-foreground",
2312
+ "aria-label": t("marketing.content.accountSettings"),
2313
+ onClick: () => setModal({ mode: "edit", account: activeAccount }),
2314
+ children: /* @__PURE__ */ jsx(Settings2, { className: "h-4 w-4" })
2315
+ }
2316
+ )
2317
+ ] }),
2318
+ /* @__PURE__ */ jsx(
2319
+ AccountModal,
2320
+ {
2321
+ open: !!modal,
2322
+ account: modal?.mode === "edit" ? modal.account : null,
2323
+ onClose: () => setModal(null)
2324
+ }
2325
+ )
2326
+ ] });
2327
+ }
2328
+ function ViewSwitcher({ mode, onChange }) {
2329
+ const t = useTranslation();
2330
+ return /* @__PURE__ */ jsx(
2331
+ SegmentedControl,
2332
+ {
2333
+ size: "md",
2334
+ "aria-label": t("marketing.content.viewBoard"),
2335
+ value: mode,
2336
+ onChange,
2337
+ options: [
2338
+ { value: "board", label: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
2339
+ /* @__PURE__ */ jsx(Columns3, { className: "h-3.5 w-3.5" }),
2340
+ t("marketing.content.viewBoard")
2341
+ ] }) },
2342
+ { value: "calendar", label: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
2343
+ /* @__PURE__ */ jsx(CalendarDays, { className: "h-3.5 w-3.5" }),
2344
+ t("marketing.content.viewCalendar")
2345
+ ] }) },
2346
+ { value: "list", label: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1.5", children: [
2347
+ /* @__PURE__ */ jsx(List, { className: "h-3.5 w-3.5" }),
2348
+ t("marketing.content.viewList")
2349
+ ] }) }
2350
+ ]
2351
+ }
2352
+ );
2353
+ }
2354
+ function shortDay(d) {
2355
+ return d.toLocaleDateString(getCurrentLocale(), { day: "numeric", month: "short" });
2356
+ }
2357
+ function buildBoardWeeks(startDate, weeksCount) {
2358
+ return Array.from({ length: weeksCount }, (_, i) => {
2359
+ const start = new Date(startDate);
2360
+ start.setDate(start.getDate() + i * 7);
2361
+ const end = new Date(start);
2362
+ end.setDate(start.getDate() + 6);
2363
+ return {
2364
+ week: i + 1,
2365
+ rangeLabel: `${shortDay(start)} \u2013 ${shortDay(end)}`,
2366
+ monthLabel: start.toLocaleDateString(getCurrentLocale(), { month: "long", year: "numeric" })
2367
+ };
2368
+ });
2369
+ }
2370
+ function BoardView({ startDate, weeksCount, postsByWeek, onOpenPost, onNewPost, onDeletePost, onDropPost, disabled }) {
2371
+ const months = [];
2372
+ for (const bw of buildBoardWeeks(startDate, weeksCount)) {
2373
+ const current = months[months.length - 1];
2374
+ if (current && current.label === bw.monthLabel) current.weeks.push(bw);
2375
+ else months.push({ label: bw.monthLabel, weeks: [bw] });
2376
+ }
2377
+ return /* @__PURE__ */ jsx("div", { className: "space-y-8", children: months.map(({ label, weeks }) => /* @__PURE__ */ jsxs("section", { className: "space-y-3", children: [
2378
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold uppercase tracking-wide text-muted-foreground", children: label }),
2379
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: weeks.map(({ week, rangeLabel }) => /* @__PURE__ */ jsx(
2380
+ WeekColumn,
2381
+ {
2382
+ week,
2383
+ rangeLabel,
2384
+ posts: postsByWeek.get(week) ?? [],
2385
+ onOpenPost,
2386
+ onNewPost,
2387
+ onDeletePost,
2388
+ onDropPost,
2389
+ disabled
2390
+ },
2391
+ week
2392
+ )) })
2393
+ ] }, label)) });
2394
+ }
2395
+ function toISODate(d) {
2396
+ return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
2397
+ }
2398
+ function mondayOfCurrentWeek() {
2399
+ const now = /* @__PURE__ */ new Date();
2400
+ const monday = new Date(now.getFullYear(), now.getMonth(), now.getDate());
2401
+ monday.setDate(monday.getDate() - (monday.getDay() + 6) % 7);
2402
+ return monday;
2403
+ }
2404
+ function CalendarView({ posts, onOpenPost, onNewPostAt, disabled }) {
2405
+ const t = useTranslation();
2406
+ const [cursor, setCursor] = React12.useState(() => {
2407
+ const first = posts.find((p) => p.scheduledDate)?.scheduledDate;
2408
+ const base = first ? /* @__PURE__ */ new Date(`${first}T00:00:00`) : /* @__PURE__ */ new Date();
2409
+ return new Date(base.getFullYear(), base.getMonth(), 1);
2410
+ });
2411
+ const [expandedDay, setExpandedDay] = React12.useState(null);
2412
+ const MAX_VISIBLE = 3;
2413
+ const postsByDate = React12.useMemo(() => {
2414
+ const map = /* @__PURE__ */ new Map();
2415
+ for (const post of posts) {
2416
+ if (!post.scheduledDate) continue;
2417
+ const list = map.get(post.scheduledDate) ?? [];
2418
+ list.push(post);
2419
+ map.set(post.scheduledDate, list);
2420
+ }
2421
+ return map;
2422
+ }, [posts]);
2423
+ const unscheduled = posts.filter((p) => !p.scheduledDate);
2424
+ const days = React12.useMemo(() => {
2425
+ const firstOfMonth = new Date(cursor.getFullYear(), cursor.getMonth(), 1);
2426
+ const start = new Date(firstOfMonth);
2427
+ start.setDate(start.getDate() - (firstOfMonth.getDay() + 6) % 7);
2428
+ const cells = [];
2429
+ for (let i = 0; i < 42; i++) {
2430
+ const d = new Date(start);
2431
+ d.setDate(start.getDate() + i);
2432
+ cells.push(d);
2433
+ }
2434
+ while (cells.length > 7 && cells[cells.length - 7].getMonth() !== cursor.getMonth()) {
2435
+ cells.splice(cells.length - 7, 7);
2436
+ }
2437
+ return cells;
2438
+ }, [cursor]);
2439
+ const monthLabel = cursor.toLocaleDateString(getCurrentLocale(), { month: "long", year: "numeric" });
2440
+ const weekdayLabels = days.slice(0, 7).map(
2441
+ (d) => d.toLocaleDateString(getCurrentLocale(), { weekday: "short" })
2442
+ );
2443
+ const todayISO = toISODate(/* @__PURE__ */ new Date());
2444
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
2445
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2446
+ /* @__PURE__ */ jsx("h3", { className: "text-sm font-semibold capitalize text-foreground", children: monthLabel }),
2447
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
2448
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() - 1, 1)), children: /* @__PURE__ */ jsx(ChevronLeft, { className: "h-4 w-4" }) }),
2449
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "sm", onClick: () => setCursor(new Date(cursor.getFullYear(), cursor.getMonth() + 1, 1)), children: /* @__PURE__ */ jsx(ChevronRight, { className: "h-4 w-4" }) })
2450
+ ] })
2451
+ ] }),
2452
+ unscheduled.length > 0 && /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 rounded-card border border-dashed border-border bg-muted/20 p-2.5", children: [
2453
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("marketing.content.unscheduled") }),
2454
+ unscheduled.map((post) => /* @__PURE__ */ jsxs(
2455
+ Button,
2456
+ {
2457
+ variant: "bare",
2458
+ size: "bare",
2459
+ type: "button",
2460
+ onClick: () => onOpenPost(post.id),
2461
+ className: "inline-flex items-center gap-1.5 rounded-full border border-border bg-card px-2.5 py-1 text-xs font-medium text-foreground transition-colors hover:bg-muted/40",
2462
+ children: [
2463
+ post.title || t("marketing.content.untitled"),
2464
+ /* @__PURE__ */ jsx(FormatBadge, { format: post.format })
2465
+ ]
2466
+ },
2467
+ post.id
2468
+ ))
2469
+ ] }),
2470
+ /* @__PURE__ */ jsxs("div", { className: "overflow-hidden rounded-card border border-border", children: [
2471
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7 border-b border-border bg-muted/30", children: weekdayLabels.map((label, i) => /* @__PURE__ */ jsx("div", { className: "px-2 py-1.5 text-center text-xs font-medium capitalize text-muted-foreground", children: label }, i)) }),
2472
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-7", children: days.map((day, i) => {
2473
+ const iso = toISODate(day);
2474
+ const inMonth = day.getMonth() === cursor.getMonth();
2475
+ const dayPosts = postsByDate.get(iso) ?? [];
2476
+ return /* @__PURE__ */ jsxs(
2477
+ "div",
2478
+ {
2479
+ className: cn(
2480
+ "group min-h-24 border-b border-r border-border p-1.5 [&:nth-child(7n)]:border-r-0",
2481
+ !inMonth && "bg-muted/20"
2482
+ ),
2483
+ children: [
2484
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2485
+ /* @__PURE__ */ jsx(
2486
+ "span",
2487
+ {
2488
+ className: cn(
2489
+ "text-xs",
2490
+ inMonth ? "text-foreground" : "text-muted-foreground/60",
2491
+ iso === todayISO && "flex h-5 w-5 items-center justify-center rounded-full bg-primary font-semibold text-primary-foreground"
2492
+ ),
2493
+ children: day.getDate()
2494
+ }
2495
+ ),
2496
+ !disabled && /* @__PURE__ */ jsx(
2497
+ Button,
2498
+ {
2499
+ variant: "bare",
2500
+ size: "bare",
2501
+ type: "button",
2502
+ onClick: () => onNewPostAt(iso),
2503
+ className: "rounded p-0.5 text-muted-foreground opacity-0 transition-opacity hover:bg-muted/60 hover:text-foreground group-hover:opacity-100",
2504
+ "aria-label": t("marketing.content.newPost"),
2505
+ children: /* @__PURE__ */ jsx(Plus, { className: "h-3.5 w-3.5" })
2506
+ }
2507
+ )
2508
+ ] }),
2509
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 space-y-1", children: [
2510
+ (expandedDay === iso ? dayPosts : dayPosts.slice(0, MAX_VISIBLE)).map((post) => /* @__PURE__ */ jsx(
2511
+ Button,
2512
+ {
2513
+ variant: "bare",
2514
+ size: "bare",
2515
+ type: "button",
2516
+ onClick: () => onOpenPost(post.id),
2517
+ className: "block w-full truncate rounded border border-border bg-card px-1.5 py-1 text-left text-xs text-foreground transition-colors hover:bg-muted/40 font-normal",
2518
+ children: post.title || t("marketing.content.untitled")
2519
+ },
2520
+ post.id
2521
+ )),
2522
+ dayPosts.length > MAX_VISIBLE && /* @__PURE__ */ jsx(
2523
+ Button,
2524
+ {
2525
+ variant: "bare",
2526
+ size: "bare",
2527
+ type: "button",
2528
+ onClick: () => setExpandedDay(expandedDay === iso ? null : iso),
2529
+ className: "block w-full px-1.5 py-0.5 text-left text-xs font-medium text-muted-foreground transition-colors hover:text-foreground",
2530
+ children: expandedDay === iso ? t("marketing.content.showLess") : `+${dayPosts.length - MAX_VISIBLE}`
2531
+ }
2532
+ )
2533
+ ] })
2534
+ ]
2535
+ },
2536
+ i
2537
+ );
2538
+ }) })
2539
+ ] })
2540
+ ] });
2541
+ }
2542
+ function ListView({ posts, onOpenPost }) {
2543
+ const t = useTranslation();
2544
+ const columns = React12.useMemo(() => {
2545
+ const head = (key) => () => /* @__PURE__ */ jsx("span", { className: "text-xs uppercase tracking-wide", children: t(key) });
2546
+ return [
2547
+ { id: "week", header: head("marketing.content.listWeek"), cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: row.original.weekNumber }) },
2548
+ {
2549
+ id: "title",
2550
+ header: head("marketing.content.listTitle"),
2551
+ cell: ({ row: { original: post } }) => /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2 font-medium text-foreground", children: [
2552
+ post.title || t("marketing.content.untitled"),
2553
+ /* @__PURE__ */ jsx(PlatformBadges, { platforms: post.platforms })
2554
+ ] })
2555
+ },
2556
+ { id: "format", header: head("marketing.content.listFormat"), cell: ({ row }) => /* @__PURE__ */ jsx(FormatBadge, { format: row.original.format }) },
2557
+ { id: "status", header: head("marketing.content.listStatus"), cell: ({ row }) => /* @__PURE__ */ jsx(PostStatusBadge, { status: row.original.status }) },
2558
+ { id: "date", header: head("marketing.content.listDate"), cell: ({ row }) => /* @__PURE__ */ jsx("span", { className: "text-muted-foreground", children: row.original.scheduledDate ?? "\u2014" }) }
2559
+ ];
2560
+ }, [t]);
2561
+ if (posts.length === 0) {
2562
+ return /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("marketing.content.noPosts") });
2563
+ }
2564
+ return /* @__PURE__ */ jsx("div", { className: "overflow-hidden rounded-card border border-border", children: /* @__PURE__ */ jsx(DataTable, { variant: "bare", columns, data: posts, onRowClick: (post) => onOpenPost(post.id) }) });
2565
+ }
2566
+ function ContentView({ initialPostId = null }) {
2567
+ const t = useTranslation();
2568
+ const [openPostId, setOpenPostId] = React12.useState(initialPostId);
2569
+ const onOpenPost = React12.useCallback((id) => {
2570
+ setOpenPostId(id);
2571
+ window.history.replaceState(window.history.state, "", id ? `#/marketing/content/post/${id}` : "#/marketing/content");
2572
+ }, []);
2573
+ const loaded = useContentPlannerStore((s) => s.loaded);
2574
+ const loading = useContentPlannerStore((s) => s.loading);
2575
+ const accounts = useContentPlannerStore((s) => s.accounts);
2576
+ const activeAccountId = useContentPlannerStore((s) => s.activeAccountId);
2577
+ const plan = useContentPlannerStore((s) => s.activePlan);
2578
+ const posts = useContentPlannerStore((s) => s.posts);
2579
+ const load = useContentPlannerStore((s) => s.load);
2580
+ const savePlan = useContentPlannerStore((s) => s.savePlan);
2581
+ const savePost = useContentPlannerStore((s) => s.savePost);
2582
+ const deletePost = useContentPlannerStore((s) => s.deletePost);
2583
+ const viewMode = useContentPlannerStore((s) => s.viewMode);
2584
+ const setViewMode = useContentPlannerStore((s) => s.setViewMode);
2585
+ const loadedTenantId = useContentPlannerStore((s) => s.loadedTenantId);
2586
+ const guardContentPosts = useLimitGuard("content_posts_month");
2587
+ const [briefOpen, setBriefOpen] = React12.useState(false);
2588
+ const [pendingDeleteId, setPendingDeleteId] = React12.useState(null);
2589
+ const tenantId = useOrganizationStore((state) => state.currentOrg?.id);
2590
+ React12.useEffect(() => {
2591
+ if (!loading && (!loaded || tenantId && tenantId !== loadedTenantId)) void load();
2592
+ }, [tenantId, loadedTenantId, loaded, loading, load]);
2593
+ const postsByWeek = React12.useMemo(() => {
2594
+ const map = /* @__PURE__ */ new Map();
2595
+ for (const post of posts) {
2596
+ const list = map.get(post.weekNumber) ?? [];
2597
+ list.push(post);
2598
+ map.set(post.weekNumber, list);
2599
+ }
2600
+ return map;
2601
+ }, [posts]);
2602
+ const ensurePlan = async () => {
2603
+ if (plan) return plan;
2604
+ if (!activeAccountId) return null;
2605
+ return savePlan({
2606
+ accountId: activeAccountId,
2607
+ name: t("marketing.content.defaultPlanName"),
2608
+ status: "active",
2609
+ weeksCount: DEFAULT_WEEKS,
2610
+ startDate: toISODate(mondayOfCurrentWeek())
2611
+ });
2612
+ };
2613
+ const createPost = async (week) => {
2614
+ if (await guardContentPosts() === "blocked") return;
2615
+ const targetPlan = await ensurePlan();
2616
+ if (!targetPlan) return;
2617
+ const start = targetPlan.startDate ? /* @__PURE__ */ new Date(`${targetPlan.startDate}T00:00:00`) : mondayOfCurrentWeek();
2618
+ start.setDate(start.getDate() + (week - 1) * 7);
2619
+ const post = await savePost({
2620
+ planId: targetPlan.id,
2621
+ weekNumber: week,
2622
+ title: "",
2623
+ format: "reel",
2624
+ status: "idea",
2625
+ scheduledDate: toISODate(start)
2626
+ });
2627
+ invalidateLimit("content_posts_month");
2628
+ onOpenPost(post.id);
2629
+ };
2630
+ const movePost = async (postId, week) => {
2631
+ const post = posts.find((p) => p.id === postId);
2632
+ if (!post || post.weekNumber === week) return;
2633
+ await savePost({ id: postId, weekNumber: week });
2634
+ };
2635
+ const createPostAt = async (isoDate) => {
2636
+ if (await guardContentPosts() === "blocked") return;
2637
+ const targetPlan = await ensurePlan();
2638
+ if (!targetPlan) return;
2639
+ let week = 1;
2640
+ if (targetPlan.startDate) {
2641
+ const start = /* @__PURE__ */ new Date(`${targetPlan.startDate}T00:00:00`);
2642
+ const day = /* @__PURE__ */ new Date(`${isoDate}T00:00:00`);
2643
+ const diff = Math.floor((day.getTime() - start.getTime()) / (7 * 24 * 3600 * 1e3)) + 1;
2644
+ week = Math.min(Math.max(diff, 1), targetPlan.weeksCount);
2645
+ }
2646
+ const post = await savePost({
2647
+ planId: targetPlan.id,
2648
+ weekNumber: week,
2649
+ title: "",
2650
+ format: "reel",
2651
+ status: "idea",
2652
+ scheduledDate: isoDate
2653
+ });
2654
+ invalidateLimit("content_posts_month");
2655
+ onOpenPost(post.id);
2656
+ };
2657
+ const busy = !loaded || loading;
2658
+ const hasAccount = accounts.length > 0;
2659
+ const weeksCount = plan?.weeksCount ?? DEFAULT_WEEKS;
2660
+ const boardStart = plan?.startDate ? /* @__PURE__ */ new Date(`${plan.startDate}T00:00:00`) : mondayOfCurrentWeek();
2661
+ return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl space-y-6", children: [
2662
+ plan && /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsxs(Button, { onClick: () => setBriefOpen(true), children: [
2663
+ /* @__PURE__ */ jsx(NotebookPen, { className: "mr-1.5 h-4 w-4" }),
2664
+ t("marketing.content.brief")
2665
+ ] }) }),
2666
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-start justify-between gap-3", children: [
2667
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-3", children: [
2668
+ /* @__PURE__ */ jsx(AccountSelector, {}),
2669
+ plan && /* @__PURE__ */ jsx("span", { className: "text-sm text-muted-foreground", children: plan.name })
2670
+ ] }),
2671
+ /* @__PURE__ */ jsx("div", { className: "flex items-center gap-2", children: /* @__PURE__ */ jsx(ViewSwitcher, { mode: viewMode, onChange: setViewMode }) })
2672
+ ] }),
2673
+ !busy && !hasAccount && /* @__PURE__ */ jsx("p", { className: "text-sm text-muted-foreground", children: t("marketing.content.noAccountsHint") }),
2674
+ busy && viewMode === "board" && /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-2 lg:grid-cols-4", children: Array.from({ length: 8 }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-44" }, i)) }),
2675
+ busy && viewMode === "calendar" && /* @__PURE__ */ jsx(Skeleton, { className: "h-96 w-full" }),
2676
+ busy && viewMode === "list" && /* @__PURE__ */ jsx("div", { className: "space-y-2", children: Array.from({ length: 6 }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }, i)) }),
2677
+ !busy && viewMode === "board" && /* @__PURE__ */ jsx(
2678
+ BoardView,
2456
2679
  {
2457
- ref: inputRef,
2458
- type: "file",
2459
- accept: "image/*,video/*",
2460
- className: "hidden",
2461
- onChange: (e) => {
2462
- const file = e.target.files?.[0];
2463
- if (file) void upload(file);
2464
- e.target.value = "";
2465
- }
2680
+ startDate: boardStart,
2681
+ weeksCount,
2682
+ postsByWeek,
2683
+ onOpenPost,
2684
+ onNewPost: (w) => void createPost(w),
2685
+ onDeletePost: setPendingDeleteId,
2686
+ onDropPost: (id, w) => void movePost(id, w),
2687
+ disabled: !hasAccount
2466
2688
  }
2467
2689
  ),
2468
- post.mediaUrl ? /* @__PURE__ */ jsxs("div", { className: "relative overflow-hidden rounded-card border border-border bg-muted/20", children: [
2469
- isVideo2 ? /* @__PURE__ */ jsx("video", { src: post.mediaUrl, controls: true, className: "max-h-72 w-full object-contain" }) : /* @__PURE__ */ jsx("img", { src: post.mediaUrl, alt: "", className: "max-h-72 w-full object-contain" }),
2470
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2 border-t border-border p-2", children: [
2471
- /* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", onClick: pick, disabled: busy, children: [
2472
- busy ? /* @__PURE__ */ jsx(Loader2, { className: "mr-1.5 h-3.5 w-3.5 animate-spin" }) : /* @__PURE__ */ jsx(ImagePlus, { className: "mr-1.5 h-3.5 w-3.5" }),
2473
- t("marketing.content.mediaReplace")
2474
- ] }),
2475
- /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: () => onChanged(null), disabled: busy, children: [
2476
- /* @__PURE__ */ jsx(X, { className: "mr-1 h-3.5 w-3.5" }),
2477
- t("marketing.content.mediaRemove")
2478
- ] })
2479
- ] })
2480
- ] }) : /* @__PURE__ */ jsxs(
2481
- "button",
2690
+ !busy && viewMode === "calendar" && /* @__PURE__ */ jsx(
2691
+ CalendarView,
2482
2692
  {
2483
- type: "button",
2484
- onClick: pick,
2485
- disabled: busy,
2486
- className: "flex w-full flex-col items-center justify-center gap-2 rounded-card border border-dashed border-border p-6 text-muted-foreground transition-colors hover:bg-muted/30 hover:text-foreground",
2487
- children: [
2488
- busy ? /* @__PURE__ */ jsx(Loader2, { className: "h-5 w-5 animate-spin" }) : /* @__PURE__ */ jsx(ImagePlus, { className: "h-5 w-5" }),
2489
- /* @__PURE__ */ jsx("span", { className: "text-sm font-medium", children: t("marketing.content.mediaUpload") }),
2490
- /* @__PURE__ */ jsx("span", { className: "text-xs", children: t("marketing.content.mediaHint") })
2491
- ]
2693
+ posts,
2694
+ onOpenPost,
2695
+ onNewPostAt: (iso) => void createPostAt(iso),
2696
+ disabled: !hasAccount
2697
+ }
2698
+ ),
2699
+ !busy && viewMode === "list" && /* @__PURE__ */ jsx(ListView, { posts, onOpenPost }),
2700
+ /* @__PURE__ */ jsx(PlanBriefSheet, { open: briefOpen, onClose: () => setBriefOpen(false) }),
2701
+ /* @__PURE__ */ jsx(PostModal, { postId: openPostId, onClose: () => onOpenPost(null) }),
2702
+ /* @__PURE__ */ jsx(
2703
+ ConfirmDialog,
2704
+ {
2705
+ open: !!pendingDeleteId,
2706
+ variant: "destructive",
2707
+ title: t("marketing.content.deletePost"),
2708
+ description: t("marketing.content.deletePostConfirm"),
2709
+ confirmLabel: t("marketing.content.deletePost"),
2710
+ cancelLabel: t("marketing.content.cancel"),
2711
+ onCancel: () => setPendingDeleteId(null),
2712
+ onConfirm: () => {
2713
+ const id = pendingDeleteId;
2714
+ setPendingDeleteId(null);
2715
+ if (id) void deletePost(id);
2716
+ }
2492
2717
  }
2493
2718
  )
2494
2719
  ] });
2495
2720
  }
2496
- function toDraft2(post) {
2497
- return {
2498
- title: post.title,
2499
- format: post.format,
2500
- status: post.status,
2501
- scheduledDate: post.scheduledDate ?? "",
2502
- platforms: post.platforms,
2503
- hook: post.hook ?? "",
2504
- cta: post.cta ?? "",
2505
- contentMd: post.contentMd
2506
- };
2507
- }
2508
- function PostPage({ postId, onBack }) {
2509
- const t = useTranslation();
2510
- const provider2 = useContentPlannerProvider();
2511
- const storePost = useContentPlannerStore((s) => s.posts.find((p) => p.id === postId));
2512
- const savePost = useContentPlannerStore((s) => s.savePost);
2513
- const deletePost = useContentPlannerStore((s) => s.deletePost);
2514
- const accounts = useContentPlannerStore((s) => s.accounts);
2515
- const activeAccountId = useContentPlannerStore((s) => s.activeAccountId);
2516
- const [post, setPost] = React9.useState(storePost ?? null);
2517
- const [draft, setDraft] = React9.useState(storePost ? toDraft2(storePost) : null);
2518
- const [saving, setSaving] = React9.useState(false);
2519
- const [confirmDelete, setConfirmDelete] = React9.useState(false);
2520
- React9.useEffect(() => {
2521
- if (post?.id === postId) return;
2522
- if (storePost) {
2523
- setPost(storePost);
2524
- setDraft(toDraft2(storePost));
2525
- return;
2526
- }
2527
- void provider2.getPost(postId).then((fetched) => {
2528
- if (fetched) {
2529
- setPost(fetched);
2530
- setDraft(toDraft2(fetched));
2531
- }
2532
- });
2533
- }, [postId, storePost, post?.id, provider2]);
2534
- if (!post || !draft) {
2535
- return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-3xl space-y-5", children: [
2536
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2537
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-56" }),
2538
- /* @__PURE__ */ jsx(Skeleton, { className: "h-8 w-40" })
2539
- ] }),
2540
- /* @__PURE__ */ jsx(Skeleton, { className: "h-10 w-full" }),
2541
- /* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: Array.from({ length: 3 }, (_, i) => /* @__PURE__ */ jsx(Skeleton, { className: "h-14" }, i)) }),
2542
- /* @__PURE__ */ jsx(Skeleton, { className: "h-64 w-full" })
2543
- ] });
2544
- }
2545
- const dirty = draft.title !== post.title || draft.format !== post.format || draft.status !== post.status || draft.scheduledDate !== (post.scheduledDate ?? "") || draft.platforms.join(",") !== post.platforms.join(",") || draft.hook !== (post.hook ?? "") || draft.cta !== (post.cta ?? "") || draft.contentMd !== post.contentMd;
2546
- const patch = (partial) => setDraft((d) => d ? { ...d, ...partial } : d);
2547
- const save = async () => {
2548
- setSaving(true);
2549
- try {
2550
- const updated = await savePost({
2551
- id: post.id,
2552
- title: draft.title,
2553
- format: draft.format,
2554
- status: draft.status,
2555
- scheduledDate: draft.scheduledDate || null,
2556
- platforms: draft.platforms,
2557
- hook: draft.hook,
2558
- cta: draft.cta,
2559
- contentMd: draft.contentMd
2560
- });
2561
- setPost(updated);
2562
- setDraft(toDraft2(updated));
2563
- } finally {
2564
- setSaving(false);
2565
- }
2566
- };
2567
- const insertTemplate = () => {
2568
- patch({ contentMd: POST_TEMPLATES[draft.format](draft.title || t("marketing.content.untitled")) });
2569
- };
2570
- const isStatic = draft.format === "static";
2571
- const account = accounts.find((a) => a.id === activeAccountId) ?? null;
2572
- const now = /* @__PURE__ */ new Date();
2573
- const todayISO = `${now.getFullYear()}-${String(now.getMonth() + 1).padStart(2, "0")}-${String(now.getDate()).padStart(2, "0")}`;
2574
- const isReleaseDay = !!post.scheduledDate && post.scheduledDate === todayISO;
2575
- const handleMediaChanged = (mediaUrl) => {
2576
- void savePost({ id: post.id, mediaUrl }).then((updated) => {
2577
- setPost(updated);
2578
- setDraft((d) => d ? { ...d } : d);
2579
- });
2580
- };
2581
- return /* @__PURE__ */ jsx(
2582
- ContentSplit,
2583
- {
2584
- className: "mx-auto max-w-5xl",
2585
- aside: /* @__PURE__ */ jsx(RecordingChecklist, { post }),
2586
- asideTitle: t("marketing.content.checklistTitle"),
2587
- asideDescription: t("marketing.content.checklistDescription"),
2588
- collapsible: true,
2589
- defaultCollapsed: !isReleaseDay,
2590
- children: /* @__PURE__ */ jsxs("div", { className: "space-y-5", children: [
2591
- /* @__PURE__ */ jsx(
2592
- SubpageHeader,
2593
- {
2594
- title: draft.title || t("marketing.content.untitled"),
2595
- parentLabel: t("marketing.content.title"),
2596
- onBack,
2597
- actions: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2598
- /* @__PURE__ */ jsx(ContentSplitTrigger, { label: t("marketing.content.checklistOpen") }),
2599
- /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: () => setConfirmDelete(true), children: t("marketing.content.deletePost") }),
2600
- /* @__PURE__ */ jsx(Button, { size: "sm", onClick: () => void save(), disabled: !dirty || saving, children: t("marketing.content.save") })
2601
- ] })
2602
- }
2603
- ),
2604
- /* @__PURE__ */ jsx(
2605
- Input,
2606
- {
2607
- value: draft.title,
2608
- onChange: (e) => patch({ title: e.target.value }),
2609
- placeholder: t("marketing.content.titlePlaceholder"),
2610
- className: "text-base font-medium"
2611
- }
2612
- ),
2613
- /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-3", children: [
2614
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2615
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.formatLabel") }),
2616
- /* @__PURE__ */ jsxs(Select, { value: draft.format, onValueChange: (v) => patch({ format: v }), children: [
2617
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
2618
- /* @__PURE__ */ jsx(SelectContent, { children: POST_FORMATS.map((f) => /* @__PURE__ */ jsx(SelectItem, { value: f, children: t(`marketing.content.format.${f}`) }, f)) })
2619
- ] })
2620
- ] }),
2621
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2622
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.statusLabel") }),
2623
- /* @__PURE__ */ jsxs(Select, { value: draft.status, onValueChange: (v) => patch({ status: v }), children: [
2624
- /* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
2625
- /* @__PURE__ */ jsx(SelectContent, { children: POST_STATUS_ORDER.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s, children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-2", children: [
2626
- /* @__PURE__ */ jsx(StatusDot, { status: s }),
2627
- t(`marketing.content.status.${s}`)
2628
- ] }) }, s)) })
2629
- ] })
2630
- ] }),
2631
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2632
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.scheduledDate") }),
2633
- /* @__PURE__ */ jsx(
2634
- Input,
2635
- {
2636
- type: "date",
2637
- value: draft.scheduledDate,
2638
- onChange: (e) => patch({ scheduledDate: e.target.value })
2639
- }
2640
- )
2641
- ] }),
2642
- /* @__PURE__ */ jsxs("div", { className: "space-y-1 sm:col-span-2", children: [
2643
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.hook") }),
2644
- /* @__PURE__ */ jsx(
2645
- Input,
2646
- {
2647
- value: draft.hook,
2648
- onChange: (e) => patch({ hook: e.target.value }),
2649
- placeholder: t("marketing.content.hookPlaceholder")
2650
- }
2651
- )
2652
- ] }),
2653
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2654
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.cta") }),
2655
- /* @__PURE__ */ jsx(
2656
- Input,
2657
- {
2658
- value: draft.cta,
2659
- onChange: (e) => patch({ cta: e.target.value }),
2660
- placeholder: t("marketing.content.ctaPlaceholder")
2661
- }
2662
- )
2663
- ] }),
2664
- /* @__PURE__ */ jsxs("div", { className: "space-y-1 sm:col-span-3", children: [
2665
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.platformsLabel") }),
2666
- /* @__PURE__ */ jsx(PlatformPicker, { value: draft.platforms, onChange: (platforms) => patch({ platforms }) })
2667
- ] })
2668
- ] }),
2669
- isStatic && /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2670
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("marketing.content.mediaLabel") }),
2671
- /* @__PURE__ */ jsx(MediaAsset, { post, onChanged: handleMediaChanged })
2672
- ] }),
2673
- /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
2674
- /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2675
- /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: isStatic ? t("marketing.content.captionLabel") : t("marketing.content.scriptLabel") }),
2676
- draft.contentMd.trim() === "" && /* @__PURE__ */ jsx(Button, { variant: "outline", size: "sm", onClick: insertTemplate, children: t("marketing.content.insertTemplate") })
2677
- ] }),
2678
- /* @__PURE__ */ jsx(
2679
- MarkdownEditor,
2680
- {
2681
- value: draft.contentMd,
2682
- onChange: (contentMd) => patch({ contentMd }),
2683
- placeholder: isStatic ? t("marketing.content.captionPlaceholder") : t("marketing.content.bodyPlaceholder"),
2684
- editLabel: t("marketing.content.edit"),
2685
- previewLabel: t("marketing.content.preview"),
2686
- renderPreview: isStatic ? (value) => /* @__PURE__ */ jsx(
2687
- PostSocialPreview,
2688
- {
2689
- caption: value,
2690
- mediaUrl: post.mediaUrl,
2691
- accountName: account?.name ?? "@conta",
2692
- accountHandle: account?.handle,
2693
- platforms: draft.platforms.length > 0 ? draft.platforms : account?.platforms ?? []
2694
- }
2695
- ) : void 0
2696
- }
2697
- )
2698
- ] }),
2699
- /* @__PURE__ */ jsx(
2700
- ConfirmDialog,
2701
- {
2702
- open: confirmDelete,
2703
- variant: "destructive",
2704
- title: t("marketing.content.deletePost"),
2705
- description: t("marketing.content.deletePostConfirm"),
2706
- confirmLabel: t("marketing.content.deletePost"),
2707
- cancelLabel: t("marketing.content.cancel"),
2708
- onCancel: () => setConfirmDelete(false),
2709
- onConfirm: () => {
2710
- setConfirmDelete(false);
2711
- void deletePost(post.id).then(onBack);
2712
- }
2713
- }
2714
- )
2715
- ] })
2716
- },
2717
- post.id
2718
- );
2719
- }
2720
2721
 
2721
2722
  // src/data/blogEntities.tsx
2722
2723
  var BLOG_POSTS_TABLE = "plg_blog_posts";
@@ -2904,8 +2905,8 @@ function BlogEntityForm({
2904
2905
  const isPost = entity.data?.table === BLOG_POSTS_TABLE;
2905
2906
  const guardBlogPosts = useLimitGuard("blog_posts");
2906
2907
  const namePlural = entity.namePlural ?? entity.name + "s";
2907
- const [initialData, setInitialData] = React9.useState(isEdit ? null : {});
2908
- React9.useEffect(() => {
2908
+ const [initialData, setInitialData] = React12.useState(isEdit ? null : {});
2909
+ React12.useEffect(() => {
2909
2910
  if (!editId) return;
2910
2911
  let cancelled = false;
2911
2912
  (async () => {
@@ -3063,7 +3064,7 @@ function MarketingPage({ config, provider: provider2, store: store3, contentProv
3063
3064
  "blog-cat-new": 1,
3064
3065
  "blog-cat": 1
3065
3066
  }, "overview");
3066
- React9.useEffect(() => {
3067
+ React12.useEffect(() => {
3067
3068
  void store3.getState().load();
3068
3069
  }, [store3]);
3069
3070
  useDataChanged({ table: "plg_marketing_campaigns" }, () => {
@@ -3071,9 +3072,9 @@ function MarketingPage({ config, provider: provider2, store: store3, contentProv
3071
3072
  }, [store3]);
3072
3073
  const isOverview = view === "overview";
3073
3074
  const nav = buildNav(config, view, navigate, Boolean(cadenceSurface), t);
3074
- const blogPostEntity = React9.useMemo(() => buildBlogPostEntity(), []);
3075
- const blogCategoryEntity = React9.useMemo(() => buildBlogCategoryEntity(), []);
3076
- const quickActions = React9.useMemo(() => [
3075
+ const blogPostEntity = React12.useMemo(() => buildBlogPostEntity(), []);
3076
+ const blogCategoryEntity = React12.useMemo(() => buildBlogCategoryEntity(), []);
3077
+ const quickActions = React12.useMemo(() => [
3077
3078
  { id: "new-campaign", label: t("marketing.campaigns.new"), icon: "Megaphone", description: config.labels.campaigns, action: () => navigate("campaigns") }
3078
3079
  ], [t, config.labels.campaigns]);
3079
3080
  const renderView = createViewRouter([
@@ -3083,8 +3084,8 @@ function MarketingPage({ config, provider: provider2, store: store3, contentProv
3083
3084
  { id: "funnel", render: () => /* @__PURE__ */ jsx(FunnelView, {}) },
3084
3085
  { id: "cadences", render: () => /* @__PURE__ */ jsx(CadencesView, { surface: cadenceSurface }) },
3085
3086
  { id: "landing-pages", render: () => /* @__PURE__ */ jsx(LandingPagesView, {}) },
3086
- { id: "content", render: () => /* @__PURE__ */ jsx(ContentView, { onOpenPost: (id) => navigate(`content-post:${id}`) }) },
3087
- { id: "content-post", render: ({ id }) => /* @__PURE__ */ jsx(PostPage, { postId: id, onBack: () => navigate("content") }) },
3087
+ { id: "content", render: () => /* @__PURE__ */ jsx(ContentView, {}) },
3088
+ { id: "content-post", render: ({ id }) => /* @__PURE__ */ jsx(ContentView, { initialPostId: id }) },
3088
3089
  { id: "content-brief", render: () => /* @__PURE__ */ jsx(PlanBriefView, { onBack: () => navigate("content") }) },
3089
3090
  { id: "blog", render: () => /* @__PURE__ */ jsx(BlogAdminView, { navigate }) },
3090
3091
  { id: "blog-new", render: () => /* @__PURE__ */ jsx(BlogEntityForm, { entity: blogPostEntity, parentLabel: config.labels.blog ?? t("marketing.nav.blog"), onDone: () => navigate("blog") }) },
@@ -3130,11 +3131,11 @@ var SOURCE_OPTIONS = [
3130
3131
  function SettingsView() {
3131
3132
  const t = useTranslation();
3132
3133
  const { conversion, channels } = useMarketingConfig();
3133
- const [source, setSource] = React9.useState(conversion.source);
3134
- const [trackValue, setTrackValue] = React9.useState(true);
3135
- const [autoSync, setAutoSync] = React9.useState(true);
3136
- const [assisted, setAssisted] = React9.useState(false);
3137
- const [tracked, setTracked] = React9.useState(
3134
+ const [source, setSource] = React12.useState(conversion.source);
3135
+ const [trackValue, setTrackValue] = React12.useState(true);
3136
+ const [autoSync, setAutoSync] = React12.useState(true);
3137
+ const [assisted, setAssisted] = React12.useState(false);
3138
+ const [tracked, setTracked] = React12.useState(
3138
3139
  () => Object.fromEntries(channels.map((c) => [c.id, true]))
3139
3140
  );
3140
3141
  return /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
@@ -7385,22 +7386,22 @@ function createMarketingPlugin(options) {
7385
7386
  () => createMockContentPlannerProvider()
7386
7387
  );
7387
7388
  const contentStore = createContentPlannerStore(contentProvider);
7388
- const PageComponent = () => React9.createElement(
7389
+ const PageComponent = () => React12.createElement(
7389
7390
  MarketingContextProvider,
7390
7391
  { config, provider: provider2, store: store3 },
7391
- React9.createElement(MarketingPage, { config, provider: provider2, store: store3, contentProvider, contentStore, cadenceSurface: options?.cadenceSurface })
7392
+ React12.createElement(MarketingPage, { config, provider: provider2, store: store3, contentProvider, contentStore, cadenceSurface: options?.cadenceSurface })
7392
7393
  );
7393
7394
  PageComponent.displayName = "MarketingPage";
7394
7395
  const marketingRegistries = [
7395
7396
  ...buildMarketingRegistries(config.channels),
7396
7397
  ...options?.settingsRegistries ?? []
7397
7398
  ];
7398
- const SettingsComponent = () => React9.createElement(
7399
+ const SettingsComponent = () => React12.createElement(
7399
7400
  MarketingContextProvider,
7400
7401
  { config, provider: provider2, store: store3 },
7401
- React9.createElement(PluginSettingsPanel, {
7402
+ React12.createElement(PluginSettingsPanel, {
7402
7403
  title: config.labels.settings ?? config.labels.pageTitle,
7403
- generalSettings: React9.createElement(SettingsView),
7404
+ generalSettings: React12.createElement(SettingsView),
7404
7405
  registries: marketingRegistries,
7405
7406
  // Enables the "Integrações" tab (renders manifest connectors for this host).
7406
7407
  hostPluginId: "marketing",