Compare commits
5 Commits
383ce4c33a
...
5fd72fc3bb
| Author | SHA1 | Date | |
|---|---|---|---|
| 5fd72fc3bb | |||
| 112ab01445 | |||
| 6818046d58 | |||
| ef035e2b7d | |||
| 1a30b729e6 |
@@ -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"
|
||||||
|
|
||||||
|
|||||||
@@ -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 >
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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>
|
||||||
Reference in New Issue
Block a user