@academy-sdk/sdk 0.1.1 → 0.2.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.
Files changed (239) hide show
  1. package/dist/academy-sdk-sdk-v1.0.0.zip +0 -0
  2. package/dist/bundle.js +70 -0
  3. package/dist/manifest.json +5 -0
  4. package/dist/styles.css +3307 -0
  5. package/package.json +41 -46
  6. package/src/components/atoms/Avatar.tsx +38 -0
  7. package/src/components/atoms/Badge.tsx +32 -0
  8. package/src/components/atoms/Button.tsx +48 -0
  9. package/src/components/atoms/Card.tsx +33 -0
  10. package/src/components/atoms/Input.tsx +39 -0
  11. package/src/components/atoms/ProgressBar.tsx +52 -0
  12. package/src/components/atoms/Tabs.tsx +47 -0
  13. package/{dist/components/atoms/index.d.ts → src/components/atoms/index.ts} +0 -1
  14. package/{dist/components/index.d.ts → src/components/index.ts} +7 -1
  15. package/src/components/molecules/CourseCard.tsx +215 -0
  16. package/src/components/molecules/EmptyState.tsx +23 -0
  17. package/src/components/molecules/LoadingSpinner.tsx +27 -0
  18. package/src/components/molecules/PageHeader.tsx +22 -0
  19. package/src/components/molecules/Pagination.tsx +82 -0
  20. package/src/components/molecules/SearchInput.tsx +35 -0
  21. package/{dist/components/molecules/index.d.ts → src/components/molecules/index.ts} +0 -1
  22. package/src/components/organisms/CourseSidebar.tsx +276 -0
  23. package/src/components/organisms/LearnerNavbar.tsx +129 -0
  24. package/src/components/organisms/LearnerSidebar.tsx +148 -0
  25. package/src/components/organisms/LessonBookmarks.tsx +128 -0
  26. package/src/components/organisms/LessonNotes.tsx +153 -0
  27. package/{dist/components/organisms/index.d.ts → src/components/organisms/index.ts} +0 -1
  28. package/src/components/pages/BundleDetailPage.tsx +388 -0
  29. package/src/components/pages/CatalogBundlesPage.tsx +96 -0
  30. package/src/components/pages/CatalogCoursesPage.tsx +299 -0
  31. package/src/components/pages/CourseDetailPage.tsx +582 -0
  32. package/src/components/pages/CoursePlayerPage.tsx +481 -0
  33. package/src/components/pages/CreatorProfilePage.tsx +161 -0
  34. package/src/components/pages/LearnerSettingsPage.tsx +58 -0
  35. package/src/components/pages/ManualReviewDetailPage.tsx +254 -0
  36. package/src/components/pages/ManualReviewPage.tsx +228 -0
  37. package/src/components/pages/MessagesPage.tsx +285 -0
  38. package/src/components/pages/MyLearningPage.tsx +239 -0
  39. package/src/components/pages/PaymentCancelPage.tsx +74 -0
  40. package/src/components/pages/PaymentSuccessPage.tsx +73 -0
  41. package/{dist/components/pages/index.d.ts → src/components/pages/index.ts} +0 -1
  42. package/src/components/utils.ts +6 -0
  43. package/src/contracts/components.contract.ts +89 -0
  44. package/{dist/contracts/index.d.ts → src/contracts/index.ts} +0 -1
  45. package/src/contracts/layout.contract.ts +36 -0
  46. package/src/contracts/pages.contract.ts +275 -0
  47. package/src/contracts/template.contract.ts +100 -0
  48. package/src/default-template.tsx +52 -0
  49. package/{dist/hooks/index.d.ts → src/hooks/index.ts} +15 -1
  50. package/src/hooks/sdk-context.tsx +152 -0
  51. package/src/hooks/useAiCoach.ts +27 -0
  52. package/src/hooks/useBookmarks.ts +35 -0
  53. package/{dist/hooks/useCourseSearch.d.ts → src/hooks/useCourseSearch.ts} +8 -5
  54. package/{dist/hooks/useDebounce.d.ts → src/hooks/useDebounce.ts} +8 -2
  55. package/{dist/hooks/useMyBundles.d.ts → src/hooks/useMyBundles.ts} +8 -6
  56. package/{dist/hooks/useMyCourses.d.ts → src/hooks/useMyCourses.ts} +8 -6
  57. package/src/hooks/useNotes.ts +35 -0
  58. package/src/hooks/useNotifications.ts +16 -0
  59. package/{dist/hooks/useTheme.d.ts → src/hooks/useTheme.ts} +8 -5
  60. package/src/hooks/useToast.ts +17 -0
  61. package/{dist/hooks/useUser.d.ts → src/hooks/useUser.ts} +13 -9
  62. package/src/index.ts +33 -0
  63. package/src/layouts/DefaultLayout.tsx +58 -0
  64. package/src/manifest.json +5 -0
  65. package/src/styles.css +43 -0
  66. package/src/types/ai-coach.ts +25 -0
  67. package/src/types/bookmarks.ts +20 -0
  68. package/src/types/bundle.ts +119 -0
  69. package/src/types/common.ts +24 -0
  70. package/src/types/course.ts +135 -0
  71. package/src/types/enrollment.ts +35 -0
  72. package/{dist/types/index.d.ts → src/types/index.ts} +0 -1
  73. package/src/types/lesson.ts +106 -0
  74. package/src/types/manual-review.ts +116 -0
  75. package/src/types/messaging.ts +109 -0
  76. package/src/types/notification.ts +30 -0
  77. package/src/types/payment.ts +40 -0
  78. package/src/types/progress.ts +19 -0
  79. package/src/types/rating.ts +20 -0
  80. package/src/types/search.ts +31 -0
  81. package/src/types/user.ts +16 -0
  82. package/src/utils/formatters.ts +74 -0
  83. package/src/utils/index.ts +8 -0
  84. package/dist/components/atoms/Avatar.d.ts +0 -9
  85. package/dist/components/atoms/Avatar.d.ts.map +0 -1
  86. package/dist/components/atoms/Badge.d.ts +0 -10
  87. package/dist/components/atoms/Badge.d.ts.map +0 -1
  88. package/dist/components/atoms/Button.d.ts +0 -11
  89. package/dist/components/atoms/Button.d.ts.map +0 -1
  90. package/dist/components/atoms/Card.d.ts +0 -11
  91. package/dist/components/atoms/Card.d.ts.map +0 -1
  92. package/dist/components/atoms/Input.d.ts +0 -7
  93. package/dist/components/atoms/Input.d.ts.map +0 -1
  94. package/dist/components/atoms/ProgressBar.d.ts +0 -11
  95. package/dist/components/atoms/ProgressBar.d.ts.map +0 -1
  96. package/dist/components/atoms/Tabs.d.ts +0 -16
  97. package/dist/components/atoms/Tabs.d.ts.map +0 -1
  98. package/dist/components/atoms/index.cjs +0 -318
  99. package/dist/components/atoms/index.d.ts.map +0 -1
  100. package/dist/components/atoms/index.js +0 -288
  101. package/dist/components/index.cjs +0 -1275
  102. package/dist/components/index.d.ts.map +0 -1
  103. package/dist/components/index.js +0 -1245
  104. package/dist/components/molecules/CourseCard.d.ts +0 -25
  105. package/dist/components/molecules/CourseCard.d.ts.map +0 -1
  106. package/dist/components/molecules/EmptyState.d.ts +0 -10
  107. package/dist/components/molecules/EmptyState.d.ts.map +0 -1
  108. package/dist/components/molecules/LoadingSpinner.d.ts +0 -7
  109. package/dist/components/molecules/LoadingSpinner.d.ts.map +0 -1
  110. package/dist/components/molecules/PageHeader.d.ts +0 -8
  111. package/dist/components/molecules/PageHeader.d.ts.map +0 -1
  112. package/dist/components/molecules/Pagination.d.ts +0 -13
  113. package/dist/components/molecules/Pagination.d.ts.map +0 -1
  114. package/dist/components/molecules/SearchInput.d.ts +0 -8
  115. package/dist/components/molecules/SearchInput.d.ts.map +0 -1
  116. package/dist/components/molecules/index.cjs +0 -334
  117. package/dist/components/molecules/index.d.ts.map +0 -1
  118. package/dist/components/molecules/index.js +0 -311
  119. package/dist/components/organisms/CourseSidebar.d.ts +0 -37
  120. package/dist/components/organisms/CourseSidebar.d.ts.map +0 -1
  121. package/dist/components/organisms/LearnerNavbar.d.ts +0 -8
  122. package/dist/components/organisms/LearnerNavbar.d.ts.map +0 -1
  123. package/dist/components/organisms/LearnerSidebar.d.ts +0 -16
  124. package/dist/components/organisms/LearnerSidebar.d.ts.map +0 -1
  125. package/dist/components/organisms/LessonBookmarks.d.ts +0 -8
  126. package/dist/components/organisms/LessonBookmarks.d.ts.map +0 -1
  127. package/dist/components/organisms/LessonNotes.d.ts +0 -8
  128. package/dist/components/organisms/LessonNotes.d.ts.map +0 -1
  129. package/dist/components/organisms/index.cjs +0 -855
  130. package/dist/components/organisms/index.d.ts.map +0 -1
  131. package/dist/components/organisms/index.js +0 -825
  132. package/dist/components/pages/BundleDetailPage.d.ts +0 -3
  133. package/dist/components/pages/BundleDetailPage.d.ts.map +0 -1
  134. package/dist/components/pages/CatalogBundlesPage.d.ts +0 -3
  135. package/dist/components/pages/CatalogBundlesPage.d.ts.map +0 -1
  136. package/dist/components/pages/CatalogCoursesPage.d.ts +0 -3
  137. package/dist/components/pages/CatalogCoursesPage.d.ts.map +0 -1
  138. package/dist/components/pages/CourseDetailPage.d.ts +0 -3
  139. package/dist/components/pages/CourseDetailPage.d.ts.map +0 -1
  140. package/dist/components/pages/CoursePlayerPage.d.ts +0 -8
  141. package/dist/components/pages/CoursePlayerPage.d.ts.map +0 -1
  142. package/dist/components/pages/CreatorProfilePage.d.ts +0 -3
  143. package/dist/components/pages/CreatorProfilePage.d.ts.map +0 -1
  144. package/dist/components/pages/LearnerSettingsPage.d.ts +0 -3
  145. package/dist/components/pages/LearnerSettingsPage.d.ts.map +0 -1
  146. package/dist/components/pages/ManualReviewDetailPage.d.ts +0 -3
  147. package/dist/components/pages/ManualReviewDetailPage.d.ts.map +0 -1
  148. package/dist/components/pages/ManualReviewPage.d.ts +0 -3
  149. package/dist/components/pages/ManualReviewPage.d.ts.map +0 -1
  150. package/dist/components/pages/MessagesPage.d.ts +0 -3
  151. package/dist/components/pages/MessagesPage.d.ts.map +0 -1
  152. package/dist/components/pages/MyLearningPage.d.ts +0 -3
  153. package/dist/components/pages/MyLearningPage.d.ts.map +0 -1
  154. package/dist/components/pages/PaymentCancelPage.d.ts +0 -3
  155. package/dist/components/pages/PaymentCancelPage.d.ts.map +0 -1
  156. package/dist/components/pages/PaymentSuccessPage.d.ts +0 -3
  157. package/dist/components/pages/PaymentSuccessPage.d.ts.map +0 -1
  158. package/dist/components/pages/index.cjs +0 -3306
  159. package/dist/components/pages/index.d.ts.map +0 -1
  160. package/dist/components/pages/index.js +0 -3315
  161. package/dist/components/utils.d.ts +0 -3
  162. package/dist/components/utils.d.ts.map +0 -1
  163. package/dist/contracts/components.contract.d.ts +0 -87
  164. package/dist/contracts/components.contract.d.ts.map +0 -1
  165. package/dist/contracts/index.cjs +0 -52
  166. package/dist/contracts/index.d.ts.map +0 -1
  167. package/dist/contracts/index.js +0 -29
  168. package/dist/contracts/layout.contract.d.ts +0 -35
  169. package/dist/contracts/layout.contract.d.ts.map +0 -1
  170. package/dist/contracts/pages.contract.d.ts +0 -192
  171. package/dist/contracts/pages.contract.d.ts.map +0 -1
  172. package/dist/contracts/template.contract.d.ts +0 -49
  173. package/dist/contracts/template.contract.d.ts.map +0 -1
  174. package/dist/hooks/index.cjs +0 -165
  175. package/dist/hooks/index.d.ts.map +0 -1
  176. package/dist/hooks/index.js +0 -142
  177. package/dist/hooks/sdk-context.d.ts +0 -125
  178. package/dist/hooks/sdk-context.d.ts.map +0 -1
  179. package/dist/hooks/useAiCoach.d.ts +0 -32
  180. package/dist/hooks/useAiCoach.d.ts.map +0 -1
  181. package/dist/hooks/useBookmarks.d.ts +0 -31
  182. package/dist/hooks/useBookmarks.d.ts.map +0 -1
  183. package/dist/hooks/useCourseSearch.d.ts.map +0 -1
  184. package/dist/hooks/useDebounce.d.ts.map +0 -1
  185. package/dist/hooks/useMyBundles.d.ts.map +0 -1
  186. package/dist/hooks/useMyCourses.d.ts.map +0 -1
  187. package/dist/hooks/useNotes.d.ts +0 -31
  188. package/dist/hooks/useNotes.d.ts.map +0 -1
  189. package/dist/hooks/useNotifications.d.ts +0 -19
  190. package/dist/hooks/useNotifications.d.ts.map +0 -1
  191. package/dist/hooks/useTheme.d.ts.map +0 -1
  192. package/dist/hooks/useToast.d.ts +0 -17
  193. package/dist/hooks/useToast.d.ts.map +0 -1
  194. package/dist/hooks/useUser.d.ts.map +0 -1
  195. package/dist/index.cjs +0 -630
  196. package/dist/index.d.ts +0 -17
  197. package/dist/index.d.ts.map +0 -1
  198. package/dist/index.js +0 -600
  199. package/dist/layouts/DefaultLayout.d.ts +0 -9
  200. package/dist/layouts/DefaultLayout.d.ts.map +0 -1
  201. package/dist/types/ai-coach.d.ts +0 -22
  202. package/dist/types/ai-coach.d.ts.map +0 -1
  203. package/dist/types/bookmarks.d.ts +0 -19
  204. package/dist/types/bookmarks.d.ts.map +0 -1
  205. package/dist/types/bundle.d.ts +0 -114
  206. package/dist/types/bundle.d.ts.map +0 -1
  207. package/dist/types/common.d.ts +0 -23
  208. package/dist/types/common.d.ts.map +0 -1
  209. package/dist/types/course.d.ts +0 -127
  210. package/dist/types/course.d.ts.map +0 -1
  211. package/dist/types/enrollment.d.ts +0 -34
  212. package/dist/types/enrollment.d.ts.map +0 -1
  213. package/dist/types/index.cjs +0 -18
  214. package/dist/types/index.d.ts.map +0 -1
  215. package/dist/types/index.js +0 -0
  216. package/dist/types/lesson.d.ts +0 -105
  217. package/dist/types/lesson.d.ts.map +0 -1
  218. package/dist/types/manual-review.d.ts +0 -123
  219. package/dist/types/manual-review.d.ts.map +0 -1
  220. package/dist/types/messaging.d.ts +0 -101
  221. package/dist/types/messaging.d.ts.map +0 -1
  222. package/dist/types/notification.d.ts +0 -28
  223. package/dist/types/notification.d.ts.map +0 -1
  224. package/dist/types/payment.d.ts +0 -38
  225. package/dist/types/payment.d.ts.map +0 -1
  226. package/dist/types/progress.d.ts +0 -18
  227. package/dist/types/progress.d.ts.map +0 -1
  228. package/dist/types/rating.d.ts +0 -20
  229. package/dist/types/rating.d.ts.map +0 -1
  230. package/dist/types/search.d.ts +0 -28
  231. package/dist/types/search.d.ts.map +0 -1
  232. package/dist/types/user.d.ts +0 -15
  233. package/dist/types/user.d.ts.map +0 -1
  234. package/dist/utils/formatters.d.ts +0 -25
  235. package/dist/utils/formatters.d.ts.map +0 -1
  236. package/dist/utils/index.cjs +0 -80
  237. package/dist/utils/index.d.ts +0 -2
  238. package/dist/utils/index.d.ts.map +0 -1
  239. package/dist/utils/index.js +0 -57
