Keep/keep-notes/components/reminder-dialog.tsx
sepehr 640fcb26f7 fix: improve note interactions and markdown LaTeX support
## Bug Fixes

### Note Card Actions
- Fix broken size change functionality (missing state declaration)
- Implement React 19 useOptimistic for instant UI feedback
- Add startTransition for non-blocking updates
- Ensure smooth animations without page refresh
- All note actions now work: pin, archive, color, size, checklist

### Markdown LaTeX Rendering
- Add remark-math and rehype-katex plugins
- Support inline equations with dollar sign syntax
- Support block equations with double dollar sign syntax
- Import KaTeX CSS for proper styling
- Equations now render correctly instead of showing raw LaTeX

## Technical Details

- Replace undefined currentNote references with optimistic state
- Add optimistic updates before server actions for instant feedback
- Use router.refresh() in transitions for smart cache invalidation
- Install remark-math, rehype-katex, and katex packages

## Testing

- Build passes successfully with no TypeScript errors
- Dev server hot-reloads changes correctly
2026-01-09 22:13:49 +01:00

127 lines
3.7 KiB
TypeScript

import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { useState, useEffect } from "react"
interface ReminderDialogProps {
open: boolean
onOpenChange: (open: boolean) => void
currentReminder: Date | null
onSave: (date: Date) => void
onRemove: () => void
}
export function ReminderDialog({
open,
onOpenChange,
currentReminder,
onSave,
onRemove
}: ReminderDialogProps) {
const [reminderDate, setReminderDate] = useState('')
const [reminderTime, setReminderTime] = useState('')
useEffect(() => {
if (open) {
if (currentReminder) {
const date = new Date(currentReminder)
setReminderDate(date.toISOString().split('T')[0])
setReminderTime(date.toTimeString().slice(0, 5))
} else {
const tomorrow = new Date(Date.now() + 86400000)
setReminderDate(tomorrow.toISOString().split('T')[0])
setReminderTime('09:00')
}
}
}, [open, currentReminder])
const handleSave = () => {
if (!reminderDate || !reminderTime) return
const dateTimeString = `${reminderDate}T${reminderTime}`
const date = new Date(dateTimeString)
if (!isNaN(date.getTime())) {
onSave(date)
onOpenChange(false)
}
}
return (
<Dialog open={open} onOpenChange={onOpenChange}>
<DialogContent
onInteractOutside={(event) => {
// Prevent dialog from closing when interacting with Sonner toasts
const target = event.target as HTMLElement;
const isSonnerElement =
target.closest('[data-sonner-toast]') ||
target.closest('[data-sonner-toaster]') ||
target.closest('[data-icon]') ||
target.closest('[data-content]') ||
target.closest('[data-description]') ||
target.closest('[data-title]') ||
target.closest('[data-button]');
if (isSonnerElement) {
event.preventDefault();
return;
}
if (target.getAttribute('data-sonner-toaster') !== null) {
event.preventDefault();
return;
}
}}
>
<DialogHeader>
<DialogTitle>Set Reminder</DialogTitle>
</DialogHeader>
<div className="space-y-4 py-4">
<div className="space-y-2">
<label htmlFor="reminder-date" className="text-sm font-medium">
Date
</label>
<Input
id="reminder-date"
type="date"
value={reminderDate}
onChange={(e) => setReminderDate(e.target.value)}
className="w-full"
/>
</div>
<div className="space-y-2">
<label htmlFor="reminder-time" className="text-sm font-medium">
Time
</label>
<Input
id="reminder-time"
type="time"
value={reminderTime}
onChange={(e) => setReminderTime(e.target.value)}
className="w-full"
/>
</div>
</div>
<div className="flex justify-between">
<div>
{currentReminder && (
<Button variant="outline" onClick={() => { onRemove(); onOpenChange(false); }}>
Remove Reminder
</Button>
)}
</div>
<div className="flex gap-2">
<Button variant="ghost" onClick={() => onOpenChange(false)}>
Cancel
</Button>
<Button onClick={handleSave}>
Set Reminder
</Button>
</div>
</div>
</DialogContent>
</Dialog>
)
}