@fayz-ai/plugin-courses 0.2.4 → 0.8.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 +1 -1
- package/dist/dashboardWidgets.d.ts +8 -1
- package/dist/dashboardWidgets.d.ts.map +1 -1
- package/dist/index.cjs +46 -26
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -29
- package/dist/index.js.map +1 -1
- package/dist/pages/CoursesListPage.d.ts.map +1 -1
- package/dist/pages/FinancialPage.d.ts.map +1 -1
- package/package.json +9 -6
- package/src/dashboardWidgets.tsx +18 -5
- package/src/index.ts +58 -19
- package/src/pages/CoursesListPage.tsx +19 -6
- package/src/pages/FinancialPage.tsx +5 -0
package/dist/index.d.ts
CHANGED
|
@@ -3,8 +3,30 @@ export interface CoursesPluginOptions {
|
|
|
3
3
|
navPosition?: number;
|
|
4
4
|
navSection?: 'main' | 'secondary' | 'settings';
|
|
5
5
|
navLabel?: string;
|
|
6
|
+
/** Nav labels for the module entries (default English). */
|
|
7
|
+
labels?: {
|
|
8
|
+
membersArea?: string;
|
|
9
|
+
sales?: string;
|
|
10
|
+
subscriptions?: string;
|
|
11
|
+
financial?: string;
|
|
12
|
+
reports?: string;
|
|
13
|
+
};
|
|
6
14
|
scope?: PluginScope;
|
|
7
15
|
verticalId?: VerticalId;
|
|
16
|
+
/**
|
|
17
|
+
* Commerce-vertical modules — ALL OFF by default (the one architectural
|
|
18
|
+
* rule: shared-surface contributions are opt-in, never broadcast — FAY-1247).
|
|
19
|
+
* course-admin (the vertical owner) enables everything via its PluginRef
|
|
20
|
+
* config; a host embedding courses as a lightweight members feature (e.g.
|
|
21
|
+
* agency-os's "Memberships") gets only the base nav entry + editor.
|
|
22
|
+
*/
|
|
23
|
+
modules?: {
|
|
24
|
+
membersArea?: boolean;
|
|
25
|
+
sales?: boolean;
|
|
26
|
+
subscriptions?: boolean;
|
|
27
|
+
financial?: boolean;
|
|
28
|
+
reports?: boolean;
|
|
29
|
+
};
|
|
8
30
|
}
|
|
9
31
|
export declare function createCoursesPlugin(options?: CoursesPluginOptions): PluginManifest;
|
|
10
32
|
export { coursesLocales } from './locales';
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AA8B5E,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,cAAc,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,eAAe,CAAA;AA8B5E,MAAM,WAAW,oBAAoB;IACnC,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,UAAU,CAAC,EAAE,MAAM,GAAG,WAAW,GAAG,UAAU,CAAA;IAC9C,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,2DAA2D;IAC3D,MAAM,CAAC,EAAE;QACP,WAAW,CAAC,EAAE,MAAM,CAAA;QACpB,KAAK,CAAC,EAAE,MAAM,CAAA;QACd,aAAa,CAAC,EAAE,MAAM,CAAA;QACtB,SAAS,CAAC,EAAE,MAAM,CAAA;QAClB,OAAO,CAAC,EAAE,MAAM,CAAA;KACjB,CAAA;IACD,KAAK,CAAC,EAAE,WAAW,CAAA;IACnB,UAAU,CAAC,EAAE,UAAU,CAAA;IACvB;;;;;;OAMG;IACH,OAAO,CAAC,EAAE;QACR,WAAW,CAAC,EAAE,OAAO,CAAA;QACrB,KAAK,CAAC,EAAE,OAAO,CAAA;QACf,aAAa,CAAC,EAAE,OAAO,CAAA;QACvB,SAAS,CAAC,EAAE,OAAO,CAAA;QACnB,OAAO,CAAC,EAAE,OAAO,CAAA;KAClB,CAAA;CACF;AAKD,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,cAAc,CA2ElF;AAOD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { hashRouterAdapter, registerPluginFactory, registerTranslations, useTranslation } from '@fayz-ai/core';
|
|
2
2
|
import { setCoursesTenantResolver, getCoursesProviderOptional, setCoursesProvider, createMockCoursesProvider, getCoursesProvider } from '@fayz-ai/courses';
|
|
3
|
-
import { useOrganizationStore, PluginSettingsPanel, SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/saas';
|
|
3
|
+
import { useOrganizationStore, useLimitGuard, usePermissionOptional, PermissionGate, invalidateLimit, PluginSettingsPanel, SettingsGroup, SelectRow, ToggleRow } from '@fayz-ai/saas';
|
|
4
4
|
import * as React2 from 'react';
|
|
5
|
-
import { defineKpiWidget,
|
|
6
|
-
import {
|
|
5
|
+
import { defineKpiWidget, useModuleLayout, PageHeaderActions, Button, Badge, Input, KpiCard, DataTable, Card, toast } from '@fayz-ai/ui';
|
|
6
|
+
import { jsxs, jsx } from 'react/jsx-runtime';
|
|
7
7
|
|
|
8
8
|
// src/index.ts
|
|
9
9
|
|
|
@@ -314,6 +314,8 @@ function CoursesListPage() {
|
|
|
314
314
|
const shellOwnsHeader = useModuleLayout() === "tabs";
|
|
315
315
|
const [courses, setCourses] = React2.useState(null);
|
|
316
316
|
const [creating, setCreating] = React2.useState(false);
|
|
317
|
+
const guardCourses = useLimitGuard("courses");
|
|
318
|
+
const can = usePermissionOptional();
|
|
317
319
|
const load = React2.useCallback(async () => {
|
|
318
320
|
setCourses(await getCoursesProvider().listCourses());
|
|
319
321
|
}, []);
|
|
@@ -321,24 +323,27 @@ function CoursesListPage() {
|
|
|
321
323
|
void load();
|
|
322
324
|
}, [load]);
|
|
323
325
|
async function onCreate() {
|
|
326
|
+
if (!can("courses", "create")) return;
|
|
327
|
+
if (await guardCourses() === "blocked") return;
|
|
324
328
|
setCreating(true);
|
|
325
329
|
try {
|
|
326
330
|
const course = await getCoursesProvider().createCourse({
|
|
327
331
|
title: t("courses.editor.untitledCourse") || "Untitled course",
|
|
328
332
|
status: "draft"
|
|
329
333
|
});
|
|
334
|
+
invalidateLimit("courses");
|
|
330
335
|
navigateTo(`/courses/${course.id}`);
|
|
331
336
|
} finally {
|
|
332
337
|
setCreating(false);
|
|
333
338
|
}
|
|
334
339
|
}
|
|
335
340
|
return /* @__PURE__ */ jsxs("div", { className: "mx-auto max-w-6xl p-6 md:p-8", children: [
|
|
336
|
-
shellOwnsHeader ? /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" }) }) : /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
341
|
+
shellOwnsHeader ? /* @__PURE__ */ jsx(PageHeaderActions, { children: /* @__PURE__ */ jsx(PermissionGate, { feature: "courses", action: "create", children: /* @__PURE__ */ jsx(Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" }) }) }) : /* @__PURE__ */ jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
|
|
337
342
|
/* @__PURE__ */ jsxs("div", { children: [
|
|
338
343
|
/* @__PURE__ */ jsx("h1", { className: "text-2xl font-bold text-foreground", children: t("courses.title") || "Courses" }),
|
|
339
344
|
courses && /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: (t("courses.count") || "{count} courses").replace("{count}", String(courses.length)) })
|
|
340
345
|
] }),
|
|
341
|
-
/* @__PURE__ */ jsx(Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" })
|
|
346
|
+
/* @__PURE__ */ jsx(PermissionGate, { feature: "courses", action: "create", children: /* @__PURE__ */ jsx(Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" }) })
|
|
342
347
|
] }),
|
|
343
348
|
courses && courses.length === 0 && /* @__PURE__ */ 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." }),
|
|
344
349
|
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3", children: courses?.map((course) => /* @__PURE__ */ jsxs(
|
|
@@ -952,12 +957,14 @@ function FeesKpi() {
|
|
|
952
957
|
const s = useSummary();
|
|
953
958
|
return /* @__PURE__ */ jsx(KpiCard, { label: t("courses.kpi.fees") || "Platform fees", icon: "Percent", loading: !s, value: s ? formatMoney(s.platformFeeTotal, s.currency) : void 0 });
|
|
954
959
|
}
|
|
955
|
-
function createCoursesDashboardWidgets() {
|
|
960
|
+
function createCoursesDashboardWidgets(modules = {}) {
|
|
956
961
|
return [
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
962
|
+
...modules.sales ? [
|
|
963
|
+
defineKpiWidget({ id: "courses.kpi.revenue", title: "courses.kpi.revenue", domain: "courses", defaultOrder: 0, component: RevenueKpi }),
|
|
964
|
+
defineKpiWidget({ id: "courses.kpi.sales", title: "courses.kpi.sales", domain: "courses", defaultOrder: 1, component: SalesKpi })
|
|
965
|
+
] : [],
|
|
966
|
+
...modules.subscriptions ? [defineKpiWidget({ id: "courses.kpi.mrr", title: "courses.kpi.mrr", domain: "courses", defaultOrder: 2, component: MrrKpi })] : [],
|
|
967
|
+
...modules.financial ? [defineKpiWidget({ id: "courses.kpi.fees", title: "courses.kpi.fees", domain: "courses", defaultOrder: 3, component: FeesKpi })] : []
|
|
961
968
|
];
|
|
962
969
|
}
|
|
963
970
|
function createStripeConnector() {
|
|
@@ -997,6 +1004,27 @@ function createCoursesPlugin(options) {
|
|
|
997
1004
|
}
|
|
998
1005
|
const base = options?.navPosition ?? 1;
|
|
999
1006
|
const section = options?.navSection ?? "main";
|
|
1007
|
+
const modules = options?.modules ?? {};
|
|
1008
|
+
const labels = options?.labels ?? {};
|
|
1009
|
+
const navigation = [
|
|
1010
|
+
{ section, position: base, label: options?.navLabel ?? "Courses", route: "/courses", icon: "BookOpen", permission: read("courses") },
|
|
1011
|
+
...modules.membersArea ? [{ section, position: base + 1, label: labels.membersArea ?? "Members area", route: "/courses/members", icon: "GraduationCap", permission: read("courses") }] : [],
|
|
1012
|
+
...modules.sales ? [{ section, position: base + 2, label: labels.sales ?? "Sales", route: "/courses/sales", icon: "TrendingUp", permission: read("courses.sales") }] : [],
|
|
1013
|
+
...modules.subscriptions ? [{ section, position: base + 3, label: labels.subscriptions ?? "Subscriptions", route: "/courses/subscriptions", icon: "Repeat", permission: read("courses.sales") }] : [],
|
|
1014
|
+
...modules.financial ? [{ section, position: base + 4, label: labels.financial ?? "Financial", route: "/courses/financial", icon: "CreditCard", permission: read("courses.finance") }] : [],
|
|
1015
|
+
...modules.reports ? [{ section, position: base + 5, label: labels.reports ?? "Reports", route: "/courses/reports", icon: "BarChart3", permission: read("courses.sales") }] : []
|
|
1016
|
+
];
|
|
1017
|
+
const routes = [
|
|
1018
|
+
// Static segments outscore the `:id` param in the saas router, so these
|
|
1019
|
+
// resolve before the editor route (AdminShell sorts by routeScore).
|
|
1020
|
+
{ path: "/courses", component: asComp(CoursesListPage), permission: read("courses") },
|
|
1021
|
+
...modules.membersArea ? [{ path: "/courses/members", component: asComp(MembersAreaPage), permission: read("courses") }] : [],
|
|
1022
|
+
...modules.sales ? [{ path: "/courses/sales", component: asComp(SalesPage), permission: read("courses.sales") }] : [],
|
|
1023
|
+
...modules.subscriptions ? [{ path: "/courses/subscriptions", component: asComp(SubscriptionsPage), permission: read("courses.sales") }] : [],
|
|
1024
|
+
...modules.financial ? [{ path: "/courses/financial", component: asComp(FinancialPage), permission: read("courses.finance") }] : [],
|
|
1025
|
+
...modules.reports ? [{ path: "/courses/reports", component: asComp(ReportsPage), permission: read("courses.sales") }] : [],
|
|
1026
|
+
{ path: "/courses/:id", component: asComp(CourseEditorPage) }
|
|
1027
|
+
];
|
|
1000
1028
|
return {
|
|
1001
1029
|
id: "courses",
|
|
1002
1030
|
name: options?.navLabel ?? "Courses",
|
|
@@ -1004,6 +1032,10 @@ function createCoursesPlugin(options) {
|
|
|
1004
1032
|
version: "1.0.0",
|
|
1005
1033
|
scope: options?.scope ?? "vertical",
|
|
1006
1034
|
verticalId: options?.verticalId,
|
|
1035
|
+
// LMS is its own business model with opt-in commerce modules
|
|
1036
|
+
// (members/sales/subscriptions — see modules.* below) — targets the
|
|
1037
|
+
// saas/admin Panel world rather than the ecommerce storefront.
|
|
1038
|
+
scaffolds: ["saas"],
|
|
1007
1039
|
defaultEnabled: true,
|
|
1008
1040
|
dependencies: [],
|
|
1009
1041
|
declaredFeatures: [
|
|
@@ -1011,27 +1043,15 @@ function createCoursesPlugin(options) {
|
|
|
1011
1043
|
{ id: "courses.sales", label: "Sales", group: "Courses" },
|
|
1012
1044
|
{ id: "courses.finance", label: "Financial", group: "Courses" }
|
|
1013
1045
|
],
|
|
1014
|
-
|
|
1015
|
-
{
|
|
1016
|
-
{ section, position: base + 1, label: "Members area", route: "/courses/members", icon: "GraduationCap", permission: read("courses") },
|
|
1017
|
-
{ section, position: base + 2, label: "Sales", route: "/courses/sales", icon: "TrendingUp", permission: read("courses.sales") },
|
|
1018
|
-
{ section, position: base + 3, label: "Subscriptions", route: "/courses/subscriptions", icon: "Repeat", permission: read("courses.sales") },
|
|
1019
|
-
{ section, position: base + 4, label: "Financial", route: "/courses/financial", icon: "CreditCard", permission: read("courses.finance") },
|
|
1020
|
-
{ section, position: base + 5, label: "Reports", route: "/courses/reports", icon: "BarChart3", permission: read("courses.sales") }
|
|
1021
|
-
],
|
|
1022
|
-
routes: [
|
|
1023
|
-
// Static segments outscore the `:id` param in the saas router, so these
|
|
1024
|
-
// resolve before the editor route (AdminShell sorts by routeScore).
|
|
1025
|
-
{ path: "/courses", component: asComp(CoursesListPage), permission: read("courses") },
|
|
1026
|
-
{ path: "/courses/members", component: asComp(MembersAreaPage), permission: read("courses") },
|
|
1027
|
-
{ path: "/courses/sales", component: asComp(SalesPage), permission: read("courses.sales") },
|
|
1028
|
-
{ path: "/courses/subscriptions", component: asComp(SubscriptionsPage), permission: read("courses.sales") },
|
|
1029
|
-
{ path: "/courses/financial", component: asComp(FinancialPage), permission: read("courses.finance") },
|
|
1030
|
-
{ path: "/courses/reports", component: asComp(ReportsPage), permission: read("courses.sales") },
|
|
1031
|
-
{ path: "/courses/:id", component: asComp(CourseEditorPage) }
|
|
1046
|
+
declaredLimits: [
|
|
1047
|
+
{ key: "courses", label: "Courses", table: "plg_courses_courses" }
|
|
1032
1048
|
],
|
|
1049
|
+
navigation,
|
|
1050
|
+
routes,
|
|
1033
1051
|
widgets: [],
|
|
1034
|
-
|
|
1052
|
+
// Commerce KPIs on the shared 'home' surface follow the same opt-in rule:
|
|
1053
|
+
// no module enabled → no widgets broadcast into a host's dashboard.
|
|
1054
|
+
dashboardWidgets: createCoursesDashboardWidgets(modules),
|
|
1035
1055
|
connectors: [createStripeConnector()],
|
|
1036
1056
|
settings: [
|
|
1037
1057
|
{ id: "courses", label: "Courses", icon: "BookOpen", component: asComp(CoursesSettingsTab), order: 20, permission: read("courses") }
|