From 15f806980ba9f453665bcbac4c5bcf705acc2d65 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Fri, 8 Mar 2024 23:32:14 +0100 Subject: [PATCH 01/13] N-FIN-5: add navigation to layout --- package-lock.json | 674 ++++++++++++++++++++++++++ package.json | 2 + public/logo_white.png | Bin 0 -> 6965 bytes src/app/account/page.tsx | 14 +- src/app/categories/page.tsx | 7 + src/app/entities/page.tsx | 7 + src/app/layout.tsx | 2 + src/app/page.tsx | 11 +- src/app/payments/page.tsx | 7 + src/components/navigation.tsx | 65 +++ src/components/ui/navigation-menu.tsx | 128 +++++ src/components/ui/select.tsx | 160 ++++++ 12 files changed, 1056 insertions(+), 21 deletions(-) create mode 100644 public/logo_white.png create mode 100644 src/app/categories/page.tsx create mode 100644 src/app/entities/page.tsx create mode 100644 src/app/payments/page.tsx create mode 100644 src/components/navigation.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/select.tsx diff --git a/package-lock.json b/package-lock.json index fd121ba..24daa4b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,8 @@ "@lucia-auth/adapter-prisma": "^4.0.0", "@prisma/client": "^5.10.2", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", @@ -151,6 +153,40 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@floating-ui/core": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.6.0.tgz", + "integrity": "sha512-PcF++MykgmTj3CIyOQbKA/hDzOAiqI3mhuoN44WRCopIs1sgoDoU4oty4Jtqaj/y3oDU6fnVSm4QG0a3t5i0+g==", + "dependencies": { + "@floating-ui/utils": "^0.2.1" + } + }, + "node_modules/@floating-ui/dom": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.6.3.tgz", + "integrity": "sha512-RnDthu3mzPlQ31Ss/BTwQ1zjzIhr3lk1gZB1OC56h/1vEtaXkESrOqL5fQVMfXpwGtRwX+YsZBdyHtJMQnkArw==", + "dependencies": { + "@floating-ui/core": "^1.0.0", + "@floating-ui/utils": "^0.2.0" + } + }, + "node_modules/@floating-ui/react-dom": { + "version": "2.0.8", + "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.0.8.tgz", + "integrity": "sha512-HOdqOt3R3OGeTKidaLvJKcgg75S6tibQ3Tif4eyd91QnIJWr0NLvoXFpJA/j8HqkFSL68GDca9AuyWEHlhyClw==", + "dependencies": { + "@floating-ui/dom": "^1.6.1" + }, + "peerDependencies": { + "react": ">=16.8.0", + "react-dom": ">=16.8.0" + } + }, + "node_modules/@floating-ui/utils": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.1.tgz", + "integrity": "sha512-9TANp6GPoMtYzQdt54kfAyMmz1+osLlXdg2ENroU7zzrtflTLrrC/lgrIfaSe+Wu0b89GKccT7vxXA0MoAIO+Q==" + }, "node_modules/@hookform/resolvers": { "version": "3.3.4", "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz", @@ -619,6 +655,71 @@ "@prisma/debug": "5.10.2" } }, + "node_modules/@radix-ui/number": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.0.1.tgz", + "integrity": "sha512-T5gIdVO2mmPW3NNhjNgEP3cqMXjXL9UbO0BzWcXfvdBs+BohbQxvd/K5hSVKmn9/lbTdsQVKbUcP5WLCwvUbBg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/primitive": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.0.1.tgz", + "integrity": "sha512-yQ8oGX2GVsEYMWGxcovu1uGWPCxV5BFfeeYxqPmuAzUyLT9qmaMXSAhXpb0WrspIeqYzdJpkh2vHModJPgRIaw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, + "node_modules/@radix-ui/react-arrow": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", + "integrity": "sha512-wSP+pHsB/jQRaL6voubsQ/ZlrGBHHrOjmBnr19hxYgtS0WvAFwZhK2WP/YY5yF9uKECCEEDGxuLxq1NBK51wFA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-collection": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.0.3.tgz", + "integrity": "sha512-3SzW+0PW7yBBoQlT8wNcGtaxaD0XSu0uLUFgrtHY08Acx05TaHaOmVLR73c0j/cqpDy53KBMO7s0dx2wmOIDIA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-compose-refs": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.0.1.tgz", @@ -636,6 +737,127 @@ } } }, + "node_modules/@radix-ui/react-context": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.0.1.tgz", + "integrity": "sha512-ebbrdFoYTcuZ0v4wG5tedGnp9tzcV8awzsxYph7gXUyvnNLuTIcCk1q17JEbnVhXAKG9oX3KtchwiMIAYp9NLg==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-direction": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", + "integrity": "sha512-RXcvnXgyvYvBEOhCBuddKecVkoMiI10Jcm5cTI7abJRAHYfFxeu+FBQs/DvdxSYucxR5mna0dNsL6QFlds5TMA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-dismissable-layer": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.0.5.tgz", + "integrity": "sha512-aJeDjQhywg9LBu2t/At58hCvr7pEm0o2Ke1x33B+MhjNmmZ17sy4KImo0KPLgsnc/zN7GPdce8Cnn0SWvwZO7g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-escape-keydown": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-guards": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", + "integrity": "sha512-Rect2dWbQ8waGzhMavsIbmSVCgYxkXLxxR3ZvCX79JOglzdEy4JXMb98lq4hPxUbLr77nP0UOGf4rcMU+s1pUA==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-focus-scope": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.0.4.tgz", + "integrity": "sha512-sL04Mgvf+FmyvZeYfNu1EPAaaxD+aw7cYeIB9L9Fvq8+urhltTRaEo5ysKOpHuKPclsZcSUMKlN05x4u+CINpA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-id": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.0.1.tgz", + "integrity": "sha512-tI7sT/kqYp8p96yGWY1OAnLHrqDgzHefRBKQ2YAkBS5ja7QLcZ9Z/uY7bEjPUatf8RomoXM8/1sMj1IJaE5UzQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-label": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.0.2.tgz", @@ -659,6 +881,121 @@ } } }, + "node_modules/@radix-ui/react-navigation-menu": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.4.tgz", + "integrity": "sha512-Cc+seCS3PmWmjI51ufGG7zp1cAAIRqHVw7C9LOA2TZ+R4hG6rDvHcTqIsEEFLmZO3zNVH72jOOE7kKNy8W+RtA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-popper": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.1.3.tgz", + "integrity": "sha512-cKpopj/5RHZWjrbF2846jBNacjQVwkP068DfmgrNJXpvVWrOvlAmE9xSiy5OqeE+Gi8D9fP+oDhUnPqNMY8/5w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@floating-ui/react-dom": "^2.0.0", + "@radix-ui/react-arrow": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-rect": "1.0.1", + "@radix-ui/react-use-size": "1.0.1", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-portal": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.0.4.tgz", + "integrity": "sha512-Qki+C/EuGUVCQTOTD5vzJzJuMUlewbzuKyUy+/iHM2uwGiru9gZeBJtHAPKAEkB5KWGi9mP/CHKcY0wt1aW45Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-presence": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.0.1.tgz", + "integrity": "sha512-UXLW4UAbIY5ZjcvzjfRFo5gxva8QirC9hF7wRE4U5gz+TP0DbRk+//qyuAQ1McDxBt1xNMBTaciFGvEmJvAZCg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-primitive": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-1.0.3.tgz", @@ -682,6 +1019,49 @@ } } }, + "node_modules/@radix-ui/react-select": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", + "integrity": "sha512-RH5b7af4oHtkcHS7pG6Sgv5rk5Wxa7XI8W5gvB1N/yiuDGZxko1ynvOiVhFM7Cis2A8zxF9bTOUVbRDzPepe6w==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/number": "1.0.1", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1", + "@radix-ui/react-use-layout-effect": "1.0.1", + "@radix-ui/react-use-previous": "1.0.1", + "@radix-ui/react-visually-hidden": "1.0.3", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-slot": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.0.2.tgz", @@ -700,6 +1080,160 @@ } } }, + "node_modules/@radix-ui/react-use-callback-ref": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.0.1.tgz", + "integrity": "sha512-D94LjX4Sp0xJFVaoQOd3OO9k7tpBYNOXdVhkltUbGv2Qb9OXdrg/CpsjlZv7ia14Sylv398LswWBVVu5nqKzAQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-controllable-state": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.0.1.tgz", + "integrity": "sha512-Svl5GY5FQeN758fWKrjM6Qb7asvXeiZltlT4U2gVfl8Gx5UAv2sMR0LWo8yhsIZh2oQ0eFdZ59aoOOMV7b47VA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-escape-keydown": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.0.3.tgz", + "integrity": "sha512-vyL82j40hcFicA+M4Ex7hVkB9vHgSse1ZWomAqV2Je3RleKGO5iM8KMOEtfoSB0PnIelMd2lATjTGMYqN5ylTg==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-callback-ref": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-layout-effect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.0.1.tgz", + "integrity": "sha512-v/5RegiJWYdoCvMnITBkNNx6bCj20fiaJnWtRkU18yITptraXjffz5Qbn05uOiQnOvi+dbkznkoaMltz1GnszQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-previous": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.0.1.tgz", + "integrity": "sha512-cV5La9DPwiQ7S0gf/0qiD6YgNqM5Fk97Kdrlc5yBcrF3jyEZQwm7vYFqMo4IfeHgJXsRaMvLABFtd0OVEmZhDw==", + "dependencies": { + "@babel/runtime": "^7.13.10" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.0.1.tgz", + "integrity": "sha512-Cq5DLuSiuYVKNU8orzJMbl15TXilTnJKUCltMVQg53BQOF1/C5toAaGrowkgksdBQ9H+SRL23g0HDmg9tvmxXw==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/rect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-use-size": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.0.1.tgz", + "integrity": "sha512-ibay+VqrgcaI6veAojjofPATwledXiSmX+C0KrBk/xgpX9rBzPV3OsfwlhQdUOFbh+LKQorLYT+xTXW9V8yd0g==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-use-layout-effect": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "react": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@radix-ui/react-visually-hidden": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.0.3.tgz", + "integrity": "sha512-D4w41yN5YRKtu464TLnByKzMDG/JlMPHtfZgQAu9v6mNakUqGUI9vUrfQKz8NK41VMm/xbZbh76NUTVtIYqOMA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/react-primitive": "1.0.3" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, + "node_modules/@radix-ui/rect": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.0.1.tgz", + "integrity": "sha512-fyrgCaedtvMg9NK3en0pnOYJdtfwxUcNolezkNPUsoX57X8oQk+NkqcvzHXD2uKNij6GXmWU9NDru2IWjrO4BQ==", + "dependencies": { + "@babel/runtime": "^7.13.10" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.7.2", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.7.2.tgz", @@ -1014,6 +1548,17 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/aria-hidden": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.3.tgz", + "integrity": "sha512-xcLxITLe2HYa1cnYnwCjkOO1PqUHQpozB8x9AR0OgWN2woOBi5kSDVxKfd0b7sb1hw5qFeJhXm9H1nu3xSfLeQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/aria-query": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.0.tgz", @@ -1644,6 +2189,11 @@ "node": ">=6" } }, + "node_modules/detect-node-es": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", + "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==" + }, "node_modules/didyoumean": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", @@ -2518,6 +3068,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-nonce": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", + "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", + "engines": { + "node": ">=6" + } + }, "node_modules/get-symbol-description": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.2.tgz", @@ -2818,6 +3376,14 @@ "node": ">= 0.4" } }, + "node_modules/invariant": { + "version": "2.2.4", + "resolved": "https://registry.npmjs.org/invariant/-/invariant-2.2.4.tgz", + "integrity": "sha512-phJfQVBuaJM5raOpJjSfkiD6BpbCE4Ns//LaXl6wGYtUBY83nWS6Rf9tXm2e8VaK60JEjYldbPif/A2B1C2gNA==", + "dependencies": { + "loose-envify": "^1.0.0" + } + }, "node_modules/is-array-buffer": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.4.tgz", @@ -4210,6 +4776,73 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-remove-scroll": { + "version": "2.5.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", + "integrity": "sha512-ImKhrzJJsyXJfBZ4bzu8Bwpka14c/fQt0k+cyFp/PBhTfyDnU5hjOtM4AG/0AMyy8oKzOTR0lDgJIM7pYXI0kw==", + "dependencies": { + "react-remove-scroll-bar": "^2.3.3", + "react-style-singleton": "^2.2.1", + "tslib": "^2.1.0", + "use-callback-ref": "^1.3.0", + "use-sidecar": "^1.1.2" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-remove-scroll-bar": { + "version": "2.3.5", + "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.5.tgz", + "integrity": "sha512-3cqjOqg6s0XbOjWvmasmqHch+RLxIEk2r/70rzGXuz3iIGQsQheEQyqYCBb5EECoD01Vo2SIbDqW4paLeLTASw==", + "dependencies": { + "react-style-singleton": "^2.2.1", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/react-style-singleton": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", + "integrity": "sha512-ZWj0fHEMyWkHzKYUr2Bs/4zU6XLmq9HsgBURm7g5pAVfyn49DgUiNgY2d4lXRlYSiCif9YBGpQleewkcqddc7g==", + "dependencies": { + "get-nonce": "^1.0.0", + "invariant": "^2.2.4", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/read-cache": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", @@ -5146,6 +5779,47 @@ "punycode": "^2.1.0" } }, + "node_modules/use-callback-ref": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.1.tgz", + "integrity": "sha512-Lg4Vx1XZQauB42Hw3kK7JM6yjVjgFmFC5/Ab797s79aARomD2nEErc4mCgM8EZrARLmmbWpi5DGCadmK50DcAQ==", + "dependencies": { + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.8.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/use-sidecar": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.2.tgz", + "integrity": "sha512-epTbsLuzZ7lPClpz2TyryBfztm7m+28DlEv2ZCQ3MDr5ssiwyOwGH/e5F9CkfWjJ1t4clvI58yF822/GUkjjhw==", + "dependencies": { + "detect-node-es": "^1.1.0", + "tslib": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "@types/react": "^16.9.0 || ^17.0.0 || ^18.0.0", + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index a341310..78d6594 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,8 @@ "@lucia-auth/adapter-prisma": "^4.0.0", "@prisma/client": "^5.10.2", "@radix-ui/react-label": "^2.0.2", + "@radix-ui/react-navigation-menu": "^1.1.4", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", diff --git a/public/logo_white.png b/public/logo_white.png new file mode 100644 index 0000000000000000000000000000000000000000..cb112d2d9182a23c2bfb0e4342fc3e9da0d73b23 GIT binary patch literal 6965 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 y9Dw - - +
+ Hey, {user?.username}! This is your account overview. diff --git a/src/app/categories/page.tsx b/src/app/categories/page.tsx new file mode 100644 index 0000000..9313db2 --- /dev/null +++ b/src/app/categories/page.tsx @@ -0,0 +1,7 @@ +export default async function CategoriesPage() { + return ( +
+ Categories +
+ ); +} diff --git a/src/app/entities/page.tsx b/src/app/entities/page.tsx new file mode 100644 index 0000000..532b2d3 --- /dev/null +++ b/src/app/entities/page.tsx @@ -0,0 +1,7 @@ +export default async function EntitiesPage() { + return ( +
+ Entities +
+ ); +} diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6dac1ca..cac7e3c 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -4,6 +4,7 @@ import './globals.css'; import { cn } from '@/lib/utils'; import { Toaster } from '@/components/ui/sonner'; import React from 'react'; +import Navigation from '@/components/navigation'; const inter = Inter({subsets: ['latin']}); @@ -20,6 +21,7 @@ export default function RootLayout({ return ( +
{children} diff --git a/src/app/page.tsx b/src/app/page.tsx index 10f0a31..c527955 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,17 +1,8 @@ -import { Button } from '@/components/ui/button'; -import { User } from 'lucide-react'; import React from 'react'; -import Link from 'next/link'; -import { URL_ACCOUNT } from '@/lib/constants'; export default async function Home() { return ( -
- +
Next Finances
); diff --git a/src/app/payments/page.tsx b/src/app/payments/page.tsx new file mode 100644 index 0000000..6b3e460 --- /dev/null +++ b/src/app/payments/page.tsx @@ -0,0 +1,7 @@ +export default async function PaymentsPage() { + return ( +
+ Payments +
+ ); +} diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx new file mode 100644 index 0000000..5174a67 --- /dev/null +++ b/src/components/navigation.tsx @@ -0,0 +1,65 @@ +'use client'; + +import { + NavigationMenu, + NavigationMenuItem, + NavigationMenuLink, + NavigationMenuList, + navigationMenuTriggerStyle, +} from '@/components/ui/navigation-menu'; +import React from 'react'; +import Link from 'next/link'; +import { User } from 'lucide-react'; + +export default function Navigation() { + + return ( +
+ + +
+ + Finances + + + + + Dashboard + + + + + + + Payments + + + + + + + Entities + + + + + + + Categories + + + +
+ + + + + + + + +
+
+
+ ); +} diff --git a/src/components/ui/navigation-menu.tsx b/src/components/ui/navigation-menu.tsx new file mode 100644 index 0000000..800788d --- /dev/null +++ b/src/components/ui/navigation-menu.tsx @@ -0,0 +1,128 @@ +import * as React from 'react'; +import * as NavigationMenuPrimitive from '@radix-ui/react-navigation-menu'; +import { cva } from 'class-variance-authority'; +import { ChevronDown } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const NavigationMenu = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + + {children} + + +)); +NavigationMenu.displayName = NavigationMenuPrimitive.Root.displayName; + +const NavigationMenuList = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +NavigationMenuList.displayName = NavigationMenuPrimitive.List.displayName; + +const NavigationMenuItem = NavigationMenuPrimitive.Item; + +const navigationMenuTriggerStyle = cva( + 'group inline-flex h-10 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium transition-colors hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus:outline-none disabled:pointer-events-none disabled:opacity-50 data-[active]:bg-accent/50 data-[state=open]:bg-accent/50', +); + +const NavigationMenuTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + + {children}{' '} + +)); +NavigationMenuTrigger.displayName = NavigationMenuPrimitive.Trigger.displayName; + +const NavigationMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +NavigationMenuContent.displayName = NavigationMenuPrimitive.Content.displayName; + +const NavigationMenuLink = NavigationMenuPrimitive.Link; + +const NavigationMenuViewport = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( +
+ +
+)); +NavigationMenuViewport.displayName = + NavigationMenuPrimitive.Viewport.displayName; + +const NavigationMenuIndicator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +
+ +)); +NavigationMenuIndicator.displayName = + NavigationMenuPrimitive.Indicator.displayName; + +export { + navigationMenuTriggerStyle, + NavigationMenu, + NavigationMenuList, + NavigationMenuItem, + NavigationMenuContent, + NavigationMenuTrigger, + NavigationMenuLink, + NavigationMenuIndicator, + NavigationMenuViewport, +}; diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx new file mode 100644 index 0000000..ac0bfae --- /dev/null +++ b/src/components/ui/select.tsx @@ -0,0 +1,160 @@ +'use client'; + +import * as React from 'react'; +import * as SelectPrimitive from '@radix-ui/react-select'; +import { Check, ChevronDown, ChevronUp } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const Select = SelectPrimitive.Root; + +const SelectGroup = SelectPrimitive.Group; + +const SelectValue = SelectPrimitive.Value; + +const SelectTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + span]:line-clamp-1', + className, + )} + {...props} + > + {children} + + + + +)); +SelectTrigger.displayName = SelectPrimitive.Trigger.displayName; + +const SelectScrollUpButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + + + +)); +SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName; + +const SelectScrollDownButton = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + + + +)); +SelectScrollDownButton.displayName = + SelectPrimitive.ScrollDownButton.displayName; + +const SelectContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, position = 'popper', ...props}, ref) => ( + + + + + {children} + + + + +)); +SelectContent.displayName = SelectPrimitive.Content.displayName; + +const SelectLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +SelectLabel.displayName = SelectPrimitive.Label.displayName; + +const SelectItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + + + + + + + + {children} + +)); +SelectItem.displayName = SelectPrimitive.Item.displayName; + +const SelectSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +SelectSeparator.displayName = SelectPrimitive.Separator.displayName; + +export { + Select, + SelectGroup, + SelectValue, + SelectTrigger, + SelectContent, + SelectLabel, + SelectItem, + SelectSeparator, + SelectScrollUpButton, + SelectScrollDownButton, +}; From d86ae47c7cf4ab2067b04bfb8d2846293e4989d0 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sat, 9 Mar 2024 17:51:27 +0100 Subject: [PATCH 02/13] N-FIN-5: move prisma client to own file --- src/auth.ts | 4 +--- src/lib/actions/signIn.ts | 5 +++-- src/lib/actions/signOut.ts | 2 +- src/lib/actions/signUp.ts | 5 +++-- src/prisma.ts | 3 +++ 5 files changed, 11 insertions(+), 8 deletions(-) create mode 100644 src/prisma.ts diff --git a/src/auth.ts b/src/auth.ts index d364789..26cdb6c 100644 --- a/src/auth.ts +++ b/src/auth.ts @@ -1,9 +1,7 @@ import { Lucia } from 'lucia'; import { PrismaAdapter } from '@lucia-auth/adapter-prisma'; -import { PrismaClient } from '@prisma/client'; import { cookies } from 'next/headers'; - -export const prismaClient = new PrismaClient(); +import { prismaClient } from '@/prisma'; const adapter = new PrismaAdapter(prismaClient.session, prismaClient.user); diff --git a/src/lib/actions/signIn.ts b/src/lib/actions/signIn.ts index 06779a0..4027f75 100644 --- a/src/lib/actions/signIn.ts +++ b/src/lib/actions/signIn.ts @@ -1,10 +1,11 @@ import { z } from 'zod'; import { Argon2id } from 'oslo/password'; -import { lucia, prismaClient } from '@/auth'; +import { lucia } from '@/auth'; import { cookies } from 'next/headers'; import { signInFormSchema } from '@/lib/form-schemas/signInFormSchema'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; import { URL_HOME } from '@/lib/constants'; +import { prismaClient } from '@/prisma'; export default async function signIn({username, password}: z.infer): Promise { 'use server'; diff --git a/src/lib/actions/signOut.ts b/src/lib/actions/signOut.ts index 30f44bb..4525a8e 100644 --- a/src/lib/actions/signOut.ts +++ b/src/lib/actions/signOut.ts @@ -1,6 +1,6 @@ import { getSession, lucia } from '@/auth'; import { cookies } from 'next/headers'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; import { URL_SIGN_IN } from '@/lib/constants'; export default async function signOut(): Promise { diff --git a/src/lib/actions/signUp.ts b/src/lib/actions/signUp.ts index e3d0c4f..4f731b1 100644 --- a/src/lib/actions/signUp.ts +++ b/src/lib/actions/signUp.ts @@ -1,11 +1,12 @@ import { z } from 'zod'; import { Argon2id } from 'oslo/password'; import { generateId } from 'lucia'; -import { lucia, prismaClient } from '@/auth'; +import { lucia } from '@/auth'; import { cookies } from 'next/headers'; import { signUpFormSchema } from '@/lib/form-schemas/signUpFormSchema'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; import { URL_HOME } from '@/lib/constants'; +import { prismaClient } from '@/prisma'; export default async function signUp({username, password}: z.infer): Promise { 'use server'; diff --git a/src/prisma.ts b/src/prisma.ts new file mode 100644 index 0000000..5e41d6f --- /dev/null +++ b/src/prisma.ts @@ -0,0 +1,3 @@ +import { PrismaClient } from '@prisma/client'; + +export const prismaClient = new PrismaClient(); From 2840931a3d53a6364c37e707a3c274c05f3aa4b4 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sat, 9 Mar 2024 17:52:05 +0100 Subject: [PATCH 03/13] N-FIN-5: move types directory --- src/components/form/signInForm.tsx | 2 +- src/components/form/signOutForm.tsx | 2 +- src/components/form/signUpForm.tsx | 2 +- src/components/ui/sonner.tsx | 2 +- src/lib/{actions => }/types/ActionResponse.ts | 0 5 files changed, 4 insertions(+), 4 deletions(-) rename src/lib/{actions => }/types/ActionResponse.ts (100%) diff --git a/src/components/form/signInForm.tsx b/src/components/form/signInForm.tsx index 49c2ade..7d95e82 100644 --- a/src/components/form/signInForm.tsx +++ b/src/components/form/signInForm.tsx @@ -8,7 +8,7 @@ import { Input } from '@/components/ui/input'; import React from 'react'; import { Button } from '@/components/ui/button'; import { signInFormSchema } from '@/lib/form-schemas/signInFormSchema'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; import { sonnerContent } from '@/components/ui/sonner'; diff --git a/src/components/form/signOutForm.tsx b/src/components/form/signOutForm.tsx index f9758ac..b47649f 100644 --- a/src/components/form/signOutForm.tsx +++ b/src/components/form/signOutForm.tsx @@ -1,6 +1,6 @@ 'use client'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; import { Button } from '@/components/ui/button'; import React from 'react'; import { useRouter } from 'next/navigation'; diff --git a/src/components/form/signUpForm.tsx b/src/components/form/signUpForm.tsx index c9d3231..ed53c50 100644 --- a/src/components/form/signUpForm.tsx +++ b/src/components/form/signUpForm.tsx @@ -8,7 +8,7 @@ import { Input } from '@/components/ui/input'; import React from 'react'; import { Button } from '@/components/ui/button'; import { signUpFormSchema } from '@/lib/form-schemas/signUpFormSchema'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; import { sonnerContent } from '@/components/ui/sonner'; diff --git a/src/components/ui/sonner.tsx b/src/components/ui/sonner.tsx index b14e457..cd1cc44 100644 --- a/src/components/ui/sonner.tsx +++ b/src/components/ui/sonner.tsx @@ -4,7 +4,7 @@ import { useTheme } from 'next-themes'; import { Toaster as Sonner } from 'sonner'; import { AlertCircle, CheckCircle, HelpCircle, XCircle } from 'lucide-react'; import React, { JSX } from 'react'; -import { ActionResponse } from '@/lib/actions/types/ActionResponse'; +import { ActionResponse } from '@/lib/types/ActionResponse'; type ToasterProps = React.ComponentProps diff --git a/src/lib/actions/types/ActionResponse.ts b/src/lib/types/ActionResponse.ts similarity index 100% rename from src/lib/actions/types/ActionResponse.ts rename to src/lib/types/ActionResponse.ts From 791c2d852a2b7ff5905de5f46958a59a68f96bb7 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sat, 9 Mar 2024 17:56:11 +0100 Subject: [PATCH 04/13] N-FIN-5: add required shadcn/ui components --- package-lock.json | 191 ++++++++++++++++++++++++++ package.json | 4 + src/components/ui/data-table.tsx | 120 +++++++++++++++++ src/components/ui/dialog.tsx | 123 +++++++++++++++++ src/components/ui/dropdown-menu.tsx | 200 ++++++++++++++++++++++++++++ src/components/ui/table.tsx | 117 ++++++++++++++++ 6 files changed, 755 insertions(+) create mode 100644 src/components/ui/data-table.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/table.tsx diff --git a/package-lock.json b/package-lock.json index 24daa4b..58a3c2a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,10 +11,13 @@ "@hookform/resolvers": "^3.3.4", "@lucia-auth/adapter-prisma": "^4.0.0", "@prisma/client": "^5.10.2", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", + "@tanstack/react-table": "^8.13.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucia": "^3.0.1", @@ -26,6 +29,7 @@ "react-dom": "^18", "react-hook-form": "^7.51.0", "sonner": "^1.4.3", + "swr": "^2.2.5", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" @@ -754,6 +758,42 @@ } } }, + "node_modules/@radix-ui/react-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.0.5.tgz", + "integrity": "sha512-GjWJX/AUpB703eEBanuBnIWdIXg6NvJFCXcNlSZk4xdszCdhrJgBoUd1cGk67vFO+WdA2pfI/plOpqz/5GUP6Q==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-controllable-state": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-direction": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.0.1.tgz", @@ -798,6 +838,35 @@ } } }, + "node_modules/@radix-ui/react-dropdown-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.0.6.tgz", + "integrity": "sha512-i6TuFOoWmLWq+M/eCLGd/bQ2HfAX1RJgvrBQ6AQLmzfvsLdefxbWu8G9zczcPFfcSPehz9GcpF6K9QYreFV8hA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-menu": "2.0.6", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.0.1.tgz", @@ -881,6 +950,46 @@ } } }, + "node_modules/@radix-ui/react-menu": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.0.6.tgz", + "integrity": "sha512-BVkFLS+bUC8HcImkRKPSiVumA1VPOOEC5WBMiT+QAVsPzW1FJzI9KnqgGxVDPBcql5xXrHkD3JOVoXWEXD8SYA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-dismissable-layer": "1.0.5", + "@radix-ui/react-focus-guards": "1.0.1", + "@radix-ui/react-focus-scope": "1.0.4", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-popper": "1.1.3", + "@radix-ui/react-portal": "1.0.4", + "@radix-ui/react-presence": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-roving-focus": "1.0.4", + "@radix-ui/react-slot": "1.0.2", + "@radix-ui/react-use-callback-ref": "1.0.1", + "aria-hidden": "^1.1.1", + "react-remove-scroll": "2.5.5" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-navigation-menu": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/@radix-ui/react-navigation-menu/-/react-navigation-menu-1.1.4.tgz", @@ -1019,6 +1128,37 @@ } } }, + "node_modules/@radix-ui/react-roving-focus": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.0.4.tgz", + "integrity": "sha512-2mUg5Mgcu001VkGy+FfzZyzbmuUWzgWkj3rvv4yu+mLw03+mTzbxZHvfcGyFp2b8EkQeMkpRQ5FiA2Vr2O6TeQ==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-collection": "1.0.3", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-direction": "1.0.1", + "@radix-ui/react-id": "1.0.1", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-use-callback-ref": "1.0.1", + "@radix-ui/react-use-controllable-state": "1.0.1" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-select": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.0.0.tgz", @@ -1248,6 +1388,37 @@ "tslib": "^2.4.0" } }, + "node_modules/@tanstack/react-table": { + "version": "8.13.2", + "resolved": "https://registry.npmjs.org/@tanstack/react-table/-/react-table-8.13.2.tgz", + "integrity": "sha512-b6mR3mYkjRtJ443QZh9sc7CvGTce81J35F/XMr0OoWbx0KIM7TTTdyNP2XKObvkLpYnLpCrYDwI3CZnLezWvpg==", + "dependencies": { + "@tanstack/table-core": "8.13.2" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + }, + "peerDependencies": { + "react": ">=16", + "react-dom": ">=16" + } + }, + "node_modules/@tanstack/table-core": { + "version": "8.13.2", + "resolved": "https://registry.npmjs.org/@tanstack/table-core/-/table-core-8.13.2.tgz", + "integrity": "sha512-/2saD1lWBUV6/uNAwrsg2tw58uvMJ07bO2F1IWMxjFRkJiXKQRuc3Oq2aufeobD3873+4oIM/DRySIw7+QsPPw==", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/tannerlinsley" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz", @@ -5425,6 +5596,18 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/swr": { + "version": "2.2.5", + "resolved": "https://registry.npmjs.org/swr/-/swr-2.2.5.tgz", + "integrity": "sha512-QtxqyclFeAsxEUeZIYmsaQ0UjimSq1RZ9Un7I68/0ClKK/U3LoyQunwkQfJZr2fc22DfIXLNDc2wFyTEikCUpg==", + "dependencies": { + "client-only": "^0.0.1", + "use-sync-external-store": "^1.2.0" + }, + "peerDependencies": { + "react": "^16.11.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/tailwind-merge": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-2.2.1.tgz", @@ -5820,6 +6003,14 @@ } } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/package.json b/package.json index 78d6594..3e1719e 100644 --- a/package.json +++ b/package.json @@ -12,10 +12,13 @@ "@hookform/resolvers": "^3.3.4", "@lucia-auth/adapter-prisma": "^4.0.0", "@prisma/client": "^5.10.2", + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-navigation-menu": "^1.1.4", "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", + "@tanstack/react-table": "^8.13.2", "class-variance-authority": "^0.7.0", "clsx": "^2.1.0", "lucia": "^3.0.1", @@ -27,6 +30,7 @@ "react-dom": "^18", "react-hook-form": "^7.51.0", "sonner": "^1.4.3", + "swr": "^2.2.5", "tailwind-merge": "^2.2.1", "tailwindcss-animate": "^1.0.7", "zod": "^3.22.4" diff --git a/src/components/ui/data-table.tsx b/src/components/ui/data-table.tsx new file mode 100644 index 0000000..33815e4 --- /dev/null +++ b/src/components/ui/data-table.tsx @@ -0,0 +1,120 @@ +'use client'; + +import { ColumnDef, flexRender, getCoreRowModel, getPaginationRowModel, useReactTable } from '@tanstack/react-table'; + +import { Table, TableBody, TableCell, TableHead, TableHeader, TableRow } from '@/components/ui/table'; +import { Button } from '@/components/ui/button'; +import React from 'react'; +import { ChevronLeft, ChevronRight, ChevronsLeft, ChevronsRight } from 'lucide-react'; + +interface DataTableProps { + columns: ColumnDef[]; + data: TData[]; + pagination?: boolean; + className?: string; +} + +export function DataTable({ + columns, + data, + pagination, + className, +}: DataTableProps) { + const table = useReactTable({ + data, + columns, + getCoreRowModel: getCoreRowModel(), + getPaginationRowModel: pagination ? getPaginationRowModel() : undefined, + }); + + return ( +
+
+ + + {table.getHeaderGroups().map((headerGroup) => ( + + {headerGroup.headers.map((header) => { + return ( + + {header.isPlaceholder + ? null + : flexRender( + header.column.columnDef.header, + header.getContext(), + )} + + ); + })} + + ))} + + + {table.getRowModel().rows?.length ? ( + table.getRowModel().rows.map((row) => ( + + {row.getVisibleCells().map((cell) => ( + + {flexRender(cell.column.columnDef.cell, cell.getContext())} + + ))} + + )) + ) : ( + + + No results. + + + )} + +
+
+ { + pagination && ( +
+ + + + +
+ ) + } +
+ ); +} diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx new file mode 100644 index 0000000..b96ae4a --- /dev/null +++ b/src/components/ui/dialog.tsx @@ -0,0 +1,123 @@ +'use client'; + +import * as React from 'react'; +import * as DialogPrimitive from '@radix-ui/react-dialog'; +import { X } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const Dialog = DialogPrimitive.Root; + +const DialogTrigger = DialogPrimitive.Trigger; + +const DialogPortal = DialogPrimitive.Portal; + +const DialogClose = DialogPrimitive.Close; + +const DialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +DialogOverlay.displayName = DialogPrimitive.Overlay.displayName; + +const DialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + + + + {children} + + + Close + + + +)); +DialogContent.displayName = DialogPrimitive.Content.displayName; + +const DialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogHeader.displayName = 'DialogHeader'; + +const DialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +DialogFooter.displayName = 'DialogFooter'; + +const DialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +DialogTitle.displayName = DialogPrimitive.Title.displayName; + +const DialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +DialogDescription.displayName = DialogPrimitive.Description.displayName; + +export { + Dialog, + DialogPortal, + DialogOverlay, + DialogClose, + DialogTrigger, + DialogContent, + DialogHeader, + DialogFooter, + DialogTitle, + DialogDescription, +}; diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx new file mode 100644 index 0000000..e6ac546 --- /dev/null +++ b/src/components/ui/dropdown-menu.tsx @@ -0,0 +1,200 @@ +'use client'; + +import * as React from 'react'; +import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'; +import { Check, ChevronRight, Circle } from 'lucide-react'; + +import { cn } from '@/lib/utils'; + +const DropdownMenu = DropdownMenuPrimitive.Root; + +const DropdownMenuTrigger = DropdownMenuPrimitive.Trigger; + +const DropdownMenuGroup = DropdownMenuPrimitive.Group; + +const DropdownMenuPortal = DropdownMenuPrimitive.Portal; + +const DropdownMenuSub = DropdownMenuPrimitive.Sub; + +const DropdownMenuRadioGroup = DropdownMenuPrimitive.RadioGroup; + +const DropdownMenuSubTrigger = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean +} +>(({className, inset, children, ...props}, ref) => ( + + {children} + + +)); +DropdownMenuSubTrigger.displayName = + DropdownMenuPrimitive.SubTrigger.displayName; + +const DropdownMenuSubContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +DropdownMenuSubContent.displayName = + DropdownMenuPrimitive.SubContent.displayName; + +const DropdownMenuContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, sideOffset = 4, ...props}, ref) => ( + + + +)); +DropdownMenuContent.displayName = DropdownMenuPrimitive.Content.displayName; + +const DropdownMenuItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean +} +>(({className, inset, ...props}, ref) => ( + +)); +DropdownMenuItem.displayName = DropdownMenuPrimitive.Item.displayName; + +const DropdownMenuCheckboxItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, checked, ...props}, ref) => ( + + + + + + + {children} + +)); +DropdownMenuCheckboxItem.displayName = + DropdownMenuPrimitive.CheckboxItem.displayName; + +const DropdownMenuRadioItem = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, children, ...props}, ref) => ( + + + + + + + {children} + +)); +DropdownMenuRadioItem.displayName = DropdownMenuPrimitive.RadioItem.displayName; + +const DropdownMenuLabel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef & { + inset?: boolean +} +>(({className, inset, ...props}, ref) => ( + +)); +DropdownMenuLabel.displayName = DropdownMenuPrimitive.Label.displayName; + +const DropdownMenuSeparator = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +DropdownMenuSeparator.displayName = DropdownMenuPrimitive.Separator.displayName; + +const DropdownMenuShortcut = ({ + className, + ...props +}: React.HTMLAttributes) => { + return ( + + ); +}; +DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'; + +export { + DropdownMenu, + DropdownMenuTrigger, + DropdownMenuContent, + DropdownMenuItem, + DropdownMenuCheckboxItem, + DropdownMenuRadioItem, + DropdownMenuLabel, + DropdownMenuSeparator, + DropdownMenuShortcut, + DropdownMenuGroup, + DropdownMenuPortal, + DropdownMenuSub, + DropdownMenuSubContent, + DropdownMenuSubTrigger, + DropdownMenuRadioGroup, +}; diff --git a/src/components/ui/table.tsx b/src/components/ui/table.tsx new file mode 100644 index 0000000..1db2bc2 --- /dev/null +++ b/src/components/ui/table.tsx @@ -0,0 +1,117 @@ +import * as React from 'react'; + +import { cn } from '@/lib/utils'; + +const Table = React.forwardRef< + HTMLTableElement, + React.HTMLAttributes +>(({className, ...props}, ref) => ( +
+ + +)); +Table.displayName = 'Table'; + +const TableHeader = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({className, ...props}, ref) => ( + +)); +TableHeader.displayName = 'TableHeader'; + +const TableBody = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({className, ...props}, ref) => ( + +)); +TableBody.displayName = 'TableBody'; + +const TableFooter = React.forwardRef< + HTMLTableSectionElement, + React.HTMLAttributes +>(({className, ...props}, ref) => ( + tr]:last:border-b-0', + className, + )} + {...props} + /> +)); +TableFooter.displayName = 'TableFooter'; + +const TableRow = React.forwardRef< + HTMLTableRowElement, + React.HTMLAttributes +>(({className, ...props}, ref) => ( + +)); +TableRow.displayName = 'TableRow'; + +const TableHead = React.forwardRef< + HTMLTableCellElement, + React.ThHTMLAttributes +>(({className, ...props}, ref) => ( +
+)); +TableHead.displayName = 'TableHead'; + +const TableCell = React.forwardRef< + HTMLTableCellElement, + React.TdHTMLAttributes +>(({className, ...props}, ref) => ( + +)); +TableCell.displayName = 'TableCell'; + +const TableCaption = React.forwardRef< + HTMLTableCaptionElement, + React.HTMLAttributes +>(({className, ...props}, ref) => ( +
+)); +TableCaption.displayName = 'TableCaption'; + +export { + Table, + TableHeader, + TableBody, + TableFooter, + TableHead, + TableRow, + TableCell, + TableCaption, +}; From b6e144a7f5682bcc7e00cff6a8722151703780e0 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sat, 9 Mar 2024 18:02:47 +0100 Subject: [PATCH 05/13] N-FIN-5: add missing screen reader tag --- src/components/navigation.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/components/navigation.tsx b/src/components/navigation.tsx index 5174a67..e6c9648 100644 --- a/src/components/navigation.tsx +++ b/src/components/navigation.tsx @@ -54,6 +54,7 @@ export default function Navigation() { + Account From dd2b62ed13d925793cf32a0219f63fba09fef2e0 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sat, 9 Mar 2024 18:02:54 +0100 Subject: [PATCH 06/13] N-FIN-5: add entity form schema and component --- src/components/form/entityForm.tsx | 102 +++++++++++++++++++++++ src/lib/form-schemas/entityFormSchema.ts | 8 ++ 2 files changed, 110 insertions(+) create mode 100644 src/components/form/entityForm.tsx create mode 100644 src/lib/form-schemas/entityFormSchema.ts diff --git a/src/components/form/entityForm.tsx b/src/components/form/entityForm.tsx new file mode 100644 index 0000000..85dbd8a --- /dev/null +++ b/src/components/form/entityForm.tsx @@ -0,0 +1,102 @@ +'use client'; + +import { zodResolver } from '@hookform/resolvers/zod'; +import { useForm } from 'react-hook-form'; +import { z } from 'zod'; +import { Form, FormControl, FormField, FormItem, FormLabel, FormMessage } from '@/components/ui/form'; +import { Input } from '@/components/ui/input'; +import React from 'react'; +import { Button } from '@/components/ui/button'; +import { ActionResponse } from '@/lib/types/ActionResponse'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import { sonnerContent } from '@/components/ui/sonner'; +import { entityFormSchema } from '@/lib/form-schemas/entityFormSchema'; +import { Entity, EntityType } from '@prisma/client'; +import { Select, SelectContent, SelectGroup, SelectItem, SelectTrigger, SelectValue } from '@/components/ui/select'; + +export default function EntityForm({value, onSubmit, className}: { + value: Entity | undefined, + onSubmit: (data: z.infer) => Promise + className?: string +}) { + + const router = useRouter(); + + const form = useForm>({ + resolver: zodResolver(entityFormSchema), + defaultValues: { + id: value?.id ?? undefined, + name: value?.name ?? '', + type: value?.type ?? EntityType.Entity, + }, + }); + + const handleSubmit = async (data: z.infer) => { + const response = await onSubmit(data); + toast(sonnerContent(response)); + if (response.redirect) { + router.push(response.redirect); + } + }; + + return ( +
+ + + ( + + + + + + + )} + /> + +
+ ( + + Name + + + + + + )} + /> + ( + + Type + + + + )} + /> +
+ + + + ); +} diff --git a/src/lib/form-schemas/entityFormSchema.ts b/src/lib/form-schemas/entityFormSchema.ts new file mode 100644 index 0000000..b93b1d4 --- /dev/null +++ b/src/lib/form-schemas/entityFormSchema.ts @@ -0,0 +1,8 @@ +import { z } from 'zod'; +import { EntityType } from '@prisma/client'; + +export const entityFormSchema = z.object({ + id: z.number().positive().optional(), + name: z.string().min(3).max(32), + type: z.nativeEnum(EntityType), +}); From 978ba4bf58463cfcb993a36ab411c2d18ea49c86 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sat, 9 Mar 2024 18:03:04 +0100 Subject: [PATCH 07/13] N-FIN-5: add entity create/update server action --- src/lib/actions/entityCreateUpdate.ts | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/lib/actions/entityCreateUpdate.ts diff --git a/src/lib/actions/entityCreateUpdate.ts b/src/lib/actions/entityCreateUpdate.ts new file mode 100644 index 0000000..1f22e3f --- /dev/null +++ b/src/lib/actions/entityCreateUpdate.ts @@ -0,0 +1,56 @@ +import { z } from 'zod'; +import { ActionResponse } from '@/lib/types/ActionResponse'; +import { entityFormSchema } from '@/lib/form-schemas/entityFormSchema'; +import { prismaClient } from '@/prisma'; +import { getUser } from '@/auth'; +import { URL_SIGN_IN } from '@/lib/constants'; + +export default async function entityCreateUpdate({ + id, + name, + type, +}: z.infer): Promise { + 'use server'; + + const user = await getUser(); + if (!user) { + return { + type: 'error', + message: 'You must be logged in to create an entity.', + redirect: URL_SIGN_IN, + }; + } + + try { + if (id) { + await prismaClient.entity.update({ + where: { + id: id, + }, + data: { + name: name, + type: type, + }, + }, + ); + } else { + await prismaClient.entity.create({ + data: { + userId: user.id, + name: name, + type: type, + }, + }); + } + } catch (e) { + return { + type: 'error', + message: 'Invalid entity data', + }; + } + + return { + type: 'success', + message: `Created an entity with name: ${name} and type: ${type}`, + }; +} From 0ece1aacb4d5ec04d2b24555e102a0f0e24ff331 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 10 Mar 2024 12:38:06 +0100 Subject: [PATCH 08/13] N-FIN-5: shorten server action response --- src/lib/actions/entityCreateUpdate.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/lib/actions/entityCreateUpdate.ts b/src/lib/actions/entityCreateUpdate.ts index 1f22e3f..5e401fa 100644 --- a/src/lib/actions/entityCreateUpdate.ts +++ b/src/lib/actions/entityCreateUpdate.ts @@ -12,15 +12,17 @@ export default async function entityCreateUpdate({ }: z.infer): Promise { 'use server'; + // check that user is logged in const user = await getUser(); if (!user) { return { type: 'error', - message: 'You must be logged in to create an entity.', + message: 'You must be logged in to create/update an entity.', redirect: URL_SIGN_IN, }; } + // create/update entity try { if (id) { await prismaClient.entity.update({ @@ -45,12 +47,13 @@ export default async function entityCreateUpdate({ } catch (e) { return { type: 'error', - message: 'Invalid entity data', + message: 'Failed creating/updating entity', }; } + // return success return { type: 'success', - message: `Created an entity with name: ${name} and type: ${type}`, + message: `${type} '${name}' created`, }; } From 2793ca9204220cce132c1a81213eb593fd5ddae5 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 10 Mar 2024 12:38:26 +0100 Subject: [PATCH 09/13] N-FIN-5: add entity delete server action --- src/lib/actions/entityDelete.ts | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/lib/actions/entityDelete.ts diff --git a/src/lib/actions/entityDelete.ts b/src/lib/actions/entityDelete.ts new file mode 100644 index 0000000..37ea1bd --- /dev/null +++ b/src/lib/actions/entityDelete.ts @@ -0,0 +1,62 @@ +import { ActionResponse } from '@/lib/types/ActionResponse'; +import { prismaClient } from '@/prisma'; +import { getUser } from '@/auth'; +import { URL_SIGN_IN } from '@/lib/constants'; + +export default async function entityDelete(id: number): Promise { + 'use server'; + + // check that id is a number + if (!id || isNaN(id)) { + return { + type: 'error', + message: 'Invalid entity ID', + }; + } + + // check that user is logged in + const user = await getUser(); + if (!user) { + return { + type: 'error', + message: 'You must be logged in to delete an entity.', + redirect: URL_SIGN_IN, + }; + } + + // check that entity is associated with user + const entity = await prismaClient.entity.findFirst({ + where: { + id: id, + userId: user.id, + }, + }); + if (!entity) { + return { + type: 'error', + message: 'Entity not found', + }; + } + + // delete entity + try { + await prismaClient.entity.delete({ + where: { + id: entity.id, + userId: user.id, + }, + }, + ); + } catch (e) { + return { + type: 'error', + message: 'Failed deleting entity', + }; + } + + // return success + return { + type: 'success', + message: `${entity.type} '${entity.name}' deleted`, + }; +} From 6e449ac6037bf07529a94e55727c082767ee7889 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 10 Mar 2024 12:38:57 +0100 Subject: [PATCH 10/13] N-FIN-5: fix entity name min. length --- src/lib/form-schemas/entityFormSchema.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/lib/form-schemas/entityFormSchema.ts b/src/lib/form-schemas/entityFormSchema.ts index b93b1d4..f3e4412 100644 --- a/src/lib/form-schemas/entityFormSchema.ts +++ b/src/lib/form-schemas/entityFormSchema.ts @@ -3,6 +3,6 @@ import { EntityType } from '@prisma/client'; export const entityFormSchema = z.object({ id: z.number().positive().optional(), - name: z.string().min(3).max(32), + name: z.string().min(1).max(32), type: z.nativeEnum(EntityType), }); From 8dcc99d1f8a805e1e52ff9f1ebee071fc258cb60 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 10 Mar 2024 12:39:10 +0100 Subject: [PATCH 11/13] N-FIN-5: add entity page UI --- src/app/entities/columns.tsx | 41 ++++ src/app/entities/page.tsx | 32 +++- src/components/EntityPageClientComponents.tsx | 178 ++++++++++++++++++ 3 files changed, 248 insertions(+), 3 deletions(-) create mode 100644 src/app/entities/columns.tsx create mode 100644 src/components/EntityPageClientComponents.tsx diff --git a/src/app/entities/columns.tsx b/src/app/entities/columns.tsx new file mode 100644 index 0000000..17ac7d7 --- /dev/null +++ b/src/app/entities/columns.tsx @@ -0,0 +1,41 @@ +'use client'; + +import { ColumnDef } from '@tanstack/react-table'; +import { Entity } from '@prisma/client'; +import { CellContext, ColumnDefTemplate } from '@tanstack/table-core'; + +export const columns = ( + actionCell: ColumnDefTemplate>, +) => { + + return [ + { + accessorKey: 'name', + header: 'Name', + }, + { + accessorKey: 'type', + header: 'Type', + }, + { + accessorKey: 'createdAt', + header: 'Created at', + cell: ({row}) => { + const date = row.getValue('createdAt') as Date; + return date.toDateString(); + }, + }, + { + accessorKey: 'updatedAt', + header: 'Updated at', + cell: ({row}) => { + const date = row.getValue('updatedAt') as Date; + return date.toDateString(); + }, + }, + { + id: 'actions', + cell: actionCell, + }, + ] as ColumnDef[]; +}; diff --git a/src/app/entities/page.tsx b/src/app/entities/page.tsx index 532b2d3..216eeeb 100644 --- a/src/app/entities/page.tsx +++ b/src/app/entities/page.tsx @@ -1,7 +1,33 @@ +import { prismaClient } from '@/prisma'; +import { getUser } from '@/auth'; +import React from 'react'; +import EntityPageClientContent from '@/components/EntityPageClientComponents'; +import entityCreateUpdate from '@/lib/actions/entityCreateUpdate'; +import entityDelete from '@/lib/actions/entityDelete'; + export default async function EntitiesPage() { + + const user = await getUser(); + + const entities = await prismaClient.entity.findMany({ + where: { + userId: user?.id, + }, + orderBy: [ + { + type: 'desc', + }, + { + id: 'asc', + }, + ], + }); + return ( -
- Entities -
+ ); } diff --git a/src/components/EntityPageClientComponents.tsx b/src/components/EntityPageClientComponents.tsx new file mode 100644 index 0000000..eb0f1b7 --- /dev/null +++ b/src/components/EntityPageClientComponents.tsx @@ -0,0 +1,178 @@ +'use client'; + +import { Entity } from '@prisma/client'; +import React, { useState } from 'react'; +import { CellContext } from '@tanstack/table-core'; +import { Button } from '@/components/ui/button'; +import { Edit, Trash } from 'lucide-react'; +import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import EntityForm from '@/components/form/entityForm'; +import { DataTable } from '@/components/ui/data-table'; +import { columns } from '@/app/entities/columns'; +import { z } from 'zod'; +import { entityFormSchema } from '@/lib/form-schemas/entityFormSchema'; +import { ActionResponse } from '@/lib/types/ActionResponse'; +import { Input } from '@/components/ui/input'; +import { useRouter } from 'next/navigation'; +import { toast } from 'sonner'; +import { sonnerContent } from '@/components/ui/sonner'; + +export default function EntityPageClientContent({entities, onSubmit, onDelete, className}: { + entities: Entity[], + onSubmit: (data: z.infer) => Promise, + onDelete: (id: number) => Promise, + className: string, +}) { + + const router = useRouter(); + + const [isEditDialogOpen, setIsEditDialogOpen] = useState(false); + const [isDeleteDialogOpen, setIsDeleteDialogOpen] = useState(false); + + const [selectedEntity, setSelectedEntity] = useState(undefined); + + async function handleSubmit(data: z.infer) { + const response = await onSubmit(data); + router.refresh(); + setIsEditDialogOpen(false); + return response; + } + + async function handleDelete(id: number | undefined) { + + if (!id) { + return; + } + + const response = await onDelete(id); + toast(sonnerContent(response)); + if (response.redirect) { + router.push(response.redirect); + } + router.refresh(); + setIsDeleteDialogOpen(false); + return response; + } + + function filterEntities(entities: Entity[], filter: string) { + const filterChars = filter.toLowerCase().split(''); + const filterCharCounts: Record = {}; + + // Count character occurrences in the filter + filterChars.forEach(char => { + filterCharCounts[char] = (filterCharCounts[char] || 0) + 1; + }); + + return entities.filter(entity => { + const entityChars = entity.name.toLowerCase().split(''); + const entityCharCounts: Record = {}; + + // Check if entity has enough of each character + for (const char of entityChars) { + entityCharCounts[char] = (entityCharCounts[char] || 0) + 1; + } + + // Ensure all filter characters were found + return Object.keys(filterCharCounts).every(char => { + return entityCharCounts[char] >= filterCharCounts[char]; + }); + }); + } + + const actionCell = ({row}: CellContext) => { + const entity = row.original as Entity; + + return ( +
+ + +
+ ); + }; + + const [filter, setFilter] = useState(''); + + return ( +
+
+

Entities

+ + {/* Edit dialog */} + + + + + + + {selectedEntity?.id ? 'Update Entity' : 'Create Entity'} + + + + +
+ + {/* Filter input */} + setFilter(event.target.value)} + placeholder="Filter entities"/> + + {/* Data Table */} + + + {/* Delete confirmation dialog */} + + + Delete Entity? +

