Implement lists module and dev login setup

This commit is contained in:
ginnoir
2026-05-06 03:49:03 -05:00
parent 744c1119a9
commit 7e3ae6eb04
31 changed files with 1525 additions and 63 deletions
@@ -0,0 +1,101 @@
"use client";
import Link from "next/link";
import { Plus } from "lucide-react";
import { useMemo, useState, useTransition } from "react";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import type { ListDto } from "../server/queries";
import { createList } from "../server/actions";
export function ListsIndex({ lists }: { lists: ListDto[] }) {
const [listRows, setListRows] = useState(lists);
const [type, setType] = useState("shopping");
const [name, setName] = useState("");
const [, startTransition] = useTransition();
const grouped = useMemo(() => {
const groups = new Map<string, ListDto[]>();
for (const list of listRows) {
groups.set(list.type, [...(groups.get(list.type) ?? []), list]);
}
return [...groups.entries()].sort(([a], [b]) => a.localeCompare(b));
}, [listRows]);
function addList() {
startTransition(async () => {
const created = await createList({ type, name });
setListRows((current) => [
...current,
{
id: created.id,
type: created.type,
name: created.name,
archived: created.archived,
openCount: 0,
doneCount: 0,
createdAt: created.createdAt.toISOString(),
},
]);
setName("");
});
}
return (
<div className="mx-auto grid w-full max-w-5xl gap-6 p-4">
<div className="flex flex-col gap-2 sm:flex-row sm:items-end sm:justify-between">
<div>
<h1 className="text-2xl font-semibold">Lists</h1>
<p className="text-sm text-muted-foreground">Shopping, tasks, and whatever comes next.</p>
</div>
<div className="grid gap-2 rounded-lg border bg-background p-3 sm:grid-cols-[140px_220px_auto]">
<div className="space-y-1">
<Label htmlFor="new-list-type">Type</Label>
<Input
id="new-list-type"
value={type}
onChange={(event) => setType(event.target.value)}
/>
</div>
<div className="space-y-1">
<Label htmlFor="new-list-name">Name</Label>
<Input
id="new-list-name"
value={name}
onChange={(event) => setName(event.target.value)}
/>
</div>
<Button className="self-end" onClick={addList} disabled={!type || !name}>
<Plus />
New list
</Button>
</div>
</div>
<div className="grid gap-6">
{grouped.map(([groupType, groupLists]) => (
<section key={groupType} className="grid gap-3">
<h2 className="text-sm font-medium uppercase tracking-normal text-muted-foreground">
{groupType}
</h2>
<div className="grid gap-3 sm:grid-cols-2 lg:grid-cols-3">
{groupLists.map((list) => (
<Link
key={list.id}
href={`/lists/${list.id}`}
className="rounded-lg border bg-card p-4 text-card-foreground transition-colors hover:bg-muted"
>
<div className="font-medium">{list.name}</div>
<div className="mt-2 text-sm text-muted-foreground">
{list.openCount} open / {list.doneCount} done
</div>
</Link>
))}
</div>
</section>
))}
</div>
</div>
);
}