@academy-sdk/sdk 0.1.0 → 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/dist/bundle.js +70 -0
- package/dist/manifest.json +5 -0
- package/dist/styles.css +3307 -0
- package/package.json +40 -46
- package/src/components/atoms/Avatar.tsx +38 -0
- package/src/components/atoms/Badge.tsx +32 -0
- package/src/components/atoms/Button.tsx +48 -0
- package/src/components/atoms/Card.tsx +33 -0
- package/src/components/atoms/Input.tsx +39 -0
- package/src/components/atoms/ProgressBar.tsx +52 -0
- package/src/components/atoms/Tabs.tsx +47 -0
- package/src/components/atoms/index.ts +10 -0
- package/src/components/index.ts +11 -0
- package/src/components/molecules/CourseCard.tsx +215 -0
- package/src/components/molecules/EmptyState.tsx +23 -0
- package/src/components/molecules/LoadingSpinner.tsx +27 -0
- package/src/components/molecules/PageHeader.tsx +22 -0
- package/src/components/molecules/Pagination.tsx +82 -0
- package/src/components/molecules/SearchInput.tsx +35 -0
- package/src/components/molecules/index.ts +12 -0
- package/src/components/organisms/CourseSidebar.tsx +276 -0
- package/src/components/organisms/LearnerNavbar.tsx +129 -0
- package/src/components/organisms/LearnerSidebar.tsx +148 -0
- package/src/components/organisms/LessonBookmarks.tsx +128 -0
- package/src/components/organisms/LessonNotes.tsx +153 -0
- package/src/components/organisms/index.ts +10 -0
- package/src/components/pages/BundleDetailPage.tsx +388 -0
- package/src/components/pages/CatalogBundlesPage.tsx +96 -0
- package/src/components/pages/CatalogCoursesPage.tsx +299 -0
- package/src/components/pages/CourseDetailPage.tsx +582 -0
- package/src/components/pages/CoursePlayerPage.tsx +481 -0
- package/src/components/pages/CreatorProfilePage.tsx +161 -0
- package/src/components/pages/LearnerSettingsPage.tsx +58 -0
- package/src/components/pages/ManualReviewDetailPage.tsx +254 -0
- package/src/components/pages/ManualReviewPage.tsx +228 -0
- package/src/components/pages/MessagesPage.tsx +285 -0
- package/src/components/pages/MyLearningPage.tsx +239 -0
- package/src/components/pages/PaymentCancelPage.tsx +74 -0
- package/src/components/pages/PaymentSuccessPage.tsx +73 -0
- package/src/components/pages/index.ts +13 -0
- package/src/components/utils.ts +6 -0
- package/src/contracts/components.contract.ts +89 -0
- package/src/contracts/index.ts +4 -0
- package/src/contracts/layout.contract.ts +36 -0
- package/src/contracts/pages.contract.ts +275 -0
- package/src/contracts/template.contract.ts +100 -0
- package/src/default-template.tsx +52 -0
- package/src/hooks/index.ts +28 -0
- package/src/hooks/sdk-context.tsx +152 -0
- package/src/hooks/useAiCoach.ts +27 -0
- package/src/hooks/useBookmarks.ts +35 -0
- package/src/hooks/useCourseSearch.ts +18 -0
- package/src/hooks/useDebounce.ts +19 -0
- package/src/hooks/useMyBundles.ts +21 -0
- package/src/hooks/useMyCourses.ts +20 -0
- package/src/hooks/useNotes.ts +35 -0
- package/src/hooks/useNotifications.ts +16 -0
- package/src/hooks/useTheme.ts +17 -0
- package/src/hooks/useToast.ts +17 -0
- package/src/hooks/useUser.ts +33 -0
- package/src/index.ts +33 -0
- package/src/layouts/DefaultLayout.tsx +58 -0
- package/src/manifest.json +5 -0
- package/src/styles.css +43 -0
- package/src/types/ai-coach.ts +25 -0
- package/src/types/bookmarks.ts +20 -0
- package/src/types/bundle.ts +119 -0
- package/src/types/common.ts +24 -0
- package/src/types/course.ts +135 -0
- package/src/types/enrollment.ts +35 -0
- package/src/types/index.ts +15 -0
- package/src/types/lesson.ts +106 -0
- package/src/types/manual-review.ts +116 -0
- package/src/types/messaging.ts +109 -0
- package/src/types/notification.ts +30 -0
- package/src/types/payment.ts +40 -0
- package/src/types/progress.ts +19 -0
- package/src/types/rating.ts +20 -0
- package/src/types/search.ts +31 -0
- package/src/types/user.ts +16 -0
- package/src/utils/formatters.ts +74 -0
- package/src/utils/index.ts +8 -0
- package/dist/components/atoms/index.cjs +0 -318
- package/dist/components/atoms/index.js +0 -288
- package/dist/components/index.cjs +0 -1275
- package/dist/components/index.js +0 -1245
- package/dist/components/molecules/index.cjs +0 -334
- package/dist/components/molecules/index.js +0 -311
- package/dist/components/organisms/index.cjs +0 -855
- package/dist/components/organisms/index.js +0 -825
- package/dist/components/pages/index.cjs +0 -3306
- package/dist/components/pages/index.js +0 -3315
- package/dist/contracts/index.cjs +0 -52
- package/dist/contracts/index.js +0 -29
- package/dist/hooks/index.cjs +0 -165
- package/dist/hooks/index.js +0 -142
- package/dist/index.cjs +0 -630
- package/dist/index.js +0 -600
- package/dist/types/index.cjs +0 -18
- package/dist/types/index.js +0 -0
- package/dist/utils/index.cjs +0 -80
- package/dist/utils/index.js +0 -57
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export { MyLearningPage } from './MyLearningPage';
|
|
2
|
+
export { CatalogCoursesPage } from './CatalogCoursesPage';
|
|
3
|
+
export { CatalogBundlesPage } from './CatalogBundlesPage';
|
|
4
|
+
export { CourseDetailPage } from './CourseDetailPage';
|
|
5
|
+
export { BundleDetailPage } from './BundleDetailPage';
|
|
6
|
+
export { CoursePlayerPage } from './CoursePlayerPage';
|
|
7
|
+
export { MessagesPage } from './MessagesPage';
|
|
8
|
+
export { ManualReviewPage } from './ManualReviewPage';
|
|
9
|
+
export { ManualReviewDetailPage } from './ManualReviewDetailPage';
|
|
10
|
+
export { LearnerSettingsPage } from './LearnerSettingsPage';
|
|
11
|
+
export { PaymentSuccessPage } from './PaymentSuccessPage';
|
|
12
|
+
export { PaymentCancelPage } from './PaymentCancelPage';
|
|
13
|
+
export { CreatorProfilePage } from './CreatorProfilePage';
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Props for a course card component (used in grids/lists)
|
|
5
|
+
*/
|
|
6
|
+
export interface CourseCardProps {
|
|
7
|
+
id: string;
|
|
8
|
+
title: string;
|
|
9
|
+
thumbnail?: string;
|
|
10
|
+
thumbnails?: string[];
|
|
11
|
+
totalLessons: number;
|
|
12
|
+
progress: number;
|
|
13
|
+
status?: 'in-progress' | 'completed';
|
|
14
|
+
isFree: boolean;
|
|
15
|
+
price?: number;
|
|
16
|
+
currency?: string;
|
|
17
|
+
description?: string;
|
|
18
|
+
showPrice?: boolean;
|
|
19
|
+
showProgress?: boolean;
|
|
20
|
+
category?: string;
|
|
21
|
+
duration?: string;
|
|
22
|
+
instructorName?: string;
|
|
23
|
+
instructorAvatar?: string;
|
|
24
|
+
originalPrice?: number;
|
|
25
|
+
isBundle?: boolean;
|
|
26
|
+
onClick?: () => void;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Props for a video player component
|
|
31
|
+
*/
|
|
32
|
+
export interface VideoPlayerProps {
|
|
33
|
+
videoUrl: string;
|
|
34
|
+
provider?: 'YOUTUBE' | 'VIMEO' | 'LOOM' | 'OTHERS';
|
|
35
|
+
startTime?: number;
|
|
36
|
+
onReady?: (player: any) => void;
|
|
37
|
+
onPlay?: () => void;
|
|
38
|
+
onPause?: () => void;
|
|
39
|
+
onEnd?: () => void;
|
|
40
|
+
onStateChange?: (event: any) => void;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Props for pagination component
|
|
45
|
+
*/
|
|
46
|
+
export interface PaginationComponentProps {
|
|
47
|
+
currentPage: number;
|
|
48
|
+
totalPages: number;
|
|
49
|
+
total: number;
|
|
50
|
+
pageSize: number;
|
|
51
|
+
hasNextPage: boolean;
|
|
52
|
+
hasPreviousPage: boolean;
|
|
53
|
+
onPageChange: (page: number) => void;
|
|
54
|
+
maxVisiblePages?: number;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Props for a page header component
|
|
59
|
+
*/
|
|
60
|
+
export interface PageHeaderProps {
|
|
61
|
+
title: string;
|
|
62
|
+
description?: string;
|
|
63
|
+
className?: string;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Props for search results view
|
|
68
|
+
*/
|
|
69
|
+
export interface SearchResultsViewProps {
|
|
70
|
+
results: any;
|
|
71
|
+
isSearching: boolean;
|
|
72
|
+
searchQuery: string;
|
|
73
|
+
onCourseClick: (courseId: string, enrollmentId?: string) => void;
|
|
74
|
+
onLessonClick: (courseId: string, lessonId: string) => void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Props for the learner sidebar component override
|
|
79
|
+
*/
|
|
80
|
+
export interface LearnerSidebarComponentProps {
|
|
81
|
+
isOpen: boolean;
|
|
82
|
+
isCollapsed: boolean;
|
|
83
|
+
onClose: () => void;
|
|
84
|
+
onToggle: () => void;
|
|
85
|
+
currentPath: string;
|
|
86
|
+
onNavigate: (href: string) => void;
|
|
87
|
+
user: { name: string; email: string; avatar?: string } | null;
|
|
88
|
+
onLogout: () => void;
|
|
89
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { User, Membership } from '../types';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Props for the top-level learner layout wrapper.
|
|
6
|
+
* The template's layout receives these and renders navbar + sidebar + content.
|
|
7
|
+
*/
|
|
8
|
+
export interface LearnerLayoutProps {
|
|
9
|
+
children: ReactNode;
|
|
10
|
+
/** Current authenticated user */
|
|
11
|
+
user: User | null;
|
|
12
|
+
/** User's memberships */
|
|
13
|
+
memberships: Membership[];
|
|
14
|
+
/** Whether sidebar is open (mobile) */
|
|
15
|
+
sidebarOpen: boolean;
|
|
16
|
+
/** Whether sidebar is collapsed (desktop) */
|
|
17
|
+
sidebarCollapsed: boolean;
|
|
18
|
+
/** Toggle sidebar open/close */
|
|
19
|
+
onToggleSidebar: () => void;
|
|
20
|
+
/** Close sidebar (mobile) */
|
|
21
|
+
onCloseSidebar: () => void;
|
|
22
|
+
/** Logout handler */
|
|
23
|
+
onLogout: () => void;
|
|
24
|
+
/** Current pathname for active link highlighting */
|
|
25
|
+
pathname: string;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Navigation item for the sidebar
|
|
30
|
+
*/
|
|
31
|
+
export interface SidebarNavItem {
|
|
32
|
+
label: string;
|
|
33
|
+
href: string;
|
|
34
|
+
icon: string;
|
|
35
|
+
badge?: number;
|
|
36
|
+
}
|
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type {
|
|
3
|
+
Course,
|
|
4
|
+
CoursePlayerCourse,
|
|
5
|
+
CoursePlayerModule,
|
|
6
|
+
Bundle,
|
|
7
|
+
LearnerBundle,
|
|
8
|
+
LearnerBundleCourse,
|
|
9
|
+
SearchCourse,
|
|
10
|
+
SearchResponse,
|
|
11
|
+
PaginationMeta,
|
|
12
|
+
User,
|
|
13
|
+
LearnerManualReviewSubmission,
|
|
14
|
+
LearnerManualReviewSummary,
|
|
15
|
+
LearnerManualReviewPagination,
|
|
16
|
+
LearnerManualReviewDetailResponse,
|
|
17
|
+
LearnerManualReviewStatus,
|
|
18
|
+
AICoachInfo,
|
|
19
|
+
Rating,
|
|
20
|
+
RatingSummary,
|
|
21
|
+
PaymentProvider,
|
|
22
|
+
Coupon,
|
|
23
|
+
ValidateCouponResponse,
|
|
24
|
+
LessonNote,
|
|
25
|
+
Bookmark,
|
|
26
|
+
FAQItem,
|
|
27
|
+
LessonResources,
|
|
28
|
+
TranscriptItem,
|
|
29
|
+
} from '../types';
|
|
30
|
+
|
|
31
|
+
// ─── My Learning Page ────────────────────────────────────────────
|
|
32
|
+
export interface MyLearningPageProps {
|
|
33
|
+
user: User | null;
|
|
34
|
+
// Courses tab
|
|
35
|
+
courses: any[];
|
|
36
|
+
coursesMeta: PaginationMeta | null;
|
|
37
|
+
coursesLoading: boolean;
|
|
38
|
+
coursesPage: number;
|
|
39
|
+
onCoursesPageChange: (page: number) => void;
|
|
40
|
+
// Bundles tab
|
|
41
|
+
bundles: any[];
|
|
42
|
+
bundlesMeta: PaginationMeta | null;
|
|
43
|
+
bundlesLoading: boolean;
|
|
44
|
+
bundlesPage: number;
|
|
45
|
+
onBundlesPageChange: (page: number) => void;
|
|
46
|
+
// Search
|
|
47
|
+
searchQuery: string;
|
|
48
|
+
onSearchChange: (query: string) => void;
|
|
49
|
+
searchResults: SearchResponse | null;
|
|
50
|
+
isSearching: boolean;
|
|
51
|
+
// Filters
|
|
52
|
+
activeTab: 'courses' | 'bundles';
|
|
53
|
+
onTabChange: (tab: 'courses' | 'bundles') => void;
|
|
54
|
+
statusFilter: string;
|
|
55
|
+
onStatusFilterChange: (status: string) => void;
|
|
56
|
+
// Navigation
|
|
57
|
+
onCourseClick: (courseId: string, enrollmentId?: string) => void;
|
|
58
|
+
onBundleClick: (bundleId: string) => void;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// ─── Catalog Courses Page ────────────────────────────────────────
|
|
62
|
+
export interface CatalogCoursesPageProps {
|
|
63
|
+
courses: Course[];
|
|
64
|
+
meta: PaginationMeta | null;
|
|
65
|
+
loading: boolean;
|
|
66
|
+
searchQuery: string;
|
|
67
|
+
page: number;
|
|
68
|
+
onSearchChange: (query: string) => void;
|
|
69
|
+
onPageChange: (page: number) => void;
|
|
70
|
+
onCourseClick: (courseId: string) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ─── Catalog Bundles Page ────────────────────────────────────────
|
|
74
|
+
export interface CatalogBundlesPageProps {
|
|
75
|
+
bundles: Bundle[];
|
|
76
|
+
meta: PaginationMeta | null;
|
|
77
|
+
loading: boolean;
|
|
78
|
+
searchQuery: string;
|
|
79
|
+
page: number;
|
|
80
|
+
onSearchChange: (query: string) => void;
|
|
81
|
+
onPageChange: (page: number) => void;
|
|
82
|
+
onBundleClick: (bundleId: string) => void;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// ─── Course Detail Page ──────────────────────────────────────────
|
|
86
|
+
export interface CourseDetailPageProps {
|
|
87
|
+
course: any | null;
|
|
88
|
+
isLoading: boolean;
|
|
89
|
+
notFound: boolean;
|
|
90
|
+
isEnrolled: boolean;
|
|
91
|
+
enrollmentId: string | null;
|
|
92
|
+
// Enrollment actions
|
|
93
|
+
onEnroll: () => Promise<void>;
|
|
94
|
+
onStartLearning: (enrollmentId: string) => void;
|
|
95
|
+
isProcessingEnrollment: boolean;
|
|
96
|
+
// Payment
|
|
97
|
+
paymentProviders: PaymentProvider[];
|
|
98
|
+
onCheckout: (providerId: string) => Promise<void>;
|
|
99
|
+
isProcessingCheckout: boolean;
|
|
100
|
+
// Coupons
|
|
101
|
+
couponCode: string;
|
|
102
|
+
onCouponCodeChange: (code: string) => void;
|
|
103
|
+
onApplyCoupon: () => Promise<void>;
|
|
104
|
+
appliedCoupon: Coupon | null;
|
|
105
|
+
couponError: string | null;
|
|
106
|
+
isApplyingCoupon: boolean;
|
|
107
|
+
discountedPrice: number | undefined;
|
|
108
|
+
// Tabs
|
|
109
|
+
activeTab: 'overview' | 'curriculum';
|
|
110
|
+
onTabChange: (tab: 'overview' | 'curriculum') => void;
|
|
111
|
+
// Related courses
|
|
112
|
+
relatedCourses: any[];
|
|
113
|
+
isRelatedLoading: boolean;
|
|
114
|
+
onRelatedCourseClick: (courseId: string) => void;
|
|
115
|
+
// Ratings
|
|
116
|
+
ratings?: Rating[];
|
|
117
|
+
ratingSummary?: RatingSummary;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// ─── Bundle Detail Page ──────────────────────────────────────────
|
|
121
|
+
export interface BundleDetailPageProps {
|
|
122
|
+
bundle: LearnerBundle | null;
|
|
123
|
+
isLoading: boolean;
|
|
124
|
+
notFound: boolean;
|
|
125
|
+
isEnrolled: boolean;
|
|
126
|
+
// Enrollment
|
|
127
|
+
onEnroll: () => Promise<void>;
|
|
128
|
+
isProcessingEnrollment: boolean;
|
|
129
|
+
// Payment
|
|
130
|
+
paymentProviders: PaymentProvider[];
|
|
131
|
+
onCheckout: (providerId: string) => Promise<void>;
|
|
132
|
+
isProcessingCheckout: boolean;
|
|
133
|
+
// Coupons
|
|
134
|
+
couponCode: string;
|
|
135
|
+
onCouponCodeChange: (code: string) => void;
|
|
136
|
+
onApplyCoupon: () => Promise<void>;
|
|
137
|
+
appliedCoupon: Coupon | null;
|
|
138
|
+
couponError: string | null;
|
|
139
|
+
// Course list
|
|
140
|
+
onCourseClick: (courseId: string, enrollmentId?: string) => void;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// ─── Course Player Page ──────────────────────────────────────────
|
|
144
|
+
export interface CoursePlayerPageProps {
|
|
145
|
+
// Course data
|
|
146
|
+
course: CoursePlayerCourse | null;
|
|
147
|
+
actualCourseId: string | null;
|
|
148
|
+
isLoading: boolean;
|
|
149
|
+
courseNotFound: boolean;
|
|
150
|
+
// Current lesson
|
|
151
|
+
currentLessonId: string;
|
|
152
|
+
currentModuleIndex: number;
|
|
153
|
+
currentLessonIndex: number;
|
|
154
|
+
// Module navigation
|
|
155
|
+
expandedModules: Set<string>;
|
|
156
|
+
onToggleModule: (moduleId: string) => void;
|
|
157
|
+
onSelectLesson: (lessonId: string) => void;
|
|
158
|
+
// Progress
|
|
159
|
+
completedCount: number;
|
|
160
|
+
totalCount: number;
|
|
161
|
+
isCompleted: boolean;
|
|
162
|
+
// Video
|
|
163
|
+
currentVideoTime: number;
|
|
164
|
+
isPlaying: boolean;
|
|
165
|
+
onVideoReady: (player: any) => void;
|
|
166
|
+
onVideoPlay: () => void;
|
|
167
|
+
onVideoPause: () => void;
|
|
168
|
+
onVideoEnd: () => void;
|
|
169
|
+
onVideoStateChange: (event: any) => void;
|
|
170
|
+
// Tabs
|
|
171
|
+
activeTab: string;
|
|
172
|
+
onTabChange: (tab: string) => void;
|
|
173
|
+
// Notes
|
|
174
|
+
notes: LessonNote[];
|
|
175
|
+
onCreateNote: (text: string, timestamp?: number) => Promise<void>;
|
|
176
|
+
onUpdateNote: (noteId: string, text: string) => Promise<void>;
|
|
177
|
+
onDeleteNote: (noteId: string) => Promise<void>;
|
|
178
|
+
// Bookmarks
|
|
179
|
+
bookmarks: Bookmark[];
|
|
180
|
+
onCreateBookmark: (second: number, label?: string) => Promise<void>;
|
|
181
|
+
onUpdateBookmark: (id: string, label: string) => Promise<void>;
|
|
182
|
+
onDeleteBookmark: (id: string) => Promise<void>;
|
|
183
|
+
// AI Coach
|
|
184
|
+
aiCoachAvailable: boolean;
|
|
185
|
+
aiCoachInfo: AICoachInfo | null;
|
|
186
|
+
// Certificate
|
|
187
|
+
certificateEnabled: boolean;
|
|
188
|
+
onDownloadCertificate: () => Promise<void>;
|
|
189
|
+
// Rating
|
|
190
|
+
showRatingModal: boolean;
|
|
191
|
+
onSubmitRating: (rating: number, review: string) => Promise<void>;
|
|
192
|
+
onCloseRatingModal: () => void;
|
|
193
|
+
// Slack
|
|
194
|
+
slackCommunityEnabled: boolean;
|
|
195
|
+
onJoinSlackChannel: () => Promise<void>;
|
|
196
|
+
// Mobile
|
|
197
|
+
isMobileSidebarOpen: boolean;
|
|
198
|
+
onToggleMobileSidebar: () => void;
|
|
199
|
+
// Get live video time (for notes/bookmarks)
|
|
200
|
+
getLiveCurrentTime: () => number;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ─── Messages Page ───────────────────────────────────────────────
|
|
204
|
+
export interface MessagesPageProps {
|
|
205
|
+
conversations: any[];
|
|
206
|
+
isLoadingConversations: boolean;
|
|
207
|
+
selectedConversation: any | null;
|
|
208
|
+
onSelectConversation: (conv: any) => void;
|
|
209
|
+
messages: any[];
|
|
210
|
+
isLoadingMessages: boolean;
|
|
211
|
+
isLoadingMore: boolean;
|
|
212
|
+
hasMoreMessages: boolean;
|
|
213
|
+
onLoadMoreMessages: () => void;
|
|
214
|
+
onSendMessage: (content: string) => Promise<void>;
|
|
215
|
+
isSending: boolean;
|
|
216
|
+
searchQuery: string;
|
|
217
|
+
onSearchChange: (query: string) => void;
|
|
218
|
+
onDeleteMessage: (messageId: string) => void;
|
|
219
|
+
showNewMessageDialog: boolean;
|
|
220
|
+
onNewMessageDialogChange: (show: boolean) => void;
|
|
221
|
+
onNewMessage: (courseId: string, message: string, conversationId: string, courseName?: string) => void;
|
|
222
|
+
notifications: any[];
|
|
223
|
+
unreadCount: number;
|
|
224
|
+
mobileView: 'list' | 'chat';
|
|
225
|
+
onMobileViewChange: (view: 'list' | 'chat') => void;
|
|
226
|
+
currentUserId: string;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// ─── Manual Review Page ──────────────────────────────────────────
|
|
230
|
+
export interface ManualReviewPageProps {
|
|
231
|
+
submissions: LearnerManualReviewSubmission[];
|
|
232
|
+
summary: LearnerManualReviewSummary;
|
|
233
|
+
pagination: LearnerManualReviewPagination;
|
|
234
|
+
isLoading: boolean;
|
|
235
|
+
// Filters
|
|
236
|
+
filterStatus: LearnerManualReviewStatus | 'ALL';
|
|
237
|
+
onFilterStatusChange: (status: LearnerManualReviewStatus | 'ALL') => void;
|
|
238
|
+
searchQuery: string;
|
|
239
|
+
onSearchChange: (query: string) => void;
|
|
240
|
+
currentPage: number;
|
|
241
|
+
onPageChange: (page: number) => void;
|
|
242
|
+
// Navigation
|
|
243
|
+
onSubmissionClick: (submissionId: string) => void;
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
// ─── Manual Review Detail Page ───────────────────────────────────
|
|
247
|
+
export interface ManualReviewDetailPageProps {
|
|
248
|
+
submission: LearnerManualReviewDetailResponse | null;
|
|
249
|
+
isLoading: boolean;
|
|
250
|
+
notFound: boolean;
|
|
251
|
+
onBack: () => void;
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// ─── Learner Settings Page ───────────────────────────────────────
|
|
255
|
+
export interface LearnerSettingsPageProps {
|
|
256
|
+
user: User | null;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// ─── Payment Result Pages ────────────────────────────────────────
|
|
260
|
+
export interface PaymentSuccessPageProps {
|
|
261
|
+
onContinueLearning: () => void;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export interface PaymentCancelPageProps {
|
|
265
|
+
onRetry: () => void;
|
|
266
|
+
onGoBack: () => void;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// ─── Creator Profile Page ────────────────────────────────────────
|
|
270
|
+
export interface CreatorProfilePageProps {
|
|
271
|
+
creator: any | null;
|
|
272
|
+
courses: Course[];
|
|
273
|
+
isLoading: boolean;
|
|
274
|
+
onCourseClick: (courseId: string) => void;
|
|
275
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import type { ComponentType } from 'react';
|
|
2
|
+
import type { LearnerLayoutProps } from './layout.contract';
|
|
3
|
+
import type {
|
|
4
|
+
MyLearningPageProps,
|
|
5
|
+
CatalogCoursesPageProps,
|
|
6
|
+
CatalogBundlesPageProps,
|
|
7
|
+
CourseDetailPageProps,
|
|
8
|
+
BundleDetailPageProps,
|
|
9
|
+
CoursePlayerPageProps,
|
|
10
|
+
MessagesPageProps,
|
|
11
|
+
ManualReviewPageProps,
|
|
12
|
+
ManualReviewDetailPageProps,
|
|
13
|
+
LearnerSettingsPageProps,
|
|
14
|
+
PaymentSuccessPageProps,
|
|
15
|
+
PaymentCancelPageProps,
|
|
16
|
+
CreatorProfilePageProps,
|
|
17
|
+
} from './pages.contract';
|
|
18
|
+
import type {
|
|
19
|
+
CourseCardProps,
|
|
20
|
+
VideoPlayerProps,
|
|
21
|
+
PaginationComponentProps,
|
|
22
|
+
PageHeaderProps,
|
|
23
|
+
SearchResultsViewProps,
|
|
24
|
+
LearnerSidebarComponentProps,
|
|
25
|
+
} from './components.contract';
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* TemplateManifest defines the complete set of components a template must/can provide.
|
|
29
|
+
*
|
|
30
|
+
* Required: LearnerLayout + all page components
|
|
31
|
+
* Optional: component overrides (will fallback to platform defaults if not provided)
|
|
32
|
+
*/
|
|
33
|
+
export interface TemplateManifest {
|
|
34
|
+
/** Unique template name (e.g., "@client/modern-template") */
|
|
35
|
+
name: string;
|
|
36
|
+
/** Template version (semver) */
|
|
37
|
+
version: string;
|
|
38
|
+
|
|
39
|
+
/** The root layout component wrapping all learner pages */
|
|
40
|
+
LearnerLayout: ComponentType<LearnerLayoutProps>;
|
|
41
|
+
|
|
42
|
+
/** Page components — one per learner route */
|
|
43
|
+
pages: {
|
|
44
|
+
MyLearningPage: ComponentType<Partial<MyLearningPageProps>>;
|
|
45
|
+
CatalogCoursesPage: ComponentType<Partial<CatalogCoursesPageProps>>;
|
|
46
|
+
CatalogBundlesPage: ComponentType<Partial<CatalogBundlesPageProps>>;
|
|
47
|
+
CourseDetailPage: ComponentType<Partial<CourseDetailPageProps>>;
|
|
48
|
+
BundleDetailPage: ComponentType<Partial<BundleDetailPageProps>>;
|
|
49
|
+
CoursePlayerPage: ComponentType<Partial<CoursePlayerPageProps>>;
|
|
50
|
+
MessagesPage: ComponentType<Partial<MessagesPageProps>>;
|
|
51
|
+
ManualReviewPage: ComponentType<Partial<ManualReviewPageProps>>;
|
|
52
|
+
ManualReviewDetailPage: ComponentType<Partial<ManualReviewDetailPageProps>>;
|
|
53
|
+
LearnerSettingsPage: ComponentType<Partial<LearnerSettingsPageProps>>;
|
|
54
|
+
PaymentSuccessPage: ComponentType<Partial<PaymentSuccessPageProps>>;
|
|
55
|
+
PaymentCancelPage: ComponentType<Partial<PaymentCancelPageProps>>;
|
|
56
|
+
CreatorProfilePage: ComponentType<Partial<CreatorProfilePageProps>>;
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
/** Optional component overrides — templates can provide custom implementations */
|
|
60
|
+
components?: {
|
|
61
|
+
CourseCard?: ComponentType<CourseCardProps>;
|
|
62
|
+
VideoPlayer?: ComponentType<VideoPlayerProps>;
|
|
63
|
+
Pagination?: ComponentType<PaginationComponentProps>;
|
|
64
|
+
PageHeader?: ComponentType<PageHeaderProps>;
|
|
65
|
+
SearchResultsView?: ComponentType<SearchResultsViewProps>;
|
|
66
|
+
LearnerSidebar?: ComponentType<LearnerSidebarComponentProps>;
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Validates that a template manifest has all required fields.
|
|
72
|
+
* Throws an error if any required page is missing.
|
|
73
|
+
*/
|
|
74
|
+
export function validateTemplateManifest(manifest: TemplateManifest): void {
|
|
75
|
+
if (!manifest.name) throw new Error('Template manifest must have a name');
|
|
76
|
+
if (!manifest.version) throw new Error('Template manifest must have a version');
|
|
77
|
+
if (!manifest.LearnerLayout) throw new Error('Template must provide LearnerLayout');
|
|
78
|
+
|
|
79
|
+
const requiredPages = [
|
|
80
|
+
'MyLearningPage',
|
|
81
|
+
'CatalogCoursesPage',
|
|
82
|
+
'CatalogBundlesPage',
|
|
83
|
+
'CourseDetailPage',
|
|
84
|
+
'BundleDetailPage',
|
|
85
|
+
'CoursePlayerPage',
|
|
86
|
+
'MessagesPage',
|
|
87
|
+
'ManualReviewPage',
|
|
88
|
+
'ManualReviewDetailPage',
|
|
89
|
+
'LearnerSettingsPage',
|
|
90
|
+
'PaymentSuccessPage',
|
|
91
|
+
'PaymentCancelPage',
|
|
92
|
+
'CreatorProfilePage',
|
|
93
|
+
] as const;
|
|
94
|
+
|
|
95
|
+
for (const page of requiredPages) {
|
|
96
|
+
if (!manifest.pages[page]) {
|
|
97
|
+
throw new Error(`Template must provide pages.${page}`);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { TemplateManifest } from './contracts';
|
|
2
|
+
import { validateTemplateManifest } from './contracts';
|
|
3
|
+
import manifest from './manifest.json';
|
|
4
|
+
import { DefaultLayout } from './layouts/DefaultLayout';
|
|
5
|
+
import {
|
|
6
|
+
MyLearningPage,
|
|
7
|
+
CatalogCoursesPage,
|
|
8
|
+
CatalogBundlesPage,
|
|
9
|
+
CourseDetailPage,
|
|
10
|
+
BundleDetailPage,
|
|
11
|
+
CoursePlayerPage,
|
|
12
|
+
MessagesPage,
|
|
13
|
+
ManualReviewPage,
|
|
14
|
+
ManualReviewDetailPage,
|
|
15
|
+
LearnerSettingsPage,
|
|
16
|
+
PaymentSuccessPage,
|
|
17
|
+
PaymentCancelPage,
|
|
18
|
+
CreatorProfilePage,
|
|
19
|
+
} from './components/pages';
|
|
20
|
+
import { CourseCard, Pagination, PageHeader } from './components/molecules';
|
|
21
|
+
import { LearnerSidebar } from './components/organisms';
|
|
22
|
+
|
|
23
|
+
const template: TemplateManifest = {
|
|
24
|
+
name: manifest.name,
|
|
25
|
+
version: manifest.version,
|
|
26
|
+
LearnerLayout: DefaultLayout,
|
|
27
|
+
pages: {
|
|
28
|
+
MyLearningPage,
|
|
29
|
+
CatalogCoursesPage,
|
|
30
|
+
CatalogBundlesPage,
|
|
31
|
+
CourseDetailPage,
|
|
32
|
+
BundleDetailPage,
|
|
33
|
+
CoursePlayerPage,
|
|
34
|
+
MessagesPage,
|
|
35
|
+
ManualReviewPage,
|
|
36
|
+
ManualReviewDetailPage,
|
|
37
|
+
LearnerSettingsPage,
|
|
38
|
+
PaymentSuccessPage,
|
|
39
|
+
PaymentCancelPage,
|
|
40
|
+
CreatorProfilePage,
|
|
41
|
+
},
|
|
42
|
+
components: {
|
|
43
|
+
CourseCard,
|
|
44
|
+
Pagination,
|
|
45
|
+
PageHeader,
|
|
46
|
+
LearnerSidebar,
|
|
47
|
+
},
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
validateTemplateManifest(template);
|
|
51
|
+
|
|
52
|
+
export default template;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Context & Provider
|
|
2
|
+
export { SDKProvider, useSDK } from './sdk-context';
|
|
3
|
+
export type { SDKHookImplementations, SDKProviderProps } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
// User & Auth
|
|
6
|
+
export { useUser, useLogout } from './useUser';
|
|
7
|
+
|
|
8
|
+
// Data hooks
|
|
9
|
+
export { useMyCourses } from './useMyCourses';
|
|
10
|
+
export { useMyBundles } from './useMyBundles';
|
|
11
|
+
export { useCourseSearch } from './useCourseSearch';
|
|
12
|
+
|
|
13
|
+
// Lesson features
|
|
14
|
+
export { useLessonNotes, useCreateNote, useUpdateNote, useDeleteNote } from './useNotes';
|
|
15
|
+
export { useLessonBookmarks, useCreateBookmark, useUpdateBookmark, useDeleteBookmark } from './useBookmarks';
|
|
16
|
+
|
|
17
|
+
// AI Coach
|
|
18
|
+
export { useAiCoachAvailability, useAiCoachChat, useAiCoachHistory } from './useAiCoach';
|
|
19
|
+
|
|
20
|
+
// Notifications
|
|
21
|
+
export { useNotifications } from './useNotifications';
|
|
22
|
+
|
|
23
|
+
// Theme
|
|
24
|
+
export { useTheme } from './useTheme';
|
|
25
|
+
|
|
26
|
+
// Utilities
|
|
27
|
+
export { useDebounce } from './useDebounce';
|
|
28
|
+
export { useToast } from './useToast';
|