style(app): standardize app page file formatting
This commit is contained in:
@@ -1,23 +1,25 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card"
|
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
import Link from "next/link"
|
import Link from "next/link";
|
||||||
import { useSearchParams } from "next/navigation"
|
import { useSearchParams } from "next/navigation";
|
||||||
import { Suspense } from "react"
|
import { Suspense } from "react";
|
||||||
|
|
||||||
function Search() {
|
function Search() {
|
||||||
const searchParams = useSearchParams()
|
const searchParams = useSearchParams();
|
||||||
const errorMessage = searchParams.get('error')
|
const errorMessage = searchParams.get("error");
|
||||||
|
|
||||||
// Sanitize error message to prevent XSS
|
// Sanitize error message to prevent XSS
|
||||||
const sanitizedError = errorMessage
|
const sanitizedError = errorMessage
|
||||||
? errorMessage.replace(/[<>]/g, '')
|
? errorMessage.replace(/[<>]/g, "")
|
||||||
: 'An authentication error occurred'
|
: "An authentication error occurred";
|
||||||
|
|
||||||
return (<div className="text-center p-3 bg-background rounded-lg">
|
return (
|
||||||
|
<div className="text-center p-3 bg-background rounded-lg">
|
||||||
{sanitizedError}
|
{sanitizedError}
|
||||||
</div>)
|
</div>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function AuthErrorPage() {
|
export default function AuthErrorPage() {
|
||||||
@@ -39,5 +41,5 @@ export default function AuthErrorPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,44 +1,50 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { signIn, useSession } from "@/lib/auth-client"
|
import { signIn, useSession } from "@/lib/auth-client";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import {
|
||||||
import Link from "next/link"
|
Card,
|
||||||
import { useRouter } from "next/navigation"
|
CardContent,
|
||||||
import { useEffect, useState } from "react"
|
CardDescription,
|
||||||
import { toast } from "sonner"
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
export default function SignInPage() {
|
export default function SignInPage() {
|
||||||
const { data: session, isPending } = useSession()
|
const { data: session, isPending } = useSession();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
const [isLoading, setIsLoading] = useState(false)
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
router.push("/")
|
router.push("/");
|
||||||
}
|
}
|
||||||
}, [session, router])
|
}, [session, router]);
|
||||||
|
|
||||||
const handleSignIn = async () => {
|
const handleSignIn = async () => {
|
||||||
setIsLoading(true)
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await signIn.oauth2({
|
await signIn.oauth2({
|
||||||
providerId: "authentik",
|
providerId: "authentik",
|
||||||
callbackURL: "/",
|
callbackURL: "/",
|
||||||
})
|
});
|
||||||
} catch (_error) {
|
} catch (_error) {
|
||||||
toast.error("Failed to sign in. Please try again.")
|
toast.error("Failed to sign in. Please try again.");
|
||||||
} finally {
|
} finally {
|
||||||
setIsLoading(false)
|
setIsLoading(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
if (isPending) {
|
if (isPending) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (session?.user) {
|
if (session?.user) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -51,17 +57,25 @@ export default function SignInPage() {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<Button onClick={handleSignIn} className="w-full" size="lg" disabled={isLoading}>
|
<Button
|
||||||
|
onClick={handleSignIn}
|
||||||
|
className="w-full"
|
||||||
|
size="lg"
|
||||||
|
disabled={isLoading}
|
||||||
|
>
|
||||||
{isLoading ? "Signing in..." : "Continue with Authentik"}
|
{isLoading ? "Signing in..." : "Continue with Authentik"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
<div className="text-center">
|
<div className="text-center">
|
||||||
<Link href="/" className="text-sm text-muted-foreground hover:underline">
|
<Link
|
||||||
|
href="/"
|
||||||
|
className="text-sm text-muted-foreground hover:underline"
|
||||||
|
>
|
||||||
Continue without signing in
|
Continue without signing in
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,29 +1,35 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { signOut, useSession } from "@/lib/auth-client"
|
import { signOut, useSession } from "@/lib/auth-client";
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button";
|
||||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card"
|
import {
|
||||||
import Link from "next/link"
|
Card,
|
||||||
import { useRouter } from "next/navigation"
|
CardContent,
|
||||||
import { useEffect } from "react"
|
CardDescription,
|
||||||
|
CardHeader,
|
||||||
|
CardTitle,
|
||||||
|
} from "@/components/ui/card";
|
||||||
|
import Link from "next/link";
|
||||||
|
import { useRouter } from "next/navigation";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
|
||||||
export default function SignOutPage() {
|
export default function SignOutPage() {
|
||||||
const { data: session, isPending } = useSession()
|
const { data: session, isPending } = useSession();
|
||||||
const router = useRouter()
|
const router = useRouter();
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!session?.user) {
|
if (!session?.user) {
|
||||||
router.push("/")
|
router.push("/");
|
||||||
}
|
}
|
||||||
}, [session, router])
|
}, [session, router]);
|
||||||
|
|
||||||
const handleSignOut = async () => {
|
const handleSignOut = async () => {
|
||||||
await signOut()
|
await signOut();
|
||||||
router.push("/")
|
router.push("/");
|
||||||
}
|
};
|
||||||
|
|
||||||
if (isPending || !session?.user) {
|
if (isPending || !session?.user) {
|
||||||
return null
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -31,18 +37,24 @@ export default function SignOutPage() {
|
|||||||
<Card className="w-full max-w-md">
|
<Card className="w-full max-w-md">
|
||||||
<CardHeader className="text-center">
|
<CardHeader className="text-center">
|
||||||
<CardTitle className="text-2xl font-bold">Sign Out</CardTitle>
|
<CardTitle className="text-2xl font-bold">Sign Out</CardTitle>
|
||||||
<CardDescription>
|
<CardDescription>Are you sure you want to sign out?</CardDescription>
|
||||||
Are you sure you want to sign out?
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
<div className="text-center p-3 bg-muted rounded-lg">
|
<div className="text-center p-3 bg-muted rounded-lg">
|
||||||
<div className="text-sm text-muted-foreground">Currently signed in as</div>
|
<div className="text-sm text-muted-foreground">
|
||||||
<div className="font-medium">{session.user?.name || session.user?.email}</div>
|
Currently signed in as
|
||||||
|
</div>
|
||||||
|
<div className="font-medium">
|
||||||
|
{session.user?.name || session.user?.email}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div className="grid grid-cols-2 gap-3">
|
||||||
<Button onClick={handleSignOut} variant="destructive" className="w-full">
|
<Button
|
||||||
|
onClick={handleSignOut}
|
||||||
|
variant="destructive"
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
Sign Out
|
Sign Out
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
@@ -53,5 +65,5 @@ export default function SignOutPage() {
|
|||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
</div>
|
</div>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,17 +5,24 @@ import { ThemeProvider } from "next-themes";
|
|||||||
import { ModeToggle } from "@/components/mode-toggle";
|
import { ModeToggle } from "@/components/mode-toggle";
|
||||||
import SignIn from "@/components/sign-in";
|
import SignIn from "@/components/sign-in";
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import Link from "next/link"
|
import Link from "next/link";
|
||||||
|
|
||||||
const geist = Geist({ subsets: ['latin', 'cyrillic'], variable: "--font-geist-sans" })
|
const geist = Geist({
|
||||||
|
subsets: ["latin", "cyrillic"],
|
||||||
|
variable: "--font-geist-sans",
|
||||||
|
});
|
||||||
|
|
||||||
const magra = Magra({ subsets: ["latin"], weight: "400", variable: "--font-cascadia-code" })
|
const magra = Magra({
|
||||||
|
subsets: ["latin"],
|
||||||
|
weight: "400",
|
||||||
|
variable: "--font-cascadia-code",
|
||||||
|
});
|
||||||
|
|
||||||
export const metadata: Metadata = {
|
export const metadata: Metadata = {
|
||||||
title: 'Local iCal',
|
title: "Local iCal",
|
||||||
description: 'Local iCal editor for calendar events',
|
description: "Local iCal editor for calendar events",
|
||||||
creator: "Dmytro Stanchiev",
|
creator: "Dmytro Stanchiev",
|
||||||
}
|
};
|
||||||
|
|
||||||
export default function RootLayout({
|
export default function RootLayout({
|
||||||
children,
|
children,
|
||||||
@@ -36,7 +43,7 @@ export default function RootLayout({
|
|||||||
<header className="dark:text-white text-gray-900 px-4 py-3 font-bold flex justify-between items-center-safe">
|
<header className="dark:text-white text-gray-900 px-4 py-3 font-bold flex justify-between items-center-safe">
|
||||||
<Link href={"/"}>
|
<Link href={"/"}>
|
||||||
<p className={`${magra.variable}`}>
|
<p className={`${magra.variable}`}>
|
||||||
{metadata.title as string || "iCal PWA"}
|
{(metadata.title as string) || "iCal PWA"}
|
||||||
</p>
|
</p>
|
||||||
</Link>
|
</Link>
|
||||||
<div className="flex flex-row gap-2">
|
<div className="flex flex-row gap-2">
|
||||||
|
|||||||
326
src/app/page.tsx
326
src/app/page.tsx
@@ -1,62 +1,70 @@
|
|||||||
"use client"
|
"use client";
|
||||||
|
|
||||||
import { useEffect, useState } from 'react'
|
import { useEffect, useState } from "react";
|
||||||
import { nanoid } from 'nanoid'
|
import { nanoid } from "nanoid";
|
||||||
import { useSession } from '@/lib/auth-client'
|
import { useSession } from "@/lib/auth-client";
|
||||||
import { toast } from 'sonner'
|
import { toast } from "sonner";
|
||||||
|
|
||||||
import { saveEvent as addEvent, deleteEvent, getEvents as getAllEvents, clearEvents, updateEvent } from '@/lib/events-db'
|
import {
|
||||||
import { parseICS, generateICS } from '@/lib/ical'
|
saveEvent as addEvent,
|
||||||
import type { CalendarEvent } from '@/lib/types'
|
deleteEvent,
|
||||||
|
getEvents as getAllEvents,
|
||||||
|
clearEvents,
|
||||||
|
updateEvent,
|
||||||
|
} from "@/lib/events-db";
|
||||||
|
import { parseICS, generateICS } from "@/lib/ical";
|
||||||
|
import type { CalendarEvent } from "@/lib/types";
|
||||||
|
|
||||||
import { AIToolbar } from '@/components/ai-toolbar'
|
import { AIToolbar } from "@/components/ai-toolbar";
|
||||||
import { EventActionsToolbar } from '@/components/event-actions-toolbar'
|
import { EventActionsToolbar } from "@/components/event-actions-toolbar";
|
||||||
import { EventsList } from '@/components/events-list'
|
import { EventsList } from "@/components/events-list";
|
||||||
import { EventDialog } from '@/components/event-dialog'
|
import { EventDialog } from "@/components/event-dialog";
|
||||||
import { DragDropContainer } from '@/components/drag-drop-container'
|
import { DragDropContainer } from "@/components/drag-drop-container";
|
||||||
|
|
||||||
export default function HomePage() {
|
export default function HomePage() {
|
||||||
const [events, setEvents] = useState<CalendarEvent[]>([])
|
const [events, setEvents] = useState<CalendarEvent[]>([]);
|
||||||
const [dialogOpen, setDialogOpen] = useState(false)
|
const [dialogOpen, setDialogOpen] = useState(false);
|
||||||
const [editingId, setEditingId] = useState<string | null>(null)
|
const [editingId, setEditingId] = useState<string | null>(null);
|
||||||
const [isDragOver, setIsDragOver] = useState(false)
|
const [isDragOver, setIsDragOver] = useState(false);
|
||||||
|
|
||||||
// Form fields
|
// Form fields
|
||||||
const [title, setTitle] = useState('')
|
const [title, setTitle] = useState("");
|
||||||
const [description, setDescription] = useState('')
|
const [description, setDescription] = useState("");
|
||||||
const [location, setLocation] = useState('')
|
const [location, setLocation] = useState("");
|
||||||
const [url, setUrl] = useState('')
|
const [url, setUrl] = useState("");
|
||||||
const [start, setStart] = useState('')
|
const [start, setStart] = useState("");
|
||||||
const [end, setEnd] = useState('')
|
const [end, setEnd] = useState("");
|
||||||
const [allDay, setAllDay] = useState(false)
|
const [allDay, setAllDay] = useState(false);
|
||||||
const [recurrenceRule, setRecurrenceRule] = useState<string | undefined>(undefined)
|
const [recurrenceRule, setRecurrenceRule] = useState<string | undefined>(
|
||||||
|
undefined,
|
||||||
|
);
|
||||||
|
|
||||||
// AI
|
// AI
|
||||||
const [aiPrompt, setAiPrompt] = useState('')
|
const [aiPrompt, setAiPrompt] = useState("");
|
||||||
const [aiLoading, setAiLoading] = useState(false)
|
const [aiLoading, setAiLoading] = useState(false);
|
||||||
const [summary, setSummary] = useState<string | null>(null)
|
const [summary, setSummary] = useState<string | null>(null);
|
||||||
const [summaryUpdated, setSummaryUpdated] = useState<string | null>(null)
|
const [summaryUpdated, setSummaryUpdated] = useState<string | null>(null);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
(async () => {
|
(async () => {
|
||||||
const stored = await getAllEvents()
|
const stored = await getAllEvents();
|
||||||
setEvents(stored)
|
setEvents(stored);
|
||||||
})()
|
})();
|
||||||
}, [])
|
}, []);
|
||||||
|
|
||||||
const { data: session, isPending } = useSession()
|
const { data: session, isPending } = useSession();
|
||||||
|
|
||||||
const resetForm = () => {
|
const resetForm = () => {
|
||||||
setTitle('')
|
setTitle("");
|
||||||
setDescription('')
|
setDescription("");
|
||||||
setLocation('')
|
setLocation("");
|
||||||
setUrl('')
|
setUrl("");
|
||||||
setStart('')
|
setStart("");
|
||||||
setEnd('')
|
setEnd("");
|
||||||
setAllDay(false)
|
setAllDay(false);
|
||||||
setEditingId(null)
|
setEditingId(null);
|
||||||
setRecurrenceRule(undefined)
|
setRecurrenceRule(undefined);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleSave = async () => {
|
const handleSave = async () => {
|
||||||
const eventData: CalendarEvent = {
|
const eventData: CalendarEvent = {
|
||||||
@@ -70,96 +78,98 @@ export default function HomePage() {
|
|||||||
end: end || undefined,
|
end: end || undefined,
|
||||||
allDay,
|
allDay,
|
||||||
createdAt: editingId
|
createdAt: editingId
|
||||||
? events.find(e => e.id === editingId)?.createdAt
|
? events.find((e) => e.id === editingId)?.createdAt
|
||||||
: new Date().toISOString(),
|
: new Date().toISOString(),
|
||||||
lastModified: new Date().toISOString(),
|
lastModified: new Date().toISOString(),
|
||||||
}
|
};
|
||||||
if (editingId) {
|
if (editingId) {
|
||||||
await updateEvent(eventData)
|
await updateEvent(eventData);
|
||||||
setEvents(prev => prev.map(e => (e.id === editingId ? eventData : e)))
|
setEvents((prev) =>
|
||||||
|
prev.map((e) => (e.id === editingId ? eventData : e)),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await addEvent(eventData)
|
await addEvent(eventData);
|
||||||
setEvents(prev => [...prev, eventData])
|
setEvents((prev) => [...prev, eventData]);
|
||||||
}
|
|
||||||
resetForm()
|
|
||||||
setDialogOpen(false)
|
|
||||||
}
|
}
|
||||||
|
resetForm();
|
||||||
|
setDialogOpen(false);
|
||||||
|
};
|
||||||
|
|
||||||
const handleDelete = async (id: string) => {
|
const handleDelete = async (id: string) => {
|
||||||
await deleteEvent(id)
|
await deleteEvent(id);
|
||||||
setEvents(prev => prev.filter(e => e.id !== id))
|
setEvents((prev) => prev.filter((e) => e.id !== id));
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleClearAll = async () => {
|
const handleClearAll = async () => {
|
||||||
await clearEvents()
|
await clearEvents();
|
||||||
setEvents([])
|
setEvents([]);
|
||||||
}
|
};
|
||||||
|
|
||||||
const handleImport = async (file: File) => {
|
const handleImport = async (file: File) => {
|
||||||
const text = await file.text()
|
const text = await file.text();
|
||||||
const parsed = parseICS(text)
|
const parsed = parseICS(text);
|
||||||
for (const ev of parsed) {
|
for (const ev of parsed) {
|
||||||
await addEvent(ev)
|
await addEvent(ev);
|
||||||
}
|
|
||||||
const stored = await getAllEvents()
|
|
||||||
setEvents(stored)
|
|
||||||
}
|
}
|
||||||
|
const stored = await getAllEvents();
|
||||||
|
setEvents(stored);
|
||||||
|
};
|
||||||
|
|
||||||
const handleExport = () => {
|
const handleExport = () => {
|
||||||
const icsData = generateICS(events)
|
const icsData = generateICS(events);
|
||||||
const blob = new Blob([icsData], { type: 'text/calendar' })
|
const blob = new Blob([icsData], { type: "text/calendar" });
|
||||||
const url = URL.createObjectURL(blob)
|
const url = URL.createObjectURL(blob);
|
||||||
const a = document.createElement('a')
|
const a = document.createElement("a");
|
||||||
a.href = url
|
a.href = url;
|
||||||
a.download = `icallocal-export-${new Date().toLocaleTimeString()}.ics`
|
a.download = `icallocal-export-${new Date().toLocaleTimeString()}.ics`;
|
||||||
document.body.appendChild(a)
|
document.body.appendChild(a);
|
||||||
a.click()
|
a.click();
|
||||||
document.body.removeChild(a)
|
document.body.removeChild(a);
|
||||||
URL.revokeObjectURL(url)
|
URL.revokeObjectURL(url);
|
||||||
}
|
};
|
||||||
|
|
||||||
// AI Create Event
|
// AI Create Event
|
||||||
const handleAiCreate = async () => {
|
const handleAiCreate = async () => {
|
||||||
if (!aiPrompt.trim()) return
|
if (!aiPrompt.trim()) return;
|
||||||
setAiLoading(true)
|
setAiLoading(true);
|
||||||
|
|
||||||
const promise = (): Promise<{ message: string }> => new Promise(async (resolve, reject) => {
|
const promise = (): Promise<{ message: string }> =>
|
||||||
|
new Promise(async (resolve, reject) => {
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/ai-event', {
|
const res = await fetch("/api/ai-event", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ prompt: aiPrompt })
|
body: JSON.stringify({ prompt: aiPrompt }),
|
||||||
})
|
});
|
||||||
|
|
||||||
if (res.status === 401) {
|
if (res.status === 401) {
|
||||||
setAiLoading(false)
|
setAiLoading(false);
|
||||||
reject({
|
reject({
|
||||||
message: 'Please sign in to use AI features.'
|
message: "Please sign in to use AI features.",
|
||||||
})
|
});
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const data = await res.json()
|
const data = await res.json();
|
||||||
|
|
||||||
if (Array.isArray(data) && data.length > 0) {
|
if (Array.isArray(data) && data.length > 0) {
|
||||||
if (data.length === 1) {
|
if (data.length === 1) {
|
||||||
// Prefill dialog directly (same as before)
|
// Prefill dialog directly (same as before)
|
||||||
const ev = data[0]
|
const ev = data[0];
|
||||||
setTitle(ev.title || '')
|
setTitle(ev.title || "");
|
||||||
setDescription(ev.description || '')
|
setDescription(ev.description || "");
|
||||||
setLocation(ev.location || '')
|
setLocation(ev.location || "");
|
||||||
setUrl(ev.url || '')
|
setUrl(ev.url || "");
|
||||||
setStart(ev.start || '')
|
setStart(ev.start || "");
|
||||||
setEnd(ev.end || '')
|
setEnd(ev.end || "");
|
||||||
setAllDay(ev.allDay || false)
|
setAllDay(ev.allDay || false);
|
||||||
setEditingId(null)
|
setEditingId(null);
|
||||||
setAiPrompt("")
|
setAiPrompt("");
|
||||||
setDialogOpen(true)
|
setDialogOpen(true);
|
||||||
setRecurrenceRule(ev.recurrenceRule || undefined)
|
setRecurrenceRule(ev.recurrenceRule || undefined);
|
||||||
resolve({
|
resolve({
|
||||||
message: 'Event has been created!'
|
message: "Event has been created!",
|
||||||
})
|
});
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Save them all directly to DB
|
// Save them all directly to DB
|
||||||
for (const ev of data) {
|
for (const ev of data) {
|
||||||
@@ -168,86 +178,86 @@ export default function HomePage() {
|
|||||||
...ev,
|
...ev,
|
||||||
createdAt: new Date().toISOString(),
|
createdAt: new Date().toISOString(),
|
||||||
lastModified: new Date().toISOString(),
|
lastModified: new Date().toISOString(),
|
||||||
|
};
|
||||||
|
await addEvent(newEvent);
|
||||||
}
|
}
|
||||||
await addEvent(newEvent)
|
const stored = await getAllEvents();
|
||||||
}
|
setEvents(stored);
|
||||||
const stored = await getAllEvents()
|
setAiPrompt("");
|
||||||
setEvents(stored)
|
setSummary(`Added ${data.length} AI-generated events.`);
|
||||||
setAiPrompt("")
|
setSummaryUpdated(new Date().toLocaleString());
|
||||||
setSummary(`Added ${data.length} AI-generated events.`)
|
|
||||||
setSummaryUpdated(new Date().toLocaleString())
|
|
||||||
resolve({
|
resolve({
|
||||||
message: 'Event has been created!'
|
message: "Event has been created!",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
reject({
|
reject({
|
||||||
message: 'AI did not return event data.'
|
message: "AI did not return event data.",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
reject({
|
reject({
|
||||||
message: 'Error from AI service.'
|
message: "Error from AI service.",
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
})
|
});
|
||||||
|
|
||||||
toast.promise(promise, {
|
toast.promise(promise, {
|
||||||
loading: "Generating event...",
|
loading: "Generating event...",
|
||||||
success: ({ message }) => {
|
success: ({ message }) => {
|
||||||
return message
|
return message;
|
||||||
},
|
},
|
||||||
error: ({ message }) => {
|
error: ({ message }) => {
|
||||||
return message
|
return message;
|
||||||
}
|
},
|
||||||
})
|
});
|
||||||
|
|
||||||
setAiLoading(false)
|
setAiLoading(false);
|
||||||
}
|
};
|
||||||
|
|
||||||
// AI Summarize Events
|
// AI Summarize Events
|
||||||
const handleAiSummarize = async () => {
|
const handleAiSummarize = async () => {
|
||||||
if (!events.length) {
|
if (!events.length) {
|
||||||
setSummary("No events to summarize.")
|
setSummary("No events to summarize.");
|
||||||
setSummaryUpdated(new Date().toLocaleString())
|
setSummaryUpdated(new Date().toLocaleString());
|
||||||
return
|
return;
|
||||||
}
|
}
|
||||||
setAiLoading(true)
|
setAiLoading(true);
|
||||||
try {
|
try {
|
||||||
const res = await fetch('/api/ai-summary', {
|
const res = await fetch("/api/ai-summary", {
|
||||||
method: 'POST',
|
method: "POST",
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { "Content-Type": "application/json" },
|
||||||
body: JSON.stringify({ events })
|
body: JSON.stringify({ events }),
|
||||||
})
|
});
|
||||||
const data = await res.json()
|
const data = await res.json();
|
||||||
if (data.summary) {
|
if (data.summary) {
|
||||||
setSummary(data.summary)
|
setSummary(data.summary);
|
||||||
setSummaryUpdated(new Date().toLocaleString())
|
setSummaryUpdated(new Date().toLocaleString());
|
||||||
} else {
|
} else {
|
||||||
setSummary("No summary generated.")
|
setSummary("No summary generated.");
|
||||||
setSummaryUpdated(new Date().toLocaleString())
|
setSummaryUpdated(new Date().toLocaleString());
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
setSummary("Error summarizing events")
|
setSummary("Error summarizing events");
|
||||||
setSummaryUpdated(new Date().toLocaleString())
|
setSummaryUpdated(new Date().toLocaleString());
|
||||||
} finally {
|
} finally {
|
||||||
setAiLoading(false)
|
setAiLoading(false);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const handleEdit = (eventData: CalendarEvent) => {
|
const handleEdit = (eventData: CalendarEvent) => {
|
||||||
setTitle(eventData.title)
|
setTitle(eventData.title);
|
||||||
setDescription(eventData.description || "")
|
setDescription(eventData.description || "");
|
||||||
setLocation(eventData.location || "")
|
setLocation(eventData.location || "");
|
||||||
setUrl(eventData.url || "")
|
setUrl(eventData.url || "");
|
||||||
setStart(eventData.start)
|
setStart(eventData.start);
|
||||||
setEnd(eventData.end || "")
|
setEnd(eventData.end || "");
|
||||||
setAllDay(eventData.allDay || false)
|
setAllDay(eventData.allDay || false);
|
||||||
setEditingId(eventData.id)
|
setEditingId(eventData.id);
|
||||||
setRecurrenceRule(eventData.recurrenceRule)
|
setRecurrenceRule(eventData.recurrenceRule);
|
||||||
setDialogOpen(true)
|
setDialogOpen(true);
|
||||||
}
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DragDropContainer
|
<DragDropContainer
|
||||||
@@ -275,11 +285,7 @@ export default function HomePage() {
|
|||||||
onClearAll={handleClearAll}
|
onClearAll={handleClearAll}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<EventsList
|
<EventsList events={events} onEdit={handleEdit} onDelete={handleDelete} />
|
||||||
events={events}
|
|
||||||
onEdit={handleEdit}
|
|
||||||
onDelete={handleDelete}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<EventDialog
|
<EventDialog
|
||||||
open={dialogOpen}
|
open={dialogOpen}
|
||||||
@@ -305,5 +311,5 @@ export default function HomePage() {
|
|||||||
onReset={resetForm}
|
onReset={resetForm}
|
||||||
/>
|
/>
|
||||||
</DragDropContainer>
|
</DragDropContainer>
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user