fix: clipboard error handling, Escape key support, per-notification read state
This commit is contained in:
@@ -27,8 +27,12 @@ const quickStart: { step: number; text: string }[] = [
|
|||||||
const HelpPanel: React.FC<HelpPanelProps> = ({ open, onClose }) => {
|
const HelpPanel: React.FC<HelpPanelProps> = ({ open, onClose }) => {
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose();
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-backdrop" onClick={onClose}>
|
<div className="modal-backdrop" onClick={onClose} onKeyDown={handleKeyDown} tabIndex={-1}>
|
||||||
<div className="modal-panel help-panel" onClick={(e) => e.stopPropagation()}>
|
<div className="modal-panel help-panel" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h2>Hilfe</h2>
|
<h2>Hilfe</h2>
|
||||||
|
|||||||
@@ -6,17 +6,17 @@ interface Notification {
|
|||||||
icon: string;
|
icon: string;
|
||||||
title: string;
|
title: string;
|
||||||
timestamp: string;
|
timestamp: string;
|
||||||
|
read: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const initialNotifications: Notification[] = [
|
const initialNotifications: Notification[] = [
|
||||||
{ id: 'n1', icon: '💾', title: 'Projekt automatisch gespeichert', timestamp: 'vor 2 Min.' },
|
{ id: 'n1', icon: '💾', title: 'Projekt automatisch gespeichert', timestamp: 'vor 2 Min.', read: false },
|
||||||
{ id: 'n2', icon: '🔄', title: 'Neue Version verfügbar', timestamp: 'vor 15 Min.' },
|
{ id: 'n2', icon: '🔄', title: 'Neue Version verfügbar', timestamp: 'vor 15 Min.', read: false },
|
||||||
{ id: 'n3', icon: '👤', title: 'Benutzer ist beigetreten', timestamp: 'vor 1 Std.' },
|
{ id: 'n3', icon: '👤', title: 'Benutzer ist beigetreten', timestamp: 'vor 1 Std.', read: false },
|
||||||
];
|
];
|
||||||
|
|
||||||
const NotificationPanel: React.FC<NotificationPanelProps> = ({ open, onClose }) => {
|
const NotificationPanel: React.FC<NotificationPanelProps> = ({ open, onClose }) => {
|
||||||
const [notifications, setNotifications] = useState<Notification[]>(initialNotifications);
|
const [notifications, setNotifications] = useState<Notification[]>(initialNotifications);
|
||||||
const [allRead, setAllRead] = useState(false);
|
|
||||||
const ref = useRef<HTMLDivElement>(null);
|
const ref = useRef<HTMLDivElement>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -26,8 +26,15 @@ const NotificationPanel: React.FC<NotificationPanelProps> = ({ open, onClose })
|
|||||||
onClose();
|
onClose();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
const handleKeyDown = (e: KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose();
|
||||||
|
};
|
||||||
document.addEventListener('mousedown', handleClickOutside);
|
document.addEventListener('mousedown', handleClickOutside);
|
||||||
return () => document.removeEventListener('mousedown', handleClickOutside);
|
document.addEventListener('keydown', handleKeyDown);
|
||||||
|
return () => {
|
||||||
|
document.removeEventListener('mousedown', handleClickOutside);
|
||||||
|
document.removeEventListener('keydown', handleKeyDown);
|
||||||
|
};
|
||||||
}, [open, onClose]);
|
}, [open, onClose]);
|
||||||
|
|
||||||
if (!open) return null;
|
if (!open) return null;
|
||||||
@@ -37,7 +44,7 @@ const NotificationPanel: React.FC<NotificationPanelProps> = ({ open, onClose })
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleMarkAllRead = () => {
|
const handleMarkAllRead = () => {
|
||||||
setAllRead(true);
|
setNotifications((prev) => prev.map((n) => ({ ...n, read: true })));
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -53,7 +60,7 @@ const NotificationPanel: React.FC<NotificationPanelProps> = ({ open, onClose })
|
|||||||
<p className="notif-empty">Keine Benachrichtigungen</p>
|
<p className="notif-empty">Keine Benachrichtigungen</p>
|
||||||
) : (
|
) : (
|
||||||
notifications.map((n) => (
|
notifications.map((n) => (
|
||||||
<div key={n.id} className={`notif-item ${allRead ? 'read' : ''}`}>
|
<div key={n.id} className={`notif-item ${n.read ? 'read' : ''}`}>
|
||||||
<span className="notif-icon">{n.icon}</span>
|
<span className="notif-icon">{n.icon}</span>
|
||||||
<div className="notif-content">
|
<div className="notif-content">
|
||||||
<span className="notif-title">{n.title}</span>
|
<span className="notif-title">{n.title}</span>
|
||||||
|
|||||||
@@ -14,9 +14,15 @@ const ShareDialog: React.FC<ShareDialogProps> = ({ open, onClose, projectName })
|
|||||||
navigator.clipboard.writeText(shareLink).then(() => {
|
navigator.clipboard.writeText(shareLink).then(() => {
|
||||||
setCopied(true);
|
setCopied(true);
|
||||||
setTimeout(() => setCopied(false), 2000);
|
setTimeout(() => setCopied(false), 2000);
|
||||||
|
}).catch(() => {
|
||||||
|
setCopied(false);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const handleKeyDown = (e: React.KeyboardEvent) => {
|
||||||
|
if (e.key === 'Escape') onClose();
|
||||||
|
};
|
||||||
|
|
||||||
const handleInvite = () => {
|
const handleInvite = () => {
|
||||||
if (email.trim()) {
|
if (email.trim()) {
|
||||||
setInvited(true);
|
setInvited(true);
|
||||||
@@ -26,7 +32,7 @@ const ShareDialog: React.FC<ShareDialogProps> = ({ open, onClose, projectName })
|
|||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="modal-backdrop" onClick={onClose}>
|
<div className="modal-backdrop" onClick={onClose} onKeyDown={handleKeyDown} tabIndex={-1}>
|
||||||
<div className="modal-panel share-dialog" onClick={(e) => e.stopPropagation()}>
|
<div className="modal-panel share-dialog" onClick={(e) => e.stopPropagation()}>
|
||||||
<div className="modal-header">
|
<div className="modal-header">
|
||||||
<h2>Projekt teilen</h2>
|
<h2>Projekt teilen</h2>
|
||||||
|
|||||||
Reference in New Issue
Block a user