Are your sure you want to delete the entity {selectedEntity?.name}?

+ + + + +
+
+
+ ); +} From e3874add659cdf3b0cf0892428310a5d6dcb9579 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 10 Mar 2024 17:01:01 +0100 Subject: [PATCH 12/13] N-FIN-5: refactor delete dialog to AlertDialog component --- package-lock.json | 29 ++++ package.json | 1 + src/components/EntityPageClientComponents.tsx | 38 ++--- src/components/ui/alert-dialog.tsx | 141 ++++++++++++++++++ 4 files changed, 191 insertions(+), 18 deletions(-) create mode 100644 src/components/ui/alert-dialog.tsx diff --git a/package-lock.json b/package-lock.json index 58a3c2a..e99aefb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,6 +11,7 @@ "@hookform/resolvers": "^3.3.4", "@lucia-auth/adapter-prisma": "^4.0.0", "@prisma/client": "^5.10.2", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", @@ -675,6 +676,34 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@radix-ui/react-alert-dialog": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@radix-ui/react-alert-dialog/-/react-alert-dialog-1.0.5.tgz", + "integrity": "sha512-OrVIOcZL0tl6xibeuGt5/+UxoT2N27KCFOPjFyfXMnchxSHZ/OW7cCX2nGlIYJrbHK/fczPcFzAwvNBB6XBNMA==", + "dependencies": { + "@babel/runtime": "^7.13.10", + "@radix-ui/primitive": "1.0.1", + "@radix-ui/react-compose-refs": "1.0.1", + "@radix-ui/react-context": "1.0.1", + "@radix-ui/react-dialog": "1.0.5", + "@radix-ui/react-primitive": "1.0.3", + "@radix-ui/react-slot": "1.0.2" + }, + "peerDependencies": { + "@types/react": "*", + "@types/react-dom": "*", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + } + } + }, "node_modules/@radix-ui/react-arrow": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.0.3.tgz", diff --git a/package.json b/package.json index 3e1719e..8f98fd3 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "@hookform/resolvers": "^3.3.4", "@lucia-auth/adapter-prisma": "^4.0.0", "@prisma/client": "^5.10.2", + "@radix-ui/react-alert-dialog": "^1.0.5", "@radix-ui/react-dialog": "^1.0.5", "@radix-ui/react-dropdown-menu": "^2.0.6", "@radix-ui/react-label": "^2.0.2", diff --git a/src/components/EntityPageClientComponents.tsx b/src/components/EntityPageClientComponents.tsx index eb0f1b7..282dcc4 100644 --- a/src/components/EntityPageClientComponents.tsx +++ b/src/components/EntityPageClientComponents.tsx @@ -5,7 +5,7 @@ import React, { useState } from 'react'; import { CellContext } from '@tanstack/table-core'; import { Button } from '@/components/ui/button'; import { Edit, Trash } from 'lucide-react'; -import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; +import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogTrigger } from '@/components/ui/dialog'; import EntityForm from '@/components/form/entityForm'; import { DataTable } from '@/components/ui/data-table'; import { columns } from '@/app/entities/columns'; @@ -16,6 +16,14 @@ import { Input } from '@/components/ui/input'; import { useRouter } from 'next/navigation'; import { toast } from 'sonner'; import { sonnerContent } from '@/components/ui/sonner'; +import { + AlertDialog, + AlertDialogAction, + AlertDialogCancel, + AlertDialogContent, + AlertDialogFooter, + AlertDialogHeader, +} from '@/components/ui/alert-dialog'; export default function EntityPageClientContent({entities, onSubmit, onDelete, className}: { entities: Entity[], @@ -153,26 +161,20 @@ export default function EntityPageClientContent({entities, onSubmit, onDelete, c pagination/> {/* Delete confirmation dialog */} - - - Delete Entity? + + + Delete Entity?

Are your sure you want to delete the entity {selectedEntity?.name}?

- - - - -
-
+ + + + ); } diff --git a/src/components/ui/alert-dialog.tsx b/src/components/ui/alert-dialog.tsx new file mode 100644 index 0000000..04a9ccd --- /dev/null +++ b/src/components/ui/alert-dialog.tsx @@ -0,0 +1,141 @@ +'use client'; + +import * as React from 'react'; +import * as AlertDialogPrimitive from '@radix-ui/react-alert-dialog'; + +import { cn } from '@/lib/utils'; +import { buttonVariants } from '@/components/ui/button'; + +const AlertDialog = AlertDialogPrimitive.Root; + +const AlertDialogTrigger = AlertDialogPrimitive.Trigger; + +const AlertDialogPortal = AlertDialogPrimitive.Portal; + +const AlertDialogOverlay = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName; + +const AlertDialogContent = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + + + + +)); +AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName; + +const AlertDialogHeader = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogHeader.displayName = 'AlertDialogHeader'; + +const AlertDialogFooter = ({ + className, + ...props +}: React.HTMLAttributes) => ( +
+); +AlertDialogFooter.displayName = 'AlertDialogFooter'; + +const AlertDialogTitle = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName; + +const AlertDialogDescription = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +AlertDialogDescription.displayName = + AlertDialogPrimitive.Description.displayName; + +const AlertDialogAction = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName; + +const AlertDialogCancel = React.forwardRef< + React.ElementRef, + React.ComponentPropsWithoutRef +>(({className, ...props}, ref) => ( + +)); +AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName; + +export { + AlertDialog, + AlertDialogPortal, + AlertDialogOverlay, + AlertDialogTrigger, + AlertDialogContent, + AlertDialogHeader, + AlertDialogFooter, + AlertDialogTitle, + AlertDialogDescription, + AlertDialogAction, + AlertDialogCancel, +}; From 4e761d81deab58dedd7f169342e35462f983d8a4 Mon Sep 17 00:00:00 2001 From: Markus Thielker Date: Sun, 10 Mar 2024 17:02:32 +0100 Subject: [PATCH 13/13] N-FIN-5: changed success message depending on condition --- src/lib/actions/entityCreateUpdate.ts | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/src/lib/actions/entityCreateUpdate.ts b/src/lib/actions/entityCreateUpdate.ts index 5e401fa..37027b6 100644 --- a/src/lib/actions/entityCreateUpdate.ts +++ b/src/lib/actions/entityCreateUpdate.ts @@ -35,6 +35,12 @@ export default async function entityCreateUpdate({ }, }, ); + + // return success + return { + type: 'success', + message: `${type} '${name}' updated`, + }; } else { await prismaClient.entity.create({ data: { @@ -43,6 +49,12 @@ export default async function entityCreateUpdate({ type: type, }, }); + + // return success + return { + type: 'success', + message: `${type} '${name}' created`, + }; } } catch (e) { return { @@ -50,10 +62,4 @@ export default async function entityCreateUpdate({ message: 'Failed creating/updating entity', }; } - - // return success - return { - type: 'success', - message: `${type} '${name}' created`, - }; }