@fayz-ai/plugin-courses 0.8.1 → 0.8.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/dist/components/CommerceUI.d.ts +31 -1
  2. package/dist/components/CommerceUI.d.ts.map +1 -1
  3. package/dist/index.cjs +1237 -301
  4. package/dist/index.cjs.map +1 -1
  5. package/dist/index.d.ts.map +1 -1
  6. package/dist/index.js +1240 -304
  7. package/dist/index.js.map +1 -1
  8. package/dist/locales/en.d.ts.map +1 -1
  9. package/dist/locales/pt-BR.d.ts.map +1 -1
  10. package/dist/pages/CourseEditorPage.d.ts.map +1 -1
  11. package/dist/pages/CoursesListPage.d.ts.map +1 -1
  12. package/dist/pages/SalesPage.d.ts.map +1 -1
  13. package/dist/public/CheckoutScreen.d.ts +17 -0
  14. package/dist/public/CheckoutScreen.d.ts.map +1 -0
  15. package/dist/public/createPublicCheckoutPlugin.d.ts +27 -0
  16. package/dist/public/createPublicCheckoutPlugin.d.ts.map +1 -0
  17. package/dist/public/data.d.ts +28 -0
  18. package/dist/public/data.d.ts.map +1 -0
  19. package/dist/public/data.supabase.d.ts +7 -0
  20. package/dist/public/data.supabase.d.ts.map +1 -0
  21. package/dist/public/index.d.ts +9 -0
  22. package/dist/public/index.d.ts.map +1 -0
  23. package/dist/views/CourseOrderDetailView.d.ts +10 -0
  24. package/dist/views/CourseOrderDetailView.d.ts.map +1 -0
  25. package/package.json +14 -5
  26. package/src/components/CommerceUI.tsx +78 -2
  27. package/src/index.ts +6 -3
  28. package/src/locales/en.ts +73 -12
  29. package/src/locales/pt-BR.ts +73 -12
  30. package/src/pages/CourseEditorPage.tsx +990 -177
  31. package/src/pages/CoursesListPage.tsx +20 -1
  32. package/src/pages/SalesPage.tsx +49 -20
  33. package/src/public/CheckoutScreen.tsx +279 -0
  34. package/src/public/createPublicCheckoutPlugin.tsx +88 -0
  35. package/src/public/data.supabase.ts +63 -0
  36. package/src/public/data.ts +89 -0
  37. package/src/public/index.ts +26 -0
  38. package/src/views/CourseOrderDetailView.tsx +132 -0
  39. package/dist/pages/MembersAreaPage.d.ts +0 -6
  40. package/dist/pages/MembersAreaPage.d.ts.map +0 -1
  41. package/src/pages/MembersAreaPage.tsx +0 -60
package/dist/index.cjs CHANGED
@@ -3,9 +3,13 @@
3
3
  var core = require('@fayz-ai/core');
4
4
  var courses = require('@fayz-ai/courses');
5
5
  var saas = require('@fayz-ai/saas');
6
- var React2 = require('react');
6
+ var React3 = require('react');
7
7
  var ui = require('@fayz-ai/ui');
8
8
  var jsxRuntime = require('react/jsx-runtime');
9
+ var lucideReact = require('lucide-react');
10
+ var core$1 = require('@dnd-kit/core');
11
+ var sortable = require('@dnd-kit/sortable');
12
+ var utilities = require('@dnd-kit/utilities');
9
13
 
