@academy-sdk/sdk 0.1.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.
@@ -0,0 +1,334 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/components/molecules/index.ts
21
+ var molecules_exports = {};
22
+ __export(molecules_exports, {
23
+ CourseCard: () => CourseCard,
24
+ EmptyState: () => EmptyState,
25
+ LoadingSpinner: () => LoadingSpinner,
26
+ PageHeader: () => PageHeader,
27
+ Pagination: () => Pagination,
28
+ SearchInput: () => SearchInput
29
+ });
30
+ module.exports = __toCommonJS(molecules_exports);
31
+
32
+ // src/components/molecules/CourseCard.tsx
33
+ var import_react = require("react");
34
+ var import_lucide_react = require("lucide-react");
35
+
36
+ // src/components/utils.ts
37
+ var import_clsx = require("clsx");
38
+ var import_tailwind_merge = require("tailwind-merge");
39
+ function cn(...inputs) {
40
+ return (0, import_tailwind_merge.twMerge)((0, import_clsx.clsx)(inputs));
41
+ }
42
+
43
+ // src/components/molecules/CourseCard.tsx
44
+ var import_jsx_runtime = require("react/jsx-runtime");
45
+ function CourseCard({
46
+ id,
47
+ title,
48
+ thumbnail,
49
+ thumbnails,
50
+ totalLessons,
51
+ progress,
52
+ status,
53
+ isFree,
54
+ price = 0,
55
+ currency = "USD",
56
+ description,
57
+ showPrice = true,
58
+ showProgress = true,
59
+ category,
60
+ duration,
61
+ instructorName,
62
+ instructorAvatar,
63
+ originalPrice,
64
+ isBundle = false,
65
+ onClick,
66
+ className
67
+ }) {
68
+ const [imageError, setImageError] = (0, import_react.useState)(false);
69
+ const clampedProgress = Math.min(Math.max(progress, 0), 100);
70
+ const stripHtml = (html) => {
71
+ if (typeof document === "undefined") return html;
72
+ const tmp = document.createElement("div");
73
+ tmp.innerHTML = html;
74
+ return tmp.textContent || tmp.innerText || "";
75
+ };
76
+ const displayCategory = category || "Design";
77
+ const displayDuration = duration || "3 Month";
78
+ const displayInstructorName = instructorName || (description ? stripHtml(description).slice(0, 15) : "Instructor");
79
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
80
+ "div",
81
+ {
82
+ className: cn(
83
+ "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",
84
+ className
85
+ ),
86
+ onClick,
87
+ children: [
88
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative h-[238.66px] w-full overflow-hidden mx-auto px-3 pt-3", children: [
89
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative w-full h-full rounded-xl overflow-hidden bg-gray-100", children: isBundle && thumbnails && thumbnails.length > 0 && !imageError ? /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "relative w-full h-full flex", children: [
90
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative h-full w-1/2", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
91
+ "img",
92
+ {
93
+ src: thumbnails[0],
94
+ alt: `${title} - image 1`,
95
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
96
+ onError: () => setImageError(true)
97
+ }
98
+ ) }),
99
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex flex-col w-1/2 h-full", children: [
100
+ thumbnails[1] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("relative w-full overflow-hidden", thumbnails.length > 2 ? "h-1/2" : "h-full"), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
101
+ "img",
102
+ {
103
+ src: thumbnails[1],
104
+ alt: `${title} - image 2`,
105
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
106
+ onError: () => setImageError(true)
107
+ }
108
+ ) }),
109
+ thumbnails.length > 2 && thumbnails[2] && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "relative w-full h-1/2 overflow-hidden", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
110
+ "img",
111
+ {
112
+ src: thumbnails[2],
113
+ alt: `${title} - image 3`,
114
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
115
+ onError: () => setImageError(true)
116
+ }
117
+ ) })
118
+ ] })
119
+ ] }) : thumbnail && !imageError ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
120
+ "img",
121
+ {
122
+ src: thumbnail,
123
+ alt: title,
124
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
125
+ onError: () => setImageError(true)
126
+ }
127
+ ) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-full h-full flex items-center justify-center bg-gray-200", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.BookOpen, { className: "w-12 h-12 text-gray-400" }) }) }),
128
+ status && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "absolute right-6 top-5", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
129
+ "span",
130
+ {
131
+ 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))]"}`,
132
+ children: status
133
+ }
134
+ ) })
135
+ ] }),
136
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "p-5 space-y-3 flex-1 flex flex-col min-w-0", children: [
137
+ (displayCategory || displayDuration) && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between text-xs text-gray-500", children: [
138
+ displayCategory && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5", children: [
139
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Grid2X2, { className: "w-3.5 h-3.5 text-gray-400" }),
140
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: displayCategory })
141
+ ] }),
142
+ displayDuration && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-1.5", children: [
143
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_lucide_react.Clock, { className: "w-3.5 h-3.5 text-gray-400" }),
144
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { children: displayDuration })
145
+ ] })
146
+ ] }),
147
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("h3", { className: "text-base font-bold text-gray-900 line-clamp-2 leading-snug", children: title }),
148
+ description && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("p", { className: "text-sm text-gray-500 line-clamp-3 leading-relaxed", children: stripHtml(description) }),
149
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "flex-1" }),
150
+ showProgress && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "space-y-2", children: [
151
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "h-2 overflow-hidden rounded-full bg-gray-100", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
152
+ "div",
153
+ {
154
+ className: "h-full rounded-full bg-[rgb(var(--accent-primary))] transition-all duration-300",
155
+ style: { width: `${clampedProgress}%` }
156
+ }
157
+ ) }),
158
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between text-xs", children: [
159
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-gray-400" }),
160
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-gray-500", children: [
161
+ isBundle ? "Course" : "Lesson",
162
+ " ",
163
+ Math.ceil(clampedProgress / 100 * totalLessons),
164
+ " of ",
165
+ totalLessons
166
+ ] })
167
+ ] })
168
+ ] }),
169
+ showPrice && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center justify-between pt-3 border-t border-gray-100", children: [
170
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
171
+ instructorAvatar ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-7 h-7 rounded-full overflow-hidden flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: instructorAvatar, alt: displayInstructorName, className: "w-full h-full object-cover" }) }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-[10px] font-semibold text-gray-500", children: displayInstructorName.charAt(0).toUpperCase() }) }),
172
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-sm text-gray-600 font-medium truncate max-w-[80px]", children: displayInstructorName })
173
+ ] }),
174
+ /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: "flex items-center gap-2", children: [
175
+ !isFree && originalPrice && originalPrice > price && /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("span", { className: "text-sm text-gray-400 line-through", children: [
176
+ "$",
177
+ originalPrice
178
+ ] }),
179
+ /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "text-lg font-bold text-[rgb(var(--accent-primary))]", children: isFree ? "Free" : `$${price}` })
180
+ ] })
181
+ ] })
182
+ ] })
183
+ ]
184
+ }
185
+ );
186
+ }
187
+
188
+ // src/components/molecules/Pagination.tsx
189
+ var import_lucide_react2 = require("lucide-react");
190
+ var import_jsx_runtime2 = require("react/jsx-runtime");
191
+ function Pagination({
192
+ currentPage,
193
+ totalPages,
194
+ total,
195
+ pageSize,
196
+ hasNextPage,
197
+ hasPreviousPage,
198
+ onPageChange,
199
+ maxVisiblePages = 5,
200
+ className
201
+ }) {
202
+ const renderPageNumbers = () => {
203
+ const pages = [];
204
+ let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
205
+ let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
206
+ if (endPage - startPage < maxVisiblePages - 1) {
207
+ startPage = Math.max(1, endPage - maxVisiblePages + 1);
208
+ }
209
+ for (let i = startPage; i <= endPage; i++) {
210
+ pages.push(
211
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
212
+ "button",
213
+ {
214
+ onClick: () => onPageChange(i),
215
+ className: cn(
216
+ "px-3 py-1 rounded cursor-pointer transition-colors",
217
+ i === currentPage ? "bg-theme-accent-primary text-white" : "text-theme-text-primary border border-theme-border-primary hover:bg-theme-bg-tertiary"
218
+ ),
219
+ children: i
220
+ },
221
+ i
222
+ )
223
+ );
224
+ }
225
+ return pages;
226
+ };
227
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("flex items-center justify-between border-t border-theme-border-primary pt-4", className), children: [
228
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "text-sm text-theme-text-secondary", children: [
229
+ "Showing ",
230
+ (currentPage - 1) * pageSize + 1,
231
+ " to ",
232
+ Math.min(currentPage * pageSize, total),
233
+ " of ",
234
+ total,
235
+ " results"
236
+ ] }),
237
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center gap-2", children: [
238
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
239
+ "button",
240
+ {
241
+ onClick: () => onPageChange(currentPage - 1),
242
+ disabled: !hasPreviousPage,
243
+ className: "p-2 rounded border border-theme-border-primary hover:bg-theme-bg-tertiary disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-colors",
244
+ "aria-label": "Previous page",
245
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.ChevronLeft, { className: "h-4 w-4" })
246
+ }
247
+ ),
248
+ renderPageNumbers(),
249
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
250
+ "button",
251
+ {
252
+ onClick: () => onPageChange(currentPage + 1),
253
+ disabled: !hasNextPage,
254
+ className: "p-2 rounded border border-theme-border-primary hover:bg-theme-bg-tertiary disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-colors",
255
+ "aria-label": "Next page",
256
+ children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_lucide_react2.ChevronRight, { className: "h-4 w-4" })
257
+ }
258
+ )
259
+ ] })
260
+ ] });
261
+ }
262
+
263
+ // src/components/molecules/PageHeader.tsx
264
+ var import_jsx_runtime3 = require("react/jsx-runtime");
265
+ function PageHeader({ title, description, actions, className }) {
266
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6", className), children: [
267
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
268
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h1", { className: "text-2xl font-bold text-theme-text-primary", children: title }),
269
+ description && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-theme-text-secondary mt-1", children: description })
270
+ ] }),
271
+ actions && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex items-center gap-2", children: actions })
272
+ ] });
273
+ }
274
+
275
+ // src/components/molecules/SearchInput.tsx
276
+ var import_lucide_react3 = require("lucide-react");
277
+ var import_jsx_runtime4 = require("react/jsx-runtime");
278
+ function SearchInput({ value, onChange, placeholder = "Search...", className }) {
279
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)("div", { className: cn("relative", className), children: [
280
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-theme-text-muted" }),
281
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
282
+ "input",
283
+ {
284
+ type: "text",
285
+ value,
286
+ onChange: (e) => onChange(e.target.value),
287
+ placeholder,
288
+ className: "w-full pl-10 pr-10 py-2 text-sm rounded-lg border border-theme-border-primary bg-theme-bg-primary text-theme-text-primary placeholder:text-[rgb(var(--text-muted))] focus:outline-none focus:border-theme-accent-primary transition-colors"
289
+ }
290
+ ),
291
+ value && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
292
+ "button",
293
+ {
294
+ onClick: () => onChange(""),
295
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-theme-text-muted hover:text-theme-text-primary cursor-pointer",
296
+ "aria-label": "Clear search",
297
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(import_lucide_react3.X, { className: "h-4 w-4" })
298
+ }
299
+ )
300
+ ] });
301
+ }
302
+
303
+ // src/components/molecules/EmptyState.tsx
304
+ var import_jsx_runtime5 = require("react/jsx-runtime");
305
+ function EmptyState({ icon, title, description, action, className }) {
306
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { className: cn("flex flex-col items-center justify-center py-12 text-center", className), children: [
307
+ icon && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mb-4 text-theme-text-muted", children: icon }),
308
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("h3", { className: "text-lg font-semibold text-theme-text-primary", children: title }),
309
+ description && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("p", { className: "mt-2 text-sm text-theme-text-secondary max-w-md", children: description }),
310
+ action && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className: "mt-4", children: action })
311
+ ] });
312
+ }
313
+
314
+ // src/components/molecules/LoadingSpinner.tsx
315
+ var import_jsx_runtime6 = require("react/jsx-runtime");
316
+ var sizeClasses = {
317
+ sm: "h-4 w-4",
318
+ md: "h-8 w-8",
319
+ lg: "h-12 w-12"
320
+ };
321
+ function LoadingSpinner({ size = "md", className, text }) {
322
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsxs)("div", { className: cn("flex flex-col items-center justify-center gap-3", className), children: [
323
+ /* @__PURE__ */ (0, import_jsx_runtime6.jsx)(
324
+ "div",
325
+ {
326
+ className: cn(
327
+ "animate-spin rounded-full border-2 border-theme-bg-tertiary border-t-theme-accent-primary",
328
+ sizeClasses[size]
329
+ )
330
+ }
331
+ ),
332
+ text && /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("p", { className: "text-sm text-theme-text-secondary", children: text })
333
+ ] });
334
+ }
@@ -0,0 +1,311 @@
1
+ // src/components/molecules/CourseCard.tsx
2
+ import { useState } from "react";
3
+ import { BookOpen, Clock, Grid2X2 } from "lucide-react";
4
+
5
+ // src/components/utils.ts
6
+ import { clsx } from "clsx";
7
+ import { twMerge } from "tailwind-merge";
8
+ function cn(...inputs) {
9
+ return twMerge(clsx(inputs));
10
+ }
11
+
12
+ // src/components/molecules/CourseCard.tsx
13
+ import { jsx, jsxs } from "react/jsx-runtime";
14
+ function CourseCard({
15
+ id,
16
+ title,
17
+ thumbnail,
18
+ thumbnails,
19
+ totalLessons,
20
+ progress,
21
+ status,
22
+ isFree,
23
+ price = 0,
24
+ currency = "USD",
25
+ description,
26
+ showPrice = true,
27
+ showProgress = true,
28
+ category,
29
+ duration,
30
+ instructorName,
31
+ instructorAvatar,
32
+ originalPrice,
33
+ isBundle = false,
34
+ onClick,
35
+ className
36
+ }) {
37
+ const [imageError, setImageError] = useState(false);
38
+ const clampedProgress = Math.min(Math.max(progress, 0), 100);
39
+ const stripHtml = (html) => {
40
+ if (typeof document === "undefined") return html;
41
+ const tmp = document.createElement("div");
42
+ tmp.innerHTML = html;
43
+ return tmp.textContent || tmp.innerText || "";
44
+ };
45
+ const displayCategory = category || "Design";
46
+ const displayDuration = duration || "3 Month";
47
+ const displayInstructorName = instructorName || (description ? stripHtml(description).slice(0, 15) : "Instructor");
48
+ return /* @__PURE__ */ jsxs(
49
+ "div",
50
+ {
51
+ className: cn(
52
+ "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",
53
+ className
54
+ ),
55
+ onClick,
56
+ children: [
57
+ /* @__PURE__ */ jsxs("div", { className: "relative h-[238.66px] w-full overflow-hidden mx-auto px-3 pt-3", children: [
58
+ /* @__PURE__ */ jsx("div", { className: "relative w-full h-full rounded-xl overflow-hidden bg-gray-100", children: isBundle && thumbnails && thumbnails.length > 0 && !imageError ? /* @__PURE__ */ jsxs("div", { className: "relative w-full h-full flex", children: [
59
+ /* @__PURE__ */ jsx("div", { className: "relative h-full w-1/2", children: /* @__PURE__ */ jsx(
60
+ "img",
61
+ {
62
+ src: thumbnails[0],
63
+ alt: `${title} - image 1`,
64
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
65
+ onError: () => setImageError(true)
66
+ }
67
+ ) }),
68
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col w-1/2 h-full", children: [
69
+ thumbnails[1] && /* @__PURE__ */ jsx("div", { className: cn("relative w-full overflow-hidden", thumbnails.length > 2 ? "h-1/2" : "h-full"), children: /* @__PURE__ */ jsx(
70
+ "img",
71
+ {
72
+ src: thumbnails[1],
73
+ alt: `${title} - image 2`,
74
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
75
+ onError: () => setImageError(true)
76
+ }
77
+ ) }),
78
+ thumbnails.length > 2 && thumbnails[2] && /* @__PURE__ */ jsx("div", { className: "relative w-full h-1/2 overflow-hidden", children: /* @__PURE__ */ jsx(
79
+ "img",
80
+ {
81
+ src: thumbnails[2],
82
+ alt: `${title} - image 3`,
83
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
84
+ onError: () => setImageError(true)
85
+ }
86
+ ) })
87
+ ] })
88
+ ] }) : thumbnail && !imageError ? /* @__PURE__ */ jsx(
89
+ "img",
90
+ {
91
+ src: thumbnail,
92
+ alt: title,
93
+ className: "w-full h-full object-cover transition-transform duration-300 group-hover:scale-110",
94
+ onError: () => setImageError(true)
95
+ }
96
+ ) : /* @__PURE__ */ jsx("div", { className: "w-full h-full flex items-center justify-center bg-gray-200", children: /* @__PURE__ */ jsx(BookOpen, { className: "w-12 h-12 text-gray-400" }) }) }),
97
+ status && /* @__PURE__ */ jsx("div", { className: "absolute right-6 top-5", children: /* @__PURE__ */ jsx(
98
+ "span",
99
+ {
100
+ 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))]"}`,
101
+ children: status
102
+ }
103
+ ) })
104
+ ] }),
105
+ /* @__PURE__ */ jsxs("div", { className: "p-5 space-y-3 flex-1 flex flex-col min-w-0", children: [
106
+ (displayCategory || displayDuration) && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs text-gray-500", children: [
107
+ displayCategory && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
108
+ /* @__PURE__ */ jsx(Grid2X2, { className: "w-3.5 h-3.5 text-gray-400" }),
109
+ /* @__PURE__ */ jsx("span", { children: displayCategory })
110
+ ] }),
111
+ displayDuration && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
112
+ /* @__PURE__ */ jsx(Clock, { className: "w-3.5 h-3.5 text-gray-400" }),
113
+ /* @__PURE__ */ jsx("span", { children: displayDuration })
114
+ ] })
115
+ ] }),
116
+ /* @__PURE__ */ jsx("h3", { className: "text-base font-bold text-gray-900 line-clamp-2 leading-snug", children: title }),
117
+ description && /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-500 line-clamp-3 leading-relaxed", children: stripHtml(description) }),
118
+ /* @__PURE__ */ jsx("div", { className: "flex-1" }),
119
+ showProgress && /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
120
+ /* @__PURE__ */ jsx("div", { className: "h-2 overflow-hidden rounded-full bg-gray-100", children: /* @__PURE__ */ jsx(
121
+ "div",
122
+ {
123
+ className: "h-full rounded-full bg-[rgb(var(--accent-primary))] transition-all duration-300",
124
+ style: { width: `${clampedProgress}%` }
125
+ }
126
+ ) }),
127
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-xs", children: [
128
+ /* @__PURE__ */ jsx("span", { className: "text-gray-400" }),
129
+ /* @__PURE__ */ jsxs("span", { className: "text-gray-500", children: [
130
+ isBundle ? "Course" : "Lesson",
131
+ " ",
132
+ Math.ceil(clampedProgress / 100 * totalLessons),
133
+ " of ",
134
+ totalLessons
135
+ ] })
136
+ ] })
137
+ ] }),
138
+ showPrice && /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between pt-3 border-t border-gray-100", children: [
139
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
140
+ instructorAvatar ? /* @__PURE__ */ jsx("div", { className: "w-7 h-7 rounded-full overflow-hidden flex-shrink-0", children: /* @__PURE__ */ jsx("img", { src: instructorAvatar, alt: displayInstructorName, className: "w-full h-full object-cover" }) }) : /* @__PURE__ */ jsx("div", { className: "w-7 h-7 rounded-full bg-gray-200 flex items-center justify-center flex-shrink-0", children: /* @__PURE__ */ jsx("span", { className: "text-[10px] font-semibold text-gray-500", children: displayInstructorName.charAt(0).toUpperCase() }) }),
141
+ /* @__PURE__ */ jsx("span", { className: "text-sm text-gray-600 font-medium truncate max-w-[80px]", children: displayInstructorName })
142
+ ] }),
143
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
144
+ !isFree && originalPrice && originalPrice > price && /* @__PURE__ */ jsxs("span", { className: "text-sm text-gray-400 line-through", children: [
145
+ "$",
146
+ originalPrice
147
+ ] }),
148
+ /* @__PURE__ */ jsx("span", { className: "text-lg font-bold text-[rgb(var(--accent-primary))]", children: isFree ? "Free" : `$${price}` })
149
+ ] })
150
+ ] })
151
+ ] })
152
+ ]
153
+ }
154
+ );
155
+ }
156
+
157
+ // src/components/molecules/Pagination.tsx
158
+ import { ChevronLeft, ChevronRight } from "lucide-react";
159
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
160
+ function Pagination({
161
+ currentPage,
162
+ totalPages,
163
+ total,
164
+ pageSize,
165
+ hasNextPage,
166
+ hasPreviousPage,
167
+ onPageChange,
168
+ maxVisiblePages = 5,
169
+ className
170
+ }) {
171
+ const renderPageNumbers = () => {
172
+ const pages = [];
173
+ let startPage = Math.max(1, currentPage - Math.floor(maxVisiblePages / 2));
174
+ let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1);
175
+ if (endPage - startPage < maxVisiblePages - 1) {
176
+ startPage = Math.max(1, endPage - maxVisiblePages + 1);
177
+ }
178
+ for (let i = startPage; i <= endPage; i++) {
179
+ pages.push(
180
+ /* @__PURE__ */ jsx2(
181
+ "button",
182
+ {
183
+ onClick: () => onPageChange(i),
184
+ className: cn(
185
+ "px-3 py-1 rounded cursor-pointer transition-colors",
186
+ i === currentPage ? "bg-theme-accent-primary text-white" : "text-theme-text-primary border border-theme-border-primary hover:bg-theme-bg-tertiary"
187
+ ),
188
+ children: i
189
+ },
190
+ i
191
+ )
192
+ );
193
+ }
194
+ return pages;
195
+ };
196
+ return /* @__PURE__ */ jsxs2("div", { className: cn("flex items-center justify-between border-t border-theme-border-primary pt-4", className), children: [
197
+ /* @__PURE__ */ jsxs2("div", { className: "text-sm text-theme-text-secondary", children: [
198
+ "Showing ",
199
+ (currentPage - 1) * pageSize + 1,
200
+ " to ",
201
+ Math.min(currentPage * pageSize, total),
202
+ " of ",
203
+ total,
204
+ " results"
205
+ ] }),
206
+ /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
207
+ /* @__PURE__ */ jsx2(
208
+ "button",
209
+ {
210
+ onClick: () => onPageChange(currentPage - 1),
211
+ disabled: !hasPreviousPage,
212
+ className: "p-2 rounded border border-theme-border-primary hover:bg-theme-bg-tertiary disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-colors",
213
+ "aria-label": "Previous page",
214
+ children: /* @__PURE__ */ jsx2(ChevronLeft, { className: "h-4 w-4" })
215
+ }
216
+ ),
217
+ renderPageNumbers(),
218
+ /* @__PURE__ */ jsx2(
219
+ "button",
220
+ {
221
+ onClick: () => onPageChange(currentPage + 1),
222
+ disabled: !hasNextPage,
223
+ className: "p-2 rounded border border-theme-border-primary hover:bg-theme-bg-tertiary disabled:opacity-50 disabled:cursor-not-allowed cursor-pointer transition-colors",
224
+ "aria-label": "Next page",
225
+ children: /* @__PURE__ */ jsx2(ChevronRight, { className: "h-4 w-4" })
226
+ }
227
+ )
228
+ ] })
229
+ ] });
230
+ }
231
+
232
+ // src/components/molecules/PageHeader.tsx
233
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
234
+ function PageHeader({ title, description, actions, className }) {
235
+ return /* @__PURE__ */ jsxs3("div", { className: cn("flex flex-col sm:flex-row sm:items-center justify-between gap-4 mb-6", className), children: [
236
+ /* @__PURE__ */ jsxs3("div", { children: [
237
+ /* @__PURE__ */ jsx3("h1", { className: "text-2xl font-bold text-theme-text-primary", children: title }),
238
+ description && /* @__PURE__ */ jsx3("p", { className: "text-sm text-theme-text-secondary mt-1", children: description })
239
+ ] }),
240
+ actions && /* @__PURE__ */ jsx3("div", { className: "flex items-center gap-2", children: actions })
241
+ ] });
242
+ }
243
+
244
+ // src/components/molecules/SearchInput.tsx
245
+ import { Search, X } from "lucide-react";
246
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
247
+ function SearchInput({ value, onChange, placeholder = "Search...", className }) {
248
+ return /* @__PURE__ */ jsxs4("div", { className: cn("relative", className), children: [
249
+ /* @__PURE__ */ jsx4(Search, { className: "absolute left-3 top-1/2 -translate-y-1/2 h-4 w-4 text-theme-text-muted" }),
250
+ /* @__PURE__ */ jsx4(
251
+ "input",
252
+ {
253
+ type: "text",
254
+ value,
255
+ onChange: (e) => onChange(e.target.value),
256
+ placeholder,
257
+ className: "w-full pl-10 pr-10 py-2 text-sm rounded-lg border border-theme-border-primary bg-theme-bg-primary text-theme-text-primary placeholder:text-[rgb(var(--text-muted))] focus:outline-none focus:border-theme-accent-primary transition-colors"
258
+ }
259
+ ),
260
+ value && /* @__PURE__ */ jsx4(
261
+ "button",
262
+ {
263
+ onClick: () => onChange(""),
264
+ className: "absolute right-3 top-1/2 -translate-y-1/2 text-theme-text-muted hover:text-theme-text-primary cursor-pointer",
265
+ "aria-label": "Clear search",
266
+ children: /* @__PURE__ */ jsx4(X, { className: "h-4 w-4" })
267
+ }
268
+ )
269
+ ] });
270
+ }
271
+
272
+ // src/components/molecules/EmptyState.tsx
273
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
274
+ function EmptyState({ icon, title, description, action, className }) {
275
+ return /* @__PURE__ */ jsxs5("div", { className: cn("flex flex-col items-center justify-center py-12 text-center", className), children: [
276
+ icon && /* @__PURE__ */ jsx5("div", { className: "mb-4 text-theme-text-muted", children: icon }),
277
+ /* @__PURE__ */ jsx5("h3", { className: "text-lg font-semibold text-theme-text-primary", children: title }),
278
+ description && /* @__PURE__ */ jsx5("p", { className: "mt-2 text-sm text-theme-text-secondary max-w-md", children: description }),
279
+ action && /* @__PURE__ */ jsx5("div", { className: "mt-4", children: action })
280
+ ] });
281
+ }
282
+
283
+ // src/components/molecules/LoadingSpinner.tsx
284
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
285
+ var sizeClasses = {
286
+ sm: "h-4 w-4",
287
+ md: "h-8 w-8",
288
+ lg: "h-12 w-12"
289
+ };
290
+ function LoadingSpinner({ size = "md", className, text }) {
291
+ return /* @__PURE__ */ jsxs6("div", { className: cn("flex flex-col items-center justify-center gap-3", className), children: [
292
+ /* @__PURE__ */ jsx6(
293
+ "div",
294
+ {
295
+ className: cn(
296
+ "animate-spin rounded-full border-2 border-theme-bg-tertiary border-t-theme-accent-primary",
297
+ sizeClasses[size]
298
+ )
299
+ }
300
+ ),
301
+ text && /* @__PURE__ */ jsx6("p", { className: "text-sm text-theme-text-secondary", children: text })
302
+ ] });
303
+ }
304
+ export {
305
+ CourseCard,
306
+ EmptyState,
307
+ LoadingSpinner,
308
+ PageHeader,
309
+ Pagination,
310
+ SearchInput
311
+ };