@fayz-ai/plugin-courses 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -3
- package/dist/components/CommerceUI.d.ts +19 -2
- package/dist/components/CommerceUI.d.ts.map +1 -1
- package/dist/components/dashboardWidgets.d.ts.map +1 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +342 -253
- package/dist/index.js.map +1 -1
- package/dist/lib/nav.d.ts.map +1 -0
- package/dist/locales/en.d.ts.map +1 -1
- package/dist/locales/pt-BR.d.ts.map +1 -1
- package/dist/public/createPublicCheckoutPlugin.d.ts +1 -1
- package/dist/public/createPublicCheckoutPlugin.d.ts.map +1 -1
- package/dist/public/{data.d.ts → data/index.d.ts} +1 -1
- package/dist/public/data/index.d.ts.map +1 -0
- package/dist/public/{data.supabase.d.ts → data/supabase.d.ts} +2 -2
- package/dist/public/data/supabase.d.ts.map +1 -0
- package/dist/public/index.d.ts +4 -4
- package/dist/public/index.d.ts.map +1 -1
- package/dist/public/index.js +353 -0
- package/dist/public/index.js.map +1 -0
- package/dist/public/{CheckoutScreen.d.ts → views/CheckoutScreen.d.ts} +1 -1
- package/dist/public/views/CheckoutScreen.d.ts.map +1 -0
- package/dist/views/CourseEditorPage.d.ts.map +1 -0
- package/dist/views/CourseOrderDetailView.d.ts +4 -3
- package/dist/views/CourseOrderDetailView.d.ts.map +1 -1
- package/dist/{pages → views}/CoursesListPage.d.ts.map +1 -1
- package/dist/{pages → views}/FinancialPage.d.ts.map +1 -1
- package/dist/{pages → views}/ReportsPage.d.ts.map +1 -1
- package/dist/views/SalesPage.d.ts +19 -0
- package/dist/views/SalesPage.d.ts.map +1 -0
- package/dist/views/SubscriptionsPage.d.ts +7 -0
- package/dist/views/SubscriptionsPage.d.ts.map +1 -0
- package/package.json +6 -9
- package/dist/dashboardWidgets.d.ts.map +0 -1
- package/dist/nav.d.ts.map +0 -1
- package/dist/pages/CourseEditorPage.d.ts.map +0 -1
- package/dist/pages/SalesPage.d.ts +0 -6
- package/dist/pages/SalesPage.d.ts.map +0 -1
- package/dist/pages/SubscriptionsPage.d.ts +0 -6
- package/dist/pages/SubscriptionsPage.d.ts.map +0 -1
- package/dist/public/CheckoutScreen.d.ts.map +0 -1
- package/dist/public/data.d.ts.map +0 -1
- package/dist/public/data.supabase.d.ts.map +0 -1
- package/src/components/CommerceUI.tsx +0 -153
- package/src/connectors/stripe.ts +0 -34
- package/src/dashboardWidgets.tsx +0 -64
- package/src/index.ts +0 -150
- package/src/lib/format.ts +0 -22
- package/src/locales/en.ts +0 -203
- package/src/locales/index.ts +0 -7
- package/src/locales/pt-BR.ts +0 -203
- package/src/nav.ts +0 -9
- package/src/pages/CourseEditorPage.tsx +0 -1122
- package/src/pages/CoursesListPage.tsx +0 -137
- package/src/pages/FinancialPage.tsx +0 -81
- package/src/pages/ReportsPage.tsx +0 -59
- package/src/pages/SalesPage.tsx +0 -86
- package/src/pages/SubscriptionsPage.tsx +0 -56
- package/src/public/CheckoutScreen.tsx +0 -279
- package/src/public/createPublicCheckoutPlugin.tsx +0 -88
- package/src/public/data.supabase.ts +0 -63
- package/src/public/data.ts +0 -89
- package/src/public/index.ts +0 -26
- package/src/settings/CoursesSettings.tsx +0 -55
- package/src/views/CourseOrderDetailView.tsx +0 -132
- /package/dist/{dashboardWidgets.d.ts → components/dashboardWidgets.d.ts} +0 -0
- /package/dist/{nav.d.ts → lib/nav.d.ts} +0 -0
- /package/dist/{pages → views}/CourseEditorPage.d.ts +0 -0
- /package/dist/{pages → views}/CoursesListPage.d.ts +0 -0
- /package/dist/{pages → views}/FinancialPage.d.ts +0 -0
- /package/dist/{pages → views}/ReportsPage.d.ts +0 -0
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
|
+
import * as React3 from 'react';
|
|
1
2
|
import { hashRouterAdapter, registerPluginFactory, registerTranslations, useTranslation, getSupabaseClientOptional } from '@fayz-ai/core';
|
|
2
3
|
import { setCoursesTenantResolver, getCoursesProviderOptional, setCoursesProvider, createMockCoursesProvider, getCoursesProvider } from '@fayz-ai/courses';
|
|
3
|
-
import { useOrganizationStore, useLimitGuard, usePermissionOptional, PermissionGate, OrderListView, OrderDetailSkeleton, PersonLink, OrderDetailView, invalidateLimit, PluginSettingsPanel, SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/
|
|
4
|
-
import
|
|
5
|
-
import { defineKpiWidget, useModuleLayout, PageHeaderActions, Button, Badge, useSaveBar, toast, Tabs, TabsList, TabsTrigger, TabsContent, Input, SegmentedControl, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Checkbox, ConfirmDialog, KpiCard, Skeleton, Card, DataTable } from '@fayz-ai/ui';
|
|
4
|
+
import { useOrganizationStore, useLimitGuard, usePermissionOptional, PermissionGate, OrderListView, OrderDetailSkeleton, PersonLink, OrderDetailView, invalidateLimit, PluginSettingsPanel, SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/admin';
|
|
5
|
+
import { defineKpiWidget, useModuleLayout, PageHeaderActions, Button, Badge, useSaveBar, PageHeader, toast, Separator, Tabs, TabsList, TabsTrigger, TabsContent, Input, SegmentedControl, Select, SelectTrigger, SelectValue, SelectContent, SelectItem, Checkbox, ConfirmDialog, KpiCard, Skeleton, HeaderTabs, Card, DataTable } from '@fayz-ai/ui';
|
|
6
6
|
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
7
|
-
import {
|
|
7
|
+
import { ImagePlus, Users, ShoppingCart, Layers, Tag, Loader2, UploadCloud, Info, Plus, Trash2, Clock, Lock, Check, GripVertical, ChevronDown, Video, FileText, PlayCircle } from 'lucide-react';
|
|
8
8
|
import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from '@dnd-kit/core';
|
|
9
9
|
import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, arrayMove, useSortable } from '@dnd-kit/sortable';
|
|
10
10
|
import { CSS } from '@dnd-kit/utilities';
|
|
@@ -23,7 +23,6 @@ var en = {
|
|
|
23
23
|
"courses.status.draft": "Draft",
|
|
24
24
|
"courses.status.published": "Published",
|
|
25
25
|
"courses.status.archived": "Archived",
|
|
26
|
-
"courses.editor.back": "Back to courses",
|
|
27
26
|
"courses.editor.details": "Details",
|
|
28
27
|
"courses.editor.curriculum": "Curriculum",
|
|
29
28
|
"courses.editor.fieldTitle": "Title",
|
|
@@ -135,6 +134,9 @@ var en = {
|
|
|
135
134
|
"courses.offers.checkoutFlagsNote": "Apply once the public checkout ships (coming soon).",
|
|
136
135
|
// Sales
|
|
137
136
|
"courses.sales.title": "Sales",
|
|
137
|
+
"courses.order.title": "Order",
|
|
138
|
+
"courses.order.notFound": "Order not found",
|
|
139
|
+
"courses.order.noInstallments": "No installments.",
|
|
138
140
|
"courses.sales.subtitle": "Every transaction across your courses.",
|
|
139
141
|
"courses.sales.date": "Date",
|
|
140
142
|
"courses.sales.customer": "Customer",
|
|
@@ -228,7 +230,6 @@ var ptBR = {
|
|
|
228
230
|
"courses.status.draft": "Rascunho",
|
|
229
231
|
"courses.status.published": "Publicado",
|
|
230
232
|
"courses.status.archived": "Arquivado",
|
|
231
|
-
"courses.editor.back": "Voltar aos cursos",
|
|
232
233
|
"courses.editor.details": "Detalhes",
|
|
233
234
|
"courses.editor.curriculum": "Conte\xFAdo",
|
|
234
235
|
"courses.editor.fieldTitle": "T\xEDtulo",
|
|
@@ -340,6 +341,9 @@ var ptBR = {
|
|
|
340
341
|
"courses.offers.checkoutFlagsNote": "Aplicam-se quando o checkout p\xFAblico entrar no ar (em breve).",
|
|
341
342
|
// Sales
|
|
342
343
|
"courses.sales.title": "Vendas",
|
|
344
|
+
"courses.order.title": "Pedido",
|
|
345
|
+
"courses.order.notFound": "Pedido n\xE3o encontrado",
|
|
346
|
+
"courses.order.noInstallments": "Sem parcelas.",
|
|
343
347
|
"courses.sales.subtitle": "Todas as transa\xE7\xF5es dos seus cursos.",
|
|
344
348
|
"courses.sales.date": "Data",
|
|
345
349
|
"courses.sales.customer": "Cliente",
|
|
@@ -530,9 +534,24 @@ function formatDate(iso) {
|
|
|
530
534
|
function formatPercent(fraction, digits = 0) {
|
|
531
535
|
return `${(fraction * 100).toFixed(digits)}%`;
|
|
532
536
|
}
|
|
533
|
-
function PageContainer({ title, subtitle, actions, children }) {
|
|
534
|
-
return /* @__PURE__ */ jsxs("div", {
|
|
535
|
-
|
|
537
|
+
function PageContainer({ title, subtitle, actions, navTabs, activeNavTab, onNavTabChange, children }) {
|
|
538
|
+
return /* @__PURE__ */ jsxs("div", { children: [
|
|
539
|
+
navTabs && navTabs.length > 0 ? (
|
|
540
|
+
// Mesma linha de CrudListView/OrderListView: abas à esquerda, ações à
|
|
541
|
+
// direita. Com abas o <h1> sai: a aba ativa já nomeia a tela, e a barra
|
|
542
|
+
// de topo nomeia o grupo.
|
|
543
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-6 flex flex-wrap items-center justify-between gap-3", children: [
|
|
544
|
+
/* @__PURE__ */ jsx(
|
|
545
|
+
HeaderTabs,
|
|
546
|
+
{
|
|
547
|
+
tabs: navTabs,
|
|
548
|
+
value: activeNavTab ?? navTabs[0].value,
|
|
549
|
+
onChange: (v) => onNavTabChange?.(v)
|
|
550
|
+
}
|
|
551
|
+
),
|
|
552
|
+
actions
|
|
553
|
+
] })
|
|
554
|
+
) : /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-start justify-between gap-4", children: [
|
|
536
555
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
537
556
|
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: title }),
|
|
538
557
|
subtitle && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: subtitle })
|
|
@@ -684,7 +703,18 @@ function CourseEditorPage({ id }) {
|
|
|
684
703
|
},
|
|
685
704
|
saveLabel: t("courses.editor.save") || "Salvar"
|
|
686
705
|
});
|
|
687
|
-
|
|
706
|
+
const header = /* @__PURE__ */ jsx(
|
|
707
|
+
PageHeader,
|
|
708
|
+
{
|
|
709
|
+
title: course?.title ?? "",
|
|
710
|
+
parentLabel: t("courses.title") || "Courses",
|
|
711
|
+
onBack: () => navigateTo("/courses")
|
|
712
|
+
}
|
|
713
|
+
);
|
|
714
|
+
if (loading || !course) return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
715
|
+
header,
|
|
716
|
+
/* @__PURE__ */ jsx(EditorSkeleton, {})
|
|
717
|
+
] });
|
|
688
718
|
function patchCourse(patch) {
|
|
689
719
|
setCourse((c) => c ? { ...c, ...patch } : c);
|
|
690
720
|
}
|
|
@@ -844,245 +874,246 @@ function CourseEditorPage({ id }) {
|
|
|
844
874
|
value: d,
|
|
845
875
|
label: t(`courses.editor.delivery.${d}`) || d
|
|
846
876
|
}));
|
|
847
|
-
return
|
|
848
|
-
|
|
849
|
-
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
] }) }),
|
|
896
|
-
/* @__PURE__ */ 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__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-start", children: [
|
|
897
|
-
/* @__PURE__ */ jsx("div", { className: "aspect-video w-full shrink-0 overflow-hidden rounded-lg border border-border bg-muted sm:w-56", children: course.thumbnailUrl ? (
|
|
898
|
-
// eslint-disable-next-line @next/next/no-img-element
|
|
899
|
-
/* @__PURE__ */ jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" })
|
|
900
|
-
) : /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1 text-muted-foreground", children: [
|
|
901
|
-
/* @__PURE__ */ jsx(ImagePlus, { className: "h-6 w-6" }),
|
|
902
|
-
/* @__PURE__ */ jsx("span", { className: "text-xs", children: t("courses.editor.thumbnailEmpty") || "No image" })
|
|
877
|
+
return (
|
|
878
|
+
// Sem padding e sem max-width próprios: a shell já envolve toda página em
|
|
879
|
+
// `p-3 md:p-6` (AdminShell). Reaplicar `p-6 md:p-8` aqui empurrava o editor
|
|
880
|
+
// 32px à direita do título na barra de topo e de todas as outras telas, e o
|
|
881
|
+
// `mx-auto max-w-4xl` ainda o centralizava enquanto a barra ficava à esquerda.
|
|
882
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-6", children: [
|
|
883
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-start gap-5", children: [
|
|
884
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-video w-36 shrink-0 overflow-hidden rounded-2xl border border-border bg-muted shadow-sm", children: course.thumbnailUrl ? (
|
|
885
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
886
|
+
/* @__PURE__ */ jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" })
|
|
887
|
+
) : /* @__PURE__ */ jsx("div", { className: "flex h-full w-full items-center justify-center text-muted-foreground", children: /* @__PURE__ */ jsx(ImagePlus, { className: "h-6 w-6" }) }) }),
|
|
888
|
+
/* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-3 gap-y-2", children: [
|
|
889
|
+
/* @__PURE__ */ jsx("h1", { className: "truncate text-2xl font-bold text-foreground", children: course.title || (t("courses.editor.untitledCourse") || "Untitled course") }),
|
|
890
|
+
/* @__PURE__ */ jsx(Badge, { variant: course.status === "published" ? "success" : course.status === "archived" ? "secondary" : "outline", children: t(`courses.status.${course.status}`) || course.status }),
|
|
891
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", children: t(`courses.editor.delivery.${course.deliveryType}`) || course.deliveryType })
|
|
892
|
+
] }) })
|
|
893
|
+
] }),
|
|
894
|
+
/* @__PURE__ */ jsx(Separator, {}),
|
|
895
|
+
/* @__PURE__ */ jsxs(Tabs, { value: tab, onValueChange: (v) => setTab(v), children: [
|
|
896
|
+
/* @__PURE__ */ jsx(TabsList, { className: "max-w-full justify-start overflow-x-auto scrollbar-none", children: TABS.map((tk) => {
|
|
897
|
+
const Icon = TAB_ICON[tk];
|
|
898
|
+
return /* @__PURE__ */ jsxs(TabsTrigger, { value: tk, className: "gap-1.5", children: [
|
|
899
|
+
/* @__PURE__ */ jsx(Icon, { className: "h-3.5 w-3.5" }),
|
|
900
|
+
t(`courses.editor.tab.${tk}`) || tk
|
|
901
|
+
] }, tk);
|
|
902
|
+
}) }),
|
|
903
|
+
/* @__PURE__ */ jsxs(TabsContent, { value: "details", className: "mt-4 space-y-5", children: [
|
|
904
|
+
/* @__PURE__ */ jsx(SectionCard, { title: t("courses.editor.sectionBasics") || "Basics", children: /* @__PURE__ */ jsxs("div", { className: "space-y-4", children: [
|
|
905
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldTitle") || "Title", children: /* @__PURE__ */ jsx(Input, { value: course.title, onChange: (e) => patchCourse({ title: e.target.value }) }) }),
|
|
906
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldSubtitle") || "Subtitle", children: /* @__PURE__ */ jsx(Input, { value: course.subtitle ?? "", onChange: (e) => patchCourse({ subtitle: e.target.value }) }) }),
|
|
907
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldDescription") || "Description", children: /* @__PURE__ */ jsx(
|
|
908
|
+
Textarea,
|
|
909
|
+
{
|
|
910
|
+
value: course.description ?? "",
|
|
911
|
+
onChange: (e) => patchCourse({ description: e.target.value }),
|
|
912
|
+
placeholder: t("courses.editor.descriptionPlaceholder") || "What will students learn?"
|
|
913
|
+
}
|
|
914
|
+
) }),
|
|
915
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldDelivery") || "Format", hint: t("courses.editor.deliveryHint") || "How this course is delivered.", children: /* @__PURE__ */ jsx(
|
|
916
|
+
SegmentedControl,
|
|
917
|
+
{
|
|
918
|
+
size: "md",
|
|
919
|
+
options: deliveryOptions,
|
|
920
|
+
value: course.deliveryType,
|
|
921
|
+
onChange: (v) => patchCourse({ deliveryType: v }),
|
|
922
|
+
"aria-label": t("courses.editor.fieldDelivery") || "Format"
|
|
923
|
+
}
|
|
924
|
+
) })
|
|
903
925
|
] }) }),
|
|
904
|
-
/* @__PURE__ */ jsxs("div", { className: "flex-
|
|
905
|
-
/* @__PURE__ */ jsx("
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
926
|
+
/* @__PURE__ */ 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__ */ jsxs("div", { className: "flex flex-col gap-4 sm:flex-row sm:items-start", children: [
|
|
927
|
+
/* @__PURE__ */ jsx("div", { className: "aspect-video w-full shrink-0 overflow-hidden rounded-lg border border-border bg-muted sm:w-56", children: course.thumbnailUrl ? (
|
|
928
|
+
// eslint-disable-next-line @next/next/no-img-element
|
|
929
|
+
/* @__PURE__ */ jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" })
|
|
930
|
+
) : /* @__PURE__ */ jsxs("div", { className: "flex h-full w-full flex-col items-center justify-center gap-1 text-muted-foreground", children: [
|
|
931
|
+
/* @__PURE__ */ jsx(ImagePlus, { className: "h-6 w-6" }),
|
|
932
|
+
/* @__PURE__ */ jsx("span", { className: "text-xs", children: t("courses.editor.thumbnailEmpty") || "No image" })
|
|
933
|
+
] }) }),
|
|
934
|
+
/* @__PURE__ */ jsxs("div", { className: "flex-1 space-y-3", children: [
|
|
935
|
+
/* @__PURE__ */ jsx("input", { ref: fileRef, type: "file", accept: "image/*", className: "hidden", onChange: onPickThumbnail }),
|
|
936
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", size: "sm", disabled: uploading, onClick: () => fileRef.current?.click(), children: [
|
|
937
|
+
uploading ? /* @__PURE__ */ jsx(Loader2, { className: "h-4 w-4 animate-spin" }) : /* @__PURE__ */ jsx(UploadCloud, { className: "h-4 w-4" }),
|
|
938
|
+
uploading ? t("courses.editor.uploading") || "Uploading\u2026" : t("courses.editor.thumbnailUpload") || "Upload image"
|
|
939
|
+
] }),
|
|
940
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.thumbnailUrl") || "Or paste an image URL", children: /* @__PURE__ */ jsx(
|
|
941
|
+
Input,
|
|
942
|
+
{
|
|
943
|
+
value: course.thumbnailUrl ?? "",
|
|
944
|
+
placeholder: "https://\u2026",
|
|
945
|
+
onChange: (e) => patchCourse({ thumbnailUrl: e.target.value || null })
|
|
946
|
+
}
|
|
947
|
+
) })
|
|
948
|
+
] })
|
|
949
|
+
] }) }),
|
|
950
|
+
/* @__PURE__ */ jsx(SectionCard, { title: t("courses.editor.sectionPricing") || "Pricing & status", children: /* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [
|
|
951
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldPrice") || "Price", hint: course.currency, children: /* @__PURE__ */ jsx(
|
|
911
952
|
Input,
|
|
912
953
|
{
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
954
|
+
type: "number",
|
|
955
|
+
min: 0,
|
|
956
|
+
step: "0.01",
|
|
957
|
+
inputMode: "decimal",
|
|
958
|
+
value: String(course.price),
|
|
959
|
+
onChange: (e) => patchCourse({ price: Number(e.target.value) || 0 })
|
|
916
960
|
}
|
|
917
|
-
) })
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
Input,
|
|
923
|
-
{
|
|
924
|
-
type: "number",
|
|
925
|
-
min: 0,
|
|
926
|
-
step: "0.01",
|
|
927
|
-
inputMode: "decimal",
|
|
928
|
-
value: String(course.price),
|
|
929
|
-
onChange: (e) => patchCourse({ price: Number(e.target.value) || 0 })
|
|
930
|
-
}
|
|
931
|
-
) }),
|
|
932
|
-
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldStatus") || "Status", children: /* @__PURE__ */ jsxs(Select, { value: course.status, onValueChange: (v) => patchCourse({ status: v }), children: [
|
|
933
|
-
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
934
|
-
/* @__PURE__ */ jsx(SelectContent, { children: STATUSES.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s, children: t(`courses.status.${s}`) || s }, s)) })
|
|
961
|
+
) }),
|
|
962
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.editor.fieldStatus") || "Status", children: /* @__PURE__ */ jsxs(Select, { value: course.status, onValueChange: (v) => patchCourse({ status: v }), children: [
|
|
963
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
964
|
+
/* @__PURE__ */ jsx(SelectContent, { children: STATUSES.map((s) => /* @__PURE__ */ jsx(SelectItem, { value: s, children: t(`courses.status.${s}`) || s }, s)) })
|
|
965
|
+
] }) })
|
|
935
966
|
] }) })
|
|
936
|
-
] })
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
CurriculumBuilder,
|
|
940
|
-
{
|
|
941
|
-
t,
|
|
942
|
-
modules,
|
|
943
|
-
lessons,
|
|
944
|
-
autoFocusId,
|
|
945
|
-
onAutoFocused: () => setAutoFocusId(null),
|
|
946
|
-
onAddModule: addModule,
|
|
947
|
-
onRenameModule: renameModule,
|
|
948
|
-
onAddLesson: addLesson,
|
|
949
|
-
onPatchLesson: patchLesson,
|
|
950
|
-
onRequestDelete: (kind, delId) => setPendingDelete({ kind, id: delId }),
|
|
951
|
-
onReorderModules: reorderModules,
|
|
952
|
-
onReorderLessons: reorderLessons
|
|
953
|
-
}
|
|
954
|
-
) }),
|
|
955
|
-
/* @__PURE__ */ jsxs(TabsContent, { value: "offers", className: "space-y-4", children: [
|
|
956
|
-
/* @__PURE__ */ jsx(InfoNote, { icon: /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }), children: t("courses.offers.help") || "Price points for this course. Sales run through orders and the Financeiro module." }),
|
|
957
|
-
offers.length === 0 ? /* @__PURE__ */ jsx(
|
|
958
|
-
EmptyState,
|
|
967
|
+
] }),
|
|
968
|
+
/* @__PURE__ */ jsx(TabsContent, { value: "curriculum", className: "mt-4 space-y-4", children: /* @__PURE__ */ jsx(
|
|
969
|
+
CurriculumBuilder,
|
|
959
970
|
{
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
971
|
+
t,
|
|
972
|
+
modules,
|
|
973
|
+
lessons,
|
|
974
|
+
autoFocusId,
|
|
975
|
+
onAutoFocused: () => setAutoFocusId(null),
|
|
976
|
+
onAddModule: addModule,
|
|
977
|
+
onRenameModule: renameModule,
|
|
978
|
+
onAddLesson: addLesson,
|
|
979
|
+
onPatchLesson: patchLesson,
|
|
980
|
+
onRequestDelete: (kind, delId) => setPendingDelete({ kind, id: delId }),
|
|
981
|
+
onReorderModules: reorderModules,
|
|
982
|
+
onReorderLessons: reorderLessons
|
|
967
983
|
}
|
|
968
|
-
)
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
)
|
|
981
|
-
|
|
982
|
-
|
|
983
|
-
|
|
984
|
-
/* @__PURE__ */
|
|
985
|
-
|
|
984
|
+
) }),
|
|
985
|
+
/* @__PURE__ */ jsxs(TabsContent, { value: "offers", className: "mt-4 space-y-4", children: [
|
|
986
|
+
/* @__PURE__ */ jsx(InfoNote, { icon: /* @__PURE__ */ jsx(Info, { className: "h-4 w-4" }), children: t("courses.offers.help") || "Price points for this course. Sales run through orders and the Financeiro module." }),
|
|
987
|
+
offers.length === 0 ? /* @__PURE__ */ jsx(
|
|
988
|
+
EmptyState,
|
|
989
|
+
{
|
|
990
|
+
icon: /* @__PURE__ */ jsx(Tag, { className: "h-5 w-5" }),
|
|
991
|
+
title: t("courses.offers.empty") || "No offers yet",
|
|
992
|
+
description: t("courses.offers.emptyHint") || "Create an offer to sell this course.",
|
|
993
|
+
action: /* @__PURE__ */ jsxs(Button, { onClick: addOffer, children: [
|
|
994
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
995
|
+
t("courses.offers.firstOffer") || "Create first offer"
|
|
996
|
+
] })
|
|
997
|
+
}
|
|
998
|
+
) : /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
999
|
+
offers.map((offer) => /* @__PURE__ */ jsxs("div", { className: "rounded-xl border border-border bg-card p-4 shadow-sm md:p-5", children: [
|
|
1000
|
+
/* @__PURE__ */ jsxs("div", { className: "mb-4 flex items-start justify-between gap-3", children: [
|
|
1001
|
+
/* @__PURE__ */ jsx(Badge, { variant: "outline", className: "gap-1", children: offer.kind === "subscription" ? t("courses.offers.subscription") || "Subscription" : t("courses.offers.oneTime") || "One-time" }),
|
|
1002
|
+
/* @__PURE__ */ jsx(
|
|
1003
|
+
"button",
|
|
1004
|
+
{
|
|
1005
|
+
onClick: () => deleteOffer(offer.id),
|
|
1006
|
+
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",
|
|
1007
|
+
"aria-label": t("courses.offers.remove") || "Remove",
|
|
1008
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
1009
|
+
}
|
|
1010
|
+
)
|
|
1011
|
+
] }),
|
|
1012
|
+
/* @__PURE__ */ jsxs("div", { className: "grid grid-cols-1 gap-3 sm:grid-cols-[1fr_140px_160px]", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.offers.name") || "Offer name", children: /* @__PURE__ */ jsx(Input, { defaultValue: offer.name, onBlur: (e) => patchOffer(offer.id, { name: e.target.value }) }) }),
|
|
1014
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.offers.price") || "Price", hint: offer.currency, children: /* @__PURE__ */ jsx(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 }) }) }),
|
|
1015
|
+
/* @__PURE__ */ jsx(Field, { label: t("courses.offers.kind") || "Type", children: /* @__PURE__ */ jsxs(
|
|
1016
|
+
Select,
|
|
1017
|
+
{
|
|
1018
|
+
value: offer.kind,
|
|
1019
|
+
onValueChange: (v) => patchOffer(offer.id, { kind: v, recurringInterval: v === "subscription" ? offer.recurringInterval ?? "month" : null }),
|
|
1020
|
+
children: [
|
|
1021
|
+
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
1022
|
+
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
1023
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "one_time", children: t("courses.offers.oneTime") || "One-time" }),
|
|
1024
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "subscription", children: t("courses.offers.subscription") || "Subscription" })
|
|
1025
|
+
] })
|
|
1026
|
+
]
|
|
1027
|
+
}
|
|
1028
|
+
) })
|
|
1029
|
+
] }),
|
|
1030
|
+
offer.kind === "subscription" && /* @__PURE__ */ jsx("div", { className: "mt-3 max-w-[200px]", children: /* @__PURE__ */ jsx(Field, { label: t("courses.offers.interval") || "Billing interval", children: /* @__PURE__ */ jsxs(
|
|
986
1031
|
Select,
|
|
987
1032
|
{
|
|
988
|
-
value: offer.
|
|
989
|
-
onValueChange: (v) => patchOffer(offer.id, {
|
|
1033
|
+
value: offer.recurringInterval ?? "month",
|
|
1034
|
+
onValueChange: (v) => patchOffer(offer.id, { recurringInterval: v }),
|
|
990
1035
|
children: [
|
|
991
1036
|
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
992
1037
|
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
993
|
-
/* @__PURE__ */ jsx(SelectItem, { value: "
|
|
994
|
-
/* @__PURE__ */ jsx(SelectItem, { value: "
|
|
1038
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "month", children: t("courses.interval.month") || "month" }),
|
|
1039
|
+
/* @__PURE__ */ jsx(SelectItem, { value: "year", children: t("courses.interval.year") || "year" })
|
|
995
1040
|
] })
|
|
996
1041
|
]
|
|
997
1042
|
}
|
|
998
|
-
) })
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
value: offer.recurringInterval ?? "month",
|
|
1004
|
-
onValueChange: (v) => patchOffer(offer.id, { recurringInterval: v }),
|
|
1005
|
-
children: [
|
|
1006
|
-
/* @__PURE__ */ jsx(SelectTrigger, { children: /* @__PURE__ */ jsx(SelectValue, {}) }),
|
|
1007
|
-
/* @__PURE__ */ jsxs(SelectContent, { children: [
|
|
1008
|
-
/* @__PURE__ */ jsx(SelectItem, { value: "month", children: t("courses.interval.month") || "month" }),
|
|
1009
|
-
/* @__PURE__ */ jsx(SelectItem, { value: "year", children: t("courses.interval.year") || "year" })
|
|
1010
|
-
] })
|
|
1011
|
-
]
|
|
1012
|
-
}
|
|
1013
|
-
) }) }),
|
|
1014
|
-
/* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-dashed border-border bg-muted/30 p-3", children: [
|
|
1015
|
-
/* @__PURE__ */ jsxs("p", { className: "mb-2.5 flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1016
|
-
/* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5" }),
|
|
1017
|
-
t("courses.offers.checkoutFlagsNote") || "Apply once the public checkout ships (coming soon)."
|
|
1018
|
-
] }),
|
|
1019
|
-
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-5 gap-y-2", children: [
|
|
1020
|
-
/* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1021
|
-
/* @__PURE__ */ jsx(Checkbox, { checked: offer.isDefault, onChange: (v) => patchOffer(offer.id, { isDefault: v }) }),
|
|
1022
|
-
t("courses.offers.default") || "Default"
|
|
1043
|
+
) }) }),
|
|
1044
|
+
/* @__PURE__ */ jsxs("div", { className: "mt-4 rounded-lg border border-dashed border-border bg-muted/30 p-3", children: [
|
|
1045
|
+
/* @__PURE__ */ jsxs("p", { className: "mb-2.5 flex items-center gap-1.5 text-xs text-muted-foreground", children: [
|
|
1046
|
+
/* @__PURE__ */ jsx(Clock, { className: "h-3.5 w-3.5" }),
|
|
1047
|
+
t("courses.offers.checkoutFlagsNote") || "Apply once the public checkout ships (coming soon)."
|
|
1023
1048
|
] }),
|
|
1024
|
-
/* @__PURE__ */ jsxs("
|
|
1025
|
-
/* @__PURE__ */
|
|
1026
|
-
|
|
1049
|
+
/* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-x-5 gap-y-2", children: [
|
|
1050
|
+
/* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1051
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: offer.isDefault, onChange: (v) => patchOffer(offer.id, { isDefault: v }) }),
|
|
1052
|
+
t("courses.offers.default") || "Default"
|
|
1053
|
+
] }),
|
|
1054
|
+
/* @__PURE__ */ jsxs("label", { className: "flex cursor-pointer items-center gap-2 text-sm text-muted-foreground", children: [
|
|
1055
|
+
/* @__PURE__ */ jsx(Checkbox, { checked: offer.isOrderBump, onChange: (v) => patchOffer(offer.id, { isOrderBump: v }) }),
|
|
1056
|
+
t("courses.offers.bump") || "Order bump"
|
|
1057
|
+
] })
|
|
1027
1058
|
] })
|
|
1028
1059
|
] })
|
|
1060
|
+
] }, offer.id)),
|
|
1061
|
+
/* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: addOffer, className: "w-full border-dashed", children: [
|
|
1062
|
+
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
1063
|
+
t("courses.offers.add") || "Add offer"
|
|
1029
1064
|
] })
|
|
1030
|
-
] }, offer.id)),
|
|
1031
|
-
/* @__PURE__ */ jsxs(Button, { variant: "outline", onClick: addOffer, className: "w-full border-dashed", children: [
|
|
1032
|
-
/* @__PURE__ */ jsx(Plus, { className: "h-4 w-4" }),
|
|
1033
|
-
t("courses.offers.add") || "Add offer"
|
|
1034
1065
|
] })
|
|
1035
|
-
] })
|
|
1036
|
-
] }),
|
|
1037
|
-
/* @__PURE__ */ jsxs(TabsContent, { value: "members", className: "space-y-4", children: [
|
|
1038
|
-
/* @__PURE__ */ jsx(InfoNote, { icon: /* @__PURE__ */ jsx(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." }),
|
|
1039
|
-
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1040
|
-
/* @__PURE__ */ jsxs("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [
|
|
1041
|
-
t("courses.editor.enrolledStudents") || "Enrolled students",
|
|
1042
|
-
" (",
|
|
1043
|
-
students.length,
|
|
1044
|
-
")"
|
|
1045
|
-
] }),
|
|
1046
|
-
students.length === 0 ? /* @__PURE__ */ jsx(
|
|
1047
|
-
EmptyState,
|
|
1048
|
-
{
|
|
1049
|
-
icon: /* @__PURE__ */ jsx(Users, { className: "h-5 w-5" }),
|
|
1050
|
-
title: t("courses.editor.membersEmpty") || "No students yet",
|
|
1051
|
-
description: t("courses.editor.membersEmptyHint") || "Buyers are enrolled here automatically after a paid order."
|
|
1052
|
-
}
|
|
1053
|
-
) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border bg-card", children: students.map((s) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
1054
|
-
/* @__PURE__ */ 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) }),
|
|
1055
|
-
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
1056
|
-
/* @__PURE__ */ jsx("span", { className: "truncate font-medium text-foreground", children: s.name }),
|
|
1057
|
-
s.email && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: s.email })
|
|
1058
|
-
] }),
|
|
1059
|
-
s.since && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: new Date(s.since).toLocaleDateString() })
|
|
1060
|
-
] }, s.key)) })
|
|
1061
1066
|
] }),
|
|
1062
|
-
|
|
1063
|
-
/* @__PURE__ */ jsx(
|
|
1064
|
-
/* @__PURE__ */
|
|
1065
|
-
/* @__PURE__ */
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1067
|
+
/* @__PURE__ */ jsxs(TabsContent, { value: "members", className: "mt-4 space-y-4", children: [
|
|
1068
|
+
/* @__PURE__ */ jsx(InfoNote, { icon: /* @__PURE__ */ jsx(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." }),
|
|
1069
|
+
/* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1070
|
+
/* @__PURE__ */ jsxs("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: [
|
|
1071
|
+
t("courses.editor.enrolledStudents") || "Enrolled students",
|
|
1072
|
+
" (",
|
|
1073
|
+
students.length,
|
|
1074
|
+
")"
|
|
1075
|
+
] }),
|
|
1076
|
+
students.length === 0 ? /* @__PURE__ */ jsx(
|
|
1077
|
+
EmptyState,
|
|
1078
|
+
{
|
|
1079
|
+
icon: /* @__PURE__ */ jsx(Users, { className: "h-5 w-5" }),
|
|
1080
|
+
title: t("courses.editor.membersEmpty") || "No students yet",
|
|
1081
|
+
description: t("courses.editor.membersEmptyHint") || "Buyers are enrolled here automatically after a paid order."
|
|
1082
|
+
}
|
|
1083
|
+
) : /* @__PURE__ */ jsx("div", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border bg-card", children: students.map((s) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
1084
|
+
/* @__PURE__ */ 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) }),
|
|
1085
|
+
/* @__PURE__ */ jsxs("span", { className: "flex min-w-0 flex-1 flex-col", children: [
|
|
1086
|
+
/* @__PURE__ */ jsx("span", { className: "truncate font-medium text-foreground", children: s.name }),
|
|
1087
|
+
s.email && /* @__PURE__ */ jsx("span", { className: "truncate text-xs text-muted-foreground", children: s.email })
|
|
1088
|
+
] }),
|
|
1089
|
+
s.since && /* @__PURE__ */ jsx("span", { className: "shrink-0 text-xs tabular-nums text-muted-foreground", children: new Date(s.since).toLocaleDateString() })
|
|
1090
|
+
] }, s.key)) })
|
|
1091
|
+
] }),
|
|
1092
|
+
offers.length > 0 && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
|
|
1093
|
+
/* @__PURE__ */ jsx("p", { className: "text-xs font-medium uppercase tracking-wide text-muted-foreground", children: t("courses.editor.accessUnlockedBy") || "Access is unlocked by" }),
|
|
1094
|
+
/* @__PURE__ */ jsx("div", { className: "divide-y divide-border overflow-hidden rounded-xl border border-border bg-card", children: offers.map((offer) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3 px-4 py-3", children: [
|
|
1095
|
+
/* @__PURE__ */ 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__ */ jsx(Lock, { className: "h-4 w-4" }) }),
|
|
1096
|
+
/* @__PURE__ */ jsx("span", { className: "min-w-0 flex-1 truncate font-medium text-foreground", children: offer.name }),
|
|
1097
|
+
/* @__PURE__ */ jsx("span", { className: "shrink-0 text-sm tabular-nums text-muted-foreground", children: formatMoney(offer.price, offer.currency) })
|
|
1098
|
+
] }, offer.id)) })
|
|
1099
|
+
] })
|
|
1069
1100
|
] })
|
|
1070
|
-
] })
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
)
|
|
1085
|
-
|
|
1101
|
+
] }),
|
|
1102
|
+
/* @__PURE__ */ jsx(
|
|
1103
|
+
ConfirmDialog,
|
|
1104
|
+
{
|
|
1105
|
+
open: pendingDelete !== null,
|
|
1106
|
+
variant: "destructive",
|
|
1107
|
+
title: pendingDelete?.kind === "module" ? t("courses.editor.deleteModuleConfirm") || "Delete this module?" : t("courses.editor.deleteLessonConfirm") || "Remove this lesson?",
|
|
1108
|
+
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.",
|
|
1109
|
+
confirmLabel: t("courses.editor.confirmDelete") || "Delete",
|
|
1110
|
+
cancelLabel: t("courses.editor.cancel") || "Cancel",
|
|
1111
|
+
onConfirm: confirmDelete,
|
|
1112
|
+
onCancel: () => setPendingDelete(null)
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
] })
|
|
1116
|
+
);
|
|
1086
1117
|
}
|
|
1087
1118
|
CourseEditorPage.displayName = "CourseEditorPage";
|
|
1088
1119
|
function formatDuration(sec, t) {
|
|
@@ -1507,9 +1538,10 @@ function statusVariant2(s) {
|
|
|
1507
1538
|
return "outline";
|
|
1508
1539
|
}
|
|
1509
1540
|
var salesCache = null;
|
|
1510
|
-
function SalesPage() {
|
|
1541
|
+
function SalesPage({ navTabs, activeNavTab, ordersRoute = "/courses/sales" } = {}) {
|
|
1511
1542
|
const t = useTranslation();
|
|
1512
1543
|
const [orders, setOrders] = React3.useState(salesCache);
|
|
1544
|
+
const tabs = navTabs?.();
|
|
1513
1545
|
React3.useEffect(() => {
|
|
1514
1546
|
if (salesCache) return;
|
|
1515
1547
|
let alive = true;
|
|
@@ -1552,6 +1584,12 @@ function SalesPage() {
|
|
|
1552
1584
|
{
|
|
1553
1585
|
title: t("courses.sales.title") || "Vendas",
|
|
1554
1586
|
subtitle: t("courses.sales.subtitle") || "Todas as transa\xE7\xF5es dos seus cursos.",
|
|
1587
|
+
navTabs: tabs?.map((tb) => ({ value: tb.value, label: tb.label })),
|
|
1588
|
+
activeNavTab,
|
|
1589
|
+
onNavTabChange: (v) => {
|
|
1590
|
+
const target = tabs?.find((tb) => tb.value === v);
|
|
1591
|
+
if (target) navigateTo(target.route);
|
|
1592
|
+
},
|
|
1555
1593
|
stats: [
|
|
1556
1594
|
{ label: t("courses.sales.count") || "Vendas", value: String(paid.length) },
|
|
1557
1595
|
{ label: t("courses.sales.revenue") || "Receita", value: formatMoney(revenue, currency) }
|
|
@@ -1560,7 +1598,7 @@ function SalesPage() {
|
|
|
1560
1598
|
rows: orders ?? [],
|
|
1561
1599
|
loading: orders === null,
|
|
1562
1600
|
empty: t("courses.sales.empty") || "Nenhuma venda ainda.",
|
|
1563
|
-
onRowClick: (o) => navigateTo(
|
|
1601
|
+
onRowClick: (o) => navigateTo(`${ordersRoute}/${o.id}`)
|
|
1564
1602
|
}
|
|
1565
1603
|
);
|
|
1566
1604
|
}
|
|
@@ -1578,7 +1616,7 @@ function instTone(s) {
|
|
|
1578
1616
|
if (s === "overdue") return "destructive";
|
|
1579
1617
|
return "warning";
|
|
1580
1618
|
}
|
|
1581
|
-
function CourseOrderDetailView({ id }) {
|
|
1619
|
+
function CourseOrderDetailView({ id, ordersRoute = "/courses/sales" }) {
|
|
1582
1620
|
const t = useTranslation();
|
|
1583
1621
|
const [order, setOrder] = React3.useState(() => id ? orderCache.get(id) ?? null : null);
|
|
1584
1622
|
const [loading, setLoading] = React3.useState(() => !(id && orderCache.has(id)));
|
|
@@ -1605,7 +1643,7 @@ function CourseOrderDetailView({ id }) {
|
|
|
1605
1643
|
alive = false;
|
|
1606
1644
|
};
|
|
1607
1645
|
}, [id]);
|
|
1608
|
-
const back = () => navigateTo(
|
|
1646
|
+
const back = () => navigateTo(ordersRoute);
|
|
1609
1647
|
if (loading) return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(OrderDetailSkeleton, {}) });
|
|
1610
1648
|
if (!order) {
|
|
1611
1649
|
return /* @__PURE__ */ jsx(Page, { children: /* @__PURE__ */ jsx(
|
|
@@ -1678,9 +1716,10 @@ function statusVariant3(s) {
|
|
|
1678
1716
|
if (s === "past_due") return "secondary";
|
|
1679
1717
|
return "destructive";
|
|
1680
1718
|
}
|
|
1681
|
-
function SubscriptionsPage() {
|
|
1719
|
+
function SubscriptionsPage({ navTabs, activeNavTab } = {}) {
|
|
1682
1720
|
const t = useTranslation();
|
|
1683
1721
|
const [subs, setSubs] = React3.useState(null);
|
|
1722
|
+
const tabs = navTabs?.();
|
|
1684
1723
|
React3.useEffect(() => {
|
|
1685
1724
|
void getCoursesProvider().listSubscriptions().then(setSubs);
|
|
1686
1725
|
}, []);
|
|
@@ -1702,20 +1741,33 @@ function SubscriptionsPage() {
|
|
|
1702
1741
|
] })
|
|
1703
1742
|
] }) }
|
|
1704
1743
|
];
|
|
1705
|
-
return /* @__PURE__ */ jsxs(
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
{
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1744
|
+
return /* @__PURE__ */ jsxs(
|
|
1745
|
+
PageContainer,
|
|
1746
|
+
{
|
|
1747
|
+
title: t("courses.subs.title") || "Subscriptions",
|
|
1748
|
+
subtitle: t("courses.subs.subtitle") || "Recurring plans and MRR.",
|
|
1749
|
+
navTabs: tabs?.map((tb) => ({ value: tb.value, label: tb.label })),
|
|
1750
|
+
activeNavTab,
|
|
1751
|
+
onNavTabChange: (v) => {
|
|
1752
|
+
const target = tabs?.find((tb) => tb.value === v);
|
|
1753
|
+
if (target) navigateTo(target.route);
|
|
1754
|
+
},
|
|
1755
|
+
children: [
|
|
1756
|
+
/* @__PURE__ */ jsxs(StatGrid, { children: [
|
|
1757
|
+
/* @__PURE__ */ jsx(StatCard, { label: t("courses.subs.active") || "Active subscriptions", value: String(active.length) }),
|
|
1758
|
+
/* @__PURE__ */ jsx(StatCard, { label: t("courses.subs.mrr") || "Monthly recurring revenue", value: formatMoney(mrr, currency) })
|
|
1759
|
+
] }),
|
|
1760
|
+
/* @__PURE__ */ jsx(
|
|
1761
|
+
SimpleTable,
|
|
1762
|
+
{
|
|
1763
|
+
columns,
|
|
1764
|
+
rows: subs ?? [],
|
|
1765
|
+
empty: t("courses.subs.empty") || "No subscriptions yet."
|
|
1766
|
+
}
|
|
1767
|
+
)
|
|
1768
|
+
]
|
|
1769
|
+
}
|
|
1770
|
+
);
|
|
1719
1771
|
}
|
|
1720
1772
|
SubscriptionsPage.displayName = "SubscriptionsPage";
|
|
1721
1773
|
function FinancialPage() {
|
|
@@ -1929,6 +1981,22 @@ function createStripeConnector() {
|
|
|
1929
1981
|
// src/index.ts
|
|
1930
1982
|
var asComp = (c) => c;
|
|
1931
1983
|
var read = (feature) => ({ feature, action: "read" });
|
|
1984
|
+
function withProps(Page2, props) {
|
|
1985
|
+
const Bound = (routeProps) => React3.createElement(Page2, { ...props, ...routeProps });
|
|
1986
|
+
Bound.displayName = `${Page2.displayName ?? Page2.name}(bound)`;
|
|
1987
|
+
return Bound;
|
|
1988
|
+
}
|
|
1989
|
+
function redirectTo(target) {
|
|
1990
|
+
const Redirect = (params) => {
|
|
1991
|
+
const to = typeof target === "function" ? target(params) : target;
|
|
1992
|
+
React3.useEffect(() => {
|
|
1993
|
+
navigateTo(to);
|
|
1994
|
+
}, [to]);
|
|
1995
|
+
return null;
|
|
1996
|
+
};
|
|
1997
|
+
Redirect.displayName = `Redirect(${typeof target === "string" ? target : "fn"})`;
|
|
1998
|
+
return Redirect;
|
|
1999
|
+
}
|
|
1932
2000
|
function createCoursesPlugin(options) {
|
|
1933
2001
|
registerTranslations(coursesLocales);
|
|
1934
2002
|
setCoursesTenantResolver(() => useOrganizationStore.getState().currentOrg?.id);
|
|
@@ -1939,27 +2007,48 @@ function createCoursesPlugin(options) {
|
|
|
1939
2007
|
const section = options?.navSection ?? "main";
|
|
1940
2008
|
const modules = options?.modules ?? {};
|
|
1941
2009
|
const labels = options?.labels ?? {};
|
|
2010
|
+
const ordersRoute = options?.routes?.orders ?? "/courses/sales";
|
|
2011
|
+
const subscriptionsRoute = options?.routes?.subscriptions ?? "/courses/subscriptions";
|
|
2012
|
+
const salesTabs = options?.salesTabs ?? false;
|
|
2013
|
+
const salesTabStrip = salesTabs ? () => [
|
|
2014
|
+
{ value: "orders", label: labels.sales ?? "Sales", route: ordersRoute },
|
|
2015
|
+
{ value: "subscriptions", label: labels.subscriptions ?? "Subscriptions", route: subscriptionsRoute }
|
|
2016
|
+
] : void 0;
|
|
1942
2017
|
const navigation = [
|
|
1943
2018
|
{ section, position: base, label: options?.navLabel ?? "Courses", route: "/courses", icon: "BookOpen", permission: read("courses") },
|
|
1944
2019
|
// The members area is no longer a standalone nav entry — it lives inside each
|
|
1945
2020
|
// course's record (CourseEditorPage → "Alunos" tab). modules.membersArea is
|
|
1946
2021
|
// kept accepted-but-inert for backward compat with existing manifests.
|
|
1947
|
-
...modules.sales ? [{ section, position: base + 2, label: labels.sales ?? "Sales", route:
|
|
1948
|
-
|
|
2022
|
+
...modules.sales ? [{ section, position: options?.ordersNavPosition ?? base + 2, label: labels.sales ?? "Sales", route: ordersRoute, icon: "TrendingUp", permission: read("courses.sales") }] : [],
|
|
2023
|
+
// Com `salesTabs`, Assinaturas é uma ABA de Pedidos — listá-la também no menu
|
|
2024
|
+
// seria a mesma navegação duas vezes, que é o que a faixa existe para evitar.
|
|
2025
|
+
...modules.subscriptions && !salesTabs ? [{ section, position: base + 3, label: labels.subscriptions ?? "Subscriptions", route: subscriptionsRoute, icon: "Repeat", permission: read("courses.sales") }] : [],
|
|
1949
2026
|
...modules.financial ? [{ section, position: base + 4, label: labels.financial ?? "Financial", route: "/courses/financial", icon: "CreditCard", permission: read("courses.finance") }] : [],
|
|
1950
2027
|
...modules.reports ? [{ section, position: base + 5, label: labels.reports ?? "Reports", route: "/courses/reports", icon: "BarChart3", permission: read("courses.sales") }] : []
|
|
1951
2028
|
];
|
|
2029
|
+
const salesProps = { navTabs: salesTabStrip, activeNavTab: "orders" };
|
|
2030
|
+
const subsProps = { navTabs: salesTabStrip, activeNavTab: "subscriptions" };
|
|
1952
2031
|
const routes = [
|
|
1953
2032
|
// Static segments outscore the `:id` param in the saas router, so these
|
|
1954
2033
|
// resolve before the editor route (AdminShell sorts by routeScore).
|
|
1955
2034
|
{ path: "/courses", component: asComp(CoursesListPage), permission: read("courses") },
|
|
1956
|
-
...modules.sales ? [{ path:
|
|
1957
|
-
// Order detail — static
|
|
1958
|
-
...modules.sales ? [{ path:
|
|
1959
|
-
...modules.subscriptions ? [{ path:
|
|
2035
|
+
...modules.sales ? [{ path: ordersRoute, component: asComp(withProps(SalesPage, salesProps)), permission: read("courses.sales") }] : [],
|
|
2036
|
+
// Order detail — the static `${ordersRoute}/:id` outscores `/courses/:id`.
|
|
2037
|
+
...modules.sales ? [{ path: `${ordersRoute}/:id`, component: asComp(withProps(CourseOrderDetailView, { ordersRoute })), permission: read("courses.sales") }] : [],
|
|
2038
|
+
...modules.subscriptions ? [{ path: subscriptionsRoute, component: asComp(withProps(SubscriptionsPage, subsProps)), permission: read("courses.sales") }] : [],
|
|
1960
2039
|
...modules.financial ? [{ path: "/courses/financial", component: asComp(FinancialPage), permission: read("courses.finance") }] : [],
|
|
1961
2040
|
...modules.reports ? [{ path: "/courses/reports", component: asComp(ReportsPage), permission: read("courses.sales") }] : [],
|
|
1962
|
-
{ path: "/courses/:id", component: asComp(CourseEditorPage) }
|
|
2041
|
+
{ path: "/courses/:id", component: asComp(CourseEditorPage) },
|
|
2042
|
+
// Onde as telas moravam quando o host as reposiciona. Redirecionam para não
|
|
2043
|
+
// deixar um link antigo renderizando tela em branco.
|
|
2044
|
+
...modules.sales && ordersRoute !== "/courses/sales" ? [
|
|
2045
|
+
// O detalhe vem PRIMEIRO e com o param: `/courses/sales/:id` (score 40)
|
|
2046
|
+
// ganha do curinga `/courses/sales/*` (29) que o shell registra junto
|
|
2047
|
+
// com o pai — sem ele, todo link antigo de pedido caía na lista.
|
|
2048
|
+
{ path: "/courses/sales/:id", component: asComp(redirectTo((p) => `${ordersRoute}/${p.id}`)) },
|
|
2049
|
+
{ path: "/courses/sales", component: asComp(redirectTo(ordersRoute)) }
|
|
2050
|
+
] : [],
|
|
2051
|
+
...modules.subscriptions && subscriptionsRoute !== "/courses/subscriptions" ? [{ path: "/courses/subscriptions", component: asComp(redirectTo(subscriptionsRoute)) }] : []
|
|
1963
2052
|
];
|
|
1964
2053
|
return {
|
|
1965
2054
|
id: "courses",
|