mirror of
https://github.com/SamyRai/turash.git
synced 2025-12-26 23:01:33 +00:00
- Remove nested git repository from bugulma/frontend/.git - Add all frontend files to main repository tracking - Convert from separate frontend/backend repos to unified monorepo - Preserve all frontend code and development history as tracked files - Eliminate nested repository complexity for simpler development workflow This creates a proper monorepo structure with frontend and backend coexisting in the same repository for easier development and deployment.
118 lines
3.6 KiB
TypeScript
118 lines
3.6 KiB
TypeScript
import React, { useRef, useState } from 'react';
|
|
import { useTranslation } from '@/hooks/useI18n.tsx';
|
|
import { Trash2, UploadCloud } from 'lucide-react';
|
|
import Button from '@/components/ui/Button.tsx';
|
|
import Spinner from '@/components/ui/Spinner.tsx';
|
|
|
|
interface ImageUploadProps {
|
|
value: string | null | undefined;
|
|
onChange: (value: string | null) => void;
|
|
className?: string;
|
|
}
|
|
|
|
const fileToDataUrl = (file: File): Promise<string> => {
|
|
return new Promise((resolve, reject) => {
|
|
const reader = new FileReader();
|
|
reader.onload = () => resolve(reader.result as string);
|
|
reader.onerror = reject;
|
|
reader.readAsDataURL(file);
|
|
});
|
|
};
|
|
|
|
const ImageUpload: React.FC<ImageUploadProps> = ({ value, onChange, className }) => {
|
|
const { t } = useTranslation();
|
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
const [isLoading, setIsLoading] = useState(false);
|
|
|
|
const handleFileChange = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = event.target.files?.[0];
|
|
if (file) {
|
|
setIsLoading(true);
|
|
try {
|
|
const dataUrl = await fileToDataUrl(file);
|
|
onChange(dataUrl);
|
|
} catch (error) {
|
|
console.error('Error converting file to data URL', error);
|
|
onChange(null);
|
|
} finally {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
};
|
|
|
|
const handleRemoveImage = () => {
|
|
onChange(null);
|
|
if (fileInputRef.current) {
|
|
fileInputRef.current.value = ''; // Reset file input
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className={className}>
|
|
<div
|
|
className="w-full aspect-[4/3] rounded-lg border-2 border-dashed flex items-center justify-center bg-muted/50 relative overflow-hidden cursor-pointer"
|
|
onClick={() => !isLoading && fileInputRef.current?.click()}
|
|
>
|
|
{isLoading ? (
|
|
<Spinner className="h-8 w-8 text-primary" />
|
|
) : value ? (
|
|
<img
|
|
src={value}
|
|
alt={t('imageUpload.logoAlt')}
|
|
className="max-h-full max-w-full object-contain"
|
|
loading="eager"
|
|
decoding="sync"
|
|
onError={(e) => {
|
|
(e.target as HTMLImageElement).style.display = 'none';
|
|
}}
|
|
/>
|
|
) : (
|
|
<div className="text-center text-muted-foreground p-4">
|
|
<UploadCloud className="h-10 h-4 mb-2 mx-auto text-current w-10 w-4" />
|
|
<p className="font-medium">{t('imageUpload.dropzoneHint')}</p>
|
|
</div>
|
|
)}
|
|
</div>
|
|
|
|
<input
|
|
type="file"
|
|
id="image-upload-input"
|
|
name="image-upload-input"
|
|
ref={fileInputRef}
|
|
onChange={handleFileChange}
|
|
className="hidden"
|
|
accept="image/png, image/jpeg, image/svg+xml"
|
|
disabled={isLoading}
|
|
/>
|
|
|
|
<div className="flex items-center gap-2 mt-2">
|
|
<Button
|
|
type="button"
|
|
variant="outline"
|
|
className="w-full"
|
|
onClick={() => fileInputRef.current?.click()}
|
|
disabled={isLoading}
|
|
>
|
|
<UploadCloud className="h-4 mr-2 text-current w-4" />
|
|
{value ? t('imageUpload.change') : t('imageUpload.upload')}
|
|
</Button>
|
|
{value && (
|
|
<Button
|
|
type="button"
|
|
variant="ghost"
|
|
size="sm"
|
|
className="text-destructive hover:text-destructive hover:bg-destructive/10 p-2 h-auto shrink-0"
|
|
onClick={handleRemoveImage}
|
|
aria-label={t('imageUpload.remove')}
|
|
disabled={isLoading}
|
|
>
|
|
<Trash2 className="h-4 text-current w-4" />
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default ImageUpload;
|