- Introduction
- Pitch
- Hogwarts
- Live Demo
- MVP
- Roadmap
- Launch Sprint
- PRD
- Get Started
- Localhost
- Architecture
- Structure
- Pattern
- Page
- Layout
- Content
- Types
- Config
- Actions
- Queries
- Authorization
- Validation
- Form
- Table
- Detail
- Card
- Util
- Hooks
- List Params
- Views
- README.md
- ISSUE.md
- Technology Stack
- Database
- File
- CDN Assets
- Entry Points
- Dashboard
- Authentication
- Credentials
- OAuth
- Flow Diagrams
- Multi-Tenancy
- Offline
- Onboarding
- Onboarding Videos
- Add Values
- Admission
- Application
- Attendance
- Compliance
- Profile
- Exams
- Exam Wizard
- Timetable
- Classrooms
- Notifications
- Conference
- LMS (Lumos)
- Finance
- Fee Management
- Invoice
- Wallet
- Salary
- Payroll
- Timesheet
- Expenses
- Budget
- Receipt
- Accounts
- Banking
- Reports
- Dashboard
- Permissions
- Messages
- Integration Flow
- Provision
- AI Document Processing
- Document Intelligence
- Internationalization
- Translation
- Translation Guide
- Icons
- Docs Factory
- Inspiration
- Listings
- Teachers
- Students
- Catalog
- Library
- Contributing
- Code of conduct
- GitHub Workflow
- Database Seeds
- Database Safety
- Test Accounts
- Playwright
- Prettier
- Block Rebound
The @/components/file module consolidates every file-related concern into one place. Six sub-modules, one import surface.
| Sub-module | Purpose |
|---|---|
| Upload | Cloud-based file upload with drag-and-drop and progress tracking |
| Browser | File manager UI with grid / list views |
| Export | Generate PDF, Excel, CSV, JSON from data |
| Import | Parse CSV / Excel with a validation wizard |
| Browser-native print and print-to-PDF | |
| Generate | Templated documents — invoice, receipt, certificate, report card, ID card, transcript |
Quick start
import {
Uploader,
ExportButton,
Importer,
PrintButton,
FileBrowser,
useUpload,
useExport,
useGenerate,
} from "@/components/file"
<Uploader folder="documents" category="document" onUpload={(urls) => console.log(urls)} />
<ExportButton
data={students}
config={{ columns: STUDENT_EXPORT_COLUMNS, filename: "students" }}
formats={["csv", "excel", "pdf"]}
/>
<Importer columns={STUDENT_IMPORT_COLUMNS} onImport={handleImport} />
<PrintButton contentRef={printRef}>Print Report</PrintButton>
<FileBrowser rootFolder="/school/documents" onSelect={handleSelect} />Upload
The Uploader component renders a drag-and-drop dropzone backed by S3 signed URLs.
<Uploader
folder="avatars"
category="image"
accept={{ "image/*": [".jpg", ".png", ".webp"] }}
maxSize={5 * 1024 * 1024}
maxFiles={1}
onUpload={(urls) => setAvatarUrl(urls[0])}
onError={(error) => toast.error(error)}
/>Presets
Ready-to-use configurations live alongside the Uploader: AvatarUploader, DocumentUploader, ImageUploader, VideoUploader. Each presets accept, maxSize, and category for the common shapes. Read source for exact props.
useUpload hook
The hook exposes the same upload pipeline imperatively (no UI), useful for custom triggers.
const { upload, progress, isUploading, error } = useUpload({
folder: "documents",
category: "document",
})
await upload(file) // returns the public URLServer actions
Two actions back the module:
| Action | Purpose |
|---|---|
getSignedUrl() | Mint a presigned PUT URL for the client to upload to S3. |
confirmUpload() | Persist the resulting FileObject row keyed by schoolId. |
Both validate auth() + getTenantContext() and write schoolId into every record.
Export
ExportButton opens a format picker (CSV, Excel, PDF, JSON) and triggers download.
<ExportButton
data={students}
config={{
columns: STUDENT_EXPORT_COLUMNS,
filename: "students",
title: "Student Roster",
}}
formats={["csv", "excel", "pdf"]}
/>For inline use without a picker, SimpleExportButton accepts a single format.
useExport hook
const { exportData, isExporting } = useExport()
await exportData({
format: "pdf",
data: students,
config: { columns: STUDENT_EXPORT_COLUMNS, filename: "students" },
})Columns
Per-feature column definitions live alongside the feature (e.g. students/columns-export.ts). They build on createColumn(key, label, options) helpers from @/components/file/export/columns. Common options: format, width, align, accessor.
Import
Importer renders a four-step wizard: upload → preview → map columns → validate.
<Importer
columns={STUDENT_IMPORT_COLUMNS}
template={{ filename: "students-template.xlsx", sheets: ["Students"] }}
onImport={async (rows) => {
const result = await bulkImportStudents(rows)
toast.success(`Imported ${result.created} of ${rows.length}`)
}}
/>useImport hook
const { parse, validate, isImporting } = useImport()
const rows = await parse(file)
const issues = await validate(rows, columns)Validation
validateRow(row, columns) checks every column's required, type, and custom validate(value, row). validateBatch(rows, columns) returns a per-row issue list and a totals summary.
PrintButton opens the browser print dialog scoped to a content ref.
const printRef = useRef<HTMLDivElement>(null)
<PrintButton contentRef={printRef} title="Student Report">
Print
</PrintButton>
<div ref={printRef}>{/* printable content */}</div>usePrint hook
const { print } = usePrint()
print(printRef.current, { title: "Student Report" })Generate
Pre-built templates render PDFs via @react-pdf/renderer. All return a Blob and a download trigger.
| Template | Helper | Use |
|---|---|---|
| Invoice | useGenerate.invoice() | Fee invoices, receipts |
| Receipt | useGenerate.receipt() | Payment confirmation |
| Certificate | useGenerate.certificate() | Course or achievement certificates |
| Report card | useGenerate.reportCard() | Term and annual reports |
| ID card | useGenerate.idCard() | Student / staff badges |
| Transcript | useGenerate.transcript() | Academic transcripts |
const { invoice } = useGenerate()
const blob = await invoice({ schoolId, items, customer, total })Each template accepts a typed payload and renders both a downloadable PDF and an inline preview component (InvoicePreview, etc.).
Browser
FileBrowser renders a tree-aware file manager rooted at any S3 prefix.
<FileBrowser
rootFolder={`/${schoolId}/documents`}
onSelect={(file) => setSelected(file)}
view="grid"
/>useBrowser hook
const { files, currentFolder, navigate, search } = useBrowser({
rootFolder: `/${schoolId}/documents`,
})Storage and limits
| Constraint | Default |
|---|---|
| Provider | AWS S3 + CloudFront CDN |
| Default size cap | 10 MB per file (per category override) |
| Image cap | 5 MB |
| Document cap | 25 MB |
| Video cap | 100 MB |
| Allowed types | Per-category whitelist; rejected uploads return error code |
Multi-tenant safety
Every persisted file row carries schoolId. Signed URLs include a schoolId claim so cross-tenant URL forging is impossible. FileBrowser always rooted at a schoolId-scoped prefix.
// correct — schoolId scope on every query
await db.fileObject.findMany({ where: { schoolId, folder } })i18n
UI text comes from the dictionary.file slice. Helpers (getDictionary server-side, useI18nMessages on the client) provide the full set of labels for the picker, the wizard, error toasts, and progress states.
Errors
Server actions return error codes; the client maps them via useI18nMessages(dictionary).error.
| Code | Meaning |
|---|---|
FILE_TOO_LARGE | Exceeds the per-category size cap |
INVALID_FILE_TYPE | MIME type not in the category whitelist |
UPLOAD_FAILED | S3 returned non-2xx |
MISSING_TENANT | No schoolId on the session |
UNAUTHORIZED | auth() check failed |
INVALID_FOLDER | Folder path outside the schoolId-scoped tree |
TypeScript
All public exports are fully typed. Common types:
import type {
ExportConfig,
FileObject,
GenerateConfig,
ImportConfig,
PrintConfig,
UploadConfig,
} from "@/components/file"Migration from the old layout
Older code imported from @/components/upload, @/components/export, @/components/import, etc. Migrate to a single import:
// before
import { ExportButton } from "@/components/export"
// after
import { ExportButton, Uploader } from "@/components/file"
import { Uploader } from "@/components/upload"The old module paths still re-export for compatibility but are deprecated. Open issues for any direct imports still in the wild.