mirror of
https://github.com/Tria-plc/edr-platform.git
synced 2026-08-30 01:48:12 +00:00
- Introduced StampUpload component for uploading company stamp images. - Integrated stamp upload in contract signing modal, supporting PNG and JPG formats. - Implemented validation for file type and size (max 5 MB). - Added visual feedback for drag-and-drop functionality. - Updated contract-related pages to handle duplicate contract alerts and pricing notices. - Enhanced contract expiry management with a nightly sweep service. - Added unit tests for new features and updated existing tests for contract handling.
172 lines
5.0 KiB
TypeScript
172 lines
5.0 KiB
TypeScript
import { useRef, useState } from "react";
|
|
import { Box, Button, Group, Image, Paper, Stack, Text } from "@mantine/core";
|
|
import { RefreshCw, Stamp, X } from "lucide-react";
|
|
|
|
const MAX_STAMP_MB = 5;
|
|
|
|
export interface StampUploadProps {
|
|
/** Stamp image as a data URL, or null when none is attached yet. */
|
|
value: string | null;
|
|
onChange: (dataUrl: string | null) => void;
|
|
label?: string;
|
|
description?: string;
|
|
}
|
|
|
|
/**
|
|
* Company stamp/seal attachment for the contract signing modal. Reads the
|
|
* picked image straight into a data URL because the signing endpoint takes
|
|
* base64 in JSON (same transport as the drawn signature), not multipart.
|
|
*/
|
|
export function StampUpload({
|
|
value,
|
|
onChange,
|
|
label = "Company stamp",
|
|
description = "Attach your official company stamp or seal.",
|
|
}: StampUploadProps) {
|
|
const inputRef = useRef<HTMLInputElement>(null);
|
|
const [dragging, setDragging] = useState(false);
|
|
const [error, setError] = useState<string | null>(null);
|
|
const [fileName, setFileName] = useState<string | null>(null);
|
|
|
|
const readFile = (file: File | undefined | null) => {
|
|
if (!file) return;
|
|
if (!file.type.startsWith("image/")) {
|
|
setError("The stamp must be an image file (PNG or JPG).");
|
|
return;
|
|
}
|
|
if (file.size > MAX_STAMP_MB * 1024 * 1024) {
|
|
setError(`The stamp image must be under ${MAX_STAMP_MB} MB.`);
|
|
return;
|
|
}
|
|
const reader = new FileReader();
|
|
reader.onload = () => {
|
|
setError(null);
|
|
setFileName(file.name);
|
|
onChange(typeof reader.result === "string" ? reader.result : null);
|
|
};
|
|
reader.onerror = () => setError("Could not read that file. Try another.");
|
|
reader.readAsDataURL(file);
|
|
};
|
|
|
|
const openPicker = () => inputRef.current?.click();
|
|
|
|
const clear = () => {
|
|
setFileName(null);
|
|
setError(null);
|
|
onChange(null);
|
|
if (inputRef.current) inputRef.current.value = "";
|
|
};
|
|
|
|
return (
|
|
<Stack gap={6}>
|
|
<Text size="sm" fw={500}>
|
|
{label}
|
|
</Text>
|
|
|
|
<input
|
|
ref={inputRef}
|
|
type="file"
|
|
accept="image/png,image/jpeg,image/webp"
|
|
hidden
|
|
onChange={(e) => readFile(e.currentTarget.files?.[0])}
|
|
/>
|
|
|
|
{value ? (
|
|
<Paper withBorder radius="md" p="sm">
|
|
<Group gap="md" wrap="nowrap" align="center">
|
|
<Box
|
|
style={{
|
|
background:
|
|
"repeating-conic-gradient(var(--mantine-color-gray-1) 0% 25%, transparent 0% 50%) 50% / 14px 14px",
|
|
borderRadius: 8,
|
|
flexShrink: 0,
|
|
padding: 6,
|
|
}}
|
|
>
|
|
<Image
|
|
src={value}
|
|
alt="Company stamp"
|
|
fit="contain"
|
|
h={92}
|
|
w={92}
|
|
/>
|
|
</Box>
|
|
<Stack gap={4} style={{ flex: 1, minWidth: 0 }}>
|
|
<Text size="sm" fw={500} truncate>
|
|
{fileName ?? "Stamp attached"}
|
|
</Text>
|
|
<Text size="xs" c="dimmed">
|
|
This stamp is applied next to your signature on the contract.
|
|
</Text>
|
|
<Group gap="xs" mt={2}>
|
|
<Button
|
|
size="compact-xs"
|
|
variant="light"
|
|
color="edr-green"
|
|
leftSection={<RefreshCw size={13} />}
|
|
onClick={openPicker}
|
|
>
|
|
Replace
|
|
</Button>
|
|
<Button
|
|
size="compact-xs"
|
|
variant="subtle"
|
|
color="red"
|
|
leftSection={<X size={13} />}
|
|
onClick={clear}
|
|
>
|
|
Remove
|
|
</Button>
|
|
</Group>
|
|
</Stack>
|
|
</Group>
|
|
</Paper>
|
|
) : (
|
|
<Paper
|
|
withBorder
|
|
radius="md"
|
|
p="lg"
|
|
onClick={openPicker}
|
|
onDragOver={(e) => {
|
|
e.preventDefault();
|
|
setDragging(true);
|
|
}}
|
|
onDragLeave={() => setDragging(false)}
|
|
onDrop={(e) => {
|
|
e.preventDefault();
|
|
setDragging(false);
|
|
readFile(e.dataTransfer.files?.[0]);
|
|
}}
|
|
style={{
|
|
borderColor: dragging
|
|
? "var(--mantine-color-edr-green-6)"
|
|
: undefined,
|
|
borderStyle: "dashed",
|
|
backgroundColor: dragging
|
|
? "var(--mantine-color-edr-green-0)"
|
|
: undefined,
|
|
cursor: "pointer",
|
|
}}
|
|
>
|
|
<Stack gap={6} align="center">
|
|
<Stamp size={26} color="var(--mantine-color-edr-green-6)" />
|
|
<Text size="sm" fw={500}>
|
|
Upload company stamp
|
|
</Text>
|
|
<Text size="xs" c="dimmed" ta="center">
|
|
{description} Drop an image here or click to browse — PNG or JPG,
|
|
up to {MAX_STAMP_MB} MB.
|
|
</Text>
|
|
</Stack>
|
|
</Paper>
|
|
)}
|
|
|
|
{error && (
|
|
<Text size="xs" c="red.7">
|
|
{error}
|
|
</Text>
|
|
)}
|
|
</Stack>
|
|
);
|
|
}
|