239 lines
8.1 KiB
TypeScript
239 lines
8.1 KiB
TypeScript
"use client"
|
|
|
|
import { useMemo, useState } from "react"
|
|
import { Menu, Plus, Loader2 } from "lucide-react"
|
|
|
|
import { useAuth } from "@/lib/auth-context"
|
|
import { useRooms, useEvents, useMyResponses } from "@/lib/hooks"
|
|
import { AuthScreen } from "@/components/auth-screen"
|
|
import { Sidebar } from "@/components/sidebar"
|
|
import { WeekView } from "@/components/week-view"
|
|
import { CreateEventDialog } from "@/components/create-event-dialog"
|
|
import { EventDetailDialog } from "@/components/event-detail-dialog"
|
|
import { CreateRoomDialog, JoinRoomDialog } from "@/components/room-dialogs"
|
|
import { RoomSettingsDialog } from "@/components/room-settings-dialog"
|
|
import { AccountSettingsDialog } from "@/components/account-settings-dialog"
|
|
import { RoomBackground } from "@/components/room-background"
|
|
import { getRoomBackground } from "@/lib/room-theme"
|
|
import { addDays, startOfWeek, toDateParam, formatMonthYear } from "@/lib/date-utils"
|
|
import type { CalendarEvent, User } from "@/lib/types"
|
|
|
|
type DialogState =
|
|
| { type: "none" }
|
|
| { type: "create-event" }
|
|
| { type: "edit-event"; event: CalendarEvent }
|
|
| { type: "event-detail"; event: CalendarEvent }
|
|
| { type: "create-room" }
|
|
| { type: "join-room" }
|
|
| { type: "room-settings" }
|
|
| { type: "account-settings" }
|
|
|
|
export default function Home() {
|
|
const { user, loading, logout } = useAuth()
|
|
|
|
if (loading) {
|
|
return (
|
|
<div className="flex min-h-screen w-full items-center justify-center bg-slate-900">
|
|
<Loader2 className="h-8 w-8 animate-spin text-white/70" />
|
|
</div>
|
|
)
|
|
}
|
|
|
|
if (!user) {
|
|
return <AuthScreen />
|
|
}
|
|
|
|
return <CalendarApp user={user} onLogout={logout} />
|
|
}
|
|
|
|
function CalendarApp({ user, onLogout }: { user: User; onLogout: () => void }) {
|
|
const [activeRoomId, setActiveRoomId] = useState<number | null>(null)
|
|
const [selectedDate, setSelectedDate] = useState(() => new Date())
|
|
const [sidebarOpen, setSidebarOpen] = useState(false)
|
|
const [dialog, setDialog] = useState<DialogState>({ type: "none" })
|
|
|
|
const { rooms, mutate: mutateRooms } = useRooms()
|
|
|
|
const weekStart = useMemo(() => startOfWeek(selectedDate), [selectedDate])
|
|
const weekEnd = useMemo(() => addDays(weekStart, 7), [weekStart])
|
|
const from = useMemo(() => toDateParam(weekStart), [weekStart])
|
|
const to = useMemo(() => toDateParam(weekEnd), [weekEnd])
|
|
|
|
const {
|
|
events,
|
|
isLoading: eventsLoading,
|
|
mutate: mutateEvents,
|
|
} = useEvents(activeRoomId, from, to)
|
|
|
|
const { myResponses } = useMyResponses(events, user.id)
|
|
|
|
const activeRoom = rooms.find((r) => r.id === activeRoomId)
|
|
const background = getRoomBackground(activeRoomId)
|
|
|
|
const refreshAll = () => {
|
|
mutateEvents()
|
|
mutateRooms()
|
|
}
|
|
|
|
return (
|
|
<div className="relative h-screen w-full overflow-hidden bg-slate-900">
|
|
<RoomBackground src={background} />
|
|
<div className="absolute inset-0 bg-black/35" />
|
|
|
|
<div className="relative z-10 flex h-full w-full">
|
|
{/* Mobile sidebar backdrop */}
|
|
{sidebarOpen && (
|
|
<div
|
|
className="fixed inset-0 z-30 bg-black/50 md:hidden"
|
|
onClick={() => setSidebarOpen(false)}
|
|
/>
|
|
)}
|
|
|
|
<div
|
|
className={`fixed z-40 h-full transition-transform duration-300 md:relative md:translate-x-0 ${
|
|
sidebarOpen ? "translate-x-0" : "-translate-x-full"
|
|
}`}
|
|
>
|
|
<Sidebar
|
|
rooms={rooms}
|
|
activeRoomId={activeRoomId}
|
|
onSelectRoom={(id) => {
|
|
setActiveRoomId(id)
|
|
setSidebarOpen(false)
|
|
}}
|
|
selectedDate={selectedDate}
|
|
onSelectDate={setSelectedDate}
|
|
onCreateEvent={() => setDialog({ type: "create-event" })}
|
|
onCreateRoom={() => setDialog({ type: "create-room" })}
|
|
onJoinRoom={() => setDialog({ type: "join-room" })}
|
|
user={user}
|
|
onLogout={onLogout}
|
|
onOpenAccountSettings={() => setDialog({ type: "account-settings" })}
|
|
/>
|
|
</div>
|
|
|
|
<main className="flex h-full flex-1 flex-col overflow-hidden">
|
|
<header className="flex items-center justify-between gap-3 border-b border-white/10 bg-white/5 px-4 py-3 backdrop-blur-md md:px-6">
|
|
<div className="flex items-center gap-3">
|
|
<button
|
|
className="rounded-lg p-2 text-white/80 hover:bg-white/10 md:hidden"
|
|
onClick={() => setSidebarOpen(true)}
|
|
aria-label="Open menu"
|
|
>
|
|
<Menu className="h-5 w-5" />
|
|
</button>
|
|
<div>
|
|
<h1 className="text-lg font-semibold text-white">
|
|
{activeRoom ? activeRoom.name : "All Rooms"}
|
|
</h1>
|
|
<p className="text-sm text-white/60">{formatMonthYear(weekStart)}</p>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex items-center gap-2">
|
|
{activeRoom && (
|
|
<button
|
|
onClick={() => setDialog({ type: "room-settings" })}
|
|
className="rounded-lg px-3 py-2 text-sm text-white/80 hover:bg-white/10"
|
|
>
|
|
Room settings
|
|
</button>
|
|
)}
|
|
<button
|
|
onClick={() => setDialog({ type: "create-event" })}
|
|
className="flex items-center gap-1.5 rounded-xl bg-white/15 px-3 py-2 text-sm font-medium text-white shadow-lg backdrop-blur-md hover:bg-white/25"
|
|
>
|
|
<Plus className="h-4 w-4" />
|
|
New Event
|
|
</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div className="min-h-0 flex-1 overflow-auto p-3 md:p-6">
|
|
{eventsLoading ? (
|
|
<div className="flex h-full items-center justify-center">
|
|
<Loader2 className="h-6 w-6 animate-spin text-white/60" />
|
|
</div>
|
|
) : (
|
|
<WeekView
|
|
weekStart={weekStart}
|
|
events={events}
|
|
myResponses={myResponses}
|
|
onEventClick={(event) => setDialog({ type: "event-detail", event })}
|
|
/>
|
|
)}
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
{(dialog.type === "create-event" || dialog.type === "edit-event") && (
|
|
<CreateEventDialog
|
|
rooms={rooms}
|
|
defaultRoomId={activeRoomId}
|
|
defaultDate={selectedDate}
|
|
editingEvent={dialog.type === "edit-event" ? dialog.event : undefined}
|
|
onClose={() => setDialog({ type: "none" })}
|
|
onCreated={() => {
|
|
setDialog({ type: "none" })
|
|
refreshAll()
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{dialog.type === "event-detail" && (
|
|
<EventDetailDialog
|
|
event={dialog.event}
|
|
room={rooms.find((r) => r.id === dialog.event.room_id)}
|
|
currentUserId={user.id}
|
|
onClose={() => setDialog({ type: "none" })}
|
|
onChanged={refreshAll}
|
|
onEdit={() => setDialog({ type: "edit-event", event: dialog.event })}
|
|
/>
|
|
)}
|
|
|
|
{dialog.type === "create-room" && (
|
|
<CreateRoomDialog
|
|
onClose={() => setDialog({ type: "none" })}
|
|
onCreated={(roomId: number) => {
|
|
setDialog({ type: "none" })
|
|
mutateRooms()
|
|
setActiveRoomId(roomId)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{dialog.type === "join-room" && (
|
|
<JoinRoomDialog
|
|
onClose={() => setDialog({ type: "none" })}
|
|
onJoined={(roomId: number) => {
|
|
setDialog({ type: "none" })
|
|
mutateRooms()
|
|
setActiveRoomId(roomId)
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{dialog.type === "room-settings" && activeRoom && (
|
|
<RoomSettingsDialog
|
|
room={activeRoom}
|
|
currentUserId={user.id}
|
|
onClose={() => setDialog({ type: "none" })}
|
|
onChanged={mutateRooms}
|
|
onLeftOrDeleted={() => {
|
|
setDialog({ type: "none" })
|
|
setActiveRoomId(null)
|
|
mutateRooms()
|
|
}}
|
|
/>
|
|
)}
|
|
|
|
{dialog.type === "account-settings" && (
|
|
<AccountSettingsDialog
|
|
user={user}
|
|
onClose={() => setDialog({ type: "none" })}
|
|
onLogout={onLogout}
|
|
/>
|
|
)}
|
|
</div>
|
|
)
|
|
} |