@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,152 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { createContext, useContext, type ReactNode } from 'react';
|
|
4
|
+
import type { User, Membership } from '../types';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* SDK Hook implementations provided by the platform.
|
|
8
|
+
* Template developers don't interact with this directly — they use the individual hooks.
|
|
9
|
+
*/
|
|
10
|
+
export interface SDKHookImplementations {
|
|
11
|
+
// User & Auth
|
|
12
|
+
useUser: () => { user: User | null; isAuthenticated: boolean; memberships: Membership[] };
|
|
13
|
+
useLogout: () => { logout: () => void };
|
|
14
|
+
|
|
15
|
+
// My Learning
|
|
16
|
+
useMyCourses: (status?: string, page?: number, limit?: number) => {
|
|
17
|
+
courses: any[];
|
|
18
|
+
meta: any | null;
|
|
19
|
+
isLoading: boolean;
|
|
20
|
+
};
|
|
21
|
+
useMyBundles: (status?: string, page?: number, limit?: number, search?: string) => {
|
|
22
|
+
bundles: any[];
|
|
23
|
+
meta: any | null;
|
|
24
|
+
isLoading: boolean;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Search
|
|
28
|
+
useCourseSearch: (query: string) => {
|
|
29
|
+
results: any | null;
|
|
30
|
+
isSearching: boolean;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
// Notes
|
|
34
|
+
useLessonNotes: (activityId: string) => {
|
|
35
|
+
data: any[];
|
|
36
|
+
isLoading: boolean;
|
|
37
|
+
error: Error | null;
|
|
38
|
+
refetch: () => Promise<void>;
|
|
39
|
+
};
|
|
40
|
+
useCreateNote: () => {
|
|
41
|
+
createNote: (payload: any) => Promise<any>;
|
|
42
|
+
isLoading: boolean;
|
|
43
|
+
};
|
|
44
|
+
useUpdateNote: () => {
|
|
45
|
+
updateNote: (noteId: string, payload: any) => Promise<any>;
|
|
46
|
+
isLoading: boolean;
|
|
47
|
+
};
|
|
48
|
+
useDeleteNote: () => {
|
|
49
|
+
deleteNote: (noteId: string) => Promise<boolean>;
|
|
50
|
+
isLoading: boolean;
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Bookmarks
|
|
54
|
+
useLessonBookmarks: (activityId: string) => {
|
|
55
|
+
data: any[];
|
|
56
|
+
isLoading: boolean;
|
|
57
|
+
error: Error | null;
|
|
58
|
+
refetch: () => Promise<void>;
|
|
59
|
+
};
|
|
60
|
+
useCreateBookmark: () => {
|
|
61
|
+
createBookmark: (payload: any) => Promise<any>;
|
|
62
|
+
isLoading: boolean;
|
|
63
|
+
};
|
|
64
|
+
useUpdateBookmark: () => {
|
|
65
|
+
updateBookmark: (id: string, payload: any) => Promise<any>;
|
|
66
|
+
isLoading: boolean;
|
|
67
|
+
};
|
|
68
|
+
useDeleteBookmark: () => {
|
|
69
|
+
deleteBookmark: (id: string) => Promise<boolean>;
|
|
70
|
+
isLoading: boolean;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// AI Coach
|
|
74
|
+
useAiCoachAvailability: (courseId?: string | null) => {
|
|
75
|
+
loading: boolean;
|
|
76
|
+
active: boolean;
|
|
77
|
+
coach: any | null;
|
|
78
|
+
error: Error | null;
|
|
79
|
+
};
|
|
80
|
+
useAiCoachChat: (courseId: string | null | undefined) => {
|
|
81
|
+
messages: any[];
|
|
82
|
+
threadId: string | null;
|
|
83
|
+
isStreaming: boolean;
|
|
84
|
+
error: Error | null;
|
|
85
|
+
sendMessage: (text: string) => Promise<void>;
|
|
86
|
+
stopStreaming: () => void;
|
|
87
|
+
};
|
|
88
|
+
useAiCoachHistory: (courseId: string | null | undefined, enabled: boolean) => {
|
|
89
|
+
loading: boolean;
|
|
90
|
+
threadId: string | null;
|
|
91
|
+
messages: any[];
|
|
92
|
+
groupedHistory: any[];
|
|
93
|
+
hasMore: boolean;
|
|
94
|
+
loadMore: () => Promise<void>;
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// Notifications
|
|
98
|
+
useNotifications: () => {
|
|
99
|
+
notifications: any[];
|
|
100
|
+
unreadCount: number;
|
|
101
|
+
isLoading: boolean;
|
|
102
|
+
isLoadingMore: boolean;
|
|
103
|
+
hasMore: boolean;
|
|
104
|
+
loadMore: () => void;
|
|
105
|
+
refresh: () => void;
|
|
106
|
+
markConversationRead: (conversationId: string) => Promise<void>;
|
|
107
|
+
};
|
|
108
|
+
|
|
109
|
+
// Theme
|
|
110
|
+
useTheme: () => {
|
|
111
|
+
theme: string;
|
|
112
|
+
setTheme: (theme: string) => void;
|
|
113
|
+
};
|
|
114
|
+
|
|
115
|
+
// Debounce utility hook
|
|
116
|
+
useDebounce: <T>(value: T, delay: number) => T;
|
|
117
|
+
|
|
118
|
+
// Toast notifications
|
|
119
|
+
useToast: () => {
|
|
120
|
+
toast: (props: { title?: string; description?: string; variant?: 'default' | 'destructive' }) => void;
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
const SDKContext = createContext<SDKHookImplementations | null>(null);
|
|
125
|
+
|
|
126
|
+
export interface SDKProviderProps {
|
|
127
|
+
implementations: SDKHookImplementations;
|
|
128
|
+
children: ReactNode;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* SDKProvider wraps learner routes and provides hook implementations.
|
|
133
|
+
* Used by the platform internally — template developers don't need this.
|
|
134
|
+
*/
|
|
135
|
+
export function SDKProvider({ implementations, children }: SDKProviderProps) {
|
|
136
|
+
return <SDKContext.Provider value={implementations}>{children}</SDKContext.Provider>;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* Internal hook to access SDK implementations.
|
|
141
|
+
* @internal
|
|
142
|
+
*/
|
|
143
|
+
export function useSDK(): SDKHookImplementations {
|
|
144
|
+
const ctx = useContext(SDKContext);
|
|
145
|
+
if (!ctx) {
|
|
146
|
+
throw new Error(
|
|
147
|
+
'useSDK must be used within an SDKProvider. ' +
|
|
148
|
+
'Make sure your learner routes are wrapped with <SDKProvider>.'
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
return ctx;
|
|
152
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Check if AI Coach is available for a course.
|
|
7
|
+
*/
|
|
8
|
+
export function useAiCoachAvailability(courseId?: string | null) {
|
|
9
|
+
const sdk = useSDK();
|
|
10
|
+
return sdk.useAiCoachAvailability(courseId);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Chat with the AI Coach.
|
|
15
|
+
*/
|
|
16
|
+
export function useAiCoachChat(courseId: string | null | undefined) {
|
|
17
|
+
const sdk = useSDK();
|
|
18
|
+
return sdk.useAiCoachChat(courseId);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get AI Coach chat history.
|
|
23
|
+
*/
|
|
24
|
+
export function useAiCoachHistory(courseId: string | null | undefined, enabled: boolean) {
|
|
25
|
+
const sdk = useSDK();
|
|
26
|
+
return sdk.useAiCoachHistory(courseId, enabled);
|
|
27
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetch bookmarks for a lesson/activity.
|
|
7
|
+
*/
|
|
8
|
+
export function useLessonBookmarks(activityId: string) {
|
|
9
|
+
const sdk = useSDK();
|
|
10
|
+
return sdk.useLessonBookmarks(activityId);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a new bookmark.
|
|
15
|
+
*/
|
|
16
|
+
export function useCreateBookmark() {
|
|
17
|
+
const sdk = useSDK();
|
|
18
|
+
return sdk.useCreateBookmark();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Update an existing bookmark.
|
|
23
|
+
*/
|
|
24
|
+
export function useUpdateBookmark() {
|
|
25
|
+
const sdk = useSDK();
|
|
26
|
+
return sdk.useUpdateBookmark();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Delete a bookmark.
|
|
31
|
+
*/
|
|
32
|
+
export function useDeleteBookmark() {
|
|
33
|
+
const sdk = useSDK();
|
|
34
|
+
return sdk.useDeleteBookmark();
|
|
35
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Search enrolled courses.
|
|
7
|
+
*
|
|
8
|
+
* @param query - Search query string
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```tsx
|
|
12
|
+
* const { results, isSearching } = useCourseSearch(debouncedQuery);
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
export function useCourseSearch(query: string) {
|
|
16
|
+
const sdk = useSDK();
|
|
17
|
+
return sdk.useCourseSearch(query);
|
|
18
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Debounce a value.
|
|
7
|
+
*
|
|
8
|
+
* @param value - The value to debounce
|
|
9
|
+
* @param delay - Delay in milliseconds
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```tsx
|
|
13
|
+
* const debouncedSearch = useDebounce(searchQuery, 500);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function useDebounce<T>(value: T, delay: number): T {
|
|
17
|
+
const sdk = useSDK();
|
|
18
|
+
return sdk.useDebounce(value, delay);
|
|
19
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetch the current user's enrolled bundles.
|
|
7
|
+
*
|
|
8
|
+
* @param status - Filter by status: 'IN_PROGRESS' | 'COMPLETED' | undefined (all)
|
|
9
|
+
* @param page - Page number (1-based)
|
|
10
|
+
* @param limit - Items per page (default: 12)
|
|
11
|
+
* @param search - Optional search query
|
|
12
|
+
*
|
|
13
|
+
* @example
|
|
14
|
+
* ```tsx
|
|
15
|
+
* const { bundles, meta, isLoading } = useMyBundles(undefined, 1, 12);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
export function useMyBundles(status?: string, page?: number, limit?: number, search?: string) {
|
|
19
|
+
const sdk = useSDK();
|
|
20
|
+
return sdk.useMyBundles(status, page, limit, search);
|
|
21
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetch the current user's enrolled courses.
|
|
7
|
+
*
|
|
8
|
+
* @param status - Filter by status: 'IN_PROGRESS' | 'COMPLETED' | undefined (all)
|
|
9
|
+
* @param page - Page number (1-based)
|
|
10
|
+
* @param limit - Items per page (default: 12)
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```tsx
|
|
14
|
+
* const { courses, meta, isLoading } = useMyCourses('IN_PROGRESS', 1, 12);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
export function useMyCourses(status?: string, page?: number, limit?: number) {
|
|
18
|
+
const sdk = useSDK();
|
|
19
|
+
return sdk.useMyCourses(status, page, limit);
|
|
20
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Fetch notes for a lesson/activity.
|
|
7
|
+
*/
|
|
8
|
+
export function useLessonNotes(activityId: string) {
|
|
9
|
+
const sdk = useSDK();
|
|
10
|
+
return sdk.useLessonNotes(activityId);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a new note.
|
|
15
|
+
*/
|
|
16
|
+
export function useCreateNote() {
|
|
17
|
+
const sdk = useSDK();
|
|
18
|
+
return sdk.useCreateNote();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Update an existing note.
|
|
23
|
+
*/
|
|
24
|
+
export function useUpdateNote() {
|
|
25
|
+
const sdk = useSDK();
|
|
26
|
+
return sdk.useUpdateNote();
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Delete a note.
|
|
31
|
+
*/
|
|
32
|
+
export function useDeleteNote() {
|
|
33
|
+
const sdk = useSDK();
|
|
34
|
+
return sdk.useDeleteNote();
|
|
35
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Manage notifications (message notifications).
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { notifications, unreadCount, markConversationRead } = useNotifications();
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export function useNotifications() {
|
|
14
|
+
const sdk = useSDK();
|
|
15
|
+
return sdk.useNotifications();
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Access and change the current theme.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { theme, setTheme } = useTheme();
|
|
11
|
+
* return <button onClick={() => setTheme('dark')}>Dark Mode</button>;
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function useTheme() {
|
|
15
|
+
const sdk = useSDK();
|
|
16
|
+
return sdk.useTheme();
|
|
17
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Show toast notifications.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { toast } = useToast();
|
|
11
|
+
* toast({ title: 'Success', description: 'Course enrolled!' });
|
|
12
|
+
* ```
|
|
13
|
+
*/
|
|
14
|
+
export function useToast() {
|
|
15
|
+
const sdk = useSDK();
|
|
16
|
+
return sdk.useToast();
|
|
17
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useSDK } from './sdk-context';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Get the current authenticated user.
|
|
7
|
+
*
|
|
8
|
+
* @example
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { user, isAuthenticated } = useUser();
|
|
11
|
+
* if (user) {
|
|
12
|
+
* return <span>Hello, {user.name}</span>;
|
|
13
|
+
* }
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export function useUser() {
|
|
17
|
+
const sdk = useSDK();
|
|
18
|
+
return sdk.useUser();
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Get the logout function.
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const { logout } = useLogout();
|
|
27
|
+
* return <button onClick={logout}>Log out</button>;
|
|
28
|
+
* ```
|
|
29
|
+
*/
|
|
30
|
+
export function useLogout() {
|
|
31
|
+
const sdk = useSDK();
|
|
32
|
+
return sdk.useLogout();
|
|
33
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @academy/sdk
|
|
3
|
+
*
|
|
4
|
+
* SDK for building custom learner templates for the Academy platform.
|
|
5
|
+
*
|
|
6
|
+
* @example
|
|
7
|
+
* ```tsx
|
|
8
|
+
* import { useMyCourses, useUser } from '@academy/sdk';
|
|
9
|
+
* import type { MyLearningPageProps, TemplateManifest } from '@academy/sdk';
|
|
10
|
+
* ```
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
// Types
|
|
14
|
+
export * from './types';
|
|
15
|
+
|
|
16
|
+
// Hooks
|
|
17
|
+
export * from './hooks';
|
|
18
|
+
|
|
19
|
+
// Contracts
|
|
20
|
+
export * from './contracts';
|
|
21
|
+
|
|
22
|
+
// Utilities
|
|
23
|
+
export * from './utils';
|
|
24
|
+
|
|
25
|
+
// Layouts
|
|
26
|
+
export { DefaultLayout } from './layouts/DefaultLayout';
|
|
27
|
+
|
|
28
|
+
// Components are available via sub-path imports:
|
|
29
|
+
// import { Button, Card } from '@academy/sdk/components/atoms';
|
|
30
|
+
// import { CourseCard, Pagination } from '@academy/sdk/components/molecules';
|
|
31
|
+
// import { LearnerNavbar, LearnerSidebar } from '@academy/sdk/components/organisms';
|
|
32
|
+
// import { MyLearningPage, CatalogCoursesPage } from '@academy/sdk/components/pages';
|
|
33
|
+
// Or import all: import { Button, CourseCard } from '@academy/sdk/components';
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState, type ReactNode } from 'react';
|
|
4
|
+
import { LearnerNavbar } from '../components/organisms/LearnerNavbar';
|
|
5
|
+
import { LearnerSidebar } from '../components/organisms/LearnerSidebar';
|
|
6
|
+
|
|
7
|
+
interface DefaultLayoutProps {
|
|
8
|
+
children: ReactNode;
|
|
9
|
+
currentPath?: string;
|
|
10
|
+
onNavigate?: (path: string) => void;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function DefaultLayout({ children, currentPath = '/my-learning', onNavigate }: DefaultLayoutProps) {
|
|
14
|
+
const [sidebarCollapsed, setSidebarCollapsed] = useState(false);
|
|
15
|
+
const [sidebarOpen, setSidebarOpen] = useState(false);
|
|
16
|
+
|
|
17
|
+
return (
|
|
18
|
+
<div className="min-h-screen bg-theme-bg-primary">
|
|
19
|
+
{/* Fixed Navbar */}
|
|
20
|
+
<div style={{ position: 'fixed', top: 'var(--preview-toolbar-h, 0px)', left: 0, right: 0, zIndex: 40 }}>
|
|
21
|
+
<LearnerNavbar
|
|
22
|
+
onMenuClick={() => {
|
|
23
|
+
if (typeof window !== 'undefined' && window.innerWidth >= 1024) {
|
|
24
|
+
setSidebarCollapsed(c => !c);
|
|
25
|
+
} else {
|
|
26
|
+
setSidebarOpen(o => !o);
|
|
27
|
+
}
|
|
28
|
+
}}
|
|
29
|
+
/>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
{/* Content below navbar (h-20 = 80px) + preview toolbar offset */}
|
|
33
|
+
<div style={{ paddingTop: 'calc(80px + var(--preview-toolbar-h, 0px))', display: 'flex', minHeight: '100vh' }}>
|
|
34
|
+
{/* Sidebar */}
|
|
35
|
+
<LearnerSidebar
|
|
36
|
+
currentPath={currentPath}
|
|
37
|
+
isCollapsed={sidebarCollapsed}
|
|
38
|
+
isOpen={sidebarOpen}
|
|
39
|
+
onClose={() => setSidebarOpen(false)}
|
|
40
|
+
onToggle={() => setSidebarCollapsed(c => !c)}
|
|
41
|
+
onNavigate={onNavigate ?? (() => {})}
|
|
42
|
+
/>
|
|
43
|
+
|
|
44
|
+
{/* Main content */}
|
|
45
|
+
<main
|
|
46
|
+
style={{
|
|
47
|
+
flex: 1,
|
|
48
|
+
minWidth: 0,
|
|
49
|
+
transition: 'margin-left 0.3s',
|
|
50
|
+
}}
|
|
51
|
+
className="px-4 sm:px-6 lg:px-8 py-6"
|
|
52
|
+
>
|
|
53
|
+
{children}
|
|
54
|
+
</main>
|
|
55
|
+
</div>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
@import "tailwindcss";
|
|
2
|
+
|
|
3
|
+
/* Tell Tailwind v4 where to scan for class names */
|
|
4
|
+
@source "../src/**/*.{tsx,ts,js,jsx}";
|
|
5
|
+
|
|
6
|
+
/* Mirror the host app's @theme mappings so custom Tailwind utilities work */
|
|
7
|
+
@theme {
|
|
8
|
+
/* Background colors */
|
|
9
|
+
--color-theme-bg-primary: rgb(var(--bg-primary));
|
|
10
|
+
--color-theme-bg-secondary: rgb(var(--bg-secondary));
|
|
11
|
+
--color-theme-bg-tertiary: rgb(var(--bg-tertiary));
|
|
12
|
+
|
|
13
|
+
/* Text colors */
|
|
14
|
+
--color-theme-text-primary: rgb(var(--text-primary));
|
|
15
|
+
--color-theme-text-secondary: rgb(var(--text-secondary));
|
|
16
|
+
--color-theme-text-muted: rgb(var(--text-muted));
|
|
17
|
+
|
|
18
|
+
/* Border colors */
|
|
19
|
+
--color-theme-border-primary: rgb(var(--border-primary));
|
|
20
|
+
|
|
21
|
+
/* Accent colors */
|
|
22
|
+
--color-theme-accent-primary: rgb(var(--accent-primary));
|
|
23
|
+
--color-theme-accent-secondary: rgb(var(--accent-secondary));
|
|
24
|
+
|
|
25
|
+
/* Marketing success stats gradient */
|
|
26
|
+
--color-theme-success-gradient-from: rgb(var(--success-gradient-from));
|
|
27
|
+
--color-theme-success-gradient-to: rgb(var(--success-gradient-to));
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/* Default (light) theme — fallback when host doesn't inject theme variables */
|
|
31
|
+
:root {
|
|
32
|
+
--bg-primary: 249 250 251;
|
|
33
|
+
--bg-secondary: 255 255 255;
|
|
34
|
+
--bg-tertiary: 243 244 246;
|
|
35
|
+
--text-primary: 17 24 39;
|
|
36
|
+
--text-secondary: 75 85 99;
|
|
37
|
+
--text-muted: 156 163 175;
|
|
38
|
+
--border-primary: 229 231 235;
|
|
39
|
+
--accent-primary: 73 187 189;
|
|
40
|
+
--accent-secondary: 73 187 189;
|
|
41
|
+
--success-gradient-from: 19 108 181;
|
|
42
|
+
--success-gradient-to: 73 187 189;
|
|
43
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
export type ChatRole = 'user' | 'assistant';
|
|
2
|
+
|
|
3
|
+
export interface ChatMessage {
|
|
4
|
+
id: string;
|
|
5
|
+
role: ChatRole;
|
|
6
|
+
content: string;
|
|
7
|
+
createdAt: number;
|
|
8
|
+
isStreaming?: boolean;
|
|
9
|
+
sources?: unknown[];
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export interface AICoachInfo {
|
|
13
|
+
id: string;
|
|
14
|
+
name: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface AICoachAvailability {
|
|
18
|
+
active: boolean;
|
|
19
|
+
coach: AICoachInfo | null;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface AICoachHistoryGroup {
|
|
23
|
+
date: string;
|
|
24
|
+
messages: ChatMessage[];
|
|
25
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface Bookmark {
|
|
2
|
+
id: string;
|
|
3
|
+
activityId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
second: number;
|
|
6
|
+
label?: string;
|
|
7
|
+
createdAt: string;
|
|
8
|
+
updatedAt: string;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export interface CreateBookmarkPayload {
|
|
12
|
+
activityId: string;
|
|
13
|
+
second: number;
|
|
14
|
+
label?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface UpdateBookmarkPayload {
|
|
18
|
+
second?: number;
|
|
19
|
+
label?: string;
|
|
20
|
+
}
|