10
14
  function _interopNamespace(e) {
11
15
  if (e && e.__esModule) return e;
@@ -25,7 +29,7 @@ function _interopNamespace(e) {
25
29
  return Object.freeze(n);
26
30
  }
27
31
 
28
- var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
32
+ var React3__namespace = /*#__PURE__*/_interopNamespace(React3);
29
33
 
30
34
  // src/index.ts
31
35
 
@@ -51,14 +55,75 @@ var en = {
51
55
  "courses.editor.fieldStatus": "Status",
52
56
  "courses.editor.save": "Save",
53
57
  "courses.editor.saved": "Course saved",
58
+ "courses.editor.saveFailed": "Could not save the course",
59
+ "courses.editor.unsaved": "Unsaved changes",
60
+ // General tab
61
+ "courses.editor.sectionBasics": "Basics",
62
+ "courses.editor.sectionThumbnail": "Cover image",
63
+ "courses.editor.sectionPricing": "Pricing & status",
64
+ "courses.editor.descriptionPlaceholder": "What will students learn?",
65
+ "courses.editor.fieldDelivery": "Format",
66
+ "courses.editor.deliveryHint": "How this course is delivered.",
67
+ "courses.editor.delivery.online": "Online",
68
+ "courses.editor.delivery.in_person": "In person",
69
+ "courses.editor.delivery.hybrid": "Hybrid",
70
+ "courses.editor.thumbnailHint": "Shown on the storefront and in listings. 1280\xD7720 recommended.",
71
+ "courses.editor.thumbnailUpload": "Upload image",
72
+ "courses.editor.thumbnailUrl": "Or paste an image URL",
73
+ "courses.editor.thumbnailEmpty": "No image",
74
+ "courses.editor.thumbnailUploaded": "Image uploaded \u2014 remember to save.",
75
+ "courses.editor.uploading": "Uploading\u2026",
76
+ "courses.editor.uploadFailed": "Could not upload the image",
77
+ "courses.editor.uploadUnavailable": "Upload unavailable \u2014 paste an image URL instead.",
78
+ // Curriculum tab
54
79
  "courses.editor.addModule": "Add module",
55
80
  "courses.editor.moduleTitle": "Module title",
81
+ "courses.editor.moduleAdded": "Module added",
56
82
  "courses.editor.addLesson": "Add lesson",
57
83
  "courses.editor.lessonTitle": "Lesson title",
58
84
  "courses.editor.lessonVideo": "Video URL (embed)",
59
85
  "courses.editor.lessonDuration": "Duration (min)",
60
86
  "courses.editor.deleteModule": "Delete module",
61
- "courses.editor.deleteLesson": "Remove",
87
+ "courses.editor.deleteLesson": "Remove lesson",
88
+ "courses.editor.deleted": "Removed",
89
+ "courses.editor.autosaved": "Changes are saved automatically.",
90
+ "courses.editor.modulesEmpty": "No modules yet",
91
+ "courses.editor.modulesEmptyHint": "Create a module to start organizing your lessons.",
92
+ "courses.editor.lessonsEmpty": "No lessons in this module yet.",
93
+ "courses.editor.deleteModuleConfirm": "Delete this module?",
94
+ "courses.editor.deleteModuleConfirmBody": "Its lessons will be removed too. This cannot be undone.",
95
+ "courses.editor.deleteLessonConfirm": "Remove this lesson?",
96
+ "courses.editor.deleteLessonConfirmBody": "This cannot be undone.",
97
+ "courses.editor.confirmDelete": "Delete",
98
+ "courses.editor.cancel": "Cancel",
99
+ // Curriculum tree builder
100
+ "courses.editor.newModule": "New module",
101
+ "courses.editor.firstModule": "Create first module",
102
+ "courses.editor.newLesson": "New lesson",
103
+ "courses.editor.firstLesson": "Add the first lesson",
104
+ "courses.editor.moduleLabel": "module",
105
+ "courses.editor.moduleLabelPlural": "modules",
106
+ "courses.editor.lessonUnitOne": "lesson",
107
+ "courses.editor.lessonUnitOther": "lessons",
108
+ "courses.editor.noLessons": "No lessons",
109
+ "courses.editor.minShort": "min",
110
+ "courses.editor.hourShort": "h",
111
+ "courses.editor.dragToReorder": "Drag to reorder",
112
+ "courses.editor.expand": "Expand",
113
+ "courses.editor.collapse": "Collapse",
114
+ "courses.editor.editLesson": "Edit lesson",
115
+ "courses.editor.addVideo": "Add video",
116
+ "courses.editor.videoSet": "Video",
117
+ "courses.editor.videoUrlLabel": "Video URL",
118
+ "courses.editor.videoUrlPlaceholder": "Paste the video link (YouTube, Vimeo\u2026)",
119
+ "courses.editor.reorderFailed": "Could not reorder",
120
+ // Members area
121
+ "courses.editor.membersHelp": "Buyers are enrolled automatically through orders and get access to all of this course\u2019s content.",
122
+ "courses.editor.enrolledStudents": "Enrolled students",
123
+ "courses.editor.membersEmpty": "No students yet",
124
+ "courses.editor.membersEmptyHint": "Buyers are enrolled here automatically after a paid order.",
125
+ "courses.editor.accessUnlockedBy": "Access is unlocked by",
126
+ "courses.editor.membersNote": "Buying any offer grants full access to this course\u2019s content.",
62
127
  "courses.editor.untitledCourse": "Untitled course",
63
128
  "courses.editor.untitledModule": "New module",
64
129
  "courses.editor.untitledLesson": "New lesson",
@@ -67,29 +132,29 @@ var en = {
67
132
  "courses.editor.tab.curriculum": "Curriculum",
68
133
  "courses.editor.tab.offers": "Offers",
69
134
  "courses.editor.tab.members": "Members area",
70
- "courses.editor.tab.settings": "Payment",
71
- "courses.editor.tab.checkout": "Checkout",
72
- "courses.editor.tab.links": "Links",
73
- "courses.editor.membersHelp": "Buyers of this course are added to its members area automatically. Access groups per offer:",
74
- "courses.editor.defaultGroup": "Default group",
75
- "courses.editor.settingsHelp": "Payment methods offered at checkout (Stripe).",
135
+ // Payment methods (used by the Courses plugin settings surface)
76
136
  "courses.editor.payCard": "Credit card (up to 12x)",
77
137
  "courses.editor.payPix": "Pix",
78
138
  "courses.editor.payBoleto": "Boleto",
79
- "courses.editor.settingsNote": "Method availability depends on Stripe in your region; wired in Stage 3.",
80
- "courses.editor.checkoutSoon": "Custom checkout builder comes in a later milestone. A default checkout is generated per offer.",
81
- "courses.editor.linksHelp": "Shareable checkout links, one per offer.",
82
139
  // Offers
83
- "courses.offers.help": "Price points a customer can buy. Mark one default; order-bumps show on other checkouts.",
140
+ "courses.offers.help": "Price points for this course. Sales run through orders and the Financeiro module.",
84
141
  "courses.offers.name": "Offer name",
85
142
  "courses.offers.price": "Price",
143
+ "courses.offers.kind": "Type",
86
144
  "courses.offers.oneTime": "One-time",
87
145
  "courses.offers.subscription": "Subscription",
146
+ "courses.offers.interval": "Billing interval",
88
147
  "courses.offers.default": "Default",
89
- "courses.offers.bump": "Bump",
148
+ "courses.offers.bump": "Order bump",
90
149
  "courses.offers.remove": "Remove",
91
150
  "courses.offers.add": "Add offer",
151
+ "courses.offers.added": "Offer added",
152
+ "courses.offers.removed": "Offer removed",
92
153
  "courses.offers.newName": "New offer",
154
+ "courses.offers.empty": "No offers yet",
155
+ "courses.offers.emptyHint": "Create an offer to sell this course.",
156
+ "courses.offers.firstOffer": "Create first offer",
157
+ "courses.offers.checkoutFlagsNote": "Apply once the public checkout ships (coming soon).",
93
158
  // Sales
94
159
  "courses.sales.title": "Sales",
95
160
  "courses.sales.subtitle": "Every transaction across your courses.",
@@ -195,14 +260,75 @@ var ptBR = {
195
260
  "courses.editor.fieldStatus": "Status",
196
261
  "courses.editor.save": "Salvar",
197
262
  "courses.editor.saved": "Curso salvo",
263
+ "courses.editor.saveFailed": "N\xE3o foi poss\xEDvel salvar o curso",
264
+ "courses.editor.unsaved": "Altera\xE7\xF5es n\xE3o salvas",
265
+ // Geral tab
266
+ "courses.editor.sectionBasics": "Informa\xE7\xF5es b\xE1sicas",
267
+ "courses.editor.sectionThumbnail": "Imagem de capa",
268
+ "courses.editor.sectionPricing": "Pre\xE7o e publica\xE7\xE3o",
269
+ "courses.editor.descriptionPlaceholder": "O que o aluno vai aprender?",
270
+ "courses.editor.fieldDelivery": "Formato",
271
+ "courses.editor.deliveryHint": "Como o curso \xE9 entregue.",
272
+ "courses.editor.delivery.online": "Online",
273
+ "courses.editor.delivery.in_person": "Presencial",
274
+ "courses.editor.delivery.hybrid": "H\xEDbrido",
275
+ "courses.editor.thumbnailHint": "Aparece na loja e nas listagens. Recomendado 1280\xD7720.",
276
+ "courses.editor.thumbnailUpload": "Enviar imagem",
277
+ "courses.editor.thumbnailUrl": "Ou cole uma URL de imagem",
278
+ "courses.editor.thumbnailEmpty": "Sem imagem",
279
+ "courses.editor.thumbnailUploaded": "Imagem enviada \u2014 lembre de salvar.",
280
+ "courses.editor.uploading": "Enviando\u2026",
281
+ "courses.editor.uploadFailed": "N\xE3o foi poss\xEDvel enviar a imagem",
282
+ "courses.editor.uploadUnavailable": "Upload indispon\xEDvel \u2014 cole uma URL de imagem.",
283
+ // Conteúdo tab
198
284
  "courses.editor.addModule": "Adicionar m\xF3dulo",
199
285
  "courses.editor.moduleTitle": "T\xEDtulo do m\xF3dulo",
286
+ "courses.editor.moduleAdded": "M\xF3dulo adicionado",
200
287
  "courses.editor.addLesson": "Adicionar aula",
201
288
  "courses.editor.lessonTitle": "T\xEDtulo da aula",
202
289
  "courses.editor.lessonVideo": "URL do v\xEDdeo (embed)",
203
290
  "courses.editor.lessonDuration": "Dura\xE7\xE3o (min)",
204
291
  "courses.editor.deleteModule": "Excluir m\xF3dulo",
205
- "courses.editor.deleteLesson": "Remover",
292
+ "courses.editor.deleteLesson": "Remover aula",
293
+ "courses.editor.deleted": "Removido",
294
+ "courses.editor.autosaved": "As altera\xE7\xF5es s\xE3o salvas automaticamente.",
295
+ "courses.editor.modulesEmpty": "Nenhum m\xF3dulo ainda",
296
+ "courses.editor.modulesEmptyHint": "Crie um m\xF3dulo para come\xE7ar a organizar as aulas.",
297
+ "courses.editor.lessonsEmpty": "Nenhuma aula neste m\xF3dulo ainda.",
298
+ "courses.editor.deleteModuleConfirm": "Excluir este m\xF3dulo?",
299
+ "courses.editor.deleteModuleConfirmBody": "As aulas deste m\xF3dulo tamb\xE9m ser\xE3o removidas. Isso n\xE3o pode ser desfeito.",
300
+ "courses.editor.deleteLessonConfirm": "Remover esta aula?",
301
+ "courses.editor.deleteLessonConfirmBody": "Isso n\xE3o pode ser desfeito.",
302
+ "courses.editor.confirmDelete": "Excluir",
303
+ "courses.editor.cancel": "Cancelar",
304
+ // Curriculum tree builder
305
+ "courses.editor.newModule": "Novo m\xF3dulo",
306
+ "courses.editor.firstModule": "Criar primeiro m\xF3dulo",
307
+ "courses.editor.newLesson": "Nova aula",
308
+ "courses.editor.firstLesson": "Adicione a primeira aula",
309
+ "courses.editor.moduleLabel": "m\xF3dulo",
310
+ "courses.editor.moduleLabelPlural": "m\xF3dulos",
311
+ "courses.editor.lessonUnitOne": "aula",
312
+ "courses.editor.lessonUnitOther": "aulas",
313
+ "courses.editor.noLessons": "Sem aulas",
314
+ "courses.editor.minShort": "min",
315
+ "courses.editor.hourShort": "h",
316
+ "courses.editor.dragToReorder": "Arraste para reordenar",
317
+ "courses.editor.expand": "Expandir",
318
+ "courses.editor.collapse": "Recolher",
319
+ "courses.editor.editLesson": "Editar aula",
320
+ "courses.editor.addVideo": "Adicionar v\xEDdeo",
321
+ "courses.editor.videoSet": "V\xEDdeo",
322
+ "courses.editor.videoUrlLabel": "URL do v\xEDdeo",
323
+ "courses.editor.videoUrlPlaceholder": "Cole o link do v\xEDdeo (YouTube, Vimeo\u2026)",
324
+ "courses.editor.reorderFailed": "N\xE3o foi poss\xEDvel reordenar",
325
+ // Área de membros
326
+ "courses.editor.membersHelp": "Quem compra este curso \xE9 matriculado automaticamente pelos pedidos e ganha acesso a todo o conte\xFAdo.",
327
+ "courses.editor.enrolledStudents": "Alunos matriculados",
328
+ "courses.editor.membersEmpty": "Nenhum aluno ainda",
329
+ "courses.editor.membersEmptyHint": "Os compradores s\xE3o matriculados aqui automaticamente ap\xF3s um pedido pago.",
330
+ "courses.editor.accessUnlockedBy": "Acesso liberado por",
331
+ "courses.editor.membersNote": "Comprar qualquer oferta libera o acesso completo ao conte\xFAdo deste curso.",
206
332
  "courses.editor.untitledCourse": "Curso sem t\xEDtulo",
207
333
  "courses.editor.untitledModule": "Novo m\xF3dulo",
208
334
  "courses.editor.untitledLesson": "Nova aula",
@@ -211,29 +337,29 @@ var ptBR = {
211
337
  "courses.editor.tab.curriculum": "Conte\xFAdo",
212
338
  "courses.editor.tab.offers": "Ofertas",
213
339
  "courses.editor.tab.members": "\xC1rea de membros",
214
- "courses.editor.tab.settings": "Pagamento",
215
- "courses.editor.tab.checkout": "Checkout",
216
- "courses.editor.tab.links": "Links",
217
- "courses.editor.membersHelp": "Quem compra este curso entra automaticamente na \xE1rea de membros. Grupos de acesso por oferta:",
218
- "courses.editor.defaultGroup": "Grupo padr\xE3o",
219
- "courses.editor.settingsHelp": "M\xE9todos de pagamento no checkout (Stripe).",
340
+ // Métodos de pagamento (usados na tela de configurações do plugin de Cursos)
220
341
  "courses.editor.payCard": "Cart\xE3o de cr\xE9dito (at\xE9 12x)",
221
342
  "courses.editor.payPix": "Pix",
222
343
  "courses.editor.payBoleto": "Boleto",
223
- "courses.editor.settingsNote": "A disponibilidade de cada m\xE9todo depende do Stripe na sua regi\xE3o; ligado na Etapa 3.",
224
- "courses.editor.checkoutSoon": "O construtor de checkout chega numa etapa futura. Um checkout padr\xE3o \xE9 gerado por oferta.",
225
- "courses.editor.linksHelp": "Links de checkout compartilh\xE1veis, um por oferta.",
226
344
  // Offers
227
- "courses.offers.help": "Pre\xE7os que o cliente pode comprar. Marque um padr\xE3o; order-bumps aparecem nos checkouts das outras.",
345
+ "courses.offers.help": "Pre\xE7os deste curso. As vendas acontecem pelos pedidos e pelo Financeiro.",
228
346
  "courses.offers.name": "Nome da oferta",
229
347
  "courses.offers.price": "Pre\xE7o",
348
+ "courses.offers.kind": "Tipo",
230
349
  "courses.offers.oneTime": "\xDAnico",
231
350
  "courses.offers.subscription": "Assinatura",
351
+ "courses.offers.interval": "Intervalo de cobran\xE7a",
232
352
  "courses.offers.default": "Padr\xE3o",
233
- "courses.offers.bump": "Bump",
353
+ "courses.offers.bump": "Order bump",
234
354
  "courses.offers.remove": "Remover",
235
355
  "courses.offers.add": "Adicionar oferta",
356
+ "courses.offers.added": "Oferta adicionada",
357
+ "courses.offers.removed": "Oferta removida",
236
358
  "courses.offers.newName": "Nova oferta",
359
+ "courses.offers.empty": "Nenhuma oferta ainda",
360
+ "courses.offers.emptyHint": "Crie uma oferta para vender este curso.",
361
+ "courses.offers.firstOffer": "Criar primeira oferta",
362
+ "courses.offers.checkoutFlagsNote": "Aplicam-se quando o checkout p\xFAblico entrar no ar (em breve).",
237
363
  // Sales
238
364
  "courses.sales.title": "Vendas",
239
365
  "courses.sales.subtitle": "Todas as transa\xE7\xF5es dos seus cursos.",
@@ -334,14 +460,14 @@ function statusVariant(status) {
334
460
  function CoursesListPage() {
335
461
  const t = core.useTranslation();
336
462
  const shellOwnsHeader = ui.useModuleLayout() === "tabs";
337
- const [courses$1, setCourses] = React2__namespace.useState(null);
338
- const [creating, setCreating] = React2__namespace.useState(false);
463
+ const [courses$1, setCourses] = React3__namespace.useState(null);
464
+ const [creating, setCreating] = React3__namespace.useState(false);
339
465
  const guardCourses = saas.useLimitGuard("courses");
340
466
  const can = saas.usePermissionOptional();
341
- const load = React2__namespace.useCallback(async () => {
467
+ const load = React3__namespace.useCallback(async () => {
342
468
  setCourses(await courses.getCoursesProvider().listCourses());
343
469
  }, []);
344
- React2__namespace.useEffect(() => {
470
+ React3__namespace.useEffect(() => {
345
471
  void load();
346
472
  }, [load]);
347
473
  async function onCreate() {
@@ -368,29 +494,45 @@ function CoursesListPage() {
368
494
  /* @__PURE__ */ jsxRuntime.jsx(saas.PermissionGate, { feature: "courses", action: "create", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" }) })
369
495
  ] }),
370
496
  courses$1 && courses$1.length === 0 && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-dashed border-border p-12 text-center text-muted-foreground", children: t("courses.empty") || "No courses yet. Create your first one." }),
371
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: courses$1?.map((course) => /* @__PURE__ */ jsxRuntime.jsxs(
372
- "button",
373
- {
374
- onClick: () => navigateTo(`/courses/${course.id}`),
375
- className: "group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md",
376
- children: [
377
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-video w-full overflow-hidden bg-muted", children: course.thumbnailUrl && // eslint-disable-next-line @next/next/no-img-element
378
- /* @__PURE__ */ jsxRuntime.jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" }) }),
379
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 p-4", children: [
380
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
381
- /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-foreground line-clamp-2", children: course.title }),
382
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: statusVariant(course.status), children: t(`courses.status.${course.status}`) || course.status })
383
- ] }),
384
- course.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground line-clamp-2", children: course.subtitle }),
385
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: course.price > 0 ? `${course.currency} ${course.price.toFixed(2)}` : "\u2014" })
386
- ] })
387
- ]
388
- },
389
- course.id
390
- )) })
497
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: [
498
+ courses$1 === null && Array.from({ length: 6 }).map((_, i) => /* @__PURE__ */ jsxRuntime.jsx(CourseCardSkeleton, {}, i)),
499
+ courses$1?.map((course) => /* @__PURE__ */ jsxRuntime.jsxs(
500
+ "button",
501
+ {
502
+ onClick: () => navigateTo(`/courses/${course.id}`),
503
+ className: "group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md",
504
+ children: [
505
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-video w-full overflow-hidden bg-muted", children: course.thumbnailUrl && // eslint-disable-next-line @next/next/no-img-element
506
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" }) }),
507
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 p-4", children: [
508
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
509
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-foreground line-clamp-2", children: course.title }),
510
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: statusVariant(course.status), children: t(`courses.status.${course.status}`) || course.status })
511
+ ] }),
512
+ course.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground line-clamp-2", children: course.subtitle }),
513
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: course.price > 0 ? `${course.currency} ${course.price.toFixed(2)}` : "\u2014" })
514
+ ] })
515
+ ]
516
+ },
517
+ course.id
518
+ ))
519
+ ] })
391
520
  ] });
392
521
  }
393
522
  CoursesListPage.displayName = "CoursesListPage";
523
+ function CourseCardSkeleton() {
524
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "overflow-hidden rounded-xl border border-border bg-card", children: [
525
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "aspect-video w-full rounded-none" }),
526
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 p-4", children: [
527
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
528
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-4 w-2/3" }),
529
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-5 w-16 rounded-full" })
530
+ ] }),
531
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-3 w-full" }),
532
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-4 w-20" })
533
+ ] })
534
+ ] });
535
+ }
394
536
 
395
537
  // src/lib/format.ts
