chore: heygermany customer project
This commit is contained in:
170
apps/website/components/ui/FileUpload.tsx
Normal file
170
apps/website/components/ui/FileUpload.tsx
Normal file
@@ -0,0 +1,170 @@
|
||||
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>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user