@fayz-ai/plugin-courses 0.8.0 → 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/dist/index.cjs +16 -7
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +17 -8
- 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 +5 -5
- package/src/index.ts +17 -5
- package/src/pages/CoursesListPage.tsx +19 -6
- package/src/pages/FinancialPage.tsx +5 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CoursesListPage.d.ts","sourceRoot":"","sources":["../../src/pages/CoursesListPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"CoursesListPage.d.ts","sourceRoot":"","sources":["../../src/pages/CoursesListPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAa9B,wBAAgB,eAAe,sBAuG9B;yBAvGe,eAAe"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"FinancialPage.d.ts","sourceRoot":"","sources":["../../src/pages/FinancialPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;
|
|
1
|
+
{"version":3,"file":"FinancialPage.d.ts","sourceRoot":"","sources":["../../src/pages/FinancialPage.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,MAAM,OAAO,CAAA;AAY9B,wBAAgB,aAAa,sBAmE5B;yBAnEe,aAAa"}
|
package/package.json
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"fayz": {
|
|
4
4
|
"status": "preview"
|
|
5
5
|
},
|
|
6
|
-
"version": "0.8.
|
|
6
|
+
"version": "0.8.1",
|
|
7
7
|
"description": "[experimental] Fayz SDK — courses admin management plugin",
|
|
8
8
|
"type": "module",
|
|
9
9
|
"main": "./dist/index.cjs",
|
|
@@ -28,10 +28,10 @@
|
|
|
28
28
|
"dependencies": {
|
|
29
29
|
"lucide-react": "^0.400.0",
|
|
30
30
|
"@tanstack/react-table": "^8.20.0",
|
|
31
|
-
"@fayz-ai/
|
|
32
|
-
"@fayz-ai/
|
|
33
|
-
"@fayz-ai/
|
|
34
|
-
"@fayz-ai/
|
|
31
|
+
"@fayz-ai/saas": "^0.8.2",
|
|
32
|
+
"@fayz-ai/core": "^0.8.2",
|
|
33
|
+
"@fayz-ai/ui": "^0.8.1",
|
|
34
|
+
"@fayz-ai/courses": "^0.8.0"
|
|
35
35
|
},
|
|
36
36
|
"devDependencies": {
|
|
37
37
|
"@types/react": "^18.3.0",
|
package/src/index.ts
CHANGED
|
@@ -33,6 +33,14 @@ export interface CoursesPluginOptions {
|
|
|
33
33
|
navPosition?: number
|
|
34
34
|
navSection?: 'main' | 'secondary' | 'settings'
|
|
35
35
|
navLabel?: string
|
|
36
|
+
/** Nav labels for the module entries (default English). */
|
|
37
|
+
labels?: {
|
|
38
|
+
membersArea?: string
|
|
39
|
+
sales?: string
|
|
40
|
+
subscriptions?: string
|
|
41
|
+
financial?: string
|
|
42
|
+
reports?: string
|
|
43
|
+
}
|
|
36
44
|
scope?: PluginScope
|
|
37
45
|
verticalId?: VerticalId
|
|
38
46
|
/**
|
|
@@ -74,13 +82,14 @@ export function createCoursesPlugin(options?: CoursesPluginOptions): PluginManif
|
|
|
74
82
|
|
|
75
83
|
// Opt-in nav/routes per module. Position offsets stay stable relative to the
|
|
76
84
|
// enabled set so hosts control exactly where the block sits.
|
|
85
|
+
const labels = options?.labels ?? {}
|
|
77
86
|
const navigation = [
|
|
78
87
|
{ section, position: base, label: options?.navLabel ?? 'Courses', route: '/courses', icon: 'BookOpen', permission: read('courses') },
|
|
79
|
-
...(modules.membersArea ? [{ section, position: base + 1, label: 'Members area', route: '/courses/members', icon: 'GraduationCap', permission: read('courses') }] : []),
|
|
80
|
-
...(modules.sales ? [{ section, position: base + 2, label: 'Sales', route: '/courses/sales', icon: 'TrendingUp', permission: read('courses.sales') }] : []),
|
|
81
|
-
...(modules.subscriptions ? [{ section, position: base + 3, label: 'Subscriptions', route: '/courses/subscriptions', icon: 'Repeat', permission: read('courses.sales') }] : []),
|
|
82
|
-
...(modules.financial ? [{ section, position: base + 4, label: 'Financial', route: '/courses/financial', icon: 'CreditCard', permission: read('courses.finance') }] : []),
|
|
83
|
-
...(modules.reports ? [{ section, position: base + 5, label: 'Reports', route: '/courses/reports', icon: 'BarChart3', permission: read('courses.sales') }] : []),
|
|
88
|
+
...(modules.membersArea ? [{ section, position: base + 1, label: labels.membersArea ?? 'Members area', route: '/courses/members', icon: 'GraduationCap', permission: read('courses') }] : []),
|
|
89
|
+
...(modules.sales ? [{ section, position: base + 2, label: labels.sales ?? 'Sales', route: '/courses/sales', icon: 'TrendingUp', permission: read('courses.sales') }] : []),
|
|
90
|
+
...(modules.subscriptions ? [{ section, position: base + 3, label: labels.subscriptions ?? 'Subscriptions', route: '/courses/subscriptions', icon: 'Repeat', permission: read('courses.sales') }] : []),
|
|
91
|
+
...(modules.financial ? [{ section, position: base + 4, label: labels.financial ?? 'Financial', route: '/courses/financial', icon: 'CreditCard', permission: read('courses.finance') }] : []),
|
|
92
|
+
...(modules.reports ? [{ section, position: base + 5, label: labels.reports ?? 'Reports', route: '/courses/reports', icon: 'BarChart3', permission: read('courses.sales') }] : []),
|
|
84
93
|
]
|
|
85
94
|
|
|
86
95
|
const routes = [
|
|
@@ -113,6 +122,9 @@ export function createCoursesPlugin(options?: CoursesPluginOptions): PluginManif
|
|
|
113
122
|
{ id: 'courses.sales', label: 'Sales', group: 'Courses' },
|
|
114
123
|
{ id: 'courses.finance', label: 'Financial', group: 'Courses' },
|
|
115
124
|
],
|
|
125
|
+
declaredLimits: [
|
|
126
|
+
{ key: 'courses', label: 'Courses', table: 'plg_courses_courses' },
|
|
127
|
+
],
|
|
116
128
|
navigation,
|
|
117
129
|
routes,
|
|
118
130
|
widgets: [],
|
|
@@ -2,6 +2,7 @@ import * as React from 'react'
|
|
|
2
2
|
import { Button, Badge, PageHeaderActions, useModuleLayout } from '@fayz-ai/ui'
|
|
3
3
|
import { useTranslation } from '@fayz-ai/core'
|
|
4
4
|
import { getCoursesProvider, type Course } from '@fayz-ai/courses'
|
|
5
|
+
import { useLimitGuard, invalidateLimit, PermissionGate, usePermissionOptional } from '@fayz-ai/saas'
|
|
5
6
|
import { navigateTo } from '../nav'
|
|
6
7
|
|
|
7
8
|
function statusVariant(status: Course['status']): 'success' | 'secondary' | 'outline' {
|
|
@@ -17,6 +18,8 @@ export function CoursesListPage() {
|
|
|
17
18
|
const shellOwnsHeader = useModuleLayout() === 'tabs'
|
|
18
19
|
const [courses, setCourses] = React.useState<Course[] | null>(null)
|
|
19
20
|
const [creating, setCreating] = React.useState(false)
|
|
21
|
+
const guardCourses = useLimitGuard('courses')
|
|
22
|
+
const can = usePermissionOptional()
|
|
20
23
|
|
|
21
24
|
const load = React.useCallback(async () => {
|
|
22
25
|
setCourses(await getCoursesProvider().listCourses())
|
|
@@ -27,12 +30,18 @@ export function CoursesListPage() {
|
|
|
27
30
|
}, [load])
|
|
28
31
|
|
|
29
32
|
async function onCreate() {
|
|
33
|
+
// Defense in depth: never create without the role's `create` action.
|
|
34
|
+
if (!can('courses', 'create')) return
|
|
35
|
+
// Plan quantity guard (client-side, before the provider call). Opens the
|
|
36
|
+
// global UpgradeModal and aborts when the plan's course cap is reached.
|
|
37
|
+
if ((await guardCourses()) === 'blocked') return
|
|
30
38
|
setCreating(true)
|
|
31
39
|
try {
|
|
32
40
|
const course = await getCoursesProvider().createCourse({
|
|
33
41
|
title: t('courses.editor.untitledCourse') || 'Untitled course',
|
|
34
42
|
status: 'draft',
|
|
35
43
|
})
|
|
44
|
+
invalidateLimit('courses')
|
|
36
45
|
navigateTo(`/courses/${course.id}`)
|
|
37
46
|
} finally {
|
|
38
47
|
setCreating(false)
|
|
@@ -43,9 +52,11 @@ export function CoursesListPage() {
|
|
|
43
52
|
<div className="mx-auto max-w-6xl p-6 md:p-8">
|
|
44
53
|
{shellOwnsHeader ? (
|
|
45
54
|
<PageHeaderActions>
|
|
46
|
-
<
|
|
47
|
-
{
|
|
48
|
-
|
|
55
|
+
<PermissionGate feature="courses" action="create">
|
|
56
|
+
<Button onClick={onCreate} disabled={creating}>
|
|
57
|
+
{t('courses.new') || 'New course'}
|
|
58
|
+
</Button>
|
|
59
|
+
</PermissionGate>
|
|
49
60
|
</PageHeaderActions>
|
|
50
61
|
) : (
|
|
51
62
|
<div className="mb-6 flex items-center justify-between">
|
|
@@ -57,9 +68,11 @@ export function CoursesListPage() {
|
|
|
57
68
|
</p>
|
|
58
69
|
)}
|
|
59
70
|
</div>
|
|
60
|
-
<
|
|
61
|
-
{
|
|
62
|
-
|
|
71
|
+
<PermissionGate feature="courses" action="create">
|
|
72
|
+
<Button onClick={onCreate} disabled={creating}>
|
|
73
|
+
{t('courses.new') || 'New course'}
|
|
74
|
+
</Button>
|
|
75
|
+
</PermissionGate>
|
|
63
76
|
</div>
|
|
64
77
|
)}
|
|
65
78
|
|
|
@@ -5,6 +5,11 @@ import { getCoursesProvider, type Payout, type FinancialSummary, type CreatorAcc
|
|
|
5
5
|
import { PageContainer, StatCard, StatGrid, SimpleTable, type SimpleColumn } from '../components/CommerceUI'
|
|
6
6
|
import { formatMoney, formatDate, formatPercent } from '../lib/format'
|
|
7
7
|
|
|
8
|
+
// No plan gate here on purpose. The page is registered as a shell ROUTE with
|
|
9
|
+
// `permission: read('courses.finance')`, so the route guard already resolves
|
|
10
|
+
// role AND plan for it — and whether `courses.finance` is a paid feature is the
|
|
11
|
+
// APP's call (its billing.ts), never the plugin's. Hardcoding a gate here would
|
|
12
|
+
// bake one product's packaging into the SDK.
|
|
8
13
|
export function FinancialPage() {
|
|
9
14
|
const t = useTranslation()
|
|
10
15
|
const [summary, setSummary] = React.useState<FinancialSummary | null>(null)
|