396
538
  function formatMoney(value, currency = "BRL") {
@@ -410,69 +552,248 @@ function formatDate(iso) {
410
552
  function formatPercent(fraction, digits = 0) {
411
553
  return `${(fraction * 100).toFixed(digits)}%`;
412
554
  }
555
+ function PageContainer({ title, subtitle, actions, children }) {
556
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-6xl p-6 md:p-8", children: [
557
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-start justify-between gap-4", children: [
558
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
559
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-foreground", children: title }),
560
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
561
+ ] }),
562
+ actions
563
+ ] }),
564
+ children
565
+ ] });
566
+ }
567
+ function StatCard({ label, value, hint }) {
568
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { className: "p-5", children: [
569
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: label }),
570
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-2xl font-bold text-foreground", children: value }),
571
+ hint && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: hint })
572
+ ] });
573
+ }
574
+ function StatGrid({ children }) {
575
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children });
576
+ }
577
+ function EmptyState({ icon, title, description, action }) {
578
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col items-center justify-center rounded-xl border border-dashed border-border bg-card/40 px-6 py-12 text-center", children: [
579
+ icon && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-3 flex h-11 w-11 items-center justify-center rounded-full bg-muted text-muted-foreground", children: icon }),
580
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: title }),
581
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 max-w-sm text-sm text-muted-foreground", children: description }),
582
+ action && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-4", children: action })
583
+ ] });
584
+ }
585
+ function InfoNote({ icon, children, className }) {
586
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start gap-2.5 rounded-lg border border-border bg-muted/40 px-3.5 py-3 text-sm text-muted-foreground " + (className ?? ""), children: [
587
+ icon && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "mt-0.5 shrink-0 text-muted-foreground/80", children: icon }),
588
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "min-w-0 [&_strong]:font-medium [&_strong]:text-foreground", children })
589
+ ] });
590
+ }
591
+ function SectionCard({ title, description, actions, children, className }) {
592
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { className: "p-5 md:p-6 " + (className ?? ""), children: [
593
+ (title || actions) && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-start justify-between gap-4", children: [
594
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
595
+ title && /* @__PURE__ */ jsxRuntime.jsx("h2", { className: "text-sm font-semibold text-foreground", children: title }),
596
+ description && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-0.5 text-sm text-muted-foreground", children: description })
597
+ ] }),
598
+ actions
599
+ ] }),
600
+ children
601
+ ] });
602
+ }
603
+ function SimpleTable({ columns, rows, empty, loading, onRowClick }) {
604
+ const cols = React3__namespace.useMemo(
605
+ () => columns.map((c) => ({
606
+ id: c.key,
607
+ header: () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.header }),
608
+ cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.render(row.original) })
609
+ })),
610
+ [columns]
611
+ );
612
+ return /* @__PURE__ */ jsxRuntime.jsx(
613
+ ui.DataTable,
614
+ {
615
+ columns: cols,
616
+ data: rows,
617
+ emptyMessage: empty,
618
+ loading,
619
+ onRowClick: onRowClick ? (row) => onRowClick(row) : void 0
620
+ }
621
+ );
622
+ }
623
+ function buildCourseStudents(orders) {
624
+ const byKey = /* @__PURE__ */ new Map();
625
+ for (const o of orders) {
626
+ const key = o.customerId ?? o.customerEmail ?? o.id;
627
+ const existing = byKey.get(key);
628
+ if (!existing || o.createdAt && existing.since && o.createdAt > existing.since) {
629
+ byKey.set(key, {
630
+ key,
631
+ name: o.customerName ?? o.customerEmail ?? "\u2014",
632
+ email: o.customerEmail ?? null,
633
+ since: o.createdAt ?? null
634
+ });
635
+ }
636
+ }
637
+ return [...byKey.values()].sort((a, b) => (b.since ?? "").localeCompare(a.since ?? ""));
638
+ }
413
639
  var STATUSES = ["draft", "published", "archived"];
414
- var TABS = ["details", "curriculum", "offers", "members", "settings", "checkout", "links"];
640
+ var DELIVERY_TYPES = ["online", "in_person", "hybrid"];
641
+ var TABS = ["details", "curriculum", "offers", "members"];
642
+ var TAB_ICON = {
643
+ details: lucideReact.Tag,
644
+ curriculum: lucideReact.Layers,
645
+ offers: lucideReact.ShoppingCart,
646
+ members: lucideReact.Users
647
+ };
648
+ function editSnapshot(c) {
649
+ return JSON.stringify({
650
+ title: c.title,
651
+ subtitle: c.subtitle,
652
+ description: c.description,
653
+ price: c.price,
654
+ status: c.status,
655
+ deliveryType: c.deliveryType,
656
+ thumbnailUrl: c.thumbnailUrl
657
+ });
658
+ }
415
659
  function CourseEditorPage({ id }) {
416
660
  const t = core.useTranslation();
417
- const [tab, setTab] = React2__namespace.useState("details");
418
- const [course, setCourse] = React2__namespace.useState(null);
419
- const [modules, setModules] = React2__namespace.useState([]);
420
- const [lessons, setLessons] = React2__namespace.useState([]);
421
- const [offers, setOffers] = React2__namespace.useState([]);
422
- const reload = React2__namespace.useCallback(async () => {
661
+ const [tab, setTab] = React3__namespace.useState("details");
662
+ const [course, setCourse] = React3__namespace.useState(null);
663
+ const [modules, setModules] = React3__namespace.useState([]);
664
+ const [lessons, setLessons] = React3__namespace.useState([]);
665
+ const [offers, setOffers] = React3__namespace.useState([]);
666
+ const [students, setStudents] = React3__namespace.useState([]);
667
+ const [loading, setLoading] = React3__namespace.useState(true);
668
+ const [savedSnap, setSavedSnap] = React3__namespace.useState("");
669
+ const [saving, setSaving] = React3__namespace.useState(false);
670
+ const [uploading, setUploading] = React3__namespace.useState(false);
671
+ const fileRef = React3__namespace.useRef(null);
672
+ const [pendingDelete, setPendingDelete] = React3__namespace.useState(null);
673
+ const [autoFocusId, setAutoFocusId] = React3__namespace.useState(null);
674
+ const reload = React3__namespace.useCallback(async () => {
423
675
  if (!id) return;
424
676
  const provider = courses.getCoursesProvider();
425
677
  const c = await provider.getCourse(id);
426
678
  setCourse(c);
427
679
  if (c) {
428
- const [m, l, o] = await Promise.all([
680
+ setSavedSnap(editSnapshot(c));
681
+ const [m, l, o, paidOrders] = await Promise.all([
429
682
  provider.listModules(c.id),
430
683
  provider.listLessons(c.id),
431
- provider.listOffers(c.id)
684
+ provider.listOffers(c.id),
685
+ provider.listOrders({ courseId: c.id, financialStatus: "paid" })
432
686
  ]);
433
687
  setModules(m);
434
688
  setLessons(l);
435
689
  setOffers(o);
690
+ setStudents(buildCourseStudents(paidOrders));
436
691
  }
692
+ setLoading(false);
437
693
  }, [id]);
438
- React2__namespace.useEffect(() => {
694
+ React3__namespace.useEffect(() => {
439
695
  void reload();
440
696
  }, [reload]);
441
- if (!course) {
442
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-muted-foreground", children: "\u2026" });
697
+ const dirty = !!course && editSnapshot(course) !== savedSnap;
698
+ ui.useSaveBar({
699
+ dirty,
700
+ saving,
701
+ onSave: () => {
702
+ void saveDetails();
703
+ },
704
+ onDiscard: () => {
705
+ void reload();
706
+ },
707
+ saveLabel: t("courses.editor.save") || "Salvar"
708
+ });
709
+ if (loading || !course) return /* @__PURE__ */ jsxRuntime.jsx(EditorSkeleton, {});
710
+ function patchCourse(patch) {
711
+ setCourse((c) => c ? { ...c, ...patch } : c);
443
712
  }
444
713
  async function saveDetails() {
445
- await courses.getCoursesProvider().updateCourse(course.id, {
446
- title: course.title,
447
- subtitle: course.subtitle ?? void 0,
448
- description: course.description ?? void 0,
449
- price: course.price,
450
- status: course.status
451
- });
452
- ui.toast.success(t("courses.editor.saved") || "Course saved");
714
+ if (!course) return;
715
+ setSaving(true);
716
+ try {
717
+ await courses.getCoursesProvider().updateCourse(course.id, {
718
+ title: course.title,
719
+ subtitle: course.subtitle ?? void 0,
720
+ description: course.description ?? void 0,
721
+ thumbnailUrl: course.thumbnailUrl ?? void 0,
722
+ price: course.price,
723
+ status: course.status,
724
+ deliveryType: course.deliveryType
725
+ });
726
+ setSavedSnap(editSnapshot(course));
727
+ ui.toast.success(t("courses.editor.saved") || "Course saved");
728
+ } catch {
729
+ ui.toast.error(t("courses.editor.saveFailed") || "Could not save the course");
730
+ } finally {
731
+ setSaving(false);
732
+ }
453
733
  }
454
- function patchCourse(patch) {
455
- setCourse((c) => c ? { ...c, ...patch } : c);
734
+ async function onPickThumbnail(e) {
735
+ const file = e.target.files?.[0];
736
+ if (!file || !course) return;
737
+ const supabase = core.getSupabaseClientOptional();
738
+ if (!supabase?.storage) {
739
+ ui.toast.error(t("courses.editor.uploadUnavailable") || "Upload unavailable \u2014 paste an image URL instead.");
740
+ if (fileRef.current) fileRef.current.value = "";
741
+ return;
742
+ }
743
+ setUploading(true);
744
+ try {
745
+ const ext = file.name.split(".").pop() || "jpg";
746
+ const path = `${course.id}/cover-${Date.now()}.${ext}`;
747
+ const bucket = supabase.storage.from("course-media");
748
+ const { error } = await bucket.upload(path, file, { upsert: true, contentType: file.type });
749
+ if (error) throw error;
750
+ const { data } = bucket.getPublicUrl(path);
751
+ patchCourse({ thumbnailUrl: data.publicUrl });
752
+ ui.toast.success(t("courses.editor.thumbnailUploaded") || "Image uploaded \u2014 remember to save.");
753
+ } catch {
754
+ ui.toast.error(t("courses.editor.uploadFailed") || "Could not upload the image");
755
+ } finally {
756
+ setUploading(false);
757
+ if (fileRef.current) fileRef.current.value = "";
758
+ }
456
759
  }
457
760
  async function addModule() {
458
- await courses.getCoursesProvider().createModule({ courseId: course.id, title: t("courses.editor.untitledModule") || "New module" });
761
+ const mod = await courses.getCoursesProvider().createModule({ courseId: course.id, title: t("courses.editor.untitledModule") || "New module" });
762
+ ui.toast.success(t("courses.editor.moduleAdded") || "Module added");
459
763
  await reload();
764
+ setAutoFocusId(mod.id);
460
765
  }
461
766
  async function renameModule(moduleId, title) {
462
767
  await courses.getCoursesProvider().updateModule(moduleId, { title });
463
768
  }
464
- async function deleteModule(moduleId) {
465
- await courses.getCoursesProvider().deleteModule(moduleId);
466
- await reload();
467
- }
468
769
  async function addLesson(moduleId) {
469
- await courses.getCoursesProvider().createLesson({
770
+ const tempId = `tmp-${crypto.randomUUID()}`;
771
+ const nextOrder = lessons.filter((l) => l.moduleId === moduleId).reduce((max, l) => Math.max(max, l.sortOrder), -1) + 1;
772
+ const optimistic = {
773
+ id: tempId,
470
774
  courseId: course.id,
471
775
  moduleId,
472
776
  title: t("courses.editor.untitledLesson") || "New lesson",
473
- videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ"
474
- });
475
- await reload();
777
+ description: null,
778
+ videoUrl: "",
779
+ durationSec: 0,
780
+ sortOrder: nextOrder
781
+ };
782
+ setLessons((ls) => [...ls, optimistic]);
783
+ setAutoFocusId(tempId);
784
+ try {
785
+ const real = await courses.getCoursesProvider().createLesson({
786
+ courseId: course.id,
787
+ moduleId,
788
+ title: optimistic.title,
789
+ videoUrl: ""
790
+ });
791
+ setLessons((ls) => ls.map((l) => l.id === tempId ? { ...l, id: real.id } : l));
792
+ setAutoFocusId((cur) => cur === tempId ? real.id : cur);
793
+ } catch {
794
+ setLessons((ls) => ls.filter((l) => l.id !== tempId));
795
+ ui.toast.error(t("courses.editor.saveFailed") || "Could not add the lesson");
796
+ }
476
797
  }
477
798
  async function patchLesson(lessonId, patch) {
478
799
  await courses.getCoursesProvider().updateLesson(lessonId, {
@@ -482,8 +803,37 @@ function CourseEditorPage({ id }) {
482
803
  description: patch.description ?? void 0
483
804
  });
484
805
  }
485
- async function deleteLesson(lessonId) {
486
- await courses.getCoursesProvider().deleteLesson(lessonId);
806
+ async function reorderModules(orderedIds) {
807
+ const prev = modules;
808
+ setModules(orderedIds.map((mid, i) => ({ ...prev.find((m) => m.id === mid), sortOrder: i })));
809
+ try {
810
+ await Promise.all(orderedIds.map((mid, i) => courses.getCoursesProvider().updateModule(mid, { sortOrder: i })));
811
+ } catch {
812
+ ui.toast.error(t("courses.editor.reorderFailed") || "Could not reorder");
813
+ } finally {
814
+ await reload();
815
+ }
816
+ }
817
+ async function reorderLessons(moduleId, orderedIds) {
818
+ const prev = lessons;
819
+ const others = prev.filter((l) => l.moduleId !== moduleId);
820
+ const reordered = orderedIds.map((lid, i) => ({ ...prev.find((l) => l.id === lid), sortOrder: i }));
821
+ setLessons([...others, ...reordered]);
822
+ try {
823
+ await Promise.all(orderedIds.map((lid, i) => courses.getCoursesProvider().updateLesson(lid, { sortOrder: i })));
824
+ } catch {
825
+ ui.toast.error(t("courses.editor.reorderFailed") || "Could not reorder");
826
+ } finally {
827
+ await reload();
828
+ }
829
+ }
830
+ async function confirmDelete() {
831
+ if (!pendingDelete) return;
832
+ const provider = courses.getCoursesProvider();
833
+ if (pendingDelete.kind === "module") await provider.deleteModule(pendingDelete.id);
834
+ else await provider.deleteLesson(pendingDelete.id);
835
+ ui.toast.success(t("courses.editor.deleted") || "Removed");
836
+ setPendingDelete(null);
487
837
  await reload();
488
838
  }
489
839
  async function addOffer() {
@@ -493,6 +843,7 @@ function CourseEditorPage({ id }) {
493
843
  price: course.price,
494
844
  kind: "one_time"
495
845
  });
846
+ ui.toast.success(t("courses.offers.added") || "Offer added");
496
847
  await reload();
497
848
  }
498
849
  async function patchOffer(offerId, patch) {
@@ -508,179 +859,668 @@ function CourseEditorPage({ id }) {
508
859
  }
509
860
  async function deleteOffer(offerId) {
510
861
  await courses.getCoursesProvider().deleteOffer(offerId);
862
+ ui.toast.success(t("courses.offers.removed") || "Offer removed");
511
863
  await reload();
512
864
  }
865
+ const deliveryOptions = DELIVERY_TYPES.map((d) => ({
866
+ value: d,
867
+ label: t(`courses.editor.delivery.${d}`) || d
868
+ }));
513
869
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl p-6 md:p-8", children: [
514
- /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => navigateTo("/courses"), className: "mb-4 text-sm text-muted-foreground hover:text-foreground", children: [
515
- "\u2190 ",
516
- t("courses.editor.back") || "Back to courses"
517
- ] }),
518
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mb-1 text-2xl font-bold text-foreground", children: course.title }),
519
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 mt-4 flex gap-1 overflow-x-auto border-b border-border", children: TABS.map((tk) => /* @__PURE__ */ jsxRuntime.jsx(
870
+ /* @__PURE__ */ jsxRuntime.jsxs(
520
871
  "button",
521
872
  {
522
- onClick: () => setTab(tk),
523
- className: `-mb-px whitespace-nowrap border-b-2 px-3 py-2 text-sm font-medium ${tab === tk ? "border-primary text-foreground" : "border-transparent text-muted-foreground hover:text-foreground"}`,
524
- children: t(`courses.editor.tab.${tk}`) || tk
525
- },
526
- tk
527
- )) }),
528
- tab === "details" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-xl space-y-4", children: [
529
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldTitle") || "Title", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: course.title, onChange: (e) => patchCourse({ title: e.target.value }) }) }),
530
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldSubtitle") || "Subtitle", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: course.subtitle ?? "", onChange: (e) => patchCourse({ subtitle: e.target.value }) }) }),
531
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldDescription") || "Description", children: /* @__PURE__ */ jsxRuntime.jsx(
532
- "textarea",
873
+ onClick: () => navigateTo("/courses"),
874
+ className: "mb-4 inline-flex items-center gap-1.5 text-sm text-muted-foreground transition-colors hover:text-foreground",
875
+ children: [
876
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ArrowLeft, { className: "h-4 w-4" }),
877
+ " ",
878
+ t("courses.editor.back") || "Back to courses"
879
+ ]
880
+ }
881
+ ),
882
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex flex-wrap items-center gap-x-3 gap-y-2", children: [
883
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-foreground", children: course.title || (t("courses.editor.untitledCourse") || "Untitled course") }),
884
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: course.status === "published" ? "success" : course.status === "archived" ? "secondary" : "outline", children: t(`courses.status.${course.status}`) || course.status }),
885
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "outline", children: t(`courses.editor.delivery.${course.deliveryType}`) || course.deliveryType })
886
+ ] }),
887
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Tabs, { value: tab, onValueChange: (v) => setTab(v), children: [
888
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "-mx-1 mb-6 overflow-x-auto px-1 pb-1", children: /* @__PURE__ */ jsxRuntime.jsx(ui.TabsList, { className: "h-auto flex-nowrap", children: TABS.map((tk) => {
889
+ const Icon = TAB_ICON[tk];
890
+ return /* @__PURE__ */ jsxRuntime.jsxs(ui.TabsTrigger, { value: tk, className: "gap-1.5 whitespace-nowrap py-1.5", children: [
891
+ /* @__PURE__ */ jsxRuntime.jsx(Icon, { className: "h-3.5 w-3.5" }),
892
+ t(`courses.editor.tab.${tk}`) || tk
893
+ ] }, tk);
894
+ }) }) }),
895
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabsContent, { value: "details", className: "space-y-5", children: [
896
+ /* @__PURE__ */ jsxRuntime.jsx(SectionCard, { title: t("courses.editor.sectionBasics") || "Basics", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
897
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldTitle") || "Title", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: course.title, onChange: (e) => patchCourse({ title: e.target.value }) }) }),
898
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldSubtitle") || "Subtitle", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { value: course.subtitle ?? "", onChange: (e) => patchCourse({ subtitle: e.target.value }) }) }),
899
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldDescription") || "Description", children: /* @__PURE__ */ jsxRuntime.jsx(
900
+ Textarea,
901
+ {
902
+ value: course.description ?? "",
903
+ onChange: (e) => patchCourse({ description: e.target.value }),
904
+ placeholder: t("courses.editor.descriptionPlaceholder") || "What will students learn?"
905
+ }
906
+ ) }),
907
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldDelivery") || "Format", hint: t("courses.editor.deliveryHint") || "How this course is delivered.", children: /* @__PURE__ */ jsxRuntime.jsx(
908
+ ui.SegmentedControl,
909
+ {
910
+ size: "md",
911
+ options: deliveryOptions,
912
+ value: course.deliveryType,
913
+ onChange: (v) => patchCourse({ deliveryType: v }),
914
+ "aria-label": t("courses.editor.fieldDelivery") || "Format"
915
+ }
916
+ ) })
917
+ ] }) }),
918
+ /* @__PURE__ */ jsxRuntime.jsx(SectionCard, { title: t("courses.editor.sectionThumbnail") || "Cover image", description: t("courses.editor.thumbnailHint") || "Shown on the storefront and in listings. 1280\xD7720 recommended.", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-start", children: [
919
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "aspect-video w-full shrink-0 overflow-hidden rounded-lg border border-border bg-muted sm:w-56", children: course.thumbnailUrl ? (
920
+ // eslint-disable-next-line @next/next/no-img-element
921
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" })
922
+ ) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1 text-muted-foreground", children: [
923
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ImagePlus, { className: "h-6 w-6" }),
924
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs", children: t("courses.editor.thumbnailEmpty") || "No image" })
925
+ ] }) }),
926
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex-1 space-y-3", children: [
927
+ /* @__PURE__ */ jsxRuntime.jsx("input", { ref: fileRef, type: "file", accept: "image/*", className: "hidden", onChange: onPickThumbnail }),
928
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", disabled: uploading, onClick: () => fileRef.current?.click(), children: [
929
+ uploading ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.UploadCloud, { className: "h-4 w-4" }),
930
+ uploading ? t("courses.editor.uploading") || "Uploading\u2026" : t("courses.editor.thumbnailUpload") || "Upload image"
931
+ ] }),
932
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.thumbnailUrl") || "Or paste an image URL", children: /* @__PURE__ */ jsxRuntime.jsx(
933
+ ui.Input,
934
+ {
935
+ value: course.thumbnailUrl ?? "",
936
+ placeholder: "https://\u2026",
937
+ onChange: (e) => patchCourse({ thumbnailUrl: e.target.value || null })
938
+ }
939
+ ) })
940
+ ] })
941
+ ] }) }),
942
+ /* @__PURE__ */ jsxRuntime.jsx(SectionCard, { title: t("courses.editor.sectionPricing") || "Pricing & status", children: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
943
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldPrice") || "Price", hint: course.currency, children: /* @__PURE__ */ jsxRuntime.jsx(
944
+ ui.Input,
945
+ {
946
+ type: "number",
947
+ min: 0,
948
+ step: "0.01",
949
+ inputMode: "decimal",
950
+ value: String(course.price),
951
+ onChange: (e) => patchCourse({ price: Number(e.target.value) || 0 })
952
+ }
953
+ ) }),
954
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldStatus") || "Status", children: /* @__PURE__ */ jsxRuntime.jsxs(ui.Select, { value: course.status, onValueChange: (v) => patchCourse({ status: v }), children: [
955
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
956
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectContent, { children: STATUSES.map((s) => /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: s, children: t(`courses.status.${s}`) || s }, s)) })
957
+ ] }) })
958
+ ] }) })
959
+ ] }),
960
+ /* @__PURE__ */ jsxRuntime.jsx(ui.TabsContent, { value: "curriculum", className: "space-y-4", children: /* @__PURE__ */ jsxRuntime.jsx(
961
+ CurriculumBuilder,
533
962
  {
534
- className: "min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
535
- value: course.description ?? "",
536
- onChange: (e) => patchCourse({ description: e.target.value })
963
+ t,
964
+ modules,
965
+ lessons,
966
+ autoFocusId,
967
+ onAutoFocused: () => setAutoFocusId(null),
968
+ onAddModule: addModule,
969
+ onRenameModule: renameModule,
970
+ onAddLesson: addLesson,
971
+ onPatchLesson: patchLesson,
972
+ onRequestDelete: (kind, delId) => setPendingDelete({ kind, id: delId }),
973
+ onReorderModules: reorderModules,
974
+ onReorderLessons: reorderLessons
537
975
  }
538
976
  ) }),
