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 */}
+
+
+ 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 (
+
+
+
+
+
+
+
+
+
+ );
+};
+
+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 (
+
+
+
+ )}
+
+
+ );
+};
+
+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 (
+
+ );
+};
+
+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 ?
+
(
+
+
+ <>
+
+
+ >
+
+
+
+ )}
+ />
+
+ {/* TO DO: radio fields on physical difficulty and clues difficulty */}
+
+ (
+
+
+
+
+
+ )}
+ />
+
+ (
+
+
+
+
+
+ )}
+ />
+
+ (
+
+
+
+
+
+ )}
+ />
+
+ (
+
+
+
+
+
+ )}
+ />
+
+
+
+
+
+
+ );
+};
+
+export default SecondStep;
diff --git a/src/components/form/journey/steps/Steps.tsx b/src/components/form/journey/steps/Steps.tsx
new file mode 100644
index 0000000..c9be48b
--- /dev/null
+++ b/src/components/form/journey/steps/Steps.tsx
@@ -0,0 +1,63 @@
+"use client";
+
+import { UseFormReturn } from "react-hook-form";
+import { JourneyFormValues } from "../JourneyForm";
+import FirstStep from "./FirstStep";
+import SecondStep from "./SecondStep";
+import ThirdStep from "./ThirdStep";
+import TreasureStep from "./TreasureStep";
+
+import { motion } from "framer-motion";
+
+type StepsProps = {
+ step: number;
+ next: () => Promise;
+ prev: () => void;
+ form: UseFormReturn;
+ dir: "ltr" | "rtl";
+};
+
+const Steps = ({ step, next, prev, form, dir }: StepsProps) => {
+ return (
+ <>
+ {step === 0 && (
+
+
+
+ )}
+ {step === 1 && (
+
+
+
+ )}
+ {step === 2 && (
+
+
+
+ )}
+ {step === 3 && (
+
+
+
+ )}
+ >
+ );
+};
+
+export default Steps;
diff --git a/src/components/form/journey/steps/ThirdStep.tsx b/src/components/form/journey/steps/ThirdStep.tsx
new file mode 100644
index 0000000..40e8e60
--- /dev/null
+++ b/src/components/form/journey/steps/ThirdStep.tsx
@@ -0,0 +1,79 @@
+"use client";
+import { UseFormReturn } from "react-hook-form";
+import { JourneyFormValues } from "../JourneyForm";
+import { Button } from "@/components/ui/button";
+import { useJourneyFormStore } from "@/store/journeyFormStore";
+import { Plus, Edit, Trash } from "lucide-react";
+import BottomSheetModal from "../BottomSheetModal";
+import {
+ FormControl,
+ FormField,
+ FormItem,
+ FormMessage,
+} from "@/components/ui/form";
+import { useEffect } from "react";
+
+type ThirdStepProps = {
+ prev: () => void;
+ next: () => Promise;
+ form: UseFormReturn;
+};
+
+const ThirdStep = ({ form, next, prev }: ThirdStepProps) => {
+ const { steps, showBottomSheet, removeStep, setEditedStep } =
+ useJourneyFormStore();
+
+ useEffect(() => {
+ form.setValue("steps", steps.length ? JSON.stringify(steps) : "");
+ form.trigger("steps");
+ }, [steps]);
+
+ return (
+
+
Quelles sont les étapes ?
+ {steps.map((step, index) => (
+
+
{step.puzzle}
+
{
+ setEditedStep({ ...step, index });
+ showBottomSheet();
+ }}
+ />
+ removeStep(index)} />
+
+ ))}
+
+
(
+
+
+
+
+ )}
+ />
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default ThirdStep;
diff --git a/src/components/form/journey/steps/TreasureStep.tsx b/src/components/form/journey/steps/TreasureStep.tsx
new file mode 100644
index 0000000..208776b
--- /dev/null
+++ b/src/components/form/journey/steps/TreasureStep.tsx
@@ -0,0 +1,58 @@
+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 TreasureStepProps = {
+ prev: () => void;
+ next: () => Promise;
+ form: UseFormReturn;
+};
+
+const TreasureStep = ({ form, next, prev }: TreasureStepProps) => {
+ return (
+
+
Définissez le trésor
+
(
+
+
+ <>
+
+
+ >
+
+
+
+ )}
+ />
+
+
+
+
+
+
+ );
+};
+
+export default TreasureStep;
diff --git a/src/components/map/LeafletMap.tsx b/src/components/map/LeafletMap.tsx
new file mode 100644
index 0000000..65f806f
--- /dev/null
+++ b/src/components/map/LeafletMap.tsx
@@ -0,0 +1,5 @@
+import dynamic from "next/dynamic"; // Composant LeafletMap chargé dynamiquement
+
+const LeafletMap = dynamic(() => import("./Map"), { ssr: false });
+
+export default LeafletMap;
diff --git a/src/components/map/LocationMarker.tsx b/src/components/map/LocationMarker.tsx
new file mode 100644
index 0000000..3bde87b
--- /dev/null
+++ b/src/components/map/LocationMarker.tsx
@@ -0,0 +1,53 @@
+import { addStepSchema } from "@/validators/stepFormSchema";
+import L, { LatLng } from "leaflet";
+import { useState } from "react";
+import { UseFormReturn } from "react-hook-form";
+import { useMapEvents, Marker } from "react-leaflet";
+import { z } from "zod";
+
+type AddStepFormValues = z.infer;
+
+type LocationMarkerProps = {
+ setDragDisabled: (value: boolean) => void;
+ form: UseFormReturn;
+ defaultCoordinates: {
+ latitude: number;
+ longitude: number;
+ } | null;
+};
+
+const LocationMarker = ({
+ setDragDisabled,
+ form,
+ defaultCoordinates,
+}: LocationMarkerProps) => {
+ const defaultPosition = defaultCoordinates
+ ? new L.LatLng(defaultCoordinates.latitude, defaultCoordinates.longitude)
+ : null;
+ const [position, setPosition] = useState(defaultPosition);
+ useMapEvents({
+ click(e) {
+ setPosition(e.latlng);
+ form.setValue("coordinates", `${e.latlng.lat};${e.latlng.lng}`);
+ form.trigger("coordinates");
+ },
+ dragstart() {
+ setDragDisabled(true);
+ },
+ dragend() {
+ setDragDisabled(false);
+ },
+ });
+
+ const myIcon = L.icon({
+ iconUrl: "/img/pin.png",
+ iconSize: [30, 30],
+ iconAnchor: [15, 30],
+ });
+
+ return position === null ? null : (
+
+ );
+};
+
+export default LocationMarker;
diff --git a/src/components/map/Map.tsx b/src/components/map/Map.tsx
new file mode 100644
index 0000000..1dfcfe6
--- /dev/null
+++ b/src/components/map/Map.tsx
@@ -0,0 +1,49 @@
+"use client";
+
+import { useJourneyFormStore } from "@/store/journeyFormStore";
+import React from "react";
+import { UseFormReturn } from "react-hook-form";
+import { MapContainer, ZoomControl, TileLayer } from "react-leaflet";
+import LocationMarker from "./LocationMarker";
+import { addStepSchema } from "@/validators/stepFormSchema";
+import { z } from "zod";
+
+type AddStepFormValues = z.infer;
+
+type MapProps = {
+ setDragDisabled: (value: boolean) => void;
+ form: UseFormReturn;
+};
+
+const Map = ({ setDragDisabled, form }: MapProps) => {
+ const { editedStep } = useJourneyFormStore();
+ return (
+
+
+
+
+
+ );
+};
+
+export default Map;
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
index b9cd8f9..0c50212 100644
--- a/src/components/ui/button.tsx
+++ b/src/components/ui/button.tsx
@@ -2,7 +2,7 @@ import * as React from "react";
import { Slot } from "@radix-ui/react-slot";
import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils";
+import { cn } from "@/lib/tailwindUtils";
const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50",
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
index 2f02434..bab7515 100644
--- a/src/components/ui/calendar.tsx
+++ b/src/components/ui/calendar.tsx
@@ -1,13 +1,13 @@
-"use client"
+"use client";
-import * as React from "react"
-import { ChevronLeft, ChevronRight } from "lucide-react"
-import { DayPicker } from "react-day-picker"
+import * as React from "react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
+import { DayPicker } from "react-day-picker";
-import { cn } from "@/lib/utils"
-import { buttonVariants } from "@/components/ui/button"
+import { cn } from "@/lib/tailwindUtils";
+import { buttonVariants } from "@/components/ui/button";
-export type CalendarProps = React.ComponentProps
+export type CalendarProps = React.ComponentProps;
function Calendar({
className,
@@ -59,8 +59,8 @@ function Calendar({
}}
{...props}
/>
- )
+ );
}
-Calendar.displayName = "Calendar"
+Calendar.displayName = "Calendar";
-export { Calendar }
+export { Calendar };
diff --git a/src/components/ui/form.tsx b/src/components/ui/form.tsx
index 4603f8b..4501fd5 100644
--- a/src/components/ui/form.tsx
+++ b/src/components/ui/form.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { Slot } from "@radix-ui/react-slot"
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { Slot } from "@radix-ui/react-slot";
import {
Controller,
ControllerProps,
@@ -8,23 +8,23 @@ import {
FieldValues,
FormProvider,
useFormContext,
-} from "react-hook-form"
+} from "react-hook-form";
-import { cn } from "@/lib/utils"
-import { Label } from "@/components/ui/label"
+import { cn } from "@/lib/tailwindUtils";
+import { Label } from "@/components/ui/label";
-const Form = FormProvider
+const Form = FormProvider;
type FormFieldContextValue<
TFieldValues extends FieldValues = FieldValues,
TName extends FieldPath = FieldPath
> = {
- name: TName
-}
+ name: TName;
+};
const FormFieldContext = React.createContext(
{} as FormFieldContextValue
-)
+);
const FormField = <
TFieldValues extends FieldValues = FieldValues,
@@ -36,21 +36,21 @@ const FormField = <
- )
-}
+ );
+};
const useFormField = () => {
- const fieldContext = React.useContext(FormFieldContext)
- const itemContext = React.useContext(FormItemContext)
- const { getFieldState, formState } = useFormContext()
+ const fieldContext = React.useContext(FormFieldContext);
+ const itemContext = React.useContext(FormItemContext);
+ const { getFieldState, formState } = useFormContext();
- const fieldState = getFieldState(fieldContext.name, formState)
+ const fieldState = getFieldState(fieldContext.name, formState);
if (!fieldContext) {
- throw new Error("useFormField should be used within ")
+ throw new Error("useFormField should be used within ");
}
- const { id } = itemContext
+ const { id } = itemContext;
return {
id,
@@ -59,36 +59,36 @@ const useFormField = () => {
formDescriptionId: `${id}-form-item-description`,
formMessageId: `${id}-form-item-message`,
...fieldState,
- }
-}
+ };
+};
type FormItemContextValue = {
- id: string
-}
+ id: string;
+};
const FormItemContext = React.createContext(
{} as FormItemContextValue
-)
+);
const FormItem = React.forwardRef<
HTMLDivElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const id = React.useId()
+ const id = React.useId();
return (
- )
-})
-FormItem.displayName = "FormItem"
+ );
+});
+FormItem.displayName = "FormItem";
const FormLabel = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ className, ...props }, ref) => {
- const { error, formItemId } = useFormField()
+ const { error, formItemId } = useFormField();
return (
- )
-})
-FormLabel.displayName = "FormLabel"
+ );
+});
+FormLabel.displayName = "FormLabel";
const FormControl = React.forwardRef<
React.ElementRef,
React.ComponentPropsWithoutRef
>(({ ...props }, ref) => {
- const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
+ const { error, formItemId, formDescriptionId, formMessageId } =
+ useFormField();
return (
- )
-})
-FormControl.displayName = "FormControl"
+ );
+});
+FormControl.displayName = "FormControl";
const FormDescription = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes
>(({ className, ...props }, ref) => {
- const { formDescriptionId } = useFormField()
+ const { formDescriptionId } = useFormField();
return (
- )
-})
-FormDescription.displayName = "FormDescription"
+ );
+});
+FormDescription.displayName = "FormDescription";
const FormMessage = React.forwardRef<
HTMLParagraphElement,
React.HTMLAttributes
>(({ className, children, ...props }, ref) => {
- const { error, formMessageId } = useFormField()
- const body = error ? String(error?.message) : children
+ const { error, formMessageId } = useFormField();
+ const body = error ? String(error?.message) : children;
if (!body) {
- return null
+ return null;
}
return (
@@ -160,9 +161,9 @@ const FormMessage = React.forwardRef<
>
{body}
- )
-})
-FormMessage.displayName = "FormMessage"
+ );
+});
+FormMessage.displayName = "FormMessage";
export {
useFormField,
@@ -173,4 +174,4 @@ export {
FormDescription,
FormMessage,
FormField,
-}
+};
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
index 677d05f..aec2612 100644
--- a/src/components/ui/input.tsx
+++ b/src/components/ui/input.tsx
@@ -1,6 +1,6 @@
-import * as React from "react"
+import * as React from "react";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/tailwindUtils";
export interface InputProps
extends React.InputHTMLAttributes {}
@@ -17,9 +17,9 @@ const Input = React.forwardRef(
ref={ref}
{...props}
/>
- )
+ );
}
-)
-Input.displayName = "Input"
+);
+Input.displayName = "Input";
-export { Input }
+export { Input };
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
index 5341821..7a5d530 100644
--- a/src/components/ui/label.tsx
+++ b/src/components/ui/label.tsx
@@ -1,14 +1,14 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as LabelPrimitive from "@radix-ui/react-label"
-import { cva, type VariantProps } from "class-variance-authority"
+import * as React from "react";
+import * as LabelPrimitive from "@radix-ui/react-label";
+import { cva, type VariantProps } from "class-variance-authority";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/tailwindUtils";
const labelVariants = cva(
"text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
-)
+);
const Label = React.forwardRef<
React.ElementRef,
@@ -20,7 +20,7 @@ const Label = React.forwardRef<
className={cn(labelVariants(), className)}
{...props}
/>
-))
-Label.displayName = LabelPrimitive.Root.displayName
+));
+Label.displayName = LabelPrimitive.Root.displayName;
-export { Label }
+export { Label };
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
index a0ec48b..f8fe796 100644
--- a/src/components/ui/popover.tsx
+++ b/src/components/ui/popover.tsx
@@ -1,13 +1,13 @@
-"use client"
+"use client";
-import * as React from "react"
-import * as PopoverPrimitive from "@radix-ui/react-popover"
+import * as React from "react";
+import * as PopoverPrimitive from "@radix-ui/react-popover";
-import { cn } from "@/lib/utils"
+import { cn } from "@/lib/tailwindUtils";
-const Popover = PopoverPrimitive.Root
+const Popover = PopoverPrimitive.Root;
-const PopoverTrigger = PopoverPrimitive.Trigger
+const PopoverTrigger = PopoverPrimitive.Trigger;
const PopoverContent = React.forwardRef<
React.ElementRef,
@@ -25,7 +25,7 @@ const PopoverContent = React.forwardRef<
{...props}
/>
-))
-PopoverContent.displayName = PopoverPrimitive.Content.displayName
+));
+PopoverContent.displayName = PopoverPrimitive.Content.displayName;
-export { Popover, PopoverTrigger, PopoverContent }
+export { Popover, PopoverTrigger, PopoverContent };
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 0000000..6ffe7fb
--- /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/tailwindUtils";
+
+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,
+};
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..c9ba003
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,24 @@
+import * as React from "react";
+
+import { cn } from "@/lib/tailwindUtils";
+
+export interface TextareaProps
+ extends React.TextareaHTMLAttributes {}
+
+const Textarea = React.forwardRef(
+ ({ className, ...props }, ref) => {
+ return (
+
+ );
+ }
+);
+Textarea.displayName = "Textarea";
+
+export { Textarea };
diff --git a/src/lib/utils.ts b/src/lib/tailwindUtils.ts
similarity index 100%
rename from src/lib/utils.ts
rename to src/lib/tailwindUtils.ts
diff --git a/src/store/example.ts b/src/store/example.ts
deleted file mode 100644
index eddb2d7..0000000
--- a/src/store/example.ts
+++ /dev/null
@@ -1,13 +0,0 @@
-import { create } from "zustand";
-
-interface ExampleStore {
- count: number;
- increment: () => void;
- decrement: () => void;
-}
-
-export const useExampleStore = create((set) => ({
- count: 0,
- increment: () => set((state) => ({ count: state.count + 1 })),
- decrement: () => set((state) => ({ count: state.count - 1 })),
-}));
diff --git a/src/store/journeyFormStore.ts b/src/store/journeyFormStore.ts
new file mode 100644
index 0000000..abd915e
--- /dev/null
+++ b/src/store/journeyFormStore.ts
@@ -0,0 +1,71 @@
+import { create } from "zustand";
+
+interface AddStepFormValues {
+ puzzle: string;
+ answer: string;
+ hint: string;
+ coordinates: {
+ latitude: number;
+ longitude: number;
+ };
+}
+
+interface EditedStep extends AddStepFormValues {
+ index: number;
+}
+
+interface JourneyFormStore {
+ isVisible: boolean;
+ showModal: () => void;
+ hideModal: () => void;
+ bottomSheetVisible: boolean;
+ showBottomSheet: () => void;
+ hideBottomSheet: () => void;
+ steps: AddStepFormValues[];
+ addStep: (step: AddStepFormValues) => void;
+ removeStep: (index: number) => void;
+ editStep: (step: AddStepFormValues, index: number) => void;
+ editedStep: EditedStep | null;
+ setEditedStep: (step: EditedStep | null) => void;
+}
+
+export const useJourneyFormStore = create((set) => ({
+ isVisible: false,
+ showModal: () => {
+ const body = document.querySelector("body");
+ body?.classList.add("overflow-hidden");
+ set({ isVisible: true });
+ },
+ hideModal: () => {
+ const body = document.querySelector("body");
+ body?.classList.remove("overflow-hidden");
+ set({ isVisible: false });
+ },
+ bottomSheetVisible: false,
+ showBottomSheet: () => {
+ set({ bottomSheetVisible: true });
+ },
+ hideBottomSheet: () => {
+ set({ bottomSheetVisible: false });
+ },
+ steps: [],
+ addStep: (step) => {
+ set((state) => ({ steps: [...state.steps, step] }));
+ },
+ removeStep: (index) => {
+ set((state) => ({
+ steps: state.steps.filter((_, loopIndex) => loopIndex !== index),
+ }));
+ },
+ editStep: (step, index) => {
+ set((state) => ({
+ steps: state.steps.map((currentStep, loopIndex) =>
+ loopIndex === index ? step : currentStep
+ ),
+ }));
+ },
+ editedStep: null,
+ setEditedStep: (step) => {
+ set({ editedStep: step });
+ },
+}));
diff --git a/src/validators/journeyFormSchema.ts b/src/validators/journeyFormSchema.ts
new file mode 100644
index 0000000..28dcde2
--- /dev/null
+++ b/src/validators/journeyFormSchema.ts
@@ -0,0 +1,66 @@
+import { z } from "zod";
+
+export const firstStepSchema = z.object({
+ title: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ description: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ // image: z.string(),
+});
+
+export const secondStepSchema = z.object({
+ requirement: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ // physicalDifficulty: z.enum(["easy", "medium", "hard"], {
+ // required_error: "Vous devez sélectionner une option",
+ // }),
+ // cluesDifficulty: z.enum(["easy", "medium", "hard"], {
+ // required_error: "Vous devez sélectionner une option",
+ // }),
+ mobilityImpaired: z.enum(
+ ["undefined", "unaccessible", "partiallyAccessible", "accessible"],
+ {
+ required_error: "Vous devez sélectionner une option",
+ }
+ ),
+ partiallySighted: z.enum(
+ ["undefined", "unaccessible", "partiallyAccessible", "accessible"],
+ {
+ required_error: "Vous devez sélectionner une option",
+ }
+ ),
+ partiallyDeaf: z.enum(
+ ["undefined", "unaccessible", "partiallyAccessible", "accessible"],
+ {
+ required_error: "Vous devez sélectionner une option",
+ }
+ ),
+ cognitivelyImpaired: z.enum(
+ ["undefined", "unaccessible", "partiallyAccessible", "accessible"],
+ {
+ required_error: "Vous devez sélectionner une option",
+ }
+ ),
+});
+
+export const thirdStepSchema = z.object({
+ steps: z
+ .string()
+ .min(1, { message: "Vous devez ajouter au moins une étape" }),
+});
+
+export const forthStepSchema = z.object({
+ treasure: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+});
+
+export const journeyFormSchema = z.object({
+ ...firstStepSchema.shape,
+ ...secondStepSchema.shape,
+ ...thirdStepSchema.shape,
+ ...forthStepSchema.shape,
+});
diff --git a/src/validators/stepFormSchema.ts b/src/validators/stepFormSchema.ts
new file mode 100644
index 0000000..90af453
--- /dev/null
+++ b/src/validators/stepFormSchema.ts
@@ -0,0 +1,90 @@
+import { z } from "zod";
+
+export const addStepSchema = z.object({
+ puzzle: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ answer: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ hint: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ // picturePuzzle: z.string({ required_error: "Ce champ est requis" }).optional(),
+ // pictureHint: z.string({ required_error: "Ce champ est requis" }).optional(),
+ coordinates: z
+ .string({ required_error: "Ce champ est requis" })
+ .min(1, { message: "Ce champ est requis" }),
+});
+
+export const addStepArraySchema = z
+ .object({
+ puzzle: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ answer: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ hint: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ // picturePuzzle: z.string({ required_error: "Ce champ est requis" }).optional(),
+ // pictureHint: z.string({ required_error: "Ce champ est requis" }).optional(),
+ coordinates: z.object({
+ latitude: z.number({ required_error: "Ce champ est requis" }),
+ longitude: z.number({ required_error: "Ce champ est requis" }),
+ }),
+ })
+ .array();
+
+export const addStepAPISchema = z
+ .object({
+ puzzle: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ answer: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ hint: z.string({ required_error: "Ce champ est requis" }).min(1, {
+ message: "Ce champ est requis",
+ }),
+ // picturePuzzle: z.string({ required_error: "Ce champ est requis" }).optional(),
+ // pictureHint: z.string({ required_error: "Ce champ est requis" }).optional(),
+ latitude: z
+ .number({ required_error: "Ce champ est requis" })
+ .min(-90, { message: "La latitude doit être entre -90 et 90" })
+ .max(90, { message: "La latitude doit être entre -90 et 90" }),
+ longitude: z
+ .number({ required_error: "Ce champ est requis" })
+ .min(-180, { message: "La longitude doit être entre -180 et 180" })
+ .max(180, { message: "La longitude doit être entre -180 et 180" }),
+ })
+ .array();
+
+// const coordinates = data.coordinates.split(";");
+
+// // check if we have exactly two parts
+// if (coordinates.length !== 2) {
+// return false;
+// }
+
+// // parse float
+// const latitude = parseFloat(coordinates[0]);
+// const longitude = parseFloat(coordinates[1]);
+
+// // check if both are numbers
+// if (isNaN(latitude) || isNaN(longitude)) {
+// return false;
+// }
+
+// // check latitude range
+// if (latitude < -90 || latitude > 90) {
+// return false;
+// }
+
+// // check longitude range
+// if (longitude < -180 || longitude > 180) {
+// return false;
+// }
+
+// return true;