first commit

This commit is contained in:
2026-01-19 13:08:58 +03:30
commit 850b4a3f1e
293 changed files with 51775 additions and 0 deletions

View File

@@ -0,0 +1,122 @@
import { TrashIcon } from "@heroicons/react/24/outline";
import { useState, useRef, ChangeEvent } from "react";
interface FileUploaderProps {
onFileSelected: (base64: string) => void;
error?: string;
defaultValue?: string;
title?: string;
}
export default function FileUploader({
onFileSelected,
error,
defaultValue,
title = "سند",
}: FileUploaderProps) {
const [selectedFile, setSelectedFile] = useState<File | null>(null);
const fileInputRef = useRef<HTMLInputElement>(null);
const handleFileChange = async (e: ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
setSelectedFile(file);
const base64 = await convertToBase64(file);
onFileSelected(base64);
};
const convertToBase64 = (file: File): Promise<string> => {
return new Promise((resolve, reject) => {
const reader = new FileReader();
reader.readAsDataURL(file);
reader.onload = () => resolve(reader.result as string);
reader.onerror = (error) => reject(error);
});
};
const handleRemoveFile = () => {
setSelectedFile(null);
if (fileInputRef.current) {
fileInputRef.current.value = "";
}
onFileSelected("");
};
const handleButtonClick = () => {
fileInputRef.current?.click();
};
const formatFileSize = (size: number) => {
const sizeInMB = size / (1024 * 1024);
return sizeInMB.toFixed(2) + " MB";
};
const getDefaultDocumentName = () => {
const name = defaultValue?.split("/")[defaultValue?.split("/")?.length - 1];
if (name) {
return name;
} else {
return null;
}
};
return (
<>
<div className="flex items-center w-full gap-2 border border-gray1-200 dark:border-dark-400 rounded-lg">
<input
type="file"
ref={fileInputRef}
onChange={handleFileChange}
className="hidden"
accept="*/*"
/>
<button
type="button"
onClick={handleButtonClick}
className={`flex justify-center cursor-pointer items-center px-4 py-2 rounded-lg transition-colors w-full ${
selectedFile
? "border-l border-gray1-200 dark:border-dark-400 bg-gray-50 dark:bg-dark-600 dark:text-dark-100 hover:bg-gray-100 dark:hover:bg-dark-600 text-gray-700"
: "bg-white1-200 dark:bg-dark-500 dark:text-dark-100 text-gray-700 hover:bg-white1-300 dark:hover:bg-dark-600"
}`}
>
{selectedFile || getDefaultDocumentName() ? (
<div className="flex items-center gap-2">
<div className="flex justify-center">
<span className=" text-[10px] w-10 bg-info-500 rounded-2xl text-white">
{title}
</span>
</div>
{selectedFile ? (
<span className="">
{selectedFile.name?.slice(0, 15)}
{selectedFile.name?.length > 15 ? "..." : ""} (
{formatFileSize(selectedFile.size)})
</span>
) : (
<span>{getDefaultDocumentName()}</span>
)}
</div>
) : (
<span>انتخاب {title}</span>
)}
</button>
{selectedFile && (
<button
type="button"
onClick={handleRemoveFile}
className="p-2 text-red-400 rounded-lg transition-colors"
aria-label="Remove file"
>
<TrashIcon className="h-5 w-5" />
</button>
)}
</div>
{error && (
<p className="text-xs text-red-500 dark:text-red-400">{error}</p>
)}
</>
);
}