539
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
540
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldPrice") || "Price", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "number", value: String(course.price), onChange: (e) => patchCourse({ price: Number(e.target.value) || 0 }) }) }),
541
- /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldStatus") || "Status", children: /* @__PURE__ */ jsxRuntime.jsx(
542
- "select",
977
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabsContent, { value: "offers", className: "space-y-4", children: [
978
+ /* @__PURE__ */ jsxRuntime.jsx(InfoNote, { icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Info, { className: "h-4 w-4" }), children: t("courses.offers.help") || "Price points for this course. Sales run through orders and the Financeiro module." }),
979
+ offers.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
980
+ EmptyState,
543
981
  {
544
- className: "h-10 rounded-md border border-input bg-background px-3 text-sm",
545
- value: course.status,
546
- onChange: (e) => patchCourse({ status: e.target.value }),
547
- children: STATUSES.map((s) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: s, children: t(`courses.status.${s}`) || s }, s))
982
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Tag, { className: "h-5 w-5" }),
983
+ title: t("courses.offers.empty") || "No offers yet",
984
+ description: t("courses.offers.emptyHint") || "Create an offer to sell this course.",
985
+ action: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: addOffer, children: [
986
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
987
+ t("courses.offers.firstOffer") || "Create first offer"
988
+ ] })
548
989
  }
549
- ) })
990
+ ) : /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
991
+ offers.map((offer) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-border bg-card p-4 shadow-sm md:p-5", children: [
992
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
993
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "outline", className: "gap-1", children: offer.kind === "subscription" ? t("courses.offers.subscription") || "Subscription" : t("courses.offers.oneTime") || "One-time" }),
994
+ /* @__PURE__ */ jsxRuntime.jsx(
995
+ "button",
996
+ {
997
+ onClick: () => deleteOffer(offer.id),
998
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground/60 transition-colors hover:bg-destructive/10 hover:text-destructive",
999
+ "aria-label": t("courses.offers.remove") || "Remove",
1000
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
1001
+ }
1002
+ )
1003
+ ] }),
1004
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-[1fr_140px_160px]", children: [
1005
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.offers.name") || "Offer name", children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { defaultValue: offer.name, onBlur: (e) => patchOffer(offer.id, { name: e.target.value }) }) }),
1006
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.offers.price") || "Price", hint: offer.currency, children: /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "number", min: 0, step: "0.01", inputMode: "decimal", defaultValue: String(offer.price), onBlur: (e) => patchOffer(offer.id, { price: Number(e.target.value) || 0 }) }) }),
1007
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.offers.kind") || "Type", children: /* @__PURE__ */ jsxRuntime.jsxs(
1008
+ ui.Select,
1009
+ {
1010
+ value: offer.kind,
1011
+ onValueChange: (v) => patchOffer(offer.id, { kind: v, recurringInterval: v === "subscription" ? offer.recurringInterval ?? "month" : null }),
1012
+ children: [
1013
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
1014
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.SelectContent, { children: [
1015
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: "one_time", children: t("courses.offers.oneTime") || "One-time" }),
1016
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: "subscription", children: t("courses.offers.subscription") || "Subscription" })
1017
+ ] })
1018
+ ]
1019
+ }
1020
+ ) })
1021
+ ] }),
1022
+ offer.kind === "subscription" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mt-3 max-w-[200px]", children: /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.offers.interval") || "Billing interval", children: /* @__PURE__ */ jsxRuntime.jsxs(
1023
+ ui.Select,
1024
+ {
1025
+ value: offer.recurringInterval ?? "month",
1026
+ onValueChange: (v) => patchOffer(offer.id, { recurringInterval: v }),
1027
+ children: [
1028
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectTrigger, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.SelectValue, {}) }),
1029
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.SelectContent, { children: [
1030
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: "month", children: t("courses.interval.month") || "month" }),
1031
+ /* @__PURE__ */ jsxRuntime.jsx(ui.SelectItem, { value: "year", children: t("courses.interval.year") || "year" })
1032
+ ] })
1033
+ ]
1034
+ }
1035
+ ) }) }),
1036
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mt-4 rounded-lg border border-dashed border-border bg-muted/30 p-3", children: [
1037
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "mb-2.5 flex items-center gap-1.5 text-xs text-muted-foreground", children: [
1038
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3.5 w-3.5" }),
1039
+ t("courses.offers.checkoutFlagsNote") || "Apply once the public checkout ships (coming soon)."
1040
+ ] }),
1041
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center gap-x-5 gap-y-2", children: [
1042
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-muted-foreground", children: [
1043
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: offer.isDefault, onChange: (v) => patchOffer(offer.id, { isDefault: v }) }),
1044
+ t("courses.offers.default") || "Default"
1045
+ ] }),
1046
+ /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-muted-foreground", children: [
1047
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Checkbox, { checked: offer.isOrderBump, onChange: (v) => patchOffer(offer.id, { isOrderBump: v }) }),
1048
+ t("courses.offers.bump") || "Order bump"
1049
+ ] })
1050
+ ] })
1051
+ ] })
1052
+ ] }, offer.id)),
1053
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", onClick: addOffer, className: "w-full border-dashed", children: [
1054
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
1055
+ t("courses.offers.add") || "Add offer"
1056
+ ] })
1057
+ ] })
550
1058
  ] }),
