Files
rat-notes-frontend/vite-project/src/App.tsx
2025-12-17 21:09:55 +11:00

151 lines
3.4 KiB
TypeScript

import { useEffect, useState } from "react";
import "./App.css";
import { ArrowUpIcon } from "lucide-react";
import {
InputGroup,
InputGroupAddon,
InputGroupButton,
InputGroupTextarea,
} from "@/components/ui/input-group";
import {
Card,
CardContent,
CardDescription,
CardFooter,
CardHeader,
CardTitle,
} from "@/components/ui/card";
const baseUrl = "http://localhost:8080";
const username = "ratludu";
type Note = {
id: string;
content: string;
title: string;
};
export function InputGroupDemo({ onSent }): {
onSent?: (note: Note) => void;
} {
const [text, setText] = useState("");
const [isSending, setIsSending] = useState(false);
const [error, setError] = useState<string | null>(null);
const isEmpty = text.trim().length === 0;
async function handleSend() {
const value = text.trim();
if (!value || isSending) return;
try {
setIsSending(true);
setError(null);
const resp = await fetch(`${baseUrl}/content`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
username: username,
content: value,
}),
});
if (!resp.ok) {
const msg = await resp.text().catch(() => "An error occurred");
throw new Error(msg || "Unknown error");
}
const created = {
id: crypto.randomUUID(),
content: String(value),
title: "Note",
};
onSent?.(created);
setText("");
} catch (e) {
const msg = e instanceof Error ? e.message : "Unknown error";
setError(msg);
} finally {
setIsSending(false);
}
setText("");
}
function handleKeyDown(event: React.KeyboardEvent<HTMLInputElement>) {
if (event.key === "Enter" && !event.shiftKey) {
event.preventDefault();
handleSend();
}
}
return (
<div className="grid w-full max-w-none gap-6">
<InputGroup>
<InputGroupTextarea
placeholder="What are you thinking..."
value={text}
onChange={(e: React.ChangeEvent<HTMLTextAreaElement>) =>
setText(e.target.value)
}
onKeyDown={handleKeyDown}
/>
<InputGroupAddon align="block-end">
<InputGroupButton
variant="default"
className="rounded-full"
size="icon-xs"
onClick={handleSend}
disabled={isEmpty}
aria-disabled={isEmpty}
aria-label="Send"
title={isEmpty ? "Type something first" : "Send"}
>
<ArrowUpIcon />
<span className="sr-only">Send</span>
</InputGroupButton>
</InputGroupAddon>
</InputGroup>
</div>
);
}
function NotesList({ notes }: { notes: Note[] }) {
if (!notes.length) {
return <p className="text-center">No notes yet.</p>;
}
return (
<div className="space-y-4">
{notes.map((note) => (
<Card>
<CardContent key={note.id}>
<p>{note.content}</p>
</CardContent>
</Card>
))}
</div>
);
}
function App() {
const [notes, setNotes] = useState<Note[]>([]);
function handleSend(newNote: Note) {
setNotes((prev) => [newNote, ...prev]);
}
return (
<div className="w-150 fixed top-10 left-0 right-0 mx-auto space-y-4">
<InputGroupDemo onSent={handleSend} />
<NotesList notes={notes} />
</div>
);
}
export default App;