151 lines
3.4 KiB
TypeScript
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;
|