@appcorp/shadcn 1.1.89 → 1.1.91
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/components/enhanced-dropzone.js +92 -21
- package/components/ui/resizable.d.ts +1 -1
- package/package.json +1 -1
- package/templates/data/edupilotpro-v1.d.ts +33 -0
- package/templates/data/edupilotpro-v1.js +116 -0
- package/templates/edupilotpro-v1/feature-card.d.ts +13 -0
- package/templates/edupilotpro-v1/feature-card.js +28 -0
- package/templates/edupilotpro-v1/mockups/dashboard-mockup.d.ts +3 -0
- package/templates/edupilotpro-v1/mockups/dashboard-mockup.js +55 -0
|
@@ -72,8 +72,8 @@ var EnhancedDropzone = function (_a) {
|
|
|
72
72
|
var _f = (0, react_1.useState)(false), resizeDialogOpen = _f[0], setResizeDialogOpen = _f[1];
|
|
73
73
|
var _g = (0, react_1.useState)(null), pendingFile = _g[0], setPendingFile = _g[1];
|
|
74
74
|
var _h = (0, react_1.useState)(""), pendingFileUrl = _h[0], setPendingFileUrl = _h[1];
|
|
75
|
-
// Create object URLs for local files
|
|
76
|
-
|
|
75
|
+
// Create and manage object URLs for local files
|
|
76
|
+
(0, react_1.useEffect)(function () {
|
|
77
77
|
var map = localPreviewsRef.current;
|
|
78
78
|
// Create URLs for new files
|
|
79
79
|
localFiles.forEach(function (file) {
|
|
@@ -97,8 +97,6 @@ var EnhancedDropzone = function (_a) {
|
|
|
97
97
|
}
|
|
98
98
|
});
|
|
99
99
|
}, [localFiles]);
|
|
100
|
-
// Run synchronously on every render to avoid "loading" flash
|
|
101
|
-
createObjectURLs();
|
|
102
100
|
// Cleanup on unmount
|
|
103
101
|
(0, react_1.useEffect)(function () {
|
|
104
102
|
var map = localPreviewsRef.current;
|
|
@@ -111,15 +109,38 @@ var EnhancedDropzone = function (_a) {
|
|
|
111
109
|
}, []);
|
|
112
110
|
// Cleanup pending file URL when dialog closes
|
|
113
111
|
(0, react_1.useEffect)(function () {
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
112
|
+
return function () {
|
|
113
|
+
if (pendingFileUrl) {
|
|
114
|
+
URL.revokeObjectURL(pendingFileUrl);
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
}, [pendingFileUrl]);
|
|
118
|
+
// Check if accept includes image types
|
|
119
|
+
var isImageOnly = react_1.default.useMemo(function () {
|
|
120
|
+
if (!accept)
|
|
121
|
+
return false;
|
|
122
|
+
var acceptTypes = Array.isArray(accept)
|
|
123
|
+
? accept
|
|
124
|
+
: Object.keys(accept || {});
|
|
125
|
+
return acceptTypes.some(function (type) { return typeof type === "string" && type.toLowerCase().includes("image"); });
|
|
126
|
+
}, [accept]);
|
|
127
|
+
// Convert accept array to react-dropzone format if needed
|
|
128
|
+
var normalizedAccept = react_1.default.useMemo(function () {
|
|
129
|
+
if (!accept)
|
|
130
|
+
return undefined;
|
|
131
|
+
if (Array.isArray(accept)) {
|
|
132
|
+
// Convert string array like ['image/*'] to object format { 'image/*': [] }
|
|
133
|
+
return accept.reduce(function (acc, type) {
|
|
134
|
+
acc[type] = [];
|
|
135
|
+
return acc;
|
|
136
|
+
}, {});
|
|
118
137
|
}
|
|
119
|
-
|
|
120
|
-
var dropzoneOptions = {
|
|
138
|
+
// Already in object format
|
|
121
139
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
122
|
-
|
|
140
|
+
return accept;
|
|
141
|
+
}, [accept]);
|
|
142
|
+
var dropzoneOptions = {
|
|
143
|
+
accept: normalizedAccept,
|
|
123
144
|
maxFiles: maxFiles,
|
|
124
145
|
maxSize: maxSize,
|
|
125
146
|
minSize: minSize,
|
|
@@ -169,6 +190,9 @@ var EnhancedDropzone = function (_a) {
|
|
|
169
190
|
// Handle resize dialog close
|
|
170
191
|
var handleResizeDialogClose = (0, react_1.useCallback)(function () {
|
|
171
192
|
setResizeDialogOpen(false);
|
|
193
|
+
// Cleanup will happen via the pendingFileUrl useEffect
|
|
194
|
+
setPendingFileUrl("");
|
|
195
|
+
setPendingFile(null);
|
|
172
196
|
}, []);
|
|
173
197
|
// Get all preview URLs (remote + local)
|
|
174
198
|
var allPreviews = __spreadArray(__spreadArray([], value.map(function (url) { return ({ type: "remote", url: url, index: 0 }); }), true), localFiles.map(function (file, index) { return ({
|
|
@@ -176,11 +200,36 @@ var EnhancedDropzone = function (_a) {
|
|
|
176
200
|
url: localPreviewsRef.current.get(file) || "",
|
|
177
201
|
index: index,
|
|
178
202
|
}); }), true);
|
|
179
|
-
|
|
203
|
+
// Format bytes to human-readable size
|
|
204
|
+
var formatBytes = function (bytes) {
|
|
205
|
+
if (bytes === 0)
|
|
206
|
+
return "0 B";
|
|
207
|
+
var k = 1024;
|
|
208
|
+
var sizes = ["B", "KB", "MB", "GB"];
|
|
209
|
+
var i = Math.floor(Math.log(bytes) / Math.log(k));
|
|
210
|
+
return Math.round((bytes / Math.pow(k, i)) * 100) / 100 + " " + sizes[i];
|
|
211
|
+
};
|
|
212
|
+
// Build constraint text for the note
|
|
213
|
+
var getConstraintText = function () {
|
|
214
|
+
var constraints = [];
|
|
215
|
+
if (maxFiles && maxFiles > 1) {
|
|
216
|
+
constraints.push("Up to ".concat(maxFiles, " files"));
|
|
217
|
+
}
|
|
218
|
+
if (minSize || maxSize) {
|
|
219
|
+
var sizes = [];
|
|
220
|
+
if (minSize)
|
|
221
|
+
sizes.push("min ".concat(formatBytes(minSize)));
|
|
222
|
+
if (maxSize)
|
|
223
|
+
sizes.push("max ".concat(formatBytes(maxSize)));
|
|
224
|
+
constraints.push("".concat(sizes.join(", ")));
|
|
225
|
+
}
|
|
226
|
+
return constraints.join(" • ");
|
|
227
|
+
};
|
|
228
|
+
return (react_1.default.createElement("div", { className: (0, utils_1.cn)("w-full", className), "data-slot": "enhanced-dropzone", "data-testid": testIdDropzone },
|
|
180
229
|
label && (react_1.default.createElement("label", { className: "mb-2 block text-sm font-medium" }, label)),
|
|
181
|
-
react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center min-h-
|
|
230
|
+
react_1.default.createElement("div", __assign({}, getRootProps(), { className: (0, utils_1.cn)("relative w-full rounded-md border border-dashed p-6 text-center min-h-70 flex items-center justify-center", isDragActive && "ring-2 ring-ring ring-offset-2", disabled && "opacity-60 pointer-events-none") }),
|
|
182
231
|
react_1.default.createElement("input", __assign({}, getInputProps(), { id: id, "data-testid": testIdInput })),
|
|
183
|
-
allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full" },
|
|
232
|
+
isImageOnly && allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full" },
|
|
184
233
|
react_1.default.createElement("div", { className: "relative w-full max-w-md" },
|
|
185
234
|
react_1.default.createElement(carousel_1.Carousel, { className: "w-full" },
|
|
186
235
|
react_1.default.createElement(carousel_1.CarouselPrevious, { type: "button", onClick: function (e) { return e.stopPropagation(); }, onPointerDown: function (e) { return e.stopPropagation(); }, onMouseDown: function (e) { return e.stopPropagation(); }, "data-testid": testIdPrev }),
|
|
@@ -204,6 +253,31 @@ var EnhancedDropzone = function (_a) {
|
|
|
204
253
|
" image",
|
|
205
254
|
allPreviews.length !== 1 ? "s" : "",
|
|
206
255
|
" ",
|
|
256
|
+
"selected"))) : !isImageOnly && allPreviews.length > 0 ? (react_1.default.createElement("div", { className: "flex flex-col items-center w-full gap-4" },
|
|
257
|
+
react_1.default.createElement("div", { className: "w-full max-w-md" },
|
|
258
|
+
react_1.default.createElement("div", { className: "space-y-2" }, allPreviews.map(function (preview, idx) {
|
|
259
|
+
var _a;
|
|
260
|
+
return (react_1.default.createElement("div", { key: "".concat(preview.type, "-").concat(preview.url, "-").concat(idx), "data-testid": "".concat(testIdPreviewPrefix !== null && testIdPreviewPrefix !== void 0 ? testIdPreviewPrefix : "dropzone-preview", "-").concat(idx), className: "flex items-center justify-between gap-3 p-3 rounded-lg border bg-muted/50" },
|
|
261
|
+
react_1.default.createElement("span", { className: "text-sm truncate flex-1", "data-testid": "".concat(testIdImagePrefix !== null && testIdImagePrefix !== void 0 ? testIdImagePrefix : "dropzone-image", "-").concat(idx) }, preview.type === "remote"
|
|
262
|
+
? new URL(preview.url).pathname.split("/").pop() ||
|
|
263
|
+
"File"
|
|
264
|
+
: ((_a = localFiles[preview.index]) === null || _a === void 0 ? void 0 : _a.name) || "File"),
|
|
265
|
+
react_1.default.createElement(button_1.Button, { type: "button", size: "icon", variant: "ghost", onClick: function (e) {
|
|
266
|
+
e.stopPropagation();
|
|
267
|
+
if (preview.type === "remote") {
|
|
268
|
+
handleRemoveRemote(preview.url);
|
|
269
|
+
}
|
|
270
|
+
else {
|
|
271
|
+
handleRemoveLocal(preview.index);
|
|
272
|
+
}
|
|
273
|
+
}, className: "h-8 w-8 shrink-0", "aria-label": "Remove file", "data-testid": "".concat(testIdRemovePrefix !== null && testIdRemovePrefix !== void 0 ? testIdRemovePrefix : "dropzone-remove", "-").concat(idx) },
|
|
274
|
+
react_1.default.createElement(lucide_react_1.XIcon, { className: "h-4 w-4" }))));
|
|
275
|
+
}))),
|
|
276
|
+
react_1.default.createElement("p", { className: "text-sm font-medium text-muted-foreground", "data-testid": testIdCount },
|
|
277
|
+
allPreviews.length,
|
|
278
|
+
" file",
|
|
279
|
+
allPreviews.length !== 1 ? "s" : "",
|
|
280
|
+
" ",
|
|
207
281
|
"selected"))) : (react_1.default.createElement("div", { className: "flex flex-col items-center justify-center gap-2" },
|
|
208
282
|
react_1.default.createElement("div", { className: "flex h-12 w-12 items-center justify-center rounded-lg bg-muted text-muted-foreground", "data-testid": testIdEmptyIcon },
|
|
209
283
|
react_1.default.createElement("svg", { xmlns: "http://www.w3.org/2000/svg", width: "24", height: "24", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: "lucide lucide-image" },
|
|
@@ -211,14 +285,11 @@ var EnhancedDropzone = function (_a) {
|
|
|
211
285
|
react_1.default.createElement("circle", { cx: "9", cy: "9", r: "2" }),
|
|
212
286
|
react_1.default.createElement("path", { d: "m21 15-3.086-3.086a2 2 0 0 0-2.828 0L6 21" }))),
|
|
213
287
|
react_1.default.createElement("div", { className: "space-y-1 text-center" },
|
|
214
|
-
react_1.default.createElement("p", { className: "font-medium text-sm", "data-testid": testIdEmptyTitle },
|
|
215
|
-
"Upload "
|
|
216
|
-
|
|
288
|
+
react_1.default.createElement("p", { className: "font-medium text-sm", "data-testid": testIdEmptyTitle }, maxFiles === 1
|
|
289
|
+
? "Upload ".concat(isImageOnly ? "an image" : "a file")
|
|
290
|
+
: "Upload ".concat(isImageOnly ? "images" : "files")),
|
|
217
291
|
react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptySubtitle }, "Drag and drop or click to browse"),
|
|
218
|
-
maxFiles > 1 && (react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptyNote },
|
|
219
|
-
"Up to ",
|
|
220
|
-
maxFiles,
|
|
221
|
-
" files")))))),
|
|
292
|
+
(maxFiles > 1 || minSize || maxSize) && (react_1.default.createElement("p", { className: "text-muted-foreground text-xs", "data-testid": testIdEmptyNote }, getConstraintText())))))),
|
|
222
293
|
(error || info) && (react_1.default.createElement("div", { className: "mt-2", "data-testid": testIdMessage }, error ? (react_1.default.createElement("p", { className: "text-xs text-destructive" }, error)) : info ? (react_1.default.createElement("p", { className: "text-xs text-blue-600 dark:text-blue-400" }, info)) : null)),
|
|
223
294
|
resize && pendingFile && (react_1.default.createElement(image_resize_dialog_1.ImageResizeDialog, { open: resizeDialogOpen, onClose: handleResizeDialogClose, imageUrl: pendingFileUrl, onCropComplete: handleCropComplete, fileName: pendingFile.name }))));
|
|
224
295
|
};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import React from "react";
|
|
2
2
|
import * as ResizablePrimitive from "react-resizable-panels";
|
|
3
3
|
declare const ResizablePanelGroup: ({ className, ...props }: React.ComponentProps<typeof ResizablePrimitive.PanelGroup>) => React.JSX.Element;
|
|
4
|
-
declare const ResizablePanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement | HTMLButtonElement |
|
|
4
|
+
declare const ResizablePanel: React.ForwardRefExoticComponent<Omit<React.HTMLAttributes<HTMLElement | HTMLButtonElement | HTMLObjectElement | HTMLSlotElement | HTMLStyleElement | HTMLTitleElement | HTMLInputElement | HTMLSpanElement | HTMLAnchorElement | HTMLAreaElement | HTMLAudioElement | HTMLBaseElement | HTMLQuoteElement | HTMLBodyElement | HTMLBRElement | HTMLCanvasElement | HTMLTableColElement | HTMLDataElement | HTMLDataListElement | HTMLModElement | HTMLDetailsElement | HTMLDialogElement | HTMLDivElement | HTMLDListElement | HTMLEmbedElement | HTMLFieldSetElement | HTMLFormElement | HTMLHeadingElement | HTMLHeadElement | HTMLHRElement | HTMLHtmlElement | HTMLIFrameElement | HTMLImageElement | HTMLLabelElement | HTMLLegendElement | HTMLLIElement | HTMLLinkElement | HTMLMapElement | HTMLMetaElement | HTMLMeterElement | HTMLOListElement | HTMLOptGroupElement | HTMLOptionElement | HTMLOutputElement | HTMLParagraphElement | HTMLPreElement | HTMLProgressElement | HTMLScriptElement | HTMLSelectElement | HTMLSourceElement | HTMLTableElement | HTMLTemplateElement | HTMLTableSectionElement | HTMLTableCellElement | HTMLTextAreaElement | HTMLTimeElement | HTMLTableRowElement | HTMLTrackElement | HTMLUListElement | HTMLVideoElement | HTMLTableCaptionElement | HTMLMenuElement | HTMLPictureElement>, "id" | "onResize"> & {
|
|
5
5
|
className?: string;
|
|
6
6
|
collapsedSize?: number | undefined;
|
|
7
7
|
collapsible?: boolean | undefined;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@appcorp/shadcn",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.91",
|
|
4
4
|
"scripts": {
|
|
5
5
|
"build:next": "next build",
|
|
6
6
|
"build:storybook": "mv ../.pnp.cjs ../.pnp.cjs.bak 2>/dev/null || true && storybook build -c .storybook -o .out && mv ../.pnp.cjs.bak ../.pnp.cjs 2>/dev/null || true",
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
export interface DashboardMockupProps {
|
|
2
|
+
domain: string;
|
|
3
|
+
enrollmentHeading: string;
|
|
4
|
+
enrollmentList: {
|
|
5
|
+
name: string;
|
|
6
|
+
class: string;
|
|
7
|
+
status: string;
|
|
8
|
+
}[];
|
|
9
|
+
items: {
|
|
10
|
+
icon: React.ElementType;
|
|
11
|
+
label: string;
|
|
12
|
+
active: boolean;
|
|
13
|
+
}[];
|
|
14
|
+
schoolName: string;
|
|
15
|
+
stats: {
|
|
16
|
+
label: string;
|
|
17
|
+
value: string;
|
|
18
|
+
color: string;
|
|
19
|
+
}[];
|
|
20
|
+
}
|
|
21
|
+
export declare const dashboardMockupMenuList: DashboardMockupProps;
|
|
22
|
+
export interface FeatureCardsProps {
|
|
23
|
+
items: {
|
|
24
|
+
icon: React.ElementType;
|
|
25
|
+
title: string;
|
|
26
|
+
description: string;
|
|
27
|
+
highlights: string[];
|
|
28
|
+
href: string;
|
|
29
|
+
accent: string;
|
|
30
|
+
bg: string;
|
|
31
|
+
}[];
|
|
32
|
+
}
|
|
33
|
+
export declare const featureCards: FeatureCardsProps;
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.featureCards = exports.dashboardMockupMenuList = void 0;
|
|
4
|
+
var lucide_react_1 = require("lucide-react");
|
|
5
|
+
exports.dashboardMockupMenuList = {
|
|
6
|
+
domain: "subdomain.edupilotpro.com",
|
|
7
|
+
items: [
|
|
8
|
+
{ icon: lucide_react_1.LayoutDashboard, label: "Dashboard", active: true },
|
|
9
|
+
{ icon: lucide_react_1.Users, label: "Students", active: false },
|
|
10
|
+
{ icon: lucide_react_1.BookOpen, label: "Courses", active: false },
|
|
11
|
+
{ icon: lucide_react_1.Calendar, label: "Attendance", active: false },
|
|
12
|
+
{ icon: lucide_react_1.Receipt, label: "Finance", active: false },
|
|
13
|
+
{ icon: lucide_react_1.BarChart3, label: "Analytics", active: false },
|
|
14
|
+
],
|
|
15
|
+
schoolName: "Greenwood Academy",
|
|
16
|
+
stats: [
|
|
17
|
+
{ label: "Students", value: "412", color: "text-blue-600" },
|
|
18
|
+
{ label: "Teachers", value: "34", color: "text-purple-600" },
|
|
19
|
+
{ label: "Attendance", value: "97%", color: "text-green-700" },
|
|
20
|
+
{ label: "Fees Due", value: "$4.2k", color: "text-rose-600" },
|
|
21
|
+
],
|
|
22
|
+
enrollmentHeading: "Recent Enrollments",
|
|
23
|
+
enrollmentList: [
|
|
24
|
+
{ name: "Aisha Patel", class: "Grade 7A", status: "Active" },
|
|
25
|
+
{ name: "Omar Faruk", class: "Grade 5B", status: "Active" },
|
|
26
|
+
{ name: "Lena Müller", class: "Grade 9C", status: "Pending" },
|
|
27
|
+
],
|
|
28
|
+
};
|
|
29
|
+
exports.featureCards = {
|
|
30
|
+
items: [
|
|
31
|
+
{
|
|
32
|
+
icon: lucide_react_1.Users,
|
|
33
|
+
title: "AI Admission Assistant",
|
|
34
|
+
description: "Automate your entire admissions pipeline. The AI Admission Assistant captures enquiries, collects documents, and moves students from application to enrolled — with no manual follow-up from your team.",
|
|
35
|
+
highlights: [
|
|
36
|
+
"Digital admission forms & document collection",
|
|
37
|
+
"AI-guided enrolment workflows",
|
|
38
|
+
"Family portal with sibling linking",
|
|
39
|
+
"Searchable student & family database",
|
|
40
|
+
],
|
|
41
|
+
accent: "text-blue-700",
|
|
42
|
+
bg: "bg-blue-50 dark:bg-blue-950/30",
|
|
43
|
+
href: "/features/student-family",
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
icon: lucide_react_1.BookOpen,
|
|
47
|
+
title: "Academic Structure Engine",
|
|
48
|
+
description: "Configure your school's full academic hierarchy — year groups, sections, subjects, courses — once. Every module (attendance, grades, timetables) inherits this structure automatically.",
|
|
49
|
+
highlights: [
|
|
50
|
+
"Multi-level class & section hierarchy",
|
|
51
|
+
"Subject catalogue & course management",
|
|
52
|
+
"Teacher assignments & academic year config",
|
|
53
|
+
"Supports all curriculum frameworks",
|
|
54
|
+
],
|
|
55
|
+
accent: "text-purple-700",
|
|
56
|
+
bg: "bg-purple-50 dark:bg-purple-950/30",
|
|
57
|
+
href: "/features/academic-structure",
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
icon: lucide_react_1.Calendar,
|
|
61
|
+
title: "AI Attendance Assistant",
|
|
62
|
+
description: "Teachers mark an entire class present in one tap. The AI Attendance Assistant records every entry, detects chronic absence patterns, and automatically notifies parents — all without lifting a finger.",
|
|
63
|
+
highlights: [
|
|
64
|
+
"One-tap bulk attendance marking",
|
|
65
|
+
"AI-powered absence pattern detection",
|
|
66
|
+
"Instant automated parent notifications",
|
|
67
|
+
"Conflict-free visual timetable builder",
|
|
68
|
+
],
|
|
69
|
+
accent: "text-green-700",
|
|
70
|
+
bg: "bg-green-50 dark:bg-green-950/30",
|
|
71
|
+
href: "/features/attendance-schedules",
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
icon: lucide_react_1.BarChart3,
|
|
75
|
+
title: "AI Reporting Assistant",
|
|
76
|
+
description: "From test scores to polished report cards in one click. The AI Reporting Assistant calculates GPA, ranks students, flags underperformers, and generates print-ready PDFs at the end of every term.",
|
|
77
|
+
highlights: [
|
|
78
|
+
"Automatic GPA & class rank calculation",
|
|
79
|
+
"AI-flagged underperformance alerts",
|
|
80
|
+
"One-click PDF report card generation",
|
|
81
|
+
"Multi-term performance trend analytics",
|
|
82
|
+
],
|
|
83
|
+
accent: "text-orange-700",
|
|
84
|
+
bg: "bg-orange-50 dark:bg-orange-950/30",
|
|
85
|
+
href: "/features/grades-assessments",
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
icon: lucide_react_1.CreditCard,
|
|
89
|
+
title: "AI Fee Reminder Automation",
|
|
90
|
+
description: "Build your fee structures once. EduPilotPro auto-generates invoices every term, sends escalating payment reminders automatically, and keeps your outstanding fee dashboard updated in real time.",
|
|
91
|
+
highlights: [
|
|
92
|
+
"Automated termly invoice generation",
|
|
93
|
+
"AI fee reminder sequences to parents",
|
|
94
|
+
"Real-time outstanding balance tracking",
|
|
95
|
+
"Discount, scholarship & exemption management",
|
|
96
|
+
],
|
|
97
|
+
accent: "text-rose-700",
|
|
98
|
+
bg: "bg-rose-50 dark:bg-rose-950/30",
|
|
99
|
+
href: "/features/financial-management",
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
icon: lucide_react_1.Shield,
|
|
103
|
+
title: "Smart Role-Based Access",
|
|
104
|
+
description: "Every staff member, teacher, and parent sees exactly what they need. Five permission levels ensure data is protected, workflows are clean, and nothing leaks between roles.",
|
|
105
|
+
highlights: [
|
|
106
|
+
"5-tier role hierarchy (Admin → Parent)",
|
|
107
|
+
"Staff & workspace user management",
|
|
108
|
+
"Audit-ready access and activity logs",
|
|
109
|
+
"Secure, GDPR-compliant authentication",
|
|
110
|
+
],
|
|
111
|
+
accent: "text-teal-700",
|
|
112
|
+
bg: "bg-teal-50 dark:bg-teal-950/30",
|
|
113
|
+
href: "/features/access-control",
|
|
114
|
+
},
|
|
115
|
+
],
|
|
116
|
+
};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import React, { FC } from "react";
|
|
2
|
+
export interface FeatureCardProps {
|
|
3
|
+
accent: string;
|
|
4
|
+
bg: string;
|
|
5
|
+
description: string;
|
|
6
|
+
highlights: string[];
|
|
7
|
+
href: string;
|
|
8
|
+
Icon: React.ComponentType<{
|
|
9
|
+
className?: string;
|
|
10
|
+
}>;
|
|
11
|
+
title: string;
|
|
12
|
+
}
|
|
13
|
+
export declare const FeatureCard: FC<FeatureCardProps>;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.FeatureCard = void 0;
|
|
7
|
+
var react_1 = __importDefault(require("react"));
|
|
8
|
+
var link_1 = __importDefault(require("next/link"));
|
|
9
|
+
var lucide_react_1 = require("lucide-react");
|
|
10
|
+
var card_1 = require("../../components/ui/card");
|
|
11
|
+
var FeatureCard = function (_a) {
|
|
12
|
+
var accent = _a.accent, bg = _a.bg, description = _a.description, highlights = _a.highlights, href = _a.href, Icon = _a.Icon, title = _a.title;
|
|
13
|
+
return (react_1.default.createElement(link_1.default, { key: title, href: href, prefetch: false, className: "group flex" },
|
|
14
|
+
react_1.default.createElement(card_1.Card, { className: "flex w-full flex-col border-border/60 transition-all hover:shadow-lg hover:border-primary/40" },
|
|
15
|
+
react_1.default.createElement(card_1.CardHeader, { className: "pb-3" },
|
|
16
|
+
react_1.default.createElement("div", { className: "mb-4 inline-flex h-11 w-11 items-center justify-center rounded-xl ".concat(bg) },
|
|
17
|
+
react_1.default.createElement(Icon, { className: "h-5 w-5 ".concat(accent) })),
|
|
18
|
+
react_1.default.createElement(card_1.CardTitle, { className: "text-lg" }, title)),
|
|
19
|
+
react_1.default.createElement(card_1.CardContent, { className: "flex flex-col flex-1 gap-4" },
|
|
20
|
+
react_1.default.createElement("p", { className: "text-sm text-muted-foreground leading-relaxed" }, description),
|
|
21
|
+
react_1.default.createElement("ul", { className: "mt-auto space-y-1.5" }, highlights.map(function (h) { return (react_1.default.createElement("li", { key: h, className: "flex items-start gap-2 text-sm text-foreground" },
|
|
22
|
+
react_1.default.createElement(lucide_react_1.CheckCircle2, { className: "mt-0.5 h-4 w-4 shrink-0 ".concat(accent) }),
|
|
23
|
+
h)); })),
|
|
24
|
+
react_1.default.createElement("div", { className: "mt-2 flex items-center gap-1 text-xs font-semibold ".concat(accent) },
|
|
25
|
+
"Learn more",
|
|
26
|
+
react_1.default.createElement(lucide_react_1.ArrowRight, { className: "h-3.5 w-3.5 transition-transform group-hover:translate-x-0.5" }))))));
|
|
27
|
+
};
|
|
28
|
+
exports.FeatureCard = FeatureCard;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.DashboardMockup = void 0;
|
|
7
|
+
var react_1 = __importDefault(require("react"));
|
|
8
|
+
var lucide_react_1 = require("lucide-react");
|
|
9
|
+
var DashboardMockup = function (_a) {
|
|
10
|
+
var domain = _a.domain, enrollmentHeading = _a.enrollmentHeading, enrollmentList = _a.enrollmentList, items = _a.items, schoolName = _a.schoolName, stats = _a.stats;
|
|
11
|
+
return (react_1.default.createElement("div", { className: "relative mx-auto w-full max-w-3xl" },
|
|
12
|
+
react_1.default.createElement("div", { className: "absolute inset-0 -z-10 scale-95 blur-3xl opacity-30 bg-gradient-to-br from-primary via-purple-500 to-blue-500 rounded-2xl" }),
|
|
13
|
+
react_1.default.createElement("div", { className: "rounded-xl border border-border/60 bg-background shadow-2xl overflow-hidden" },
|
|
14
|
+
react_1.default.createElement("div", { className: "flex items-center gap-2 border-b border-border/60 bg-muted/50 px-4 py-3" },
|
|
15
|
+
react_1.default.createElement("span", { className: "h-3 w-3 rounded-full bg-red-400" }),
|
|
16
|
+
react_1.default.createElement("span", { className: "h-3 w-3 rounded-full bg-yellow-400" }),
|
|
17
|
+
react_1.default.createElement("span", { className: "h-3 w-3 rounded-full bg-green-400" }),
|
|
18
|
+
react_1.default.createElement("div", { className: "ml-3 flex-1 rounded-md bg-background/70 border border-border/40 px-3 py-1 text-xs text-muted-foreground" }, domain)),
|
|
19
|
+
react_1.default.createElement("div", { className: "flex h-[340px] sm:h-[380px]" },
|
|
20
|
+
react_1.default.createElement("div", { className: "hidden w-44 flex-shrink-0 border-r border-border/40 bg-muted/30 p-3 sm:block" },
|
|
21
|
+
react_1.default.createElement("div", { className: "mb-4 flex items-center gap-2" },
|
|
22
|
+
react_1.default.createElement("span", { className: "flex h-6 w-6 items-center justify-center rounded bg-primary text-primary-foreground" },
|
|
23
|
+
react_1.default.createElement(lucide_react_1.GraduationCap, { className: "h-4 w-4" })),
|
|
24
|
+
react_1.default.createElement("span", { className: "text-xs font-bold" }, "EduPilotPro")),
|
|
25
|
+
items.map(function (_a) {
|
|
26
|
+
var Icon = _a.icon, label = _a.label, active = _a.active;
|
|
27
|
+
return (react_1.default.createElement("div", { key: label, className: "mb-1 flex items-center gap-2 rounded-md px-2 py-1.5 text-xs ".concat(active
|
|
28
|
+
? "bg-primary/10 text-primary font-semibold"
|
|
29
|
+
: "text-muted-foreground") },
|
|
30
|
+
react_1.default.createElement(Icon, { className: "h-3.5 w-3.5 shrink-0" }),
|
|
31
|
+
label));
|
|
32
|
+
})),
|
|
33
|
+
react_1.default.createElement("div", { className: "flex-1 overflow-hidden p-4" },
|
|
34
|
+
react_1.default.createElement("p", { className: "mb-3 text-sm font-semibold text-foreground" },
|
|
35
|
+
"Overview \u2014 ",
|
|
36
|
+
schoolName),
|
|
37
|
+
react_1.default.createElement("div", { className: "grid grid-cols-2 gap-2 sm:grid-cols-4" }, stats.map(function (_a) {
|
|
38
|
+
var label = _a.label, value = _a.value, color = _a.color;
|
|
39
|
+
return (react_1.default.createElement("div", { key: label, className: "rounded-lg border border-border/50 bg-card p-3" },
|
|
40
|
+
react_1.default.createElement("p", { className: "text-[10px] text-muted-foreground" }, label),
|
|
41
|
+
react_1.default.createElement("p", { className: "text-lg font-bold ".concat(color) }, value)));
|
|
42
|
+
})),
|
|
43
|
+
react_1.default.createElement("div", { className: "mt-3 rounded-lg border border-border/50 bg-card p-3" },
|
|
44
|
+
react_1.default.createElement("p", { className: "mb-2 text-xs font-semibold text-foreground" }, enrollmentHeading),
|
|
45
|
+
react_1.default.createElement("div", { className: "space-y-1.5" }, enrollmentList.map(function (_a) {
|
|
46
|
+
var name = _a.name, cls = _a.class, status = _a.status;
|
|
47
|
+
return (react_1.default.createElement("div", { key: name, className: "flex items-center justify-between text-[10px]" },
|
|
48
|
+
react_1.default.createElement("span", { className: "font-medium text-foreground" }, name),
|
|
49
|
+
react_1.default.createElement("span", { className: "text-muted-foreground" }, cls),
|
|
50
|
+
react_1.default.createElement("span", { className: "rounded-full px-1.5 py-0.5 text-[9px] font-medium ".concat(status === "Active"
|
|
51
|
+
? "bg-green-100 text-green-700"
|
|
52
|
+
: "bg-yellow-100 text-yellow-700") }, status)));
|
|
53
|
+
}))))))));
|
|
54
|
+
};
|
|
55
|
+
exports.DashboardMockup = DashboardMockup;
|