ovr

Todo

  • Build a todo app
Reset

A server driven todo app that stores data in the URL.

tsx
import { Chunk, Context, Get, Post } from "ovr";
import * as z from "zod";

export const add = new Post(async () => {
	const todos = getTodos();
	const { text } = await data();
	todos.push({ id: (todos.at(-1)?.id ?? 0) + 1, text, done: false });
	redirect(todos);
});

export const toggle = new Post(async () => {
	const todos = getTodos();
	const { id } = await data();
	const current = todos.find((t) => t.id === id);
	if (current) current.done = !current.done;
	redirect(todos);
});

export const remove = new Post(async () => {
	const todos = getTodos();
	const { id } = await data();
	redirect(todos.filter((t) => t.id !== id));
});

export const todo = new Get("/demo/todo", (c) => {
	return (
		<div>
			<add.Form search>
				<input name="text" placeholder="Add todo" />
				<button>Add</button>
			</add.Form>

			<ul>
				{getTodos().map((t) => (
					<li>
						<form>
							<input type="hidden" name="id" value={t.id} />

							<div>
								<toggle.Button search aria-label="toggle todo">
									{t.done ? "done" : "todo"}
								</toggle.Button>

								<span>{t.text}</span>
							</div>

							<remove.Button search aria-label="delete todo">
								x
							</remove.Button>
						</form>
					</li>
				))}
			</ul>

			<todo.Anchor>Reset</todo.Anchor>
		</div>
	);
});

const TodoSchema = z.object({
	done: z.boolean().optional(),
	id: z.coerce.number(),
	text: z.coerce.string(),
});

const redirect = (todos: z.infer<(typeof TodoSchema)[]>) => {
	const c = Context.get();
	const location = new URL(todo.pathname(), c.url);
	location.searchParams.set("todos", JSON.stringify(todos));
	c.redirect(location, 303);
};

const getTodos = () => {
	const todos = Context.get().url.searchParams.get("todos");
	if (!todos) return [{ done: false, id: 0, text: "Build a todo app" }];
	return z.array(TodoSchema).parse(JSON.parse(todos));
};

const data = async () => {
	const data = await Context.get().req.formData();
	return TodoSchema.parse({ id: data.get("id"), text: data.get("text") });
};