551
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: saveDetails, children: t("courses.editor.save") || "Save" })
552
- ] }),
553
- tab === "curriculum" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
554
- modules.map((mod) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-border bg-card p-4", children: [
555
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
556
- /* @__PURE__ */ jsxRuntime.jsx(
557
- ui.Input,
1059
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.TabsContent, { value: "members", className: "space-y-4", children: [
1060
+ /* @__PURE__ */ jsxRuntime.jsx(InfoNote, { icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "h-4 w-4" }), children: t("courses.editor.membersHelp") || "Buyers are enrolled automatically through orders and get access to all of this course\u2019s content." }),
1061
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1062
+ /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [
1063
+ t("courses.editor.enrolledStudents") || "Enrolled students",
1064
+ " (",
1065
+ students.length,
1066
+ ")"
1067
+ ] }),
1068
+ students.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
1069
+ EmptyState,
558
1070
  {
559
- className: "font-medium",
560
- defaultValue: mod.title,
561
- onBlur: (e) => renameModule(mod.id, e.target.value),
562
- "aria-label": t("courses.editor.moduleTitle") || "Module title"
1071
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Users, { className: "h-5 w-5" }),
1072
+ title: t("courses.editor.membersEmpty") || "No students yet",
1073
+ description: t("courses.editor.membersEmptyHint") || "Buyers are enrolled here automatically after a paid order."
563
1074
  }
564
- ),
565
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "sm", onClick: () => deleteModule(mod.id), children: t("courses.editor.deleteModule") || "Delete module" })
1075
+ ) : /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border bg-card", children: students.map((s) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
1076
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-full bg-muted text-xs font-semibold uppercase text-muted-foreground", "aria-hidden": true, children: (s.name || s.email || "?").charAt(0) }),
1077
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
1078
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate font-medium text-foreground", children: s.name }),
1079
+ s.email && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "truncate text-xs text-muted-foreground", children: s.email })
1080
+ ] }),
1081
+ s.since && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: new Date(s.since).toLocaleDateString() })
1082
+ ] }, s.key)) })
566
1083
  ] }),
567
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
568
- lessons.filter((l) => l.moduleId === mod.id).map((lesson) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 rounded-md border border-border p-3 md:grid-cols-[1fr_1fr_90px_auto]", children: [
569
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { defaultValue: lesson.title, placeholder: t("courses.editor.lessonTitle") || "Lesson title", onBlur: (e) => patchLesson(lesson.id, { title: e.target.value }) }),
570
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { defaultValue: lesson.videoUrl, placeholder: t("courses.editor.lessonVideo") || "Video URL (embed)", onBlur: (e) => patchLesson(lesson.id, { videoUrl: e.target.value }) }),
571
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "number", defaultValue: String(Math.round(lesson.durationSec / 60)), onBlur: (e) => patchLesson(lesson.id, { durationSec: (Number(e.target.value) || 0) * 60 }), "aria-label": t("courses.editor.lessonDuration") || "Duration (min)" }),
572
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "sm", onClick: () => deleteLesson(lesson.id), children: t("courses.editor.deleteLesson") || "Remove" })
573
- ] }, lesson.id)),
574
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => addLesson(mod.id), children: [
575
- "+ ",
576
- t("courses.editor.addLesson") || "Add lesson"
577
- ] })
578
- ] })
579
- ] }, mod.id)),
580
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: addModule, children: t("courses.editor.addModule") || "Add module" })
581
- ] }),
582
- tab === "offers" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3", children: [
583
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t("courses.offers.help") || "Price points a customer can buy. Mark one default; order-bumps show on other checkouts." }),
584
- offers.map((offer) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "grid grid-cols-1 gap-2 rounded-xl border border-border bg-card p-3 md:grid-cols-[1fr_120px_130px_auto]", children: [
585
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { defaultValue: offer.name, onBlur: (e) => patchOffer(offer.id, { name: e.target.value }), "aria-label": t("courses.offers.name") || "Offer name" }),
586
- /* @__PURE__ */ jsxRuntime.jsx(ui.Input, { type: "number", defaultValue: String(offer.price), onBlur: (e) => patchOffer(offer.id, { price: Number(e.target.value) || 0 }), "aria-label": t("courses.offers.price") || "Price" }),
587
- /* @__PURE__ */ jsxRuntime.jsxs(
588
- "select",
589
- {
590
- className: "h-10 rounded-md border border-input bg-background px-2 text-sm",
591
- value: offer.kind,
592
- onChange: (e) => patchOffer(offer.id, { kind: e.target.value, recurringInterval: e.target.value === "subscription" ? offer.recurringInterval ?? "month" : null }),
593
- children: [
594
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "one_time", children: t("courses.offers.oneTime") || "One-time" }),
595
- /* @__PURE__ */ jsxRuntime.jsx("option", { value: "subscription", children: t("courses.offers.subscription") || "Subscription" })
596
- ]
597
- }
598
- ),
599
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [
600
- offer.isDefault && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "secondary", children: t("courses.offers.default") || "Default" }),
601
- offer.isOrderBump && /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "outline", children: t("courses.offers.bump") || "Bump" }),
602
- /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "sm", onClick: () => deleteOffer(offer.id), children: t("courses.offers.remove") || "Remove" })
1084
+ offers.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
1085
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("courses.editor.accessUnlockedBy") || "Access is unlocked by" }),
1086
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border bg-card", children: offers.map((offer) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
1087
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg bg-muted text-muted-foreground", "aria-hidden": true, children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Lock, { className: "h-4 w-4" }) }),
1088
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: offer.name }),
1089
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "shrink-0 text-sm tabular-nums text-muted-foreground", children: formatMoney(offer.price, offer.currency) })
1090
+ ] }, offer.id)) })
603
1091
  ] })
604
- ] }, offer.id)),
605
- /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: addOffer, children: [
606
- "+ ",
607
- t("courses.offers.add") || "Add offer"
608
1092
  ] })
609
1093
  ] }),
610
- tab === "members" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 text-sm", children: [
611
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: t("courses.editor.membersHelp") || "Buyers of this course are added to its members area automatically. Access groups per offer:" }),
612
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-border bg-card divide-y divide-border", children: offers.map((offer) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center justify-between px-4 py-3", children: [
613
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: offer.name }),
614
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: formatMoney(offer.price, offer.currency) }),
615
- /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: "secondary", children: t("courses.editor.defaultGroup") || "Default group" })
616
- ] }, offer.id)) })
617
- ] }),
618
- tab === "settings" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-xl space-y-4 text-sm", children: [
619
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: t("courses.editor.settingsHelp") || "Payment methods offered at checkout (Stripe)." }),
620
- [
621
- ["card", t("courses.editor.payCard") || "Credit card (up to 12x)"],
622
- ["pix", t("courses.editor.payPix") || "Pix"],
623
- ["boleto", t("courses.editor.payBoleto") || "Boleto"]
624
- ].map(([k, label]) => /* @__PURE__ */ jsxRuntime.jsxs("label", { className: "flex items-center gap-3 rounded-lg border border-border bg-card px-4 py-3", children: [
625
- /* @__PURE__ */ jsxRuntime.jsx("input", { type: "checkbox", defaultChecked: true, className: "h-4 w-4" }),
626
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: label })
627
- ] }, k)),
628
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-xs text-muted-foreground", children: t("courses.editor.settingsNote") || "Method availability depends on Stripe in your region; wired in Stage 3." })
629
- ] }),
630
- tab === "checkout" && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-dashed border-border p-10 text-center text-sm text-muted-foreground", children: t("courses.editor.checkoutSoon") || "Custom checkout builder comes in a later milestone. A default checkout is generated per offer." }),
631
- tab === "links" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
632
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: t("courses.editor.linksHelp") || "Shareable checkout links, one per offer." }),
633
- /* @__PURE__ */ jsxRuntime.jsx("div", { className: "rounded-xl border border-border bg-card divide-y divide-border", children: offers.map((offer) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-col gap-1 px-4 py-3 md:flex-row md:items-center md:justify-between", children: [
634
- /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: offer.name }),
635
- /* @__PURE__ */ jsxRuntime.jsxs("code", { className: "rounded bg-muted px-2 py-1 text-xs text-muted-foreground", children: [
636
- "/checkout/",
637
- course.slug,
638
- "?offer=",
639
- offer.id
640
- ] })
641
- ] }, offer.id)) })
642
- ] })
1094
+ /* @__PURE__ */ jsxRuntime.jsx(
1095
+ ui.ConfirmDialog,
1096
+ {
1097
+ open: pendingDelete !== null,
1098
+ variant: "destructive",
1099
+ title: pendingDelete?.kind === "module" ? t("courses.editor.deleteModuleConfirm") || "Delete this module?" : t("courses.editor.deleteLessonConfirm") || "Remove this lesson?",
1100
+ description: pendingDelete?.kind === "module" ? t("courses.editor.deleteModuleConfirmBody") || "Its lessons will be removed too. This cannot be undone." : t("courses.editor.deleteLessonConfirmBody") || "This cannot be undone.",
1101
+ confirmLabel: t("courses.editor.confirmDelete") || "Delete",
1102
+ cancelLabel: t("courses.editor.cancel") || "Cancel",
1103
+ onConfirm: confirmDelete,
1104
+ onCancel: () => setPendingDelete(null)
1105
+ }
1106
+ )
643
1107
  ] });
644
1108
  }
645
1109
  CourseEditorPage.displayName = "CourseEditorPage";
646
- function Field({ label, children }) {
647
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
648
- /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium text-foreground", children: label }),
649
- children
1110
+ function formatDuration(sec, t) {
1111
+ const min = Math.max(0, Math.round(sec / 60));
1112
+ const mLabel = t("courses.editor.minShort") || "min";
1113
+ if (min < 60) return `${min} ${mLabel}`;
1114
+ const h = Math.floor(min / 60);
1115
+ const rem = min % 60;
1116
+ const hLabel = t("courses.editor.hourShort") || "h";
1117
+ return rem ? `${h} ${hLabel} ${rem} ${mLabel}` : `${h} ${hLabel}`;
1118
+ }
1119
+ function CurriculumBuilder({
1120
+ t,
1121
+ modules,
1122
+ lessons,
1123
+ autoFocusId,
1124
+ onAutoFocused,
1125
+ onAddModule,
1126
+ onRenameModule,
1127
+ onAddLesson,
1128
+ onPatchLesson,
1129
+ onRequestDelete,
1130
+ onReorderModules,
1131
+ onReorderLessons
1132
+ }) {
1133
+ const [collapsed, setCollapsed] = React3__namespace.useState(/* @__PURE__ */ new Set());
1134
+ const sensors = core$1.useSensors(
1135
+ core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 6 } }),
1136
+ core$1.useSensor(core$1.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
1137
+ );
1138
+ function toggleModule(id) {
1139
+ setCollapsed((prev) => {
1140
+ const next = new Set(prev);
1141
+ if (next.has(id)) next.delete(id);
1142
+ else next.add(id);
1143
+ return next;
1144
+ });
1145
+ }
1146
+ function handleModuleDragEnd(e) {
1147
+ const { active, over } = e;
1148
+ if (!over || active.id === over.id) return;
1149
+ const ids = modules.map((m) => m.id);
1150
+ const from = ids.indexOf(String(active.id));
1151
+ const to = ids.indexOf(String(over.id));
1152
+ if (from < 0 || to < 0) return;
1153
+ onReorderModules(sortable.arrayMove(ids, from, to));
1154
+ }
1155
+ if (modules.length === 0) {
1156
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1157
+ /* @__PURE__ */ jsxRuntime.jsx(AutosaveHint, { t }),
1158
+ /* @__PURE__ */ jsxRuntime.jsx(
1159
+ EmptyState,
1160
+ {
1161
+ icon: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Layers, { className: "h-5 w-5" }),
1162
+ title: t("courses.editor.modulesEmpty") || "No modules yet",
1163
+ description: t("courses.editor.modulesEmptyHint") || "Create a module to start organizing your lessons.",
1164
+ action: /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { onClick: onAddModule, children: [
1165
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
1166
+ t("courses.editor.firstModule") || "Create first module"
1167
+ ] })
1168
+ }
1169
+ )
1170
+ ] });
1171
+ }
1172
+ const totalLessons = lessons.length;
1173
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1174
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex flex-wrap items-center justify-between gap-2", children: [
1175
+ /* @__PURE__ */ jsxRuntime.jsx(AutosaveHint, { t }),
1176
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "text-xs text-muted-foreground tabular-nums", children: [
1177
+ modules.length,
1178
+ " ",
1179
+ modules.length === 1 ? t("courses.editor.moduleLabel") || "module" : t("courses.editor.moduleLabelPlural") || "modules",
1180
+ " \xB7 ",
1181
+ totalLessons,
1182
+ " ",
1183
+ totalLessons === 1 ? t("courses.editor.lessonUnitOne") || "lesson" : t("courses.editor.lessonUnitOther") || "lessons"
1184
+ ] })
1185
+ ] }),
1186
+ /* @__PURE__ */ jsxRuntime.jsx(core$1.DndContext, { sensors, collisionDetection: core$1.closestCenter, onDragEnd: handleModuleDragEnd, children: /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: modules.map((m) => m.id), strategy: sortable.verticalListSortingStrategy, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-3", children: modules.map((mod, i) => /* @__PURE__ */ jsxRuntime.jsx(
1187
+ SortableModuleCard,
1188
+ {
1189
+ t,
1190
+ index: i,
1191
+ mod,
1192
+ lessons: lessons.filter((l) => l.moduleId === mod.id),
1193
+ expanded: !collapsed.has(mod.id),
1194
+ onToggle: () => toggleModule(mod.id),
1195
+ autoFocusId,
1196
+ onAutoFocused,
1197
+ onRenameModule,
1198
+ onAddLesson,
1199
+ onPatchLesson,
1200
+ onRequestDelete,
1201
+ onReorderLessons
1202
+ },
1203
+ mod.id
1204
+ )) }) }) }),
1205
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", onClick: onAddModule, className: "w-full border-dashed", children: [
1206
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
1207
+ t("courses.editor.newModule") || "New module"
1208
+ ] })
650
1209
  ] });
