create-aron-app 0.1.7 → 0.1.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.cursor/worktrees.json +3 -0
- package/README.md +24 -31
- package/dist/index.js +38 -49
- package/package.json +3 -7
- package/templates/.cursor/rules/backend.mdc +112 -0
- package/templates/.cursor/rules/coding_standards.mdc +85 -4
- package/templates/.cursor/rules/frontend_architecture.mdc +334 -0
- package/templates/.env.example +6 -0
- package/templates/apps/{react-router → web}/.react-router/types/+routes.ts +11 -6
- package/templates/apps/{react-router/.react-router/types/src/routes/(dashboard)/todos → web/.react-router/types/src/routes/(dashboard)/(todos)}/+types/[id].ts +5 -2
- package/templates/apps/{react-router/.react-router/types/src/routes/(dashboard)/todos → web/.react-router/types/src/routes/(dashboard)/(todos)}/+types/index.ts +5 -2
- package/templates/apps/web/.react-router/types/src/routes/(dashboard)/(todos)/+types/layout.ts +65 -0
- package/templates/apps/{react-router → web}/project.json +11 -4
- package/templates/apps/{react-router → web}/react-router.config.ts +1 -1
- package/templates/apps/web/src/libs/convex_query_client.ts +11 -0
- package/templates/apps/web/src/libs/react_query_client.ts +17 -0
- package/templates/apps/web/src/libs/server/auth.ts +32 -0
- package/templates/apps/web/src/libs/server/protected.ts +17 -0
- package/templates/apps/web/src/providers/api_auth_provider.tsx +26 -0
- package/templates/apps/web/src/providers/global_provider.tsx +28 -0
- package/templates/apps/web/src/providers/navigation_loading_bar_provider.tsx +72 -0
- package/templates/apps/web/src/root.tsx +68 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/[id].tsx +33 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/index.tsx +26 -0
- package/templates/apps/web/src/routes/(dashboard)/(todos)/layout.tsx +9 -0
- package/templates/apps/{react-router → web}/src/routes/(dashboard)/index.tsx +3 -2
- package/templates/apps/web/src/routes/(dashboard)/layout.tsx +20 -0
- package/templates/apps/{react-router → web}/src/routes.ts +4 -2
- package/templates/apps/{react-router → web}/src/surfaces/sidebar/install.tsx +1 -5
- package/templates/apps/{react-router → web}/src/surfaces/sidebar/layout.tsx +24 -15
- package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos.tsx +1 -1
- package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/all_todos_controller.ts +1 -1
- package/templates/apps/web/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/bootstrap.ts +4 -5
- package/templates/apps/{nextjs → web}/src/surfaces/todos/single_todo/header/create.tsx +4 -6
- package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo_controller.ts +1 -1
- package/templates/components.json +20 -0
- package/templates/gitignore +60 -0
- package/templates/nx.json +0 -11
- package/templates/package.json +2 -3
- package/templates/shared/assets/src/styles/global.css +14 -8
- package/templates/shared/ui/src/base/collapsible.tsx +31 -0
- package/templates/shared/ui/src/base/hover-card.tsx +42 -0
- package/templates/shared/ui/src/base/input-group.tsx +168 -0
- package/templates/shared/ui/src/base/panel.tsx +93 -0
- package/templates/shared/ui/src/hooks/use_mobile.tsx +1 -1
- package/templates/shared/ui/src/hooks/use_query_params.tsx +6 -7
- package/templates/shared/utils/src/convex.ts +2 -1
- package/templates/tsconfig.base.json +2 -4
- package/templates/.cursor/commands/builder.md +0 -0
- package/templates/.cursor/rules/api_architecture.mdc +0 -262
- package/templates/.cursor/rules/convex_rules.mdc +0 -331
- package/templates/.cursor/rules/frontend_architecture_core.mdc +0 -495
- package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +0 -458
- package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +0 -473
- package/templates/.github/workflows/ci.yml +0 -29
- package/templates/apps/api/tsconfig.json +0 -23
- package/templates/apps/nextjs/.env.example +0 -10
- package/templates/apps/nextjs/index.d.ts +0 -6
- package/templates/apps/nextjs/next-env.d.ts +0 -5
- package/templates/apps/nextjs/next.config.js +0 -22
- package/templates/apps/nextjs/postcss.config.js +0 -17
- package/templates/apps/nextjs/project.json +0 -22
- package/templates/apps/nextjs/src/app/(auth)/layout.tsx +0 -21
- package/templates/apps/nextjs/src/app/(auth)/not-allowed/page.tsx +0 -23
- package/templates/apps/nextjs/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -15
- package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +0 -22
- package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +0 -12
- package/templates/apps/nextjs/src/app/(dashboard)/todos/[id]/page.tsx +0 -26
- package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +0 -19
- package/templates/apps/nextjs/src/app/app.css +0 -3
- package/templates/apps/nextjs/src/app/layout.tsx +0 -26
- package/templates/apps/nextjs/src/middleware.ts +0 -18
- package/templates/apps/nextjs/src/providers/convex_provider.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/home/home.tsx +0 -27
- package/templates/apps/nextjs/src/surfaces/home/layout.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +0 -34
- package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +0 -118
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +0 -19
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +0 -22
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -25
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -33
- package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx +0 -39
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +0 -28
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -42
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +0 -29
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +0 -61
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +0 -21
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +0 -23
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +0 -44
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +0 -49
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +0 -70
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -56
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -99
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +0 -32
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +0 -22
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +0 -27
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +0 -55
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +0 -38
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +0 -49
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +0 -29
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +0 -13
- package/templates/apps/nextjs/src/utils/auth.ts +0 -18
- package/templates/apps/nextjs/src/utils/convex.ts +0 -11
- package/templates/apps/nextjs/src/utils/font.ts +0 -9
- package/templates/apps/nextjs/tsconfig.json +0 -42
- package/templates/apps/react-router/src/providers/api_auth_provider.tsx +0 -40
- package/templates/apps/react-router/src/root.tsx +0 -37
- package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +0 -37
- package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +0 -19
- package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +0 -19
- package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +0 -9
- package/templates/apps/react-router/src/surfaces/home/install.tsx +0 -17
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +0 -21
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +0 -31
- package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +0 -18
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -11
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +0 -32
- package/templates/apps/react-router/tsconfig.json +0 -20
- package/templates/biome.json +0 -121
- package/templates/bun.lock +0 -3187
- package/templates/emails/tsconfig.json +0 -5
- package/templates/shared/assets/tsconfig.json +0 -5
- package/templates/shared/ui/tsconfig.json +0 -8
- package/templates/shared/utils/tsconfig.json +0 -5
- /package/templates/apps/{react-router → web}/.env.example +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/+future.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/+server-build.d.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/+types/root.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/+types/layout.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/index.ts +0 -0
- /package/templates/apps/{react-router → web}/.react-router/types/src/routes/(dashboard)/+types/layout.ts +0 -0
- /package/templates/apps/{react-router → web}/postcss.config.js +0 -0
- /package/templates/apps/{react-router → web}/public/favicon.ico +0 -0
- /package/templates/apps/{react-router → web}/src/app.css +0 -0
- /package/templates/apps/{react-router → web}/src/components/error_boundary.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/routes/(auth)/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/routes/(auth)/sign-in/index.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/home/bootstrap.ts +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/home.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/home/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/home/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/nav_main/nav_main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/sidebar.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/sidebar/user_menu/user_menu.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/header/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +0 -0
- /package/templates/apps/{nextjs → web}/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/header/header.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/install.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/layout.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/create.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/main/main.tsx +0 -0
- /package/templates/apps/{react-router → web}/src/surfaces/todos/single_todo/single_todo.tsx +0 -0
- /package/templates/apps/{react-router → web}/vite.config.ts +0 -0
- /package/templates/emails/{welcome_email.tsx → src/welcome_email.tsx} +0 -0
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { UserButton } from "@clerk/nextjs";
|
|
8
|
-
|
|
9
|
-
import { SidebarMenu, SidebarMenuItem } from "@/ui/base/side_bar";
|
|
10
|
-
import { Skeleton } from "@/ui/base/skeleton";
|
|
11
|
-
|
|
12
|
-
type UserMenuProps = {
|
|
13
|
-
isLoaded: boolean;
|
|
14
|
-
fullName: string | null | undefined;
|
|
15
|
-
email: string | null | undefined;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const UserMenu = ({ isLoaded, fullName, email }: UserMenuProps) => {
|
|
19
|
-
if (!isLoaded) {
|
|
20
|
-
return (
|
|
21
|
-
<SidebarMenu>
|
|
22
|
-
<SidebarMenuItem className="w-full">
|
|
23
|
-
<Skeleton className="h-12 w-full rounded-lg border" />
|
|
24
|
-
</SidebarMenuItem>
|
|
25
|
-
</SidebarMenu>
|
|
26
|
-
);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
<SidebarMenu>
|
|
31
|
-
<SidebarMenuItem className="w-full">
|
|
32
|
-
<div className="flex w-full items-start gap-2 rounded-md border px-2 py-2 shadow-sm">
|
|
33
|
-
<UserButton />
|
|
34
|
-
<div className="flex w-fit min-w-0 flex-col gap-0 text-left leading-tight">
|
|
35
|
-
<span className="truncate text-sm font-medium break-words">{fullName}</span>
|
|
36
|
-
<span className="truncate text-xs font-light">{email}</span>
|
|
37
|
-
</div>
|
|
38
|
-
</div>
|
|
39
|
-
</SidebarMenuItem>
|
|
40
|
-
</SidebarMenu>
|
|
41
|
-
);
|
|
42
|
-
};
|
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { useConvex } from "convex/react";
|
|
8
|
-
import { useMemo, useRef } from "react";
|
|
9
|
-
|
|
10
|
-
import type { AllTodosBootstrap } from "@/web/surfaces/todos/all_todos/bootstrap";
|
|
11
|
-
import { installAllTodos } from "@/web/surfaces/todos/all_todos/install";
|
|
12
|
-
import { createLayout } from "@/web/surfaces/todos/all_todos/layout";
|
|
13
|
-
|
|
14
|
-
export type AllTodosProps = {
|
|
15
|
-
bootstrap: AllTodosBootstrap;
|
|
16
|
-
};
|
|
17
|
-
|
|
18
|
-
export const AllTodos = ({ bootstrap }: AllTodosProps) => {
|
|
19
|
-
const convex = useConvex();
|
|
20
|
-
const { layout, Layout } = useMemo(createLayout, []);
|
|
21
|
-
const installed = useRef(false);
|
|
22
|
-
|
|
23
|
-
if (!installed.current) {
|
|
24
|
-
installed.current = true;
|
|
25
|
-
installAllTodos({ layout, bootstrap, convex });
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
return <Layout />;
|
|
29
|
-
};
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ConvexReactClient } from "convex/react";
|
|
6
|
-
import { action, makeObservable, observable, runInAction } from "mobx";
|
|
7
|
-
|
|
8
|
-
import { api } from "@/api/_generated/api";
|
|
9
|
-
import type { TodoId } from "@/api/todos/types";
|
|
10
|
-
|
|
11
|
-
type AllTodosControllerOpts = {
|
|
12
|
-
convex: ConvexReactClient;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export class AllTodosController {
|
|
16
|
-
private readonly convex: ConvexReactClient;
|
|
17
|
-
|
|
18
|
-
public isPending = false;
|
|
19
|
-
public isNewTodoSheetOpen = false;
|
|
20
|
-
|
|
21
|
-
constructor({ convex }: AllTodosControllerOpts) {
|
|
22
|
-
this.convex = convex;
|
|
23
|
-
|
|
24
|
-
makeObservable(this, {
|
|
25
|
-
isPending: observable,
|
|
26
|
-
isNewTodoSheetOpen: observable,
|
|
27
|
-
openNewTodoSheet: action,
|
|
28
|
-
closeNewTodoSheet: action,
|
|
29
|
-
createTodo: action,
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
openNewTodoSheet() {
|
|
34
|
-
this.isNewTodoSheetOpen = true;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
closeNewTodoSheet() {
|
|
38
|
-
this.isNewTodoSheetOpen = false;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
async updateTodo(args: { todoId: TodoId; isCompleted: boolean }) {
|
|
42
|
-
await this.convex.mutation(api.todos.crud.updateTodo, args);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
async deleteTodo(args: { todoId: TodoId }) {
|
|
46
|
-
await this.convex.mutation(api.todos.crud.deleteTodo, args);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
async createTodo(args: { title: string; description?: string }) {
|
|
50
|
-
runInAction(() => {
|
|
51
|
-
this.isPending = true;
|
|
52
|
-
});
|
|
53
|
-
try {
|
|
54
|
-
await this.convex.mutation(api.todos.crud.createTodo, args);
|
|
55
|
-
} finally {
|
|
56
|
-
runInAction(() => {
|
|
57
|
-
this.isPending = false;
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use server";
|
|
6
|
-
|
|
7
|
-
import { fetchQuery } from "convex/nextjs";
|
|
8
|
-
|
|
9
|
-
import { api } from "@/api/_generated/api";
|
|
10
|
-
import type { Todo } from "@/api/todos/types";
|
|
11
|
-
import { getAuthToken } from "@/web/utils/auth";
|
|
12
|
-
|
|
13
|
-
export type AllTodosBootstrap = {
|
|
14
|
-
todos: Todo[];
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const bootstrapAllTodos = async (): Promise<AllTodosBootstrap> => {
|
|
18
|
-
const token = await getAuthToken();
|
|
19
|
-
const todos = await fetchQuery(api.todos.crud.listTodos, {}, { token });
|
|
20
|
-
return { todos };
|
|
21
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { observer } from "mobx-react-lite";
|
|
8
|
-
import type { ComponentType } from "react";
|
|
9
|
-
|
|
10
|
-
export type CreateHeaderOpts = {
|
|
11
|
-
NewTodoSheet: ComponentType;
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
export const createHeader = ({ NewTodoSheet }: CreateHeaderOpts) => {
|
|
15
|
-
return observer(() => {
|
|
16
|
-
return (
|
|
17
|
-
<div className="mb-6 flex items-center justify-between">
|
|
18
|
-
<h1 className="text-2xl font-semibold">Todos</h1>
|
|
19
|
-
<NewTodoSheet />
|
|
20
|
-
</div>
|
|
21
|
-
);
|
|
22
|
-
});
|
|
23
|
-
};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { ConvexReactClient } from "convex/react";
|
|
6
|
-
|
|
7
|
-
import { AllTodosController } from "@/web/surfaces/todos/all_todos/all_todos_controller";
|
|
8
|
-
import type { AllTodosBootstrap } from "@/web/surfaces/todos/all_todos/bootstrap";
|
|
9
|
-
import type { AllTodosLayoutController } from "@/web/surfaces/todos/all_todos/layout";
|
|
10
|
-
|
|
11
|
-
export type InstallAllTodosOpts = {
|
|
12
|
-
layout: AllTodosLayoutController;
|
|
13
|
-
bootstrap: AllTodosBootstrap;
|
|
14
|
-
convex: ConvexReactClient;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const installAllTodos = ({ layout, bootstrap, convex }: InstallAllTodosOpts) => {
|
|
18
|
-
const controller = new AllTodosController({ convex });
|
|
19
|
-
|
|
20
|
-
import("@/web/surfaces/todos/all_todos/main/create").then(({ createMain }) => {
|
|
21
|
-
layout.setMain(createMain({ controller, bootstrap }));
|
|
22
|
-
});
|
|
23
|
-
};
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { makeObservable, observable, action, runInAction } from "mobx";
|
|
8
|
-
import { observer } from "mobx-react-lite";
|
|
9
|
-
import type { ComponentType } from "react";
|
|
10
|
-
|
|
11
|
-
import { Skeleton } from "@/ui/base/skeleton";
|
|
12
|
-
|
|
13
|
-
export class AllTodosLayoutController {
|
|
14
|
-
Main: ComponentType | undefined = undefined;
|
|
15
|
-
|
|
16
|
-
constructor() {
|
|
17
|
-
makeObservable(this, {
|
|
18
|
-
Main: observable.ref,
|
|
19
|
-
setMain: action,
|
|
20
|
-
});
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
setMain(Main: ComponentType) {
|
|
24
|
-
runInAction(() => {
|
|
25
|
-
this.Main = Main;
|
|
26
|
-
});
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
export const createLayout = () => {
|
|
31
|
-
const layout = new AllTodosLayoutController();
|
|
32
|
-
return {
|
|
33
|
-
layout,
|
|
34
|
-
Layout: observer(() => {
|
|
35
|
-
const Main = layout.Main;
|
|
36
|
-
|
|
37
|
-
return (
|
|
38
|
-
<div className="flex w-full flex-1 flex-col">
|
|
39
|
-
{Main ? <Main /> : <Skeleton className="h-64 w-full rounded-md" />}
|
|
40
|
-
</div>
|
|
41
|
-
);
|
|
42
|
-
}),
|
|
43
|
-
};
|
|
44
|
-
};
|
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { convexQuery } from "@convex-dev/react-query";
|
|
8
|
-
import { useQuery } from "@tanstack/react-query";
|
|
9
|
-
import { observer } from "mobx-react-lite";
|
|
10
|
-
|
|
11
|
-
import { api } from "@/api/_generated/api";
|
|
12
|
-
import type { AllTodosController } from "@/web/surfaces/todos/all_todos/all_todos_controller";
|
|
13
|
-
import type { AllTodosBootstrap } from "@/web/surfaces/todos/all_todos/bootstrap";
|
|
14
|
-
import { createHeader } from "@/web/surfaces/todos/all_todos/header/create";
|
|
15
|
-
import { Main } from "@/web/surfaces/todos/all_todos/main/main";
|
|
16
|
-
import { createNewTodoSheet } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/create";
|
|
17
|
-
|
|
18
|
-
export type CreateMainOpts = {
|
|
19
|
-
controller: AllTodosController;
|
|
20
|
-
bootstrap: AllTodosBootstrap;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
export const createMain = ({ controller, bootstrap }: CreateMainOpts) => {
|
|
24
|
-
const NewTodoSheet = createNewTodoSheet({ controller });
|
|
25
|
-
const Header = createHeader({ NewTodoSheet });
|
|
26
|
-
|
|
27
|
-
return observer(() => {
|
|
28
|
-
const { data: todos, isPending } = useQuery({
|
|
29
|
-
...convexQuery(api.todos.crud.listTodos, {}),
|
|
30
|
-
initialData: bootstrap.todos as never,
|
|
31
|
-
});
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<main className="flex flex-1 flex-col overflow-auto p-6">
|
|
35
|
-
<Header />
|
|
36
|
-
<Main
|
|
37
|
-
todos={todos}
|
|
38
|
-
isPending={isPending}
|
|
39
|
-
onToggle={(todoId, isCompleted) => {
|
|
40
|
-
void controller.updateTodo({ todoId, isCompleted });
|
|
41
|
-
}}
|
|
42
|
-
onDelete={(todoId) => {
|
|
43
|
-
void controller.deleteTodo({ todoId });
|
|
44
|
-
}}
|
|
45
|
-
/>
|
|
46
|
-
</main>
|
|
47
|
-
);
|
|
48
|
-
});
|
|
49
|
-
};
|
|
@@ -1,70 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { Trash2 } from "lucide-react";
|
|
8
|
-
import Link from "next/link";
|
|
9
|
-
|
|
10
|
-
import type { Todo, TodoId } from "@/api/todos/types";
|
|
11
|
-
import { Button } from "@/ui/base/button";
|
|
12
|
-
import { Checkbox } from "@/ui/base/checkbox";
|
|
13
|
-
import { Spinner } from "@/ui/base/spinner";
|
|
14
|
-
|
|
15
|
-
type MainProps = {
|
|
16
|
-
todos: Todo[] | undefined;
|
|
17
|
-
isPending: boolean;
|
|
18
|
-
onToggle: (todoId: TodoId, isCompleted: boolean) => void;
|
|
19
|
-
onDelete: (todoId: TodoId) => void;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
export const Main = ({ todos, isPending, onToggle, onDelete }: MainProps) => {
|
|
23
|
-
if (isPending) {
|
|
24
|
-
return (
|
|
25
|
-
<div className="flex flex-1 items-center justify-center">
|
|
26
|
-
<Spinner />
|
|
27
|
-
</div>
|
|
28
|
-
);
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
if (!todos?.length) {
|
|
32
|
-
return (
|
|
33
|
-
<div className="flex flex-1 flex-col items-center justify-center gap-2 text-muted-foreground">
|
|
34
|
-
<p>No todos yet.</p>
|
|
35
|
-
<p className="text-sm">Use New Todo above to add one.</p>
|
|
36
|
-
</div>
|
|
37
|
-
);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return (
|
|
41
|
-
<ul className="flex max-w-2xl flex-col gap-2">
|
|
42
|
-
{todos.map((todo) => (
|
|
43
|
-
<li key={todo._id} className="flex items-center gap-3 rounded-lg border bg-card px-4 py-3">
|
|
44
|
-
<Checkbox
|
|
45
|
-
checked={todo.isCompleted}
|
|
46
|
-
onCheckedChange={(checked) => onToggle(todo._id, !!checked)}
|
|
47
|
-
/>
|
|
48
|
-
<Link
|
|
49
|
-
href={`/todos/${todo._id}`}
|
|
50
|
-
className="flex-1 truncate text-sm hover:underline"
|
|
51
|
-
style={{
|
|
52
|
-
textDecoration: todo.isCompleted ? "line-through" : undefined,
|
|
53
|
-
}}
|
|
54
|
-
>
|
|
55
|
-
{todo.title}
|
|
56
|
-
</Link>
|
|
57
|
-
<Button
|
|
58
|
-
variant="ghost"
|
|
59
|
-
size="icon"
|
|
60
|
-
className="size-7 shrink-0 text-muted-foreground hover:text-destructive"
|
|
61
|
-
type="button"
|
|
62
|
-
onClick={() => onDelete(todo._id)}
|
|
63
|
-
>
|
|
64
|
-
<Trash2 className="size-4" />
|
|
65
|
-
</Button>
|
|
66
|
-
</li>
|
|
67
|
-
))}
|
|
68
|
-
</ul>
|
|
69
|
-
);
|
|
70
|
-
};
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { zodResolver } from "@hookform/resolvers/zod";
|
|
8
|
-
import { observer } from "mobx-react-lite";
|
|
9
|
-
import { FormProvider, useForm } from "react-hook-form";
|
|
10
|
-
|
|
11
|
-
import type { AllTodosController } from "@/web/surfaces/todos/all_todos/all_todos_controller";
|
|
12
|
-
import { NewTodoSheet } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet";
|
|
13
|
-
import { type NewTodoSchema, newTodoSchema } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/schema";
|
|
14
|
-
|
|
15
|
-
export type CreateNewTodoSheetOpts = {
|
|
16
|
-
controller: AllTodosController;
|
|
17
|
-
};
|
|
18
|
-
|
|
19
|
-
export const createNewTodoSheet = ({ controller }: CreateNewTodoSheetOpts) => {
|
|
20
|
-
return observer(() => {
|
|
21
|
-
const form = useForm<NewTodoSchema>({
|
|
22
|
-
resolver: zodResolver(newTodoSchema),
|
|
23
|
-
defaultValues: {
|
|
24
|
-
title: "",
|
|
25
|
-
description: "",
|
|
26
|
-
},
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
const handleSubmit = form.handleSubmit(async (values) => {
|
|
30
|
-
await controller.createTodo({
|
|
31
|
-
title: values.title.trim(),
|
|
32
|
-
description: values.description?.trim() || undefined,
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
form.reset();
|
|
36
|
-
controller.closeNewTodoSheet();
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
return (
|
|
40
|
-
<FormProvider {...form}>
|
|
41
|
-
<NewTodoSheet
|
|
42
|
-
isPending={controller.createTodoPending}
|
|
43
|
-
onSubmit={handleSubmit}
|
|
44
|
-
open={controller.isNewTodoSheetOpen}
|
|
45
|
-
onOpenChange={(open) => {
|
|
46
|
-
if (open) {
|
|
47
|
-
controller.openNewTodoSheet();
|
|
48
|
-
} else {
|
|
49
|
-
controller.closeNewTodoSheet();
|
|
50
|
-
}
|
|
51
|
-
}}
|
|
52
|
-
/>
|
|
53
|
-
</FormProvider>
|
|
54
|
-
);
|
|
55
|
-
});
|
|
56
|
-
};
|
package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { PlusIcon } from "lucide-react";
|
|
8
|
-
import { useFormContext } from "react-hook-form";
|
|
9
|
-
|
|
10
|
-
import { Button } from "@/ui/base/button";
|
|
11
|
-
import { DialogDescription, DialogFooter, DialogHeader, DialogTitle } from "@/ui/base/dialog";
|
|
12
|
-
import { FormControl, FormField, FormItem, FormLabel, FormMessage } from "@/ui/base/form";
|
|
13
|
-
import { Input } from "@/ui/base/input";
|
|
14
|
-
import { Sheet, SheetContent, SheetTrigger } from "@/ui/base/sheet";
|
|
15
|
-
import { TextArea } from "@/ui/base/text_area";
|
|
16
|
-
import type { NewTodoSchema } from "@/web/surfaces/todos/all_todos/main/new_todo_sheet/schema";
|
|
17
|
-
|
|
18
|
-
type NewTodoSheetProps = {
|
|
19
|
-
isPending: boolean;
|
|
20
|
-
onSubmit: () => void;
|
|
21
|
-
open: boolean;
|
|
22
|
-
onOpenChange: (open: boolean) => void;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
export const NewTodoSheet = ({
|
|
26
|
-
isPending,
|
|
27
|
-
onSubmit,
|
|
28
|
-
open,
|
|
29
|
-
onOpenChange,
|
|
30
|
-
}: NewTodoSheetProps) => {
|
|
31
|
-
const form = useFormContext<NewTodoSchema>();
|
|
32
|
-
|
|
33
|
-
return (
|
|
34
|
-
<Sheet open={open} onOpenChange={onOpenChange}>
|
|
35
|
-
<SheetTrigger asChild>
|
|
36
|
-
<Button type="button">
|
|
37
|
-
<PlusIcon className="mr-2 size-4" />
|
|
38
|
-
New Todo
|
|
39
|
-
</Button>
|
|
40
|
-
</SheetTrigger>
|
|
41
|
-
<SheetContent className="sm:max-w-[440px]">
|
|
42
|
-
<DialogHeader>
|
|
43
|
-
<DialogTitle>Create Todo</DialogTitle>
|
|
44
|
-
<DialogDescription>Add a new item to your todo list.</DialogDescription>
|
|
45
|
-
</DialogHeader>
|
|
46
|
-
<form
|
|
47
|
-
onSubmit={(e) => {
|
|
48
|
-
e.preventDefault();
|
|
49
|
-
onSubmit();
|
|
50
|
-
}}
|
|
51
|
-
className="flex flex-col gap-4 pt-4"
|
|
52
|
-
onKeyDown={(e) => {
|
|
53
|
-
if (e.key === "Enter") {
|
|
54
|
-
e.preventDefault();
|
|
55
|
-
onSubmit();
|
|
56
|
-
}
|
|
57
|
-
}}
|
|
58
|
-
>
|
|
59
|
-
<FormField
|
|
60
|
-
control={form.control}
|
|
61
|
-
name="title"
|
|
62
|
-
render={({ field }) => (
|
|
63
|
-
<FormItem>
|
|
64
|
-
<FormLabel>Title</FormLabel>
|
|
65
|
-
<FormControl>
|
|
66
|
-
<Input placeholder="Buy groceries..." {...field} />
|
|
67
|
-
</FormControl>
|
|
68
|
-
<FormMessage />
|
|
69
|
-
</FormItem>
|
|
70
|
-
)}
|
|
71
|
-
/>
|
|
72
|
-
<FormField
|
|
73
|
-
control={form.control}
|
|
74
|
-
name="description"
|
|
75
|
-
render={({ field }) => (
|
|
76
|
-
<FormItem>
|
|
77
|
-
<FormLabel>Description</FormLabel>
|
|
78
|
-
<FormControl>
|
|
79
|
-
<TextArea
|
|
80
|
-
placeholder="Optional details..."
|
|
81
|
-
className="resize-none"
|
|
82
|
-
rows={3}
|
|
83
|
-
{...field}
|
|
84
|
-
/>
|
|
85
|
-
</FormControl>
|
|
86
|
-
<FormMessage />
|
|
87
|
-
</FormItem>
|
|
88
|
-
)}
|
|
89
|
-
/>
|
|
90
|
-
<DialogFooter>
|
|
91
|
-
<Button type="submit" disabled={isPending}>
|
|
92
|
-
{isPending ? "Creating..." : "Create Todo"}
|
|
93
|
-
</Button>
|
|
94
|
-
</DialogFooter>
|
|
95
|
-
</form>
|
|
96
|
-
</SheetContent>
|
|
97
|
-
</Sheet>
|
|
98
|
-
);
|
|
99
|
-
};
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { fetchQuery } from "convex/nextjs";
|
|
6
|
-
import { redirect } from "next/navigation";
|
|
7
|
-
|
|
8
|
-
import { api } from "@/api/_generated/api";
|
|
9
|
-
import type { Todo, TodoId } from "@/api/todos/types";
|
|
10
|
-
import { getAuthToken } from "@/web/utils/auth";
|
|
11
|
-
|
|
12
|
-
export type SingleTodoBootstrapArgs = {
|
|
13
|
-
todoId: TodoId;
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
export type SingleTodoBootstrap = {
|
|
17
|
-
todoId: TodoId;
|
|
18
|
-
todo: Todo;
|
|
19
|
-
};
|
|
20
|
-
|
|
21
|
-
export const bootstrapSingleTodo = async ({
|
|
22
|
-
todoId,
|
|
23
|
-
}: SingleTodoBootstrapArgs): Promise<SingleTodoBootstrap> => {
|
|
24
|
-
try {
|
|
25
|
-
const token = await getAuthToken();
|
|
26
|
-
const todo = await fetchQuery(api.todos.crud.getTodo, { todoId }, { token });
|
|
27
|
-
return { todoId, todo };
|
|
28
|
-
} catch {
|
|
29
|
-
redirect("/todos");
|
|
30
|
-
throw new Error("Unreachable");
|
|
31
|
-
}
|
|
32
|
-
};
|
|
@@ -1,22 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { ArrowLeft } from "lucide-react";
|
|
8
|
-
|
|
9
|
-
import { Button } from "@/ui/base/button";
|
|
10
|
-
|
|
11
|
-
type HeaderProps = {
|
|
12
|
-
onBack: () => void;
|
|
13
|
-
};
|
|
14
|
-
|
|
15
|
-
export const Header = ({ onBack }: HeaderProps) => {
|
|
16
|
-
return (
|
|
17
|
-
<Button variant="ghost" className="-ml-2 w-fit text-muted-foreground" type="button" onClick={onBack}>
|
|
18
|
-
<ArrowLeft className="mr-2 size-4" />
|
|
19
|
-
Back
|
|
20
|
-
</Button>
|
|
21
|
-
);
|
|
22
|
-
};
|
|
@@ -1,27 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import type { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
|
|
6
|
-
|
|
7
|
-
import type { SingleTodoBootstrap } from "@/web/surfaces/todos/single_todo/bootstrap";
|
|
8
|
-
import type { SingleTodoLayoutController } from "@/web/surfaces/todos/single_todo/layout";
|
|
9
|
-
import { SingleTodoController } from "@/web/surfaces/todos/single_todo/single_todo_controller";
|
|
10
|
-
|
|
11
|
-
export type InstallSingleTodoOpts = {
|
|
12
|
-
layout: SingleTodoLayoutController;
|
|
13
|
-
bootstrap: SingleTodoBootstrap;
|
|
14
|
-
router: AppRouterInstance;
|
|
15
|
-
};
|
|
16
|
-
|
|
17
|
-
export const installSingleTodo = ({ layout, bootstrap, router }: InstallSingleTodoOpts) => {
|
|
18
|
-
const controller = new SingleTodoController();
|
|
19
|
-
|
|
20
|
-
import("@/web/surfaces/todos/single_todo/header/create").then(({ createHeader }) => {
|
|
21
|
-
layout.setHeader(createHeader({ router }));
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
import("@/web/surfaces/todos/single_todo/main/create").then(({ createMain }) => {
|
|
25
|
-
layout.setMain(createMain({ controller, bootstrap }));
|
|
26
|
-
});
|
|
27
|
-
};
|
|
@@ -1,55 +0,0 @@
|
|
|
1
|
-
/*
|
|
2
|
-
* Copyright (c) Aron Weston 2026.
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
"use client";
|
|
6
|
-
|
|
7
|
-
import { makeObservable, observable, action, runInAction } from "mobx";
|
|
8
|
-
import { observer } from "mobx-react-lite";
|
|
9
|
-
import type { ComponentType } from "react";
|
|
10
|
-
|
|
11
|
-
import { Skeleton } from "@/ui/base/skeleton";
|
|
12
|
-
|
|
13
|
-
export class SingleTodoLayoutController {
|
|
14
|
-
Header: ComponentType | undefined = undefined;
|
|
15
|
-
Main: ComponentType | undefined = undefined;
|
|
16
|
-
|
|
17
|
-
constructor() {
|
|
18
|
-
makeObservable(this, {
|
|
19
|
-
Header: observable.ref,
|
|
20
|
-
Main: observable.ref,
|
|
21
|
-
setHeader: action,
|
|
22
|
-
setMain: action,
|
|
23
|
-
});
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
setHeader(Header: ComponentType) {
|
|
27
|
-
runInAction(() => {
|
|
28
|
-
this.Header = Header;
|
|
29
|
-
});
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
setMain(Main: ComponentType) {
|
|
33
|
-
runInAction(() => {
|
|
34
|
-
this.Main = Main;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
export const createLayout = () => {
|
|
40
|
-
const layout = new SingleTodoLayoutController();
|
|
41
|
-
return {
|
|
42
|
-
layout,
|
|
43
|
-
Layout: observer(() => {
|
|
44
|
-
const Header = layout.Header;
|
|
45
|
-
const Main = layout.Main;
|
|
46
|
-
|
|
47
|
-
return (
|
|
48
|
-
<div className="flex w-full flex-1 flex-col gap-4">
|
|
49
|
-
{Header ? <Header /> : <Skeleton className="h-10 w-48 rounded-md" />}
|
|
50
|
-
{Main ? <Main /> : <Skeleton className="h-64 w-full max-w-2xl rounded-md" />}
|
|
51
|
-
</div>
|
|
52
|
-
);
|
|
53
|
-
}),
|
|
54
|
-
};
|
|
55
|
-
};
|