Files
time_to_leave/apps/web/src/app/add-event/AddEventModal.tsx
T
fegger bb286e1667 Refactor component styles to use CSS utility classes
Convert inline styles to reusable CSS classes for better maintainability
2026-05-18 22:32:09 +02:00

201 lines
6.5 KiB
TypeScript

"use client";
import React, { useState, useEffect } from "react";
import { format } from "date-fns";
import Button from "@/app/ui/Button";
import { useEventsStore } from "@/hooks/useEventsStore";
import type { Event } from "@timetoleave/core";
/**
* Modal for creating or editing a manually added event.
*
* Collects title, destination, date, and time. In edit mode, populates
* fields from the existing `editEvent` and calls `updateEvent` instead of `addEvent`.
* Shows a brief success overlay before closing on submit.
*
* Closes on Escape key press or backdrop click.
*/
type AddEventModalProps = {
isOpen: boolean;
onClose: () => void;
editEvent?: Event;
className?: string;
};
const AddEventModal: React.FC<AddEventModalProps> = ({ isOpen, onClose, editEvent, className = "" }) => {
const isEditing = !!editEvent;
const [title, setTitle] = useState("");
const [destination, setDestination] = useState("");
const [eventTime, setEventTime] = useState("");
const [eventDate, setEventDate] = useState("");
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState(false);
const { addEvent, updateEvent } = useEventsStore();
useEffect(() => {
if (!isOpen) return;
if (editEvent) {
setTitle(editEvent.title);
setDestination(editEvent.destination);
setEventDate(format(editEvent.eventTime, "yyyy-MM-dd"));
setEventTime(format(editEvent.eventTime, "HH:mm"));
} else {
setTitle("");
setDestination("");
setEventDate("");
setEventTime("");
}
setSuccess(false);
}, [editEvent, isOpen]);
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!title.trim() || !destination.trim() || !eventTime.trim() || !eventDate.trim()) return;
setLoading(true);
try {
const eventTimeDate = new Date(`${eventDate}T${eventTime}`);
if (isEditing && editEvent) {
updateEvent(editEvent.id, {
title: title.trim(),
destination: destination.trim(),
eventTime: eventTimeDate,
});
} else {
await addEvent({
id: `manual-${Date.now()}`,
title: title.trim(),
destination: destination.trim(),
eventTime: eventTimeDate,
source: "manual",
});
}
setSuccess(true);
setTimeout(() => {
onClose();
setSuccess(false);
}, 1200);
} catch (err) {
console.error("Error saving event:", err);
} finally {
setLoading(false);
}
};
const handleBackdropClick = (e: React.MouseEvent) => {
if (e.target === e.currentTarget) onClose();
};
useEffect(() => {
const handleEscape = (e: KeyboardEvent) => {
if (e.key === "Escape") onClose();
};
if (isOpen) document.addEventListener("keydown", handleEscape);
return () => document.removeEventListener("keydown", handleEscape);
}, [isOpen, onClose]);
if (!isOpen) return null;
return (
<div
className="fixed inset-0 z-50 flex items-center justify-center bg-black/60 p-4 backdrop-blur-sm"
onClick={handleBackdropClick}
>
<div className={`brand-panel relative w-full max-w-md ${className}`}>
<div className="p-6">
<p className="mb-2 eyebrow">
{isEditing ? "Edit event" : "Manual event"}
</p>
<h3 className="mb-6 text-2xl font-bold text-white">
{isEditing ? "Edit departure target" : "Add a departure target"}
</h3>
<form onSubmit={handleSubmit} className="space-y-4">
<div>
<label htmlFor="event-title" className="mb-1 block text-sm font-medium text-[#F4F1EA]/76">
Event Title
</label>
<input
id="event-title"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Team Meeting"
className="brand-input"
required
/>
</div>
<div>
<label htmlFor="event-destination" className="mb-1 block text-sm font-medium text-[#F4F1EA]/76">
Destination
</label>
<input
id="event-destination"
type="text"
value={destination}
onChange={(e) => setDestination(e.target.value)}
placeholder="Technikum Wien or Hoechstaedtplatz 6, 1200 Wien"
className="brand-input"
required
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div>
<label htmlFor="event-date" className="mb-1 block text-sm font-medium text-[#F4F1EA]/76">
Date
</label>
<input
id="event-date"
type="date"
value={eventDate}
onChange={(e) => setEventDate(e.target.value)}
className="brand-input"
required
/>
</div>
<div>
<label htmlFor="event-time" className="mb-1 block text-sm font-medium text-[#F4F1EA]/76">
Time
</label>
<input
id="event-time"
type="time"
value={eventTime}
onChange={(e) => setEventTime(e.target.value)}
className="brand-input"
required
/>
</div>
</div>
<div className="flex justify-end space-x-3 mt-6">
<Button type="button" variant="secondary" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={loading}>
{isEditing ? "Save Changes" : "Add Event"}
</Button>
</div>
</form>
</div>
{success && (
<div className="absolute inset-0 flex items-center justify-center rounded-2xl bg-[#090816]/90 backdrop-blur-sm">
<div className="flex flex-col items-center gap-3">
<div className="flex h-16 w-16 items-center justify-center rounded-full bg-gradient-to-br from-[#34C759] to-[#30d158] text-3xl shadow-lg">
</div>
<p className="text-lg font-semibold text-white">
{isEditing ? "Changes saved" : "Event added"}
</p>
</div>
</div>
)}
</div>
</div>
);
};
export default AddEventModal;