@fayz-ai/plugin-courses 0.2.0

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Faya Labs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,48 @@
1
+ # @fayz-ai/plugin-courses
2
+
3
+ > Drop a course-builder admin into any Fayz SaaS — courses, modules, lessons, enrollments.
4
+
5
+ [![npm](https://img.shields.io/npm/v/@fayz-ai/plugin-courses.svg)](https://www.npmjs.com/package/@fayz-ai/plugin-courses)
6
+ [![license](https://img.shields.io/npm/l/@fayz-ai/plugin-courses.svg)](https://github.com/FayaLabs/fayz-sdk/blob/main/LICENSE)
7
+
8
+ Every vertical eventually wants to sell knowledge. A salon teaches a masterclass, a clinic runs patient education, a gym ships training programs. Bolting an LMS onto each is wasted work. This plugin makes "courses" a capability you snap on: a navigation entry plus admin pages for authoring courses, organizing modules and lessons, and tracking enrollments and progress.
9
+
10
+ The admin pages are built directly on the `@fayz-ai/courses` engine through a swappable **provider** — ship the bundled mock to demo instantly, or wire your own (Supabase, REST) so the same UI runs on your data. One engine, many surfaces: the admin here, and a member-facing surface elsewhere, sharing the same provider and types. That's the Fayz way — compose a real SaaS from plugins instead of rebuilding the LMS for every app.
11
+
12
+ ## What's inside
13
+ - Navigation entry + routes: `/courses` (list) and `/courses/:id` (editor)
14
+ - Course list with create-and-open flow and draft / published / archived status
15
+ - Course editor with **Details** and **Curriculum** tabs — title, subtitle, description, price, status; modules and lessons
16
+ - A provider contract (`CoursesProvider`) covering courses, modules, lessons, enrollments, and progress
17
+ - A bundled mock provider so the admin renders out of the box (`createMockCoursesProvider`)
18
+ - Self-registers as the `courses` plugin factory; full i18n via bundled locales
19
+
20
+ ## Install
21
+ ```bash
22
+ npm install @fayz-ai/plugin-courses
23
+ ```
24
+ Peer deps: `@fayz-ai/core`, `@fayz-ai/ui`, `@fayz-ai/courses` (+ `react`, `react-dom`).
25
+
26
+ ## Usage
27
+ ```tsx
28
+ import { defineSaas } from '@fayz-ai/saas'
29
+ import { createCoursesPlugin } from '@fayz-ai/plugin-courses'
30
+
31
+ export const app = defineSaas({
32
+ // ...
33
+ plugins: [
34
+ createCoursesPlugin({
35
+ navLabel: 'Academy',
36
+ navSection: 'main',
37
+ navPosition: 1,
38
+ }),
39
+ ],
40
+ })
41
+ ```
42
+ To run on real data, set a provider before render via `setCoursesProvider(...)` from `@fayz-ai/courses`; if none is set, the plugin falls back to the bundled mock so the admin still renders.
43
+
44
+ ## Part of the Fayz SDK
45
+ The admin surface for the `@fayz-ai/courses` engine — one of the verticals you compose into a Fayz app.
46
+
47
+ ## Roadmap & contributing
48
+ Built and evolving in the open. See the [Fayz SDK roadmap](../../docs/ROADMAP.md#plugin-courses) for current gaps, missing features, and good first issues.
package/dist/index.cjs ADDED
@@ -0,0 +1,388 @@
1
+ 'use strict';
2
+
3
+ var core = require('@fayz-ai/core');
4
+ var courses = require('@fayz-ai/courses');
5
+ var React2 = require('react');
6
+ var ui = require('@fayz-ai/ui');
7
+ var jsxRuntime = require('react/jsx-runtime');
8
+
9
+ function _interopNamespace(e) {
10
+ if (e && e.__esModule) return e;
11
+ var n = Object.create(null);
12
+ if (e) {
13
+ Object.keys(e).forEach(function (k) {
14
+ if (k !== 'default') {
15
+ var d = Object.getOwnPropertyDescriptor(e, k);
16
+ Object.defineProperty(n, k, d.get ? d : {
17
+ enumerable: true,
18
+ get: function () { return e[k]; }
19
+ });
20
+ }
21
+ });
22
+ }
23
+ n.default = e;
24
+ return Object.freeze(n);
25
+ }
26
+
27
+ var React2__namespace = /*#__PURE__*/_interopNamespace(React2);
28
+
29
+ // src/index.ts
30
+
31
+ // src/locales/en.ts
32
+ var en = {
33
+ "courses.nav": "Courses",
34
+ "courses.title": "Courses",
35
+ "courses.new": "New course",
36
+ "courses.empty": "No courses yet. Create your first one.",
37
+ "courses.count": "{count} courses",
38
+ "courses.students": "students",
39
+ "courses.lessons": "lessons",
40
+ "courses.status.draft": "Draft",
41
+ "courses.status.published": "Published",
42
+ "courses.status.archived": "Archived",
43
+ "courses.editor.back": "Back to courses",
44
+ "courses.editor.details": "Details",
45
+ "courses.editor.curriculum": "Curriculum",
46
+ "courses.editor.fieldTitle": "Title",
47
+ "courses.editor.fieldSubtitle": "Subtitle",
48
+ "courses.editor.fieldDescription": "Description",
49
+ "courses.editor.fieldPrice": "Price",
50
+ "courses.editor.fieldStatus": "Status",
51
+ "courses.editor.save": "Save",
52
+ "courses.editor.saved": "Course saved",
53
+ "courses.editor.addModule": "Add module",
54
+ "courses.editor.moduleTitle": "Module title",
55
+ "courses.editor.addLesson": "Add lesson",
56
+ "courses.editor.lessonTitle": "Lesson title",
57
+ "courses.editor.lessonVideo": "Video URL (embed)",
58
+ "courses.editor.lessonDuration": "Duration (min)",
59
+ "courses.editor.deleteModule": "Delete module",
60
+ "courses.editor.deleteLesson": "Remove",
61
+ "courses.editor.untitledCourse": "Untitled course",
62
+ "courses.editor.untitledModule": "New module",
63
+ "courses.editor.untitledLesson": "New lesson"
64
+ };
65
+
66
+ // src/locales/pt-BR.ts
67
+ var ptBR = {
68
+ "courses.nav": "Cursos",
69
+ "courses.title": "Cursos",
70
+ "courses.new": "Novo curso",
71
+ "courses.empty": "Nenhum curso ainda. Crie o primeiro.",
72
+ "courses.count": "{count} cursos",
73
+ "courses.students": "alunos",
74
+ "courses.lessons": "aulas",
75
+ "courses.status.draft": "Rascunho",
76
+ "courses.status.published": "Publicado",
77
+ "courses.status.archived": "Arquivado",
78
+ "courses.editor.back": "Voltar aos cursos",
79
+ "courses.editor.details": "Detalhes",
80
+ "courses.editor.curriculum": "Conte\xFAdo",
81
+ "courses.editor.fieldTitle": "T\xEDtulo",
82
+ "courses.editor.fieldSubtitle": "Subt\xEDtulo",
83
+ "courses.editor.fieldDescription": "Descri\xE7\xE3o",
84
+ "courses.editor.fieldPrice": "Pre\xE7o",
85
+ "courses.editor.fieldStatus": "Status",
86
+ "courses.editor.save": "Salvar",
87
+ "courses.editor.saved": "Curso salvo",
88
+ "courses.editor.addModule": "Adicionar m\xF3dulo",
89
+ "courses.editor.moduleTitle": "T\xEDtulo do m\xF3dulo",
90
+ "courses.editor.addLesson": "Adicionar aula",
91
+ "courses.editor.lessonTitle": "T\xEDtulo da aula",
92
+ "courses.editor.lessonVideo": "URL do v\xEDdeo (embed)",
93
+ "courses.editor.lessonDuration": "Dura\xE7\xE3o (min)",
94
+ "courses.editor.deleteModule": "Excluir m\xF3dulo",
95
+ "courses.editor.deleteLesson": "Remover",
96
+ "courses.editor.untitledCourse": "Curso sem t\xEDtulo",
97
+ "courses.editor.untitledModule": "Novo m\xF3dulo",
98
+ "courses.editor.untitledLesson": "Nova aula"
99
+ };
100
+
101
+ // src/locales/index.ts
102
+ var coursesLocales = {
103
+ en,
104
+ "pt-BR": ptBR
105
+ };
106
+ var adapter = core.hashRouterAdapter();
107
+ function navigateTo(to) {
108
+ adapter.navigate(to);
109
+ }
110
+ function statusVariant(status) {
111
+ if (status === "published") return "success";
112
+ if (status === "archived") return "outline";
113
+ return "secondary";
114
+ }
115
+ function CoursesListPage() {
116
+ const t = core.useTranslation();
117
+ const shellOwnsHeader = ui.useModuleLayout() === "tabs";
118
+ const [courses$1, setCourses] = React2__namespace.useState(null);
119
+ const [creating, setCreating] = React2__namespace.useState(false);
120
+ const load = React2__namespace.useCallback(async () => {
121
+ setCourses(await courses.getCoursesProvider().listCourses());
122
+ }, []);
123
+ React2__namespace.useEffect(() => {
124
+ void load();
125
+ }, [load]);
126
+ async function onCreate() {
127
+ setCreating(true);
128
+ try {
129
+ const course = await courses.getCoursesProvider().createCourse({
130
+ title: t("courses.editor.untitledCourse") || "Untitled course",
131
+ status: "draft"
132
+ });
133
+ navigateTo(`/courses/${course.id}`);
134
+ } finally {
135
+ setCreating(false);
136
+ }
137
+ }
138
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-6xl p-6 md:p-8", children: [
139
+ shellOwnsHeader ? /* @__PURE__ */ jsxRuntime.jsx(ui.PageHeaderActions, { children: /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" }) }) : /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-6 flex items-center justify-between", children: [
140
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { children: [
141
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "text-2xl font-bold text-foreground", children: t("courses.title") || "Courses" }),
142
+ courses$1 && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "mt-1 text-sm text-muted-foreground", children: (t("courses.count") || "{count} courses").replace("{count}", String(courses$1.length)) })
143
+ ] }),
144
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: onCreate, disabled: creating, children: t("courses.new") || "New course" })
145
+ ] }),
146
+ 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." }),
147
+ /* @__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(
148
+ "button",
149
+ {
150
+ onClick: () => navigateTo(`/courses/${course.id}`),
151
+ className: "group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md",
152
+ children: [
153
+ /* @__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
154
+ /* @__PURE__ */ jsxRuntime.jsx("img", { src: course.thumbnailUrl, alt: "", className: "h-full w-full object-cover" }) }),
155
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2 p-4", children: [
156
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-start justify-between gap-2", children: [
157
+ /* @__PURE__ */ jsxRuntime.jsx("h3", { className: "font-semibold text-foreground line-clamp-2", children: course.title }),
158
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Badge, { variant: statusVariant(course.status), children: t(`courses.status.${course.status}`) || course.status })
159
+ ] }),
160
+ course.subtitle && /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm text-muted-foreground line-clamp-2", children: course.subtitle }),
161
+ /* @__PURE__ */ jsxRuntime.jsx("p", { className: "text-sm font-medium text-foreground", children: course.price > 0 ? `${course.currency} ${course.price.toFixed(2)}` : "\u2014" })
162
+ ] })
163
+ ]
164
+ },
165
+ course.id
166
+ )) })
167
+ ] });
168
+ }
169
+ CoursesListPage.displayName = "CoursesListPage";
170
+ var STATUSES = ["draft", "published", "archived"];
171
+ function CourseEditorPage({ id }) {
172
+ const t = core.useTranslation();
173
+ const [tab, setTab] = React2__namespace.useState("details");
174
+ const [course, setCourse] = React2__namespace.useState(null);
175
+ const [modules, setModules] = React2__namespace.useState([]);
176
+ const [lessons, setLessons] = React2__namespace.useState([]);
177
+ const reload = React2__namespace.useCallback(async () => {
178
+ if (!id) return;
179
+ const provider = courses.getCoursesProvider();
180
+ const c = await provider.getCourse(id);
181
+ setCourse(c);
182
+ if (c) {
183
+ const [m, l] = await Promise.all([provider.listModules(c.id), provider.listLessons(c.id)]);
184
+ setModules(m);
185
+ setLessons(l);
186
+ }
187
+ }, [id]);
188
+ React2__namespace.useEffect(() => {
189
+ void reload();
190
+ }, [reload]);
191
+ if (!course) {
192
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "p-8 text-muted-foreground", children: "\u2026" });
193
+ }
194
+ async function saveDetails() {
195
+ const provider = courses.getCoursesProvider();
196
+ await provider.updateCourse(course.id, {
197
+ title: course.title,
198
+ subtitle: course.subtitle ?? void 0,
199
+ description: course.description ?? void 0,
200
+ price: course.price,
201
+ status: course.status
202
+ });
203
+ ui.toast.success(t("courses.editor.saved") || "Course saved");
204
+ }
205
+ function patchCourse(patch) {
206
+ setCourse((c) => c ? { ...c, ...patch } : c);
207
+ }
208
+ async function addModule() {
209
+ await courses.getCoursesProvider().createModule({
210
+ courseId: course.id,
211
+ title: t("courses.editor.untitledModule") || "New module"
212
+ });
213
+ await reload();
214
+ }
215
+ async function renameModule(moduleId, title) {
216
+ await courses.getCoursesProvider().updateModule(moduleId, { title });
217
+ }
218
+ async function deleteModule(moduleId) {
219
+ await courses.getCoursesProvider().deleteModule(moduleId);
220
+ await reload();
221
+ }
222
+ async function addLesson(moduleId) {
223
+ await courses.getCoursesProvider().createLesson({
224
+ courseId: course.id,
225
+ moduleId,
226
+ title: t("courses.editor.untitledLesson") || "New lesson",
227
+ videoUrl: "https://www.youtube.com/embed/dQw4w9WgXcQ"
228
+ });
229
+ await reload();
230
+ }
231
+ async function patchLesson(lessonId, patch) {
232
+ await courses.getCoursesProvider().updateLesson(lessonId, {
233
+ title: patch.title,
234
+ videoUrl: patch.videoUrl,
235
+ durationSec: patch.durationSec,
236
+ description: patch.description ?? void 0
237
+ });
238
+ }
239
+ async function deleteLesson(lessonId) {
240
+ await courses.getCoursesProvider().deleteLesson(lessonId);
241
+ await reload();
242
+ }
243
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mx-auto max-w-4xl p-6 md:p-8", children: [
244
+ /* @__PURE__ */ jsxRuntime.jsxs("button", { onClick: () => navigateTo("/courses"), className: "mb-4 text-sm text-muted-foreground hover:text-foreground", children: [
245
+ "\u2190 ",
246
+ t("courses.editor.back") || "Back to courses"
247
+ ] }),
248
+ /* @__PURE__ */ jsxRuntime.jsx("h1", { className: "mb-1 text-2xl font-bold text-foreground", children: course.title }),
249
+ /* @__PURE__ */ jsxRuntime.jsx("div", { className: "mb-6 mt-4 flex gap-2 border-b border-border", children: ["details", "curriculum"].map((tk) => /* @__PURE__ */ jsxRuntime.jsx(
250
+ "button",
251
+ {
252
+ onClick: () => setTab(tk),
253
+ className: `-mb-px border-b-2 px-3 py-2 text-sm font-medium ${tab === tk ? "border-primary text-foreground" : "border-transparent text-muted-foreground"}`,
254
+ children: tk === "details" ? t("courses.editor.details") || "Details" : t("courses.editor.curriculum") || "Curriculum"
255
+ },
256
+ tk
257
+ )) }),
258
+ tab === "details" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "max-w-xl space-y-4", children: [
259
+ /* @__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 }) }) }),
260
+ /* @__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 }) }) }),
261
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldDescription") || "Description", children: /* @__PURE__ */ jsxRuntime.jsx(
262
+ "textarea",
263
+ {
264
+ className: "min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm",
265
+ value: course.description ?? "",
266
+ onChange: (e) => patchCourse({ description: e.target.value })
267
+ }
268
+ ) }),
269
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-4", children: [
270
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldPrice") || "Price", children: /* @__PURE__ */ jsxRuntime.jsx(
271
+ ui.Input,
272
+ {
273
+ type: "number",
274
+ value: String(course.price),
275
+ onChange: (e) => patchCourse({ price: Number(e.target.value) || 0 })
276
+ }
277
+ ) }),
278
+ /* @__PURE__ */ jsxRuntime.jsx(Field, { label: t("courses.editor.fieldStatus") || "Status", children: /* @__PURE__ */ jsxRuntime.jsx(
279
+ "select",
280
+ {
281
+ className: "h-10 rounded-md border border-input bg-background px-3 text-sm",
282
+ value: course.status,
283
+ onChange: (e) => patchCourse({ status: e.target.value }),
284
+ children: STATUSES.map((s) => /* @__PURE__ */ jsxRuntime.jsx("option", { value: s, children: t(`courses.status.${s}`) || s }, s))
285
+ }
286
+ ) })
287
+ ] }),
288
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: saveDetails, children: t("courses.editor.save") || "Save" })
289
+ ] }),
290
+ tab === "curriculum" && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-6", children: [
291
+ modules.map((mod) => /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "rounded-xl border border-border bg-card p-4", children: [
292
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "mb-3 flex items-center gap-2", children: [
293
+ /* @__PURE__ */ jsxRuntime.jsx(
294
+ ui.Input,
295
+ {
296
+ className: "font-medium",
297
+ defaultValue: mod.title,
298
+ onBlur: (e) => renameModule(mod.id, e.target.value),
299
+ "aria-label": t("courses.editor.moduleTitle") || "Module title"
300
+ }
301
+ ),
302
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "sm", onClick: () => deleteModule(mod.id), children: t("courses.editor.deleteModule") || "Delete module" })
303
+ ] }),
304
+ /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-2", children: [
305
+ 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: [
306
+ /* @__PURE__ */ jsxRuntime.jsx(
307
+ ui.Input,
308
+ {
309
+ defaultValue: lesson.title,
310
+ placeholder: t("courses.editor.lessonTitle") || "Lesson title",
311
+ onBlur: (e) => patchLesson(lesson.id, { title: e.target.value })
312
+ }
313
+ ),
314
+ /* @__PURE__ */ jsxRuntime.jsx(
315
+ ui.Input,
316
+ {
317
+ defaultValue: lesson.videoUrl,
318
+ placeholder: t("courses.editor.lessonVideo") || "Video URL (embed)",
319
+ onBlur: (e) => patchLesson(lesson.id, { videoUrl: e.target.value })
320
+ }
321
+ ),
322
+ /* @__PURE__ */ jsxRuntime.jsx(
323
+ ui.Input,
324
+ {
325
+ type: "number",
326
+ defaultValue: String(Math.round(lesson.durationSec / 60)),
327
+ onBlur: (e) => patchLesson(lesson.id, { durationSec: (Number(e.target.value) || 0) * 60 }),
328
+ "aria-label": t("courses.editor.lessonDuration") || "Duration (min)"
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { variant: "ghost", size: "sm", onClick: () => deleteLesson(lesson.id), children: t("courses.editor.deleteLesson") || "Remove" })
332
+ ] }, lesson.id)),
333
+ /* @__PURE__ */ jsxRuntime.jsxs(ui.Button, { variant: "outline", size: "sm", onClick: () => addLesson(mod.id), children: [
334
+ "+ ",
335
+ t("courses.editor.addLesson") || "Add lesson"
336
+ ] })
337
+ ] })
338
+ ] }, mod.id)),
339
+ /* @__PURE__ */ jsxRuntime.jsx(ui.Button, { onClick: addModule, children: t("courses.editor.addModule") || "Add module" })
340
+ ] })
341
+ ] });
342
+ }
343
+ CourseEditorPage.displayName = "CourseEditorPage";
344
+ function Field({ label, children }) {
345
+ return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "space-y-1.5", children: [
346
+ /* @__PURE__ */ jsxRuntime.jsx("label", { className: "text-sm font-medium text-foreground", children: label }),
347
+ children
348
+ ] });
349
+ }
350
+
351
+ // src/index.ts
352
+ function createCoursesPlugin(options) {
353
+ core.registerTranslations(coursesLocales);
354
+ if (!courses.getCoursesProviderOptional()) {
355
+ courses.setCoursesProvider(courses.createMockCoursesProvider());
356
+ }
357
+ return {
358
+ id: "courses",
359
+ name: options?.navLabel ?? "Courses",
360
+ icon: "BookOpen",
361
+ version: "1.0.0",
362
+ scope: options?.scope ?? "vertical",
363
+ verticalId: options?.verticalId,
364
+ defaultEnabled: true,
365
+ dependencies: [],
366
+ navigation: [
367
+ {
368
+ section: options?.navSection ?? "main",
369
+ position: options?.navPosition ?? 1,
370
+ label: options?.navLabel ?? "Courses",
371
+ route: "/courses",
372
+ icon: "BookOpen"
373
+ }
374
+ ],
375
+ routes: [
376
+ { path: "/courses", component: CoursesListPage },
377
+ { path: "/courses/:id", component: CourseEditorPage }
378
+ ],
379
+ widgets: [],
380
+ locales: coursesLocales
381
+ };
382
+ }
383
+ core.registerPluginFactory("courses", createCoursesPlugin);
384
+
385
+ exports.coursesLocales = coursesLocales;
386
+ exports.createCoursesPlugin = createCoursesPlugin;
387
+ //# sourceMappingURL=index.cjs.map
388
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/locales/en.ts","../src/locales/pt-BR.ts","../src/locales/index.ts","../src/nav.ts","../src/pages/CoursesListPage.tsx","../src/pages/CourseEditorPage.tsx","../src/index.ts"],"names":["hashRouterAdapter","useTranslation","useModuleLayout","courses","React","getCoursesProvider","jsxs","PageHeaderActions","jsx","Button","Badge","React2","toast","Input","registerTranslations","getCoursesProviderOptional","setCoursesProvider","createMockCoursesProvider","registerPluginFactory"],"mappings":"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","file":"index.cjs","sourcesContent":["export const en: Record<string, string> = {\n 'courses.nav': 'Courses',\n 'courses.title': 'Courses',\n 'courses.new': 'New course',\n 'courses.empty': 'No courses yet. Create your first one.',\n 'courses.count': '{count} courses',\n 'courses.students': 'students',\n 'courses.lessons': 'lessons',\n 'courses.status.draft': 'Draft',\n 'courses.status.published': 'Published',\n 'courses.status.archived': 'Archived',\n 'courses.editor.back': 'Back to courses',\n 'courses.editor.details': 'Details',\n 'courses.editor.curriculum': 'Curriculum',\n 'courses.editor.fieldTitle': 'Title',\n 'courses.editor.fieldSubtitle': 'Subtitle',\n 'courses.editor.fieldDescription': 'Description',\n 'courses.editor.fieldPrice': 'Price',\n 'courses.editor.fieldStatus': 'Status',\n 'courses.editor.save': 'Save',\n 'courses.editor.saved': 'Course saved',\n 'courses.editor.addModule': 'Add module',\n 'courses.editor.moduleTitle': 'Module title',\n 'courses.editor.addLesson': 'Add lesson',\n 'courses.editor.lessonTitle': 'Lesson title',\n 'courses.editor.lessonVideo': 'Video URL (embed)',\n 'courses.editor.lessonDuration': 'Duration (min)',\n 'courses.editor.deleteModule': 'Delete module',\n 'courses.editor.deleteLesson': 'Remove',\n 'courses.editor.untitledCourse': 'Untitled course',\n 'courses.editor.untitledModule': 'New module',\n 'courses.editor.untitledLesson': 'New lesson',\n}\n","export const ptBR: Record<string, string> = {\n 'courses.nav': 'Cursos',\n 'courses.title': 'Cursos',\n 'courses.new': 'Novo curso',\n 'courses.empty': 'Nenhum curso ainda. Crie o primeiro.',\n 'courses.count': '{count} cursos',\n 'courses.students': 'alunos',\n 'courses.lessons': 'aulas',\n 'courses.status.draft': 'Rascunho',\n 'courses.status.published': 'Publicado',\n 'courses.status.archived': 'Arquivado',\n 'courses.editor.back': 'Voltar aos cursos',\n 'courses.editor.details': 'Detalhes',\n 'courses.editor.curriculum': 'Conteúdo',\n 'courses.editor.fieldTitle': 'Título',\n 'courses.editor.fieldSubtitle': 'Subtítulo',\n 'courses.editor.fieldDescription': 'Descrição',\n 'courses.editor.fieldPrice': 'Preço',\n 'courses.editor.fieldStatus': 'Status',\n 'courses.editor.save': 'Salvar',\n 'courses.editor.saved': 'Curso salvo',\n 'courses.editor.addModule': 'Adicionar módulo',\n 'courses.editor.moduleTitle': 'Título do módulo',\n 'courses.editor.addLesson': 'Adicionar aula',\n 'courses.editor.lessonTitle': 'Título da aula',\n 'courses.editor.lessonVideo': 'URL do vídeo (embed)',\n 'courses.editor.lessonDuration': 'Duração (min)',\n 'courses.editor.deleteModule': 'Excluir módulo',\n 'courses.editor.deleteLesson': 'Remover',\n 'courses.editor.untitledCourse': 'Curso sem título',\n 'courses.editor.untitledModule': 'Novo módulo',\n 'courses.editor.untitledLesson': 'Nova aula',\n}\n","import { en } from './en'\nimport { ptBR } from './pt-BR'\n\nexport const coursesLocales: Record<string, Record<string, string>> = {\n en,\n 'pt-BR': ptBR,\n}\n","import { hashRouterAdapter } from '@fayz-ai/core'\n\n// Shared hash-router adapter — the same singleton the admin shell drives, so\n// navigateTo here updates the shell's active route.\nconst adapter = hashRouterAdapter()\n\nexport function navigateTo(to: string): void {\n adapter.navigate(to)\n}\n","import * as React from 'react'\nimport { Button, Badge, PageHeaderActions, useModuleLayout } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport { getCoursesProvider, type Course } from '@fayz-ai/courses'\nimport { navigateTo } from '../nav'\n\nfunction statusVariant(status: Course['status']): 'success' | 'secondary' | 'outline' {\n if (status === 'published') return 'success'\n if (status === 'archived') return 'outline'\n return 'secondary'\n}\n\nexport function CoursesListPage() {\n const t = useTranslation()\n // When the app shell owns the page header (sidebar/GHL-style layouts), don't\n // render our own title — and project the action button into the top bar.\n const shellOwnsHeader = useModuleLayout() === 'tabs'\n const [courses, setCourses] = React.useState<Course[] | null>(null)\n const [creating, setCreating] = React.useState(false)\n\n const load = React.useCallback(async () => {\n setCourses(await getCoursesProvider().listCourses())\n }, [])\n\n React.useEffect(() => {\n void load()\n }, [load])\n\n async function onCreate() {\n setCreating(true)\n try {\n const course = await getCoursesProvider().createCourse({\n title: t('courses.editor.untitledCourse') || 'Untitled course',\n status: 'draft',\n })\n navigateTo(`/courses/${course.id}`)\n } finally {\n setCreating(false)\n }\n }\n\n return (\n <div className=\"mx-auto max-w-6xl p-6 md:p-8\">\n {shellOwnsHeader ? (\n <PageHeaderActions>\n <Button onClick={onCreate} disabled={creating}>\n {t('courses.new') || 'New course'}\n </Button>\n </PageHeaderActions>\n ) : (\n <div className=\"mb-6 flex items-center justify-between\">\n <div>\n <h1 className=\"text-2xl font-bold text-foreground\">{t('courses.title') || 'Courses'}</h1>\n {courses && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {(t('courses.count') || '{count} courses').replace('{count}', String(courses.length))}\n </p>\n )}\n </div>\n <Button onClick={onCreate} disabled={creating}>\n {t('courses.new') || 'New course'}\n </Button>\n </div>\n )}\n\n {courses && courses.length === 0 && (\n <div className=\"rounded-xl border border-dashed border-border p-12 text-center text-muted-foreground\">\n {t('courses.empty') || 'No courses yet. Create your first one.'}\n </div>\n )}\n\n <div className=\"grid grid-cols-1 gap-4 sm:grid-cols-2 lg:grid-cols-3\">\n {courses?.map((course) => (\n <button\n key={course.id}\n onClick={() => navigateTo(`/courses/${course.id}`)}\n className=\"group overflow-hidden rounded-xl border border-border bg-card text-left transition hover:border-primary hover:shadow-md\"\n >\n <div className=\"aspect-video w-full overflow-hidden bg-muted\">\n {course.thumbnailUrl && (\n // eslint-disable-next-line @next/next/no-img-element\n <img src={course.thumbnailUrl} alt=\"\" className=\"h-full w-full object-cover\" />\n )}\n </div>\n <div className=\"space-y-2 p-4\">\n <div className=\"flex items-start justify-between gap-2\">\n <h3 className=\"font-semibold text-foreground line-clamp-2\">{course.title}</h3>\n <Badge variant={statusVariant(course.status)}>\n {t(`courses.status.${course.status}`) || course.status}\n </Badge>\n </div>\n {course.subtitle && (\n <p className=\"text-sm text-muted-foreground line-clamp-2\">{course.subtitle}</p>\n )}\n <p className=\"text-sm font-medium text-foreground\">\n {course.price > 0 ? `${course.currency} ${course.price.toFixed(2)}` : '—'}\n </p>\n </div>\n </button>\n ))}\n </div>\n </div>\n )\n}\nCoursesListPage.displayName = 'CoursesListPage'\n","import * as React from 'react'\nimport { Button, Input, toast } from '@fayz-ai/ui'\nimport { useTranslation } from '@fayz-ai/core'\nimport {\n getCoursesProvider,\n type Course, type Module, type Lesson, type CourseStatus,\n} from '@fayz-ai/courses'\nimport { navigateTo } from '../nav'\n\nconst STATUSES: CourseStatus[] = ['draft', 'published', 'archived']\n\nexport function CourseEditorPage({ id }: { id?: string }) {\n const t = useTranslation()\n const [tab, setTab] = React.useState<'details' | 'curriculum'>('details')\n const [course, setCourse] = React.useState<Course | null>(null)\n const [modules, setModules] = React.useState<Module[]>([])\n const [lessons, setLessons] = React.useState<Lesson[]>([])\n\n const reload = React.useCallback(async () => {\n if (!id) return\n const provider = getCoursesProvider()\n const c = await provider.getCourse(id)\n setCourse(c)\n if (c) {\n const [m, l] = await Promise.all([provider.listModules(c.id), provider.listLessons(c.id)])\n setModules(m)\n setLessons(l)\n }\n }, [id])\n\n React.useEffect(() => { void reload() }, [reload])\n\n if (!course) {\n return <div className=\"p-8 text-muted-foreground\">…</div>\n }\n\n async function saveDetails() {\n const provider = getCoursesProvider()\n await provider.updateCourse(course!.id, {\n title: course!.title,\n subtitle: course!.subtitle ?? undefined,\n description: course!.description ?? undefined,\n price: course!.price,\n status: course!.status,\n })\n toast.success(t('courses.editor.saved') || 'Course saved')\n }\n\n function patchCourse(patch: Partial<Course>) {\n setCourse((c) => (c ? { ...c, ...patch } : c))\n }\n\n // --- module / lesson mutations (persist immediately, then reload) ---------\n async function addModule() {\n await getCoursesProvider().createModule({\n courseId: course!.id,\n title: t('courses.editor.untitledModule') || 'New module',\n })\n await reload()\n }\n async function renameModule(moduleId: string, title: string) {\n await getCoursesProvider().updateModule(moduleId, { title })\n }\n async function deleteModule(moduleId: string) {\n await getCoursesProvider().deleteModule(moduleId)\n await reload()\n }\n async function addLesson(moduleId: string) {\n await getCoursesProvider().createLesson({\n courseId: course!.id,\n moduleId,\n title: t('courses.editor.untitledLesson') || 'New lesson',\n videoUrl: 'https://www.youtube.com/embed/dQw4w9WgXcQ',\n })\n await reload()\n }\n async function patchLesson(lessonId: string, patch: Partial<Lesson>) {\n await getCoursesProvider().updateLesson(lessonId, {\n title: patch.title,\n videoUrl: patch.videoUrl,\n durationSec: patch.durationSec,\n description: patch.description ?? undefined,\n })\n }\n async function deleteLesson(lessonId: string) {\n await getCoursesProvider().deleteLesson(lessonId)\n await reload()\n }\n\n return (\n <div className=\"mx-auto max-w-4xl p-6 md:p-8\">\n <button onClick={() => navigateTo('/courses')} className=\"mb-4 text-sm text-muted-foreground hover:text-foreground\">\n ← {t('courses.editor.back') || 'Back to courses'}\n </button>\n\n <h1 className=\"mb-1 text-2xl font-bold text-foreground\">{course.title}</h1>\n\n <div className=\"mb-6 mt-4 flex gap-2 border-b border-border\">\n {(['details', 'curriculum'] as const).map((tk) => (\n <button\n key={tk}\n onClick={() => setTab(tk)}\n className={`-mb-px border-b-2 px-3 py-2 text-sm font-medium ${\n tab === tk ? 'border-primary text-foreground' : 'border-transparent text-muted-foreground'\n }`}\n >\n {tk === 'details' ? (t('courses.editor.details') || 'Details') : (t('courses.editor.curriculum') || 'Curriculum')}\n </button>\n ))}\n </div>\n\n {tab === 'details' && (\n <div className=\"max-w-xl space-y-4\">\n <Field label={t('courses.editor.fieldTitle') || 'Title'}>\n <Input value={course.title} onChange={(e) => patchCourse({ title: e.target.value })} />\n </Field>\n <Field label={t('courses.editor.fieldSubtitle') || 'Subtitle'}>\n <Input value={course.subtitle ?? ''} onChange={(e) => patchCourse({ subtitle: e.target.value })} />\n </Field>\n <Field label={t('courses.editor.fieldDescription') || 'Description'}>\n <textarea\n className=\"min-h-24 w-full rounded-md border border-input bg-background px-3 py-2 text-sm\"\n value={course.description ?? ''}\n onChange={(e) => patchCourse({ description: e.target.value })}\n />\n </Field>\n <div className=\"flex gap-4\">\n <Field label={t('courses.editor.fieldPrice') || 'Price'}>\n <Input\n type=\"number\"\n value={String(course.price)}\n onChange={(e) => patchCourse({ price: Number(e.target.value) || 0 })}\n />\n </Field>\n <Field label={t('courses.editor.fieldStatus') || 'Status'}>\n <select\n className=\"h-10 rounded-md border border-input bg-background px-3 text-sm\"\n value={course.status}\n onChange={(e) => patchCourse({ status: e.target.value as CourseStatus })}\n >\n {STATUSES.map((s) => (\n <option key={s} value={s}>{t(`courses.status.${s}`) || s}</option>\n ))}\n </select>\n </Field>\n </div>\n <Button onClick={saveDetails}>{t('courses.editor.save') || 'Save'}</Button>\n </div>\n )}\n\n {tab === 'curriculum' && (\n <div className=\"space-y-6\">\n {modules.map((mod) => (\n <div key={mod.id} className=\"rounded-xl border border-border bg-card p-4\">\n <div className=\"mb-3 flex items-center gap-2\">\n <Input\n className=\"font-medium\"\n defaultValue={mod.title}\n onBlur={(e) => renameModule(mod.id, e.target.value)}\n aria-label={t('courses.editor.moduleTitle') || 'Module title'}\n />\n <Button variant=\"ghost\" size=\"sm\" onClick={() => deleteModule(mod.id)}>\n {t('courses.editor.deleteModule') || 'Delete module'}\n </Button>\n </div>\n <div className=\"space-y-2\">\n {lessons.filter((l) => l.moduleId === mod.id).map((lesson) => (\n <div key={lesson.id} className=\"grid grid-cols-1 gap-2 rounded-md border border-border p-3 md:grid-cols-[1fr_1fr_90px_auto]\">\n <Input\n defaultValue={lesson.title}\n placeholder={t('courses.editor.lessonTitle') || 'Lesson title'}\n onBlur={(e) => patchLesson(lesson.id, { title: e.target.value })}\n />\n <Input\n defaultValue={lesson.videoUrl}\n placeholder={t('courses.editor.lessonVideo') || 'Video URL (embed)'}\n onBlur={(e) => patchLesson(lesson.id, { videoUrl: e.target.value })}\n />\n <Input\n type=\"number\"\n defaultValue={String(Math.round(lesson.durationSec / 60))}\n onBlur={(e) => patchLesson(lesson.id, { durationSec: (Number(e.target.value) || 0) * 60 })}\n aria-label={t('courses.editor.lessonDuration') || 'Duration (min)'}\n />\n <Button variant=\"ghost\" size=\"sm\" onClick={() => deleteLesson(lesson.id)}>\n {t('courses.editor.deleteLesson') || 'Remove'}\n </Button>\n </div>\n ))}\n <Button variant=\"outline\" size=\"sm\" onClick={() => addLesson(mod.id)}>\n + {t('courses.editor.addLesson') || 'Add lesson'}\n </Button>\n </div>\n </div>\n ))}\n <Button onClick={addModule}>{t('courses.editor.addModule') || 'Add module'}</Button>\n </div>\n )}\n </div>\n )\n}\nCourseEditorPage.displayName = 'CourseEditorPage'\n\nfunction Field({ label, children }: { label: string; children: React.ReactNode }) {\n return (\n <div className=\"space-y-1.5\">\n <label className=\"text-sm font-medium text-foreground\">{label}</label>\n {children}\n </div>\n )\n}\n","import * as React from 'react'\nimport type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core'\nimport { registerTranslations, registerPluginFactory } from '@fayz-ai/core'\nimport {\n getCoursesProviderOptional,\n setCoursesProvider,\n createMockCoursesProvider,\n} from '@fayz-ai/courses'\nimport { coursesLocales } from './locales'\nimport { CoursesListPage } from './pages/CoursesListPage'\nimport { CourseEditorPage } from './pages/CourseEditorPage'\n\n// ---------------------------------------------------------------------------\n// @fayz-ai/plugin-courses — admin management for the courses domain. Mirrors\n// @fayz-ai/plugin-shop: a navigation entry + routes pointing at custom admin pages\n// built directly on @fayz-ai/courses. Native from day one (no saas-core imports).\n// ---------------------------------------------------------------------------\n\nexport interface CoursesPluginOptions {\n navPosition?: number\n navSection?: 'main' | 'secondary' | 'settings'\n navLabel?: string\n scope?: PluginScope\n verticalId?: VerticalId\n}\n\nexport function createCoursesPlugin(options?: CoursesPluginOptions): PluginManifest {\n // Register locales globally so keys resolve even before the I18nProvider mounts.\n registerTranslations(coursesLocales)\n\n // Defensive default: if the host app didn't set a provider, fall back to a\n // mock so the admin still renders. Apps that share admin↔member set their own.\n if (!getCoursesProviderOptional()) {\n setCoursesProvider(createMockCoursesProvider())\n }\n\n return {\n id: 'courses',\n name: options?.navLabel ?? 'Courses',\n icon: 'BookOpen',\n version: '1.0.0',\n scope: options?.scope ?? 'vertical',\n verticalId: options?.verticalId,\n defaultEnabled: true,\n dependencies: [],\n navigation: [\n {\n section: options?.navSection ?? 'main',\n position: options?.navPosition ?? 1,\n label: options?.navLabel ?? 'Courses',\n route: '/courses',\n icon: 'BookOpen',\n },\n ],\n routes: [\n { path: '/courses', component: CoursesListPage as React.ComponentType<unknown> },\n { path: '/courses/:id', component: CourseEditorPage as React.ComponentType<unknown> },\n ],\n widgets: [],\n locales: coursesLocales,\n }\n}\n\n// Self-register the factory so renderApp(manifest) with a PluginRef { id:\n// 'courses' } resolves without a separate plugins.generated.ts entry (apps may\n// still register explicitly; registration is idempotent on id).\nregisterPluginFactory('courses', createCoursesPlugin as (config?: Record<string, unknown>) => PluginManifest)\n\nexport { coursesLocales } from './locales'\n"]}
@@ -0,0 +1,11 @@
1
+ import type { PluginManifest, PluginScope, VerticalId } from '@fayz-ai/core';
2
+ export interface CoursesPluginOptions {
3
+ navPosition?: number;
4
+ navSection?: 'main' | 'secondary' | 'settings';
5
+ navLabel?: string;
6
+ scope?: PluginScope;
7
+ verticalId?: VerticalId;
8
+ }
9
+ export declare function createCoursesPlugin(options?: CoursesPluginOptions): PluginManifest;
10
+ export { coursesLocales } from './locales';
11
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +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;AAiB5E,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;CACxB;AAED,wBAAgB,mBAAmB,CAAC,OAAO,CAAC,EAAE,oBAAoB,GAAG,cAAc,CAmClF;AAOD,OAAO,EAAE,cAAc,EAAE,MAAM,WAAW,CAAA"}