Files
deardiary/frontend/src/components/QuickAddWidget.tsx

142 lines
4.7 KiB
TypeScript

import { useState, useRef, useEffect } from 'react';
import { api } from '../lib/api';
import { getCurrentLocation } from '../lib/geolocation';
interface Props {
isOpen: boolean;
onClose: () => void;
}
export default function QuickAddWidget({ isOpen, onClose }: Props) {
const [type, setType] = useState('event');
const [content, setContent] = useState('');
const [loading, setLoading] = useState(false);
const [locked, setLocked] = useState(false);
const inputRef = useRef<HTMLInputElement>(null);
const today = new Date().toISOString().split('T')[0];
useEffect(() => {
if (isOpen) {
checkDiaryStatus();
setTimeout(() => inputRef.current?.focus(), 50);
}
}, [isOpen]);
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
if (e.key === 'Enter' && (e.ctrlKey || e.metaKey)) {
e.preventDefault();
if (content.trim() && !locked && !loading) {
const form = inputRef.current?.form;
if (form) {
const submitEvent = new Event('submit', { bubbles: true, cancelable: true });
form.dispatchEvent(submitEvent);
}
}
}
};
if (isOpen) {
window.addEventListener('keydown', handleKeyDown);
}
return () => window.removeEventListener('keydown', handleKeyDown);
}, [isOpen, content, locked, loading]);
const checkDiaryStatus = async () => {
const res = await api.getDay(today);
if (res.data) {
setLocked(!!res.data.journal);
}
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!content.trim() || locked) return;
setLoading(true);
const location = await getCurrentLocation();
await api.createEvent(today, type, content, undefined, location ?? undefined);
setContent('');
setLoading(false);
onClose();
};
if (!isOpen) return null;
if (locked) {
return (
<div className="fixed inset-0 z-50 flex items-start justify-center pt-20">
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
<div className="relative bg-slate-800 rounded-2xl shadow-2xl w-full max-w-md mx-4 p-6 text-center">
<p className="text-slate-300 mb-2">Today's diary is locked</p>
<p className="text-slate-500 text-sm mb-4">Delete the diary to add more events.</p>
<a
href={`/journal/${today}`}
className="inline-block px-4 py-2 bg-purple-600 hover:bg-purple-700 rounded-lg text-sm font-medium transition"
>
View Diary
</a>
</div>
</div>
);
}
return (
<div className="fixed inset-0 z-50 flex items-start justify-center pt-20">
<div className="absolute inset-0 bg-black/50 backdrop-blur-sm" onClick={onClose} />
<div className="relative bg-slate-800 rounded-2xl shadow-2xl w-full max-w-md mx-4">
<form onSubmit={handleSubmit} className="p-4">
<div className="flex gap-2 mb-3">
<button
type="button"
onClick={() => setType('event')}
className={`px-3 py-1 rounded text-sm ${type === 'event' ? 'bg-purple-600' : 'bg-slate-700'}`}
>
Event
</button>
<button
type="button"
onClick={() => setType('health')}
className={`px-3 py-1 rounded text-sm ${type === 'health' ? 'bg-purple-600' : 'bg-slate-700'}`}
>
Health
</button>
<button
type="button"
onClick={() => setType('photo')}
className={`px-3 py-1 rounded text-sm ${type === 'photo' ? 'bg-purple-600' : 'bg-slate-700'}`}
>
Photo
</button>
<button
type="button"
onClick={() => setType('voice')}
className={`px-3 py-1 rounded text-sm ${type === 'voice' ? 'bg-purple-600' : 'bg-slate-700'}`}
>
Voice
</button>
</div>
<div className="flex gap-2">
<input
ref={inputRef}
type="text"
value={content}
onChange={(e) => setContent(e.target.value)}
placeholder={type === 'health' ? 'How are you feeling?' : 'Log an event...'}
className="flex-1 px-4 py-3 bg-slate-900 rounded-lg border border-slate-700 focus:border-purple-500 focus:outline-none"
/>
<button
type="submit"
disabled={loading || !content.trim()}
className="px-6 py-3 bg-purple-600 hover:bg-purple-700 rounded-lg font-medium transition disabled:opacity-50"
>
+
</button>
</div>
</form>
</div>
</div>
);
}