651
1210
  }
652
- function PageContainer({ title, subtitle, actions, children }) {
653
- return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-6xl p-6 md:p-8", children: [
654
- /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-start justify-between gap-4", children: [
655
- /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
656
- /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-foreground", children: title }),
657
- subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
658
- ] }),
659
- actions
1211
+ function SortableModuleCard({
1212
+ t,
1213
+ index,
1214
+ mod,
1215
+ lessons,
1216
+ expanded,
1217
+ onToggle,
1218
+ autoFocusId,
1219
+ onAutoFocused,
1220
+ onRenameModule,
1221
+ onAddLesson,
1222
+ onPatchLesson,
1223
+ onRequestDelete,
1224
+ onReorderLessons
1225
+ }) {
1226
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = sortable.useSortable({ id: mod.id });
1227
+ const sensors = core$1.useSensors(
1228
+ core$1.useSensor(core$1.PointerSensor, { activationConstraint: { distance: 6 } }),
1229
+ core$1.useSensor(core$1.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
1230
+ );
1231
+ const style = {
1232
+ transform: utilities.CSS.Transform.toString(transform),
1233
+ transition,
1234
+ opacity: isDragging ? 0.6 : 1,
1235
+ zIndex: isDragging ? 20 : void 0
1236
+ };
1237
+ const count = lessons.length;
1238
+ const totalSec = lessons.reduce((s, l) => s + (l.durationSec || 0), 0);
1239
+ function handleLessonDragEnd(e) {
1240
+ const { active, over } = e;
1241
+ if (!over || active.id === over.id) return;
1242
+ const ids = lessons.map((l) => l.id);
1243
+ const from = ids.indexOf(String(active.id));
1244
+ const to = ids.indexOf(String(over.id));
1245
+ if (from < 0 || to < 0) return;
1246
+ onReorderLessons(mod.id, sortable.arrayMove(ids, from, to));
1247
+ }
1248
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1249
+ "div",
1250
+ {
1251
+ ref: setNodeRef,
1252
+ style,
1253
+ className: "overflow-hidden rounded-xl border border-border bg-card shadow-sm",
1254
+ children: [
1255
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-2 py-2 sm:gap-2 sm:px-3", children: [
1256
+ /* @__PURE__ */ jsxRuntime.jsx(
1257
+ "button",
1258
+ {
1259
+ ...attributes,
1260
+ ...listeners,
1261
+ className: "hidden h-8 w-6 shrink-0 cursor-grab touch-none items-center justify-center rounded-md text-muted-foreground/40 transition-colors hover:text-muted-foreground active:cursor-grabbing sm:flex",
1262
+ "aria-label": t("courses.editor.dragToReorder") || "Drag to reorder",
1263
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { className: "h-4 w-4" })
1264
+ }
1265
+ ),
1266
+ /* @__PURE__ */ jsxRuntime.jsx(
1267
+ "button",
1268
+ {
1269
+ onClick: onToggle,
1270
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-muted hover:text-foreground",
1271
+ "aria-label": expanded ? t("courses.editor.collapse") || "Collapse" : t("courses.editor.expand") || "Expand",
1272
+ "aria-expanded": expanded,
1273
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.ChevronDown, { className: "h-4 w-4 transition-transform " + (expanded ? "" : "-rotate-90") })
1274
+ }
1275
+ ),
1276
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden shrink-0 items-center rounded-md bg-muted px-2 py-1 text-xs font-semibold tabular-nums text-muted-foreground sm:inline-flex", children: String(index + 1).padStart(2, "0") }),
1277
+ /* @__PURE__ */ jsxRuntime.jsx(
1278
+ TitleField,
1279
+ {
1280
+ value: mod.title,
1281
+ autoFocus: autoFocusId === mod.id,
1282
+ onAutoFocused,
1283
+ onCommit: (v) => onRenameModule(mod.id, v),
1284
+ placeholder: t("courses.editor.moduleTitle") || "Module title",
1285
+ ariaLabel: t("courses.editor.moduleTitle") || "Module title",
1286
+ className: "text-sm font-semibold"
1287
+ }
1288
+ ),
1289
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden shrink-0 text-xs text-muted-foreground tabular-nums md:inline", children: count > 0 ? `${count} ${count === 1 ? t("courses.editor.lessonUnitOne") || "lesson" : t("courses.editor.lessonUnitOther") || "lessons"} \xB7 ${formatDuration(totalSec, t)}` : t("courses.editor.noLessons") || "No lessons" }),
1290
+ /* @__PURE__ */ jsxRuntime.jsx(
1291
+ "button",
1292
+ {
1293
+ onClick: () => onRequestDelete("module", mod.id),
1294
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground/60 transition-colors hover:bg-destructive/10 hover:text-destructive",
1295
+ "aria-label": t("courses.editor.deleteModule") || "Delete module",
1296
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
1297
+ }
1298
+ )
1299
+ ] }),
1300
+ expanded && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "border-t border-border/60 bg-muted/20 p-2 sm:p-3", children: [
1301
+ count === 0 ? /* @__PURE__ */ jsxRuntime.jsxs(
1302
+ "button",
1303
+ {
1304
+ onClick: () => onAddLesson(mod.id),
1305
+ className: "flex w-full items-center justify-center gap-2 rounded-lg border border-dashed border-border px-3 py-4 text-sm text-muted-foreground transition-colors hover:border-primary/40 hover:bg-card hover:text-foreground",
1306
+ children: [
1307
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
1308
+ " ",
1309
+ t("courses.editor.firstLesson") || "Add the first lesson"
1310
+ ]
1311
+ }
1312
+ ) : /* @__PURE__ */ jsxRuntime.jsx(core$1.DndContext, { sensors, collisionDetection: core$1.closestCenter, onDragEnd: handleLessonDragEnd, children: /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: lessons.map((l) => l.id), strategy: sortable.verticalListSortingStrategy, children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "space-y-1.5", children: lessons.map((lesson) => /* @__PURE__ */ jsxRuntime.jsx(
1313
+ SortableLessonRow,
1314
+ {
1315
+ t,
1316
+ lesson,
1317
+ autoFocusId,
1318
+ onAutoFocused,
1319
+ onPatchLesson,
1320
+ onRequestDelete
1321
+ },
1322
+ lesson.id
1323
+ )) }) }) }),
1324
+ count > 0 && /* @__PURE__ */ jsxRuntime.jsxs(
1325
+ "button",
1326
+ {
1327
+ onClick: () => onAddLesson(mod.id),
1328
+ className: "mt-2 flex items-center gap-1.5 rounded-md px-2 py-1.5 text-sm font-medium text-primary transition-colors hover:bg-primary/10",
1329
+ children: [
1330
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Plus, { className: "h-4 w-4" }),
1331
+ " ",
1332
+ t("courses.editor.newLesson") || "New lesson"
1333
+ ]
1334
+ }
1335
+ )
1336
+ ] })
1337
+ ]
1338
+ }
1339
+ );
1340
+ }
1341
+ function SortableLessonRow({ t, lesson, autoFocusId, onAutoFocused, onPatchLesson, onRequestDelete }) {
1342
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = sortable.useSortable({ id: lesson.id });
1343
+ const [open, setOpen] = React3__namespace.useState(false);
1344
+ const [videoUrl, setVideoUrl] = React3__namespace.useState(lesson.videoUrl);
1345
+ const [durationSec, setDurationSec] = React3__namespace.useState(lesson.durationSec);
1346
+ React3__namespace.useEffect(() => {
1347
+ setVideoUrl(lesson.videoUrl);
1348
+ }, [lesson.videoUrl]);
1349
+ React3__namespace.useEffect(() => {
1350
+ setDurationSec(lesson.durationSec);
1351
+ }, [lesson.durationSec]);
1352
+ const hasVideo = videoUrl.trim().length > 0;
1353
+ const style = {
1354
+ transform: utilities.CSS.Transform.toString(transform),
1355
+ transition,
1356
+ opacity: isDragging ? 0.6 : 1,
1357
+ zIndex: isDragging ? 10 : void 0
1358
+ };
1359
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1360
+ "div",
1361
+ {
1362
+ ref: setNodeRef,
1363
+ style,
1364
+ className: "group rounded-lg border bg-card transition-colors " + (open ? "border-border" : "border-transparent hover:border-border"),
1365
+ children: [
1366
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1.5 px-1.5 py-1.5 sm:gap-2 sm:px-2", children: [
1367
+ /* @__PURE__ */ jsxRuntime.jsx(
1368
+ "button",
1369
+ {
1370
+ ...attributes,
1371
+ ...listeners,
1372
+ className: "hidden h-8 w-5 shrink-0 cursor-grab touch-none items-center justify-center text-muted-foreground/40 transition-colors hover:text-muted-foreground active:cursor-grabbing sm:flex",
1373
+ "aria-label": t("courses.editor.dragToReorder") || "Drag to reorder",
1374
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.GripVertical, { className: "h-4 w-4" })
1375
+ }
1376
+ ),
1377
+ /* @__PURE__ */ jsxRuntime.jsx(
1378
+ "span",
1379
+ {
1380
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-lg " + (hasVideo ? "bg-primary/10 text-primary" : "bg-muted text-muted-foreground"),
1381
+ "aria-hidden": true,
1382
+ children: hasVideo ? /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Video, { className: "h-4 w-4" }) : /* @__PURE__ */ jsxRuntime.jsx(lucideReact.FileText, { className: "h-4 w-4" })
1383
+ }
1384
+ ),
1385
+ /* @__PURE__ */ jsxRuntime.jsx(
1386
+ TitleField,
1387
+ {
1388
+ value: lesson.title,
1389
+ autoFocus: autoFocusId === lesson.id,
1390
+ onAutoFocused,
1391
+ onCommit: (v) => onPatchLesson(lesson.id, { title: v }),
1392
+ placeholder: t("courses.editor.lessonTitle") || "Lesson title",
1393
+ ariaLabel: t("courses.editor.lessonTitle") || "Lesson title",
1394
+ className: "text-sm"
1395
+ }
1396
+ ),
1397
+ /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "hidden shrink-0 items-center gap-1 rounded-full bg-muted px-2 py-0.5 text-xs tabular-nums text-muted-foreground sm:inline-flex", children: [
1398
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Clock, { className: "h-3 w-3" }),
1399
+ Math.round(durationSec / 60),
1400
+ " ",
1401
+ t("courses.editor.minShort") || "min"
1402
+ ] }),
1403
+ /* @__PURE__ */ jsxRuntime.jsxs(
1404
+ "button",
1405
+ {
1406
+ onClick: () => setOpen((v) => !v),
1407
+ className: "inline-flex shrink-0 items-center gap-1 rounded-full border px-2 py-1 text-xs font-medium transition-colors " + (hasVideo ? "border-border text-muted-foreground hover:text-foreground" : "border-dashed border-border text-muted-foreground hover:border-primary/40 hover:text-foreground") + " opacity-100 sm:opacity-0 sm:group-hover:opacity-100 " + (open ? "sm:opacity-100" : ""),
1408
+ "aria-label": t("courses.editor.editLesson") || "Edit lesson",
1409
+ "aria-expanded": open,
1410
+ children: [
1411
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.PlayCircle, { className: "h-3.5 w-3.5" }),
1412
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "hidden sm:inline", children: hasVideo ? t("courses.editor.videoSet") || "Video" : t("courses.editor.addVideo") || "Add video" })
1413
+ ]
1414
+ }
1415
+ ),
1416
+ /* @__PURE__ */ jsxRuntime.jsx(
1417
+ "button",
1418
+ {
1419
+ onClick: () => onRequestDelete("lesson", lesson.id),
1420
+ className: "flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground/60 transition-colors hover:bg-destructive/10 hover:text-destructive sm:opacity-0 sm:group-hover:opacity-100",
1421
+ "aria-label": t("courses.editor.deleteLesson") || "Remove lesson",
1422
+ children: /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Trash2, { className: "h-4 w-4" })
1423
+ }
1424
+ )
1425
+ ] }),
1426
+ open && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-3 border-t border-border/60 px-3 py-3 sm:pl-11", children: [
1427
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
1428
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("courses.editor.videoUrlLabel") || "Video URL" }),
1429
+ /* @__PURE__ */ jsxRuntime.jsx(
1430
+ ui.Input,
1431
+ {
1432
+ defaultValue: lesson.videoUrl,
1433
+ placeholder: t("courses.editor.videoUrlPlaceholder") || "Paste the video link (YouTube, Vimeo\u2026)",
1434
+ onBlur: (e) => {
1435
+ setVideoUrl(e.target.value);
1436
+ onPatchLesson(lesson.id, { videoUrl: e.target.value });
1437
+ },
1438
+ "aria-label": t("courses.editor.videoUrlLabel") || "Video URL"
1439
+ }
1440
+ )
1441
+ ] }),
1442
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-[160px] space-y-1.5", children: [
1443
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-xs font-medium text-muted-foreground", children: t("courses.editor.lessonDuration") || "Duration (min)" }),
1444
+ /* @__PURE__ */ jsxRuntime.jsx(
1445
+ ui.Input,
1446
+ {
1447
+ type: "number",
1448
+ min: 0,
1449
+ inputMode: "numeric",
1450
+ defaultValue: String(Math.round(lesson.durationSec / 60)),
1451
+ onBlur: (e) => {
1452
+ const s = (Number(e.target.value) || 0) * 60;
1453
+ setDurationSec(s);
1454
+ onPatchLesson(lesson.id, { durationSec: s });
1455
+ },
1456
+ "aria-label": t("courses.editor.lessonDuration") || "Duration (min)"
1457
+ }
1458
+ )
1459
+ ] })
1460
+ ] })
1461
+ ]
1462
+ }
1463
+ );
1464
+ }
1465
+ function TitleField({ value, autoFocus, onAutoFocused, onCommit, placeholder, ariaLabel, className }) {
1466
+ const ref = React3__namespace.useRef(null);
1467
+ React3__namespace.useEffect(() => {
1468
+ if (autoFocus && ref.current) {
1469
+ ref.current.focus();
1470
+ ref.current.select();
1471
+ onAutoFocused?.();
1472
+ }
1473
+ }, [autoFocus]);
1474
+ return /* @__PURE__ */ jsxRuntime.jsx(
1475
+ "input",
1476
+ {
1477
+ ref,
1478
+ defaultValue: value,
1479
+ placeholder,
1480
+ "aria-label": ariaLabel,
1481
+ onBlur: (e) => onCommit(e.target.value),
1482
+ onKeyDown: (e) => {
1483
+ if (e.key === "Enter") e.currentTarget.blur();
1484
+ },
1485
+ className: "min-w-0 flex-1 rounded-md bg-transparent px-2 py-1.5 text-foreground outline-none transition-colors placeholder:text-muted-foreground/60 hover:bg-muted/60 focus:bg-background focus:ring-2 focus:ring-ring " + (className ?? "")
1486
+ }
1487
+ );
1488
+ }
1489
+ function Field({ label, hint, children }) {
1490
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
1491
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline justify-between gap-2", children: [
1492
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium text-foreground", children: label }),
1493
+ hint && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-xs text-muted-foreground", children: hint })
660
1494
  ] }),
