Files
e2e 2c4174b44b
Some checks failed
Main / Setup and Test (push) Has been cancelled
Main / Build Website (push) Has been cancelled
chore: heygermany customer project
2026-07-11 11:13:08 +02:00

171 lines
5.9 KiB
TypeScript

import { useState } from 'react';
import { Dropzone, MIME_TYPES, FileRejection } from '@mantine/dropzone';
import { Button, Box, Text, Stack, Group, ActionIcon, Paper, Alert } from '@pikku/mantine/core';
import { IconX, IconPlus } from '@tabler/icons-react';
import { useI18n } from '@/context/i18n-provider';
import { asI18n } from '@pikku/react';
type UploadedFile = {
id: string
fileName: string
}
export type UploadFile = (files: File[]) => Promise<void>
export type DeleteFile = (id: string) => Promise<void>
interface FileUploadProps {
multiple?: boolean;
accept?: string;
maxFileSize?: number;
chooseLabel?: string;
className?: string;
files: UploadedFile[]
uploadFiles: UploadFile
deleteFile: DeleteFile
}
export const FileUpload: React.FunctionComponent<FileUploadProps> = ({
multiple = true,
accept = ".pdf,.jpg,.jpeg,.png",
maxFileSize = 210000000,
chooseLabel,
files = [],
uploadFiles,
deleteFile
}) => {
const t = useI18n()
const [uploading, setUploading] = useState(false);
const [rejectionError, setRejectionError] = useState<string | null>(null);
const defaultChooseLabel = chooseLabel ? asI18n(chooseLabel) : t('jobs.fileupload.choosefile')
const handleUpload = async (files: File[]) => {
setRejectionError(null); // Clear any previous errors
setUploading(true);
try {
await uploadFiles(files)
} catch (error) {
console.error('Error uploading files:', error)
}
setUploading(false);
}
const handleReject = (rejectedFiles: FileRejection[]) => {
if (rejectedFiles.length > 0) {
const firstError = rejectedFiles[0].errors[0];
let errorMessage: string;
switch (firstError.code) {
case 'file-invalid-type':
errorMessage = t('jobs.fileupload.error.file-invalid-type');
break;
case 'file-too-large':
errorMessage = t('jobs.fileupload.error.file-too-large');
break;
case 'file-too-small':
errorMessage = t('jobs.fileupload.error.file-too-small');
break;
case 'too-many-files':
errorMessage = t('jobs.fileupload.error.too-many-files');
break;
default:
errorMessage = firstError.message;
}
setRejectionError(errorMessage);
}
}
const handleDelete = async (file: UploadedFile) => {
try {
await deleteFile(file.id)
} catch (error) {
console.error('Error deleting file:', error)
}
}
const acceptTypes = accept.split(',').map(type => {
switch (type.trim()) {
case '.pdf': return MIME_TYPES.pdf;
case '.jpg':
case '.jpeg': return MIME_TYPES.jpeg;
case '.png': return MIME_TYPES.png;
default: return type.trim();
}
});
return (
<Paper p="xl" withBorder>
<Box pos="relative">
<Dropzone
multiple={multiple}
accept={acceptTypes}
maxSize={maxFileSize}
onDrop={handleUpload}
onReject={handleReject}
loading={uploading}
bd='none'
bg='transparent'
p={0}
>
<Stack gap="md">
<Button
size="md"
loading={uploading}
leftSection={<IconPlus size={16} />}
>
{defaultChooseLabel}
</Button>
{!files || files.length === 0 ? (
<Stack gap="xs">
<Text size="md" fw={500}>{t('jobs.fileupload.dragdroptext')}</Text>
<Text size="sm" c="dimmed">
{t('jobs.fileupload.acceptedformats')}
<br />
{t('jobs.fileupload.maxsize')}
</Text>
</Stack>
) : null}
</Stack>
</Dropzone>
</Box>
{files && files.length > 0 && (
<Stack gap="sm" mt="md">
{files.map((file) => (
<Group key={file.id} justify="space-between" align="center">
<Text
fw={500}
c="dark"
maw={250}
style={{
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
>
{asI18n(file.fileName)}
</Text>
<ActionIcon
variant="subtle"
color="red"
size="sm"
onClick={() => handleDelete(file)}
aria-label={t('jobs.fileupload.delete')}
>
<IconX size={16} />
</ActionIcon>
</Group>
))}
</Stack>
)}
{rejectionError && (
<Alert color="red" mt="md" onClose={() => setRejectionError(null)} withCloseButton>
{asI18n(rejectionError)}
</Alert>
)}
</Paper>
);
}