@academy-sdk/sdk 0.1.1 → 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/{dist/components/atoms/index.d.ts → src/components/atoms/index.ts} +0 -1
- package/{dist/components/index.d.ts → src/components/index.ts} +7 -1
- 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/{dist/components/molecules/index.d.ts → src/components/molecules/index.ts} +0 -1
- 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/{dist/components/organisms/index.d.ts → src/components/organisms/index.ts} +0 -1
- 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/{dist/components/pages/index.d.ts → src/components/pages/index.ts} +0 -1
- package/src/components/utils.ts +6 -0
- package/src/contracts/components.contract.ts +89 -0
- package/{dist/contracts/index.d.ts → src/contracts/index.ts} +0 -1
- 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/{dist/hooks/index.d.ts → src/hooks/index.ts} +15 -1
- package/src/hooks/sdk-context.tsx +152 -0
- package/src/hooks/useAiCoach.ts +27 -0
- package/src/hooks/useBookmarks.ts +35 -0
- package/{dist/hooks/useCourseSearch.d.ts → src/hooks/useCourseSearch.ts} +8 -5
- package/{dist/hooks/useDebounce.d.ts → src/hooks/useDebounce.ts} +8 -2
- package/{dist/hooks/useMyBundles.d.ts → src/hooks/useMyBundles.ts} +8 -6
- package/{dist/hooks/useMyCourses.d.ts → src/hooks/useMyCourses.ts} +8 -6
- package/src/hooks/useNotes.ts +35 -0
- package/src/hooks/useNotifications.ts +16 -0
- package/{dist/hooks/useTheme.d.ts → src/hooks/useTheme.ts} +8 -5
- package/src/hooks/useToast.ts +17 -0
- package/{dist/hooks/useUser.d.ts → src/hooks/useUser.ts} +13 -9
- 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/{dist/types/index.d.ts → src/types/index.ts} +0 -1
- 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/Avatar.d.ts +0 -9
- package/dist/components/atoms/Avatar.d.ts.map +0 -1
- package/dist/components/atoms/Badge.d.ts +0 -10
- package/dist/components/atoms/Badge.d.ts.map +0 -1
- package/dist/components/atoms/Button.d.ts +0 -11
- package/dist/components/atoms/Button.d.ts.map +0 -1
- package/dist/components/atoms/Card.d.ts +0 -11
- package/dist/components/atoms/Card.d.ts.map +0 -1
- package/dist/components/atoms/Input.d.ts +0 -7
- package/dist/components/atoms/Input.d.ts.map +0 -1
- package/dist/components/atoms/ProgressBar.d.ts +0 -11
- package/dist/components/atoms/ProgressBar.d.ts.map +0 -1
- package/dist/components/atoms/Tabs.d.ts +0 -16
- package/dist/components/atoms/Tabs.d.ts.map +0 -1
- package/dist/components/atoms/index.cjs +0 -318
- package/dist/components/atoms/index.d.ts.map +0 -1
- package/dist/components/atoms/index.js +0 -288
- package/dist/components/index.cjs +0 -1275
- package/dist/components/index.d.ts.map +0 -1
- package/dist/components/index.js +0 -1245
- package/dist/components/molecules/CourseCard.d.ts +0 -25
- package/dist/components/molecules/CourseCard.d.ts.map +0 -1
- package/dist/components/molecules/EmptyState.d.ts +0 -10
- package/dist/components/molecules/EmptyState.d.ts.map +0 -1
- package/dist/components/molecules/LoadingSpinner.d.ts +0 -7
- package/dist/components/molecules/LoadingSpinner.d.ts.map +0 -1
- package/dist/components/molecules/PageHeader.d.ts +0 -8
- package/dist/components/molecules/PageHeader.d.ts.map +0 -1
- package/dist/components/molecules/Pagination.d.ts +0 -13
- package/dist/components/molecules/Pagination.d.ts.map +0 -1
- package/dist/components/molecules/SearchInput.d.ts +0 -8
- package/dist/components/molecules/SearchInput.d.ts.map +0 -1
- package/dist/components/molecules/index.cjs +0 -334
- package/dist/components/molecules/index.d.ts.map +0 -1
- package/dist/components/molecules/index.js +0 -311
- package/dist/components/organisms/CourseSidebar.d.ts +0 -37
- package/dist/components/organisms/CourseSidebar.d.ts.map +0 -1
- package/dist/components/organisms/LearnerNavbar.d.ts +0 -8
- package/dist/components/organisms/LearnerNavbar.d.ts.map +0 -1
- package/dist/components/organisms/LearnerSidebar.d.ts +0 -16
- package/dist/components/organisms/LearnerSidebar.d.ts.map +0 -1
- package/dist/components/organisms/LessonBookmarks.d.ts +0 -8
- package/dist/components/organisms/LessonBookmarks.d.ts.map +0 -1
- package/dist/components/organisms/LessonNotes.d.ts +0 -8
- package/dist/components/organisms/LessonNotes.d.ts.map +0 -1
- package/dist/components/organisms/index.cjs +0 -855
- package/dist/components/organisms/index.d.ts.map +0 -1
- package/dist/components/organisms/index.js +0 -825
- package/dist/components/pages/BundleDetailPage.d.ts +0 -3
- package/dist/components/pages/BundleDetailPage.d.ts.map +0 -1
- package/dist/components/pages/CatalogBundlesPage.d.ts +0 -3
- package/dist/components/pages/CatalogBundlesPage.d.ts.map +0 -1
- package/dist/components/pages/CatalogCoursesPage.d.ts +0 -3
- package/dist/components/pages/CatalogCoursesPage.d.ts.map +0 -1
- package/dist/components/pages/CourseDetailPage.d.ts +0 -3
- package/dist/components/pages/CourseDetailPage.d.ts.map +0 -1
- package/dist/components/pages/CoursePlayerPage.d.ts +0 -8
- package/dist/components/pages/CoursePlayerPage.d.ts.map +0 -1
- package/dist/components/pages/CreatorProfilePage.d.ts +0 -3
- package/dist/components/pages/CreatorProfilePage.d.ts.map +0 -1
- package/dist/components/pages/LearnerSettingsPage.d.ts +0 -3
- package/dist/components/pages/LearnerSettingsPage.d.ts.map +0 -1
- package/dist/components/pages/ManualReviewDetailPage.d.ts +0 -3
- package/dist/components/pages/ManualReviewDetailPage.d.ts.map +0 -1
- package/dist/components/pages/ManualReviewPage.d.ts +0 -3
- package/dist/components/pages/ManualReviewPage.d.ts.map +0 -1
- package/dist/components/pages/MessagesPage.d.ts +0 -3
- package/dist/components/pages/MessagesPage.d.ts.map +0 -1
- package/dist/components/pages/MyLearningPage.d.ts +0 -3
- package/dist/components/pages/MyLearningPage.d.ts.map +0 -1
- package/dist/components/pages/PaymentCancelPage.d.ts +0 -3
- package/dist/components/pages/PaymentCancelPage.d.ts.map +0 -1
- package/dist/components/pages/PaymentSuccessPage.d.ts +0 -3
- package/dist/components/pages/PaymentSuccessPage.d.ts.map +0 -1
- package/dist/components/pages/index.cjs +0 -3306
- package/dist/components/pages/index.d.ts.map +0 -1
- package/dist/components/pages/index.js +0 -3315
- package/dist/components/utils.d.ts +0 -3
- package/dist/components/utils.d.ts.map +0 -1
- package/dist/contracts/components.contract.d.ts +0 -87
- package/dist/contracts/components.contract.d.ts.map +0 -1
- package/dist/contracts/index.cjs +0 -52
- package/dist/contracts/index.d.ts.map +0 -1
- package/dist/contracts/index.js +0 -29
- package/dist/contracts/layout.contract.d.ts +0 -35
- package/dist/contracts/layout.contract.d.ts.map +0 -1
- package/dist/contracts/pages.contract.d.ts +0 -192
- package/dist/contracts/pages.contract.d.ts.map +0 -1
- package/dist/contracts/template.contract.d.ts +0 -49
- package/dist/contracts/template.contract.d.ts.map +0 -1
- package/dist/hooks/index.cjs +0 -165
- package/dist/hooks/index.d.ts.map +0 -1
- package/dist/hooks/index.js +0 -142
- package/dist/hooks/sdk-context.d.ts +0 -125
- package/dist/hooks/sdk-context.d.ts.map +0 -1
- package/dist/hooks/useAiCoach.d.ts +0 -32
- package/dist/hooks/useAiCoach.d.ts.map +0 -1
- package/dist/hooks/useBookmarks.d.ts +0 -31
- package/dist/hooks/useBookmarks.d.ts.map +0 -1
- package/dist/hooks/useCourseSearch.d.ts.map +0 -1
- package/dist/hooks/useDebounce.d.ts.map +0 -1
- package/dist/hooks/useMyBundles.d.ts.map +0 -1
- package/dist/hooks/useMyCourses.d.ts.map +0 -1
- package/dist/hooks/useNotes.d.ts +0 -31
- package/dist/hooks/useNotes.d.ts.map +0 -1
- package/dist/hooks/useNotifications.d.ts +0 -19
- package/dist/hooks/useNotifications.d.ts.map +0 -1
- package/dist/hooks/useTheme.d.ts.map +0 -1
- package/dist/hooks/useToast.d.ts +0 -17
- package/dist/hooks/useToast.d.ts.map +0 -1
- package/dist/hooks/useUser.d.ts.map +0 -1
- package/dist/index.cjs +0 -630
- package/dist/index.d.ts +0 -17
- package/dist/index.d.ts.map +0 -1
- package/dist/index.js +0 -600
- package/dist/layouts/DefaultLayout.d.ts +0 -9
- package/dist/layouts/DefaultLayout.d.ts.map +0 -1
- package/dist/types/ai-coach.d.ts +0 -22
- package/dist/types/ai-coach.d.ts.map +0 -1
- package/dist/types/bookmarks.d.ts +0 -19
- package/dist/types/bookmarks.d.ts.map +0 -1
- package/dist/types/bundle.d.ts +0 -114
- package/dist/types/bundle.d.ts.map +0 -1
- package/dist/types/common.d.ts +0 -23
- package/dist/types/common.d.ts.map +0 -1
- package/dist/types/course.d.ts +0 -127
- package/dist/types/course.d.ts.map +0 -1
- package/dist/types/enrollment.d.ts +0 -34
- package/dist/types/enrollment.d.ts.map +0 -1
- package/dist/types/index.cjs +0 -18
- package/dist/types/index.d.ts.map +0 -1
- package/dist/types/index.js +0 -0
- package/dist/types/lesson.d.ts +0 -105
- package/dist/types/lesson.d.ts.map +0 -1
- package/dist/types/manual-review.d.ts +0 -123
- package/dist/types/manual-review.d.ts.map +0 -1
- package/dist/types/messaging.d.ts +0 -101
- package/dist/types/messaging.d.ts.map +0 -1
- package/dist/types/notification.d.ts +0 -28
- package/dist/types/notification.d.ts.map +0 -1
- package/dist/types/payment.d.ts +0 -38
- package/dist/types/payment.d.ts.map +0 -1
- package/dist/types/progress.d.ts +0 -18
- package/dist/types/progress.d.ts.map +0 -1
- package/dist/types/rating.d.ts +0 -20
- package/dist/types/rating.d.ts.map +0 -1
- package/dist/types/search.d.ts +0 -28
- package/dist/types/search.d.ts.map +0 -1
- package/dist/types/user.d.ts +0 -15
- package/dist/types/user.d.ts.map +0 -1
- package/dist/utils/formatters.d.ts +0 -25
- package/dist/utils/formatters.d.ts.map +0 -1
- package/dist/utils/index.cjs +0 -80
- package/dist/utils/index.d.ts +0 -2
- package/dist/utils/index.d.ts.map +0 -1
- package/dist/utils/index.js +0 -57
package/package.json
CHANGED
|
@@ -1,67 +1,58 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@academy-sdk/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "SDK for building custom learner templates for the Academy platform",
|
|
5
|
-
"main": "./dist/index.cjs",
|
|
6
|
-
"module": "./dist/index.js",
|
|
7
|
-
"types": "./dist/index.d.ts",
|
|
8
5
|
"files": [
|
|
9
|
-
"dist"
|
|
6
|
+
"dist",
|
|
7
|
+
"src"
|
|
10
8
|
],
|
|
11
9
|
"publishConfig": {
|
|
12
10
|
"access": "public"
|
|
13
11
|
},
|
|
14
12
|
"exports": {
|
|
15
13
|
".": {
|
|
16
|
-
"types": "./
|
|
17
|
-
"
|
|
18
|
-
"require": "./dist/index.cjs"
|
|
14
|
+
"types": "./src/index.ts",
|
|
15
|
+
"default": "./src/index.ts"
|
|
19
16
|
},
|
|
20
17
|
"./types": {
|
|
21
|
-
"types": "./
|
|
22
|
-
"
|
|
23
|
-
"require": "./dist/types/index.cjs"
|
|
18
|
+
"types": "./src/types/index.ts",
|
|
19
|
+
"default": "./src/types/index.ts"
|
|
24
20
|
},
|
|
25
21
|
"./hooks": {
|
|
26
|
-
"types": "./
|
|
27
|
-
"
|
|
28
|
-
"require": "./dist/hooks/index.cjs"
|
|
22
|
+
"types": "./src/hooks/index.ts",
|
|
23
|
+
"default": "./src/hooks/index.ts"
|
|
29
24
|
},
|
|
30
25
|
"./contracts": {
|
|
31
|
-
"types": "./
|
|
32
|
-
"
|
|
33
|
-
"require": "./dist/contracts/index.cjs"
|
|
26
|
+
"types": "./src/contracts/index.ts",
|
|
27
|
+
"default": "./src/contracts/index.ts"
|
|
34
28
|
},
|
|
35
29
|
"./utils": {
|
|
36
|
-
"types": "./
|
|
37
|
-
"
|
|
38
|
-
"require": "./dist/utils/index.cjs"
|
|
30
|
+
"types": "./src/utils/index.ts",
|
|
31
|
+
"default": "./src/utils/index.ts"
|
|
39
32
|
},
|
|
40
33
|
"./components": {
|
|
41
|
-
"types": "./
|
|
42
|
-
"
|
|
43
|
-
"require": "./dist/components/index.cjs"
|
|
34
|
+
"types": "./src/components/index.ts",
|
|
35
|
+
"default": "./src/components/index.ts"
|
|
44
36
|
},
|
|
45
37
|
"./components/atoms": {
|
|
46
|
-
"types": "./
|
|
47
|
-
"
|
|
48
|
-
"require": "./dist/components/atoms/index.cjs"
|
|
38
|
+
"types": "./src/components/atoms/index.ts",
|
|
39
|
+
"default": "./src/components/atoms/index.ts"
|
|
49
40
|
},
|
|
50
41
|
"./components/molecules": {
|
|
51
|
-
"types": "./
|
|
52
|
-
"
|
|
53
|
-
"require": "./dist/components/molecules/index.cjs"
|
|
42
|
+
"types": "./src/components/molecules/index.ts",
|
|
43
|
+
"default": "./src/components/molecules/index.ts"
|
|
54
44
|
},
|
|
55
45
|
"./components/organisms": {
|
|
56
|
-
"types": "./
|
|
57
|
-
"
|
|
58
|
-
"require": "./dist/components/organisms/index.cjs"
|
|
46
|
+
"types": "./src/components/organisms/index.ts",
|
|
47
|
+
"default": "./src/components/organisms/index.ts"
|
|
59
48
|
},
|
|
60
49
|
"./components/pages": {
|
|
61
|
-
"types": "./
|
|
62
|
-
"
|
|
63
|
-
|
|
64
|
-
|
|
50
|
+
"types": "./src/components/pages/index.ts",
|
|
51
|
+
"default": "./src/components/pages/index.ts"
|
|
52
|
+
},
|
|
53
|
+
"./styles.css": "./dist/styles.css",
|
|
54
|
+
"./template": "./dist/bundle.js",
|
|
55
|
+
"./manifest": "./dist/manifest.json"
|
|
65
56
|
},
|
|
66
57
|
"scripts": {
|
|
67
58
|
"build": "node build.mjs",
|
|
@@ -69,23 +60,26 @@
|
|
|
69
60
|
"preview": "node preview.mjs"
|
|
70
61
|
},
|
|
71
62
|
"peerDependencies": {
|
|
63
|
+
"class-variance-authority": ">=0.7.0",
|
|
64
|
+
"clsx": ">=2.0.0",
|
|
65
|
+
"lucide-react": ">=0.300.0",
|
|
72
66
|
"react": ">=18.0.0",
|
|
73
67
|
"react-dom": ">=18.0.0",
|
|
74
|
-
"
|
|
75
|
-
"tailwind-merge": ">=2.0.0",
|
|
76
|
-
"class-variance-authority": ">=0.7.0",
|
|
77
|
-
"lucide-react": ">=0.300.0"
|
|
68
|
+
"tailwind-merge": ">=2.0.0"
|
|
78
69
|
},
|
|
79
70
|
"devDependencies": {
|
|
80
|
-
"
|
|
81
|
-
"typescript": "5.9.3",
|
|
82
|
-
"react": "19.2.0",
|
|
83
|
-
"react-dom": "19.2.0",
|
|
71
|
+
"@tailwindcss/postcss": "^4.2.1",
|
|
84
72
|
"@types/react": "19.2.7",
|
|
85
73
|
"@types/react-dom": "19.2.3",
|
|
74
|
+
"class-variance-authority": "0.7.1",
|
|
86
75
|
"clsx": "2.1.1",
|
|
76
|
+
"esbuild": "0.25.5",
|
|
77
|
+
"lucide-react": "0.555.0",
|
|
78
|
+
"postcss": "^8.5.8",
|
|
79
|
+
"react": "19.2.0",
|
|
80
|
+
"react-dom": "19.2.0",
|
|
87
81
|
"tailwind-merge": "3.4.0",
|
|
88
|
-
"
|
|
89
|
-
"
|
|
82
|
+
"tailwindcss": "^4.2.1",
|
|
83
|
+
"typescript": "5.9.3"
|
|
90
84
|
}
|
|
91
85
|
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils';
|
|
3
|
+
|
|
4
|
+
interface AvatarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
src?: string;
|
|
6
|
+
alt?: string;
|
|
7
|
+
fallback?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
const Avatar = React.forwardRef<HTMLDivElement, AvatarProps>(
|
|
11
|
+
({ className, src, alt, fallback, ...props }, ref) => {
|
|
12
|
+
return (
|
|
13
|
+
<div
|
|
14
|
+
ref={ref}
|
|
15
|
+
className={cn(
|
|
16
|
+
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
|
|
17
|
+
className
|
|
18
|
+
)}
|
|
19
|
+
{...props}
|
|
20
|
+
>
|
|
21
|
+
{src ? (
|
|
22
|
+
<img
|
|
23
|
+
src={src}
|
|
24
|
+
alt={alt || 'Avatar'}
|
|
25
|
+
className="aspect-square h-full w-full object-cover"
|
|
26
|
+
/>
|
|
27
|
+
) : (
|
|
28
|
+
<div className="flex h-full w-full items-center justify-center bg-gray-200 text-gray-600 text-sm font-medium">
|
|
29
|
+
{fallback || '?'}
|
|
30
|
+
</div>
|
|
31
|
+
)}
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
Avatar.displayName = 'Avatar';
|
|
37
|
+
|
|
38
|
+
export { Avatar };
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils';
|
|
4
|
+
|
|
5
|
+
const badgeVariants = cva(
|
|
6
|
+
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: 'bg-theme-accent-primary text-white',
|
|
11
|
+
secondary: 'bg-theme-bg-tertiary text-theme-text-primary',
|
|
12
|
+
success: 'border-transparent bg-green-600 text-white',
|
|
13
|
+
warning: 'border-transparent bg-yellow-500 text-white',
|
|
14
|
+
destructive: 'border-transparent bg-red-600 text-white',
|
|
15
|
+
outline: 'text-theme-text-primary border-theme-border-primary',
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
defaultVariants: {
|
|
19
|
+
variant: 'default',
|
|
20
|
+
},
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
|
|
24
|
+
export interface BadgeProps
|
|
25
|
+
extends React.HTMLAttributes<HTMLDivElement>,
|
|
26
|
+
VariantProps<typeof badgeVariants> {}
|
|
27
|
+
|
|
28
|
+
export function Badge({ className, variant, ...props }: BadgeProps) {
|
|
29
|
+
return <div className={cn(badgeVariants({ variant }), className)} {...props} />;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export { badgeVariants };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cva, type VariantProps } from 'class-variance-authority';
|
|
3
|
+
import { cn } from '../utils';
|
|
4
|
+
|
|
5
|
+
const buttonVariants = cva(
|
|
6
|
+
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 cursor-pointer',
|
|
7
|
+
{
|
|
8
|
+
variants: {
|
|
9
|
+
variant: {
|
|
10
|
+
default: 'bg-theme-accent-primary text-white hover:opacity-90',
|
|
11
|
+
destructive: 'bg-red-600 text-white hover:bg-red-700',
|
|
12
|
+
outline: 'border border-theme-border-primary bg-theme-bg-secondary hover:bg-theme-bg-tertiary text-theme-text-primary',
|
|
13
|
+
secondary: 'bg-theme-bg-tertiary text-theme-text-primary hover:opacity-80',
|
|
14
|
+
ghost: 'hover:bg-theme-bg-tertiary text-theme-text-primary',
|
|
15
|
+
link: 'text-[rgb(var(--accent-primary))] underline-offset-4 hover:underline',
|
|
16
|
+
},
|
|
17
|
+
size: {
|
|
18
|
+
default: 'h-10 px-4 py-2',
|
|
19
|
+
sm: 'h-9 rounded-md px-3',
|
|
20
|
+
lg: 'h-11 rounded-md px-8',
|
|
21
|
+
icon: 'h-10 w-10',
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
defaultVariants: {
|
|
25
|
+
variant: 'default',
|
|
26
|
+
size: 'default',
|
|
27
|
+
},
|
|
28
|
+
}
|
|
29
|
+
);
|
|
30
|
+
|
|
31
|
+
export interface ButtonProps
|
|
32
|
+
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
|
33
|
+
VariantProps<typeof buttonVariants> {}
|
|
34
|
+
|
|
35
|
+
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
|
36
|
+
({ className, variant, size, ...props }, ref) => {
|
|
37
|
+
return (
|
|
38
|
+
<button
|
|
39
|
+
className={cn(buttonVariants({ variant, size, className }))}
|
|
40
|
+
ref={ref}
|
|
41
|
+
{...props}
|
|
42
|
+
/>
|
|
43
|
+
);
|
|
44
|
+
}
|
|
45
|
+
);
|
|
46
|
+
Button.displayName = 'Button';
|
|
47
|
+
|
|
48
|
+
export { Button, buttonVariants };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils';
|
|
3
|
+
|
|
4
|
+
interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
|
|
5
|
+
|
|
6
|
+
export function Card({ className, ...props }: CardProps) {
|
|
7
|
+
return (
|
|
8
|
+
<div
|
|
9
|
+
className={cn('rounded-lg border border-theme-border-primary bg-theme-bg-primary shadow-sm', className)}
|
|
10
|
+
{...props}
|
|
11
|
+
/>
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function CardHeader({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
16
|
+
return <div className={cn('flex flex-col space-y-1.5 p-6', className)} {...props} />;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export function CardTitle({ className, ...props }: React.HTMLAttributes<HTMLHeadingElement>) {
|
|
20
|
+
return <h3 className={cn('text-2xl font-semibold leading-none tracking-tight', className)} {...props} />;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function CardDescription({ className, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {
|
|
24
|
+
return <p className={cn('text-sm text-theme-text-secondary', className)} {...props} />;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function CardContent({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
28
|
+
return <div className={cn('p-6 pt-0', className)} {...props} />;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function CardFooter({ className, ...props }: React.HTMLAttributes<HTMLDivElement>) {
|
|
32
|
+
return <div className={cn('flex items-center p-6 pt-0', className)} {...props} />;
|
|
33
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils';
|
|
3
|
+
|
|
4
|
+
export interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
9
|
+
({ className, type, label, ...props }, ref) => {
|
|
10
|
+
const inputElement = (
|
|
11
|
+
<input
|
|
12
|
+
type={type}
|
|
13
|
+
className={cn(
|
|
14
|
+
'flex h-10 w-full rounded-md border border-theme-border-primary bg-theme-bg-primary px-3 py-2 text-sm text-theme-text-primary file:border-0 file:bg-transparent file:text-sm file:font-medium placeholder:text-[rgb(var(--text-muted))] focus-visible:outline-none focus-visible:border-theme-accent-primary transition-colors disabled:cursor-not-allowed disabled:opacity-50',
|
|
15
|
+
className
|
|
16
|
+
)}
|
|
17
|
+
ref={ref}
|
|
18
|
+
{...props}
|
|
19
|
+
/>
|
|
20
|
+
);
|
|
21
|
+
|
|
22
|
+
if (label) {
|
|
23
|
+
return (
|
|
24
|
+
<div className="space-y-2">
|
|
25
|
+
<label className="text-sm font-medium text-theme-text-primary">
|
|
26
|
+
{label}
|
|
27
|
+
{props.required && <span className="text-red-500 ml-1">*</span>}
|
|
28
|
+
</label>
|
|
29
|
+
{inputElement}
|
|
30
|
+
</div>
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return inputElement;
|
|
35
|
+
}
|
|
36
|
+
);
|
|
37
|
+
Input.displayName = 'Input';
|
|
38
|
+
|
|
39
|
+
export { Input };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import * as React from 'react';
|
|
2
|
+
import { cn } from '../utils';
|
|
3
|
+
|
|
4
|
+
interface ProgressBarProps extends React.HTMLAttributes<HTMLDivElement> {
|
|
5
|
+
value: number;
|
|
6
|
+
max?: number;
|
|
7
|
+
showLabel?: boolean;
|
|
8
|
+
size?: 'sm' | 'md' | 'lg';
|
|
9
|
+
variant?: 'default' | 'success' | 'warning' | 'error';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
const sizeClasses = {
|
|
13
|
+
sm: 'h-1.5',
|
|
14
|
+
md: 'h-2.5',
|
|
15
|
+
lg: 'h-3.5',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const variantClasses = {
|
|
19
|
+
default: 'bg-theme-accent-primary',
|
|
20
|
+
success: 'bg-green-600',
|
|
21
|
+
warning: 'bg-yellow-500',
|
|
22
|
+
error: 'bg-red-600',
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function ProgressBar({
|
|
26
|
+
value,
|
|
27
|
+
max = 100,
|
|
28
|
+
showLabel = false,
|
|
29
|
+
size = 'md',
|
|
30
|
+
variant = 'default',
|
|
31
|
+
className,
|
|
32
|
+
...props
|
|
33
|
+
}: ProgressBarProps) {
|
|
34
|
+
const percentage = Math.min(Math.max((value / max) * 100, 0), 100);
|
|
35
|
+
|
|
36
|
+
return (
|
|
37
|
+
<div className={cn('w-full', className)} {...props}>
|
|
38
|
+
{showLabel && (
|
|
39
|
+
<div className="mb-1 flex items-center justify-between text-sm">
|
|
40
|
+
<span className="text-theme-text-secondary">Progress</span>
|
|
41
|
+
<span className="font-medium text-theme-text-primary">{Math.round(percentage)}%</span>
|
|
42
|
+
</div>
|
|
43
|
+
)}
|
|
44
|
+
<div className={cn('w-full overflow-hidden rounded-full bg-theme-bg-tertiary', sizeClasses[size])}>
|
|
45
|
+
<div
|
|
46
|
+
className={cn('h-full transition-all duration-300 ease-in-out', variantClasses[variant])}
|
|
47
|
+
style={{ width: `${percentage}%` }}
|
|
48
|
+
/>
|
|
49
|
+
</div>
|
|
50
|
+
</div>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import * as React from 'react';
|
|
4
|
+
import { cn } from '../utils';
|
|
5
|
+
|
|
6
|
+
interface Tab {
|
|
7
|
+
id: string;
|
|
8
|
+
label: string;
|
|
9
|
+
icon?: React.ReactNode;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
interface TabsProps {
|
|
13
|
+
tabs: Tab[];
|
|
14
|
+
activeTab: string;
|
|
15
|
+
onTabChange: (tabId: string) => void;
|
|
16
|
+
className?: string;
|
|
17
|
+
actions?: React.ReactNode;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function Tabs({ tabs, activeTab, onTabChange, className, actions }: TabsProps) {
|
|
21
|
+
return (
|
|
22
|
+
<div className={cn('border-b border-theme-border-primary', className)}>
|
|
23
|
+
<div className="px-4 sm:px-6 pt-4">
|
|
24
|
+
<div className="p-0.5 flex items-center justify-between overflow-x-auto">
|
|
25
|
+
<nav className="flex gap-4 sm:gap-8 min-w-min">
|
|
26
|
+
{tabs.map((tab) => (
|
|
27
|
+
<button
|
|
28
|
+
key={tab.id}
|
|
29
|
+
onClick={() => onTabChange(tab.id)}
|
|
30
|
+
className={cn(
|
|
31
|
+
'pb-3 px-1 text-sm font-medium border-b-2 transition-all flex items-center gap-2 cursor-pointer whitespace-nowrap',
|
|
32
|
+
activeTab === tab.id
|
|
33
|
+
? 'border-theme-accent-primary text-theme-accent-primary'
|
|
34
|
+
: 'border-transparent text-theme-text-secondary hover:text-theme-text-primary hover:border-theme-accent-primary/40'
|
|
35
|
+
)}
|
|
36
|
+
>
|
|
37
|
+
{tab.icon && <span className="flex-shrink-0">{tab.icon}</span>}
|
|
38
|
+
{tab.label}
|
|
39
|
+
</button>
|
|
40
|
+
))}
|
|
41
|
+
</nav>
|
|
42
|
+
{actions && <div className="pb-3">{actions}</div>}
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
</div>
|
|
46
|
+
);
|
|
47
|
+
}
|
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { useState } from 'react';
|
|
2
|
+
import { BookOpen, Clock, Grid2X2 } from 'lucide-react';
|
|
3
|
+
import { cn } from '../utils';
|
|
4
|
+
import { ProgressBar } from '../atoms/ProgressBar';
|
|
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
|
+
className?: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function CourseCard({
|
|
31
|
+
id,
|
|
32
|
+
title,
|
|
33
|
+
thumbnail,
|
|
34
|
+
thumbnails,
|
|
35
|
+
totalLessons,
|
|
36
|
+
progress,
|
|
37
|
+
status,
|
|
38
|
+
isFree,
|
|
39
|
+
price = 0,
|
|
40
|
+
currency = 'USD',
|
|
41
|
+
description,
|
|
42
|
+
showPrice = true,
|
|
43
|
+
showProgress = true,
|
|
44
|
+
category,
|
|
45
|
+
duration,
|
|
46
|
+
instructorName,
|
|
47
|
+
instructorAvatar,
|
|
48
|
+
originalPrice,
|
|
49
|
+
isBundle = false,
|
|
50
|
+
onClick,
|
|
51
|
+
className,
|
|
52
|
+
}: CourseCardProps) {
|
|
53
|
+
const [imageError, setImageError] = useState(false);
|
|
54
|
+
const clampedProgress = Math.min(Math.max(progress, 0), 100);
|
|
55
|
+
|
|
56
|
+
const stripHtml = (html: string): string => {
|
|
57
|
+
if (typeof document === 'undefined') return html;
|
|
58
|
+
const tmp = document.createElement('div');
|
|
59
|
+
tmp.innerHTML = html;
|
|
60
|
+
return tmp.textContent || tmp.innerText || '';
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
const displayCategory = category || 'Design';
|
|
64
|
+
const displayDuration = duration || '3 Month';
|
|
65
|
+
const displayInstructorName = instructorName || (description ? stripHtml(description).slice(0, 15) : 'Instructor');
|
|
66
|
+
|
|
67
|
+
return (
|
|
68
|
+
<div
|
|
69
|
+
className={cn(
|
|
70
|
+
'group overflow-hidden rounded-2xl border border-gray-100 bg-white shadow-sm hover:shadow-lg transition-all duration-300 hover:-translate-y-1 h-full flex flex-col min-w-0 w-full cursor-pointer',
|
|
71
|
+
className
|
|
72
|
+
)}
|
|
73
|
+
onClick={onClick}
|
|
74
|
+
>
|
|
75
|
+
{/* Image */}
|
|
76
|
+
<div className="relative h-[238.66px] w-full overflow-hidden mx-auto px-3 pt-3">
|
|
77
|
+
<div className="relative w-full h-full rounded-xl overflow-hidden bg-gray-100">
|
|
78
|
+
{isBundle && thumbnails && thumbnails.length > 0 && !imageError ? (
|
|
79
|
+
<div className="relative w-full h-full flex">
|
|
80
|
+
<div className="relative h-full w-1/2">
|
|
81
|
+
<img
|
|
82
|
+
src={thumbnails[0]}
|
|
83
|
+
alt={`${title} - image 1`}
|
|
84
|
+
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
|
85
|
+
onError={() => setImageError(true)}
|
|
86
|
+
/>
|
|
87
|
+
</div>
|
|
88
|
+
<div className="flex flex-col w-1/2 h-full">
|
|
89
|
+
{thumbnails[1] && (
|
|
90
|
+
<div className={cn('relative w-full overflow-hidden', thumbnails.length > 2 ? 'h-1/2' : 'h-full')}>
|
|
91
|
+
<img
|
|
92
|
+
src={thumbnails[1]}
|
|
93
|
+
alt={`${title} - image 2`}
|
|
94
|
+
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
|
95
|
+
onError={() => setImageError(true)}
|
|
96
|
+
/>
|
|
97
|
+
</div>
|
|
98
|
+
)}
|
|
99
|
+
{thumbnails.length > 2 && thumbnails[2] && (
|
|
100
|
+
<div className="relative w-full h-1/2 overflow-hidden">
|
|
101
|
+
<img
|
|
102
|
+
src={thumbnails[2]}
|
|
103
|
+
alt={`${title} - image 3`}
|
|
104
|
+
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
|
105
|
+
onError={() => setImageError(true)}
|
|
106
|
+
/>
|
|
107
|
+
</div>
|
|
108
|
+
)}
|
|
109
|
+
</div>
|
|
110
|
+
</div>
|
|
111
|
+
) : thumbnail && !imageError ? (
|
|
112
|
+
<img
|
|
113
|
+
src={thumbnail}
|
|
114
|
+
alt={title}
|
|
115
|
+
className="w-full h-full object-cover transition-transform duration-300 group-hover:scale-110"
|
|
116
|
+
onError={() => setImageError(true)}
|
|
117
|
+
/>
|
|
118
|
+
) : (
|
|
119
|
+
<div className="w-full h-full flex items-center justify-center bg-gray-200">
|
|
120
|
+
<BookOpen className="w-12 h-12 text-gray-400" />
|
|
121
|
+
</div>
|
|
122
|
+
)}
|
|
123
|
+
</div>
|
|
124
|
+
|
|
125
|
+
{status && (
|
|
126
|
+
<div className="absolute right-6 top-5">
|
|
127
|
+
<span
|
|
128
|
+
className={`rounded-full px-3 py-1.5 text-xs font-medium text-white shadow-md ${status === 'completed' ? 'bg-green-500' : 'bg-[rgb(var(--accent-primary))]'}`}
|
|
129
|
+
>
|
|
130
|
+
{status}
|
|
131
|
+
</span>
|
|
132
|
+
</div>
|
|
133
|
+
)}
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
{/* Content */}
|
|
137
|
+
<div className="p-5 space-y-3 flex-1 flex flex-col min-w-0">
|
|
138
|
+
{(displayCategory || displayDuration) && (
|
|
139
|
+
<div className="flex items-center justify-between text-xs text-gray-500">
|
|
140
|
+
{displayCategory && (
|
|
141
|
+
<div className="flex items-center gap-1.5">
|
|
142
|
+
<Grid2X2 className="w-3.5 h-3.5 text-gray-400" />
|
|
143
|
+
<span>{displayCategory}</span>
|
|
144
|
+
</div>
|
|
145
|
+
)}
|
|
146
|
+
{displayDuration && (
|
|
147
|
+
<div className="flex items-center gap-1.5">
|
|
148
|
+
<Clock className="w-3.5 h-3.5 text-gray-400" />
|
|
149
|
+
<span>{displayDuration}</span>
|
|
150
|
+
</div>
|
|
151
|
+
)}
|
|
152
|
+
</div>
|
|
153
|
+
)}
|
|
154
|
+
|
|
155
|
+
<h3 className="text-base font-bold text-gray-900 line-clamp-2 leading-snug">
|
|
156
|
+
{title}
|
|
157
|
+
</h3>
|
|
158
|
+
|
|
159
|
+
{description && (
|
|
160
|
+
<p className="text-sm text-gray-500 line-clamp-3 leading-relaxed">
|
|
161
|
+
{stripHtml(description)}
|
|
162
|
+
</p>
|
|
163
|
+
)}
|
|
164
|
+
|
|
165
|
+
<div className="flex-1" />
|
|
166
|
+
|
|
167
|
+
{showProgress && (
|
|
168
|
+
<div className="space-y-2">
|
|
169
|
+
<div className="h-2 overflow-hidden rounded-full bg-gray-100">
|
|
170
|
+
<div
|
|
171
|
+
className="h-full rounded-full bg-[rgb(var(--accent-primary))] transition-all duration-300"
|
|
172
|
+
style={{ width: `${clampedProgress}%` }}
|
|
173
|
+
/>
|
|
174
|
+
</div>
|
|
175
|
+
<div className="flex items-center justify-between text-xs">
|
|
176
|
+
<span className="text-gray-400"></span>
|
|
177
|
+
<span className="text-gray-500">
|
|
178
|
+
{isBundle ? 'Course' : 'Lesson'} {Math.ceil((clampedProgress / 100) * totalLessons)} of {totalLessons}
|
|
179
|
+
</span>
|
|
180
|
+
</div>
|
|
181
|
+
</div>
|
|
182
|
+
)}
|
|
183
|
+
|
|
184
|
+
{showPrice && (
|
|
185
|
+
<div className="flex items-center justify-between pt-3 border-t border-gray-100">
|
|
186
|
+
<div className="flex items-center gap-2">
|
|
187
|
+
{instructorAvatar ? (
|
|
188
|
+
<div className="w-7 h-7 rounded-full overflow-hidden flex-shrink-0">
|
|
189
|
+
<img src={instructorAvatar} alt={displayInstructorName} className="w-full h-full object-cover" />
|
|
190
|
+
</div>
|
|
191
|
+
) : (
|
|
192
|
+
<div className="w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center flex-shrink-0">
|
|
193
|
+
<span className="text-[10px] font-semibold text-gray-500">
|
|
194
|
+
{displayInstructorName.charAt(0).toUpperCase()}
|
|
195
|
+
</span>
|
|
196
|
+
</div>
|
|
197
|
+
)}
|
|
198
|
+
<span className="text-sm text-gray-600 font-medium truncate max-w-[80px]">
|
|
199
|
+
{displayInstructorName}
|
|
200
|
+
</span>
|
|
201
|
+
</div>
|
|
202
|
+
<div className="flex items-center gap-2">
|
|
203
|
+
{!isFree && originalPrice && originalPrice > price && (
|
|
204
|
+
<span className="text-sm text-gray-400 line-through">${originalPrice}</span>
|
|
205
|
+
)}
|
|
206
|
+
<span className="text-lg font-bold text-[rgb(var(--accent-primary))]">
|
|
207
|
+
{isFree ? 'Free' : `$${price}`}
|
|
208
|
+
</span>
|
|
209
|
+
</div>
|
|
210
|
+
</div>
|
|
211
|
+
)}
|
|
212
|
+
</div>
|
|
213
|
+
</div>
|
|
214
|
+
);
|
|
215
|
+
}
|