Compare commits

...

5 Commits

4 changed files with 110 additions and 112 deletions

View File

@@ -3,7 +3,7 @@ import { Geist } from "next/font/google";
import "./globals.css"; import "./globals.css";
import { ThemeProvider } from "next-themes"; 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 AuthSessionProvider from "@/components/SessionProvider"; import AuthSessionProvider from "@/components/SessionProvider";
import Link from "next/link" import Link from "next/link"

View File

@@ -225,39 +225,36 @@ export default function HomePage() {
return ( return (
<div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop} <div onDragOver={handleDragOver} onDragLeave={handleDragLeave} onDrop={handleDrop}
className={`p-4 min-h-[80vh] relative rounded border-2 border-dashed transition ${isDragOver ? 'border-blue-500 bg-blue-50' : 'border-gray-300' className={`p-4 min-h-[80vh] rounded border-2 border-dashed transition ${isDragOver ? 'border-blue-500 bg-blue-50' : 'border-gray-700'
}`} }`}
> >
<div className='absolute bottom-0 w-full pb-4 text-gray-400'>
<div className='max-w-fit m-auto'> Drag & Drop *.ics here</div>
</div>
{/* AI Toolbar */} {/* AI Toolbar */}
{session?.user ? ( {status === "loading" ? <div className='mb-4 p-4 text-center animate-pulse bg-muted'>Loading...</div> : <div>
<div className="flex flex-row gap-4 mb-4 items-start"> {session?.user ? (
<div className='w-full'> <div className="flex flex-row gap-4 mb-4 items-start">
<Textarea <div className='w-full'>
className="wrap-anywhere min-h-12" <Textarea
placeholder='Describe event for AI to create' className="wrap-anywhere min-h-12"
value={aiPrompt} placeholder='Describe event for AI to create'
onChange={e => setAiPrompt(e.target.value)} value={aiPrompt}
/> onChange={e => setAiPrompt(e.target.value)}
/>
</div>
<div className='flex flex-row gap-2 pt-1.5'>
<Button onClick={handleAiCreate} disabled={aiLoading}>
{aiLoading ? 'Thinking...' : 'AI Create'}
</Button>
</div>
</div> </div>
<div className='flex flex-row gap-2 pt-1.5'> ) : (
<Button onClick={handleAiCreate} disabled={aiLoading}> <div className="mb-4 p-4 border border-dashed rounded-lg text-center">
{aiLoading ? 'Thinking...' : 'AI Create'} <div className="text-sm text-muted-foreground">
</Button> Sign in to unlock AI natural language event creation
</div>
</div> </div>
</div> )}
) : ( </div>}
<div className="mb-4 p-4 border border-dashed rounded-lg text-center">
<div className="text-sm text-muted-foreground mb-2">
Sign in to unlock AI-powered calendar features
</div>
<Button variant="outline" size="sm" asChild>
<a href="/auth/signin">Sign In</a>
</Button>
</div>
)}
{/* Summary Panel */} {/* Summary Panel */}
{ {
@@ -271,7 +268,16 @@ export default function HomePage() {
) )
} }
{/* AI Actions Toolbar */}
<p className='text-muted-foreground text-sm pb-2 pl-1'>AI actions</p>
<div className="gap-2 mb-4">
<Button variant="secondary" onClick={handleAiSummarize} disabled={aiLoading}>
{aiLoading ? 'Summarizing...' : 'AI Summarize'}
</Button>
</div>
{/* Control Toolbar */} {/* Control Toolbar */}
<p className='text-muted-foreground text-sm pb-2 pl-1'>Event Actions</p>
<div className="flex flex-wrap gap-2 mb-4"> <div className="flex flex-wrap gap-2 mb-4">
<Button onClick={() => setDialogOpen(true)}>Add Event</Button> <Button onClick={() => setDialogOpen(true)}>Add Event</Button>
<IcsFilePicker onFileSelect={handleImport} variant='secondary'>Import .ics</IcsFilePicker> <IcsFilePicker onFileSelect={handleImport} variant='secondary'>Import .ics</IcsFilePicker>
@@ -281,9 +287,6 @@ export default function HomePage() {
<Button variant="destructive" onClick={handleClearAll}>Clear All</Button> <Button variant="destructive" onClick={handleClearAll}>Clear All</Button>
</> </>
)} )}
<Button variant="secondary" onClick={handleAiSummarize} disabled={aiLoading}>
{aiLoading ? 'Summarizing...' : 'AI Summarize'}
</Button>
</div> </div>
{/* Event List */} {/* Event List */}
@@ -322,6 +325,7 @@ export default function HomePage() {
))} ))}
</ul> </ul>
{/* Add/Edit Dialog */} {/* Add/Edit Dialog */}
<Dialog open={dialogOpen} onOpenChange={val => { if (!val) resetForm(); setDialogOpen(val) }}> <Dialog open={dialogOpen} onOpenChange={val => { if (!val) resetForm(); setDialogOpen(val) }}>
<DialogContent> <DialogContent>
@@ -355,6 +359,9 @@ export default function HomePage() {
</DialogFooter> </DialogFooter>
</DialogContent> </DialogContent>
</Dialog> </Dialog>
<div className='mt-auto w-full pb-4 text-gray-400'>
<div className='max-w-fit m-auto'> Drag & Drop *.ics here</div>
</div>
</div > </div >
) )
} }

View File

