msg
This commit is contained in:
@@ -14,10 +14,13 @@ import {
|
||||
CheckCircle2,
|
||||
Ban,
|
||||
Loader2,
|
||||
Pencil,
|
||||
UserRound,
|
||||
} from "lucide-react"
|
||||
import { GlassModal } from "./glass-modal"
|
||||
import * as api from "@/lib/api"
|
||||
import { ApiError } from "@/lib/api"
|
||||
import { useRoomMembers } from "@/lib/hooks"
|
||||
import type { CalendarEvent, Room, SignupStatus } from "@/lib/types"
|
||||
import { getRoomColor } from "@/lib/room-theme"
|
||||
import { parseDateTime, formatDayLabel, formatTime } from "@/lib/date-utils"
|
||||
@@ -28,6 +31,7 @@ interface EventDetailDialogProps {
|
||||
currentUserId: number
|
||||
onClose: () => void
|
||||
onChanged: () => void
|
||||
onEdit: () => void
|
||||
}
|
||||
|
||||
const STATUS_STYLES: Record<string, string> = {
|
||||
@@ -43,6 +47,7 @@ export function EventDetailDialog({
|
||||
currentUserId,
|
||||
onClose,
|
||||
onChanged,
|
||||
onEdit,
|
||||
}: EventDetailDialogProps) {
|
||||
const [busy, setBusy] = useState<string | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
@@ -52,11 +57,16 @@ export function EventDetailDialog({
|
||||
["responses", event.id],
|
||||
() => api.getEventResponses(event.id),
|
||||
)
|
||||
const { members } = useRoomMembers(event.room_id)
|
||||
|
||||
const start = parseDateTime(event.start_time)
|
||||
const end = parseDateTime(event.end_time)
|
||||
const color = getRoomColor(event.room_id)
|
||||
|
||||
const creator = members.find((m) => m.id === event.creator_id)
|
||||
const creatorLabel =
|
||||
event.creator_id === currentUserId ? "you" : (creator?.username ?? `member #${event.creator_id}`)
|
||||
|
||||
const canManage =
|
||||
event.creator_id === currentUserId || room?.role === "admin" || room?.role === "owner"
|
||||
|
||||
@@ -79,6 +89,20 @@ export function EventDetailDialog({
|
||||
}
|
||||
}
|
||||
|
||||
const handleDelete = async () => {
|
||||
setBusy("delete")
|
||||
setError(null)
|
||||
try {
|
||||
await api.deleteEvent(event.id)
|
||||
onChanged()
|
||||
onClose()
|
||||
} catch (err) {
|
||||
setError(err instanceof ApiError ? err.message : "Action failed.")
|
||||
} finally {
|
||||
setBusy(null)
|
||||
}
|
||||
}
|
||||
|
||||
const respond = (status: SignupStatus) =>
|
||||
run(`respond-${status}`, () => api.respondToEvent(event.id, status), true)
|
||||
|
||||
@@ -94,13 +118,15 @@ export function EventDetailDialog({
|
||||
<div className="flex flex-wrap items-center gap-2">
|
||||
<span className={`h-3 w-3 rounded-sm ${color.dot}`} />
|
||||
<span className="text-sm text-white/80">{room?.name ?? `Room #${event.room_id}`}</span>
|
||||
<span
|
||||
className={`ml-auto rounded-full border px-2.5 py-0.5 text-xs font-medium capitalize ${
|
||||
STATUS_STYLES[event.status] ?? STATUS_STYLES.draft
|
||||
}`}
|
||||
>
|
||||
{event.status}
|
||||
</span>
|
||||
{event.type === "proposal" && (
|
||||
<span
|
||||
className={`ml-auto rounded-full border px-2.5 py-0.5 text-xs font-medium capitalize ${
|
||||
STATUS_STYLES[event.status] ?? STATUS_STYLES.draft
|
||||
}`}
|
||||
>
|
||||
{event.status}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="space-y-2 text-sm text-white/90">
|
||||
@@ -116,6 +142,10 @@ export function EventDetailDialog({
|
||||
<DoorClosed className="h-4 w-4 text-white/70" />
|
||||
{event.type} · {event.visibility.replace("_", " ")}
|
||||
</p>
|
||||
<p className="flex items-center gap-2">
|
||||
<UserRound className="h-4 w-4 text-white/70" />
|
||||
Created by {creatorLabel}
|
||||
</p>
|
||||
{event.type === "proposal" && (
|
||||
<p className="flex items-center gap-2">
|
||||
<Users className="h-4 w-4 text-white/70" />
|
||||
@@ -130,8 +160,8 @@ export function EventDetailDialog({
|
||||
</p>
|
||||
)}
|
||||
|
||||
{/* RSVP */}
|
||||
{event.status !== "cancelled" && (
|
||||
{/* RSVP — only relevant for proposals, busy/private have no attendees to respond */}
|
||||
{event.type === "proposal" && event.status !== "cancelled" && (
|
||||
<div>
|
||||
<p className="mb-2 text-sm font-medium text-white/90">Your response</p>
|
||||
<div className="flex gap-2">
|
||||
@@ -174,22 +204,31 @@ export function EventDetailDialog({
|
||||
{canManage && (
|
||||
<div className="flex flex-col gap-3 border-t border-white/15 pt-4">
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{event.status !== "confirmed" && event.status !== "cancelled" && (
|
||||
<button
|
||||
onClick={onEdit}
|
||||
disabled={busy !== null}
|
||||
className="flex items-center gap-1.5 rounded-xl bg-white/10 px-3 py-2 text-sm font-medium transition-colors hover:bg-white/20 disabled:opacity-70"
|
||||
>
|
||||
<Pencil className="h-4 w-4" /> Edit event
|
||||
</button>
|
||||
{/* Confirm/cancel only make sense for proposals — busy/private
|
||||
blocks are already "confirmed" the moment they're created. */}
|
||||
{event.type === "proposal" && event.status !== "confirmed" && event.status !== "cancelled" && (
|
||||
<button
|
||||
onClick={() => run("confirm", () => api.confirmEvent(event.id))}
|
||||
disabled={busy !== null}
|
||||
className="flex items-center gap-1.5 rounded-xl bg-emerald-500/80 px-3 py-2 text-sm font-medium transition-colors hover:bg-emerald-500 disabled:opacity-70"
|
||||
>
|
||||
<CheckCircle2 className="h-4 w-4" /> Confirm
|
||||
<CheckCircle2 className="h-4 w-4" /> Confirm event
|
||||
</button>
|
||||
)}
|
||||
{event.status !== "cancelled" && (
|
||||
{event.type === "proposal" && event.status !== "cancelled" && (
|
||||
<button
|
||||
onClick={() => run("cancel", () => api.cancelEvent(event.id))}
|
||||
disabled={busy !== null}
|
||||
className="flex items-center gap-1.5 rounded-xl bg-white/10 px-3 py-2 text-sm font-medium transition-colors hover:bg-white/20 disabled:opacity-70"
|
||||
>
|
||||
<Ban className="h-4 w-4" /> Cancel
|
||||
<Ban className="h-4 w-4" /> Cancel event
|
||||
</button>
|
||||
)}
|
||||
</div>
|
||||
@@ -214,7 +253,7 @@ export function EventDetailDialog({
|
||||
<div className="flex flex-col gap-2">
|
||||
{/* Destructive action sits on top, off the "usual" spot. */}
|
||||
<button
|
||||
onClick={() => run("delete", () => api.deleteEvent(event.id))}
|
||||
onClick={handleDelete}
|
||||
disabled={busy !== null}
|
||||
className="flex items-center justify-center gap-1.5 rounded-xl bg-red-500 px-3 py-2.5 text-sm font-medium transition-colors hover:bg-red-600 disabled:opacity-70"
|
||||
>
|
||||
@@ -239,6 +278,16 @@ export function EventDetailDialog({
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Always-available close action, bottom right. */}
|
||||
<div className="flex justify-end pt-1">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="rounded-xl border-2 border-emerald-400/80 bg-white/5 px-5 py-2 text-sm font-medium text-white transition-colors hover:bg-emerald-500/20"
|
||||
>
|
||||
OK
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</GlassModal>
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user