diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7cc0658 --- /dev/null +++ b/.env.example @@ -0,0 +1 @@ +NEXT_PUBLIC_API_URL=http://localhost:3000/api \ No newline at end of file diff --git a/.gitignore b/.gitignore index 5ef6a52..8dd2831 100644 --- a/.gitignore +++ b/.gitignore @@ -3,12 +3,8 @@ # dependencies /node_modules /.pnp -.pnp.* -.yarn/* -!.yarn/patches -!.yarn/plugins -!.yarn/releases -!.yarn/versions +.pnp.js +.yarn/install-state.gz # testing /coverage @@ -28,10 +24,11 @@ npm-debug.log* yarn-debug.log* yarn-error.log* -.pnpm-debug.log* -# env files (can opt-in for committing if needed) -.env* +# local env files +.env +.env*.local +!.env.example # vercel .vercel diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..74d52a3 --- /dev/null +++ b/biome.json @@ -0,0 +1,48 @@ +{ + "$schema": "https://biomejs.dev/schemas/1.9.4/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "ignore": [] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "indentWidth": 2, + "lineWidth": 100 + }, + "organizeImports": { + "enabled": true + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true, + "correctness": { + "noUnusedVariables": "error", + "noUnusedImports": "error", + "requireKeyForArrayComponents": "error" + }, + "suspicious": { + "noExplicitAny": "error" + }, + "style": { + "useConst": "error", + "useTemplate": "error" + }, + "naming": { + "camelCase": "error" + } + } + }, + "javascript": { + "formatter": { + "quoteStyle": "double", + "semicolons": "always" + } + } +} diff --git a/package-lock.json b/package-lock.json index e203f0f..3972028 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,17 +8,177 @@ "name": "o2o-fe", "version": "0.1.0", "dependencies": { + "@tanstack/react-query": "^5.74.7", + "ky": "^1.8.1", "next": "15.3.1", "react": "^19.0.0", - "react-dom": "^19.0.0" + "react-dom": "^19.0.0", + "use-form-light": "^1.2.0", + "zustand": "^5.0.3" }, "devDependencies": { + "@biomejs/biome": "1.9.4", "@types/node": "^20", "@types/react": "^19", "@types/react-dom": "^19", "typescript": "^5" } }, + "node_modules/@biomejs/biome": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/biome/-/biome-1.9.4.tgz", + "integrity": "sha512-1rkd7G70+o9KkTn5KLmDYXihGoTaIGO9PIIN2ZB7UJxFrWw04CZHPYiMRjYsaDvVV7hP1dYNRLxSANLaBFGpog==", + "dev": true, + "hasInstallScript": true, + "bin": { + "biome": "bin/biome" + }, + "engines": { + "node": ">=14.21.3" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/biome" + }, + "optionalDependencies": { + "@biomejs/cli-darwin-arm64": "1.9.4", + "@biomejs/cli-darwin-x64": "1.9.4", + "@biomejs/cli-linux-arm64": "1.9.4", + "@biomejs/cli-linux-arm64-musl": "1.9.4", + "@biomejs/cli-linux-x64": "1.9.4", + "@biomejs/cli-linux-x64-musl": "1.9.4", + "@biomejs/cli-win32-arm64": "1.9.4", + "@biomejs/cli-win32-x64": "1.9.4" + } + }, + "node_modules/@biomejs/cli-darwin-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-arm64/-/cli-darwin-arm64-1.9.4.tgz", + "integrity": "sha512-bFBsPWrNvkdKrNCYeAp+xo2HecOGPAy9WyNyB/jKnnedgzl4W4Hb9ZMzYNbf8dMCGmUdSavlYHiR01QaYR58cw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-darwin-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-darwin-x64/-/cli-darwin-x64-1.9.4.tgz", + "integrity": "sha512-ngYBh/+bEedqkSevPVhLP4QfVPCpb+4BBe2p7Xs32dBgs7rh9nY2AIYUL6BgLw1JVXV8GlpKmb/hNiuIxfPfZg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64/-/cli-linux-arm64-1.9.4.tgz", + "integrity": "sha512-fJIW0+LYujdjUgJJuwesP4EjIBl/N/TcOX3IvIHJQNsAqvV2CHIogsmA94BPG6jZATS4Hi+xv4SkBBQSt1N4/g==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-arm64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-arm64-musl/-/cli-linux-arm64-musl-1.9.4.tgz", + "integrity": "sha512-v665Ct9WCRjGa8+kTr0CzApU0+XXtRgwmzIf1SeKSGAv+2scAlW6JR5PMFo6FzqqZ64Po79cKODKf3/AAmECqA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64/-/cli-linux-x64-1.9.4.tgz", + "integrity": "sha512-lRCJv/Vi3Vlwmbd6K+oQ0KhLHMAysN8lXoCI7XeHlxaajk06u7G+UsFSO01NAs5iYuWKmVZjmiOzJ0OJmGsMwg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-linux-x64-musl": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-linux-x64-musl/-/cli-linux-x64-musl-1.9.4.tgz", + "integrity": "sha512-gEhi/jSBhZ2m6wjV530Yy8+fNqG8PAinM3oV7CyO+6c3CEh16Eizm21uHVsyVBEB6RIM8JHIl6AGYCv6Q6Q9Tg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-arm64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-arm64/-/cli-win32-arm64-1.9.4.tgz", + "integrity": "sha512-tlbhLk+WXZmgwoIKwHIHEBZUwxml7bRJgk0X2sPyNR3S93cdRq6XulAZRQJ17FYGGzWne0fgrXBKpl7l4M87Hg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, + "node_modules/@biomejs/cli-win32-x64": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/@biomejs/cli-win32-x64/-/cli-win32-x64-1.9.4.tgz", + "integrity": "sha512-8Y5wMhVIPaWe6jw2H+KlEm4wP/f7EW3810ZLmDlrEEy5KvBsb9ECEfu/kMWD484ijfQ8+nIi0giMgu9g1UAuuA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=14.21.3" + } + }, "node_modules/@emnapi/runtime": { "version": "1.4.3", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.4.3.tgz", @@ -555,6 +715,30 @@ "tslib": "^2.8.0" } }, + "node_modules/@tanstack/query-core": { + "version": "5.74.7", + "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.74.7.tgz", + "integrity": "sha512-X3StkN/Y6KGHndTjJf8H8th7AX4bKfbRpiVhVqevf0QWlxl6DhyJ0TYG3R0LARa/+xqDwzU9mA4pbJxzPCI29A==", + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, + "node_modules/@tanstack/react-query": { + "version": "5.74.7", + "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.74.7.tgz", + "integrity": "sha512-u4o/RIWnnrq26orGZu2NDPwmVof1vtAiiV6KYUXd49GuK+8HX+gyxoAYqIaZogvCE1cqOuZAhQKcrKGYGkrLxg==", + "dependencies": { + "@tanstack/query-core": "5.74.7" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": "^18 || ^19" + } + }, "node_modules/@types/node": { "version": "20.17.32", "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.32.tgz", @@ -569,7 +753,7 @@ "version": "19.1.2", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.1.2.tgz", "integrity": "sha512-oxLPMytKchWGbnQM9O7D67uPa9paTNxO7jVoNMXgkkErULBPhPARCfkKL9ytcIJJRGjbsVwW4ugJzyFFvm/Tiw==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.0.2" @@ -671,7 +855,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, + "devOptional": true, "license": "MIT" }, "node_modules/detect-libc": { @@ -691,6 +875,17 @@ "license": "MIT", "optional": true }, + "node_modules/ky": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/ky/-/ky-1.8.1.tgz", + "integrity": "sha512-7Bp3TpsE+L+TARSnnDpk3xg8Idi8RwSLdj6CMbNWoOARIrGrbuLGusV0dYwbZOm4bB3jHNxSw8Wk/ByDqJEnDw==", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sindresorhus/ky?sponsor=1" + } + }, "node_modules/nanoid": { "version": "3.3.11", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", @@ -954,6 +1149,43 @@ "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, "license": "MIT" + }, + "node_modules/use-form-light": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-form-light/-/use-form-light-1.2.0.tgz", + "integrity": "sha512-UqVXuxgtRn7t2NH42kKef9iogmUJC11NMC/U02YwCsC4mNi3/+sfGH2ssZYhmGNPiVJydDe/EHX4esGM3EUI9A==", + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18 || ^19", + "react-dom": "^16.8.0 || ^17 || ^18 || ^19" + } + }, + "node_modules/zustand": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", + "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "immer": ">=9.0.6", + "react": ">=18.0.0", + "use-sync-external-store": ">=1.2.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "immer": { + "optional": true + }, + "react": { + "optional": true + }, + "use-sync-external-store": { + "optional": true + } + } } } } diff --git a/package.json b/package.json index 02f628b..55d473c 100644 --- a/package.json +++ b/package.json @@ -9,14 +9,19 @@ "lint": "next lint" }, "dependencies": { + "@tanstack/react-query": "^5.74.7", + "ky": "^1.8.1", + "next": "15.3.1", "react": "^19.0.0", "react-dom": "^19.0.0", - "next": "15.3.1" + "use-form-light": "^1.2.0", + "zustand": "^5.0.3" }, "devDependencies": { - "typescript": "^5", + "@biomejs/biome": "1.9.4", "@types/node": "^20", "@types/react": "^19", - "@types/react-dom": "^19" + "@types/react-dom": "^19", + "typescript": "^5" } } diff --git a/src/app/components/sample/Counter.tsx b/src/app/components/sample/Counter.tsx new file mode 100644 index 0000000..68a8827 --- /dev/null +++ b/src/app/components/sample/Counter.tsx @@ -0,0 +1,33 @@ +"use client"; + +import { useCounterStore } from "@/stores"; + +export default function Counter() { + const { count, increment, decrement, reset } = useCounterStore(); + + return ( +
+

