new frontend

ik ben zo cool
This commit is contained in:
ben
2026-08-19 09:33:48 +02:00
parent 1822dea7e3
commit bf601846db
104 changed files with 16741 additions and 0 deletions
@@ -0,0 +1,62 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
:root {
--background: 0 0% 100%;
--foreground: 222.2 84% 4.9%;
--card: 0 0% 100%;
--card-foreground: 222.2 84% 4.9%;
--popover: 0 0% 100%;
--popover-foreground: 222.2 84% 4.9%;
--primary: 221.2 83.2% 53.3%;
--primary-foreground: 210 40% 98%;
--secondary: 210 40% 96.1%;
--secondary-foreground: 222.2 47.4% 11.2%;
--muted: 210 40% 96.1%;
--muted-foreground: 215.4 16.3% 46.9%;
--accent: 210 40% 96.1%;
--accent-foreground: 222.2 47.4% 11.2%;
--destructive: 0 84.2% 60.2%;
--destructive-foreground: 210 40% 98%;
--border: 214.3 31.8% 91.4%;
--input: 214.3 31.8% 91.4%;
--ring: 221.2 83.2% 53.3%;
--radius: 0.5rem;
}
.dark {
--background: 222.2 84% 4.9%;
--foreground: 210 40% 98%;
--card: 222.2 84% 4.9%;
--card-foreground: 210 40% 98%;
--popover: 222.2 84% 4.9%;
--popover-foreground: 210 40% 98%;
--primary: 217.2 91.2% 59.8%;
--primary-foreground: 222.2 47.4% 11.2%;
--secondary: 217.2 32.6% 17.5%;
--secondary-foreground: 210 40% 98%;
--muted: 217.2 32.6% 17.5%;
--muted-foreground: 215 20.2% 65.1%;
--accent: 217.2 32.6% 17.5%;
--accent-foreground: 210 40% 98%;
--destructive: 0 62.8% 30.6%;
--destructive-foreground: 210 40% 98%;
--border: 217.2 32.6% 17.5%;
--input: 217.2 32.6% 17.5%;
--ring: 224.3 76.3% 48%;
}
@layer utilities {
.bg-grid-slate-200\/50 {
background-image: linear-gradient(currentColor 1px, transparent 1px),
linear-gradient(to right, currentColor 1px, transparent 1px);
background-size: 20px 20px;
}
}
.react-draggable {
backdrop-filter: blur(16px);
-webkit-backdrop-filter: blur(16px);
}
@@ -0,0 +1,27 @@
import type React from "react"
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import "./globals.css"
import { AuthProvider } from "@/lib/auth-context"
const inter = Inter({ subsets: ["latin"] })
export const metadata: Metadata = {
title: "Calendar | Rooms & Events",
description: "A shared, room-based calendar for planning events together.",
generator: "v0.app",
}
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode
}>) {
return (
<html lang="en" className="bg-background">
<body className={inter.className}>
<AuthProvider>{children}</AuthProvider>
</body>
</html>
)
}
@@ -0,0 +1,4 @@
export default function Loading() {
return null
}
+229
View File
@@ -0,0 +1,229 @@
"use client"
import { useMemo, useState } from "react"
import Image from "next/image"
import { Menu, Plus, Loader2 } from "lucide-react"
import { useAuth } from "@/lib/auth-context"
import { useRooms, useEvents } 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 { 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: "event-detail"; event: CalendarEvent }
| { type: "create-room" }
| { type: "join-room" }
| { type: "room-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 activeRoom = rooms.find((r) => r.id === activeRoomId)
const background = getRoomBackground(activeRoomId)
const refreshAll = () => {
mutateEvents()
mutateRooms()
}
return (
<div className="relative min-h-screen w-full overflow-hidden bg-slate-900">
<Image
src={background || "/placeholder.svg"}
alt=""
fill
priority
className="object-cover transition-all duration-700"
/>
<div className="absolute inset-0 bg-black/35" />
<div className="relative z-10 flex min-h-screen 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}
/>
</div>
<main className="flex min-h-screen 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="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}
showRoomColors={activeRoomId === null}
onEventClick={(event) => setDialog({ type: "event-detail", event })}
/>
)}
</div>
</main>
</div>
{dialog.type === "create-event" && (
<CreateEventDialog
rooms={rooms}
defaultRoomId={activeRoomId}
defaultDate={selectedDate}
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}
/>
)}
{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()
}}
/>
)}
</div>
)
}