173 lines
5.9 KiB
TypeScript
173 lines
5.9 KiB
TypeScript
'use client'
|
|
|
|
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>
|
|
);
|
|
}
|