661
1495
  children
662
1496
  ] });
663
1497
  }
664
- function StatCard({ label, value, hint }) {
665
- return /* @__PURE__ */ jsxRuntime.jsxs(ui.Card, { className: "p-5", children: [
666
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground", children: label }),
667
- /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-2xl font-bold text-foreground", children: value }),
668
- hint && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-xs text-muted-foreground", children: hint })
669
- ] });
1498
+ function Textarea(props) {
1499
+ const { className, ...rest } = props;
1500
+ return /* @__PURE__ */ jsxRuntime.jsx(
1501
+ "textarea",
1502
+ {
1503
+ className: "flex min-h-[96px] w-full rounded-input border border-input bg-card px-2 py-1.5 text-sm text-foreground shadow-[inset_0_1px_0_rgb(0_0_0_/0.06)] placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 " + (className ?? ""),
1504
+ ...rest
1505
+ }
1506
+ );
670
1507
  }
671
- function StatGrid({ children }) {
672
- return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-4", children });
1508
+ function AutosaveHint({ t }) {
1509
+ return /* @__PURE__ */ jsxRuntime.jsxs("p", { className: "flex items-center gap-1.5 text-xs text-muted-foreground", children: [
1510
+ /* @__PURE__ */ jsxRuntime.jsx(lucideReact.Check, { className: "h-3.5 w-3.5 text-success" }),
1511
+ t("courses.editor.autosaved") || "Changes are saved automatically."
1512
+ ] });
673
1513
  }
674
- function SimpleTable({ columns, rows, empty, loading }) {
675
- const cols = React2__namespace.useMemo(
676
- () => columns.map((c) => ({
677
- id: c.key,
678
- header: () => /* @__PURE__ */ jsxRuntime.jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.header }),
679
- cell: ({ row }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: c.align === "right" ? "text-right" : "", children: c.render(row.original) })
680
- })),
681
- [columns]
682
- );
683
- return /* @__PURE__ */ jsxRuntime.jsx(ui.DataTable, { columns: cols, data: rows, emptyMessage: empty, loading });
1514
+ function EditorSkeleton() {
1515
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl p-6 md:p-8", children: [
1516
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "mb-4 h-4 w-28" }),
1517
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "mb-6 h-8 w-64" }),
1518
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "mb-6 h-10 w-full" }),
1519
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-4", children: [
1520
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-40 w-full rounded-xl" }),
1521
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Skeleton, { className: "h-40 w-full rounded-xl" })
1522
+ ] })
1523
+ ] });
684
1524
  }
685
1525
  function statusVariant2(s) {
686
1526
  if (s === "paid") return "success";
@@ -688,42 +1528,173 @@ function statusVariant2(s) {
688
1528
  if (s === "refunded" || s === "chargeback") return "destructive";
689
1529
  return "outline";
690
1530
  }
1531
+ var salesCache = null;
691
1532
  function SalesPage() {
692
1533
  const t = core.useTranslation();
693
- const [orders, setOrders] = React2__namespace.useState(null);
694
- React2__namespace.useEffect(() => {
695
- void courses.getCoursesProvider().listOrders().then(setOrders);
1534
+ const [orders, setOrders] = React3__namespace.useState(salesCache);
1535
+ React3__namespace.useEffect(() => {
1536
+ if (salesCache) return;
1537
+ let alive = true;
1538
+ const p = courses.getCoursesProvider();
1539
+ void Promise.all([
1540
+ p.listCoreSales().catch(() => []),
1541
+ p.listOrders().catch(() => [])
1542
+ ]).then(([core, store]) => {
1543
+ if (!alive) return;
1544
+ const byId = /* @__PURE__ */ new Map();
1545
+ for (const o of store) byId.set(o.id, o);
1546
+ for (const o of core) byId.set(o.id, o);
1547
+ const list = [...byId.values()].sort((a, b) => a.createdAt < b.createdAt ? 1 : -1);
1548
+ salesCache = list;
1549
+ setOrders(list);
1550
+ });
1551
+ return () => {
1552
+ alive = false;
1553
+ };
696
1554
  }, []);
697
1555
  const paid = (orders ?? []).filter((o) => o.financialStatus === "paid");
698
1556
  const revenue = paid.reduce((s, o) => s + o.total, 0);
699
1557
  const currency = orders?.[0]?.currency ?? "BRL";
700
1558
  const columns = [
701
- { key: "date", header: t("courses.sales.date") || "Date", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: formatDate(o.createdAt) }) },
702
- { key: "customer", header: t("courses.sales.customer") || "Customer", render: (o) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
1559
+ { key: "date", header: t("courses.sales.date") || "Data", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: formatDate(o.createdAt) }) },
1560
+ { key: "customer", header: t("courses.sales.customer") || "Cliente", render: (o) => /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
703
1561
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "font-medium text-foreground", children: o.customerName ?? "\u2014" }),
704
1562
  /* @__PURE__ */ jsxRuntime.jsx("div", { className: "text-xs text-muted-foreground", children: o.customerEmail })
705
1563
  ] }) },
706
- { key: "method", header: t("courses.sales.method") || "Method", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "capitalize", children: o.paymentMethod ?? "\u2014" }) },
1564
+ { key: "method", header: t("courses.sales.method") || "Forma", render: (o) => /* @__PURE__ */ jsxRuntime.jsxs("span", { className: "capitalize", children: [
1565
+ o.paymentMethod ?? "\u2014",
1566
+ o.installmentCount && o.installmentCount > 1 ? ` \xB7 ${o.installmentCount}x` : ""
1567
+ ] }) },
707
1568
  { key: "status", header: t("courses.sales.status") || "Status", render: (o) => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: statusVariant2(o.financialStatus), children: t(`courses.financialStatus.${o.financialStatus}`) || o.financialStatus }) },
708
- { key: "fee", header: t("courses.sales.fee") || "Platform fee", align: "right", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: formatMoney(o.platformFee, o.currency) }) },
709
- { key: "total", header: t("courses.sales.amount") || "Amount", align: "right", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: formatMoney(o.total, o.currency) }) }
1569
+ { key: "fee", header: t("courses.sales.fee") || "Taxa", align: "right", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-muted-foreground", children: formatMoney(o.platformFee, o.currency) }) },
1570
+ { key: "total", header: t("courses.sales.amount") || "Valor", align: "right", render: (o) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: formatMoney(o.total, o.currency) }) }
710
1571
  ];
