create-aron-app 0.1.5 → 0.1.7
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/README.md +73 -38
- package/dist/index.js +50 -115
- package/package.json +5 -2
- package/templates/{_base/.cursor → .cursor}/rules/api_architecture.mdc +27 -33
- package/templates/{_base/.cursor → .cursor}/rules/coding_standards.mdc +1 -1
- package/templates/{_base/.cursor → .cursor}/rules/convex_rules.mdc +78 -422
- package/templates/.cursor/rules/frontend_architecture_core.mdc +495 -0
- package/templates/.cursor/rules/frontend_architecture_nextjs.mdc +458 -0
- package/templates/.cursor/rules/frontend_architecture_reactrouter.mdc +473 -0
- package/templates/apps/api/_generated/api.d.ts +57 -0
- package/templates/apps/api/_generated/api.js +23 -0
- package/templates/apps/api/_generated/dataModel.d.ts +60 -0
- package/templates/apps/api/_generated/server.d.ts +143 -0
- package/templates/apps/api/_generated/server.js +93 -0
- package/templates/apps/api/http.ts +16 -0
- package/templates/apps/nextjs/.env.example +10 -0
- package/templates/{nextjs → apps/nextjs}/project.json +5 -5
- package/templates/{nextjs → apps/nextjs}/src/app/(auth)/not-allowed/page.tsx +1 -0
- package/templates/apps/nextjs/src/app/(dashboard)/layout.tsx +22 -0
- package/templates/apps/nextjs/src/app/(dashboard)/page.tsx +12 -0
- package/templates/{nextjs → apps/nextjs}/src/app/(dashboard)/todos/[id]/page.tsx +5 -2
- package/templates/apps/nextjs/src/app/(dashboard)/todos/page.tsx +19 -0
- package/templates/apps/nextjs/src/middleware.ts +18 -0
- package/templates/apps/nextjs/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/nextjs/src/surfaces/home/home.tsx +27 -0
- package/templates/apps/nextjs/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/nextjs/src/surfaces/home/layout.tsx +44 -0
- package/templates/apps/nextjs/src/surfaces/home/main/create.tsx +34 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/install.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/layout.tsx +118 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/create.tsx +19 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_config.ts +22 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_main/nav_main.tsx +25 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +33 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/sidebar.tsx +23 -0
- package/templates/{nextjs/src/ui/sidebar/nav_link.tsx → apps/nextjs/src/surfaces/sidebar/ui/sidebar_nav_link.tsx} +13 -10
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/create.tsx +28 -0
- package/templates/apps/nextjs/src/surfaces/sidebar/user_menu/user_menu.tsx +42 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos.tsx +29 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/all_todos_controller.ts +61 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/bootstrap.ts +21 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/header/create.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/install.tsx +23 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/layout.tsx +44 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/create.tsx +49 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/main.tsx +70 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +56 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +99 -0
- package/templates/apps/nextjs/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/bootstrap.ts +32 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/create.tsx +26 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/header/header.tsx +22 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/layout.tsx +55 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/create.tsx +38 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/main/main.tsx +49 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo.tsx +29 -0
- package/templates/apps/nextjs/src/surfaces/todos/single_todo/single_todo_controller.ts +13 -0
- package/templates/apps/nextjs/src/utils/auth.ts +18 -0
- package/templates/apps/react-router/.env.example +10 -0
- package/templates/apps/react-router/.react-router/types/+future.ts +9 -0
- package/templates/apps/react-router/.react-router/types/+routes.ts +71 -0
- package/templates/apps/react-router/.react-router/types/+server-build.d.ts +18 -0
- package/templates/apps/react-router/.react-router/types/src/+types/root.ts +59 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(auth)/+types/layout.ts +62 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(auth)/sign-in/+types/index.ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/index.ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/+types/layout.ts +62 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/[id].ts +65 -0
- package/templates/apps/react-router/.react-router/types/src/routes/(dashboard)/todos/+types/index.ts +65 -0
- package/templates/{react-router → apps/react-router}/project.json +4 -4
- package/templates/apps/react-router/src/app.css +3 -0
- package/templates/{react-router → apps/react-router}/src/components/error_boundary.tsx +1 -1
- package/templates/{react-router → apps/react-router}/src/providers/api_auth_provider.tsx +2 -0
- package/templates/{react-router/src/routes/auth → apps/react-router/src/routes/(auth)}/layout.tsx +1 -1
- package/templates/apps/react-router/src/routes/(dashboard)/index.tsx +19 -0
- package/templates/apps/react-router/src/routes/(dashboard)/layout.tsx +37 -0
- package/templates/apps/react-router/src/routes/(dashboard)/todos/[id].tsx +19 -0
- package/templates/apps/react-router/src/routes/(dashboard)/todos/index.tsx +19 -0
- package/templates/apps/react-router/src/routes.ts +12 -0
- package/templates/apps/react-router/src/surfaces/home/bootstrap.ts +9 -0
- package/templates/apps/react-router/src/surfaces/home/home.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/home/install.tsx +17 -0
- package/templates/apps/react-router/src/surfaces/home/layout.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/home/main/create.tsx +32 -0
- package/templates/apps/react-router/src/surfaces/sidebar/install.tsx +23 -0
- package/templates/apps/react-router/src/surfaces/sidebar/layout.tsx +110 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_main/create.tsx +31 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_main/nav_main.tsx +42 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/create.tsx +21 -0
- package/templates/apps/react-router/src/surfaces/sidebar/nav_secondary/nav_secondary.tsx +31 -0
- package/templates/apps/react-router/src/surfaces/sidebar/sidebar.tsx +18 -0
- package/templates/apps/react-router/src/surfaces/sidebar/user_menu/create.tsx +26 -0
- package/templates/{react-router/src/layouts/sidebar/sidebar_aside → apps/react-router/src/surfaces/sidebar/user_menu}/user_menu.tsx +13 -9
- package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/all_todos_controller.ts +47 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/bootstrap.ts +18 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/header/create.tsx +21 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/install.tsx +20 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/layout.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/create.tsx +47 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/main.tsx +68 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/create.tsx +54 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/new_todo_sheet.tsx +97 -0
- package/templates/apps/react-router/src/surfaces/todos/all_todos/main/new_todo_sheet/schema.ts +11 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/bootstrap.ts +36 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/create.tsx +32 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/header/header.tsx +25 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/install.tsx +27 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/layout.tsx +45 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/main/create.tsx +35 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/main/main.tsx +47 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo.tsx +27 -0
- package/templates/apps/react-router/src/surfaces/todos/single_todo/single_todo_controller.ts +16 -0
- package/templates/{react-router → apps/react-router}/vite.config.ts +27 -3
- package/templates/{_base/biome.json → biome.json} +7 -0
- package/templates/bun.lock +3187 -0
- package/templates/{_base/emails → emails}/project.json +1 -1
- package/templates/{_base/nx.json → nx.json} +11 -0
- package/templates/package.json +92 -0
- package/templates/{_base/tsconfig.base.json → tsconfig.base.json} +4 -1
- package/templates/_base/.cursor/rules/frontend_rules.mdc +0 -268
- package/templates/_base/.env.convex.example +0 -3
- package/templates/_base/_gitignore +0 -58
- package/templates/_base/package.json +0 -73
- package/templates/nextjs/.env.example +0 -8
- package/templates/nextjs/src/app/(dashboard)/layout.tsx +0 -27
- package/templates/nextjs/src/app/(dashboard)/page.tsx +0 -5
- package/templates/nextjs/src/app/(dashboard)/todos/page.tsx +0 -16
- package/templates/nextjs/src/middleware.ts +0 -18
- package/templates/nextjs/src/surfaces/home_surface.tsx +0 -22
- package/templates/nextjs/src/surfaces/todos/all_todos_surface.tsx +0 -97
- package/templates/nextjs/src/surfaces/todos/create_todo_sheet.tsx +0 -107
- package/templates/nextjs/src/surfaces/todos/single_todo_surface.tsx +0 -90
- package/templates/nextjs/src/ui/sidebar/sidebar.tsx +0 -125
- package/templates/react-router/.env.example +0 -8
- package/templates/react-router/src/app.css +0 -3
- package/templates/react-router/src/layouts/sidebar/sidebar_aside/sidebar_aside.tsx +0 -76
- package/templates/react-router/src/layouts/sidebar/sidebar_layout.tsx +0 -22
- package/templates/react-router/src/routes/index.tsx +0 -9
- package/templates/react-router/src/routes/layout.tsx +0 -26
- package/templates/react-router/src/routes/todos/[id].tsx +0 -22
- package/templates/react-router/src/routes/todos/index.tsx +0 -13
- package/templates/react-router/src/routes.ts +0 -12
- package/templates/react-router/src/surfaces/home_surface.tsx +0 -20
- package/templates/react-router/src/surfaces/todos/all_todos_surface.tsx +0 -87
- package/templates/react-router/src/surfaces/todos/create_todo_sheet.tsx +0 -102
- package/templates/react-router/src/surfaces/todos/single_todo_surface.tsx +0 -81
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/api-specs-context.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/execute-request.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-endpoint-detail.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tag-endpoints.sh +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-backend-api/scripts/extract-tags.js +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-2/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-in.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/custom-sign-up.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-custom-ui/core-3/show-component.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/api-routes.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/caching-auth.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/middleware-strategies.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-actions.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-nextjs-patterns/references/server-vs-client.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/clerk/clerk-webhooks/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/SKILL.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/agents/openai.yml +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn-small.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/assets/shadcn.png +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/cli.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/customization.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/evals/evals.json +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/mcp.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/base-vs-radix.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/composition.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/forms.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/icons.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/agents/skills/shadcn/rules/styling.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/builder.md +0 -0
- /package/templates/{_base/.cursor → .cursor}/commands/pr.md +0 -0
- /package/templates/{_base/.github → .github}/workflows/ci.yml +0 -0
- /package/templates/{_base/.nvmrc → .nvmrc} +0 -0
- /package/templates/{_base/.vscode → .vscode}/settings.json +0 -0
- /package/templates/{_base/apps → apps}/api/auth.config.ts +0 -0
- /package/templates/{_base/apps → apps}/api/functions.ts +0 -0
- /package/templates/{_base/apps → apps}/api/project.json +0 -0
- /package/templates/{_base/apps → apps}/api/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/crud.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/schema.ts +0 -0
- /package/templates/{_base/apps → apps}/api/todos/types.ts +0 -0
- /package/templates/{_base/apps → apps}/api/tsconfig.json +0 -0
- /package/templates/{_base/apps → apps}/api/types.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/index.d.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/next-env.d.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/next.config.js +0 -0
- /package/templates/{nextjs → apps/nextjs}/postcss.config.js +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/layout.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/(auth)/sign-in/[[...sign-in]]/page.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/app.css +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/app/layout.tsx +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/providers/convex_provider.tsx +0 -0
- /package/templates/{nextjs/src → apps/nextjs/src/utils}/convex.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/src/utils/font.ts +0 -0
- /package/templates/{nextjs → apps/nextjs}/tsconfig.json +0 -0
- /package/templates/{react-router → apps/react-router}/postcss.config.js +0 -0
- /package/templates/{react-router → apps/react-router}/public/favicon.ico +0 -0
- /package/templates/{react-router → apps/react-router}/react-router.config.ts +0 -0
- /package/templates/{react-router → apps/react-router}/src/root.tsx +0 -0
- /package/templates/{react-router/src/routes/auth/sign-in.tsx → apps/react-router/src/routes/(auth)/sign-in/index.tsx} +0 -0
- /package/templates/{react-router → apps/react-router}/tsconfig.json +0 -0
- /package/templates/{_base/convex.json → convex.json} +0 -0
- /package/templates/{_base/emails → emails}/tsconfig.json +0 -0
- /package/templates/{_base/emails → emails}/welcome_email.tsx +0 -0
- /package/templates/{_base/scripts → scripts}/sync_convex_env.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/image.d.ts +0 -0
- /package/templates/{_base/shared → shared}/assets/src/styles/global.css +0 -0
- /package/templates/{_base/shared → shared}/assets/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/alert_dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/badge.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/basic_data_table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/button_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/card.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/checkbox.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/command.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dialog.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/dropdown_menu.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/form.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/input.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/label.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/popover.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/radio_group.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/resizable.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/scroll_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/select.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/separator.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/sheet.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/side_bar.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/skeleton.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/spinner.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/switch.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/table.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/text_area.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/tooltip.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/base/utils.ts +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_press.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_keyboard_release.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mobile.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_mouse_location.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_outside_click.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/src/hooks/use_query_params.tsx +0 -0
- /package/templates/{_base/shared → shared}/ui/tsconfig.json +0 -0
- /package/templates/{_base/shared → shared}/utils/src/convex.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/src/time.ts +0 -0
- /package/templates/{_base/shared → shared}/utils/tsconfig.json +0 -0
- /package/templates/{_base/skills-lock.json → skills-lock.json} +0 -0
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { observer } from "mobx-react-lite";
|
|
8
|
+
|
|
9
|
+
import { useSidebar } from "@/ui/base/side_bar";
|
|
10
|
+
import { navMainItems } from "@/web/surfaces/sidebar/nav_main/nav_config";
|
|
11
|
+
import { NavMain } from "@/web/surfaces/sidebar/nav_main/nav_main";
|
|
12
|
+
|
|
13
|
+
export const createNavMain = () => {
|
|
14
|
+
return observer(() => {
|
|
15
|
+
const { setOpenMobile } = useSidebar();
|
|
16
|
+
|
|
17
|
+
return <NavMain items={navMainItems} onNavigate={() => setOpenMobile(false)} />;
|
|
18
|
+
});
|
|
19
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { CheckSquareIcon, HomeIcon } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
import type { SidebarNavItem } from "@/web/surfaces/sidebar/ui/sidebar_nav_link";
|
|
8
|
+
|
|
9
|
+
export const navMainItems: SidebarNavItem[] = [
|
|
10
|
+
{
|
|
11
|
+
id: "dashboard",
|
|
12
|
+
title: "Dashboard",
|
|
13
|
+
url: "/",
|
|
14
|
+
icon: HomeIcon,
|
|
15
|
+
},
|
|
16
|
+
{
|
|
17
|
+
id: "todos",
|
|
18
|
+
title: "Todos",
|
|
19
|
+
url: "/todos",
|
|
20
|
+
icon: CheckSquareIcon,
|
|
21
|
+
},
|
|
22
|
+
];
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { SidebarMenu } from "@/ui/base/side_bar";
|
|
8
|
+
|
|
9
|
+
import type { SidebarNavItem } from "@/web/surfaces/sidebar/ui/sidebar_nav_link";
|
|
10
|
+
import { SidebarNavLink } from "@/web/surfaces/sidebar/ui/sidebar_nav_link";
|
|
11
|
+
|
|
12
|
+
type NavMainProps = {
|
|
13
|
+
items: SidebarNavItem[];
|
|
14
|
+
onNavigate: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const NavMain = ({ items, onNavigate }: NavMainProps) => {
|
|
18
|
+
return (
|
|
19
|
+
<SidebarMenu>
|
|
20
|
+
{items.map((item) => (
|
|
21
|
+
<SidebarNavLink key={item.id} item={item} onNavigate={onNavigate} />
|
|
22
|
+
))}
|
|
23
|
+
</SidebarMenu>
|
|
24
|
+
);
|
|
25
|
+
};
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { observer } from "mobx-react-lite";
|
|
8
|
+
import { usePathname } from "next/navigation";
|
|
9
|
+
|
|
10
|
+
import { useSidebar } from "@/ui/base/side_bar";
|
|
11
|
+
|
|
12
|
+
import { NavSecondary } from "@/web/surfaces/sidebar/nav_secondary/nav_secondary";
|
|
13
|
+
|
|
14
|
+
export const createNavSecondary = () => {
|
|
15
|
+
return observer(() => {
|
|
16
|
+
const pathname = usePathname();
|
|
17
|
+
const { setOpenMobile } = useSidebar();
|
|
18
|
+
|
|
19
|
+
return <NavSecondary pathname={pathname} onNavigate={() => setOpenMobile(false)} />;
|
|
20
|
+
});
|
|
21
|
+
};
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { SettingsIcon } from "lucide-react";
|
|
8
|
+
import Link from "next/link";
|
|
9
|
+
|
|
10
|
+
import { SidebarMenu, SidebarMenuButton, SidebarMenuItem } from "@/ui/base/side_bar";
|
|
11
|
+
|
|
12
|
+
type NavSecondaryProps = {
|
|
13
|
+
pathname: string;
|
|
14
|
+
onNavigate: () => void;
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export const NavSecondary = ({ pathname, onNavigate }: NavSecondaryProps) => {
|
|
18
|
+
const settingsPath = "/settings";
|
|
19
|
+
const isActive = pathname === settingsPath || pathname.startsWith(`${settingsPath}/`);
|
|
20
|
+
|
|
21
|
+
return (
|
|
22
|
+
<SidebarMenu>
|
|
23
|
+
<SidebarMenuItem>
|
|
24
|
+
<SidebarMenuButton isActive={isActive} asChild onClick={onNavigate}>
|
|
25
|
+
<Link href={settingsPath}>
|
|
26
|
+
<SettingsIcon className="size-4" />
|
|
27
|
+
<span>Settings</span>
|
|
28
|
+
</Link>
|
|
29
|
+
</SidebarMenuButton>
|
|
30
|
+
</SidebarMenuItem>
|
|
31
|
+
</SidebarMenu>
|
|
32
|
+
);
|
|
33
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { useMemo, useRef } from "react";
|
|
8
|
+
|
|
9
|
+
import { installSidebar } from "@/web/surfaces/sidebar/install";
|
|
10
|
+
import { createSidebarLayout } from "@/web/surfaces/sidebar/layout";
|
|
11
|
+
|
|
12
|
+
export const Sidebar = () => {
|
|
13
|
+
const { layout, Layout } = useMemo(createSidebarLayout, []);
|
|
14
|
+
|
|
15
|
+
const installed = useRef(false);
|
|
16
|
+
|
|
17
|
+
if (!installed.current) {
|
|
18
|
+
installed.current = true;
|
|
19
|
+
installSidebar({ layout });
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
return <Layout />;
|
|
23
|
+
};
|
|
@@ -10,24 +10,27 @@ import { usePathname } from "next/navigation";
|
|
|
10
10
|
|
|
11
11
|
import { SidebarMenuButton, SidebarMenuItem } from "@/ui/base/side_bar";
|
|
12
12
|
|
|
13
|
-
type
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
icon: LucideIcon;
|
|
19
|
-
};
|
|
13
|
+
export type SidebarNavItem = {
|
|
14
|
+
id: string;
|
|
15
|
+
title: string;
|
|
16
|
+
url: string;
|
|
17
|
+
icon: LucideIcon;
|
|
20
18
|
};
|
|
21
19
|
|
|
22
|
-
|
|
20
|
+
type SidebarNavLinkProps = {
|
|
21
|
+
item: SidebarNavItem;
|
|
22
|
+
onNavigate: () => void;
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export const SidebarNavLink = ({ item, onNavigate }: SidebarNavLinkProps) => {
|
|
23
26
|
const pathname = usePathname();
|
|
24
27
|
const isActive = pathname === item.url;
|
|
25
28
|
|
|
26
29
|
return (
|
|
27
30
|
<SidebarMenuItem>
|
|
28
|
-
<SidebarMenuButton asChild isActive={isActive} tooltip={item.title}>
|
|
31
|
+
<SidebarMenuButton asChild isActive={isActive} tooltip={item.title} onClick={onNavigate}>
|
|
29
32
|
<Link href={item.url}>
|
|
30
|
-
<item.icon />
|
|
33
|
+
<item.icon className="size-4" />
|
|
31
34
|
<span>{item.title}</span>
|
|
32
35
|
</Link>
|
|
33
36
|
</SidebarMenuButton>
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (c) Aron Weston 2026.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
"use client";
|
|
6
|
+
|
|
7
|
+
import { useUser } from "@clerk/nextjs";
|
|
8
|
+
import { observer } from "mobx-react-lite";
|
|
9
|
+
|
|
10
|
+
import { UserMenu } from "@/web/surfaces/sidebar/user_menu/user_menu";
|
|
11
|
+
|
|
12
|
+
export const createUserMenu = () => {
|
|
13
|
+
return observer(() => {
|
|
14
|
+
const { user, isLoaded } = useUser();
|
|
15
|
+
|
|
16
|
+
if (isLoaded && !user) {
|
|
17
|
+
return null;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<UserMenu
|
|
22
|
+
isLoaded={isLoaded}
|
|
23
|
+
fullName={user?.fullName}
|
|
24
|
+
email={user?.emailAddresses[0]?.emailAddress}
|
|
25
|
+
/>
|
|
26
|
+
);
|
|
27
|
+
});
|
|
28
|
+
};
|
|
@@ -0,0 +1,42 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,29 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,61 @@
|
|
|
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
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,23 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
};
|
|
@@ -0,0 +1,56 @@
|
|
|
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
|
+
};
|