init
This commit is contained in:
150
vite-project/src/App.tsx
Normal file
150
vite-project/src/App.tsx
Normal file
@@ -0,0 +1,150 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user