711
- return /* @__PURE__ */ jsxRuntime.jsxs(PageContainer, { title: t("courses.sales.title") || "Sales", subtitle: t("courses.sales.subtitle") || "Every transaction across your courses.", children: [
712
- /* @__PURE__ */ jsxRuntime.jsxs(StatGrid, { children: [
713
- /* @__PURE__ */ jsxRuntime.jsx(StatCard, { label: t("courses.sales.count") || "Sales", value: String(paid.length) }),
714
- /* @__PURE__ */ jsxRuntime.jsx(StatCard, { label: t("courses.sales.revenue") || "Revenue", value: formatMoney(revenue, currency) })
715
- ] }),
716
- /* @__PURE__ */ jsxRuntime.jsx(
717
- SimpleTable,
1572
+ return /* @__PURE__ */ jsxRuntime.jsx(
1573
+ saas.OrderListView,
1574
+ {
1575
+ title: t("courses.sales.title") || "Vendas",
1576
+ subtitle: t("courses.sales.subtitle") || "Todas as transa\xE7\xF5es dos seus cursos.",
1577
+ stats: [
1578
+ { label: t("courses.sales.count") || "Vendas", value: String(paid.length) },
1579
+ { label: t("courses.sales.revenue") || "Receita", value: formatMoney(revenue, currency) }
1580
+ ],
1581
+ columns,
1582
+ rows: orders ?? [],
1583
+ loading: orders === null,
1584
+ empty: t("courses.sales.empty") || "Nenhuma venda ainda.",
1585
+ onRowClick: (o) => navigateTo(`/courses/sales/${o.id}`)
1586
+ }
1587
+ );
1588
+ }
1589
+ SalesPage.displayName = "SalesPage";
1590
+ var Page = ({ children }) => /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mx-auto max-w-6xl p-6 md:p-8", children });
1591
+ var orderCache = /* @__PURE__ */ new Map();
1592
+ function statusTone(s) {
1593
+ if (s === "paid") return "success";
1594
+ if (s === "pending") return "warning";
1595
+ if (s === "refunded" || s === "chargeback") return "destructive";
1596
+ return "muted";
1597
+ }
1598
+ function instTone(s) {
1599
+ if (s === "paid") return "success";
1600
+ if (s === "overdue") return "destructive";
1601
+ return "warning";
1602
+ }
1603
+ function CourseOrderDetailView({ id }) {
1604
+ const t = core.useTranslation();
1605
+ const [order, setOrder] = React3__namespace.useState(() => id ? orderCache.get(id) ?? null : null);
1606
+ const [loading, setLoading] = React3__namespace.useState(() => !(id && orderCache.has(id)));
1607
+ React3__namespace.useEffect(() => {
1608
+ let alive = true;
1609
+ if (!id) {
1610
+ setLoading(false);
1611
+ return;
1612
+ }
1613
+ const cached = orderCache.get(id);
1614
+ if (cached) {
1615
+ setOrder(cached);
1616
+ setLoading(false);
1617
+ return;
1618
+ }
1619
+ setLoading(true);
1620
+ void courses.getCoursesProvider().getOrder(id).then((o) => {
1621
+ if (!alive) return;
1622
+ if (o) orderCache.set(id, o);
1623
+ setOrder(o);
1624
+ setLoading(false);
1625
+ });
1626
+ return () => {
1627
+ alive = false;
1628
+ };
1629
+ }, [id]);
1630
+ const back = () => navigateTo("/courses/sales");
1631
+ if (loading) return /* @__PURE__ */ jsxRuntime.jsx(Page, { children: /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailSkeleton, {}) });
1632
+ if (!order) {
1633
+ return /* @__PURE__ */ jsxRuntime.jsx(Page, { children: /* @__PURE__ */ jsxRuntime.jsx(
1634
+ EmptyState,
718
1635
  {
719
- columns,
720
- rows: orders ?? [],
721
- empty: t("courses.sales.empty") || "No sales yet."
1636
+ title: t("courses.order.notFound") || "Pedido n\xE3o encontrado",
1637
+ description: "",
1638
+ action: /* @__PURE__ */ jsxRuntime.jsx("button", { className: "text-sm text-primary underline", onClick: back, children: t("common.back") || "Voltar" })
722
1639
  }
723
- )
724
- ] });
1640
+ ) });
1641
+ }
1642
+ const cur = order.currency;
1643
+ const installments = order.installments.map((m) => ({
1644
+ number: m.installmentNumber,
1645
+ amount: formatMoney(m.amount, cur),
1646
+ status: m.status,
1647
+ statusTone: instTone(m.status),
1648
+ dueDate: m.dueDate ? formatDate(m.dueDate) : null,
1649
+ paymentDate: m.paymentDate ? formatDate(m.paymentDate) : null,
1650
+ note: m.cardBrand ? `${m.cardBrand}${m.cardInstallments ? ` ${m.cardInstallments}x` : ""}` : null
1651
+ }));
1652
+ const data = {
1653
+ title: `${t("courses.order.title") || "Pedido"} #${order.id.slice(0, 8)}`,
1654
+ subtitle: formatDate(order.createdAt),
1655
+ badges: [
1656
+ { label: t(`courses.financialStatus.${order.financialStatus}`) || order.financialStatus, tone: statusTone(order.financialStatus) },
1657
+ ...order.installmentCount && order.installmentCount > 1 ? [{ label: `${order.installmentCount}x`, tone: "muted" }] : []
1658
+ ],
1659
+ customer: { name: order.customerName, email: order.customerEmail },
1660
+ customerNode: /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1 text-sm", children: [
1661
+ order.customerId ? /* @__PURE__ */ jsxRuntime.jsx(
1662
+ saas.PersonLink,
1663
+ {
1664
+ personId: order.customerId,
1665
+ name: order.customerName ?? order.customerEmail ?? "Cliente",
1666
+ className: "font-medium"
1667
+ }
1668
+ ) : /* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-medium", children: order.customerName ?? "\u2014" }),
1669
+ order.customerEmail && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-muted-foreground", children: order.customerEmail })
1670
+ ] }),
1671
+ items: [{
1672
+ name: order.courseTitle ?? "\u2014",
1673
+ description: order.offerName,
1674
+ quantity: 1,
1675
+ unitPrice: formatMoney(order.total, cur),
1676
+ total: formatMoney(order.total, cur)
1677
+ }],
1678
+ totals: [
1679
+ { label: "Total", value: formatMoney(order.total, cur), strong: true },
1680
+ ...order.platformFee > 0 ? [{ label: "Taxa", value: `\u2212 ${formatMoney(order.platformFee, cur)}`, muted: true }] : [],
1681
+ { label: "L\xEDquido", value: formatMoney(order.netValue, cur) }
1682
+ ],
1683
+ paymentMethod: order.paymentMethod ?? void 0,
1684
+ receivable: {
1685
+ rows: [
1686
+ { label: "Recebido", value: formatMoney(order.paidAmount, cur) },
1687
+ { label: "Saldo", value: formatMoney(order.balance, cur), strong: order.balance > 0 }
1688
+ ],
1689
+ installments,
1690
+ emptyLabel: t("courses.order.noInstallments") || "Sem parcelas."
1691
+ },
1692
+ onBack: back,
1693
+ backLabel: t("courses.sales.title") || "Vendas"
1694
+ };
1695
+ return /* @__PURE__ */ jsxRuntime.jsx(Page, { children: /* @__PURE__ */ jsxRuntime.jsx(saas.OrderDetailView, { ...data }) });
725
1696
  }
726
- SalesPage.displayName = "SalesPage";
1697
+ CourseOrderDetailView.displayName = "CourseOrderDetailView";
727
1698
  function statusVariant3(s) {
728
1699
  if (s === "active") return "success";
729
1700
  if (s === "past_due") return "secondary";
@@ -731,8 +1702,8 @@ function statusVariant3(s) {
731
1702
  }
732
1703
  function SubscriptionsPage() {
733
1704
  const t = core.useTranslation();
734
- const [subs, setSubs] = React2__namespace.useState(null);
735
- React2__namespace.useEffect(() => {
1705
+ const [subs, setSubs] = React3__namespace.useState(null);
1706
+ React3__namespace.useEffect(() => {
736
1707
  void courses.getCoursesProvider().listSubscriptions().then(setSubs);
737
1708
  }, []);
738
1709
  const active = (subs ?? []).filter((s) => s.status === "active");
@@ -771,10 +1742,10 @@ function SubscriptionsPage() {
771
1742
  SubscriptionsPage.displayName = "SubscriptionsPage";
772
1743
  function FinancialPage() {
773
1744
  const t = core.useTranslation();
774
- const [summary, setSummary] = React2__namespace.useState(null);
775
- const [payouts, setPayouts] = React2__namespace.useState(null);
776
- const [account, setAccount] = React2__namespace.useState(null);
777
- React2__namespace.useEffect(() => {
1745
+ const [summary, setSummary] = React3__namespace.useState(null);
1746
+ const [payouts, setPayouts] = React3__namespace.useState(null);
1747
+ const [account, setAccount] = React3__namespace.useState(null);
1748
+ React3__namespace.useEffect(() => {
778
1749
  const p = courses.getCoursesProvider();
779
1750
  void p.getFinancialSummary().then(setSummary);
780
1751
  void p.listPayouts().then(setPayouts);
@@ -829,8 +1800,8 @@ function FinancialPage() {
829
1800
  FinancialPage.displayName = "FinancialPage";
830
1801
  function ReportsPage() {
831
1802
  const t = core.useTranslation();
832
- const [rows, setRows] = React2__namespace.useState(null);
833
- React2__namespace.useEffect(() => {
1803
+ const [rows, setRows] = React3__namespace.useState(null);
1804
+ React3__namespace.useEffect(() => {
834
1805
  const p = courses.getCoursesProvider();
835
1806
  void Promise.all([p.listCourses(), p.listOrders({ financialStatus: "paid" })]).then(([courses, orders]) => {
836
1807
  setRows(buildRevenueByCourse(courses, orders));
@@ -867,48 +1838,10 @@ function buildRevenueByCourse(courses, orders) {
867
1838
  return { courseId: c.id, title: c.title, sales: agg?.sales ?? 0, revenue: agg?.revenue ?? 0, currency: agg?.currency ?? c.currency };
868
1839
  }).sort((a, b) => b.revenue - a.revenue);
869
1840
  }
870
- function MembersAreaPage() {
871
- const t = core.useTranslation();
872
- const [rows, setRows] = React2__namespace.useState(null);
873
- React2__namespace.useEffect(() => {
874
- const p = courses.getCoursesProvider();
875
- void Promise.all([p.listCourses(), p.listOrders({ financialStatus: "paid" })]).then(([courses, orders]) => {
876
- setRows(buildMembersAreas(courses, orders));
877
- });
878
- }, []);
879
- const columns = [
880
- { key: "title", header: t("courses.membersArea.name") || "Members area", render: (r) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-medium text-foreground", children: r.title }) },
881
- { key: "status", header: t("courses.membersArea.status") || "Status", render: (r) => /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: r.status === "published" ? "success" : r.status === "archived" ? "outline" : "secondary", children: t(`courses.status.${r.status}`) || r.status }) },
882
- { key: "students", header: t("courses.membersArea.students") || "Students", align: "right", render: (r) => /* @__PURE__ */ jsxRuntime.jsx("span", { className: "text-foreground", children: r.students }) }
883
- ];
884
- return /* @__PURE__ */ jsxRuntime.jsx(PageContainer, { title: t("courses.membersArea.title") || "Members area", subtitle: t("courses.membersArea.subtitle") || "One area per course; students land here after purchase.", children: /* @__PURE__ */ jsxRuntime.jsx(
885
- SimpleTable,
886
- {
887
- columns,
888
- rows: rows ?? [],
889
- empty: t("courses.membersArea.empty") || "No members areas yet."
890
- }
891
- ) });
892
- }
893
- MembersAreaPage.displayName = "MembersAreaPage";
894
- function buildMembersAreas(courses, orders) {
895
- const students = /* @__PURE__ */ new Map();
896
- for (const o of orders) {
897
- const set = students.get(o.courseId) ?? /* @__PURE__ */ new Set();
898
- if (o.customerId) set.add(o.customerId);
899
- students.set(o.courseId, set);
900
- }
901
- return courses.map((c) => ({
902
- courseId: c.id,
903
- title: c.title,
904
- status: c.status,
905
- students: students.get(c.id)?.size ?? 0
906
- }));
907
- }
908
1841
  function CoursesGeneralSettings() {
909
1842
  const t = core.useTranslation();
910
- const [currency, setCurrency] = React2__namespace.useState("BRL");
911
- const [methods, setMethods] = React2__namespace.useState({ card: true, pix: true, boleto: true });
1843
+ const [currency, setCurrency] = React3__namespace.useState("BRL");
1844
+ const [methods, setMethods] = React3__namespace.useState({ card: true, pix: true, boleto: true });
912
1845
  return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
913
1846
  /* @__PURE__ */ jsxRuntime.jsx(
914
1847
  saas.SettingsGroup,
@@ -953,8 +1886,8 @@ function CoursesSettingsTab() {
953
1886
  }
954
1887
  CoursesSettingsTab.displayName = "CoursesSettingsTab";
955
1888
  function useSummary() {
956
- const [summary, setSummary] = React2__namespace.useState(null);
957
- React2__namespace.useEffect(() => {
1889
+ const [summary, setSummary] = React3__namespace.useState(null);
1890
+ React3__namespace.useEffect(() => {
958
1891
  void courses.getCoursesProvider().getFinancialSummary().then(setSummary);
959
1892
  }, []);
960
1893
  return summary;
@@ -1030,7 +1963,9 @@ function createCoursesPlugin(options) {
1030
1963
  const labels = options?.labels ?? {};
1031
1964
  const navigation = [
1032
1965
  { section, position: base, label: options?.navLabel ?? "Courses", route: "/courses", icon: "BookOpen", permission: read("courses") },
1033
- ...modules.membersArea ? [{ section, position: base + 1, label: labels.membersArea ?? "Members area", route: "/courses/members", icon: "GraduationCap", permission: read("courses") }] : [],
1966
+ // The members area is no longer a standalone nav entry — it lives inside each
1967
+ // course's record (CourseEditorPage → "Alunos" tab). modules.membersArea is
1968
+ // kept accepted-but-inert for backward compat with existing manifests.
1034
1969
  ...modules.sales ? [{ section, position: base + 2, label: labels.sales ?? "Sales", route: "/courses/sales", icon: "TrendingUp", permission: read("courses.sales") }] : [],
1035
1970
  ...modules.subscriptions ? [{ section, position: base + 3, label: labels.subscriptions ?? "Subscriptions", route: "/courses/subscriptions", icon: "Repeat", permission: read("courses.sales") }] : [],
1036
1971
  ...modules.financial ? [{ section, position: base + 4, label: labels.financial ?? "Financial", route: "/courses/financial", icon: "CreditCard", permission: read("courses.finance") }] : [],
@@ -1040,8 +1975,9 @@ function createCoursesPlugin(options) {
1040
1975
  // Static segments outscore the `:id` param in the saas router, so these
1041
1976
  // resolve before the editor route (AdminShell sorts by routeScore).
1042
1977
  { path: "/courses", component: asComp(CoursesListPage), permission: read("courses") },
1043
- ...modules.membersArea ? [{ path: "/courses/members", component: asComp(MembersAreaPage), permission: read("courses") }] : [],
1044
1978
  ...modules.sales ? [{ path: "/courses/sales", component: asComp(SalesPage), permission: read("courses.sales") }] : [],
1979
+ // Order detail — static `/courses/sales/:id` outscores `/courses/:id`.
1980
+ ...modules.sales ? [{ path: "/courses/sales/:id", component: asComp(CourseOrderDetailView), permission: read("courses.sales") }] : [],
1045
1981
  ...modules.subscriptions ? [{ path: "/courses/subscriptions", component: asComp(SubscriptionsPage), permission: read("courses.sales") }] : [],
1046
1982
  ...modules.financial ? [{ path: "/courses/financial", component: asComp(FinancialPage), permission: read("courses.finance") }] : [],
1047
1983
  ...modules.reports ? [{ path: "/courses/reports", component: asComp(ReportsPage), permission: read("courses.sales") }] : [],