chore: update packages

Also:

- Remove Discord link and replace with email contact
- Fix none face paint type in submit form being transparent
- Update dockerfile
This commit is contained in:
trafficlunar 2026-08-01 16:18:21 +01:00
parent c2c1136c58
commit 211774e309
17 changed files with 1273 additions and 1148 deletions

View file

@ -1,73 +1,72 @@
import { type FileWithPath } from "react-dropzone";
import { DragDropContext, Draggable, Droppable, type DropResult } from "@hello-pangea/dnd";
import { Icon } from "@iconify/react";
interface Props {
files: readonly FileWithPath[];
setFiles: React.Dispatch<React.SetStateAction<FileWithPath[]>>;
}
export default function ImageList({ files, setFiles }: Props) {
const handleDelete = (index: number) => {
const newFiles = [...files];
newFiles.splice(index, 1);
setFiles(newFiles);
};
const handleDragEnd = (result: DropResult) => {
if (!result.destination) return;
const items = Array.from(files);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
setFiles(items);
};
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="imageDroppable">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps} className="flex flex-col px-12 max-lg:px-0 max-md:px-12 max-[32rem]:px-0">
{files.map((file, index) => (
<Draggable key={file.name} draggableId={file.name} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
className="w-full p-4 rounded-xl bg-orange-100 border-2 border-amber-500 flex gap-2 shadow-md my-1"
>
<img
src={URL.createObjectURL(file)}
alt={file.name}
width={96}
height={96}
className="aspect-3/2 object-contain w-24 rounded-md bg-orange-300 border-2 border-orange-400"
/>
<div className="flex flex-col justify-center w-full min-w-0">
<span className="font-semibold overflow-hidden text-ellipsis">{file.name}</span>
<button
onClick={() => handleDelete(index)}
className="pill button text-xs w-min px-3! py-1! bg-red-300! border-red-400! hover:bg-red-400!"
>
Delete
</button>
</div>
<div
{...provided.dragHandleProps}
className="h-full w-11 px-1 cursor-grab flex items-center justify-center rounded transition-colors hover:bg-black/10"
>
<Icon icon="tabler:grip-horizontal" className="size-6 text-black/50" />
</div>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}
import { DragDropContext, Draggable, Droppable, type DropResult } from "@hello-pangea/dnd";
import { Icon } from "@iconify/react";
interface Props {
files: readonly File[];
setFiles: React.Dispatch<React.SetStateAction<File[]>>;
}
export default function ImageList({ files, setFiles }: Props) {
const handleDelete = (index: number) => {
const newFiles = [...files];
newFiles.splice(index, 1);
setFiles(newFiles);
};
const handleDragEnd = (result: DropResult) => {
if (!result.destination) return;
const items = Array.from(files);
const [reorderedItem] = items.splice(result.source.index, 1);
items.splice(result.destination.index, 0, reorderedItem);
setFiles(items);
};
return (
<DragDropContext onDragEnd={handleDragEnd}>
<Droppable droppableId="imageDroppable">
{(provided) => (
<div ref={provided.innerRef} {...provided.droppableProps} className="flex flex-col px-12 max-lg:px-0 max-md:px-12 max-[32rem]:px-0">
{files.map((file, index) => (
<Draggable key={file.name} draggableId={file.name} index={index}>
{(provided) => (
<div
ref={provided.innerRef}
{...provided.draggableProps}
className="w-full p-4 rounded-xl bg-orange-100 border-2 border-amber-500 flex gap-2 shadow-md my-1"
>
<img
src={URL.createObjectURL(file)}
alt={file.name}
width={96}
height={96}
className="aspect-3/2 object-contain w-24 rounded-md bg-orange-300 border-2 border-orange-400"
/>
<div className="flex flex-col justify-center w-full min-w-0">
<span className="font-semibold overflow-hidden text-ellipsis">{file.name}</span>
<button
onClick={() => handleDelete(index)}
className="pill button text-xs w-min px-3! py-1! bg-red-300! border-red-400! hover:bg-red-400!"
>
Delete
</button>
</div>
<div
{...provided.dragHandleProps}
className="h-full w-11 px-1 cursor-grab flex items-center justify-center rounded transition-colors hover:bg-black/10"
>
<Icon icon="tabler:grip-horizontal" className="size-6 text-black/50" />
</div>
</div>
)}
</Draggable>
))}
{provided.placeholder}
</div>
)}
</Droppable>
</DragDropContext>
);
}

View file

@ -1,67 +1,66 @@
import { useCallback, useRef, useState } from "react";
import { type FileWithPath } from "react-dropzone";
import jsQR from "jsqr";
import Dropzone from "../dropzone";
interface Props {
setQrBytesRaw: React.Dispatch<React.SetStateAction<number[]>>;
}
export default function QrUpload({ setQrBytesRaw }: Props) {
const [hasImage, setHasImage] = useState(false);
const canvasRef = useRef<HTMLCanvasElement>(null);
const handleDrop = useCallback(
(acceptedFiles: FileWithPath[]) => {
const file = acceptedFiles[0];
// Scan QR code
const reader = new FileReader();
reader.onload = async (event) => {
const image = new Image();
image.onload = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0, image.width, image.height);
const imageData = ctx.getImageData(0, 0, image.width, image.height);
const code = jsQR(imageData.data, image.width, image.height);
if (!code) return;
setQrBytesRaw(code.binaryData!);
setHasImage(true);
};
image.src = event.target!.result as string;
};
reader.readAsDataURL(file);
},
[setQrBytesRaw],
);
return (
<div className="max-w-md w-full">
<Dropzone onDrop={handleDrop} options={{ maxFiles: 1 }}>
<p className="text-center text-sm">
{!hasImage ? (
<>
Drag and drop your QR code image here
<br />
or click to open
</>
) : (
"Uploaded!"
)}
</p>
</Dropzone>
{/* Canvas is used to scan the QR code */}
<canvas ref={canvasRef} className="hidden" />
</div>
);
}
import { useCallback, useRef, useState } from "react";
import jsQR from "jsqr";
import Dropzone from "../dropzone";
interface Props {
setQrBytesRaw: React.Dispatch<React.SetStateAction<number[]>>;
}
export default function QrUpload({ setQrBytesRaw }: Props) {
const [hasImage, setHasImage] = useState(false);
const canvasRef = useRef<HTMLCanvasElement>(null);
const handleDrop = useCallback(
(acceptedFiles: File[]) => {
const file = acceptedFiles[0];
// Scan QR code
const reader = new FileReader();
reader.onload = async (event) => {
const image = new Image();
image.onload = () => {
const canvas = canvasRef.current;
if (!canvas) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
canvas.width = image.width;
canvas.height = image.height;
ctx.drawImage(image, 0, 0, image.width, image.height);
const imageData = ctx.getImageData(0, 0, image.width, image.height);
const code = jsQR(imageData.data, image.width, image.height);
if (!code) return;
setQrBytesRaw(code.binaryData!);
setHasImage(true);
};
image.src = event.target!.result as string;
};
reader.readAsDataURL(file);
},
[setQrBytesRaw],
);
return (
<div className="max-w-md w-full">
<Dropzone onDrop={handleDrop} options={{ maxFiles: 1 }}>
<p className="text-center text-sm">
{!hasImage ? (
<>
Drag and drop your QR code image here
<br />
or click to open
</>
) : (
"Uploaded!"
)}
</p>
</Dropzone>
{/* Canvas is used to scan the QR code */}
<canvas ref={canvasRef} className="hidden" />
</div>
);
}

View file

@ -1,5 +1,4 @@
import { useCallback, useState } from "react";
import { type FileWithPath } from "react-dropzone";
import { Icon } from "@iconify/react";
import Dropzone from "../dropzone";
import Camera from "./camera";
@ -20,7 +19,7 @@ export default function SwitchFileUpload({ text, type = "image", forceCrop, file
const [isCropOpen, setIsCropOpen] = useState(false);
const handleDrop = useCallback(
(acceptedFiles: FileWithPath[]) => {
(acceptedFiles: File[]) => {
const file = acceptedFiles[0];
if (type === "file") {
setFile!(file);