Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 9 additions & 6 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -1,18 +1,21 @@
# TODO

- [ ] Add CodeRabbit
## `beta`

- [ ] Short animated tutorial to show how to use the guide
- show/hide
- resize
- check the network tab
- maybe build a fake network tab to show what's happening?
- [ ] (feature) query-driven sync
- maybe the todos -> per board
- [ ] `robots.txt`, Google Search
- [ ] use shadcn elements in the floating window (it's light-theme only)
- [ ] (feature) paced mutations
- maybe editing the todos
- [ ] Make the root error page responsiveerror that is shown when `navigator`
- [ ] Short animated tutorial to show how to use the guide
- show/hide
- resize
- check the network tab
- maybe build a fake network tab to show what's happening?
- [ ] Update the file links (on GH) in the guide
- [x] Add CodeRabbit
- [x] Floating window
- [x] Fix the open/close animation (chunky)
- [x] Make it resizable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ CREATE TABLE "projects" (
"name" text NOT NULL,
"description" text NOT NULL,
"created_at" date NOT NULL,
"item_positions_in_the_project" json DEFAULT '{}'::json NOT NULL,
CONSTRAINT "projects_name_unique" UNIQUE("name")
);
--> statement-breakpoint
Expand All @@ -27,7 +26,8 @@ CREATE TABLE "todo_items" (
"description" text,
"created_at" date NOT NULL,
"board_id" text NOT NULL,
"priority" integer DEFAULT 0
"priority" integer DEFAULT 0,
"position" text NOT NULL
);
--> statement-breakpoint
CREATE TABLE "users" (
Expand All @@ -39,4 +39,5 @@ CREATE TABLE "users" (
);
--> statement-breakpoint
ALTER TABLE "boards" ADD CONSTRAINT "boards_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "todo_items" ADD CONSTRAINT "todo_items_board_id_boards_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."boards"("id") ON DELETE cascade ON UPDATE no action;
ALTER TABLE "todo_items" ADD CONSTRAINT "todo_items_board_id_boards_id_fk" FOREIGN KEY ("board_id") REFERENCES "public"."boards"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "todo_items_board_id_position_idx" ON "todo_items" USING btree ("board_id","position");
39 changes: 30 additions & 9 deletions drizzle/migrations/meta/0000_snapshot.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"id": "7f0dad39-3f27-4237-a783-43c6b501d2fc",
"id": "58ddc186-dd38-49a3-81e4-b0e9c8528908",
"prevId": "00000000-0000-0000-0000-000000000000",
"version": "7",
"dialect": "postgresql",
Expand Down Expand Up @@ -88,13 +88,6 @@
"type": "date",
"primaryKey": false,
"notNull": true
},
"item_positions_in_the_project": {
"name": "item_positions_in_the_project",
"type": "json",
"primaryKey": false,
"notNull": true,
"default": "'{}'::json"
}
},
"indexes": {},
Expand Down Expand Up @@ -184,9 +177,37 @@
"primaryKey": false,
"notNull": false,
"default": 0
},
"position": {
"name": "position",
"type": "text",
"primaryKey": false,
"notNull": true
}
},
"indexes": {
"todo_items_board_id_position_idx": {
"name": "todo_items_board_id_position_idx",
"columns": [
{
"expression": "board_id",
"isExpression": false,
"asc": true,
"nulls": "last"
},
{
"expression": "position",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"indexes": {},
"foreignKeys": {
"todo_items_board_id_boards_id_fk": {
"name": "todo_items_board_id_boards_id_fk",
Expand Down
4 changes: 2 additions & 2 deletions drizzle/migrations/meta/_journal.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
{
"idx": 0,
"version": "7",
"when": 1763667992254,
"tag": "0000_overjoyed_blizzard",
"when": 1766184598553,
"tag": "0000_outstanding_silvermane",
"breakpoints": true
}
]
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"drizzle-orm": "^0.44.7",
"fractional-indexing": "^3.2.0",
"lucide-react": "^0.544.0",
"motion": "^12.23.24",
"nanoid": "^5.1.6",
Expand Down
3 changes: 3 additions & 0 deletions pnpm-lock.yaml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

25 changes: 21 additions & 4 deletions src/collections/todoItems.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ async function updateTodoItem({
return updatedItem;
}

export async function batchUpdateTodoItem({
data,
}: {
data: (Partial<TodoItemRecord> & { id: string })[];
}) {
const res = await fetch("/api/batch/todo-items", {
method: "PATCH",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(data),
});

if (!res.ok) {
throw new Error("Failed to batch update todo items");
}

await res.json();
}

export async function insertTodoItem({
data,
}: {
Expand Down Expand Up @@ -67,10 +87,7 @@ export const todoItemsCollection = createCollection(

try {
await insertTodoItem({
data: {
projectId: "project-id-placeholder", // we won't run it directly
...newTodoItem,
},
data: newTodoItem,
});
} catch (error) {
// TODO: handle error
Expand Down
58 changes: 15 additions & 43 deletions src/components/CreateOrEditTodoItems.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
import { createOptimisticAction } from "@tanstack/db";
import { generateKeyBetween } from "fractional-indexing";
import { nanoid } from "nanoid";
import { useCallback, useState } from "react";
import z from "zod";
import { projectsCollection } from "@/collections/projects";
import { insertTodoItem, todoItemsCollection } from "@/collections/todoItems";
import { todoItemsCollection } from "@/collections/todoItems";
import { Button } from "@/components/ui/button";
import {
Dialog,
Expand All @@ -17,46 +16,10 @@ import {
import type { TodoItemRecord } from "@/db/schema";
import { useAppForm } from "@/hooks/app.form";

type CreateTodoItemInput = {
id: string;
boardId: string;
title: string;
description: string;
projectId: string;
createdAtTimestampMs: number;
};

const createTodoItem = createOptimisticAction({
id: "create-todo-item",
autoCommit: true,
onMutate: (newItemData: CreateTodoItemInput) => {
todoItemsCollection.insert({
createdAt: new Date(),
...newItemData,
priority: 0,
});

projectsCollection.update(newItemData.projectId, (project) => {
project.itemPositionsInTheProject[newItemData.boardId].unshift(
newItemData.id,
);
});
},
mutationFn: async (newItemData: CreateTodoItemInput) => {
await insertTodoItem({
data: newItemData,
});
await todoItemsCollection.utils.refetch();
await projectsCollection.utils.refetch();
},
});

export function CreateOrEditTodoItems({
projectId,
todoItem,
children,
}: {
projectId: string;
todoItem: Partial<TodoItemRecord> & Pick<TodoItemRecord, "boardId">;
children: React.ReactNode;
}) {
Expand All @@ -69,21 +32,30 @@ export function CreateOrEditTodoItems({
title: todoItem.title || "",
description: todoItem.description || "",
},
onSubmit: ({ value }) => {
onSubmit: async ({ value }) => {
const itemId = todoItem.id || nanoid();

// NOTE: It'd be better to use a manual transaction here to ensure both operations
// succeed or fail together. However, we use D1 for the db, and it doesn't suppor
// transactions yet, so we can't make an endpoint that does both of these operations

if (isNewItem) {
createTodoItem({
// Find the first position in the board to prepend the new item
const itemsInBoard = (await todoItemsCollection.toArrayWhenReady())
.filter((item) => item.boardId === todoItem.boardId)
.sort((a, b) => (a.position < b.position ? -1 : 1));

const firstPosition = itemsInBoard[0]?.position;
const newPosition = generateKeyBetween(null, firstPosition ?? null);

todoItemsCollection.insert({
id: itemId,
boardId: todoItem.boardId,
projectId: projectId,
title: value.title,
description: value.description,
createdAtTimestampMs: Date.now(),
position: newPosition,
priority: 0,
createdAt: new Date(),
});
} else {
todoItemsCollection.update(itemId, (item) => {
Expand Down
Loading