Count: {count}

+
+ + + +
+
+ ); +} diff --git a/src/app/components/sample/TodoList.tsx b/src/app/components/sample/TodoList.tsx new file mode 100644 index 0000000..9660413 --- /dev/null +++ b/src/app/components/sample/TodoList.tsx @@ -0,0 +1,52 @@ +"use client"; + +import { useTodos } from "@/hooks/useTodos"; + +export default function TodoList() { + const { todos, isLoading, error, toggleTodo, deleteTodo } = useTodos(); + + if (isLoading) return
Loading...
; + if (error) + return ( +
+ Error:{" "} + {error instanceof Error ? error.message : "Failed to fetch todos"} +
+ ); + + return ( +
+

Todo List

+ +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 42fc323..af589a7 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import { Geist, Geist_Mono } from "next/font/google"; import "./globals.css"; +import Providers from "@/providers"; const geistSans = Geist({ variable: "--font-geist-sans", @@ -25,7 +26,7 @@ export default function RootLayout({ return ( - {children} + {children} ); diff --git a/src/app/page.tsx b/src/app/page.tsx index 84af2cb..7913fd6 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,5 +1,6 @@ import Image from "next/image"; import styles from "./page.module.css"; +import Counter from "@/app/components/sample/Counter"; export default function Home() { return ( @@ -45,6 +46,7 @@ export default function Home() { Read our docs +