Some checks failed
ci/woodpecker/push/web Pipeline failed
Co-authored-by: Jason Woltje <jason@diversecanvas.com> Co-committed-by: Jason Woltje <jason@diversecanvas.com>
165 lines
4.4 KiB
TypeScript
165 lines
4.4 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import type { ReactElement } from "react";
|
|
import { Calendar } from "@/components/calendar/Calendar";
|
|
import { fetchEvents } from "@/lib/api/events";
|
|
import { MosaicSpinner } from "@/components/ui/MosaicSpinner";
|
|
import { useWorkspaceId } from "@/lib/hooks";
|
|
import type { Event } from "@mosaic/shared";
|
|
|
|
export default function CalendarPage(): ReactElement {
|
|
const workspaceId = useWorkspaceId();
|
|
const [events, setEvents] = useState<Event[]>([]);
|
|
const [isLoading, setIsLoading] = useState(true);
|
|
const [error, setError] = useState<string | null>(null);
|
|
|
|
useEffect(() => {
|
|
if (!workspaceId) {
|
|
setIsLoading(false);
|
|
return;
|
|
}
|
|
|
|
const wsId = workspaceId;
|
|
let cancelled = false;
|
|
setError(null);
|
|
setIsLoading(true);
|
|
|
|
async function loadEvents(): Promise<void> {
|
|
try {
|
|
const data = await fetchEvents(wsId);
|
|
if (!cancelled) {
|
|
setEvents(data);
|
|
}
|
|
} catch (err: unknown) {
|
|
console.error("[Calendar] Failed to fetch events:", err);
|
|
if (!cancelled) {
|
|
setError(
|
|
err instanceof Error
|
|
? err.message
|
|
: "We had trouble loading your calendar. Please try again when you're ready."
|
|
);
|
|
}
|
|
} finally {
|
|
if (!cancelled) {
|
|
setIsLoading(false);
|
|
}
|
|
}
|
|
}
|
|
|
|
void loadEvents();
|
|
|
|
return (): void => {
|
|
cancelled = true;
|
|
};
|
|
}, [workspaceId]);
|
|
|
|
function handleRetry(): void {
|
|
if (!workspaceId) return;
|
|
|
|
const wsId = workspaceId;
|
|
setError(null);
|
|
setIsLoading(true);
|
|
|
|
fetchEvents(wsId)
|
|
.then((data) => {
|
|
setEvents(data);
|
|
})
|
|
.catch((err: unknown) => {
|
|
console.error("[Calendar] Retry failed:", err);
|
|
setError(
|
|
err instanceof Error
|
|
? err.message
|
|
: "We had trouble loading your calendar. Please try again when you're ready."
|
|
);
|
|
})
|
|
.finally(() => {
|
|
setIsLoading(false);
|
|
});
|
|
}
|
|
|
|
if (isLoading) {
|
|
return (
|
|
<main className="container mx-auto px-4 py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold" style={{ color: "var(--text)" }}>
|
|
Calendar
|
|
</h1>
|
|
<p style={{ color: "var(--text-muted)" }} className="mt-2">
|
|
View your schedule at a glance
|
|
</p>
|
|
</div>
|
|
<div className="flex justify-center py-16">
|
|
<MosaicSpinner label="Loading calendar..." />
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
if (error !== null) {
|
|
return (
|
|
<main className="container mx-auto px-4 py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold" style={{ color: "var(--text)" }}>
|
|
Calendar
|
|
</h1>
|
|
<p style={{ color: "var(--text-muted)" }} className="mt-2">
|
|
View your schedule at a glance
|
|
</p>
|
|
</div>
|
|
<div
|
|
className="rounded-lg p-6 text-center"
|
|
style={{
|
|
background: "var(--surface)",
|
|
border: "1px solid var(--border)",
|
|
}}
|
|
>
|
|
<p style={{ color: "var(--danger)" }}>{error}</p>
|
|
<button
|
|
onClick={handleRetry}
|
|
className="mt-4 rounded-md px-4 py-2 text-sm font-medium transition-colors"
|
|
style={{
|
|
background: "var(--accent)",
|
|
color: "var(--surface)",
|
|
}}
|
|
>
|
|
Try again
|
|
</button>
|
|
</div>
|
|
</main>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<main className="container mx-auto px-4 py-8">
|
|
<div className="mb-8">
|
|
<h1 className="text-3xl font-bold" style={{ color: "var(--text)" }}>
|
|
Calendar
|
|
</h1>
|
|
<p style={{ color: "var(--text-muted)" }} className="mt-2">
|
|
View your schedule at a glance
|
|
</p>
|
|
</div>
|
|
|
|
{events.length === 0 ? (
|
|
<div
|
|
className="rounded-lg p-8 text-center"
|
|
style={{
|
|
background: "var(--surface)",
|
|
border: "1px solid var(--border)",
|
|
}}
|
|
>
|
|
<p className="text-lg" style={{ color: "var(--text-muted)" }}>
|
|
No events scheduled
|
|
</p>
|
|
<p className="text-sm mt-2" style={{ color: "var(--text-muted)" }}>
|
|
Your calendar is clear
|
|
</p>
|
|
</div>
|
|
) : (
|
|
<Calendar events={events} isLoading={false} />
|
|
)}
|
|
</main>
|
|
);
|
|
}
|