diff --git a/.eslintrc.json b/.eslintrc.json index 207de6a..8b476e0 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -6,6 +6,7 @@ "prettier" ], "rules": { - "react/no-unescaped-entities": "off" + "react/no-unescaped-entities": "off", + "react-hooks/exhaustive-deps": "off" } } diff --git a/.github/workflows/push-build.yml b/.github/workflows/push-build.yml new file mode 100644 index 0000000..7ba773a --- /dev/null +++ b/.github/workflows/push-build.yml @@ -0,0 +1,27 @@ +name: Check build when pushing +run-name: ${{ github.actor }} is pushing +on: [push] +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout repository + uses: actions/checkout@v4 + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 20 + - name: Install dependencies + run: npm install + - name: Build + run: npm run build + - name: Notify Discord on failure + if: failure() + uses: containrrr/shoutrrr-action@v1 + with: + url: ${{ secrets.NOTIFICATION_URL }} + title: "Build failed for ${{ github.actor }}" + message: | + Build failed + Commit message: ${{ github.event.head_commit.message }} + Commit SHA: ${{ github.sha }} diff --git a/next.config.mjs b/next.config.mjs index 4678774..30acda4 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -1,4 +1,13 @@ /** @type {import('next').NextConfig} */ -const nextConfig = {}; +const nextConfig = { + images: { + remotePatterns: [ + { + protocol: "https", + hostname: "via.assets.so", + }, + ], + }, +}; export default nextConfig; diff --git a/package-lock.json b/package-lock.json index 11cbe6f..f179b57 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,39 +8,43 @@ "name": "next-patrigma", "version": "0.1.0", "dependencies": { - "@hookform/resolvers": "^3.3.4", + "@hookform/resolvers": "^3.4.2", "@prisma/client": "^5.14.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", - "jose": "^5.3.0", - "lucide-react": "^0.376.0", + "framer-motion": "^11.2.4", + "lucide-react": "^0.378.0", "next": "14.2.3", "react": "^18", "react-day-picker": "^8.10.1", "react-dom": "^18", "react-hook-form": "^7.51.4", + "react-leaflet": "^4.2.1", + "react-modal-sheet": "^3.1.0", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8", "zustand": "^4.5.2" }, "devDependencies": { + "@types/leaflet": "^1.9.12", + "@types/node": "^20.12.12", "@types/bcrypt": "^5.0.2", - "@types/node": "^20.12.11", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8.57.0", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-tailwindcss": "^3.15.1", + "eslint-plugin-tailwindcss": "^3.15.2", "postcss": "^8", - "prisma": "^5.13.0", + "prisma": "^5.14.0", "tailwindcss": "^3.4.3", "ts-node": "^10.9.2", "typescript": "^5.4.5" @@ -181,9 +185,9 @@ "integrity": "sha512-J4yDIIthosAsRZ5CPYP/jQvUAQtlZTTD/4suA08/FEnlxqW3sKS9iAhgsa9VYLZ6vDHn/ixJgIqRQPotoBjxIw==" }, "node_modules/@hookform/resolvers": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.3.4.tgz", - "integrity": "sha512-o5cgpGOuJYrd+iMKvkttOclgwRW86EsWJZZRC23prf0uU2i48Htq4PuT73AVb9ionFyZrwYEITuOFGF+BydEtQ==", + "version": "3.4.2", + "resolved": "https://registry.npmjs.org/@hookform/resolvers/-/resolvers-3.4.2.tgz", + "integrity": "sha512-1m9uAVIO8wVf7VCDAGsuGA0t6Z3m6jVGAN50HkV9vYLl0yixKK/Z1lr01vaRvYCkIKGoy1noVRxMzQYb4y/j1Q==", "peerDependencies": { "react-hook-form": "^7.0.0" } @@ -532,48 +536,56 @@ } }, "node_modules/@prisma/debug": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.13.0.tgz", - "integrity": "sha512-699iqlEvzyCj9ETrXhs8o8wQc/eVW+FigSsHpiskSFydhjVuwTJEfj/nIYqTaWFYuxiWQRfm3r01meuW97SZaQ==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@prisma/debug/-/debug-5.14.0.tgz", + "integrity": "sha512-iq56qBZuFfX3fCxoxT8gBX33lQzomBU0qIUaEj1RebsKVz1ob/BVH1XSBwwwvRVtZEV1b7Fxx2eVu34Ge/mg3w==", "devOptional": true }, "node_modules/@prisma/engines": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.13.0.tgz", - "integrity": "sha512-hIFLm4H1boj6CBZx55P4xKby9jgDTeDG0Jj3iXtwaaHmlD5JmiDkZhh8+DYWkTGchu+rRF36AVROLnk0oaqhHw==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@prisma/engines/-/engines-5.14.0.tgz", + "integrity": "sha512-lgxkKZ6IEygVcw6IZZUlPIfLQ9hjSYAtHjZ5r64sCLDgVzsPFCi2XBBJgzPMkOQ5RHzUD4E/dVdpn9+ez8tk1A==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/debug": "5.13.0", - "@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b", - "@prisma/fetch-engine": "5.13.0", - "@prisma/get-platform": "5.13.0" + "@prisma/debug": "5.14.0", + "@prisma/engines-version": "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48", + "@prisma/fetch-engine": "5.14.0", + "@prisma/get-platform": "5.14.0" } }, "node_modules/@prisma/engines-version": { - "version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b", - "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b.tgz", - "integrity": "sha512-AyUuhahTINGn8auyqYdmxsN+qn0mw3eg+uhkp8zwknXYIqoT3bChG4RqNY/nfDkPvzWAPBa9mrDyBeOnWSgO6A==", + "version": "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48", + "resolved": "https://registry.npmjs.org/@prisma/engines-version/-/engines-version-5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48.tgz", + "integrity": "sha512-ip6pNkRo1UxWv+6toxNcYvItNYaqQjXdFNGJ+Nuk2eYtRoEdoF13wxo7/jsClJFFenMPVNVqXQDV0oveXnR1cA==", "devOptional": true }, "node_modules/@prisma/fetch-engine": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.13.0.tgz", - "integrity": "sha512-Yh4W+t6YKyqgcSEB3odBXt7QyVSm0OQlBSldQF2SNXtmOgMX8D7PF/fvH6E6qBCpjB/yeJLy/FfwfFijoHI6sA==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@prisma/fetch-engine/-/fetch-engine-5.14.0.tgz", + "integrity": "sha512-VrheA9y9DMURK5vu8OJoOgQpxOhas3qF0IBHJ8G/0X44k82kc8E0w98HCn2nhnbOOMwbWsJWXfLC2/F8n5u0gQ==", "devOptional": true, "dependencies": { - "@prisma/debug": "5.13.0", - "@prisma/engines-version": "5.13.0-23.b9a39a7ee606c28e3455d0fd60e78c3ba82b1a2b", - "@prisma/get-platform": "5.13.0" + "@prisma/debug": "5.14.0", + "@prisma/engines-version": "5.14.0-25.e9771e62de70f79a5e1c604a2d7c8e2a0a874b48", + "@prisma/get-platform": "5.14.0" } }, "node_modules/@prisma/get-platform": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.13.0.tgz", - "integrity": "sha512-B/WrQwYTzwr7qCLifQzYOmQhZcFmIFhR81xC45gweInSUn2hTEbfKUPd2keAog+y5WI5xLAFNJ3wkXplvSVkSw==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/@prisma/get-platform/-/get-platform-5.14.0.tgz", + "integrity": "sha512-/yAyBvcEjRv41ynZrhdrPtHgk47xLRRq/o5eWGcUpBJ1YrUZTYB8EoPiopnP7iQrMATK8stXQdPOoVlrzuTQZw==", "devOptional": true, "dependencies": { - "@prisma/debug": "5.13.0" + "@prisma/debug": "5.14.0" + } + }, + "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": { @@ -607,6 +619,32 @@ } } }, + "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", @@ -641,6 +679,23 @@ } } }, + "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", @@ -890,6 +945,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", @@ -978,6 +1076,23 @@ } } }, + "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", @@ -1014,6 +1129,29 @@ } } }, + "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", @@ -1022,6 +1160,64 @@ "@babel/runtime": "^7.13.10" } }, + "node_modules/@react-aria/ssr": { + "version": "3.9.4", + "resolved": "https://registry.npmjs.org/@react-aria/ssr/-/ssr-3.9.4.tgz", + "integrity": "sha512-4jmAigVq409qcJvQyuorsmBR4+9r3+JEC60wC+Y0MZV0HCtTmm8D9guYXlJMdx0SSkgj0hHAyFm/HvPNFofCoQ==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "engines": { + "node": ">= 12" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-aria/utils": { + "version": "3.24.0", + "resolved": "https://registry.npmjs.org/@react-aria/utils/-/utils-3.24.0.tgz", + "integrity": "sha512-JAxkPhK5fCvFVNY2YG3TW3m1nTzwRcbz7iyTSkUzLFat4N4LZ7Kzh7NMHsgeE/oMOxd8zLY+XsUxMu/E/2GujA==", + "dependencies": { + "@react-aria/ssr": "^3.9.3", + "@react-stately/utils": "^3.10.0", + "@react-types/shared": "^3.23.0", + "@swc/helpers": "^0.5.0", + "clsx": "^2.0.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-leaflet/core": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@react-leaflet/core/-/core-2.1.0.tgz", + "integrity": "sha512-Qk7Pfu8BSarKGqILj4x7bCSZ1pjuAPZ+qmRwH5S7mDS91VSbVVsJSrW4qA+GPrro8t69gFYVMWb1Zc4yFmPiVg==", + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/@react-stately/utils": { + "version": "3.10.1", + "resolved": "https://registry.npmjs.org/@react-stately/utils/-/utils-3.10.1.tgz", + "integrity": "sha512-VS/EHRyicef25zDZcM/ClpzYMC5i2YGN6uegOeQawmgfGjb02yaCX0F0zR69Pod9m2Hr3wunTbtpgVXvYbZItg==", + "dependencies": { + "@swc/helpers": "^0.5.0" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, + "node_modules/@react-types/shared": { + "version": "3.23.1", + "resolved": "https://registry.npmjs.org/@react-types/shared/-/shared-3.23.1.tgz", + "integrity": "sha512-5d+3HbFDxGZjhbMBeFHRQhexMFt4pUce3okyRtUVKbbedQFUrtXSBg9VszgF2RTeQDKDkMCIQDtz5ccP/Lk1gw==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0-rc.1 || ^18.0.0" + } + }, "node_modules/@rushstack/eslint-patch": { "version": "1.10.2", "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.2.tgz", @@ -1066,25 +1262,25 @@ "integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==", "devOptional": true }, - "node_modules/@types/bcrypt": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@types/bcrypt/-/bcrypt-5.0.2.tgz", - "integrity": "sha512-6atioO8Y75fNcbmj0G7UjI9lXN2pQ/IGJ2FWT4a/btd0Lk9lQalHLKhkgKVZ3r+spnmWUKfbMi1GEe9wyHQfNQ==", - "dev": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@types/json5": { "version": "0.0.29", "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", "dev": true }, + "node_modules/@types/leaflet": { + "version": "1.9.12", + "resolved": "https://registry.npmjs.org/@types/leaflet/-/leaflet-1.9.12.tgz", + "integrity": "sha512-BK7XS+NyRI291HIo0HCfE18Lp8oA30H1gpi1tf0mF3TgiCEzanQjOqNZ4x126SXzzi2oNSZhZ5axJp1k0iM6jg==", + "dev": true, + "dependencies": { + "@types/geojson": "*" + } + }, "node_modules/@types/node": { - "version": "20.12.11", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.11.tgz", - "integrity": "sha512-vDg9PZ/zi+Nqp6boSOT7plNuthRugEKixDv5sFTIpkE89MmNtEArAShI4mxuX2+UrLEe9pxC1vm2cjm9YlWbJw==", + "version": "20.12.12", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.12.tgz", + "integrity": "sha512-eWLDGF/FOSPtAvEqeRAQ4C8LSA7M1I7i0ky1I8U7kD1J5ITyW3AsRhQrKVoWf5pFKZ2kILsEGJhsI9r93PYnOw==", "devOptional": true, "dependencies": { "undici-types": "~5.26.4" @@ -2741,9 +2937,9 @@ } }, "node_modules/eslint-plugin-tailwindcss": { - "version": "3.15.1", - "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.15.1.tgz", - "integrity": "sha512-4RXRMIaMG07C2TBEW1k0VM4+dDazz1kxcZhkK4zirvmHGZTA4jnlSO2kq5mamuSPi+Wo17dh2SlC8IyFBuCd7Q==", + "version": "3.15.2", + "resolved": "https://registry.npmjs.org/eslint-plugin-tailwindcss/-/eslint-plugin-tailwindcss-3.15.2.tgz", + "integrity": "sha512-+HJfWcyP5B/e8r8qVSaTbf2i4+HsESJJsue66qFHRstV11CNTfdaDD9zkCVA1pm2EplBZ/BSJ3Htfzvb4YTVKw==", "dev": true, "dependencies": { "fast-glob": "^3.2.5", @@ -2978,28 +3174,6 @@ "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/fs-minipass": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", - "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", - "dependencies": { - "minipass": "^3.0.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/fs-minipass/node_modules/minipass": { - "version": "3.3.6", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", - "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", - "dependencies": { - "yallist": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -3968,6 +4142,12 @@ "node": ">=0.10" } }, + "node_modules/leaflet": { + "version": "1.9.4", + "resolved": "https://registry.npmjs.org/leaflet/-/leaflet-1.9.4.tgz", + "integrity": "sha512-nxS1ynzJOmOlHp+iL3FyWqK89GtNL8U8rvlMOsQdTTssxZwCXh8N2NB3GDQOL+YR3XnWyZAxwQixURb+FA74PA==", + "peer": true + }, "node_modules/levn": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", @@ -4035,9 +4215,9 @@ } }, "node_modules/lucide-react": { - "version": "0.376.0", - "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.376.0.tgz", - "integrity": "sha512-g91IX3ERD6yUR1TL2dsL4BkcGygpZz/EsqjAeL/kcRQV0EApIOr/9eBfKhYOVyQIcGGuotFGjF3xKLHMEz+b7g==", + "version": "0.378.0", + "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.378.0.tgz", + "integrity": "sha512-u6EPU8juLUk9ytRcyapkWI18epAv3RU+6+TC23ivjR0e+glWKBobFeSgRwOIJihzktILQuy6E0E80P2jVTDR5g==", "peerDependencies": { "react": "^16.5.1 || ^17.0.0 || ^18.0.0" } @@ -4768,13 +4948,13 @@ } }, "node_modules/prisma": { - "version": "5.13.0", - "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.13.0.tgz", - "integrity": "sha512-kGtcJaElNRAdAGsCNykFSZ7dBKpL14Cbs+VaQ8cECxQlRPDjBlMHNFYeYt0SKovAVy2Y65JXQwB3A5+zIQwnTg==", + "version": "5.14.0", + "resolved": "https://registry.npmjs.org/prisma/-/prisma-5.14.0.tgz", + "integrity": "sha512-gCNZco7y5XtjrnQYeDJTiVZmT/ncqCr5RY1/Cf8X2wgLRmyh9ayPAGBNziI4qEE4S6SxCH5omQLVo9lmURaJ/Q==", "devOptional": true, "hasInstallScript": true, "dependencies": { - "@prisma/engines": "5.13.0" + "@prisma/engines": "5.14.0" }, "bin": { "prisma": "build/index.js" @@ -4879,6 +5059,34 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "dev": true }, + "node_modules/react-leaflet": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/react-leaflet/-/react-leaflet-4.2.1.tgz", + "integrity": "sha512-p9chkvhcKrWn/H/1FFeVSqLdReGwn2qmiobOQGO3BifX+/vV/39qhY8dGqbdcPh1e6jxh/QHriLXr7a4eLFK4Q==", + "dependencies": { + "@react-leaflet/core": "^2.1.0" + }, + "peerDependencies": { + "leaflet": "^1.9.0", + "react": "^18.0.0", + "react-dom": "^18.0.0" + } + }, + "node_modules/react-modal-sheet": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/react-modal-sheet/-/react-modal-sheet-3.1.0.tgz", + "integrity": "sha512-IjmVnNV4O1MXpYv6DFkmKmTQFYMklFig6uYLYz707uOBOO6RQMoG2dOPmQMIs41/psnp2NvHmQNA2pN1+UwgcA==", + "dependencies": { + "@react-aria/utils": "3.24.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "framer-motion": ">=8", + "react": ">=16" + } + }, "node_modules/react-remove-scroll": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.5.5.tgz", diff --git a/package.json b/package.json index 2e4f3a1..fbacaa9 100644 --- a/package.json +++ b/package.json @@ -12,39 +12,43 @@ "seed": "ts-node --compiler-options {\"module\":\"CommonJS\"} prisma/seed.ts" }, "dependencies": { - "@hookform/resolvers": "^3.3.4", + "@hookform/resolvers": "^3.4.2", "@prisma/client": "^5.14.0", "@radix-ui/react-label": "^2.0.2", "@radix-ui/react-popover": "^1.0.7", + "@radix-ui/react-select": "^2.0.0", "@radix-ui/react-slot": "^1.0.2", "bcrypt": "^5.1.1", "class-variance-authority": "^0.7.0", "clsx": "^2.1.1", "date-fns": "^3.6.0", - "jose": "^5.3.0", - "lucide-react": "^0.376.0", + "framer-motion": "^11.2.4", + "lucide-react": "^0.378.0", "next": "14.2.3", "react": "^18", "react-day-picker": "^8.10.1", "react-dom": "^18", "react-hook-form": "^7.51.4", + "react-leaflet": "^4.2.1", + "react-modal-sheet": "^3.1.0", "tailwind-merge": "^2.3.0", "tailwindcss-animate": "^1.0.7", "zod": "^3.23.8", "zustand": "^4.5.2" }, "devDependencies": { + "@types/leaflet": "^1.9.12", + "@types/node": "^20.12.12", "@types/bcrypt": "^5.0.2", - "@types/node": "^20.12.11", "@types/react": "^18", "@types/react-dom": "^18", "eslint": "^8.57.0", "eslint-config-next": "14.2.3", "eslint-config-prettier": "^9.1.0", "eslint-config-standard": "^17.1.0", - "eslint-plugin-tailwindcss": "^3.15.1", + "eslint-plugin-tailwindcss": "^3.15.2", "postcss": "^8", - "prisma": "^5.13.0", + "prisma": "^5.14.0", "tailwindcss": "^3.4.3", "ts-node": "^10.9.2", "typescript": "^5.4.5" diff --git a/prisma/schema.prisma b/prisma/schema.prisma index e19b93e..4d02667 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -91,9 +91,9 @@ model Journey { physicalDifficulty Int lastCompletion DateTime? mobilityImpaired String - PartiallySighted String - PartiallyDeaf String - CognitivelyImpaired String + partiallySighted String + partiallyDeaf String + cognitivelyImpaired String createdAt DateTime @default(now()) updatedAt DateTime @updatedAt steps Step[] @@ -112,7 +112,8 @@ model Step { pictureHint String? createdAt DateTime @default(now()) updatedAt DateTime @updatedAt - coordinates String + latitude Float + longitude Float address String city String postalCode String @@ -132,4 +133,4 @@ model Comment { updatedAt DateTime @updatedAt user User @relation(fields: [authorId], references: [id]) journey Journey @relation(fields: [journeyId], references: [id]) -} \ No newline at end of file +} diff --git a/prisma/seed.ts b/prisma/seed.ts index a0e92ea..61c7279 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -1,4 +1,12 @@ -import { Journey, Event, PrismaClient, Role, User, UserEvent, Comment } from "@prisma/client"; +import { + Journey, + Event, + PrismaClient, + Role, + User, + UserEvent, + Comment, +} from "@prisma/client"; const prisma = new PrismaClient(); @@ -269,9 +277,9 @@ async function main() { physicalDifficulty: 1, lastCompletion: new Date("2021-01-01"), mobilityImpaired: "Partially accessible", - PartiallySighted: "Partially accessible", - PartiallyDeaf: "Partially accessible", - CognitivelyImpaired: "Partially accessible", + partiallySighted: "Partially accessible", + partiallyDeaf: "Partially accessible", + cognitivelyImpaired: "Partially accessible", steps: { create: [ { @@ -406,9 +414,9 @@ async function main() { physicalDifficulty: 1, lastCompletion: new Date("2021-02-01"), mobilityImpaired: "Partially accessible", - PartiallySighted: "Partially accessible", - PartiallyDeaf: "Partially accessible", - CognitivelyImpaired: "Partially accessible", + partiallySighted: "Partially accessible", + partiallyDeaf: "Partially accessible", + cognitivelyImpaired: "Partially accessible", steps: { create: [ { @@ -458,9 +466,9 @@ async function main() { physicalDifficulty: 2, lastCompletion: new Date("2021-03-01"), mobilityImpaired: "Partially accessible", - PartiallySighted: "Partially accessible", - PartiallyDeaf: "Partially accessible", - CognitivelyImpaired: "Partially accessible", + partiallySighted: "Partially accessible", + partiallyDeaf: "Partially accessible", + cognitivelyImpaired: "Partially accessible", steps: { create: [ { @@ -592,9 +600,9 @@ async function main() { physicalDifficulty: 2, lastCompletion: new Date("2021-04-01"), mobilityImpaired: "Partially accessible", - PartiallySighted: "Partially accessible", - PartiallyDeaf: "Partially accessible", - CognitivelyImpaired: "Partially accessible", + partiallySighted: "Partially accessible", + partiallyDeaf: "Partially accessible", + cognitivelyImpaired: "Partially accessible", steps: { create: [ { @@ -672,9 +680,9 @@ async function main() { physicalDifficulty: 2, lastCompletion: new Date("2021-05-01"), mobilityImpaired: "Partially accessible", - PartiallySighted: "Partially accessible", - PartiallyDeaf: "Partially accessible", - CognitivelyImpaired: "Partially accessible", + partiallySighted: "Partially accessible", + partiallyDeaf: "Partially accessible", + cognitivelyImpaired: "Partially accessible", steps: { create: [ { diff --git a/public/img/pin.png b/public/img/pin.png new file mode 100644 index 0000000..7582c54 Binary files /dev/null and b/public/img/pin.png differ diff --git a/src/app/(app)/parcours/[id]/page.tsx b/src/app/(app)/parcours/[id]/page.tsx new file mode 100644 index 0000000..7ec18da --- /dev/null +++ b/src/app/(app)/parcours/[id]/page.tsx @@ -0,0 +1,10 @@ +import React from "react"; + +type Params = { id: string }; + +const JourneyDetail = ({ params }: { params: Params }) => { + // get journey by id + return
JourneyDetail
; +}; + +export default JourneyDetail; diff --git a/src/app/(app)/parcours/page.tsx b/src/app/(app)/parcours/page.tsx new file mode 100644 index 0000000..4a8ccd1 --- /dev/null +++ b/src/app/(app)/parcours/page.tsx @@ -0,0 +1,21 @@ +import React from "react"; +import JourneyCard from "@/components/JourneyCard"; +import AddButton from "@/components/AddButton"; +import JourneyForm from "@/components/form/journey/JourneyForm"; + +const Parcours = () => { + return ( +
+ { + // to do: fetch parcours + Array.from({ length: 10 }).map((_, i) => ( + + )) + } + + +
+ ); +}; + +export default Parcours; diff --git a/src/components/AddButton.tsx b/src/components/AddButton.tsx new file mode 100644 index 0000000..13be050 --- /dev/null +++ b/src/components/AddButton.tsx @@ -0,0 +1,24 @@ +"use client"; +import React from "react"; +import { useJourneyFormStore } from "@/store/journeyFormStore"; +import { Icons } from "./Icons"; + +type AddButtonProps = { + action: "journey" | "event"; +}; + +const AddButton = ({ action }: AddButtonProps) => { + const { showModal } = useJourneyFormStore(); + return ( +
{ + action === "journey" ? showModal() : showModal(); + }} + className="fixed bottom-24 right-[20px] cursor-pointer rounded-lg bg-slate-500 p-4 shadow-lg" + > + +
+ ); +}; + +export default AddButton; diff --git a/src/components/Icons.tsx b/src/components/Icons.tsx index bfb7227..134ebb2 100644 --- a/src/components/Icons.tsx +++ b/src/components/Icons.tsx @@ -1,4 +1,4 @@ -import { LucideProps } from "lucide-react"; +import { LucideProps, Plus } from "lucide-react"; export const Icons = { flag: (props: LucideProps) => ( @@ -44,7 +44,7 @@ export const Icons = { fill="none" xmlns="http://www.w3.org/2000/svg" > - + ), + mapPin: (props: LucideProps) => ( + + + + ), + plus: (props: LucideProps) => , }; diff --git a/src/components/JourneyCard.tsx b/src/components/JourneyCard.tsx new file mode 100644 index 0000000..5c69bf7 --- /dev/null +++ b/src/components/JourneyCard.tsx @@ -0,0 +1,54 @@ +import React from "react"; +import Image from "next/image"; +import Link from "next/link"; +import { Icons } from "./Icons"; + +// type JourneyCardProps = { +// id: string; +// title: string; +// location: string; +// description: string; +// image: string; +// cluesDifficulty: number; +// physicalDifficulty: number; +// commentsCount: number; +// rating: number; +// }; + +const JourneyCard = (/* { + id, + title, + location, + description, + cluesDifficulty, + commentsCount, + image, + physicalDifficulty, + rating, +}: JourneyCardProps */) => { + const id = "1"; + return ( + + +
+

Titre

+ {/* to do: add rating */} +
+ +

Normandie

+
+

+ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Morbi in + sodales mauris. +

+
+ + ); +}; + +export default JourneyCard; diff --git a/src/components/form/Register.tsx b/src/components/form/Register.tsx index f9f6f66..65a396e 100644 --- a/src/components/form/Register.tsx +++ b/src/components/form/Register.tsx @@ -22,7 +22,7 @@ import { PopoverContent, PopoverTrigger, } from "@/components/ui/popover"; -import { cn } from "@/lib/utils"; +import { cn } from "@/lib/tailwindUtils"; type RegisterForm = z.infer; diff --git a/src/components/form/StepCounter.tsx b/src/components/form/StepCounter.tsx new file mode 100644 index 0000000..120300b --- /dev/null +++ b/src/components/form/StepCounter.tsx @@ -0,0 +1,26 @@ +"use client"; + +type StepCounterProps = { + step: number; + totalSteps: number; +}; + +const StepCounter = ({ step, totalSteps }: StepCounterProps) => { + return ( +
+ {[...Array(totalSteps)].map((_, index) => ( +
+ ))} +

+ Étape {step + 1}/{totalSteps} +

+
+ ); +}; + +export default StepCounter; diff --git a/src/components/form/journey/BottomSheetModal.tsx b/src/components/form/journey/BottomSheetModal.tsx new file mode 100644 index 0000000..a63bfd2 --- /dev/null +++ b/src/components/form/journey/BottomSheetModal.tsx @@ -0,0 +1,201 @@ +"use client"; +import { useJourneyFormStore } from "@/store/journeyFormStore"; +import { Button } from "@/components/ui/button"; +import { addStepSchema } from "@/validators/stepFormSchema"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { z } from "zod"; +import { + Form, + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Label } from "@/components/ui/label"; +import { Sheet } from "react-modal-sheet"; +import "leaflet/dist/leaflet.css"; +import { useEffect, useState } from "react"; +import LeafletMap from "@/components/map/LeafletMap"; + +type AddStepFormValues = z.infer; + +const BottomSheetModal = () => { + const { + bottomSheetVisible, + hideBottomSheet, + addStep, + editedStep, + editStep, + setEditedStep, + } = useJourneyFormStore(); + const [dragDisabled, setDragDisabled] = useState(false); + + useEffect(() => { + console.log(editedStep, "edited step"); + if (editedStep) { + form.setValue("puzzle", editedStep.puzzle); + form.setValue("answer", editedStep.answer); + form.setValue("hint", editedStep.hint); + form.setValue( + "coordinates", + `${editedStep.coordinates.latitude};${editedStep.coordinates.longitude}` + ); + } + }, [editedStep]); + + const form = useForm({ + resolver: zodResolver(addStepSchema), + defaultValues: { + puzzle: "", + answer: "", + hint: "", + coordinates: "", + }, + }); + + const processForm: SubmitHandler = (data) => { + const coordinates = data.coordinates.split(";"); + const latitude = parseFloat(coordinates[0]); + const longitude = parseFloat(coordinates[1]); + + if (editedStep) { + editStep( + { + hint: data.hint, + puzzle: data.puzzle, + answer: data.answer, + coordinates: { + latitude, + longitude, + }, + }, + editedStep.index + ); + } else { + addStep({ + answer: data.answer, + coordinates: { + latitude, + longitude, + }, + hint: data.hint, + puzzle: data.puzzle, + }); + } + + hideBottomSheet(); + onDismiss(); + }; + + const onDismiss = () => { + hideBottomSheet(); + setEditedStep(null); + form.reset(); + }; + + return ( + + + + +
+ +
+ ( + + + <> + + + + + + + )} + /> + {/* TO DO: add file input field for picturePuzzle registration */} +
+ ( + + + <> + + + + + + + )} + /> +
+ ( + + + <> + + + + + + + )} + /> + {/* TO DO: add file input field for pictureHint registration */} +
+ ( + + + <> + + + + +
+
{/* search bar */}
+ +
+ +
+ )} + /> + + + + +
+
+
+ ); +}; + +export default BottomSheetModal; diff --git a/src/components/form/journey/JourneyForm.tsx b/src/components/form/journey/JourneyForm.tsx new file mode 100644 index 0000000..499c8c4 --- /dev/null +++ b/src/components/form/journey/JourneyForm.tsx @@ -0,0 +1,121 @@ +"use client"; +import React, { useState } from "react"; +import { + journeyFormSchema, + firstStepSchema, + secondStepSchema, + thirdStepSchema, + forthStepSchema, +} from "@/validators/journeyFormSchema"; +import { z } from "zod"; +import { useJourneyFormStore } from "@/store/journeyFormStore"; +import { useForm, SubmitHandler } from "react-hook-form"; +import { Form } from "@/components/ui/form"; +import { Button } from "@/components/ui/button"; +import { zodResolver } from "@hookform/resolvers/zod"; +import StepCounter from "../StepCounter"; +import Steps from "./steps/Steps"; + +export type JourneyFormValues = z.infer; +type FieldName = keyof JourneyFormValues; + +const firstStepFields = Object.keys(firstStepSchema.shape); +const secondStepFields = Object.keys(secondStepSchema.shape); +const thirdStepFields = Object.keys(thirdStepSchema.shape); +const forthStepFields = Object.keys(forthStepSchema.shape); + +const steps = [ + { + fields: firstStepFields, + }, + { + fields: secondStepFields, + }, + { + fields: thirdStepFields, + }, + { + fields: forthStepFields, + }, +]; + +const JourneyForm = () => { + const { isVisible, hideModal } = useJourneyFormStore(); + const [formStatus, setFormStatus] = useState<"idle" | "errored">("idle"); + const [currentStep, setCurrentStep] = useState(2); + const [dir, setDir] = useState<"ltr" | "rtl">("ltr"); + + const form = useForm({ + resolver: zodResolver(journeyFormSchema), + defaultValues: { + title: "", + description: "", + requirement: "", + mobilityImpaired: "undefined", + partiallySighted: "undefined", + partiallyDeaf: "undefined", + cognitivelyImpaired: "undefined", + steps: "", + treasure: "", + }, + }); + + const processForm: SubmitHandler = async (data) => { + console.log(data); + }; + + const next = async () => { + const fields = steps[currentStep].fields; + const output = await form.trigger(fields as FieldName[]); + console.log(output, "output", fields, "fields"); + if (!output) return; + if (currentStep < steps.length) { + if (currentStep === steps.length - 1) { + await form.handleSubmit(processForm)(); + } + + if (currentStep < steps.length - 1) { + setCurrentStep((step) => step + 1); + setDir("ltr"); + } + } + }; + + const prev = () => { + if (currentStep > 0) { + setCurrentStep((step) => step - 1); + setDir("rtl"); + } + }; + + const dismissModal = () => { + hideModal(); + setCurrentStep(0); + form.reset(); + setFormStatus("idle"); + }; + + if (!isVisible) return null; + + return ( +
+ +
+ {!form.formState.isSubmitSuccessful && formStatus === "idle" && ( + + + + + )} + +
+ ); +}; + +export default JourneyForm; diff --git a/src/components/form/journey/steps/FirstStep.tsx b/src/components/form/journey/steps/FirstStep.tsx new file mode 100644 index 0000000..ed2f824 --- /dev/null +++ b/src/components/form/journey/steps/FirstStep.tsx @@ -0,0 +1,65 @@ +import React from "react"; +import { UseFormReturn } from "react-hook-form"; +import { JourneyFormValues } from "../JourneyForm"; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; +import { Input } from "@/components/ui/input"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; + +type FirstStepProps = { + next: () => Promise; + form: UseFormReturn; +}; + +const FirstStep = ({ form, next }: FirstStepProps) => { + return ( +
+

Créez votre propre Parcours !

+

+ Ajouter un titre, une description et une image afin de rednre votre + parcours le plus attrayant possible +

+ ( + + + <> + + + + + + + )} + /> + + ( + + + <> + + + + + + + )} + /> + + {/* TODO: add image file input */} + +
+ ); +}; + +export default FirstStep; diff --git a/src/components/form/journey/steps/SecondStep.tsx b/src/components/form/journey/steps/SecondStep.tsx new file mode 100644 index 0000000..144fba6 --- /dev/null +++ b/src/components/form/journey/steps/SecondStep.tsx @@ -0,0 +1,188 @@ +import React from "react"; +import { UseFormReturn } from "react-hook-form"; +import { JourneyFormValues } from "../JourneyForm"; +import { + FormControl, + FormField, + FormItem, + FormMessage, +} from "@/components/ui/form"; +import { Textarea } from "@/components/ui/textarea"; +import { Button } from "@/components/ui/button"; +import { Label } from "@/components/ui/label"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; + +type SecondStepProps = { + prev: () => void; + next: () => Promise; + form: UseFormReturn; +}; + +const SecondStep = ({ form, next, prev }: SecondStepProps) => { + return ( +
+

Que doit-on savoir sur le parcours ?

+ ( + + + <> + +