185 lines
5.1 KiB
TypeScript
185 lines
5.1 KiB
TypeScript
// components/ProfileAvatarModal.tsx
|
|
'use client';
|
|
|
|
import { useEffect, useState } from 'react';
|
|
import Modal from '@/components/ui/Modal';
|
|
import PersonAvatar from '@/components/ui/UserAvatar';
|
|
import Button from './ui/Button';
|
|
|
|
type ProfileAvatarModalProps = {
|
|
open: boolean;
|
|
onClose: () => void;
|
|
avatarName: string;
|
|
avatarUrl?: string | null;
|
|
/**
|
|
* Wird aufgerufen, wenn eine neue Datei gespeichert werden soll.
|
|
*/
|
|
onAvatarSelected?: (file: File) => Promise<void> | void;
|
|
/**
|
|
* Optional: wird aufgerufen, wenn der Nutzer das Profilbild löschen möchte.
|
|
* Erwartet, dass Backend + Session angepasst werden (Avatar auf null).
|
|
*/
|
|
onAvatarDelete?: () => Promise<void> | void;
|
|
};
|
|
|
|
export default function ProfileAvatarModal({
|
|
open,
|
|
onClose,
|
|
avatarName,
|
|
avatarUrl,
|
|
onAvatarSelected,
|
|
onAvatarDelete,
|
|
}: ProfileAvatarModalProps) {
|
|
const [selectedFile, setSelectedFile] = useState<File | null>(null);
|
|
const [previewUrl, setPreviewUrl] = useState<string | null>(null);
|
|
const [isSaving, setIsSaving] = useState(false);
|
|
|
|
// Wenn Modal geschlossen wird → State zurücksetzen
|
|
useEffect(() => {
|
|
if (!open) {
|
|
setSelectedFile(null);
|
|
setPreviewUrl(null);
|
|
setIsSaving(false);
|
|
}
|
|
}, [open]);
|
|
|
|
// Preview-URL für ausgewählte Datei erzeugen
|
|
useEffect(() => {
|
|
if (!selectedFile) {
|
|
setPreviewUrl(null);
|
|
return;
|
|
}
|
|
|
|
const url = URL.createObjectURL(selectedFile);
|
|
setPreviewUrl(url);
|
|
|
|
return () => URL.revokeObjectURL(url);
|
|
}, [selectedFile]);
|
|
|
|
const handleFileChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
|
const file = e.target.files?.[0];
|
|
if (file) {
|
|
setSelectedFile(file);
|
|
}
|
|
};
|
|
|
|
const handleSave = async () => {
|
|
if (!selectedFile) return;
|
|
|
|
try {
|
|
setIsSaving(true);
|
|
if (onAvatarSelected) {
|
|
await onAvatarSelected(selectedFile);
|
|
}
|
|
onClose();
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const handleDeleteClick = async () => {
|
|
if (!onAvatarDelete) return;
|
|
|
|
const sure = window.confirm('Profilbild wirklich entfernen?');
|
|
if (!sure) return;
|
|
|
|
try {
|
|
setIsSaving(true);
|
|
await onAvatarDelete();
|
|
// lokale Preview zurücksetzen
|
|
setSelectedFile(null);
|
|
setPreviewUrl(null);
|
|
onClose();
|
|
} finally {
|
|
setIsSaving(false);
|
|
}
|
|
};
|
|
|
|
const effectiveAvatarUrl = previewUrl ?? avatarUrl ?? undefined;
|
|
const hasDeletableAvatar = !!avatarUrl; // Button nur anzeigen, wenn ein Avatar existiert
|
|
|
|
return (
|
|
<Modal
|
|
open={open}
|
|
onClose={onClose}
|
|
title="Profilbild ändern"
|
|
tone="info"
|
|
size="sm"
|
|
primaryAction={{
|
|
label: 'Speichern',
|
|
onClick: handleSave,
|
|
disabled: !selectedFile || isSaving,
|
|
}}
|
|
secondaryAction={{
|
|
label: 'Abbrechen',
|
|
onClick: onClose,
|
|
variant: 'secondary',
|
|
disabled: isSaving,
|
|
}}
|
|
>
|
|
<div className="space-y-4">
|
|
<p className="text-sm text-gray-600 dark:text-gray-300">
|
|
Wähle ein neues Profilbild aus oder entferne das aktuelle Bild.<br />
|
|
Unterstützte Formate: JPG, PNG, GIF.
|
|
</p>
|
|
|
|
{/* Aktuell vs. Vorschau */}
|
|
<div className="mt-4 grid grid-cols-1 gap-6 sm:grid-cols-2">
|
|
<div className="flex flex-col items-center gap-2">
|
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
Aktuell
|
|
</span>
|
|
<PersonAvatar
|
|
name={avatarName}
|
|
avatarUrl={avatarUrl ?? undefined}
|
|
size="2xl"
|
|
/>
|
|
</div>
|
|
|
|
<div className="flex flex-col items-center gap-2">
|
|
<span className="text-xs text-gray-500 dark:text-gray-400">
|
|
Vorschau
|
|
</span>
|
|
<PersonAvatar
|
|
name={avatarName}
|
|
avatarUrl={effectiveAvatarUrl}
|
|
size="2xl"
|
|
/>
|
|
</div>
|
|
</div>
|
|
|
|
{/* File-Input + ggf. Lösch-Button */}
|
|
<div className="mt-4 space-y-3">
|
|
<input
|
|
type="file"
|
|
accept="image/*"
|
|
onChange={handleFileChange}
|
|
className="mt-2 block w-full text-sm text-gray-900
|
|
file:mr-4 file:rounded-md file:border-0
|
|
file:bg-indigo-50 file:px-3 file:py-1.5
|
|
file:text-sm file:font-semibold file:text-indigo-700
|
|
hover:file:bg-indigo-100
|
|
dark:text-gray-100
|
|
dark:file:bg-indigo-500/10 dark:file:text-indigo-200
|
|
dark:hover:file:bg-indigo-500/20"
|
|
/>
|
|
|
|
{/* Profilbild löschen nur, wenn wirklich eins vorhanden ist */}
|
|
{onAvatarDelete && hasDeletableAvatar && (
|
|
<Button
|
|
onClick={handleDeleteClick}
|
|
disabled={isSaving}
|
|
size='md'
|
|
variant='soft'
|
|
tone='rose'
|
|
className="w-full text-xs font-medium text-rose-600 hover:text-rose-700 dark:text-rose-400 dark:hover:text-rose-300"
|
|
>
|
|
Profilbild entfernen
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
</Modal>
|
|
);
|
|
}
|