@@ -73,88 +73,82 @@ export function RecurrencePicker({ value, onChange }: Props) {
} }
return ( return (
<Card className="w-full"> <div className="">
<Label htmlFor="frequency" className="pt-4 pb-2 pl-1">Repeats</Label>
<div className="space-y-2">
<Select value={rec.freq} onValueChange={(value) => update({ freq: value as Recurrence["freq"] })}>
<SelectTrigger id="frequency">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="NONE">Does not repeat</SelectItem>
<SelectItem value="DAILY">Daily</SelectItem>
<SelectItem value="WEEKLY">Weekly</SelectItem>
<SelectItem value="MONTHLY">Monthly</SelectItem>
</SelectContent>
</Select>
</div>
{/* <CardHeader className="pb-4"> {rec.freq !== "NONE" && (
<CardTitle className="text-base">Recurrence Settings</CardTitle> <>
</CardHeader> */} <div className="space-y-2">
<CardContent className="space-y-4"> <Label htmlFor="interval">
<div className="space-y-2"> Interval (every {rec.interval} {rec.freq === "DAILY" ? "day" : rec.freq === "WEEKLY" ? "week" : "month"}
<Label htmlFor="frequency">Repeats</Label> {rec.interval > 1 ? "s" : ""})
<Select value={rec.freq} onValueChange={(value) => update({ freq: value as Recurrence["freq"] })}> </Label>
<SelectTrigger id="frequency"> <Input
<SelectValue /> id="interval"
</SelectTrigger> type="number"
<SelectContent> min={1}
<SelectItem value="NONE">Does not repeat</SelectItem> value={rec.interval}
<SelectItem value="DAILY">Daily</SelectItem> onChange={(e) => update({ interval: Number.parseInt(e.target.value, 10) || 1 })}
<SelectItem value="WEEKLY">Weekly</SelectItem> className="w-24"
<SelectItem value="MONTHLY">Monthly</SelectItem> />
</SelectContent> </div>
</Select>
</div>
{rec.freq !== "NONE" && ( {rec.freq === "WEEKLY" && (
<>
<div className="space-y-2"> <div className="space-y-2">
<Label htmlFor="interval"> <Label>Days of the week</Label>
Interval (every {rec.interval} {rec.freq === "DAILY" ? "day" : rec.freq === "WEEKLY" ? "week" : "month"} <div className="flex flex-wrap gap-4">
{rec.interval > 1 ? "s" : ""}) {["MO", "TU", "WE", "TH", "FR", "SA", "SU"].map((day) => (
</Label> <div key={day} className="flex items-center space-x-2">
<Checkbox
id={day}
checked={rec.byDay?.includes(day) || false}
onCheckedChange={() => toggleDay(day)}
/>
<Label htmlFor={day} className="text-sm font-normal">
{dayLabels[day as keyof typeof dayLabels]}
</Label>
</div>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="count">End after (occurrences)</Label>
<Input <Input
id="interval" id="count"
type="number" type="number"
min={1} placeholder="e.g. 10"
value={rec.interval} value={rec.count || ""}
onChange={(e) => update({ interval: Number.parseInt(e.target.value, 10) || 1 })} onChange={(e) => update({ count: e.target.value ? Number.parseInt(e.target.value, 10) : undefined })}
className="w-24"
/> />
</div> </div>
<div className="space-y-2">
{rec.freq === "WEEKLY" && ( <Label htmlFor="until">End by date</Label>
<div className="space-y-2"> <Input
<Label>Days of the week</Label> id="until"
<div className="flex flex-wrap gap-4"> type="date"
{["MO", "TU", "WE", "TH", "FR", "SA", "SU"].map((day) => ( value={rec.until || ""}
<div key={day} className="flex items-center space-x-2"> onChange={(e) => update({ until: e.target.value || undefined })}
<Checkbox />
id={day}
checked={rec.byDay?.includes(day) || false}
onCheckedChange={() => toggleDay(day)}
/>
<Label htmlFor={day} className="text-sm font-normal">
{dayLabels[day as keyof typeof dayLabels]}
</Label>
</div>
))}
</div>
</div>
)}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<div className="space-y-2">
<Label htmlFor="count">End after (occurrences)</Label>
<Input
id="count"
type="number"
placeholder="e.g. 10"
value={rec.count || ""}
onChange={(e) => update({ count: e.target.value ? Number.parseInt(e.target.value, 10) : undefined })}
/>
</div>
<div className="space-y-2">
<Label htmlFor="until">End by date</Label>
<Input
id="until"
type="date"
value={rec.until || ""}
onChange={(e) => update({ until: e.target.value || undefined })}
/>
</div>
</div> </div>
</> </div>
)} </>
</CardContent> )}
</Card> </div>
) )
} }

View File

@@ -21,10 +21,7 @@ export default function SignIn() {
if (session?.user) { if (session?.user) {
return ( return (
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<span className="text-sm text-muted-foreground hidden sm:inline"> <Button onClick={handleSignOut} variant="ghost" size="default">
{session.user.name || session.user.email}
</span>
<Button onClick={handleSignOut} variant="ghost" size="sm">
Sign Out Sign Out
</Button> </Button>
</div> </div>
@@ -35,7 +32,7 @@ export default function SignIn() {
<Button <Button
onClick={() => router.push("/auth/signin")} onClick={() => router.push("/auth/signin")}
variant="outline" variant="outline"
size="sm" size="default"
> >
Sign In Sign In
</Button> </Button>