@@ -0,0 +1,299 @@
1
+ import { useState } from 'react';
2
+ import { Search, BookOpen, Package, Twitter, Facebook, Instagram } from 'lucide-react';
3
+ import type { CatalogCoursesPageProps } from '../../contracts/pages.contract';
4
+ import { Pagination } from '../molecules/Pagination';
5
+ import { CourseCard } from '../molecules/CourseCard';
6
+
7
+ const FILE_URL = 'https://fe-academy-file.dev.magicai-testing-domain.work';
8
+
9
+ const FEATURES = [
10
+ 'Free E-book, video & consolation',
11
+ 'Top instructors from around world',
12
+ 'Top courses from your team',
13
+ ];
14
+
15
+ const DEALS = [
16
+ { id: 1, discount: '50%', title: 'Lorem ipsum dolor', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor', image: `${FILE_URL}/deal-1.png` },
17
+ { id: 2, discount: '10%', title: 'Lorem ipsum dolor', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor', image: `${FILE_URL}/deal-2.png` },
18
+ { id: 3, discount: '50%', title: 'Lorem ipsum dolor', description: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor', image: `${FILE_URL}/deal-3.png` },
19
+ ];
20
+
21
+ const RECOMMENDED = [
22
+ { id: 'd87b963f', title: 'testtgfsdfg dfs', thumbnail: `${FILE_URL}/uploads/4e2fd752-e8c0-440a-8622-3102d33e56e7-1771918805146.png`, totalLessons: 1, progress: 0, isFree: true, price: 0 },
23
+ { id: '30ceedf8', title: 'tessst 11', thumbnail: `${FILE_URL}/uploads/ec2ebd93-f06e-48ce-914a-116b1b91c480-1771878020911.jpeg`, totalLessons: 1, progress: 0, isFree: true, price: 0 },
24
+ { id: 'eae7f568', title: 'React', thumbnail: `${FILE_URL}/uploads/ca3257ea-618c-4a61-a4b6-5560c2bf5ec1-1771488717244.png`, totalLessons: 2, progress: 0, isFree: true, price: 0 },
25
+ { id: '26a52485', title: 'll1', thumbnail: `${FILE_URL}/uploads/9bf7f1ca-214b-4a82-862d-f334801bcd53-1771486627052.png`, totalLessons: 1, progress: 0, isFree: true, price: 0 },
26
+ ];
27
+
28
+ const SIDE_AVATARS = [`${FILE_URL}/avatar-1.png`, `${FILE_URL}/avatar-2.png`, `${FILE_URL}/avatar-3.png`, `${FILE_URL}/avatar-4.png`];
29
+
30
+ export function CatalogCoursesPage(props: Partial<CatalogCoursesPageProps>) {
31
+ const noop = (..._args: any[]) => {};
32
+ const {
33
+ courses = [], meta, loading = false, searchQuery = '', page = 1,
34
+ onSearchChange = noop, onPageChange = noop, onCourseClick = noop,
35
+ activeTab = 'courses', onTabChange,
36
+ bundleCourses = [], bundleMeta, bundlesLoading = false, bundlePage = 1,
37
+ onBundlePageChange = noop, onBundleClick = noop,
38
+ } = props as any;
39
+
40
+ const [localQuery, setLocalQuery] = useState(searchQuery);
41
+ const defaultHeroImage = `${FILE_URL}/catalog-hero-banner.png`;
42
+
43
+ const handleSearchSubmit = (e: React.FormEvent) => {
44
+ e.preventDefault();
45
+ onSearchChange(localQuery);
46
+ };
47
+
48
+ const handleSearchChange = (val: string) => {
49
+ setLocalQuery(val);
50
+ onSearchChange(val);
51
+ };
52
+
53
+ const displayCourses = activeTab === 'courses' ? courses : bundleCourses;
54
+ const displayMeta = activeTab === 'courses' ? meta : bundleMeta;
55
+ const displayLoading = activeTab === 'courses' ? loading : bundlesLoading;
56
+ const displayPage = activeTab === 'courses' ? page : bundlePage;
57
+ const displayPageChange = activeTab === 'courses' ? onPageChange : onBundlePageChange;
58
+
59
+ return (
60
+ <div className="container mx-auto space-y-10">
61
+
62
+ {/* Hero Search Bar */}
63
+ <div className="relative w-full overflow-hidden rounded-2xl">
64
+ <div
65
+ className="absolute inset-0 z-0 bg-cover bg-center"
66
+ style={{ backgroundImage: `var(--catalog-hero-image, url(${defaultHeroImage}))` }}
67
+ >
68
+ <div
69
+ className="absolute inset-0 backdrop-blur-[1px]"
70
+ style={{ background: 'linear-gradient(135deg, rgba(73,187,189,0.75) 0%, rgba(73,187,189,0.55) 50%, rgba(73,187,189,0.40) 100%)' }}
71
+ />
72
+ </div>
73
+ <div className="relative z-10 flex flex-col items-center justify-center px-4 py-8 sm:px-6 sm:py-10 gap-4">
74
+ <form
75
+ onSubmit={handleSearchSubmit}
76
+ className="flex w-full max-w-[680px] bg-white rounded-2xl sm:rounded-full p-1 sm:p-[5px] sm:pl-0 shadow-lg flex-col sm:flex-row gap-1 sm:gap-0 sm:items-center"
77
+ >
78
+ <div className="relative flex-1 flex items-center w-full">
79
+ <Search className="absolute left-4 sm:left-5 w-5 h-5 text-gray-400 pointer-events-none" />
80
+ <input
81
+ type="text"
82
+ placeholder={activeTab === 'courses' ? 'Search your favourite course' : 'Search your favourite bundle'}
83
+ value={localQuery}
84
+ onChange={(e) => handleSearchChange(e.target.value)}
85
+ className="w-full py-3 sm:py-3.5 pl-12 sm:pl-13 pr-4 bg-transparent text-gray-700 text-sm sm:text-[15px] rounded-full border-none outline-none placeholder:text-gray-400"
86
+ />
87
+ </div>
88
+ <button
89
+ type="submit"
90
+ className="shrink-0 px-8 py-3 bg-theme-accent-primary text-white font-semibold text-sm sm:text-[15px] rounded-xl sm:rounded-full cursor-pointer transition-all duration-150 hover:brightness-110 hover:scale-[1.02] active:scale-[0.98] w-full sm:w-auto"
91
+ >
92
+ Search
93
+ </button>
94
+ </form>
95
+ </div>
96
+ </div>
97
+
98
+ {/* Tab Bar */}
99
+ <div className="flex gap-2">
100
+ {[{ id: 'courses', label: 'Courses', Icon: BookOpen }, { id: 'bundles', label: 'Bundles', Icon: Package }].map(({ id, label, Icon }) => (
101
+ <button
102
+ key={id}
103
+ onClick={() => onTabChange?.(id)}
104
+ className={`flex items-center gap-2 px-4 py-2.5 font-medium transition-colors cursor-pointer ${
105
+ activeTab === id
106
+ ? 'border-b-2 border-theme-accent-primary text-theme-accent-primary'
107
+ : 'text-theme-text-secondary hover:text-theme-text-primary'
108
+ }`}
109
+ >
110
+ <Icon className="h-4 w-4" />
111
+ {label}
112
+ </button>
113
+ ))}
114
+ </div>
115
+
116
+ {/* Course / Bundle Grid */}
117
+ {displayLoading ? (
118
+ <div className="flex flex-col items-center justify-center py-16">
119
+ <div className="h-12 w-12 animate-spin rounded-full border-4 border-theme-border-primary border-t-theme-accent-primary"></div>
120
+ <p className="mt-4 text-theme-text-secondary">Loading {activeTab}...</p>
121
+ </div>
122
+ ) : displayCourses.length === 0 ? (
123
+ <div className="flex flex-col items-center justify-center rounded-lg border-2 border-dashed border-theme-border-primary bg-theme-bg-secondary py-16 text-center">
124
+ <div className="text-4xl mb-4">{activeTab === 'courses' ? '📚' : '📦'}</div>
125
+ <h3 className="text-xl font-semibold text-theme-text-primary">No {activeTab} found</h3>
126
+ <p className="mt-2 max-w-md text-theme-text-secondary">Try adjusting your search query or check back later</p>
127
+ </div>
128
+ ) : (
129
+ <>
130
+ <div className="grid gap-6 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
131
+ {displayCourses.map((course: any) => {
132
+ const description = (course.summary || course.description || '').replace(/<[^>]*>/g, '');
133
+ const price = course.price ?? 0;
134
+ const isFree = course.isFree !== undefined ? course.isFree : price === 0;
135
+ return (
136
+ <CourseCard
137
+ key={course.id}
138
+ id={course.id}
139
+ title={course.title || ''}
140
+ thumbnail={course.thumbnail || ''}
141
+ totalLessons={course.totalLessons || 0}
142
+ progress={0}
143
+ showProgress={false}
144
+ isFree={isFree}
145
+ price={price}
146
+ showPrice={true}
147
+ currency={course.currency}
148
+ description={description}
149
+ instructorName={course.instructorName}
150
+ instructorAvatar={course.instructorAvatar}
151
+ category={course.category}
152
+ originalPrice={course.originalPrice}
153
+ isBundle={activeTab === 'bundles'}
154
+ thumbnails={course.thumbnails}
155
+ onClick={() => activeTab === 'courses' ? onCourseClick(course.id) : onBundleClick(course.id)}
156
+ />
157
+ );
158
+ })}
159
+ </div>
160
+ {displayMeta && displayMeta.totalPages > 1 && (
161
+ <Pagination
162
+ currentPage={displayPage}
163
+ totalPages={displayMeta.totalPages}
164
+ total={displayMeta.total}
165
+ pageSize={displayMeta.limit || 8}
166
+ hasNextPage={displayPage < displayMeta.totalPages}
167
+ hasPreviousPage={displayPage > 1}
168
+ onPageChange={displayPageChange}
169
+ />
170
+ )}
171
+ </>
172
+ )}
173
+
174
+ {/* Know about learning platform */}
175
+ <section className="rounded-2xl bg-[#EEF4FA] p-6 sm:p-8 lg:p-10 overflow-hidden">
176
+ <div className="flex flex-col items-center gap-8 lg:flex-row lg:items-center lg:justify-between px-10">
177
+ <div className="space-y-6 lg:max-w-md shrink-0 py-4">
178
+ <h2 className="text-2xl font-bold text-gray-900 sm:text-3xl leading-snug">
179
+ Know about learning<br />learning platform
180
+ </h2>
181
+ <ul className="space-y-5">
182
+ {FEATURES.map((feature, i) => (
183
+ <li key={i} className="flex items-center gap-4 text-sm text-gray-600 font-medium">
184
+ <span className="h-4 w-4 rounded-full bg-[#B8F0E8] shrink-0" />
185
+ {feature}
186
+ </li>
187
+ ))}
188
+ </ul>
189
+ <button className="inline-block rounded-lg bg-theme-accent-primary px-8 py-3.5 text-sm font-bold text-white shadow-md transition-all hover:brightness-110 hover:scale-[1.02] active:scale-[0.98]">
190
+ Start learning now
191
+ </button>
192
+ </div>
193
+ <div className="relative w-full max-w-[520px] lg:w-[55%]">
194
+ <div className="aspect-[16/10] w-full flex items-center justify-center bg-[#dbeafe] rounded-xl">
195
+ <span className="text-gray-400 text-sm">Platform Preview</span>
196
+ </div>
197
+ </div>
198
+ </div>
199
+ </section>
200
+
201
+ {/* Recommended for you */}
202
+ <div className="relative w-screen left-1/2 right-1/2 -ml-[50vw] -mr-[50vw] bg-[#EEF4FA]">
203
+ <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
204
+ <section className="space-y-6">
205
+ <div className="flex items-center justify-between">
206
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">Recommended for you</h2>
207
+ <button className="shrink-0 text-sm font-semibold text-[rgb(var(--accent-primary))] hover:underline cursor-pointer">See all</button>
208
+ </div>
209
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4">
210
+ {RECOMMENDED.map((course) => (
211
+ <CourseCard
212
+ key={course.id}
213
+ id={course.id}
214
+ title={course.title}
215
+ thumbnail={course.thumbnail}
216
+ totalLessons={course.totalLessons}
217
+ progress={course.progress}
218
+ showProgress={false}
219
+ isFree={course.isFree}
220
+ price={course.price}
221
+ showPrice={true}
222
+ onClick={() => onCourseClick(course.id)}
223
+ />
224
+ ))}
225
+ </div>
226
+ </section>
227
+ </div>
228
+ </div>
229
+
230
+ {/* What our students have to say */}
231
+ <div className="relative w-screen left-1/2 right-1/2 -ml-[50vw] -mr-[50vw] bg-[#EEF4FA]">
232
+ <div className="mx-auto max-w-7xl px-4 sm:px-6 lg:px-8 py-12">
233
+ <section className="space-y-6">
234
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">What our students have to say</h2>
235
+ <div className="rounded-2xl bg-white p-6 sm:p-8 lg:p-14 shadow-sm relative overflow-hidden">
236
+ <div className="flex flex-col items-center gap-6 lg:flex-row lg:items-center lg:gap-16">
237
+ <div className="relative shrink-0 w-48 h-48 sm:w-64 sm:h-64 bg-gray-100 rounded-full flex items-center justify-center">
238
+ <img src={`${FILE_URL}/student.png`} alt="Student" className="w-full h-full object-cover rounded-full" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
239
+ </div>
240
+ <div className="flex-1 space-y-4 text-center lg:text-left min-w-0 z-10">
241
+ <div className="space-y-1">
242
+ <h3 className="text-xl font-bold text-gray-900">Savannah Nguyen</h3>
243
+ <p className="text-sm text-gray-500 font-medium tracking-wide">tanya.hill@example.com</p>
244
+ </div>
245
+ <div className="space-y-2 text-sm text-gray-500 leading-relaxed max-w-lg">
246
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor</p>
247
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor</p>
248
+ <p>Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor</p>
249
+ </div>
250
+ <div className="flex items-center gap-3 pt-2 justify-center lg:justify-start">
251
+ {[Twitter, Facebook, Instagram].map((Icon, i) => (
252
+ <button key={i} className="flex h-8 w-8 items-center justify-center rounded-full bg-theme-accent-primary text-white transition-all hover:scale-110 hover:shadow-md">
253
+ <Icon className="h-4 w-4" />
254
+ </button>
255
+ ))}
256
+ </div>
257
+ </div>
258
+ <div className="hidden lg:flex flex-col gap-5 shrink-0 z-10">
259
+ {SIDE_AVATARS.map((avatar, i) => (
260
+ <div key={i} className="relative h-14 w-14 overflow-hidden rounded-full border-2 border-white shadow-md transition-all hover:scale-110 cursor-pointer hover:border-[#49BBBD]">
261
+ <img src={avatar} alt={`Student ${i + 1}`} className="w-full h-full object-cover" onError={(e) => { (e.target as HTMLImageElement).style.display = 'none'; }} />
262
+ </div>
263
+ ))}
264
+ </div>
265
+ </div>
266
+ </div>
267
+ </section>
268
+ </div>
269
+ </div>
270
+
271
+ {/* Top Education Deals */}
272
+ <section className="space-y-6">
273
+ <div className="flex items-center justify-between">
274
+ <h2 className="text-xl font-bold text-gray-900 sm:text-2xl">Top Education offers and deals are listed here</h2>
275
+ <button className="shrink-0 text-sm font-semibold text-[rgb(var(--accent-primary))] hover:underline cursor-pointer">See all</button>
276
+ </div>
277
+ <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
278
+ {DEALS.map((deal) => (
279
+ <div key={deal.id} className="group relative overflow-hidden rounded-2xl cursor-pointer h-[320px] sm:h-[350px]">
280
+ <img src={deal.image} alt={deal.title} className="absolute inset-0 w-full h-full object-cover transition-transform duration-500 group-hover:scale-110" onError={(e) => { (e.target as HTMLImageElement).style.background = '#e5e7eb'; }} />
281
+ <div className="absolute inset-0 bg-gradient-to-t from-black/80 via-black/40 to-black/10" />
282
+ <div className="absolute inset-0 flex flex-col justify-between p-5 sm:p-6">
283
+ <div>
284
+ <span className="inline-block rounded-lg bg-[rgb(var(--accent-primary))] px-4 py-2 text-xl font-bold text-white sm:text-2xl">{deal.discount}</span>
285
+ </div>
286
+ <div className="space-y-2">
287
+ <h3 className="text-lg font-bold text-white sm:text-xl">{deal.title}</h3>
288
+ <p className="text-sm text-white/80 leading-relaxed line-clamp-2">{deal.description}</p>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ ))}
293
+ </div>
294
+ </section>
295
+
296
+ </div>
297
+ );
298
+ }
299
+