@fayz-ai/plugin-marketing 0.13.17 → 0.13.18

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