learning_ai_notes/web/src/components/CreateNoteModal.tsx

180 lines
5.4 KiB
TypeScript

"use client";
import { useState } from "react";
import { Button, Card } from "@/components/ui/Primitives";
import { createNote } from "@/lib/notes-client";
import { NOTE_TEMPLATES } from "@/lib/note-templates";
import type { WorkspaceSummary } from "@/lib/types";
interface CreateNoteModalProps {
workspaces: WorkspaceSummary[];
defaultWorkspaceId?: string;
onCreated: () => void;
onClose: () => void;
}
export function CreateNoteModal({ workspaces, defaultWorkspaceId, onCreated, onClose }: CreateNoteModalProps) {
const [title, setTitle] = useState("");
const [body, setBody] = useState("");
const [workspaceId, setWorkspaceId] = useState(defaultWorkspaceId ?? workspaces[0]?.id ?? "");
const [tags, setTags] = useState("");
const [templateId, setTemplateId] = useState<string>("");
const [saving, setSaving] = useState(false);
const [error, setError] = useState<string | null>(null);
function applyTemplate(id: string) {
setTemplateId(id);
const t = NOTE_TEMPLATES.find((x) => x.id === id);
if (t) {
setTitle(t.title);
setBody(t.body);
}
}
const canSubmit = title.trim().length > 0 && body.trim().length > 0 && workspaceId.length > 0;
async function handleSubmit(e: React.FormEvent) {
e.preventDefault();
if (!canSubmit || saving) return;
setSaving(true);
setError(null);
try {
await createNote({
id: crypto.randomUUID(),
workspaceId,
title: title.trim(),
body: body.includes("<") && body.includes(">") ? body.trim() : `<p>${body.trim()}</p>`,
tags: tags
.split(",")
.map((t) => t.trim())
.filter(Boolean),
sourceType: "manual",
});
onCreated();
} catch (err) {
setError(err instanceof Error ? err.message : "Failed to create note");
} finally {
setSaving(false);
}
}
return (
<div
className="modal-overlay"
style={{
position: "fixed",
inset: 0,
background: "var(--nl-overlay-scrim)",
display: "flex",
alignItems: "center",
justifyContent: "center",
zIndex: 1000,
}}
onClick={(e) => {
if (e.target === e.currentTarget) onClose();
}}
>
<Card
padding="lg"
style={{ width: "100%", maxWidth: 520 }}
>
<form
onSubmit={handleSubmit}
style={{
display: "grid",
gap: "var(--nl-space-4)",
}}
>
<div style={{ fontSize: "var(--nl-fs-xl)", fontWeight: 700 }}>Create Note</div>
{error && <div style={{ color: "var(--nl-danger)", fontSize: "0.875rem" }}>{error}</div>}
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Template</span>
<select
className="input"
aria-label="Note template"
value={templateId}
onChange={(e) => {
const v = e.target.value;
if (!v) {
setTemplateId("");
return;
}
applyTemplate(v);
}}
>
<option value="">Blank</option>
{NOTE_TEMPLATES.map((t) => (
<option key={t.id} value={t.id}>
{t.label}
</option>
))}
</select>
</label>
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Workspace</span>
<select value={workspaceId} onChange={(e) => setWorkspaceId(e.target.value)} className="input" aria-label="Workspace">
{workspaces.map((ws) => (
<option key={ws.id} value={ws.id}>
{ws.name}
</option>
))}
</select>
</label>
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Title</span>
<input
className="input"
type="text"
value={title}
onChange={(e) => setTitle(e.target.value)}
placeholder="Note title"
aria-label="Note title"
autoFocus
/>
</label>
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Body</span>
<textarea
className="input"
value={body}
onChange={(e) => setBody(e.target.value)}
placeholder="Note content..."
aria-label="Note body"
rows={6}
style={{ resize: "vertical" }}
/>
</label>
<label style={{ display: "grid", gap: "var(--nl-space-1)" }}>
<span style={{ fontWeight: 600, fontSize: "0.875rem" }}>Tags (comma-separated)</span>
<input
className="input"
type="text"
value={tags}
onChange={(e) => setTags(e.target.value)}
placeholder="launch, meeting, review"
aria-label="Note tags"
/>
</label>
<div style={{ display: "flex", justifyContent: "flex-end", gap: "var(--nl-space-3)" }}>
<Button type="button" variant="secondary" onClick={onClose}>
Cancel
</Button>
<Button type="submit" disabled={!canSubmit || saving} loading={saving}>
Create
</Button>
</div>
</form>
</Card>
</div>
);
}