Files
myeasycms-v2/apps/web/app/[locale]/home/[account]/files/file-upload-dialog.tsx
T. Zehetbauer bbb33aa63d
Some checks failed
Workflow / ʦ TypeScript (push) Failing after 5m43s
Workflow / ⚫️ Test (push) Has been skipped
feat: add file upload and management features; enhance pagination and permissions handling
2026-04-01 20:13:15 +02:00

185 lines
4.8 KiB
TypeScript

'use client';
import { useCallback, useRef, useState } from 'react';
import { useRouter } from 'next/navigation';
import { Upload } from 'lucide-react';
import { uploadFile } from '@kit/module-builder/actions/file-actions';
import { Button } from '@kit/ui/button';
import {
Dialog,
DialogContent,
DialogDescription,
DialogHeader,
DialogTitle,
DialogTrigger,
} from '@kit/ui/dialog';
import { useActionWithToast } from '@kit/ui/use-action-with-toast';
interface FileUploadDialogProps {
accountId: string;
}
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10 MB
const ACCEPTED_TYPES = [
'image/*',
'application/pdf',
'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
'text/csv',
'text/plain',
'application/zip',
].join(',');
function formatFileSize(bytes: number): string {
if (bytes >= 1024 * 1024) {
return `${(bytes / 1024 / 1024).toFixed(1)} MB`;
}
return `${(bytes / 1024).toFixed(1)} KB`;
}
export function FileUploadDialog({ accountId }: FileUploadDialogProps) {
const router = useRouter();
const fileInputRef = useRef<HTMLInputElement>(null);
const [open, setOpen] = useState(false);
const [selectedFile, setSelectedFile] = useState<{
name: string;
type: string;
size: number;
base64: string;
} | null>(null);
const [error, setError] = useState<string | null>(null);
const { execute, isPending } = useActionWithToast(uploadFile, {
successMessage: 'Datei hochgeladen',
onSuccess: () => {
setOpen(false);
setSelectedFile(null);
setError(null);
router.refresh();
},
});
const handleFileSelect = useCallback(
(e: React.ChangeEvent<HTMLInputElement>) => {
setError(null);
const file = e.target.files?.[0];
if (!file) {
setSelectedFile(null);
return;
}
if (file.size > MAX_FILE_SIZE) {
setError('Die Datei darf maximal 10 MB groß sein.');
setSelectedFile(null);
return;
}
const reader = new FileReader();
reader.onload = () => {
const result = reader.result as string;
// Remove the data:...;base64, prefix
const base64 = result.split(',')[1] ?? '';
setSelectedFile({
name: file.name,
type: file.type || 'application/octet-stream',
size: file.size,
base64,
});
};
reader.onerror = () => {
setError('Fehler beim Lesen der Datei.');
setSelectedFile(null);
};
reader.readAsDataURL(file);
},
[],
);
const handleUpload = useCallback(() => {
if (!selectedFile) return;
execute({
accountId,
fileName: selectedFile.name,
fileType: selectedFile.type,
fileSize: selectedFile.size,
base64: selectedFile.base64,
});
}, [accountId, execute, selectedFile]);
const handleOpenChange = useCallback(
(isOpen: boolean) => {
if (!isPending) {
setOpen(isOpen);
if (!isOpen) {
setSelectedFile(null);
setError(null);
}
}
},
[isPending],
);
return (
<Dialog open={open} onOpenChange={handleOpenChange}>
<DialogTrigger
render={
<Button size="sm" data-test="file-upload-btn">
<Upload className="mr-2 h-4 w-4" />
Datei hochladen
</Button>
}
/>
<DialogContent showCloseButton={!isPending}>
<DialogHeader>
<DialogTitle>Datei hochladen</DialogTitle>
<DialogDescription>
Wählen Sie eine Datei aus (max. 10 MB).
</DialogDescription>
</DialogHeader>
<div className="flex flex-col gap-4">
<input
ref={fileInputRef}
type="file"
accept={ACCEPTED_TYPES}
onChange={handleFileSelect}
className="file:bg-primary file:text-primary-foreground hover:file:bg-primary/90 block w-full text-sm file:mr-4 file:rounded-md file:border-0 file:px-4 file:py-2 file:text-sm file:font-medium"
data-test="file-upload-input"
/>
{error && <p className="text-destructive text-sm">{error}</p>}
{selectedFile && (
<div className="bg-muted rounded-md p-3">
<p className="text-sm font-medium">{selectedFile.name}</p>
<p className="text-muted-foreground text-xs">
{selectedFile.type} &middot; {formatFileSize(selectedFile.size)}
</p>
</div>
)}
<Button
onClick={handleUpload}
disabled={!selectedFile || isPending}
data-test="file-upload-submit"
>
{isPending ? 'Wird hochgeladen...' : 'Hochladen'}
</Button>
</div>
</DialogContent>
</Dialog>
);
}