new frontend
ik ben zo cool
This commit is contained in:
@@ -0,0 +1,220 @@
|
||||
"use client"
|
||||
|
||||
import { useState } from "react"
|
||||
import {
|
||||
ChevronLeft,
|
||||
ChevronRight,
|
||||
Plus,
|
||||
LogOut,
|
||||
Users,
|
||||
DoorOpen,
|
||||
Layers,
|
||||
Crown,
|
||||
Shield,
|
||||
} from "lucide-react"
|
||||
import type { Room } from "@/lib/types"
|
||||
import type { User } from "@/lib/types"
|
||||
import { getRoomColor } from "@/lib/room-theme"
|
||||
import { addDays, isSameDay, startOfWeek, formatMonthYear } from "@/lib/date-utils"
|
||||
|
||||
interface SidebarProps {
|
||||
rooms: Room[]
|
||||
activeRoomId: number | null
|
||||
onSelectRoom: (id: number | null) => void
|
||||
selectedDate: Date
|
||||
onSelectDate: (d: Date) => void
|
||||
onCreateEvent: () => void
|
||||
onCreateRoom: () => void
|
||||
onJoinRoom: () => void
|
||||
user: User | null
|
||||
onLogout: () => void
|
||||
}
|
||||
|
||||
const WEEKDAY_MINI = ["S", "M", "T", "W", "T", "F", "S"]
|
||||
|
||||
function RoleBadge({ role }: { role: Room["role"] }) {
|
||||
if (role === "owner")
|
||||
return <Crown className="h-3.5 w-3.5 text-amber-300" aria-label="Owner" />
|
||||
if (role === "admin")
|
||||
return <Shield className="h-3.5 w-3.5 text-blue-200" aria-label="Admin" />
|
||||
return null
|
||||
}
|
||||
|
||||
export function Sidebar({
|
||||
rooms,
|
||||
activeRoomId,
|
||||
onSelectRoom,
|
||||
selectedDate,
|
||||
onSelectDate,
|
||||
onCreateEvent,
|
||||
onCreateRoom,
|
||||
onJoinRoom,
|
||||
user,
|
||||
onLogout,
|
||||
}: SidebarProps) {
|
||||
const [miniMonth, setMiniMonth] = useState(() => {
|
||||
const d = new Date(selectedDate)
|
||||
d.setDate(1)
|
||||
return d
|
||||
})
|
||||
|
||||
const weekStart = startOfWeek(selectedDate)
|
||||
const weekEnd = addDays(weekStart, 6)
|
||||
|
||||
// Build mini-calendar grid for miniMonth.
|
||||
const firstDay = new Date(miniMonth.getFullYear(), miniMonth.getMonth(), 1)
|
||||
const offset = firstDay.getDay()
|
||||
const daysInMonth = new Date(miniMonth.getFullYear(), miniMonth.getMonth() + 1, 0).getDate()
|
||||
const cells: (Date | null)[] = []
|
||||
for (let i = 0; i < offset; i++) cells.push(null)
|
||||
for (let d = 1; d <= daysInMonth; d++) {
|
||||
cells.push(new Date(miniMonth.getFullYear(), miniMonth.getMonth(), d))
|
||||
}
|
||||
|
||||
const today = new Date()
|
||||
|
||||
const shiftMonth = (delta: number) => {
|
||||
setMiniMonth((m) => new Date(m.getFullYear(), m.getMonth() + delta, 1))
|
||||
}
|
||||
|
||||
const inSelectedWeek = (d: Date) => d >= weekStart && d <= weekEnd
|
||||
|
||||
return (
|
||||
<div className="flex h-full w-64 flex-col justify-between rounded-tr-3xl border-r border-white/20 bg-white/10 p-4 shadow-xl backdrop-blur-lg">
|
||||
<div className="flex min-h-0 flex-1 flex-col">
|
||||
<button
|
||||
onClick={onCreateEvent}
|
||||
className="mb-6 flex items-center justify-center gap-2 rounded-full bg-blue-500 px-4 py-3 text-white transition-colors hover:bg-blue-600"
|
||||
>
|
||||
<Plus className="h-5 w-5" />
|
||||
<span>Create event</span>
|
||||
</button>
|
||||
|
||||
{/* Mini Calendar */}
|
||||
<div className="mb-6">
|
||||
<div className="mb-3 flex items-center justify-between">
|
||||
<h3 className="font-medium text-white">{formatMonthYear(miniMonth)}</h3>
|
||||
<div className="flex gap-1">
|
||||
<button
|
||||
onClick={() => shiftMonth(-1)}
|
||||
className="rounded-full p-1 hover:bg-white/20"
|
||||
aria-label="Previous month"
|
||||
>
|
||||
<ChevronLeft className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
<button
|
||||
onClick={() => shiftMonth(1)}
|
||||
className="rounded-full p-1 hover:bg-white/20"
|
||||
aria-label="Next month"
|
||||
>
|
||||
<ChevronRight className="h-4 w-4 text-white" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-7 gap-1 text-center">
|
||||
{WEEKDAY_MINI.map((day, i) => (
|
||||
<div key={i} className="py-1 text-xs font-medium text-white/70">
|
||||
{day}
|
||||
</div>
|
||||
))}
|
||||
{cells.map((date, i) => {
|
||||
if (!date) return <div key={i} />
|
||||
const selected = isSameDay(date, selectedDate)
|
||||
const isToday = isSameDay(date, today)
|
||||
const highlightWeek = inSelectedWeek(date) && !selected
|
||||
return (
|
||||
<button
|
||||
key={i}
|
||||
onClick={() => onSelectDate(date)}
|
||||
className={`flex h-7 w-7 items-center justify-center rounded-full text-xs transition-colors ${
|
||||
selected
|
||||
? "bg-blue-500 font-semibold text-white"
|
||||
: highlightWeek
|
||||
? "bg-white/15 text-white"
|
||||
: "text-white hover:bg-white/20"
|
||||
} ${isToday && !selected ? "ring-1 ring-white/60" : ""}`}
|
||||
>
|
||||
{date.getDate()}
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Rooms */}
|
||||
<div className="mb-2 flex items-center justify-between">
|
||||
<h3 className="font-medium text-white">Rooms</h3>
|
||||
</div>
|
||||
<div className="-mr-1 flex-1 space-y-1 overflow-y-auto pr-1">
|
||||
<button
|
||||
onClick={() => onSelectRoom(null)}
|
||||
className={`flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left text-sm transition-colors ${
|
||||
activeRoomId === null ? "bg-white/25 text-white" : "text-white/90 hover:bg-white/15"
|
||||
}`}
|
||||
>
|
||||
<Layers className="h-4 w-4 shrink-0" />
|
||||
<span className="truncate">All rooms</span>
|
||||
</button>
|
||||
|
||||
{rooms.map((room) => {
|
||||
const color = getRoomColor(room.id)
|
||||
const active = activeRoomId === room.id
|
||||
return (
|
||||
<button
|
||||
key={room.id}
|
||||
onClick={() => onSelectRoom(room.id)}
|
||||
className={`flex w-full items-center gap-3 rounded-lg px-2 py-2 text-left text-sm transition-colors ${
|
||||
active ? "bg-white/25 text-white" : "text-white/90 hover:bg-white/15"
|
||||
}`}
|
||||
>
|
||||
<span className={`h-3 w-3 shrink-0 rounded-sm ${color.dot}`} />
|
||||
<span className="flex-1 truncate">{room.name}</span>
|
||||
<RoleBadge role={room.role} />
|
||||
</button>
|
||||
)
|
||||
})}
|
||||
|
||||
{rooms.length === 0 && (
|
||||
<p className="px-2 py-2 text-xs text-white/60">
|
||||
No rooms yet. Create or join one to get started.
|
||||
</p>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className="mt-3 flex gap-2">
|
||||
<button
|
||||
onClick={onCreateRoom}
|
||||
className="flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-white/20 bg-white/10 px-2 py-2 text-xs text-white transition-colors hover:bg-white/20"
|
||||
>
|
||||
<Plus className="h-3.5 w-3.5" /> New
|
||||
</button>
|
||||
<button
|
||||
onClick={onJoinRoom}
|
||||
className="flex flex-1 items-center justify-center gap-1.5 rounded-lg border border-white/20 bg-white/10 px-2 py-2 text-xs text-white transition-colors hover:bg-white/20"
|
||||
>
|
||||
<DoorOpen className="h-3.5 w-3.5" /> Join
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* User footer */}
|
||||
<div className="mt-4 flex items-center gap-3 border-t border-white/20 pt-4">
|
||||
<div className="flex h-9 w-9 items-center justify-center rounded-full bg-blue-500 font-bold text-white">
|
||||
{user?.username?.charAt(0).toUpperCase() ?? "U"}
|
||||
</div>
|
||||
<div className="min-w-0 flex-1">
|
||||
<p className="truncate text-sm font-medium text-white">{user?.username}</p>
|
||||
<p className="truncate text-xs text-white/60">{user?.email}</p>
|
||||
</div>
|
||||
<button
|
||||
onClick={onLogout}
|
||||
className="rounded-lg p-2 text-white/80 transition-colors hover:bg-white/20 hover:text-white"
|
||||
aria-label="Sign out"
|
||||
>
|
||||
<LogOut className="h-4 w-4" />
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
Reference in New Issue
Block a user