diff --git a/components.json b/components.json
new file mode 100644
index 0000000..edcaef2
--- /dev/null
+++ b/components.json
@@ -0,0 +1,22 @@
+{
+ "$schema": "https://ui.shadcn.com/schema.json",
+ "style": "new-york",
+ "rsc": true,
+ "tsx": true,
+ "tailwind": {
+ "config": "",
+ "css": "src/app/globals.css",
+ "baseColor": "neutral",
+ "cssVariables": true,
+ "prefix": ""
+ },
+ "iconLibrary": "lucide",
+ "aliases": {
+ "components": "@/components",
+ "utils": "@/lib/utils",
+ "ui": "@/components/ui",
+ "lib": "@/lib",
+ "hooks": "@/hooks"
+ },
+ "registries": {}
+}
diff --git a/debug.log b/debug.log
new file mode 100644
index 0000000..2cbc2a3
--- /dev/null
+++ b/debug.log
@@ -0,0 +1 @@
+[0212/070717.490:ERROR:third_party\crashpad\crashpad\util\win\registration_protocol_win.cc:108] CreateFile: The system cannot find the file specified. (0x2)
diff --git a/next.config.ts b/next.config.ts
index e9ffa30..3c6d3aa 100644
--- a/next.config.ts
+++ b/next.config.ts
@@ -1,6 +1,16 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {
+ images: {
+ remotePatterns: [
+ {
+ protocol: 'http',
+ hostname: 'localhost',
+ port: '4000',
+ pathname: '/uploads/**',
+ },
+ ],
+ },
/* config options here */
};
diff --git a/package-lock.json b/package-lock.json
index e6de71a..ddaf1ec 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -8,19 +8,48 @@
"name": "frontend-panel",
"version": "0.1.0",
"dependencies": {
+ "@emotion/styled": "^11.14.1",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@tanstack/react-query": "^5.90.12",
+ "axios": "^1.13.2",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
+ "formik": "^2.4.9",
+ "jsonwebtoken": "^9.0.3",
+ "jwt-decode": "^4.0.0",
+ "lucide-react": "^0.561.0",
"next": "15.5.7",
+ "next-nprogress-bar": "^2.4.7",
+ "quill": "^2.0.3",
"react": "19.1.0",
- "react-dom": "19.1.0"
+ "react-day-picker": "^9.13.0",
+ "react-dom": "19.1.0",
+ "react-dropzone": "^14.3.8",
+ "react-toastify": "^11.0.5",
+ "tailwind-merge": "^3.4.0",
+ "yup": "^1.7.1"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@tailwindcss/postcss": "^4",
+ "@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.5.7",
"tailwindcss": "^4",
+ "tw-animate-css": "^1.4.0",
"typescript": "^5"
}
},
@@ -37,6 +66,151 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/@babel/code-frame": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
+ "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.27.1",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.28.5.tgz",
+ "integrity": "sha512-3EwLFhZ38J4VyIP6WNtt2kUdW9dokXA9Cr4IVIFHuCpZ3H8/YFOl5JjZHisrn1fATPBmKKqXzDFvh9fUwHz6CQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/parser": "^7.28.5",
+ "@babel/types": "^7.28.5",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.27.1.tgz",
+ "integrity": "sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/traverse": "^7.27.1",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
+ "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/types": "^7.28.5"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.28.4",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz",
+ "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.27.2",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
+ "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/parser": "^7.27.2",
+ "@babel/types": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.28.5.tgz",
+ "integrity": "sha512-TCCj4t55U90khlYkVV/0TfkJkAkUg3jZFA3Neb7unZT8CPok7iiRfaX0F+WnqWqt7OxhOn0uBKXCw4lbL8W0aQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.27.1",
+ "@babel/generator": "^7.28.5",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.28.5",
+ "@babel/template": "^7.27.2",
+ "@babel/types": "^7.28.5",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
+ "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@date-fns/tz": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/@date-fns/tz/-/tz-1.4.1.tgz",
+ "integrity": "sha512-P5LUNhtbj6YfI3iJjw5EL9eUAG6OitD0W3fWQcpQjDRc/QIsL0tRNuO1PcDvPccWL1fSTXXdE1ds+l95DV/OFA==",
+ "license": "MIT"
+ },
"node_modules/@emnapi/core": {
"version": "1.7.1",
"resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.7.1.tgz",
@@ -70,6 +244,153 @@
"tslib": "^2.4.0"
}
},
+ "node_modules/@emotion/babel-plugin": {
+ "version": "11.13.5",
+ "resolved": "https://registry.npmjs.org/@emotion/babel-plugin/-/babel-plugin-11.13.5.tgz",
+ "integrity": "sha512-pxHCpT2ex+0q+HH91/zsdHkw/lXd468DIN2zvfvLtPKLLMo6gQj7oLObq8PhkrxOZb/gGCq03S3Z7PDhS8pduQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.16.7",
+ "@babel/runtime": "^7.18.3",
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/serialize": "^1.3.3",
+ "babel-plugin-macros": "^3.1.0",
+ "convert-source-map": "^1.5.0",
+ "escape-string-regexp": "^4.0.0",
+ "find-root": "^1.1.0",
+ "source-map": "^0.5.7",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/cache": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/cache/-/cache-11.14.0.tgz",
+ "integrity": "sha512-L/B1lc/TViYk4DcpGxtAVbx0ZyiKM5ktoIyafGkH6zg/tj+mA+NE//aPYKG0k8kCHSHVJrpLpcAlOBEXQ3SavA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/sheet": "^1.4.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "stylis": "4.2.0"
+ }
+ },
+ "node_modules/@emotion/hash": {
+ "version": "0.9.2",
+ "resolved": "https://registry.npmjs.org/@emotion/hash/-/hash-0.9.2.tgz",
+ "integrity": "sha512-MyqliTZGuOm3+5ZRSaaBGP3USLw6+EGykkwZns2EPC5g8jJ4z9OrdZY9apkl3+UP9+sdz76YYkwCKP5gh8iY3g==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/is-prop-valid": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/is-prop-valid/-/is-prop-valid-1.4.0.tgz",
+ "integrity": "sha512-QgD4fyscGcbbKwJmqNvUMSE02OsHUa+lAWKdEUIJKgqe5IwRSKd7+KhibEWdaKwgjLj0DRSHA9biAIqGBk05lw==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/memoize": "^0.9.0"
+ }
+ },
+ "node_modules/@emotion/memoize": {
+ "version": "0.9.0",
+ "resolved": "https://registry.npmjs.org/@emotion/memoize/-/memoize-0.9.0.tgz",
+ "integrity": "sha512-30FAj7/EoJ5mwVPOWhAyCX+FPfMDrVecJAM+Iw9NRoSl4BBAQeqj4cApHHUXOVvIPgLVDsCFoz/hGD+5QQD1GQ==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/react": {
+ "version": "11.14.0",
+ "resolved": "https://registry.npmjs.org/@emotion/react/-/react-11.14.0.tgz",
+ "integrity": "sha512-O000MLDBDdk/EohJPFUqvnp4qnHeYkVP5B0xEG0D/L7cOKP9kefu2DXn8dj74cQfsEzUqh+sr1RzFqiL1o+PpA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/cache": "^11.14.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2",
+ "@emotion/weak-memoize": "^0.4.0",
+ "hoist-non-react-statics": "^3.3.1"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/serialize": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@emotion/serialize/-/serialize-1.3.3.tgz",
+ "integrity": "sha512-EISGqt7sSNWHGI76hC7x1CksiXPahbxEOrC5RjmFRJTqLyEK9/9hZvBbiYn70dw4wuwMKiEMCUlR6ZXTSWQqxA==",
+ "license": "MIT",
+ "dependencies": {
+ "@emotion/hash": "^0.9.2",
+ "@emotion/memoize": "^0.9.0",
+ "@emotion/unitless": "^0.10.0",
+ "@emotion/utils": "^1.4.2",
+ "csstype": "^3.0.2"
+ }
+ },
+ "node_modules/@emotion/sheet": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/sheet/-/sheet-1.4.0.tgz",
+ "integrity": "sha512-fTBW9/8r2w3dXWYM4HCB1Rdp8NLibOw2+XELH5m5+AkWiL/KqYX6dc0kKYlaYyKjrQ6ds33MCdMPEwgs2z1rqg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/styled": {
+ "version": "11.14.1",
+ "resolved": "https://registry.npmjs.org/@emotion/styled/-/styled-11.14.1.tgz",
+ "integrity": "sha512-qEEJt42DuToa3gurlH4Qqc1kVpNq8wO8cJtDzU46TjlzWjDlsVyevtYCRijVq3SrHsROS+gVQ8Fnea108GnKzw==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.18.3",
+ "@emotion/babel-plugin": "^11.13.5",
+ "@emotion/is-prop-valid": "^1.3.0",
+ "@emotion/serialize": "^1.3.3",
+ "@emotion/use-insertion-effect-with-fallbacks": "^1.2.0",
+ "@emotion/utils": "^1.4.2"
+ },
+ "peerDependencies": {
+ "@emotion/react": "^11.0.0-rc.0",
+ "react": ">=16.8.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@emotion/unitless": {
+ "version": "0.10.0",
+ "resolved": "https://registry.npmjs.org/@emotion/unitless/-/unitless-0.10.0.tgz",
+ "integrity": "sha512-dFoMUuQA20zvtVTuxZww6OHoJYgrzfKM1t52mVySDJnMSEa08ruEvdYQbhvyu6soU+NeLVd3yKfTfT0NeV6qGg==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/use-insertion-effect-with-fallbacks": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@emotion/use-insertion-effect-with-fallbacks/-/use-insertion-effect-with-fallbacks-1.2.0.tgz",
+ "integrity": "sha512-yJMtVdH59sxi/aVJBpk9FQq+OR8ll5GT8oWd57UpeaKEVGab41JWaCFA7FRLoMLloOZF/c/wsPoe+bfGmRKgDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
+ "node_modules/@emotion/utils": {
+ "version": "1.4.2",
+ "resolved": "https://registry.npmjs.org/@emotion/utils/-/utils-1.4.2.tgz",
+ "integrity": "sha512-3vLclRofFziIa3J2wDh9jjbkUz9qk5Vi3IZ/FSTKViB0k+ef0fPV7dYrUIugbgupYDx7v9ud/SjrtEP8Y4xLoA==",
+ "license": "MIT"
+ },
+ "node_modules/@emotion/weak-memoize": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/@emotion/weak-memoize/-/weak-memoize-0.4.0.tgz",
+ "integrity": "sha512-snKqtPW01tN0ui7yu9rGv69aJXr/a/Ywvl11sUjNtEcRc+ng/mQriFL0wLXMef74iHa/EkftbDzU9F8iFbH+zg==",
+ "license": "MIT"
+ },
"node_modules/@eslint-community/eslint-utils": {
"version": "4.9.0",
"resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.0.tgz",
@@ -214,6 +535,44 @@
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
+ "node_modules/@floating-ui/core": {
+ "version": "1.7.3",
+ "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.3.tgz",
+ "integrity": "sha512-sGnvb5dmrJaKEZ+LDIpguvdX3bDlEllmv4/ClQ9awcmCZrlx5jQyyMWFM5kBI+EyNOCDDiKk8il0zeuX3Zlg/w==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/dom": {
+ "version": "1.7.4",
+ "resolved": "https://registry.npmjs.org/@floating-ui/dom/-/dom-1.7.4.tgz",
+ "integrity": "sha512-OOchDgh4F2CchOX94cRVqhvy7b3AFb+/rQXyswmzmGakRfkMgoWVjfnLWkRirfLEfuD4ysVW16eXzwt3jHIzKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/core": "^1.7.3",
+ "@floating-ui/utils": "^0.2.10"
+ }
+ },
+ "node_modules/@floating-ui/react-dom": {
+ "version": "2.1.6",
+ "resolved": "https://registry.npmjs.org/@floating-ui/react-dom/-/react-dom-2.1.6.tgz",
+ "integrity": "sha512-4JX6rEatQEvlmgU80wZyq9RT96HZJa88q8hp0pBd+LrczeDI4o6uA2M+uvxngVHo4Ihr8uibXxH6+70zhAFrVw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/dom": "^1.7.4"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0",
+ "react-dom": ">=16.8.0"
+ }
+ },
+ "node_modules/@floating-ui/utils": {
+ "version": "0.2.10",
+ "resolved": "https://registry.npmjs.org/@floating-ui/utils/-/utils-0.2.10.tgz",
+ "integrity": "sha512-aGTxbpbg8/b5JfU1HXSrbH3wXZuLPJcNEcZQFMxLs3oSzgtVu6nFPkbbGGUvBcUjKV2YyB9Wxxabo+HEH9tcRQ==",
+ "license": "MIT"
+ },
"node_modules/@humanfs/core": {
"version": "0.19.1",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz",
@@ -736,7 +1095,6 @@
"version": "0.3.13",
"resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
"integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/sourcemap-codec": "^1.5.0",
@@ -758,7 +1116,6 @@
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
"integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6.0.0"
@@ -768,14 +1125,12 @@
"version": "1.5.5",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
"integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
- "dev": true,
"license": "MIT"
},
"node_modules/@jridgewell/trace-mapping": {
"version": "0.3.31",
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
"integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"@jridgewell/resolve-uri": "^3.1.0",
@@ -987,6 +1342,1458 @@
"node": ">=12.4.0"
}
},
+ "node_modules/@radix-ui/number": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz",
+ "integrity": "sha512-MkKCwxlXTgz6CFoJx3pCwn07GKp36+aZyu/u2Ln2VrA5DcdyCZkASEDBTd8x5whTQQL5CiYf4prXKLcgQdv29g==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/primitive": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz",
+ "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==",
+ "license": "MIT"
+ },
+ "node_modules/@radix-ui/react-arrow": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-arrow/-/react-arrow-1.1.7.tgz",
+ "integrity": "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-arrow/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-checkbox/-/react-checkbox-1.3.3.tgz",
+ "integrity": "sha512-wBbpv+NQftHDdG86Qc0pIyXk5IR3tM8Vd0nWLKDcX8nNn4nXFOFwsKuqw2okA/1D/mpaAkmuyndrPJTYDNZtFw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-checkbox/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz",
+ "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-collection/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-compose-refs": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-compose-refs/-/react-compose-refs-1.1.2.tgz",
+ "integrity": "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-context": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz",
+ "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz",
+ "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dialog/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-direction": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz",
+ "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz",
+ "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-escape-keydown": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dismissable-layer/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-dropdown-menu/-/react-dropdown-menu-2.1.16.tgz",
+ "integrity": "sha512-1PLGQEynI/3OX/ftV54COn+3Sud/Mn8vALg2rWnBLnRaGtJDduNW/22XjlGgPdpcIbiQxjKtb7BkcjP00nqfJw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-menu": "2.1.16",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-dropdown-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-guards": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz",
+ "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope": {
+ "version": "1.1.7",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz",
+ "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-focus-scope/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-id": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz",
+ "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-label": {
+ "version": "2.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-label/-/react-label-2.1.8.tgz",
+ "integrity": "sha512-FmXs37I6hSBVDlO4y764TNz1rLgKwjJMQ0EGte6F3Cb3f4bIuHB/iLa/8I9VKkmOy+gNHq8rql3j686ACVV21A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu": {
+ "version": "2.1.16",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-menu/-/react-menu-2.1.16.tgz",
+ "integrity": "sha512-72F2T+PLlphrqLcAotYPp0uJMr5SjP5SL01wfEspJbru5Zs5vQaSHb4VB3ZMJPimgHHCHG7gMOeOB9H3Hdmtxg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-roving-focus": "1.1.11",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-menu/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover": {
+ "version": "1.1.15",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popover/-/react-popover-1.1.15.tgz",
+ "integrity": "sha512-kr0X2+6Yy/vJzLYJUPCZEc8SfQcf+1COFoAqauJm74umQhta9M7lNJHP7QQS3vkvcGLQUbWpMzwrXYwrYztHKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-presence": "1.1.5",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popover/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-popper/-/react-popper-1.2.8.tgz",
+ "integrity": "sha512-0NJQ4LFFUuWkE7Oxf0htBKS6zLkkjBH+hM1uk7Ng705ReR8m/uelduy1DBo0PyBXPKVnBA6YBlU94MBGXrSBCw==",
+ "license": "MIT",
+ "dependencies": {
+ "@floating-ui/react-dom": "^2.0.0",
+ "@radix-ui/react-arrow": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-rect": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1",
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-popper/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal": {
+ "version": "1.1.9",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz",
+ "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-portal/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-presence": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz",
+ "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.4.tgz",
+ "integrity": "sha512-9hQc4+GNVtJAIEPEqlYqW5RiYdrr8ea5XQ0ZOnD6fgru+83kqT15mq2OCcbe8KnjRZl5vF3ks69AKz3kh1jrhg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-progress/-/react-progress-1.1.8.tgz",
+ "integrity": "sha512-+gISHcSPUJ7ktBy9RnTqbdKW78bcGke3t6taawyZ71pio1JewwGSJizycs7rLhGTvMJYCQB1DBK4KQsxs7U8dA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-context": "1.1.3",
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-progress/node_modules/@radix-ui/react-context": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.3.tgz",
+ "integrity": "sha512-ieIFACdMpYfMEjF0rEf5KLvfVyIkOz6PDGyNnP+u+4xQ6jny3VCgA4OgXOwNx2aUkxn8zx9fiVcM8CfFYv9Lxw==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz",
+ "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-roving-focus/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select": {
+ "version": "2.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-select/-/react-select-2.2.6.tgz",
+ "integrity": "sha512-I30RydO+bnn2PQztvo25tswPH+wFBjehVGtmagkU78yMdwTwVf12wnAOF+AeP8S2N8xD+5UPbGhkUfPyvT+mwQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/number": "1.1.1",
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-collection": "1.1.7",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-direction": "1.1.1",
+ "@radix-ui/react-dismissable-layer": "1.1.11",
+ "@radix-ui/react-focus-guards": "1.1.3",
+ "@radix-ui/react-focus-scope": "1.1.7",
+ "@radix-ui/react-id": "1.1.1",
+ "@radix-ui/react-popper": "1.2.8",
+ "@radix-ui/react-portal": "1.1.9",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-slot": "1.2.3",
+ "@radix-ui/react-use-callback-ref": "1.1.1",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-visually-hidden": "1.2.3",
+ "aria-hidden": "^1.2.4",
+ "react-remove-scroll": "^2.6.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-select/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-separator": {
+ "version": "1.1.8",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-separator/-/react-separator-1.1.8.tgz",
+ "integrity": "sha512-sDvqVY4itsKwwSMEe0jtKgfTh+72Sy3gPmQpjqcQneqQ4PFmr/1I0YA+2/puilhggCe2gJcx5EBAYFkWkdpa5g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.4"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-slot": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.4.tgz",
+ "integrity": "sha512-Jl+bCv8HxKnlTLVrcDE8zTMJ09R9/ukw4qBs/oZClOfoQk/cOTbDn+NceXfV7j09YPVQUryJPHurafcSg6EVKA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-switch/-/react-switch-1.2.6.tgz",
+ "integrity": "sha512-bByzr1+ep1zk4VubeEVViV592vu2lHE2BZY5OnzehZqOOgogN80+mNtCqPkhn2gklJqOpxWgPoYTSnhBCqpOXQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/primitive": "1.1.3",
+ "@radix-ui/react-compose-refs": "1.1.2",
+ "@radix-ui/react-context": "1.1.2",
+ "@radix-ui/react-primitive": "2.1.3",
+ "@radix-ui/react-use-controllable-state": "1.2.2",
+ "@radix-ui/react-use-previous": "1.1.1",
+ "@radix-ui/react-use-size": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-switch/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-callback-ref": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz",
+ "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-controllable-state": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz",
+ "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-effect-event": "0.0.2",
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-effect-event": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz",
+ "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-escape-keydown": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz",
+ "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-callback-ref": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-layout-effect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz",
+ "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-previous": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-previous/-/react-use-previous-1.1.1.tgz",
+ "integrity": "sha512-2dHfToCj/pzca2Ck724OZ5L0EVrr3eHRNsG/b3xQJLA2hZpVCS99bLAX+hm1IHXDEnzU6by5z/5MIY794/a8NQ==",
+ "license": "MIT",
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-rect/-/react-use-rect-1.1.1.tgz",
+ "integrity": "sha512-QTYuDesS0VtuHNNvMh+CjlKJ4LJickCMUAqjlE3+j8w+RlRpwyX3apEQKGFzbZGdo7XNG1tXa+bQqIE7HIXT2w==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/rect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-use-size": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-use-size/-/react-use-size-1.1.1.tgz",
+ "integrity": "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-use-layout-effect": "1.1.1"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-visually-hidden/-/react-visually-hidden-1.2.3.tgz",
+ "integrity": "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-primitive": "2.1.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-primitive": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz",
+ "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-slot": "1.2.3"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "@types/react-dom": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
+ "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ },
+ "@types/react-dom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/react-visually-hidden/node_modules/@radix-ui/react-slot": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz",
+ "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==",
+ "license": "MIT",
+ "dependencies": {
+ "@radix-ui/react-compose-refs": "1.1.2"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@radix-ui/rect": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@radix-ui/rect/-/rect-1.1.1.tgz",
+ "integrity": "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw==",
+ "license": "MIT"
+ },
"node_modules/@rtsao/scc": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz",
@@ -1281,6 +3088,32 @@
"tailwindcss": "4.1.17"
}
},
+ "node_modules/@tanstack/query-core": {
+ "version": "5.90.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.90.12.tgz",
+ "integrity": "sha512-T1/8t5DhV/SisWjDnaiU2drl6ySvsHj1bHBCWNXd+/T+Hh1cf6JodyEYMd5sgwm+b/mETT4EV3H+zCVczCU5hg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ }
+ },
+ "node_modules/@tanstack/react-query": {
+ "version": "5.90.12",
+ "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.90.12.tgz",
+ "integrity": "sha512-graRZspg7EoEaw0a8faiUASCyJrqjKPdqJ9EwuDRUF9mEYJ1YPczI9H+/agJ0mOJkPCJDk0lsz5QTrLZ/jQ2rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@tanstack/query-core": "5.90.12"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/tannerlinsley"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19"
+ }
+ },
"node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.1.tgz",
@@ -1299,6 +3132,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/hoist-non-react-statics": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.7.tgz",
+ "integrity": "sha512-PQTyIulDkIDro8P+IHbKCsw7U2xxBYflVzW/FgWdCAePD9xGSidgA76/GeJ6lBKoblyhf9pBY763gbrN+1dI8g==",
+ "license": "MIT",
+ "dependencies": {
+ "hoist-non-react-statics": "^3.3.0"
+ },
+ "peerDependencies": {
+ "@types/react": "*"
+ }
+ },
"node_modules/@types/json-schema": {
"version": "7.0.15",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
@@ -1313,6 +3158,24 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/@types/jsonwebtoken": {
+ "version": "9.0.10",
+ "resolved": "https://registry.npmjs.org/@types/jsonwebtoken/-/jsonwebtoken-9.0.10.tgz",
+ "integrity": "sha512-asx5hIG9Qmf/1oStypjanR7iKTv0gXQ1Ov/jfrX6kS/EO0OFni8orbmGCn0672NHR3kXHwpAwR+B368ZGN/2rA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/ms": "*",
+ "@types/node": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
+ "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==",
+ "dev": true,
+ "license": "MIT"
+ },
"node_modules/@types/node": {
"version": "20.19.26",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.19.26.tgz",
@@ -1323,11 +3186,16 @@
"undici-types": "~6.21.0"
}
},
+ "node_modules/@types/parse-json": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/@types/parse-json/-/parse-json-4.0.2.tgz",
+ "integrity": "sha512-dISoDXWWQwUquiKsyZ4Ng+HX2KsPL7LyHKHQwgGFEA3IaKac4Obd+h2a/a6waisAoepJlBcx9paWqjA8/HVjCw==",
+ "license": "MIT"
+ },
"node_modules/@types/react": {
"version": "19.2.7",
"resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.7.tgz",
"integrity": "sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==",
- "dev": true,
"license": "MIT",
"peer": true,
"dependencies": {
@@ -1338,8 +3206,9 @@
"version": "19.2.3",
"resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz",
"integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==",
- "dev": true,
+ "devOptional": true,
"license": "MIT",
+ "peer": true,
"peerDependencies": {
"@types/react": "^19.2.0"
}
@@ -1934,6 +3803,18 @@
"dev": true,
"license": "Python-2.0"
},
+ "node_modules/aria-hidden": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz",
+ "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/aria-query": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/aria-query/-/aria-query-5.3.2.tgz",
@@ -2121,6 +4002,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==",
+ "license": "MIT"
+ },
+ "node_modules/attr-accept": {
+ "version": "2.2.5",
+ "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.5.tgz",
+ "integrity": "sha512-0bDNnY/u6pPwHDMoF0FieU354oBi0a8rD9FcsLwzcGWbc8KS8KPIi7y+s13OlVY+gMWc/9xEMUgNE6Qm8ZllYQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=4"
+ }
+ },
"node_modules/available-typed-arrays": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz",
@@ -2147,6 +4043,17 @@
"node": ">=4"
}
},
+ "node_modules/axios": {
+ "version": "1.13.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.2.tgz",
+ "integrity": "sha512-VPk9ebNqPcy5lRGuSlKx752IlDatOjT9paPlm8A7yOuW2Fbvp4X3JznJtT4f0GzGLLiWE9W8onz51SqLYwzGaA==",
+ "license": "MIT",
+ "dependencies": {
+ "follow-redirects": "^1.15.6",
+ "form-data": "^4.0.4",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
"node_modules/axobject-query": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-4.1.0.tgz",
@@ -2157,6 +4064,21 @@
"node": ">= 0.4"
}
},
+ "node_modules/babel-plugin-macros": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/babel-plugin-macros/-/babel-plugin-macros-3.1.0.tgz",
+ "integrity": "sha512-Cg7TFGpIr01vOQNODXOOaGz2NpCU5gl8x1qJFbb6hbZxR7XrcE2vtbAsTAbJ7/xwJtUuJEw8K8Zr/AE0LHlesg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/runtime": "^7.12.5",
+ "cosmiconfig": "^7.0.0",
+ "resolve": "^1.19.0"
+ },
+ "engines": {
+ "node": ">=10",
+ "npm": ">=6"
+ }
+ },
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -2188,6 +4110,12 @@
"node": ">=8"
}
},
+ "node_modules/buffer-equal-constant-time": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
+ "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/call-bind": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz",
@@ -2211,7 +4139,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz",
"integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2242,7 +4169,6 @@
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
"integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=6"
@@ -2285,12 +4211,33 @@
"url": "https://github.com/chalk/chalk?sponsor=1"
}
},
+ "node_modules/class-variance-authority": {
+ "version": "0.7.1",
+ "resolved": "https://registry.npmjs.org/class-variance-authority/-/class-variance-authority-0.7.1.tgz",
+ "integrity": "sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "funding": {
+ "url": "https://polar.sh/cva"
+ }
+ },
"node_modules/client-only": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz",
"integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==",
"license": "MIT"
},
+ "node_modules/clsx": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.1.tgz",
+ "integrity": "sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
@@ -2311,6 +4258,18 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "license": "MIT",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -2318,6 +4277,28 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==",
+ "license": "MIT"
+ },
+ "node_modules/cosmiconfig": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-7.1.0.tgz",
+ "integrity": "sha512-AdmX6xUzdNASswsFtmwSt7Vj8po9IuqXm0UXz7QKPuEUmPB4XyjGfaAr2PSuELMwkRMVH1EpIkX5bTZGRB3eCA==",
+ "license": "MIT",
+ "dependencies": {
+ "@types/parse-json": "^4.0.0",
+ "import-fresh": "^3.2.1",
+ "parse-json": "^5.0.0",
+ "path-type": "^4.0.0",
+ "yaml": "^1.10.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
"node_modules/cross-spawn": {
"version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
@@ -2337,7 +4318,6 @@
"version": "3.2.3",
"resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz",
"integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/damerau-levenshtein": {
@@ -2401,11 +4381,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/date-fns": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-4.1.0.tgz",
+ "integrity": "sha512-Ukq0owbQXxa/U3EGtsdVBkR1w7KOQ5gIBqdH2hkvknzZPYvBxb/aa6E8L7tmjFtkwZBu3UXBbjIgPo/Ez4xaNg==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/kossnocorp"
+ }
+ },
+ "node_modules/date-fns-jalali": {
+ "version": "4.1.0-0",
+ "resolved": "https://registry.npmjs.org/date-fns-jalali/-/date-fns-jalali-4.1.0-0.tgz",
+ "integrity": "sha512-hTIP/z+t+qKwBDcmmsnmjWTduxCg+5KfdqWQvb2X/8C9+knYY6epN/pfxdDuyVlSVeFz0sM5eEfwIUQ70U4ckg==",
+ "license": "MIT"
+ },
"node_modules/debug": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"ms": "^2.1.3"
@@ -2426,6 +4421,15 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/deepmerge": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
+ "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/define-data-property": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz",
@@ -2462,6 +4466,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
"node_modules/detect-libc": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz",
@@ -2472,6 +4485,12 @@
"node": ">=8"
}
},
+ "node_modules/detect-node-es": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz",
+ "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==",
+ "license": "MIT"
+ },
"node_modules/doctrine": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
@@ -2489,7 +4508,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
"integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.1",
@@ -2500,6 +4518,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/ecdsa-sig-formatter": {
+ "version": "1.0.11",
+ "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
+ "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
+ "license": "Apache-2.0",
+ "dependencies": {
+ "safe-buffer": "^5.0.1"
+ }
+ },
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
@@ -2521,6 +4548,15 @@
"node": ">=10.13.0"
}
},
+ "node_modules/error-ex": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz",
+ "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
"node_modules/es-abstract": {
"version": "1.24.0",
"resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.24.0.tgz",
@@ -2594,7 +4630,6 @@
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz",
"integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2604,7 +4639,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz",
"integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -2642,7 +4676,6 @@
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz",
"integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0"
@@ -2655,7 +4688,6 @@
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz",
"integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==",
- "dev": true,
"license": "MIT",
"dependencies": {
"es-errors": "^1.3.0",
@@ -2702,7 +4734,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
"integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=10"
@@ -2891,6 +4922,7 @@
"integrity": "sha512-whOE1HFo/qJDyX4SnXzP4N6zOWn79WhnCUY/iDR0mPfQZO8wcYE4JClzI2oZrhBnnMUCBCHZhO6VQyoBU95mZA==",
"dev": true,
"license": "MIT",
+ "peer": true,
"dependencies": {
"@rtsao/scc": "^1.1.0",
"array-includes": "^3.1.9",
@@ -3137,6 +5169,12 @@
"node": ">=0.10.0"
}
},
+ "node_modules/eventemitter3": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz",
+ "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==",
+ "license": "MIT"
+ },
"node_modules/fast-deep-equal": {
"version": "3.1.3",
"resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
@@ -3144,6 +5182,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/fast-diff": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.3.0.tgz",
+ "integrity": "sha512-VxPP4NqbUjj6MaAOafWeUn2cXWLcCtljklUtZf0Ind4XQ+QPtmA0b18zZy0jIQx+ExRVCR/ZQpBmik5lXshNsw==",
+ "license": "Apache-2.0"
+ },
"node_modules/fast-glob": {
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
@@ -3211,6 +5255,18 @@
"node": ">=16.0.0"
}
},
+ "node_modules/file-selector": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-2.1.2.tgz",
+ "integrity": "sha512-QgXo+mXTe8ljeqUFaX3QVHc5osSItJ/Km+xpocx0aSqWGMSCf6qYs/VnzZgS864Pjn5iceMRFigeAV7AfTlaig==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.7.0"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
"node_modules/fill-range": {
"version": "7.1.1",
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
@@ -3224,6 +5280,12 @@
"node": ">=8"
}
},
+ "node_modules/find-root": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/find-root/-/find-root-1.1.0.tgz",
+ "integrity": "sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==",
+ "license": "MIT"
+ },
"node_modules/find-up": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
@@ -3262,6 +5324,26 @@
"dev": true,
"license": "ISC"
},
+ "node_modules/follow-redirects": {
+ "version": "1.15.11",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz",
+ "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "license": "MIT",
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
"node_modules/for-each": {
"version": "0.3.5",
"resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz",
@@ -3278,11 +5360,51 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/form-data": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz",
+ "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==",
+ "license": "MIT",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "es-set-tostringtag": "^2.1.0",
+ "hasown": "^2.0.2",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/formik": {
+ "version": "2.4.9",
+ "resolved": "https://registry.npmjs.org/formik/-/formik-2.4.9.tgz",
+ "integrity": "sha512-5nI94BMnlFDdQRBY4Sz39WkhxajZJ57Fzs8wVbtsQlm5ScKIR1QLYqv/ultBnobObtlUyxpxoLodpixrsf36Og==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://opencollective.com/formik"
+ }
+ ],
+ "license": "Apache-2.0",
+ "dependencies": {
+ "@types/hoist-non-react-statics": "^3.3.1",
+ "deepmerge": "^2.1.1",
+ "hoist-non-react-statics": "^3.3.0",
+ "lodash": "^4.17.21",
+ "lodash-es": "^4.17.21",
+ "react-fast-compare": "^2.0.1",
+ "tiny-warning": "^1.0.2",
+ "tslib": "^2.0.0"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/function-bind": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
"integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
- "dev": true,
"license": "MIT",
"funding": {
"url": "https://github.com/sponsors/ljharb"
@@ -3333,7 +5455,6 @@
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz",
"integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"call-bind-apply-helpers": "^1.0.2",
@@ -3354,11 +5475,19 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/get-nonce": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz",
+ "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/get-proto": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz",
"integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"dunder-proto": "^1.0.1",
@@ -3446,7 +5575,6 @@
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
"integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3518,7 +5646,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz",
"integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -3531,7 +5658,6 @@
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz",
"integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==",
- "dev": true,
"license": "MIT",
"dependencies": {
"has-symbols": "^1.0.3"
@@ -3547,7 +5673,6 @@
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
"integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"function-bind": "^1.1.2"
@@ -3556,6 +5681,15 @@
"node": ">= 0.4"
}
},
+ "node_modules/hoist-non-react-statics": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+ "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "react-is": "^16.7.0"
+ }
+ },
"node_modules/ignore": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz",
@@ -3570,7 +5704,6 @@
"version": "3.3.1",
"resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz",
"integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"parent-module": "^1.0.0",
@@ -3626,6 +5759,12 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "license": "MIT"
+ },
"node_modules/is-async-function": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz",
@@ -3706,7 +5845,6 @@
"version": "2.16.1",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz",
"integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==",
- "dev": true,
"license": "MIT",
"dependencies": {
"hasown": "^2.0.2"
@@ -4056,7 +6194,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
- "dev": true,
"license": "MIT"
},
"node_modules/js-yaml": {
@@ -4072,6 +6209,18 @@
"js-yaml": "bin/js-yaml.js"
}
},
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "license": "MIT",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
"node_modules/json-buffer": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
@@ -4079,6 +6228,12 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "license": "MIT"
+ },
"node_modules/json-schema-traverse": {
"version": "0.4.1",
"resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
@@ -4106,6 +6261,28 @@
"json5": "lib/cli.js"
}
},
+ "node_modules/jsonwebtoken": {
+ "version": "9.0.3",
+ "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz",
+ "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==",
+ "license": "MIT",
+ "dependencies": {
+ "jws": "^4.0.1",
+ "lodash.includes": "^4.3.0",
+ "lodash.isboolean": "^3.0.3",
+ "lodash.isinteger": "^4.0.4",
+ "lodash.isnumber": "^3.0.3",
+ "lodash.isplainobject": "^4.0.6",
+ "lodash.isstring": "^4.0.1",
+ "lodash.once": "^4.0.0",
+ "ms": "^2.1.1",
+ "semver": "^7.5.4"
+ },
+ "engines": {
+ "node": ">=12",
+ "npm": ">=6"
+ }
+ },
"node_modules/jsx-ast-utils": {
"version": "3.3.5",
"resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz",
@@ -4122,6 +6299,36 @@
"node": ">=4.0"
}
},
+ "node_modules/jwa": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
+ "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
+ "license": "MIT",
+ "dependencies": {
+ "buffer-equal-constant-time": "^1.0.1",
+ "ecdsa-sig-formatter": "1.0.11",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jws": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz",
+ "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==",
+ "license": "MIT",
+ "dependencies": {
+ "jwa": "^2.0.1",
+ "safe-buffer": "^5.0.1"
+ }
+ },
+ "node_modules/jwt-decode": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/jwt-decode/-/jwt-decode-4.0.0.tgz",
+ "integrity": "sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=18"
+ }
+ },
"node_modules/keyv": {
"version": "4.5.4",
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
@@ -4427,6 +6634,12 @@
"url": "https://opencollective.com/parcel"
}
},
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "license": "MIT"
+ },
"node_modules/locate-path": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
@@ -4443,6 +6656,67 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash-es": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+ "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.clonedeep": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz",
+ "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.includes": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz",
+ "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isboolean": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz",
+ "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isequal": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/lodash.isequal/-/lodash.isequal-4.5.0.tgz",
+ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==",
+ "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isinteger": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz",
+ "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isnumber": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz",
+ "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isplainobject": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz",
+ "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==",
+ "license": "MIT"
+ },
+ "node_modules/lodash.isstring": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz",
+ "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==",
+ "license": "MIT"
+ },
"node_modules/lodash.merge": {
"version": "4.6.2",
"resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -4450,11 +6724,16 @@
"dev": true,
"license": "MIT"
},
+ "node_modules/lodash.once": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz",
+ "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==",
+ "license": "MIT"
+ },
"node_modules/loose-envify": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
"integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
- "dev": true,
"license": "MIT",
"dependencies": {
"js-tokens": "^3.0.0 || ^4.0.0"
@@ -4463,6 +6742,15 @@
"loose-envify": "cli.js"
}
},
+ "node_modules/lucide-react": {
+ "version": "0.561.0",
+ "resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.561.0.tgz",
+ "integrity": "sha512-Y59gMY38tl4/i0qewcqohPdEbieBy7SovpBL9IFebhc2mDd8x4PZSOsiFRkpPcOq6bj1r/mjH/Rk73gSlIJP2A==",
+ "license": "ISC",
+ "peerDependencies": {
+ "react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ }
+ },
"node_modules/magic-string": {
"version": "0.30.21",
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
@@ -4477,7 +6765,6 @@
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz",
"integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -4507,6 +6794,27 @@
"node": ">=8.6"
}
},
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "license": "MIT",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
@@ -4534,7 +6842,6 @@
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
- "dev": true,
"license": "MIT"
},
"node_modules/nanoid": {
@@ -4630,6 +6937,15 @@
}
}
},
+ "node_modules/next-nprogress-bar": {
+ "version": "2.4.7",
+ "resolved": "https://registry.npmjs.org/next-nprogress-bar/-/next-nprogress-bar-2.4.7.tgz",
+ "integrity": "sha512-OeveNQYFBhQhZ+RgrDnvHNUEQfHCmipymmD4AfAVE9pFV4jeWi7/nNK5f0lIk7ODRrtjyyr/n2YpkRbs5kUoMg==",
+ "license": "MIT",
+ "dependencies": {
+ "nprogress-v2": "^1.0.4"
+ }
+ },
"node_modules/next/node_modules/postcss": {
"version": "8.4.31",
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
@@ -4658,11 +6974,16 @@
"node": "^10 || ^12 || >=14"
}
},
+ "node_modules/nprogress-v2": {
+ "version": "1.1.10",
+ "resolved": "https://registry.npmjs.org/nprogress-v2/-/nprogress-v2-1.1.10.tgz",
+ "integrity": "sha512-MypWLNIPIM07SS0bAc/oac0vhVFz9vAHm7d1sj//Pnf3J03LQ3CuWrlDteIu6exq0fIvkDJ6tUDRWLaifsIt5w==",
+ "license": "MIT"
+ },
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=0.10.0"
@@ -4849,11 +7170,16 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/parchment": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/parchment/-/parchment-3.0.0.tgz",
+ "integrity": "sha512-HUrJFQ/StvgmXRcQ1ftY6VEZUq3jA2t9ncFN4F84J/vN0/FPpQF+8FKXb3l6fLces6q0uOHj6NJn+2xvZnxO6A==",
+ "license": "BSD-3-Clause"
+ },
"node_modules/parent-module": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
"integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
- "dev": true,
"license": "MIT",
"dependencies": {
"callsites": "^3.0.0"
@@ -4862,6 +7188,24 @@
"node": ">=6"
}
},
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "license": "MIT",
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/path-exists": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
@@ -4886,9 +7230,17 @@
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
"integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
"node_modules/picocolors": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
@@ -4961,7 +7313,6 @@
"version": "15.8.1",
"resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
"integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
- "dev": true,
"license": "MIT",
"dependencies": {
"loose-envify": "^1.4.0",
@@ -4969,6 +7320,18 @@
"react-is": "^16.13.1"
}
},
+ "node_modules/property-expr": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/property-expr/-/property-expr-2.0.6.tgz",
+ "integrity": "sha512-SVtmxhRE/CGkn3eZY1T6pC8Nln6Fr/lu1mKSgRud0eC73whjGfoAogbn78LkD8aFL0zz3bAFerKSnOl7NlErBA==",
+ "license": "MIT"
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==",
+ "license": "MIT"
+ },
"node_modules/punycode": {
"version": "2.3.1",
"resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
@@ -5000,6 +7363,35 @@
],
"license": "MIT"
},
+ "node_modules/quill": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/quill/-/quill-2.0.3.tgz",
+ "integrity": "sha512-xEYQBqfYx/sfb33VJiKnSJp8ehloavImQ2A6564GAbqG55PGw1dAWUn1MUbQB62t0azawUS2CZZhWCjO8gRvTw==",
+ "license": "BSD-3-Clause",
+ "dependencies": {
+ "eventemitter3": "^5.0.1",
+ "lodash-es": "^4.17.21",
+ "parchment": "^3.0.0",
+ "quill-delta": "^5.1.0"
+ },
+ "engines": {
+ "npm": ">=8.2.3"
+ }
+ },
+ "node_modules/quill-delta": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/quill-delta/-/quill-delta-5.1.0.tgz",
+ "integrity": "sha512-X74oCeRI4/p0ucjb5Ma8adTXd9Scumz367kkMK5V/IatcX6A0vlgLgKbzXWy5nZmCGeNJm2oQX0d2Eqj+ZIlCA==",
+ "license": "MIT",
+ "dependencies": {
+ "fast-diff": "^1.3.0",
+ "lodash.clonedeep": "^4.5.0",
+ "lodash.isequal": "^4.5.0"
+ },
+ "engines": {
+ "node": ">= 12.0.0"
+ }
+ },
"node_modules/react": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react/-/react-19.1.0.tgz",
@@ -5010,6 +7402,27 @@
"node": ">=0.10.0"
}
},
+ "node_modules/react-day-picker": {
+ "version": "9.13.0",
+ "resolved": "https://registry.npmjs.org/react-day-picker/-/react-day-picker-9.13.0.tgz",
+ "integrity": "sha512-euzj5Hlq+lOHqI53NiuNhCP8HWgsPf/bBAVijR50hNaY1XwjKjShAnIe8jm8RD2W9IJUvihDIZ+KrmqfFzNhFQ==",
+ "license": "MIT",
+ "dependencies": {
+ "@date-fns/tz": "^1.4.1",
+ "date-fns": "^4.1.0",
+ "date-fns-jalali": "^4.1.0-0"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "funding": {
+ "type": "individual",
+ "url": "https://github.com/sponsors/gpbl"
+ },
+ "peerDependencies": {
+ "react": ">=16.8.0"
+ }
+ },
"node_modules/react-dom": {
"version": "19.1.0",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.1.0.tgz",
@@ -5023,13 +7436,117 @@
"react": "^19.1.0"
}
},
+ "node_modules/react-dropzone": {
+ "version": "14.3.8",
+ "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.3.8.tgz",
+ "integrity": "sha512-sBgODnq+lcA4P296DY4wacOZz3JFpD99fp+hb//iBO2HHnyeZU3FwWyXJ6salNpqQdsZrgMrotuko/BdJMV8Ug==",
+ "license": "MIT",
+ "dependencies": {
+ "attr-accept": "^2.2.4",
+ "file-selector": "^2.1.0",
+ "prop-types": "^15.8.1"
+ },
+ "engines": {
+ "node": ">= 10.13"
+ },
+ "peerDependencies": {
+ "react": ">= 16.8 || 18.0.0"
+ }
+ },
+ "node_modules/react-fast-compare": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/react-fast-compare/-/react-fast-compare-2.0.4.tgz",
+ "integrity": "sha512-suNP+J1VU1MWFKcyt7RtjiSWUjvidmQSlqu+eHslq+342xCbGTYmC0mEhPCOHxlW0CywylOC1u2DFAT+bv4dBw==",
+ "license": "MIT"
+ },
"node_modules/react-is": {
"version": "16.13.1",
"resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
"integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
- "dev": true,
"license": "MIT"
},
+ "node_modules/react-remove-scroll": {
+ "version": "2.7.2",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz",
+ "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-remove-scroll-bar": "^2.3.7",
+ "react-style-singleton": "^2.2.3",
+ "tslib": "^2.1.0",
+ "use-callback-ref": "^1.3.3",
+ "use-sidecar": "^1.1.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-remove-scroll-bar": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz",
+ "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==",
+ "license": "MIT",
+ "dependencies": {
+ "react-style-singleton": "^2.2.2",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-style-singleton": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz",
+ "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==",
+ "license": "MIT",
+ "dependencies": {
+ "get-nonce": "^1.0.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/react-toastify": {
+ "version": "11.0.5",
+ "resolved": "https://registry.npmjs.org/react-toastify/-/react-toastify-11.0.5.tgz",
+ "integrity": "sha512-EpqHBGvnSTtHYhCPLxML05NLY2ZX0JURbAdNYa6BUkk+amz4wbKBQvoKQAB0ardvSarUBuY4Q4s1sluAzZwkmA==",
+ "license": "MIT",
+ "dependencies": {
+ "clsx": "^2.1.1"
+ },
+ "peerDependencies": {
+ "react": "^18 || ^19",
+ "react-dom": "^18 || ^19"
+ }
+ },
"node_modules/reflect.getprototypeof": {
"version": "1.0.10",
"resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz",
@@ -5078,7 +7595,6 @@
"version": "1.22.11",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz",
"integrity": "sha512-RfqAvLnMl313r7c9oclB1HhUEAezcpLjz95wFH4LVuhk9JF/r22qmVP9AMmOU4vMX7Q8pN8jwNg/CSpdFnMjTQ==",
- "dev": true,
"license": "MIT",
"dependencies": {
"is-core-module": "^2.16.1",
@@ -5099,7 +7615,6 @@
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
"integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">=4"
@@ -5170,6 +7685,26 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "license": "MIT"
+ },
"node_modules/safe-push-apply": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz",
@@ -5215,7 +7750,6 @@
"version": "7.7.3",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz",
"integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==",
- "devOptional": true,
"license": "ISC",
"bin": {
"semver": "bin/semver.js"
@@ -5417,6 +7951,15 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/source-map": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
+ "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==",
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
"node_modules/source-map-js": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
@@ -5606,6 +8149,12 @@
}
}
},
+ "node_modules/stylis": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/stylis/-/stylis-4.2.0.tgz",
+ "integrity": "sha512-Orov6g6BB1sDfYgzWfTHDOxamtX1bE/zo104Dh9e6fqJ3PooipYyfJ0pUmrZO2wAvO8YbEyeFrkV91XTsGMSrw==",
+ "license": "MIT"
+ },
"node_modules/supports-color": {
"version": "7.2.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
@@ -5623,7 +8172,6 @@
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
"integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
- "dev": true,
"license": "MIT",
"engines": {
"node": ">= 0.4"
@@ -5632,6 +8180,16 @@
"url": "https://github.com/sponsors/ljharb"
}
},
+ "node_modules/tailwind-merge": {
+ "version": "3.4.0",
+ "resolved": "https://registry.npmjs.org/tailwind-merge/-/tailwind-merge-3.4.0.tgz",
+ "integrity": "sha512-uSaO4gnW+b3Y2aWoWfFpX62vn2sR3skfhbjsEnaBI81WD1wBLlHZe5sWf0AqjksNdYTbGBEd0UasQMT3SNV15g==",
+ "license": "MIT",
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/dcastil"
+ }
+ },
"node_modules/tailwindcss": {
"version": "4.1.17",
"resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.1.17.tgz",
@@ -5653,6 +8211,18 @@
"url": "https://opencollective.com/webpack"
}
},
+ "node_modules/tiny-case": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-case/-/tiny-case-1.0.3.tgz",
+ "integrity": "sha512-Eet/eeMhkO6TX8mnUteS9zgPbUMQa4I6Kkp5ORiBD5476/m+PIRiumP5tmh5ioJpH7k51Kehawy2UDfsnxxY8Q==",
+ "license": "MIT"
+ },
+ "node_modules/tiny-warning": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/tiny-warning/-/tiny-warning-1.0.3.tgz",
+ "integrity": "sha512-lBN9zLN/oAf68o3zNXYrdCt1kP8WsiGW8Oo2ka41b2IM5JL/S1CTyX1rW0mb/zSuJun0ZUrDxx4sqvYS2FWzPA==",
+ "license": "MIT"
+ },
"node_modules/tinyglobby": {
"version": "0.2.15",
"resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
@@ -5715,6 +8285,12 @@
"node": ">=8.0"
}
},
+ "node_modules/toposort": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/toposort/-/toposort-2.0.2.tgz",
+ "integrity": "sha512-0a5EOkAUp8D4moMi2W8ZF8jcga7BgZd91O/yabJCFY8az+XSzeGyTKs0Aoo897iV1Nj6guFq8orWDS96z91oGg==",
+ "license": "MIT"
+ },
"node_modules/ts-api-utils": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.1.0.tgz",
@@ -5747,6 +8323,16 @@
"integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==",
"license": "0BSD"
},
+ "node_modules/tw-animate-css": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/tw-animate-css/-/tw-animate-css-1.4.0.tgz",
+ "integrity": "sha512-7bziOlRqH0hJx80h/3mbicLW7o8qLsH5+RaLR2t+OHM3D0JlWGODQKQ4cxbK7WlvmUxpcj6Kgu6EKqjrGFe3QQ==",
+ "dev": true,
+ "license": "MIT",
+ "funding": {
+ "url": "https://github.com/sponsors/Wombosvideo"
+ }
+ },
"node_modules/type-check": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
@@ -5760,6 +8346,18 @@
"node": ">= 0.8.0"
}
},
+ "node_modules/type-fest": {
+ "version": "2.19.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-2.19.0.tgz",
+ "integrity": "sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==",
+ "license": "(MIT OR CC0-1.0)",
+ "engines": {
+ "node": ">=12.20"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
"node_modules/typed-array-buffer": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz",
@@ -5924,6 +8522,49 @@
"punycode": "^2.1.0"
}
},
+ "node_modules/use-callback-ref": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz",
+ "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==",
+ "license": "MIT",
+ "dependencies": {
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/use-sidecar": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz",
+ "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==",
+ "license": "MIT",
+ "dependencies": {
+ "detect-node-es": "^1.1.0",
+ "tslib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "peerDependencies": {
+ "@types/react": "*",
+ "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0 || ^19.0.0-rc"
+ },
+ "peerDependenciesMeta": {
+ "@types/react": {
+ "optional": true
+ }
+ }
+ },
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -6039,6 +8680,15 @@
"node": ">=0.10.0"
}
},
+ "node_modules/yaml": {
+ "version": "1.10.2",
+ "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz",
+ "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==",
+ "license": "ISC",
+ "engines": {
+ "node": ">= 6"
+ }
+ },
"node_modules/yocto-queue": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
@@ -6051,6 +8701,18 @@
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
+ },
+ "node_modules/yup": {
+ "version": "1.7.1",
+ "resolved": "https://registry.npmjs.org/yup/-/yup-1.7.1.tgz",
+ "integrity": "sha512-GKHFX2nXul2/4Dtfxhozv701jLQHdf6J34YDh2cEkpqoo8le5Mg6/LrdseVLrFarmFygZTlfIhHx/QKfb/QWXw==",
+ "license": "MIT",
+ "dependencies": {
+ "property-expr": "^2.0.5",
+ "tiny-case": "^1.0.3",
+ "toposort": "^2.0.2",
+ "type-fest": "^2.19.0"
+ }
}
}
}
diff --git a/package.json b/package.json
index dcaec2b..375184f 100644
--- a/package.json
+++ b/package.json
@@ -9,19 +9,48 @@
"lint": "eslint"
},
"dependencies": {
+ "@emotion/styled": "^11.14.1",
+ "@radix-ui/react-checkbox": "^1.3.3",
+ "@radix-ui/react-dialog": "^1.1.15",
+ "@radix-ui/react-dropdown-menu": "^2.1.16",
+ "@radix-ui/react-label": "^2.1.8",
+ "@radix-ui/react-popover": "^1.1.15",
+ "@radix-ui/react-progress": "^1.1.8",
+ "@radix-ui/react-select": "^2.2.6",
+ "@radix-ui/react-separator": "^1.1.8",
+ "@radix-ui/react-slot": "^1.2.4",
+ "@radix-ui/react-switch": "^1.2.6",
+ "@tanstack/react-query": "^5.90.12",
+ "axios": "^1.13.2",
+ "class-variance-authority": "^0.7.1",
+ "clsx": "^2.1.1",
+ "date-fns": "^4.1.0",
+ "formik": "^2.4.9",
+ "jsonwebtoken": "^9.0.3",
+ "jwt-decode": "^4.0.0",
+ "lucide-react": "^0.561.0",
+ "next": "15.5.7",
+ "next-nprogress-bar": "^2.4.7",
+ "quill": "^2.0.3",
"react": "19.1.0",
+ "react-day-picker": "^9.13.0",
"react-dom": "19.1.0",
- "next": "15.5.7"
+ "react-dropzone": "^14.3.8",
+ "react-toastify": "^11.0.5",
+ "tailwind-merge": "^3.4.0",
+ "yup": "^1.7.1"
},
"devDependencies": {
- "typescript": "^5",
+ "@eslint/eslintrc": "^3",
+ "@tailwindcss/postcss": "^4",
+ "@types/jsonwebtoken": "^9.0.10",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
- "@tailwindcss/postcss": "^4",
- "tailwindcss": "^4",
"eslint": "^9",
"eslint-config-next": "15.5.7",
- "@eslint/eslintrc": "^3"
+ "tailwindcss": "^4",
+ "tw-animate-css": "^1.4.0",
+ "typescript": "^5"
}
}
diff --git a/public/fonts/sogand/SOGAND.ttf b/public/fonts/sogand/SOGAND.ttf
new file mode 100644
index 0000000..fdee227
Binary files /dev/null and b/public/fonts/sogand/SOGAND.ttf differ
diff --git a/public/fonts/vazir/Vazirmatn-Black.woff2 b/public/fonts/vazir/Vazirmatn-Black.woff2
new file mode 100644
index 0000000..f08cace
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-Black.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-Bold.woff2 b/public/fonts/vazir/Vazirmatn-Bold.woff2
new file mode 100644
index 0000000..65b427f
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-Bold.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-ExtraBold.woff2 b/public/fonts/vazir/Vazirmatn-ExtraBold.woff2
new file mode 100644
index 0000000..c074e70
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-ExtraBold.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-ExtraLight.woff2 b/public/fonts/vazir/Vazirmatn-ExtraLight.woff2
new file mode 100644
index 0000000..997dea0
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-ExtraLight.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-Light.woff2 b/public/fonts/vazir/Vazirmatn-Light.woff2
new file mode 100644
index 0000000..d154722
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-Light.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-Medium.woff2 b/public/fonts/vazir/Vazirmatn-Medium.woff2
new file mode 100644
index 0000000..495af75
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-Medium.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-Regular.woff2 b/public/fonts/vazir/Vazirmatn-Regular.woff2
new file mode 100644
index 0000000..c9824c8
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-Regular.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-SemiBold.woff2 b/public/fonts/vazir/Vazirmatn-SemiBold.woff2
new file mode 100644
index 0000000..5301641
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-SemiBold.woff2 differ
diff --git a/public/fonts/vazir/Vazirmatn-Thin.woff2 b/public/fonts/vazir/Vazirmatn-Thin.woff2
new file mode 100644
index 0000000..b7df278
Binary files /dev/null and b/public/fonts/vazir/Vazirmatn-Thin.woff2 differ
diff --git a/public/logo.png b/public/logo.png
new file mode 100644
index 0000000..b443044
Binary files /dev/null and b/public/logo.png differ
diff --git a/src/app/(auth)/layout.tsx b/src/app/(auth)/layout.tsx
new file mode 100644
index 0000000..1abc7ec
--- /dev/null
+++ b/src/app/(auth)/layout.tsx
@@ -0,0 +1,13 @@
+"use client";
+import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ const queryClient = new QueryClient();
+ return (
+ {children}
+ );
+}
diff --git a/src/app/(auth)/login/page.tsx b/src/app/(auth)/login/page.tsx
new file mode 100644
index 0000000..01b6e69
--- /dev/null
+++ b/src/app/(auth)/login/page.tsx
@@ -0,0 +1,24 @@
+"use client";
+import LoginForm from "@/components/forms/login/LoginForm";
+import {useUserLogin} from "@/hooks";
+import {useRouter} from "next/navigation";
+import React from "react";
+
+export default function Page() {
+ const router = useRouter();
+ const {mutateAsync, isPending} = useUserLogin();
+ return (
+
+ );
+}
diff --git a/src/app/(auth)/reset-password/[token]/page.tsx b/src/app/(auth)/reset-password/[token]/page.tsx
new file mode 100644
index 0000000..7072957
--- /dev/null
+++ b/src/app/(auth)/reset-password/[token]/page.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
diff --git a/src/app/(auth)/reset-password/page.tsx b/src/app/(auth)/reset-password/page.tsx
new file mode 100644
index 0000000..9af72a7
--- /dev/null
+++ b/src/app/(auth)/reset-password/page.tsx
@@ -0,0 +1,18 @@
+"use client"
+import ResetPasswordForm from '@/components/forms/reset-password/ResetPasswordForm'
+import { useRouter } from 'next/navigation'
+import React from 'react'
+
+export default function Page() {
+ const router = useRouter();
+ return (
+
+
+
+ فرم فراموشی رمز عبور
+
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/configs/loading.tsx b/src/app/(dashboard)/configs/loading.tsx
new file mode 100644
index 0000000..2b22b07
--- /dev/null
+++ b/src/app/(dashboard)/configs/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/src/app/(dashboard)/configs/page.tsx b/src/app/(dashboard)/configs/page.tsx
new file mode 100644
index 0000000..d5f4531
--- /dev/null
+++ b/src/app/(dashboard)/configs/page.tsx
@@ -0,0 +1,26 @@
+"use client";
+import UpdateConfigsForm from "@/components/forms/configs/ConfigsForm";
+import Loader from "@/components/Loader";
+import {useGetAllConfigs, useUpdateConfigs} from "@/hooks/configs";
+import {useRouter} from "next/navigation";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const router = useRouter();
+ const {data} = useGetAllConfigs();
+ const {mutateAsync, isPending} = useUpdateConfigs();
+ return (
+
+ }>
+ {data && data?.data?.length && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/dashboard/loading.tsx b/src/app/(dashboard)/dashboard/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/dashboard/page.tsx b/src/app/(dashboard)/dashboard/page.tsx
new file mode 100644
index 0000000..4d81877
--- /dev/null
+++ b/src/app/(dashboard)/dashboard/page.tsx
@@ -0,0 +1,25 @@
+import DashboardCDNServerStatus from "@/components/DashboardCDNServerStatus";
+import DashboardStatistics from "@/components/DashboardStatistics";
+import Loader from "@/components/Loader";
+import RoleGuard from "@/components/RoleGuard";
+
+import React, {Suspense} from "react";
+
+export default function Page() {
+ return (
+
+
+ }>
+
+
+
+
+ }>
+
+
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/default/loading.tsx b/src/app/(dashboard)/default/loading.tsx
new file mode 100644
index 0000000..2b22b07
--- /dev/null
+++ b/src/app/(dashboard)/default/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ );
+}
diff --git a/src/app/(dashboard)/default/page.tsx b/src/app/(dashboard)/default/page.tsx
new file mode 100644
index 0000000..e18cb7b
--- /dev/null
+++ b/src/app/(dashboard)/default/page.tsx
@@ -0,0 +1,33 @@
+"use client";
+import UpdateDefaultForm from "@/components/forms/default/UpdateDefaultsForm";
+import Loader from "@/components/Loader";
+import {useGetAllDefaults, useUpdateDefaults} from "@/hooks/defaults";
+import { useGetLanguages } from "@/hooks/languages";
+import {useRouter} from "next/navigation";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const router = useRouter();
+
+ const {data, isLoading} = useGetAllDefaults();
+ const {mutateAsync, isPending} = useUpdateDefaults();
+ const {
+ data: languages,
+ } = useGetLanguages();
+ return (
+
+ }>
+ {data && languages && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/department/loading.tsx b/src/app/(dashboard)/department/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/department/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/department/members/edit/[id]/page.tsx b/src/app/(dashboard)/department/members/edit/[id]/page.tsx
new file mode 100644
index 0000000..56eb0a1
--- /dev/null
+++ b/src/app/(dashboard)/department/members/edit/[id]/page.tsx
@@ -0,0 +1,64 @@
+"use client";
+import UpdateDoctorForm from "@/components/forms/doctor/update/UpdateDoctorForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import { useGetAllExpertiseList } from "@/hooks/expertise";
+import { useGetLanguages } from "@/hooks/languages";
+import {useGetSingleUser, useUpdateUser} from "@/hooks/users";
+
+import Link from "next/link";
+import {useParams, useRouter} from "next/navigation";
+import {Suspense, useState} from "react";
+
+
+export default function Page() {
+ const params = useParams();
+ const id = params?.id;
+ const router = useRouter();
+ const [loading] = useState(false);
+ const {
+ data: languages,
+ } = useGetLanguages();
+ const {
+ data: expertises,
+ } = useGetAllExpertiseList();
+ const {
+ data: singleUser,
+ } = useGetSingleUser(id?.toString() ?? "");
+
+ const {mutateAsync} = useUpdateUser();
+ const {data} = singleUser || {};
+ return (
+
+
+
+
+
+ افزودن عضو جدید
+
+
+ مشاهده لیست اعضاء دپارتمان
+
+
+
+
}>
+ {data && expertises && languages && !loading && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/department/members/edit/page.tsx b/src/app/(dashboard)/department/members/edit/page.tsx
new file mode 100644
index 0000000..6a1fa26
--- /dev/null
+++ b/src/app/(dashboard)/department/members/edit/page.tsx
@@ -0,0 +1,7 @@
+
+"use client"
+import { notFound } from 'next/navigation'
+
+export default function Page() {
+ return notFound
+}
diff --git a/src/app/(dashboard)/department/members/new/page.tsx b/src/app/(dashboard)/department/members/new/page.tsx
new file mode 100644
index 0000000..149e3ac
--- /dev/null
+++ b/src/app/(dashboard)/department/members/new/page.tsx
@@ -0,0 +1,103 @@
+"use client";
+import DenyAccess from "@/components/DenyAccess";
+import CreateDepartmentMember from "@/components/forms/department/new/CreateDepartmentMember";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {API_URL} from "@/constants";
+import {Expertise, Language} from "@/types";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import {Suspense, useEffect, useState} from "react";
+
+async function fetchLanguages() {
+ const res = await fetch(`${API_URL}/language/get/all`,{cache:"no-cache"});
+
+
+ if (!res.ok && res.status==500) {
+ throw new Error("Failed to get data");
+ }
+
+ if(!res.ok && res.status==404){
+ return []
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+async function fetchExpertise() {
+ const res = await fetch(`${API_URL}/expertise/fa/get/all/list`,{cache:"no-cache"});
+
+
+ if (!res.ok && res.status==500) {
+ throw new Error("Failed to get data");
+ }
+
+ if(!res.ok && res.status==404){
+ return []
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+
+export default function Page() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState([]);
+
+ const [expertises, setExpertises] = useState([]);
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchExpertise()
+ .then((res) => {
+ if (!active) return;
+ setExpertises(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchLanguages()
+ .then((res) => {
+ if (!active) return;
+ setData(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+
+ if (loading) {
+ return ;
+ }
+ return (
+
+
+
+ مشاهده لیست اعضاء دپارتمان
+
+
}>
+ {data?.length > 0 ? expertises?.length > 0 ?
:
:
}
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/department/members/page.tsx b/src/app/(dashboard)/department/members/page.tsx
new file mode 100644
index 0000000..781840a
--- /dev/null
+++ b/src/app/(dashboard)/department/members/page.tsx
@@ -0,0 +1,49 @@
+import DepartmentMembersTable from "@/components/DepartmentMembersTable";
+
+import Loader from "@/components/Loader";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import UsersTableExport from "@/components/usersTableExport";
+
+import {Plus} from "lucide-react";
+import Link from "next/link";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ افزودن عضو جدید
+
+
+
+
+
+
+
+
+ }>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/doctors/edit/[id]/page.tsx b/src/app/(dashboard)/doctors/edit/[id]/page.tsx
new file mode 100644
index 0000000..d83a36c
--- /dev/null
+++ b/src/app/(dashboard)/doctors/edit/[id]/page.tsx
@@ -0,0 +1,64 @@
+"use client";
+import UpdateDoctorForm from "@/components/forms/doctor/update/UpdateDoctorForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import { useGetAllExpertiseList } from "@/hooks/expertise";
+import { useGetLanguages } from "@/hooks/languages";
+import {useGetSingleUser, useUpdateUser} from "@/hooks/users";
+
+import Link from "next/link";
+import {useParams, useRouter} from "next/navigation";
+import {Suspense, useState} from "react";
+
+
+export default function Page() {
+ const params = useParams();
+ const id = params?.id;
+ const router = useRouter();
+ const [loading] = useState(false);
+ const {
+ data: languages,
+ } = useGetLanguages();
+ const {
+ data: expertises,
+ } = useGetAllExpertiseList();
+ const {
+ data: singleUser,
+ } = useGetSingleUser(id?.toString() ?? "");
+
+ const {mutateAsync} = useUpdateUser();
+ const {data} = singleUser || {};
+ return (
+
+
+
+
+
+ افزودن پزشک جدید
+
+
+ مشاهده لیست پزشکان
+
+
+
+
}>
+ {data && expertises && languages && !loading && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/doctors/edit/page.tsx b/src/app/(dashboard)/doctors/edit/page.tsx
new file mode 100644
index 0000000..11355b4
--- /dev/null
+++ b/src/app/(dashboard)/doctors/edit/page.tsx
@@ -0,0 +1,10 @@
+"use client"
+import { notFound } from "next/navigation";
+
+
+
+
+export default function Page() {
+
+ return notFound();
+}
diff --git a/src/app/(dashboard)/doctors/loading.tsx b/src/app/(dashboard)/doctors/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/doctors/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/doctors/new/page.tsx b/src/app/(dashboard)/doctors/new/page.tsx
new file mode 100644
index 0000000..058c01d
--- /dev/null
+++ b/src/app/(dashboard)/doctors/new/page.tsx
@@ -0,0 +1,103 @@
+"use client";
+import DenyAccess from "@/components/DenyAccess";
+import CreateDoctorForm from "@/components/forms/doctor/create/CreateDoctorForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {API_URL} from "@/constants";
+import {Expertise, Language} from "@/types";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import {Suspense, useEffect, useState} from "react";
+
+async function fetchLanguages() {
+ const res = await fetch(`${API_URL}/language/get/all`,{cache:"no-cache"});
+
+
+
+ if (!res.ok && res.status==500) {
+ throw new Error("Failed to get data");
+ }
+
+ if(!res.ok && res.status==404){
+ return []
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+async function fetchExpertise() {
+ const res = await fetch(`${API_URL}/expertise/fa/get/all/list`,{cache:"no-cache"});
+
+ if (!res.ok && res.status==500) {
+ throw new Error("Failed to get data");
+ }
+
+ if(!res.ok && res.status==404){
+ return []
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+
+export default function Page() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState([]);
+
+ const [expertises, setExpertises] = useState([]);
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchExpertise()
+ .then((res) => {
+ if (!active) return;
+ setExpertises(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchLanguages()
+ .then((res) => {
+ if (!active) return;
+ setData(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+
+ if (loading) {
+ return ;
+ }
+ return (
+
+
+
+ مشاهده لیست پزشکان
+
+
}>
+ {data?.length > 0 ? expertises?.length > 0 ?
:
:
}
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/doctors/page.tsx b/src/app/(dashboard)/doctors/page.tsx
new file mode 100644
index 0000000..cb64189
--- /dev/null
+++ b/src/app/(dashboard)/doctors/page.tsx
@@ -0,0 +1,51 @@
+import DoctorsTable from "@/components/DoctorsTable";
+import ExpertiseFilterBox from "@/components/ExpertiseFilterBox";
+import Loader from "@/components/Loader";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import UsersTableExport from "@/components/usersTableExport";
+
+import {Plus} from "lucide-react";
+import Link from "next/link";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+ افزودن پزشک جدید
+
+
+
+
+
+
+
+
+ }>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/expertise/edit/[id]/page.tsx b/src/app/(dashboard)/expertise/edit/[id]/page.tsx
new file mode 100644
index 0000000..f45d5f1
--- /dev/null
+++ b/src/app/(dashboard)/expertise/edit/[id]/page.tsx
@@ -0,0 +1,62 @@
+"use client";
+import UpdateExpertiseForm from "@/components/forms/expertise/edit/UpdateExpertiseForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {useGetSingleExpertise, useUpdateExpertise} from "@/hooks/expertise";
+import {useGetLanguages} from "@/hooks/languages";
+
+import Link from "next/link";
+import {useParams, useRouter} from "next/navigation";
+import {Suspense, useState} from "react";
+
+export default function Page() {
+ const params = useParams();
+ const id = params?.id;
+ const router = useRouter();
+ const [loading] = useState(false);
+ const {
+ data: languages,
+
+ } = useGetLanguages();
+
+ const {
+ data: singleExpertise,
+
+ } = useGetSingleExpertise(id?.toString() ?? "");
+
+ const {isPending, mutateAsync} = useUpdateExpertise();
+ const {data} = singleExpertise || {};
+ return (
+
+
+
+
+
+ افزودن تخصص جدید
+
+
+ مشاهده لیست تخصص ها
+
+
+
+
}>
+ {data && languages && !loading && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/expertise/edit/loading.tsx b/src/app/(dashboard)/expertise/edit/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/expertise/edit/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/expertise/edit/page.tsx b/src/app/(dashboard)/expertise/edit/page.tsx
new file mode 100644
index 0000000..7072957
--- /dev/null
+++ b/src/app/(dashboard)/expertise/edit/page.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/expertise/loading.tsx b/src/app/(dashboard)/expertise/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/expertise/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/expertise/new/loading.tsx b/src/app/(dashboard)/expertise/new/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/expertise/new/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/expertise/new/page.tsx b/src/app/(dashboard)/expertise/new/page.tsx
new file mode 100644
index 0000000..a243e71
--- /dev/null
+++ b/src/app/(dashboard)/expertise/new/page.tsx
@@ -0,0 +1,40 @@
+"use client";
+import DenyAccess from "@/components/DenyAccess";
+import CreateExpertiseForm from "@/components/forms/expertise/new/CreateExpertiseForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {useCreateExpertise} from "@/hooks/expertise";
+import {useGetLanguages} from "@/hooks/languages";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const router = useRouter();
+ const {data} = useGetLanguages();
+ const {mutateAsync, isPending} = useCreateExpertise();
+ return (
+
+
+
+
+ مشاهده لیست تخصص ها
+
+
+
}>
+ {
+ data?.data ? data?.data?.length > 0 ?
:
:
خطا در دریافت اطلاعات زبان ها
+ }
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/expertise/page.tsx b/src/app/(dashboard)/expertise/page.tsx
new file mode 100644
index 0000000..465e8fa
--- /dev/null
+++ b/src/app/(dashboard)/expertise/page.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import ExpertiseTable from "@/components/ExpertiseTable";
+import LanguagesFilterBox from "@/components/LanguagesFilterBox";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import { Spinner } from "@/components/ui/spinner";
+
+import {Plus} from "lucide-react";
+import Link from "next/link";
+
+import React, { Suspense, useState } from "react";
+
+export default function Page() {
+ const [lang,setLang]=useState("fa");
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ افزودن تخصص جدید
+
+
+ {/*
+
+ */}
+
+
+
+ }>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/languages/loading.tsx b/src/app/(dashboard)/languages/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/languages/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/languages/page.tsx b/src/app/(dashboard)/languages/page.tsx
new file mode 100644
index 0000000..023e363
--- /dev/null
+++ b/src/app/(dashboard)/languages/page.tsx
@@ -0,0 +1,52 @@
+"use client";
+import CreateLanguageForm from "@/components/forms/languages/new/CreateLanguageForm";
+import LanguagesTable from "@/components/LangaugesTable";
+import Loader from "@/components/Loader";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import {Dialog} from "@/components/ui/dialog";
+import {useCreateLanguage} from "@/hooks/languages";
+import { useQueryClient } from "@tanstack/react-query";
+import {useRouter} from "next/navigation";
+
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const router = useRouter();
+const queryClient = useQueryClient();
+ const {mutateAsync, isPending} = useCreateLanguage();
+ return (
+ <>
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/layout.tsx b/src/app/(dashboard)/layout.tsx
new file mode 100644
index 0000000..1851bb0
--- /dev/null
+++ b/src/app/(dashboard)/layout.tsx
@@ -0,0 +1,53 @@
+import type {Metadata} from "next";
+import "../globals.css";
+import ProfileDropdown from "@/components/ProfileDropdown";
+import {Button} from "@/components/ui/button";
+import {Bell} from "lucide-react";
+import SideMenu from "@/components/SideMenu";
+import PanelQueryProvider from "@/components/PanelQueryProvider";
+import AuthGuard from "@/components/AuthGuard";
+import { Suspense } from "react";
+import Loader from "@/components/Loader";
+import { Badge } from "@/components/ui/badge";
+export const metadata: Metadata = {
+ title: "پنل مدیریت سایت ipd",
+ description: "Generated by create next app",
+};
+
+export default function RootLayout({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ return (
+
+
+
+
+
+
+
+
}>
+
+
+
+
+
+
+ {new Date().toLocaleDateString("fa-IR")}
+
+
+
+ پنل مدیریت بیماران بین الملل بیمارستان شمال
+
+
+
+
+ {children}
+
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/logs/access/loading.tsx b/src/app/(dashboard)/logs/access/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/access/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/audit/loading.tsx b/src/app/(dashboard)/logs/audit/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/audit/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/errors/loading.tsx b/src/app/(dashboard)/logs/errors/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/errors/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/loading.tsx b/src/app/(dashboard)/logs/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/performance/loading.tsx b/src/app/(dashboard)/logs/performance/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/performance/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/policy/loading.tsx b/src/app/(dashboard)/logs/policy/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/policy/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/requests/loading.tsx b/src/app/(dashboard)/logs/requests/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/requests/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/upload-server/loading.tsx b/src/app/(dashboard)/logs/upload-server/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/logs/upload-server/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/logs/upload-server/page.tsx b/src/app/(dashboard)/logs/upload-server/page.tsx
new file mode 100644
index 0000000..a57611b
--- /dev/null
+++ b/src/app/(dashboard)/logs/upload-server/page.tsx
@@ -0,0 +1,43 @@
+import React from "react";
+
+async function fetchData() {
+ const res = await fetch("http://localhost:4000/logs", { cache: "no-cache" });
+
+ if (!res.ok && res.status == 500) {
+ throw new Error("Failed to fetch data");
+ }
+
+ if (!res.ok && res.status == 404) {
+ return "";
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+interface LogsType {
+ level: string;
+ message: { method: string; timestamp: Date; url: string };
+}
+export default async function Page() {
+ const data = await fetchData();
+ return (
+
+
+ {data.logs
+ .map((item: string) => JSON.parse(item))
+ .map((log: LogsType, index: number) => (
+
+
Level : {log.level}
+
message method : {log.message.method}
+
+ message timestamp :{" "}
+ {new Date(log.message.timestamp).toLocaleString("fa-IR")}
+
+
url : {log.message.url}
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/medical-packages/edit/[id]/page.tsx b/src/app/(dashboard)/medical-packages/edit/[id]/page.tsx
new file mode 100644
index 0000000..31fcb81
--- /dev/null
+++ b/src/app/(dashboard)/medical-packages/edit/[id]/page.tsx
@@ -0,0 +1,73 @@
+"use client";
+import UpdateMedicalPackageForm from "@/components/forms/medical-packages/edit/UpdateMedicalPackageForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {useGetLanguages} from "@/hooks/languages";
+import {
+ useGetAllMedicalPackagesParent,
+ useGetSingleMedicalPackage,
+ useUpdateMedicalPackage,
+} from "@/hooks/medical-package";
+import {useQueryClient} from "@tanstack/react-query";
+
+import Link from "next/link";
+import {useParams, useRouter} from "next/navigation";
+import {Suspense, useState} from "react";
+
+export default function Page() {
+ const params = useParams();
+ const id = params?.id;
+ const router = useRouter();
+ const queryClient = useQueryClient();
+ const [lang] = useState("fa");
+ const [loading] = useState(false);
+ const {
+ data: languages,
+ } = useGetLanguages();
+
+ const {
+ data: singleMedicalPackage,
+ } = useGetSingleMedicalPackage({id: id?.toString() ?? "", lang});
+ const {data: parents} =
+ useGetAllMedicalPackagesParent("fa");
+
+ const {isPending, mutateAsync} = useUpdateMedicalPackage();
+ const {data} = singleMedicalPackage || {};
+ return (
+
+
+
+
+
+
+
+ افزودن خدمت پزشکی جدید
+
+
+ مشاهده لیست خدمات پزشکی
+
+
+
+
}>
+ {data && languages && !loading && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/medical-packages/edit/page.tsx b/src/app/(dashboard)/medical-packages/edit/page.tsx
new file mode 100644
index 0000000..7072957
--- /dev/null
+++ b/src/app/(dashboard)/medical-packages/edit/page.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/medical-packages/new/page.tsx b/src/app/(dashboard)/medical-packages/new/page.tsx
new file mode 100644
index 0000000..9e41613
--- /dev/null
+++ b/src/app/(dashboard)/medical-packages/new/page.tsx
@@ -0,0 +1,30 @@
+"use client";
+import DenyAccess from "@/components/DenyAccess";
+import CreateMedicalPackageForm from "@/components/forms/medical-packages/new/CreateMedicalPackageForm";
+import Loader from "@/components/Loader";
+import {useGetLanguages} from "@/hooks/languages";
+import {useCreateMedicalPackage, useGetAllMedicalPackagesParent} from "@/hooks/medical-package";
+import {useQueryClient} from "@tanstack/react-query";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const {data} = useGetLanguages();
+ const {mutateAsync, isPending} = useCreateMedicalPackage();
+ const {data:parents}=useGetAllMedicalPackagesParent("fa")
+ const queryClient = useQueryClient();
+ return (
+
+ }>
+ {data?.data?.length > 0 ? (
+
+ ) : }
+
+
+ );
+}
diff --git a/src/app/(dashboard)/medical-packages/page.tsx b/src/app/(dashboard)/medical-packages/page.tsx
new file mode 100644
index 0000000..13f1834
--- /dev/null
+++ b/src/app/(dashboard)/medical-packages/page.tsx
@@ -0,0 +1,49 @@
+"use client";
+import LanguagesFilterBox from "@/components/LanguagesFilterBox";
+import Loader from "@/components/Loader";
+import MedicalPackagesTable from "@/components/MedicalPackagesTable";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import {Plus} from "lucide-react";
+import Link from "next/link";
+import React, {Suspense, useState} from "react";
+
+export default function Page() {
+ const [lang, setLang] = useState("fa");
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ افزودن پکیج جدید
+
+
+ {/*
+
+ */}
+
+
+
+ }>
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/online-case/new/page.tsx b/src/app/(dashboard)/online-case/new/page.tsx
new file mode 100644
index 0000000..e16a123
--- /dev/null
+++ b/src/app/(dashboard)/online-case/new/page.tsx
@@ -0,0 +1,15 @@
+"use client";
+import CreateOnlineCaseForm from "@/components/forms/online-case/new/CreateOnlineCaseForm";
+import { useGetAllCountries } from "@/hooks/country";
+import {useRouter} from "next/navigation";
+import React from "react";
+
+export default function Page() {
+ const router = useRouter();
+ const {data:countries,isLoading:fetchCountriesLoading}=useGetAllCountries()
+ return (
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/online-case/page.tsx b/src/app/(dashboard)/online-case/page.tsx
new file mode 100644
index 0000000..92e378c
--- /dev/null
+++ b/src/app/(dashboard)/online-case/page.tsx
@@ -0,0 +1,46 @@
+import Loader from "@/components/Loader";
+import OnlineCasesTable from "@/components/OnlineCasesTable";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import {Plus} from "lucide-react";
+import Link from "next/link";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ افزودن کیس جدید
+
+
+ {/*
+
+ */}
+
+
+
+ }>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/patients/edit/[id]/page.tsx b/src/app/(dashboard)/patients/edit/[id]/page.tsx
new file mode 100644
index 0000000..7cc191e
--- /dev/null
+++ b/src/app/(dashboard)/patients/edit/[id]/page.tsx
@@ -0,0 +1,66 @@
+"use client";
+import UpdatePatientForm from "@/components/forms/patients/edit/UpdatePatientForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {useGetAllCountries} from "@/hooks/country";
+import {useGetSinglePatient, useUpdatePatient} from "@/hooks/patients";
+import {useUpdateUser} from "@/hooks/users";
+
+import Link from "next/link";
+import {useParams, useRouter} from "next/navigation";
+import {Suspense, useState} from "react";
+
+export default function Page() {
+ const router = useRouter();
+ const {data: countries, isLoading: fetchCountriesLoading} =
+ useGetAllCountries();
+ const params = useParams();
+ const id = params?.id;
+ const [loading, setLoading] = useState(false);
+
+ const {
+ data: singleUser,
+ isLoading,
+ error,
+ } = useGetSinglePatient(id?.toString() ?? "");
+
+ const {isPending, mutateAsync} = useUpdatePatient();
+ const {data} = singleUser || {};
+
+
+
+
+ return (
+
+
+
+
+
+ افزودن بیمار جدید
+
+
+ مشاهده لیست بیماران
+
+
+
+
}>
+ {data && countries && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/patients/loading.tsx b/src/app/(dashboard)/patients/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/patients/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/patients/new/page.tsx b/src/app/(dashboard)/patients/new/page.tsx
new file mode 100644
index 0000000..9ed670f
--- /dev/null
+++ b/src/app/(dashboard)/patients/new/page.tsx
@@ -0,0 +1,32 @@
+"use client";
+import CreatePatientForm from "@/components/forms/patients/new/CreatePatientForm";
+import SectionTitle from "@/components/SectionTitle";
+import {useGetAllCountries} from "@/hooks/country";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import React from "react";
+
+export default function Page() {
+ const router = useRouter();
+ const {data, isLoading} = useGetAllCountries();
+
+ return (
+
+
+
+
+ مشاهده لیست بیماران
+
+
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/patients/page.tsx b/src/app/(dashboard)/patients/page.tsx
new file mode 100644
index 0000000..8e4b5a3
--- /dev/null
+++ b/src/app/(dashboard)/patients/page.tsx
@@ -0,0 +1,108 @@
+import FilterByisDeleted from "@/components/FilterByisDeleted";
+import Loader from "@/components/Loader";
+import PatientsTable from "@/components/PatientsTable";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import {Field} from "@/components/ui/field";
+import {Label} from "@/components/ui/label";
+import UsersTableExport from "@/components/usersTableExport";
+
+import {Plus} from "lucide-react";
+import Link from "next/link";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+
+
+ جستجوی پیشرفته
+
+
+
+
+
+
+
+
+
+
+
+
+ فیلتر بر اساس
+
+
+
+
+
+ {/*
*/}
+
+
+
+
+
+
+ افزودن بیمار جدید
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/patients/restore/page.tsx b/src/app/(dashboard)/patients/restore/page.tsx
new file mode 100644
index 0000000..96a1431
--- /dev/null
+++ b/src/app/(dashboard)/patients/restore/page.tsx
@@ -0,0 +1,10 @@
+import RestorePatientTable from '@/components/RestorePatientTable'
+import React from 'react'
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/privacy-policy/page.tsx b/src/app/(dashboard)/privacy-policy/page.tsx
new file mode 100644
index 0000000..e0facdb
--- /dev/null
+++ b/src/app/(dashboard)/privacy-policy/page.tsx
@@ -0,0 +1,60 @@
+"use client";
+import Loader from "@/components/Loader";
+import {Button} from "@/components/ui/button";
+import {
+ useGetPrivacyPolicy,
+ useUpdatePrivacyPolicy,
+} from "@/hooks/privacy-policy";
+import {handleAxiosError} from "@/lib/utils";
+import dynamic from "next/dynamic";
+import React, {Suspense, useEffect, useRef, useState} from "react";
+import {toast} from "react-toastify";
+const RichTextEditor = dynamic(() => import("@/components/TextEditor"), {
+ ssr: false,
+});
+
+type RichTextEditorHandle = {
+ getContent: () => string;
+};
+
+export default function Page() {
+ const editorRef = useRef(null);
+ const [editorContent, setEditorContent] = useState("");
+
+ const {mutateAsync} = useUpdatePrivacyPolicy();
+ const {data, isLoading} = useGetPrivacyPolicy();
+ const handleSave = async () => {
+ if (editorRef.current) {
+ const content = editorRef.current.getContent();
+ setEditorContent(content);
+
+ try {
+ const {message} = await mutateAsync(content);
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ }
+ };
+
+ useEffect(() => {
+ console.log(data)
+ setEditorContent(data?.data?.content);
+ }, [isLoading, data]);
+
+ return (
+
+
متن Privacy Policy
+
+ {(data?.data || !isLoading) && (
+ }>
+
+
+ )}
+
+
+ ثبت تغییرات
+
+
+ );
+}
diff --git a/src/app/(dashboard)/tos/page.tsx b/src/app/(dashboard)/tos/page.tsx
new file mode 100644
index 0000000..10d554a
--- /dev/null
+++ b/src/app/(dashboard)/tos/page.tsx
@@ -0,0 +1,193 @@
+"use client";
+import CreateTosForm from "@/components/forms/tos/create/CreateTosForm";
+import UpdateTosForm from "@/components/forms/tos/update/UpdateTosForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import { Badge } from "@/components/ui/badge";
+import { Button } from "@/components/ui/button";
+import {
+ useCreateTos,
+ useDeleteTos,
+ useGetAllTos,
+ useGetSingleTos,
+ useTosToggleActive,
+} from "@/hooks/tos";
+import { handleAxiosError } from "@/lib/utils";
+import { useQueryClient } from "@tanstack/react-query";
+import { Plus, Trash } from "lucide-react";
+import { useRouter } from "next/navigation";
+import React, { Suspense, useState } from "react";
+import { toast } from "react-toastify";
+
+
+
+
+export default function Page() {
+ const router = useRouter();
+ const [mode, setMode] = useState<"new" | "edit" | "form">("new");
+ const [version, setVersion] = useState("");
+ const { mutateAsync: createAsync, isPending: createPending } = useCreateTos();
+ const queryClient = useQueryClient();
+
+ const { data: AllTos} = useGetAllTos();
+ const { mutateAsync: toggleAsync, isPending: togglePending } =
+ useTosToggleActive();
+ const { mutateAsync: deleteAsync, isPending: deletePending } = useDeleteTos();
+ const {
+ mutateAsync: updateAsync,
+ isPending: singleTosPending,
+ data: preValues,
+ } = useGetSingleTos();
+
+ const handleGetSingleTos = async (version: string) => {
+ try {
+ await updateAsync(version);
+ } catch (error) {
+ toast.error("adassd");
+ }
+ };
+
+ const handleToggleActive = async (version: string) => {
+ try {
+ const { message } = await toggleAsync(version);
+ await queryClient.invalidateQueries({ queryKey: ["get-all-tos"] });
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ };
+ const handleDeleteTos = async (version: string) => {
+ try {
+ const { message } = await deleteAsync(version);
+ await queryClient.invalidateQueries({ queryKey: ["get-all-tos"] });
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ };
+ return (
+
+
+ {mode === "new" && (
+
{
+ setMode("form");
+ setVersion("");
+ }}
+ >
+
+ ایجاد نسخه جدید
+
+ )}
+ {mode === "form" && (
+ <>
+
+ >
+ )}
+
+ {mode === "edit" && version && preValues?.data && (
+
}>
+
+
+ )}
+
+
+
+
+
+ {AllTos &&
+ AllTos?.data?.length > 0 &&
+ AllTos?.data?.map((item, index) => (
+
+
+ ردیف :
+ {index + 1}
+
+
+ عنوان :
+ {item.title}
+
+
+ ورژن :
+
+ {item.version}
+
+
+
+
+ {item.version === version && mode === "edit" ? (
+ {
+ setVersion("");
+ setMode("new");
+ }}
+ >
+ انصراف
+
+ ) : (
+ {
+ setVersion(item.version);
+ setMode("edit");
+ handleGetSingleTos(item.version);
+ }}
+ >
+ ویرایش
+
+ )}
+
+
+ {item.isActive ? (
+
+ فعال است
+
+ ) : (
+ handleToggleActive(item.version)}
+ >
+ فعال کردن
+
+ )}
+
+
+ handleDeleteTos(item.version)}
+ >
+ {deletePending ? : }
+
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/transfer-packages/new/page.tsx b/src/app/(dashboard)/transfer-packages/new/page.tsx
new file mode 100644
index 0000000..3b545d0
--- /dev/null
+++ b/src/app/(dashboard)/transfer-packages/new/page.tsx
@@ -0,0 +1,28 @@
+"use client";
+import DenyAccess from "@/components/DenyAccess";
+import CreateTransferPackageForm from "@/components/forms/transfer-packages/new/CreateTransferPackageForm";
+import Loader from "@/components/Loader";
+import { useGetLanguages } from "@/hooks/languages";
+import React, { Suspense } from "react";
+
+export default function Page() {
+ const { data } = useGetLanguages();
+ return (
+
+
}>
+ {data ? (
+ data?.data?.length > 0 ? (
+
+ ) : (
+
+ )
+ ) : (
+
خطا در دریافت دیتای زبان ها
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/transfer-packages/page.tsx b/src/app/(dashboard)/transfer-packages/page.tsx
new file mode 100644
index 0000000..7072957
--- /dev/null
+++ b/src/app/(dashboard)/transfer-packages/page.tsx
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export default function Page() {
+ return (
+
+
+
+ )
+}
diff --git a/src/app/(dashboard)/transfer-team/loading.tsx b/src/app/(dashboard)/transfer-team/loading.tsx
new file mode 100644
index 0000000..a01d2c1
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/loading.tsx
@@ -0,0 +1,7 @@
+export default function Loading() {
+ return (
+
+ )
+}
\ No newline at end of file
diff --git a/src/app/(dashboard)/transfer-team/members/edit/[id]/page.tsx b/src/app/(dashboard)/transfer-team/members/edit/[id]/page.tsx
new file mode 100644
index 0000000..f66111f
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/members/edit/[id]/page.tsx
@@ -0,0 +1,67 @@
+"use client";
+import UpdateDoctorForm from "@/components/forms/doctor/update/UpdateDoctorForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import { useGetAllExpertiseList } from "@/hooks/expertise";
+import { useGetLanguages } from "@/hooks/languages";
+import {useGetSingleUser, useUpdateUser} from "@/hooks/users";
+
+import Link from "next/link";
+import {useParams, useRouter} from "next/navigation";
+import {Suspense, useState} from "react";
+
+
+export default function Page() {
+ const params = useParams();
+ const id = params?.id;
+ const router = useRouter();
+ const [loading] = useState(false);
+ const {
+ data: languages,
+
+ } = useGetLanguages();
+ const {
+ data: expertises,
+
+ } = useGetAllExpertiseList();
+ const {
+ data: singleUser,
+
+ } = useGetSingleUser(id?.toString() ?? "");
+
+ const {mutateAsync} = useUpdateUser();
+ const {data} = singleUser || {};
+ return (
+
+
+
+
+
+ افزودن عضو جدید
+
+
+ مشاهده لیست اعضاء تیم
+
+
+
+
}>
+ {data && expertises && languages && !loading && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/transfer-team/members/edit/page.tsx b/src/app/(dashboard)/transfer-team/members/edit/page.tsx
new file mode 100644
index 0000000..18ed602
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/members/edit/page.tsx
@@ -0,0 +1,6 @@
+"use client";
+import {notFound} from "next/navigation";
+
+export default function Page() {
+ return notFound();
+}
diff --git a/src/app/(dashboard)/transfer-team/members/new/page.tsx b/src/app/(dashboard)/transfer-team/members/new/page.tsx
new file mode 100644
index 0000000..b5f691a
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/members/new/page.tsx
@@ -0,0 +1,105 @@
+"use client";
+import DenyAccess from "@/components/DenyAccess";
+import CreateTransferTeamMember from "@/components/forms/transfer-team/member/new/CreateTransferTeamMemberForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {API_URL} from "@/constants";
+import {Expertise, Language} from "@/types";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import {Suspense, useEffect, useState} from "react";
+
+async function fetchLanguages() {
+ const res = await fetch(`${API_URL}/language/get/all`,{cache:"no-cache"});
+
+ if (!res.ok && res.status==500) {
+ throw new Error("Failed to get data");
+ }
+
+ if(!res.ok && res.status==404){
+ return []
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+async function fetchExpertise() {
+ const res = await fetch(`${API_URL}/expertise/fa/get/all/list`,{cache:"no-cache"});
+
+ if (!res.ok && res.status==500) {
+ throw new Error("Failed to get data");
+ }
+
+ if(!res.ok && res.status==404){
+ return []
+ }
+ const data = await res.json();
+
+ return data;
+}
+
+export default function Page() {
+ const router = useRouter();
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState([]);
+
+ const [expertises, setExpertises] = useState([]);
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchExpertise()
+ .then((res) => {
+ if (!active) return;
+ setExpertises(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchLanguages()
+ .then((res) => {
+ if (!active) return;
+ setData(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+
+ if (loading) {
+ return ;
+ }
+ return (
+
+
+
+
+ مشاهده لیست اعضا
+
+
+
}>
+ {data?.length > 0 ? expertises?.length > 0 ?
:
:
}
+
+
+
+ );
+}
diff --git a/src/app/(dashboard)/transfer-team/members/page.tsx b/src/app/(dashboard)/transfer-team/members/page.tsx
new file mode 100644
index 0000000..e293c92
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/members/page.tsx
@@ -0,0 +1,49 @@
+
+import Loader from "@/components/Loader";
+import RoleGuard from "@/components/RoleGuard";
+import SearchBox from "@/components/SearchBox";
+import TransferTeamTable from "@/components/TransferTeamTable";
+import UsersTableExport from "@/components/usersTableExport";
+
+import {Plus} from "lucide-react";
+import Link from "next/link";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+ افزودن عضو جدید
+
+
+
+
+
+
+
+
+ }>
+
+
+
+
+ >
+ );
+}
diff --git a/src/app/(dashboard)/transfer-team/teams/new/page.tsx b/src/app/(dashboard)/transfer-team/teams/new/page.tsx
new file mode 100644
index 0000000..4602537
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/teams/new/page.tsx
@@ -0,0 +1,41 @@
+"use client";
+import CreateTransferTeamForm from "@/components/forms/transfer-team/team/new/CreateTransferTeamForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {useCreateTransferTeam, useGetAllTransferTeamAllMembers} from "@/hooks/transfer-team";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const {data: members, isLoading: fetchMembersLoading} =
+ useGetAllTransferTeamAllMembers();
+ const {mutateAsync,isPending}=useCreateTransferTeam()
+ const router = useRouter();
+ return (
+
+
+
+
+ مشاهده لیست تیم ها
+
+
+ }>
+ {members && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/(dashboard)/transfer-team/teams/page.tsx b/src/app/(dashboard)/transfer-team/teams/page.tsx
new file mode 100644
index 0000000..7219257
--- /dev/null
+++ b/src/app/(dashboard)/transfer-team/teams/page.tsx
@@ -0,0 +1,38 @@
+"use client";
+import CreateTransferTeamForm from "@/components/forms/transfer-team/team/new/CreateTransferTeamForm";
+import Loader from "@/components/Loader";
+import SectionTitle from "@/components/SectionTitle";
+import {useGetAllTransferTeamAllMembers} from "@/hooks/transfer-team";
+import Link from "next/link";
+import {useRouter} from "next/navigation";
+import React, {Suspense} from "react";
+
+export default function Page() {
+ const {data: members, isLoading: fetchMembersLoading} =
+ useGetAllTransferTeamAllMembers();
+ const router = useRouter();
+ return (
+
+
+
+
+ مشاهده لیست تیم ها
+
+
+ }>
+ {members && (
+
+ )}
+
+
+ );
+}
diff --git a/src/app/error.tsx b/src/app/error.tsx
new file mode 100644
index 0000000..155024c
--- /dev/null
+++ b/src/app/error.tsx
@@ -0,0 +1,17 @@
+"use client"
+import Link from "next/link";
+
+export default function ErrorPage() {
+ return (
+
+
500
+
خطای سرور رخ داده است.
+
+ بازگشت به صفحه اصلی
+
+
+ );
+}
diff --git a/src/app/globals.css b/src/app/globals.css
index a2dc41e..56f9d95 100644
--- a/src/app/globals.css
+++ b/src/app/globals.css
@@ -1,26 +1,147 @@
@import "tailwindcss";
+@import "tw-animate-css";
-:root {
- --background: #ffffff;
- --foreground: #171717;
-}
+@custom-variant dark (&:is(.dark *));
@theme inline {
--color-background: var(--background);
--color-foreground: var(--foreground);
--font-sans: var(--font-geist-sans);
--font-mono: var(--font-geist-mono);
+ --color-sidebar-ring: var(--sidebar-ring);
+ --color-sidebar-border: var(--sidebar-border);
+ --color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
+ --color-sidebar-accent: var(--sidebar-accent);
+ --color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
+ --color-sidebar-primary: var(--sidebar-primary);
+ --color-sidebar-foreground: var(--sidebar-foreground);
+ --color-sidebar: var(--sidebar);
+ --color-chart-5: var(--chart-5);
+ --color-chart-4: var(--chart-4);
+ --color-chart-3: var(--chart-3);
+ --color-chart-2: var(--chart-2);
+ --color-chart-1: var(--chart-1);
+ --color-ring: var(--ring);
+ --color-input: var(--input);
+ --color-border: var(--border);
+ --color-destructive: var(--destructive);
+ --color-accent-foreground: var(--accent-foreground);
+ --color-accent: var(--accent);
+ --color-muted-foreground: var(--muted-foreground);
+ --color-muted: var(--muted);
+ --color-secondary-foreground: var(--secondary-foreground);
+ --color-secondary: var(--secondary);
+ --color-primary-foreground: var(--primary-foreground);
+ --color-primary: var(--primary);
+ --color-popover-foreground: var(--popover-foreground);
+ --color-popover: var(--popover);
+ --color-card-foreground: var(--card-foreground);
+ --color-card: var(--card);
+ --radius-sm: calc(var(--radius) - 4px);
+ --radius-md: calc(var(--radius) - 2px);
+ --radius-lg: var(--radius);
+ --radius-xl: calc(var(--radius) + 4px);
+ --radius-2xl: calc(var(--radius) + 8px);
+ --radius-3xl: calc(var(--radius) + 12px);
+ --radius-4xl: calc(var(--radius) + 16px);
+ --font-size-content: calc(var(--rem-size-2) * 1rem);
}
-@media (prefers-color-scheme: dark) {
- :root {
- --background: #0a0a0a;
- --foreground: #ededed;
+:root {
+ --radius: 0.625rem;
+ --background: #f6f6f6;
+ --foreground: oklch(0.145 0 0);
+ --card: oklch(1 0 0);
+ --card-foreground: oklch(0.145 0 0);
+ --popover: oklch(1 0 0);
+ --popover-foreground: oklch(0.145 0 0);
+ --primary: oklch(0.32 0.16 255);
+ --primary-foreground: oklch(0.985 0 0);
+ --secondary: oklch(0.97 0 0);
+ --secondary-foreground: oklch(0.205 0 0);
+ --muted: oklch(0.97 0 0);
+ --muted-foreground: oklch(0.556 0 0);
+ --accent: oklch(0.97 0 0);
+ --accent-foreground: oklch(0.205 0 0);
+ --destructive: oklch(0.577 0.245 27.325);
+ --border: oklch(0.922 0 0);
+ --input: oklch(0.922 0 0);
+ --ring: oklch(0.708 0 0);
+ --chart-1: oklch(0.646 0.222 41.116);
+ --chart-2: oklch(0.6 0.118 184.704);
+ --chart-3: oklch(0.398 0.07 227.392);
+ --chart-4: oklch(0.828 0.189 84.429);
+ --chart-5: oklch(0.769 0.188 70.08);
+ --sidebar: oklch(0.985 0 0);
+ --sidebar-foreground: oklch(0.145 0 0);
+ --sidebar-primary: oklch(0.205 0 0);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.97 0 0);
+ --sidebar-accent-foreground: oklch(0.205 0 0);
+ --sidebar-border: oklch(0.922 0 0);
+ --sidebar-ring: oklch(0.708 0 0);
+ --toastify-font-family: VazirMatn, var(--font-vazir);
+ --panel-width: 120px;
+ --panel-width-padding: 140px;
+}
+
+.dark {
+ --background: oklch(0.145 0 0);
+ --foreground: oklch(0.985 0 0);
+ --card: oklch(0.205 0 0);
+ --card-foreground: oklch(0.985 0 0);
+ --popover: oklch(0.205 0 0);
+ --popover-foreground: oklch(0.985 0 0);
+ --primary: oklch(0.922 0 0);
+ --primary-foreground: oklch(0.205 0 0);
+ --secondary: oklch(0.269 0 0);
+ --secondary-foreground: oklch(0.985 0 0);
+ --muted: oklch(0.269 0 0);
+ --muted-foreground: oklch(0.708 0 0);
+ --accent: oklch(0.269 0 0);
+ --accent-foreground: oklch(0.985 0 0);
+ --destructive: oklch(0.704 0.191 22.216);
+ --border: oklch(1 0 0 / 10%);
+ --input: oklch(1 0 0 / 15%);
+ --ring: oklch(0.556 0 0);
+ --chart-1: oklch(0.488 0.243 264.376);
+ --chart-2: oklch(0.696 0.17 162.48);
+ --chart-3: oklch(0.769 0.188 70.08);
+ --chart-4: oklch(0.627 0.265 303.9);
+ --chart-5: oklch(0.645 0.246 16.439);
+ --sidebar: oklch(0.205 0 0);
+ --sidebar-foreground: oklch(0.985 0 0);
+ --sidebar-primary: oklch(0.488 0.243 264.376);
+ --sidebar-primary-foreground: oklch(0.985 0 0);
+ --sidebar-accent: oklch(0.269 0 0);
+ --sidebar-accent-foreground: oklch(0.985 0 0);
+ --sidebar-border: oklch(1 0 0 / 10%);
+ --sidebar-ring: oklch(0.556 0 0);
+}
+
+@layer base {
+ * {
+ @apply border-border outline-ring/50 font-normal;
+ }
+ body {
+ @apply bg-background text-foreground;
+ }
+ button:not(:disabled),
+ [role="button"]:not(:disabled) {
+ cursor: pointer;
}
}
body {
- background: var(--background);
- color: var(--foreground);
- font-family: Arial, Helvetica, sans-serif;
+ font-family: VazirMatn, Arial, Helvetica, sans-serif;
+ scroll-behavior: smooth;
+}
+.custom-toast {
+ font-family: VazirMatn, sans-serif; /* فونت فارسی دلخواه */
+ font-size: 16px;
+}
+.ql-editor {
+ font-family: VazirMatn, sans-serif !important;
+ font-size: 16px;
+ line-height: 1.6;
}
diff --git a/src/app/layout.tsx b/src/app/layout.tsx
index f7fa87e..a42ca12 100644
--- a/src/app/layout.tsx
+++ b/src/app/layout.tsx
@@ -1,19 +1,10 @@
-import type { Metadata } from "next";
-import { Geist, Geist_Mono } from "next/font/google";
+import type {Metadata} from "next";
import "./globals.css";
-
-const geistSans = Geist({
- variable: "--font-geist-sans",
- subsets: ["latin"],
-});
-
-const geistMono = Geist_Mono({
- variable: "--font-geist-mono",
- subsets: ["latin"],
-});
+import {FontVazir} from "@/config/font.config";
+import {ToastContainer} from "react-toastify";
export const metadata: Metadata = {
- title: "Create Next App",
+ title: "پنل مدیریت وب سایت بیماران بین الملل",
description: "Generated by create next app",
};
@@ -23,11 +14,22 @@ export default function RootLayout({
children: React.ReactNode;
}>) {
return (
-
+
{children}
+
);
diff --git a/src/app/not-found.tsx b/src/app/not-found.tsx
new file mode 100644
index 0000000..1b9066c
--- /dev/null
+++ b/src/app/not-found.tsx
@@ -0,0 +1,16 @@
+import Link from "next/link";
+
+export default function NotFoundPage() {
+ return (
+
+
404
+
صفحهای که دنبال آن هستید پیدا نشد.
+
+ بازگشت به صفحه اصلی
+
+
+ );
+}
diff --git a/src/app/page.tsx b/src/app/page.tsx
index a932894..c946d54 100644
--- a/src/app/page.tsx
+++ b/src/app/page.tsx
@@ -1,103 +1,47 @@
+import { LogIn } from "lucide-react";
import Image from "next/image";
+import Link from "next/link";
export default function Home() {
return (
-
+
-
-
- Get started by editing{" "}
-
- src/app/page.tsx
-
- .
-
-
- Save and see your changes instantly.
-
-
+
+ پنل مدیریت سایت بیماران بین الملل
+
+ {/*
+ یکی از دکمه های زیر را فشار دهید
+
*/}
-
+
);
}
diff --git a/src/components/AuthGuard.tsx b/src/components/AuthGuard.tsx
new file mode 100644
index 0000000..7e0369d
--- /dev/null
+++ b/src/components/AuthGuard.tsx
@@ -0,0 +1,27 @@
+"use client";
+
+import {useMe} from "@/hooks";
+import Loader from "./Loader";
+
+export default function AuthGuard({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ const {isLoading} = useMe();
+
+
+
+ if (isLoading)
+ return (
+
+
+ لطفا منتظر بمانید
+
+
+
+
+
+ );
+ return children;
+}
diff --git a/src/components/CalendarHijri.tsx b/src/components/CalendarHijri.tsx
new file mode 100644
index 0000000..d0601d3
--- /dev/null
+++ b/src/components/CalendarHijri.tsx
@@ -0,0 +1,49 @@
+"use client";
+
+import * as React from "react";
+import {ChevronDownIcon} from "lucide-react";
+
+import {Button} from "@/components/ui/button";
+import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
+import {CalendarHijriBase} from "./ui/calendar-hijri";
+
+export function CalendarHijri({
+ date,
+ setDate,
+}: {
+ date: Date | undefined;
+ setDate: React.Dispatch>;
+}) {
+ const [open, setOpen] = React.useState(false);
+
+ return (
+
+
+
+
+ {date ? date.toLocaleDateString("fa-IR") : "Select date"}
+
+
+
+
+ {
+ setDate(date);
+ setOpen(false);
+ }}
+ />
+
+
+
+ );
+}
diff --git a/src/components/CalendarNormal.tsx b/src/components/CalendarNormal.tsx
new file mode 100644
index 0000000..4900033
--- /dev/null
+++ b/src/components/CalendarNormal.tsx
@@ -0,0 +1,49 @@
+"use client";
+
+import * as React from "react";
+import {ChevronDownIcon} from "lucide-react";
+
+import {Button} from "@/components/ui/button";
+import {Calendar} from "@/components/ui/calendar";
+import {Popover, PopoverContent, PopoverTrigger} from "@/components/ui/popover";
+
+export function CalendarNormal({
+ date,
+ setDate,
+}: {
+ date: Date | undefined;
+ setDate: React.Dispatch>;
+}) {
+ const [open, setOpen] = React.useState(false);
+
+ return (
+
+
+
+
+ {date ? date.toLocaleDateString() : "Select date"}
+
+
+
+
+ {
+ setDate(date);
+ setOpen(false);
+ }}
+ />
+
+
+
+ );
+}
diff --git a/src/components/DashboardCDNServerStatus.tsx b/src/components/DashboardCDNServerStatus.tsx
new file mode 100644
index 0000000..0763202
--- /dev/null
+++ b/src/components/DashboardCDNServerStatus.tsx
@@ -0,0 +1,184 @@
+import {API_CDN_URL} from "@/constants";
+import {formatSystemForUI} from "@/lib/utils";
+import React from "react";
+import SectionTitle from "./SectionTitle";
+import Link from "next/link";
+import { Logs } from "lucide-react";
+
+async function getCDNStatus() {
+ try {
+ const res = await fetch(`${API_CDN_URL}/status`, {
+ method: "GET",
+ credentials: "include",
+ });
+
+ if (!res.ok) {
+ const errorData = await res.json().catch(() => null);
+
+ return {
+ status: "SERVICE_ERROR",
+ message: errorData?.message || "سرویس با خطا پاسخ داد",
+ system: null,
+ };
+ }
+
+ const data = await res.json();
+ return {
+ status: "OK",
+ message: data.message,
+ system: data.system,
+ };
+ } catch {
+ return {
+ status: "DOWN",
+ message: "سرویس پاسخگو نیست",
+ system: null,
+ };
+ }
+}
+
+export default async function DashboardCDNServerStatus() {
+ const data = await getCDNStatus();
+
+ const {status, message, system} = data || {};
+
+ const uiSystem = system ? formatSystemForUI(system) : null;
+ return (
+
+ <>
+
+
+
+
+
وضعیت
+
+
+
+
+
+
+ {message} ({status})
+
+
+
+ >
+ {uiSystem && (
+ <>
+
+
+
+
+
+
uptime
+
+ {uiSystem.uptime}
+
+
+
+
+
pid
+
+ {uiSystem.pid}
+
+
+
+
+
node version
+
+
+ {uiSystem.nodeVersion}
+
+
+
+
+
+
heap used
+
+
+ {uiSystem.heapUsed}
+
+
+
+
+
+
heap total
+
+
+ {uiSystem.heapTotal}
+
+
+
+
+
+
rss
+
+ {uiSystem.rss}
+
+
+
+
+
cpu user
+
+
+ {uiSystem.cpuUser}
+
+
+
+
+
+
cpu system
+
+
+ {uiSystem.cpuSystem}
+
+
+
+
+
+
+ event loop utilization
+
+
+
+ {uiSystem.eventLoopUtil}
+
+
+
+
+
+
free memory
+
+
+ {uiSystem.freeMemory}
+
+
+
+
+
+
total memory
+
+
+ {uiSystem.totalMemory}
+
+
+
+
+
+
+
+ مشاهده لاگ ها
+
+
+ >
+ )}
+
+ );
+}
diff --git a/src/components/DashboardStatistics.tsx b/src/components/DashboardStatistics.tsx
new file mode 100644
index 0000000..5c540e2
--- /dev/null
+++ b/src/components/DashboardStatistics.tsx
@@ -0,0 +1,136 @@
+import React from "react";
+import SectionTitle from "./SectionTitle";
+import {API_URL} from "@/constants";
+import {cookies} from "next/headers";
+export interface DashboardCounts {
+ department: {
+ members: number;
+ };
+ patients: number;
+ onlineCases: {
+ completed: number;
+ pending: number;
+ };
+ staff: {
+ coordinators: number;
+ admins: number;
+ doctors: number;
+ };
+}
+
+async function getData() {
+ try {
+ const res = await fetch(`${API_URL}/statistics/data`, {
+ method: "GET",
+ credentials: "include",
+ cache: "no-cache",
+ headers: {
+ Cookie: (await cookies()).toString(),
+ },
+ });
+
+ const data = await res.json();
+ return data;
+ } catch {
+ // throw new Error("failed to get data");
+ return {}
+ }
+}
+export default async function DashboardStatistics() {
+ const fetchedData = await getData();
+ const {data} = fetchedData || {};
+ return (
+ <>
+
+
+
+
+ {data?.department && (
+ <>
+
+ اعضای دپارتمان
+
+ {Number(data?.department?.members).toLocaleString("fa-IR")}
+
+
+
+ پزشکان دپارتمان
+
+ {Number(data?.department?.doctors).toLocaleString("fa-IR")}
+
+
+ >
+ )}
+
+
+
+
+
+
+ {data?.staff && (
+ <>
+
+ کارشناسان سایت
+
+ {Number(data?.staff?.coordinators).toLocaleString("fa-IR")}
+
+
+
+ ادمین های سایت
+
+ {Number(data?.staff?.admins).toLocaleString("fa-IR")}
+
+
+
+ پزشکان سایت
+
+ {Number(data?.staff?.doctors).toLocaleString("fa-IR")}
+
+
+ >
+ )}
+
+
+
+
+
+
+ {data?.patients && (
+
+ تعداد کل بیماران
+
+ {Number(data?.patients?.numbers).toLocaleString("fa-IR")}
+
+
+ )}
+ {data?.onlineCases && (
+ <>
+
+
+ {" "}
+ پذیرش های کامل شده
+
+
+ {Number(data?.onlineCases?.completed).toLocaleString(
+ "fa-IR"
+ )}
+
+
+
+
+ {" "}
+ پذیرش های درحال انجام
+
+
+ {Number(data?.onlineCases?.pending).toLocaleString("fa-IR")}
+
+
+ >
+ )}
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/DeleteExpertiseButton.tsx b/src/components/DeleteExpertiseButton.tsx
new file mode 100644
index 0000000..dc58167
--- /dev/null
+++ b/src/components/DeleteExpertiseButton.tsx
@@ -0,0 +1,67 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {Trash} from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import { ServerResponseObject } from "@/types";
+
+async function deleteUser(id: number | string, route: string) {
+ try {
+ const {message} = await privateApi.delete(`/${route}/delete/${id}`) as ServerResponseObject;
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+}
+export default function DeleteExpertiseButton({
+ id,
+ route,
+}: {
+ id: number | string;
+ route: string;
+}) {
+
+ const handleDelete = async () => {
+ await deleteUser(id, route);
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+
+ حذف آیتم
+
+ آیا از حذف این آیتم اطمینان دارد ؟
+
+
+
+
+
+ انصراف
+
+
+ بله
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/DeleteFileButton.tsx b/src/components/DeleteFileButton.tsx
new file mode 100644
index 0000000..2d8473d
--- /dev/null
+++ b/src/components/DeleteFileButton.tsx
@@ -0,0 +1,40 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {Trash} from "lucide-react";
+import {useDeleteFile} from "@/hooks";
+import {handleAxiosError} from "@/lib/utils";
+import {toast} from "react-toastify";
+
+export default function DeleteFileButton({
+ type,
+ fileKey,
+ fileUrl,
+}: {
+ type: "image" | "document";
+ fileKey: string;
+ fileUrl: string;
+}) {
+ const {mutateAsync} = useDeleteFile();
+
+ const handleDeleteThumbnail = async () => {
+ try {
+ const {message} = await mutateAsync({fileKey, fileUrl, type});
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ };
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/src/components/DeleteLanguageButton.tsx b/src/components/DeleteLanguageButton.tsx
new file mode 100644
index 0000000..50f1fb8
--- /dev/null
+++ b/src/components/DeleteLanguageButton.tsx
@@ -0,0 +1,69 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {Trash} from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+import {useQueryClient} from "@tanstack/react-query";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import { ServerResponseObject } from "@/types";
+
+async function deleteUser(id: number | string, route: string) {
+ try {
+ const {message} = await privateApi.delete(`/${route}/delete/${id}`) as ServerResponseObject;
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+}
+export default function DeleteLanguageButton({
+ id,
+ route,
+}: {
+ id: number | string;
+ route: string;
+}) {
+ const queryClient = useQueryClient();
+ const handleDelete = async () => {
+ await deleteUser(id, route);
+ queryClient.invalidateQueries({queryKey: ["get-all-languages"]});
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+
+ حذف آیتم
+
+ آیا از حذف این آیتم اطمینان دارد ؟
+
+
+
+
+
+ انصراف
+
+
+ بله
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/DeleteMedicalPackageButton.tsx b/src/components/DeleteMedicalPackageButton.tsx
new file mode 100644
index 0000000..2dfe2c2
--- /dev/null
+++ b/src/components/DeleteMedicalPackageButton.tsx
@@ -0,0 +1,69 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {Trash} from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import {usePathname} from "next/navigation";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import { ServerResponseObject } from "@/types";
+
+async function deleteMedicalPackage(id: number | string, route: string) {
+ try {
+ const {message} = await privateApi.delete(`/${route}/delete/${id}`) as ServerResponseObject;
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+}
+export default function DeleteMedicalPackageButton({
+ id,
+ route,
+}: {
+ id: number | string;
+ route: string;
+}) {
+ const pathname = usePathname();
+ const handleDelete = async () => {
+ await deleteMedicalPackage(id, route);
+ window.location.pathname = pathname;
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+
+ حذف آیتم
+
+ آیا از حذف این آیتم اطمینان دارد ؟
+
+
+
+
+
+ انصراف
+
+
+ بله
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/DenyAccess.tsx b/src/components/DenyAccess.tsx
new file mode 100644
index 0000000..c927570
--- /dev/null
+++ b/src/components/DenyAccess.tsx
@@ -0,0 +1,8 @@
+import Link from 'next/link'
+import React from 'react'
+
+export default function DenyAccess({label,link}:{label:string,link:string}) {
+ return (
+ شما نمیتوانید از این بخش استفاده کنید. ابتدا می بایست حداقل یک {label} ایجاد کنید. برای ایجاد روی لینک کلیک کنید
+ )
+}
diff --git a/src/components/DepartmentMembersTable.tsx b/src/components/DepartmentMembersTable.tsx
new file mode 100644
index 0000000..37a2575
--- /dev/null
+++ b/src/components/DepartmentMembersTable.tsx
@@ -0,0 +1,260 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useSearchParams} from "next/navigation";
+import {Pencil} from "lucide-react";
+import DeleteUserButton from "./deleteUserButton";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+
+// =====================
+// Types
+// =====================
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+type Translation = {
+ displayName: string;
+ lang: string;
+};
+
+type Expertise = {
+ slug: string;
+ translations: Translation[];
+};
+
+type UserTranslation = {
+ firstName: string;
+ lastName: string;
+ lang: string;
+ position: string;
+};
+
+export type Data = {
+ id: number;
+ email: string;
+ phone: string;
+ slug: string;
+ image: string | null;
+ translations: UserTranslation[];
+ expertise: Expertise;
+};
+async function fetchData(
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ const res = await fetch(
+ `${API_URL}/user/get/all?t=department&lang=fa&page=${page}&limit=${pageSize}${
+ queries ? `&${queries}` : ""
+ }`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return res.json();
+}
+export default function DepartmentMembersTable() {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ نام
+
+
+ نام خانوادگی
+
+
+ سمت
+
+
+ تخصص
+
+
+ ایمیل
+
+
+ موبایل
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post) => (
+
+
+
+ نام
+
+ {post?.translations[0]?.firstName}
+
+
+
+
+ نام خانوادگی
+
+ {post?.translations[0]?.lastName}
+
+
+
+
+ سمت
+
+ {post.translations[0]?.position}
+
+
+
+
+ تخصص
+
+ {post?.expertise?.translations[0]?.displayName}
+
+
+
+ ایمیل
+
+ {post.email}
+
+
+
+ موبایل
+
+ {post?.phone.toString().toLocaleLowerCase("fa-IR")}
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/DoctorsTable.tsx b/src/components/DoctorsTable.tsx
new file mode 100644
index 0000000..daf3272
--- /dev/null
+++ b/src/components/DoctorsTable.tsx
@@ -0,0 +1,261 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useRouter, useSearchParams} from "next/navigation";
+import {Pencil} from "lucide-react";
+import DeleteUserButton from "./deleteUserButton";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+type Translation = {
+ displayName: string;
+ lang: string;
+};
+
+type Expertise = {
+ slug: string;
+ translations: Translation[];
+};
+
+type UserTranslation = {
+ firstName: string;
+ lastName: string;
+ lang: string;
+ position: string;
+};
+
+export type Data = {
+ id: number;
+ email: string;
+ phone: string;
+ slug: string;
+ image: string | null;
+ translations: UserTranslation[];
+ expertise: Expertise;
+};
+async function fetchData(
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ console.log("doctors table");
+
+ const res = await fetch(
+ `${API_URL}/user/get/all?t=doctor&lang=fa&page=${page}&limit=${pageSize}${
+ queries ? `&${queries}` : ""
+ }`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return res.json();
+}
+export default function DoctorsTable() {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ const router = useRouter();
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ نام
+
+
+ نام خانوادگی
+
+
+ سمت
+
+
+ تخصص
+
+
+ ایمیل
+
+
+ موبایل
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post) => (
+
+
+
+ نام
+
+ {post?.translations[0]?.firstName}
+
+
+
+
+ نام خانوادگی
+
+ {post?.translations[0]?.lastName}
+
+
+
+
+ سمت
+
+ {post.translations[0]?.position}
+
+
+
+
+ تخصص
+
+ {post?.expertise?.translations[0]?.displayName}
+
+
+
+ ایمیل
+
+ {post.email}
+
+
+
+ موبایل
+
+ {post?.phone.toString().toLocaleLowerCase("fa-IR")}
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/ExpertiseFilterBox.tsx b/src/components/ExpertiseFilterBox.tsx
new file mode 100644
index 0000000..eadb935
--- /dev/null
+++ b/src/components/ExpertiseFilterBox.tsx
@@ -0,0 +1,94 @@
+"use client";
+import React from "react";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
+import {useRouter, useSearchParams} from "next/navigation";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+
+interface Data {
+ id: number;
+ slug: string;
+ translations: {
+ id: number;
+ displayName: string;
+ lang: string;
+ }[];
+}
+async function fetchData() {
+ console.log('filters box')
+ const res = await fetch(`${API_URL}/expertise/fa/get/all/list?lang=fa`, {
+ cache: "no-store",
+ credentials: "include",
+ });
+
+ if (!res.ok) {
+
+ throw new Error("Failed to fetch data");
+ }
+
+ const data = await res.json();
+ return data;
+}
+
+export default function ExpertiseFilterBox() {
+ const urlSearchParams = useSearchParams();
+ const [data, setData] = React.useState([]);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = new URLSearchParams(urlSearchParams);
+ const router = useRouter();
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData()
+ .then((res) => {
+ if (!active) return;
+ setData(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+ const handleFilter = (value: string) => {
+ searchParams.set("e", value);
+ router.push(`/doctors${searchParams ? `?${searchParams.toString()}` : ""}`);
+ };
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ {data?.map((item: Data) => (
+
+ {item.translations[0].displayName}
+
+ ))}
+
+
+
+ >
+ );
+}
diff --git a/src/components/ExpertiseTable.tsx b/src/components/ExpertiseTable.tsx
new file mode 100644
index 0000000..1b05f34
--- /dev/null
+++ b/src/components/ExpertiseTable.tsx
@@ -0,0 +1,234 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useSearchParams} from "next/navigation";
+import {Pencil} from "lucide-react";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+import DeleteExpertiseButton from "./DeleteExpertiseButton";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+
+
+
+
+
+
+export type Data = {
+ slug: string;
+ id: number;
+ translations: {
+ displayName: string | null;
+ lang: {
+ title: string;
+ } | null;
+ }[];
+};
+async function fetchData(
+ lang: string,
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ console.log("doctors table");
+
+ const res = await fetch(
+ `${API_URL}/expertise/${
+ lang ?? "fa"
+ }/get/all?page=${page}&limit=${pageSize}${queries ? `&${queries}` : ""}`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return await res.json();
+}
+export default function ExpertiseTable({lang}: {lang: string}) {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(lang, page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ console.log(res.data);
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams, lang]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ ردیف
+
+
+ اسلاگ
+
+
+ نام
+
+
+ نام زبان
+
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post, index) => (
+
+
+
+ ردیف
+
+ {index + 1}
+
+
+
+ اسلاگ
+
+ {post?.slug}
+
+
+
+ نام
+
+ {post?.translations[0]?.displayName}
+
+
+
+
+ نام زبان
+
+ {post?.translations[0]?.lang?.title}
+
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/FeatureIcon.tsx b/src/components/FeatureIcon.tsx
new file mode 100644
index 0000000..ccead37
--- /dev/null
+++ b/src/components/FeatureIcon.tsx
@@ -0,0 +1,30 @@
+import * as Icons from "lucide-react";
+
+interface FeatureIconProps extends Icons.LucideProps {
+ iconName?: string;
+ label?: string;
+ hasLabel:boolean
+}
+
+export const FeatureIcon: React.FC = ({
+ iconName,
+ label,
+ hasLabel=true,
+ ...props
+}) => {
+ const LucideIcon =
+ iconName && iconName in Icons
+ ? (Icons[
+ iconName as keyof typeof Icons
+ ] as React.ComponentType)
+ : null;
+
+ return (
+
+ {LucideIcon && }
+ {hasLabel && {label} }
+
+ );
+};
+
+export default FeatureIcon;
diff --git a/src/components/FilterByisDeleted.tsx b/src/components/FilterByisDeleted.tsx
new file mode 100644
index 0000000..4990e38
--- /dev/null
+++ b/src/components/FilterByisDeleted.tsx
@@ -0,0 +1,45 @@
+"use client";
+import React from "react";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
+import {useRouter, useSearchParams} from "next/navigation";
+
+export default function FilterByisDeleted() {
+ const urlSearchParams = useSearchParams();
+
+ const searchParams = new URLSearchParams(urlSearchParams);
+ const router = useRouter();
+
+ const handleFilter = (value: string) => {
+ if (value === "true") {
+ searchParams.set("is_deleted", value);
+ }else{
+ searchParams.delete("is_deleted");
+ }
+ router.push(
+ `/patients${searchParams ? `?${searchParams.toString()}` : ""}`
+ );
+ };
+
+ return (
+ <>
+
+
+
+
+
+
+ همه
+ حذف شده ها
+
+
+
+ >
+ );
+}
diff --git a/src/components/LangaugesTable.tsx b/src/components/LangaugesTable.tsx
new file mode 100644
index 0000000..eac20ca
--- /dev/null
+++ b/src/components/LangaugesTable.tsx
@@ -0,0 +1,165 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import Loader from "./Loader";
+import {useRouter} from "next/navigation";
+
+import RoleGuard from "./RoleGuard";
+import DeleteLanguageButton from "./DeleteLanguageButton";
+import {useGetLanguages, useUpdateLanguage} from "@/hooks/languages";
+import {Dialog} from "./ui/dialog";
+import UpdateLanguageForm from "./forms/languages/update/UpdateLanguageForm";
+import { useQueryClient } from "@tanstack/react-query";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: T[];
+
+};
+
+export type Data = {
+ id: number;
+ title: string;
+ slug: string;
+};
+
+export default function LanguagesTable() {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const {data, isLoading} = useGetLanguages();
+ const [total] = React.useState(0);
+ const {mutateAsync, isPending} = useUpdateLanguage();
+ const totalPages = Math.ceil(total / pageSize);
+ const queryClient = useQueryClient();
+ const router = useRouter();
+
+ const languages = data ? (data?.data as Data[]) : [];
+ if (isLoading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ عنوان
+
+
+ اسلاگ
+
+
+
+ اکشن ها
+
+
+
+
+
+ {languages?.map((post) => (
+
+
+
+ عنوان
+
+ {post?.title}
+
+
+
+
+ اسلاگ
+
+ {post?.slug}
+
+
+
+
+ اکشن ها
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/LanguagesFilterBox.tsx b/src/components/LanguagesFilterBox.tsx
new file mode 100644
index 0000000..4e5ad4a
--- /dev/null
+++ b/src/components/LanguagesFilterBox.tsx
@@ -0,0 +1,91 @@
+"use client";
+import React, {SetStateAction} from "react";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
+import {useRouter, useSearchParams} from "next/navigation";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+
+interface Data {
+ id: number;
+ slug: string;
+ title:string
+}
+async function fetchData() {
+ const res = await fetch(`${API_URL}/language/get/all`, {
+ cache: "no-store",
+ credentials: "include",
+ });
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ const data = await res.json();
+ return data;
+}
+
+export default function LanguagesFilterBox({
+ lang,
+ setLang,
+}: {
+ lang: string;
+ setLang: React.Dispatch>;
+}) {
+ const urlSearchParams = useSearchParams();
+ const [data, setData] = React.useState([]);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = new URLSearchParams(urlSearchParams);
+ const router = useRouter();
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData()
+ .then((res) => {
+ if (!active) return;
+ console.log(res.data)
+ setData(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+ setLang(value)}>
+
+
+
+
+
+ {data?.map((item: Data) => (
+
+ {item.title}
+
+ ))}
+
+
+
+ >
+ );
+}
diff --git a/src/components/Loader.tsx b/src/components/Loader.tsx
new file mode 100644
index 0000000..04c22b0
--- /dev/null
+++ b/src/components/Loader.tsx
@@ -0,0 +1,13 @@
+import React from "react";
+import { Spinner } from "./ui/spinner";
+
+export default function Loader({size="size-6"}:{size?:string}) {
+ return (
+ <>
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/MedicalPackagesTable.tsx b/src/components/MedicalPackagesTable.tsx
new file mode 100644
index 0000000..a1849e0
--- /dev/null
+++ b/src/components/MedicalPackagesTable.tsx
@@ -0,0 +1,261 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import { API_URL } from "@/constants";
+import Loader from "./Loader";
+import { useSearchParams } from "next/navigation";
+import { Pencil } from "lucide-react";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+import DeleteMedicalPackageButton from "./DeleteMedicalPackageButton";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+
+export type Data = {
+ translations: {
+ id: number;
+ title: string;
+ content: string | null;
+ lang_id: number | null;
+ medicalPackageId: number | null;
+ }[];
+ children: {
+ icon: string | null;
+ priority: number | null;
+ id: number;
+ thumbnail_id: number;
+ parent_id: number | null;
+ }[];
+} & {
+ icon: string | null;
+ priority: number | null;
+ id: number;
+ thumbnail_id: number;
+ parent_id: number | null;
+};
+async function fetchData(
+ lang: string,
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ const res = await fetch(
+ `${API_URL}/medical-packages/${lang}/get/all?page=${page}&limit=${pageSize}${
+ queries ? `&${queries}` : ""
+ }`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return res.json();
+}
+export default function MedicalPackagesTable({ lang }: { lang: string }) {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(lang, page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams, lang]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ شناسه
+
+
+ عنوان
+
+
+
+ تعداد زیرمجموعه
+
+
+ آیکون
+
+
+ الویت
+
+
+ شناسه والد
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post) => (
+
+
+
+ شناسه
+
+ {post?.id}
+
+
+
+ عنوان
+
+ {post?.translations[0]?.title}
+
+
+
+
+ تعداد زیر مجموعه
+
+ {post.children.length
+ ? Number(post.children.length).toLocaleString("fa-IR")
+ : "-"}
+
+
+
+
+ آیکون
+
+ {post?.icon ?? "-"}
+
+
+
+ اولویت نمایش
+
+ {post.priority}
+
+
+
+ شناسه والد
+
+ {post?.parent_id}
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({ length: totalPages }).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/OnlineCasesTable.tsx b/src/components/OnlineCasesTable.tsx
new file mode 100644
index 0000000..770c945
--- /dev/null
+++ b/src/components/OnlineCasesTable.tsx
@@ -0,0 +1,244 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useRouter, useSearchParams} from "next/navigation";
+import {Pencil} from "lucide-react";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+import DeleteExpertiseButton from "./DeleteExpertiseButton";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+type Translation = {
+ displayName: string;
+ lang: string;
+};
+
+type Expertise = {
+ slug: string;
+ translations: Translation[];
+};
+
+type UserTranslation = {
+ firstName: string;
+ lastName: string;
+ lang: string;
+ position: string;
+};
+
+export type Data = {
+ slug: string;
+ id: number;
+ translations: {
+ displayName: string | null;
+ lang: {
+ title: string;
+ } | null;
+ }[];
+};
+async function fetchData(
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ console.log("doctors table");
+
+ const res = await fetch(
+ `${API_URL}/case/get/all?page=${page}&limit=${pageSize}${queries ? `&${queries}` : ""}`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return await res.json();
+}
+export default function OnlineCasesTable() {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ console.log(data);
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ const router = useRouter();
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ console.log(res.data);
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ ردیف
+
+
+ اسلاگ
+
+
+ نام
+
+
+ نام زبان
+
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post, index) => (
+
+
+
+ ردیف
+
+ {index + 1}
+
+
+
+ اسلاگ
+
+ {post?.slug}
+
+
+
+ نام
+
+ {post?.translations[0]?.displayName}
+
+
+
+
+ نام زبان
+
+ {post?.translations[0]?.lang?.title}
+
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/PanelQueryProvider.tsx b/src/components/PanelQueryProvider.tsx
new file mode 100644
index 0000000..5ef0b32
--- /dev/null
+++ b/src/components/PanelQueryProvider.tsx
@@ -0,0 +1,19 @@
+"use client";
+import {QueryClient, QueryClientProvider} from "@tanstack/react-query";
+import React from "react";
+
+export default function PanelQueryProvider({
+ children,
+}: Readonly<{
+ children: React.ReactNode;
+}>) {
+ const queryClient = new QueryClient();
+ return (
+ <>
+
+
+ {children}
+
+ >
+ );
+}
diff --git a/src/components/PatientsTable.tsx b/src/components/PatientsTable.tsx
new file mode 100644
index 0000000..21fa5a7
--- /dev/null
+++ b/src/components/PatientsTable.tsx
@@ -0,0 +1,326 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useSearchParams} from "next/navigation";
+import {Pencil} from "lucide-react";
+import DeleteUserButton from "./deleteUserButton";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+import RestoreUserButton from "./restoreUserButton";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+
+export type Data = {
+ id: string;
+ pid: string;
+ firstName: string;
+ lastName: string;
+ nationality: {
+ id: number;
+ name: string;
+ callCode: string;
+ };
+ nationalityCode: string | null;
+ passportCode: string | null;
+ countryCode: string | null;
+ phone: string | null;
+ email: string | null;
+ preferredLanguage: string | null;
+ age: number | null;
+ address: string | null;
+ birthDate: Date | null;
+ sex: "male" | "female" | "other" | null;
+ postalCode: string | null;
+ createdAt: Date;
+ documents: {
+ id: string;
+ createdAt: Date;
+ caseId: string | null;
+ patientId: string | null;
+ uploadedById: string | null;
+ type: DocumentType;
+ }[];
+};
+async function fetchData(
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ const res = await fetch(
+ `${API_URL}/patient/get/all?lang=fa&page=${page}&limit=${pageSize}${
+ queries ? `&${queries}` : ""
+ }`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return res.json();
+}
+export default function PatientsTable() {
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const URLSearch = useSearchParams();
+
+ const searchParams = new URLSearchParams(URLSearch);
+ const is_deleted = searchParams.get("is_deleted") ? true : false;
+ const totalPages = Math.ceil(total / pageSize);
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(page, pageSize, URLSearch.toString())
+ .then((res) => {
+ if (!active) return;
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, URLSearch]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ شناسه
+
+
+ نام
+
+
+ نام خانوادگی
+
+
+ کد ملی / کد پاسپورت
+
+
+ جنسیت
+
+
+
+ تاریخ تولد
+
+
+ کشور
+
+
+ شماره تماس
+
+
+ ایمیل
+
+
+ آدرس
+
+
+ زبان ترجیحی
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post) => (
+
+
+
+ شناسه
+
+ {post?.pid}
+
+
+
+ نام
+
+ {post?.firstName}
+
+
+
+
+ نام خانوادگی
+
+ {post?.lastName}
+
+
+
+
+ کد ملی / کدپاسپورت
+
+ {post?.nationalityCode} / {post?.passportCode}
+
+
+
+ جنسیت
+
+ {post?.sex === "male"
+ ? "مرد"
+ : post.sex === "female"
+ ? "زن"
+ : "سایر"}
+
+
+
+
+ تاریخ تولد
+
+ {post?.birthDate &&
+ new Date(post?.birthDate).toLocaleDateString()}
+
+
+
+ کشور
+
+ {post?.nationality?.name}
+
+
+
+ شماره تماس
+
+ {post?.phone &&
+ post?.phone.toString().toLocaleLowerCase("fa-IR")}
+
+
+
+ ایمیل
+
+ {post?.email}
+
+
+
+ آدرس
+
+ {post?.address}
+
+
+
+ زبان ترجیحی
+
+ {post?.preferredLanguage}
+
+
+
+
+ اکشن ها
+
+
+
+ {!is_deleted && (
+
+ )}
+ {is_deleted && (
+
+ )}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/PersianMonthDropdown.tsx b/src/components/PersianMonthDropdown.tsx
new file mode 100644
index 0000000..356a9b1
--- /dev/null
+++ b/src/components/PersianMonthDropdown.tsx
@@ -0,0 +1,32 @@
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { PERSIAN_MONTHS } from "@/constants";
+
+export default function PersianMonthDropdown({
+ value,
+ onChange,
+}: {
+ value: number;
+ onChange: (month: number) => void;
+}) {
+ return (
+ onChange(Number(v))}>
+
+
+
+
+
+ {PERSIAN_MONTHS.map((month, index) => (
+
+ {month}
+
+ ))}
+
+
+ );
+}
diff --git a/src/components/PrivateRoute.tsx b/src/components/PrivateRoute.tsx
new file mode 100644
index 0000000..d92e88f
--- /dev/null
+++ b/src/components/PrivateRoute.tsx
@@ -0,0 +1,18 @@
+"use client";
+import { useEffect } from "react";
+import { useRouter } from "next/navigation";
+
+export default function PrivateRoute({ children }: { children: React.ReactNode }) {
+ const router = useRouter();
+ const isLoggedIn = Boolean(localStorage.getItem("auth_token")); // یا useSession
+
+ useEffect(() => {
+ if (!isLoggedIn) {
+ router.push("/login");
+ }
+ }, [isLoggedIn, router]);
+
+ if (!isLoggedIn) return null; // یا loader
+
+ return <>{children}>;
+}
diff --git a/src/components/ProfileDropdown.tsx b/src/components/ProfileDropdown.tsx
new file mode 100644
index 0000000..fee6bcf
--- /dev/null
+++ b/src/components/ProfileDropdown.tsx
@@ -0,0 +1,50 @@
+"use client";
+import {
+ DropdownMenu,
+ DropdownMenuContent,
+ DropdownMenuItem,
+ DropdownMenuSeparator,
+ DropdownMenuTrigger,
+} from "@radix-ui/react-dropdown-menu";
+import React from "react";
+import {Button} from "./ui/button";
+
+import ProfileLogoutDropdown from "./ProfileLogoutDropdown";
+import {useQueryClient} from "@tanstack/react-query";
+import {ChevronDown, User} from "lucide-react";
+import { MeResponse } from "@/hooks";
+
+export default function ProfileDropdown() {
+ const qc = useQueryClient();
+ const user:MeResponse = qc.getQueryData(["me"])!;
+
+ return (
+
+
+
+
+ <>
+
+
+ {user?.data?.username || "کاربر سیستم"}
+ {/* (
+ {user?.data?.translations[0]?.displayName}) */}
+
+
+ >
+
+
+
+
+ ویرایش پروفایل
+
+
+
+
+
+
+ );
+}
diff --git a/src/components/ProfileLogoutDropdown.tsx b/src/components/ProfileLogoutDropdown.tsx
new file mode 100644
index 0000000..8a1c16e
--- /dev/null
+++ b/src/components/ProfileLogoutDropdown.tsx
@@ -0,0 +1,59 @@
+"use client";
+import React from "react";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+import {Button} from "./ui/button";
+import {useLogoutUser} from "@/hooks/users";
+import {toast} from "react-toastify";
+import Loader from "./Loader";
+import {useRouter} from "next/navigation";
+
+export default function ProfileLogoutDropdown() {
+ const {mutateAsync, isPending} = useLogoutUser();
+
+ const router = useRouter();
+ const handleLogout = async () => {
+ try {
+ const {message} = await mutateAsync();
+ router.push("/login");
+ } catch (error) {
+ toast.error("asdasdsad");
+ }
+ };
+ return (
+ <>
+
+
+
+ >
+ );
+}
diff --git a/src/components/RefreshCDNStatus.tsx b/src/components/RefreshCDNStatus.tsx
new file mode 100644
index 0000000..56409a8
--- /dev/null
+++ b/src/components/RefreshCDNStatus.tsx
@@ -0,0 +1,21 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {RefreshCw} from "lucide-react";
+import { useRouter } from "next/navigation";
+
+export default function RefreshCDNStatus() {
+ const router = useRouter();
+ return (
+ <>
+ router.refresh()}
+ variant={"outline"}
+ className="text-primary font-medium text-sm"
+ >
+ Refresh
+
+
+ >
+ );
+}
diff --git a/src/components/RestorePatientTable.tsx b/src/components/RestorePatientTable.tsx
new file mode 100644
index 0000000..51e004a
--- /dev/null
+++ b/src/components/RestorePatientTable.tsx
@@ -0,0 +1,320 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useSearchParams} from "next/navigation";
+import {Pencil} from "lucide-react";
+import DeleteUserButton from "./deleteUserButton";
+import Link from "next/link";
+import RoleGuard from "./RoleGuard";
+import {Field, FieldLabel} from "./ui/field";
+import {Input} from "./ui/input";
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+
+export type Data = {
+ id: string;
+ firstName: string;
+ lastName: string;
+ nationality: {
+ id: number;
+ name: string;
+ callCode: string;
+ };
+ nationalityCode: string | null;
+ passportCode: string | null;
+ countryCode: string | null;
+ phone: string | null;
+ email: string | null;
+ preferredLanguage: string | null;
+ age: number | null;
+ address: string | null;
+ birthDate: Date | null;
+ sex: "male" | "female" | "other" | null;
+ postalCode: string | null;
+ createdAt: Date;
+ documents: {
+ id: string;
+ createdAt: Date;
+ caseId: string | null;
+ patientId: string | null;
+ uploadedById: string | null;
+ type: DocumentType;
+ }[];
+};
+async function fetchData(
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ const res = await fetch(
+ `${API_URL}/patient/get/all?lang=fa&page=${page}&limit=${pageSize}${
+ queries ? `&${queries}` : ""
+ }`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return res.json();
+}
+export default function RestorePatientTable() {
+ const [id, setID] = React.useState("");
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+ شناسه بیمار
+ setID(e.target.value)}
+ />
+
+
+
+
+
+
+
+ نام
+
+
+ نام خانوادگی
+
+
+ کد ملی / کد پاسپورت
+
+
+ جنسیت
+
+
+
+ تاریخ تولد
+
+
+ کشور
+
+
+ شماره تماس
+
+
+ ایمیل
+
+
+ آدرس
+
+
+ زبان ترجیحی
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post) => (
+
+
+
+ نام
+
+ {post?.firstName}
+
+
+
+
+ نام خانوادگی
+
+ {post?.lastName}
+
+
+
+
+ کد ملی / کدپاسپورت
+
+ {post?.nationalityCode} / {post?.passportCode}
+
+
+
+ جنسیت
+
+ {post?.sex === "male"
+ ? "مرد"
+ : post.sex === "female"
+ ? "زن"
+ : "سایر"}
+
+
+
+
+ تاریخ تولد
+
+ {post?.birthDate &&
+ new Date(post?.birthDate).toLocaleDateString()}
+
+
+
+ کشور
+
+ {post?.nationality?.name}
+
+
+
+ شماره تماس
+
+ {post?.phone &&
+ post?.phone.toString().toLocaleLowerCase("fa-IR")}
+
+
+
+ ایمیل
+
+ {post?.email}
+
+
+
+ آدرس
+
+ {post?.address}
+
+
+
+ زبان ترجیحی
+
+ {post?.preferredLanguage}
+
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/RoleGuard.tsx b/src/components/RoleGuard.tsx
new file mode 100644
index 0000000..77ac9a6
--- /dev/null
+++ b/src/components/RoleGuard.tsx
@@ -0,0 +1,24 @@
+"use client";
+
+import {StaffRoles} from "@/constants";
+import {useMe} from "@/hooks";
+
+export default function RoleGuard({
+ roles,
+ children,
+}: {
+ roles: StaffRoles[];
+ children: React.ReactNode;
+}) {
+ const {data, isLoading} = useMe();
+
+ if (isLoading) return null; // یا Skeleton
+
+ if (!data) return null;
+
+ const userRole = data.data.role.toUpperCase() as StaffRoles;
+ console.log(!roles.includes(userRole));
+ if (!roles.includes(userRole)) return null;
+
+ return <>{children}>;
+}
diff --git a/src/components/SearchBox.tsx b/src/components/SearchBox.tsx
new file mode 100644
index 0000000..05c7153
--- /dev/null
+++ b/src/components/SearchBox.tsx
@@ -0,0 +1,63 @@
+"use client";
+import React from "react";
+import {Field} from "./ui/field";
+import {Input} from "./ui/input";
+import {useRouter, useSearchParams} from "next/navigation";
+import {Label} from "./ui/label";
+
+export default function SearchBox({
+ placeholder,
+ label,
+ hasLabel=false,
+ queryName,
+ inputName,
+ route,
+}: {
+ placeholder?: string;
+ queryName?: string;
+ inputName: string;
+ label?:string,
+ hasLabel?:boolean
+ route: string;
+}) {
+ const router = useRouter();
+ const urlSearchParams = useSearchParams();
+ const searchParams = new URLSearchParams(urlSearchParams);
+ const debounce = (onChange: (v: string) => void) => {
+ let timeout: ReturnType;
+ return (e: React.ChangeEvent) => {
+ const form = e.currentTarget.value;
+ clearTimeout(timeout);
+ timeout = setTimeout(() => {
+ onChange(form);
+ }, 1000);
+ };
+ };
+ const handleSearch = async (value: string) => {
+ try {
+ searchParams.set(`${queryName ?? inputName}`, value);
+ router.push(
+ `/${route}${searchParams ? `?${searchParams.toString()}` : ""} `
+ );
+ } catch (error) {
+ console.error("Error fetching data:", error);
+ }
+ };
+
+ return (
+ <>
+
+ {hasLabel && {label} }
+ {
+ handleSearch(e);
+ })}
+ defaultValue={searchParams.get(`${queryName ?? inputName}`) ?? ""}
+ name={inputName}
+ className="bg-white w-[240px] flex-1"
+ />
+
+ >
+ );
+}
diff --git a/src/components/SectionTitle.tsx b/src/components/SectionTitle.tsx
new file mode 100644
index 0000000..4b0ff60
--- /dev/null
+++ b/src/components/SectionTitle.tsx
@@ -0,0 +1,11 @@
+import React from "react";
+
+export default function SectionTitle({label}: {label: string}) {
+ return (
+ <>
+
+ {label}
+
+ >
+ );
+}
diff --git a/src/components/SelectCalendar.tsx b/src/components/SelectCalendar.tsx
new file mode 100644
index 0000000..af9a5d0
--- /dev/null
+++ b/src/components/SelectCalendar.tsx
@@ -0,0 +1,58 @@
+"use client";
+import React from "react";
+import {CalendarNormal} from "./CalendarNormal";
+import {CalendarHijri} from "./CalendarHijri";
+import {Label} from "./ui/label";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
+
+export default function SelectCalendar({
+ date,
+ setDate,
+}: {
+ date: Date | undefined;
+ setDate: React.Dispatch>;
+}) {
+ const [dropdown, setDropdown] = React.useState<"persian" | "general">(
+ "general"
+ );
+
+ return (
+
+
+
+ نوع تقویم را انتخاب کنید
+
+ setDropdown(value as "general" | "persian")}
+ >
+
+
+
+
+ میلادی
+ شمسی
+
+
+
+
+
+ تاریخ تولد
+
+
+ {dropdown === "general" ? : }
+
+
+ );
+}
diff --git a/src/components/SideMenu.tsx b/src/components/SideMenu.tsx
new file mode 100644
index 0000000..e889287
--- /dev/null
+++ b/src/components/SideMenu.tsx
@@ -0,0 +1,112 @@
+"use client";
+import React, {useEffect, useState} from "react";
+import {Button} from "./ui/button";
+import {ChevronRight} from "lucide-react";
+import Link from "next/link";
+import {SIDE_MENU_TYPE, SIDE_MENUS, STAFF_ROLE_TYPE} from "@/constants";
+import FeatureIcon from "./FeatureIcon";
+import {useRouter} from "next/navigation";
+import {getSideMenusByRole} from "@/lib/utils";
+import {useMe} from "@/hooks";
+
+export default function SideMenu() {
+ const {data} = useMe();
+ const [isOpen, setIsOpen] = useState(false);
+ const router = useRouter();
+ const [selected, setSelected] = useState();
+ useEffect(() => {
+ setIsOpen(false);
+ }, [router]);
+ const visibleMenus = getSideMenusByRole(
+ SIDE_MENUS,
+ data?.data?.role
+ ? (data?.data?.role.toUpperCase() as STAFF_ROLE_TYPE)
+ : "COORDINATOR"
+ );
+ return (
+ <>
+
+
+ setIsOpen(false)}
+ variant={"ghost"}
+ className="text-white"
+ >
+
+
+
+
+
+
+
+ {selected?.label}
+
+
+ {selected?.sub &&
+ selected.sub.items.length > 0 &&
+ isOpen === true &&
+ selected?.sub?.items.map((item) => {
+ if (item.show || item.show === undefined) {
+ return (
+
+
+ {item.label}
+
+ {item.hasLine && (
+
+ )}
+
+ );
+ }
+ })}
+
+
+
+ >
+ );
+}
diff --git a/src/components/TextEditor.tsx b/src/components/TextEditor.tsx
new file mode 100644
index 0000000..f672341
--- /dev/null
+++ b/src/components/TextEditor.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import React, {
+ useEffect,
+ useRef,
+ forwardRef,
+ useImperativeHandle,
+} from "react";
+import Quill from "quill";
+import "quill/dist/quill.snow.css";
+
+export type RichTextEditorHandle = {
+ getContent: () => string;
+};
+
+type Props = {
+ value?: string; // ✅ مقدار اولیه
+};
+const toolbarOptions = [
+ ['bold', 'italic', 'underline', 'strike'], // toggled buttons
+ ['blockquote', 'code-block'],
+ ['link', 'image', ],
+
+ [{ 'header': 1 }, { 'header': 2 }], // custom button values
+ [{ 'list': 'ordered'}, { 'list': 'bullet' }, { 'list': 'check' }],
+ [{ 'indent': '-1'}, { 'indent': '+1' }], // outdent/indent
+ [{ 'direction': 'rtl' }], // text direction
+
+ [{ 'size': ['small', false, 'large', 'huge'] }], // custom dropdown
+ [{ 'header': [1, 2, 3, 4, 5, 6, false] }],
+
+ [{ 'color': [] }, { 'background': [] }], // dropdown with defaults from theme
+ [{ 'font': [] }],
+ [{ 'align': [] }],
+
+ ['clean'] // remove formatting button
+];
+const RichTextEditor = forwardRef(
+ ({ value }, ref) => {
+ const editorRef = useRef(null);
+ const quillRef = useRef(null);
+
+ // init editor
+ useEffect(() => {
+ if (!editorRef.current || quillRef.current) return;
+
+ quillRef.current = new Quill(editorRef.current, {
+ theme: "snow",
+ modules: {
+ toolbar:toolbarOptions,
+ },
+ placeholder: "شروع به نوشتن...",
+ });
+
+ const editorEl =
+ editorRef.current.querySelector(".ql-editor");
+ if (editorEl) {
+ editorEl.setAttribute("dir", "rtl");
+ editorEl.style.textAlign = "right";
+ }
+ }, []);
+
+ // ✅ set content when value changes
+ useEffect(() => {
+ if (quillRef.current && value !== undefined) {
+ quillRef.current.clipboard.dangerouslyPasteHTML(value);
+ }
+ }, [value]);
+
+ useImperativeHandle(ref, () => ({
+ getContent: () => quillRef.current?.root.innerHTML || "",
+ }));
+
+ return (
+
+ );
+ },
+);
+
+RichTextEditor.displayName = "RichTextEditor";
+export default RichTextEditor;
diff --git a/src/components/TransferTeamTable.tsx b/src/components/TransferTeamTable.tsx
new file mode 100644
index 0000000..a6153c7
--- /dev/null
+++ b/src/components/TransferTeamTable.tsx
@@ -0,0 +1,263 @@
+"use client";
+
+import * as React from "react";
+import {
+ Pagination,
+ PaginationContent,
+ PaginationItem,
+ PaginationLink,
+ PaginationNext,
+ PaginationPrevious,
+} from "@/components/ui/pagination";
+import {API_URL} from "@/constants";
+import Loader from "./Loader";
+import {useRouter, useSearchParams} from "next/navigation";
+import {Pencil, Trash} from "lucide-react";
+import DeleteUserButton from "./deleteUserButton";
+import Link from "next/link";
+import {useMe} from "@/hooks";
+import RoleGuard from "./RoleGuard";
+
+// =====================
+// Types
+// =====================
+
+export type ApiResponse = {
+ status: number;
+ message: string;
+ data: {
+ data: T[];
+ page: string;
+ limit: string;
+ total: number;
+ totalPages: number;
+ hasNext: boolean;
+ hasPrev: boolean;
+ };
+};
+type Translation = {
+ displayName: string;
+ lang: string;
+};
+
+type Expertise = {
+ slug: string;
+ translations: Translation[];
+};
+
+type UserTranslation = {
+ firstName: string;
+ lastName: string;
+ lang: string;
+ position: string;
+};
+
+export type Data = {
+ id: number;
+ email: string;
+ phone: string;
+ slug: string;
+ image: string | null;
+ translations: UserTranslation[];
+ expertise: Expertise;
+};
+async function fetchData(
+ page: number,
+ pageSize: number,
+ queries?: string,
+): Promise> {
+ const res = await fetch(
+ `${API_URL}/user/get/all?t=transfer_team&lang=fa&page=${page}&limit=${pageSize}${
+ queries ? `&${queries}` : ""
+ }`,
+ {
+ cache: "no-store",
+ credentials: "include",
+ },
+ );
+
+ if (!res.ok) {
+ throw new Error("Failed to fetch data");
+ }
+
+ return res.json();
+}
+export default function TransferTeamTable() {
+ const {data: user} = useMe();
+ const [page, setPage] = React.useState(1);
+ const [pageSize] = React.useState(20);
+ const [data, setData] = React.useState([]);
+ const [total, setTotal] = React.useState(0);
+ const [loading, setLoading] = React.useState(false);
+
+ const searchParams = useSearchParams();
+ const totalPages = Math.ceil(total / pageSize);
+
+ const router = useRouter();
+ React.useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchData(page, pageSize, searchParams.toString())
+ .then((res) => {
+ if (!active) return;
+ setData(res.data?.data);
+ setTotal(res.data?.total);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, [page, pageSize, searchParams]);
+ if (loading) {
+ return (
+
+
+
+ );
+ }
+ return (
+ <>
+
+
+
+
+
+
+ نام
+
+
+ نام خانوادگی
+
+
+ سمت
+
+
+ تخصص
+
+
+ ایمیل
+
+
+ موبایل
+
+
+ اکشن ها
+
+
+
+
+
+ {data.map((post) => (
+
+
+
+ نام
+
+ {post?.translations[0]?.firstName}
+
+
+
+
+ نام خانوادگی
+
+ {post?.translations[0]?.lastName}
+
+
+
+
+ سمت
+
+ {post.translations[0]?.position}
+
+
+
+
+ تخصص
+
+ {post?.expertise?.translations[0]?.displayName}
+
+
+
+ ایمیل
+
+ {post.email}
+
+
+
+ موبایل
+
+ {post?.phone.toString().toLocaleLowerCase("fa-IR")}
+
+
+
+ اکشن ها
+
+
+
+
+ ))}
+
+
+
+ {total > 0 && (
+
+
+
+ setPage((p) => Math.max(1, p - 1))}
+ className={
+ page === 1
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+ {Array.from({length: totalPages}).map((_, i) => {
+ const pageNumber = i + 1;
+ return (
+
+ setPage(pageNumber)}
+ className="cursor-pointer"
+ >
+ {pageNumber}
+
+
+ );
+ })}
+
+
+ setPage((p) => Math.min(totalPages, p + 1))}
+ className={
+ page === totalPages
+ ? "pointer-events-none opacity-50"
+ : "cursor-pointer"
+ }
+ />
+
+
+
+ )}
+ >
+ );
+}
diff --git a/src/components/deleteUserButton.tsx b/src/components/deleteUserButton.tsx
new file mode 100644
index 0000000..a2a3ddc
--- /dev/null
+++ b/src/components/deleteUserButton.tsx
@@ -0,0 +1,69 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {Trash} from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import {usePathname} from "next/navigation";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import { ServerResponseObject } from "@/types";
+
+async function deleteUser(id: number | string, route: string) {
+ try {
+ const {message} = await privateApi.delete(`/${route}/delete/${id}`) as ServerResponseObject;
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+}
+export default function DeleteUserButton({
+ id,
+ route,
+}: {
+ id: number | string;
+ route: string;
+}) {
+ const pathname = usePathname();
+ const handleDelete = async () => {
+ await deleteUser(id, route);
+ window.location.pathname = pathname;
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+
+ حذف آیتم
+
+ آیا از حذف این آیتم اطمینان دارد ؟
+
+
+
+
+
+ انصراف
+
+
+ بله
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/forms/configs/ConfigsForm.ts b/src/components/forms/configs/ConfigsForm.ts
new file mode 100644
index 0000000..6f3550f
--- /dev/null
+++ b/src/components/forms/configs/ConfigsForm.ts
@@ -0,0 +1,45 @@
+"use client";
+import {withFormik} from "formik";
+
+import {toast} from "react-toastify";
+import InnerConfigListForm from "./InnerConfigListForm";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import {ServerResponse} from "@/types";
+export interface ConfigFormItem {
+ key: string;
+ value: string;
+ description?: string;
+}
+
+export interface ConfigFormValues {
+ configs: ConfigFormItem[];
+}
+export interface ConfigListFormProps {
+ router: AppRouterInstance;
+ initialData: ConfigFormItem[]; // 👈 دیتای ورودی تو
+ onSubmit: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ ConfigFormItem[],
+ ServerResponse
+ >;
+ updatePending: boolean;
+}
+
+const ConfigListForm = withFormik({
+ mapPropsToValues: (props) => ({
+ configs: props.initialData, // 👈 اینجاست که فرم پر میشه
+ }),
+
+ handleSubmit: async (values, {props}) => {
+ try {
+ await props.onSubmit(values.configs);
+ toast.success("تنظیمات ذخیره شد");
+ } catch (error) {
+ toast.error("خطایی رخ داده است");
+ }
+ },
+})(InnerConfigListForm);
+
+export default ConfigListForm;
diff --git a/src/components/forms/configs/InnerConfigListForm.tsx b/src/components/forms/configs/InnerConfigListForm.tsx
new file mode 100644
index 0000000..5ed4d80
--- /dev/null
+++ b/src/components/forms/configs/InnerConfigListForm.tsx
@@ -0,0 +1,61 @@
+"use client";
+import React from "react";
+import { Form, FormikProps } from "formik";
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Button } from "@/components/ui/button";
+import { ConfigFormValues, ConfigListFormProps } from "./ConfigsForm";
+
+export default function InnerConfigListForm(
+ props: FormikProps & ConfigListFormProps
+) {
+ const { values, setFieldValue } = props;
+
+ console.log(values.configs)
+ return (
+
+ );
+}
diff --git a/src/components/forms/default/InnerUpdateDefaultForm.tsx b/src/components/forms/default/InnerUpdateDefaultForm.tsx
new file mode 100644
index 0000000..2918f4c
--- /dev/null
+++ b/src/components/forms/default/InnerUpdateDefaultForm.tsx
@@ -0,0 +1,187 @@
+"use client";
+import SectionTitle from "@/components/SectionTitle";
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { Form, FormikProps, ErrorMessage } from "formik";
+import { Headset, Instagram, Linkedin, Mail, Map, MapPin } from "lucide-react";
+import React, { useRef, useState } from "react";
+import {
+ UpdateDefaultFormProps,
+ UpdateDefaultFormValues,
+} from "./UpdateDefaultsForm";
+import { Textarea } from "@/components/ui/textarea";
+import { Button } from "@/components/ui/button";
+import RichTextEditor, { RichTextEditorHandle } from "@/components/TextEditor";
+import Loader from "@/components/Loader";
+
+export default function InnerUpdateDefaultForm(
+ props: FormikProps & UpdateDefaultFormProps,
+) {
+ const { values, setFieldValue } = props;
+ const aboutUsTextRef = useRef(null);
+ const [aboutUsTextRefs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(aboutUsTextRef))),
+ );
+
+ const patiensRightsRef = useRef(null);
+ const [patientsRightsRefs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(patiensRightsRef))),
+ );
+ const handleSave = () => {
+ aboutUsTextRefs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.aboutUsText`, r.current.getContent());
+ });
+ patientsRightsRefs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.patientsRights`, r.current.getContent());
+ });
+ props.handleSubmit();
+ };
+ return (
+
+ {/* ---------- Static fields ---------- */}
+
+
+
+
+
+ ایمیل
+
+ setFieldValue("email", e.target.value)}
+ />
+
+
+
+
+
+ اینستاگرام
+
+ setFieldValue("instagramLink", e.target.value)}
+ />
+
+
+
+
+ لینکدین
+
+ setFieldValue("linkedinLink", e.target.value)}
+ />
+
+
+
+
+ گوگل مپ
+
+ setFieldValue("mapAddress", e.target.value)}
+ />
+
+
+
+
+ تلفن
+
+ setFieldValue("hospitalPhone", e.target.value)}
+ />
+
+
+
+ {/* ---------- Translations ---------- */}
+ {values.translations?.map((tr, index) => {
+ const lang = props.languages.find((l) => l.id === tr.languageId);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* Address */}
+
+
+ آدرس
+
+
+ setFieldValue(`translations.${index}.address`, e.target.value)
+ }
+ />
+
+
+
+ {/* Under logo text */}
+
+ متن زیر لوگو
+
+ setFieldValue(
+ `translations.${index}.underLogoText`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+
+ متن معرفی دپارتمان در صفحه ی درباره ما
+
+
+
+
+
+
+ منشور حقوق بیماران
+
+
+
+
+
+ );
+ })}
+
+
+ {props.updatePending ? : " ثبت اطلاعات"}
+
+
+ );
+}
diff --git a/src/components/forms/default/UpdateDefaultsForm.ts b/src/components/forms/default/UpdateDefaultsForm.ts
new file mode 100644
index 0000000..3c68af5
--- /dev/null
+++ b/src/components/forms/default/UpdateDefaultsForm.ts
@@ -0,0 +1,152 @@
+"use client";
+import {withFormik} from "formik";
+import InnerUpdateDefaultForm from "./InnerUpdateDefaultForm";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import {toast} from "react-toastify";
+import {Language, ServerResponse} from "@/types";
+import {handleAxiosError} from "@/lib/utils";
+import * as Yup from "yup";
+
+export const translationSchema = Yup.object({
+ address: Yup.string().required("آدرس الزامیست"),
+
+ underLogoText: Yup.string()
+ // .max(255, "متن زیر لوگو نباید بیشتر از 255 کاراکتر باشد")
+ .required("متن زیر لوگو الزامیست"),
+});
+
+export const updateDefaultsValidationSchema = Yup.object({
+ email: Yup.string()
+ .email("فرمت ایمیل صحیح نیست")
+ .required("ایمیل الزامی است"),
+
+ hospitalPhone: Yup.string()
+ .required("شماره تلفن الزامی است")
+ .matches(
+ /^[0-9+\-() ]+$/,
+ "شماره تلفن فقط میتواند شامل عدد و + - ( ) باشد"
+ )
+ .min(8, "شماره تلفن کوتاه است"),
+
+ mapAddress: Yup.string()
+ .required("آدرس الزامی است")
+ .min(5, "آدرس خیلی کوتاه است"),
+
+ instagramLink: Yup.string()
+ .nullable()
+ .notRequired()
+ .url("لینک اینستاگرام معتبر نیست"),
+
+ linkedinLink: Yup.string()
+ .nullable()
+ .notRequired()
+ .url("لینک لینکدین معتبر نیست"),
+
+ logoUrl: Yup.string().nullable().notRequired().url("لینک لوگو معتبر نیست"),
+ translations: Yup.array()
+ .of(translationSchema)
+ .min(1, "حداقل یک ترجمه الزامی است")
+ .required("وارد کردن ترجمه ها الزامیست"),
+});
+
+/* ---------- TYPES ---------- */
+
+export interface DefaultTranslationInput {
+ languageId: number;
+ address: string;
+ underLogoText: string;
+ aboutUsText:string;
+ patientsRights:string;
+}
+
+export interface SiteDefaultDataType {
+ id: string;
+
+ email: string;
+ hospitalPhone: string;
+ mapAddress: string;
+
+ instagramLink?: string | null;
+ linkedinLink?: string | null;
+ logoUrl?: string | null;
+
+ translations: DefaultTranslationInput[];
+
+ updatedAt: string;
+ createdAt?: string;
+}
+
+export interface UpdateDefaultFormValues {
+ email: string;
+ instagramLink: string;
+ linkedinLink: string;
+ hospitalPhone: string;
+ mapAddress: string;
+ logo?: string | null;
+ translations: DefaultTranslationInput[];
+}
+
+export type UpdateDefaultsVariables = {
+ data: UpdateDefaultFormValues;
+ id: string;
+};
+
+export interface UpdateDefaultFormProps {
+ router: AppRouterInstance;
+ languages: Language[];
+
+ updateFn: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ {data: UpdateDefaultFormValues},
+ ServerResponse
+ >;
+ updatePending: boolean;
+ preValues: SiteDefaultDataType;
+ preValuesLoading: boolean;
+}
+
+/* ---------- FORM ---------- */
+
+const UpdateDefaultForm = withFormik<
+ UpdateDefaultFormProps,
+ UpdateDefaultFormValues
+>({
+ enableReinitialize: true,
+ mapPropsToValues: (props) => ({
+ email: props?.preValues?.email || "",
+ instagramLink: props?.preValues?.instagramLink || "",
+ linkedinLink: props?.preValues?.linkedinLink || "",
+ hospitalPhone: props?.preValues?.hospitalPhone || "",
+ mapAddress: props?.preValues?.mapAddress || "",
+ logo: props?.preValues?.logoUrl || "",
+ translations:
+ props.preValues?.translations?.length > 0
+ ? props?.preValues?.translations
+ : props?.languages.map((lang) => ({
+ languageId: lang.id,
+ address: "",
+ underLogoText: "",
+ aboutUsText: "",
+ patientsRights: "",
+ // position: "",
+ })), // چندزبانه
+ }),
+ // validationSchema:updateDefaultsValidationSchema,
+ handleSubmit: async (values, {props}) => {
+ try {
+
+ await props.updateFn({
+ data: values,
+ });
+
+ toast.success("done");
+ } catch (error) {
+ const message = handleAxiosError(error);
+ toast.error(message);
+ }
+ },
+})(InnerUpdateDefaultForm);
+
+export default UpdateDefaultForm;
diff --git a/src/components/forms/department/new/CreateDepartmentMember.ts b/src/components/forms/department/new/CreateDepartmentMember.ts
new file mode 100644
index 0000000..4a25551
--- /dev/null
+++ b/src/components/forms/department/new/CreateDepartmentMember.ts
@@ -0,0 +1,114 @@
+"use client";
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import * as Yup from "yup";
+import {UsersType} from "@/constants";
+import {CreateUserTranslation, Expertise, Language} from "@/types";
+import {toast} from "react-toastify";
+import privateApi from "@/service/http/privateCall.axios";
+import InnerDepartmentMemberForm from "./InnerDepartmentMemberForm";
+import { handleAxiosError } from "@/lib/utils";
+
+export const translationSchema = Yup.object({
+ lang_id: Yup.number().required("Language is required"),
+
+ firstName: Yup.string()
+ .max(255, "First name is too long")
+ .required("First name is required"),
+
+ lastName: Yup.string()
+ .max(255, "Last name is too long")
+ .required("Last name is required"),
+
+ position: Yup.string()
+ .max(255, "Position is too long")
+ .required("Position is required"),
+
+ expertise: Yup.string()
+ .max(500, "Expertise is too long")
+ .nullable()
+ .notRequired(),
+});
+
+export const CreateUserValidationSchema = Yup.object({
+ email: Yup.string().email("فرمت ایمیل اشتباه است").required("ایمیل الزامیست"),
+ // slug: Yup.string().required("اسلاگ الزامیست"),
+
+ // medicalNumber: Yup.string().required("شماره نظام پزشکی الزامیست"),
+
+ phone: Yup.string()
+ .matches(/^(?:\+98|0)?9\d{9}$/, "فرمت موبایل اشتباه است")
+ .nullable()
+ .notRequired(),
+
+ type: Yup.mixed()
+ .oneOf(
+ [UsersType.DOCTOR, UsersType.TRANSFER_TEAM, UsersType.DEPARTMENT],
+ "دسته بندی کاربر اشتباه است"
+ )
+ .required("دسته بندی کاربر وارد نشده است"),
+
+ imageId: Yup.number().nullable().notRequired(),
+
+ expertiseId: Yup.number()
+ .typeError("فرمت تخصص اشتباه است")
+ .required("تخصص وارد نشده است"),
+
+ translations: Yup.array()
+ .of(translationSchema)
+ .min(1, "حداقل یک ترجمه الزامی است")
+ .required("Translations are required"),
+});
+
+function buildInitialTranslations(
+ languages: Language[]
+): CreateUserTranslation[] {
+ return languages.map((lang) => ({
+ lang_id: lang.id,
+ firstName: "",
+ lastName: "",
+ }));
+}
+export interface CreateDepartmentMemberValues {
+ translations: CreateUserTranslation[];
+ type: UsersType;
+ email: string;
+ phone: string;
+ medicalNumber: string;
+ imageId: number | null;
+ expertiseId: number | null;
+}
+
+export interface CreateDepartmentMemberProps {
+ router: AppRouterInstance;
+ languages: Language[];
+ expertises: Expertise[];
+ initialValues?: Partial;
+}
+const CreateDepartmentMember = withFormik<
+ CreateDepartmentMemberProps,
+ CreateDepartmentMemberValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: buildInitialTranslations(props.languages),
+ email: "",
+ phone: "",
+ medicalNumber: "",
+ imageId: null,
+ expertiseId: null,
+ type: "DEPARTMENT",
+ ...props.initialValues, // اگر فیلدهای دیگری بخواهی
+ }),
+ validationSchema: CreateUserValidationSchema,
+ handleSubmit: async (values, {props}) => {
+
+ try {
+ const data = await privateApi.post("/user/create", values);
+ toast.success(data?.data?.message);
+ } catch (error) {
+ toast.error(handleAxiosError(error))
+ }
+ },
+})(InnerDepartmentMemberForm);
+
+export default CreateDepartmentMember;
diff --git a/src/components/forms/department/new/InnerDepartmentMemberForm.tsx b/src/components/forms/department/new/InnerDepartmentMemberForm.tsx
new file mode 100644
index 0000000..2e034e2
--- /dev/null
+++ b/src/components/forms/department/new/InnerDepartmentMemberForm.tsx
@@ -0,0 +1,207 @@
+"use client";
+import { ErrorMessage, Form, FormikProps } from "formik";
+import React from "react";
+
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+
+import UploadDropzone from "@/components/uploadDropzone";
+import {
+ CreateDepartmentMemberProps,
+ CreateDepartmentMemberValues,
+} from "./CreateDepartmentMember";
+
+export default function InnerDepartmentMemberForm(
+ props: FormikProps &
+ CreateDepartmentMemberProps,
+) {
+ const { setFieldValue} = props;
+
+ return (
+
+
+
+ عکس پروفایل
+ props.setFieldValue("imageId", value)}
+ />
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+ شماره موبایل
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+
+ تخصص
+
+
+ setFieldValue(`expertiseId`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props.expertises.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang.slug === "fa",
+ );
+
+ console.log(translation);
+ return (
+
+ {translation?.displayName ?? exp.slug}
+
+ );
+ })}
+
+
+
+
+
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ setFieldValue(
+ `translations.${index}.firstName`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ نام خانوادگی
+
+
+ setFieldValue(
+ `translations.${index}.lastName`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+ {/* Position */}
+
+
+ سمت
+
+
+ setFieldValue(
+ `translations.${index}.position`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+ );
+ })}
+ props.handleSubmit()}>
+ ثبت اطلاعات
+
+
+ );
+}
diff --git a/src/components/forms/department/update/InnerUpdateDepartmentMemberForm.tsx b/src/components/forms/department/update/InnerUpdateDepartmentMemberForm.tsx
new file mode 100644
index 0000000..951bc2f
--- /dev/null
+++ b/src/components/forms/department/update/InnerUpdateDepartmentMemberForm.tsx
@@ -0,0 +1,199 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {Button} from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { CreateDoctorFormValues, UpdateDepartmentMemberFormProps } from "./UpdateDepartmentMemberForm";
+
+
+export default function InnerUpdateDepartmentMemberForm(
+ props: FormikProps & UpdateDepartmentMemberFormProps
+) {
+ const {setFieldValue, ...rest} = props;
+
+ return (
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+ شماره موبایل
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+ شماره نظام پزشکی
+ setFieldValue(`medicalNumber`, e.target.value)}
+ />
+
+
+
+ تخصص
+
+
+ setFieldValue(`expertiseId`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props.expertises.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang_id === 1
+ );
+
+ return (
+
+ {translation?.displayName ?? exp.slug}
+
+ );
+ })}
+
+
+
+
+
+
+ {props.values.translations && props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ setFieldValue(
+ `translations.${index}.firstName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ نام خانوادگی
+
+
+ setFieldValue(
+ `translations.${index}.lastName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ {/* Position */}
+
+
+ سمت
+
+
+ setFieldValue(
+ `translations.${index}.position`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ );
+ })}
+ ثبت اطلاعات
+
+ );
+}
diff --git a/src/components/forms/department/update/UpdateDepartmentMemberForm.ts b/src/components/forms/department/update/UpdateDepartmentMemberForm.ts
new file mode 100644
index 0000000..bf3c62e
--- /dev/null
+++ b/src/components/forms/department/update/UpdateDepartmentMemberForm.ts
@@ -0,0 +1,64 @@
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+
+import {CreateUserTranslation, Expertise, getSingleUserDataType, Language, ServerResponse} from "@/types";
+import {toast} from "react-toastify";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import { CreateDepartmentMemberValues } from "../new/CreateDepartmentMember";
+import { UsersType } from "@/constants";
+import InnerUpdateDepartmentMemberForm from "./InnerUpdateDepartmentMemberForm";
+
+export interface CreateDoctorFormValues {
+ translations: CreateUserTranslation[];
+ type: UsersType;
+ email: string;
+ phone: string;
+ medicalNumber: string;
+ imageId: number | null;
+ expertiseId: number | null;
+}
+
+export type UpdateUserVariables = {
+ data: CreateDepartmentMemberValues;
+ id: string;
+};
+export interface UpdateDepartmentMemberFormProps {
+ router: AppRouterInstance;
+ preValues: getSingleUserDataType;
+ expertises: Expertise[];
+ languages: Language[];
+ updateFn: UseMutateAsyncFunction<
+ {status: number; data?: getSingleUserDataType[]; message: string},
+ Error,
+ UpdateUserVariables,
+ ServerResponse
+ >;
+ id: string;
+}
+
+const UpdateDepartmentMemberForm = withFormik<
+ UpdateDepartmentMemberFormProps,
+ CreateDoctorFormValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: props?.preValues?.translations,
+
+ email: props?.preValues?.email,
+ phone: props?.preValues?.phone,
+ expertiseId: props.preValues.expertiseId,
+ imageId: null,
+ medicalNumber: props?.preValues?.medicalNumber,
+ type: "DEPARTMENT",
+
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.updateFn({data: values, id: props.id});
+ toast.success(message);
+ } catch (error) {
+ toast.error("dasd");
+ }
+ },
+})(InnerUpdateDepartmentMemberForm);
+
+export default UpdateDepartmentMemberForm;
diff --git a/src/components/forms/doctor/create/CreateDoctorForm.ts b/src/components/forms/doctor/create/CreateDoctorForm.ts
new file mode 100644
index 0000000..0a891f5
--- /dev/null
+++ b/src/components/forms/doctor/create/CreateDoctorForm.ts
@@ -0,0 +1,123 @@
+"use client";
+import { withFormik } from "formik";
+import InnerCreateDoctorForm from "./InnerCreateDoctorForm";
+import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
+import * as Yup from "yup";
+import { UsersType } from "@/constants";
+import { CreateUserTranslation, Expertise, Language } from "@/types";
+import { toast } from "react-toastify";
+import axios from "axios";
+import privateApi from "@/service/http/privateCall.axios";
+
+export const translationSchema = Yup.object({
+ lang_id: Yup.number().required("Language is required"),
+
+ firstName: Yup.string()
+ .max(255, "First name is too long")
+ .required("First name is required"),
+
+ lastName: Yup.string()
+ .max(255, "Last name is too long")
+ .required("Last name is required"),
+ bio: Yup.string().optional(),
+ excerpt: Yup.string().required("خلاصه پروفایل الزامیست"),
+ // position: Yup.string()
+ // .max(255, "Position is too long")
+ // .required("Position is required"),
+
+ expertise: Yup.string()
+ .max(500, "Expertise is too long")
+ .nullable()
+ .notRequired(),
+});
+export const CreateUserValidationSchema = Yup.object({
+ email: Yup.string().email("فرمت ایمیل اشتباه است").required("ایمیل الزامیست"),
+ // slug: Yup.string().required("اسلاگ الزامیست"),
+
+ medicalNumber: Yup.string().required("شماره نظام پزشکی الزامیست"),
+
+ phone: Yup.string()
+ .matches(/^(?:\+98|0)?9\d{9}$/, "فرمت موبایل اشتباه است")
+ .nullable()
+ .notRequired(),
+
+ type: Yup.mixed()
+ .oneOf(
+ [UsersType.DOCTOR, UsersType.TRANSFER_TEAM, UsersType.DEPARTMENT],
+ "دسته بندی کاربر اشتباه است",
+ )
+ .required("دسته بندی کاربر وارد نشده است"),
+
+ imageId: Yup.number().nullable().notRequired(),
+
+ expertiseId: Yup.number()
+ .typeError("فرمت تخصص اشتباه است")
+ .required("تخصص وارد نشده است"),
+
+ translations: Yup.array()
+ .of(translationSchema)
+ .min(1, "حداقل یک ترجمه الزامی است")
+ .required("Translations are required"),
+});
+
+function buildInitialTranslations(
+ languages: Language[],
+): CreateUserTranslation[] {
+ return languages.map((lang) => ({
+ lang_id: lang.id,
+ firstName: "",
+ lastName: "",
+ bio: "",
+ excerpt: "",
+ // position: "",
+ }));
+}
+export interface CreateDoctorFormValues {
+ translations: CreateUserTranslation[];
+ type: UsersType;
+
+ email: string;
+ phone: string;
+ medicalNumber: string;
+ imageId: number | null;
+ expertiseId: number | null;
+}
+
+export interface CreateDoctorFormProps {
+ router: AppRouterInstance;
+ languages: Language[];
+ expertises: Expertise[];
+ initialValues?: Partial;
+}
+const CreateDoctorForm = withFormik<
+ CreateDoctorFormProps,
+ CreateDoctorFormValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: buildInitialTranslations(props.languages),
+ email: "",
+ phone: "",
+ medicalNumber: "",
+ imageId: null,
+ expertiseId: null,
+ type: "DOCTOR",
+ ...props.initialValues, // اگر فیلدهای دیگری بخواهی
+ }),
+ validationSchema: CreateUserValidationSchema,
+ handleSubmit: async (values, { props }) => {
+ try {
+ const data = await privateApi.post("/user/create", values);
+ toast.success(data?.data?.message);
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ // اینجا میدونیم که خطا از axios است
+ toast.error(error.response?.data?.error?.message);
+ } else {
+ // خطای عمومی
+ console.error("Unexpected error:", error);
+ }
+ }
+ },
+})(InnerCreateDoctorForm);
+
+export default CreateDoctorForm;
diff --git a/src/components/forms/doctor/create/InnerCreateDoctorForm.tsx b/src/components/forms/doctor/create/InnerCreateDoctorForm.tsx
new file mode 100644
index 0000000..59a5c3c
--- /dev/null
+++ b/src/components/forms/doctor/create/InnerCreateDoctorForm.tsx
@@ -0,0 +1,236 @@
+"use client";
+import { ErrorMessage, Form, FormikProps } from "formik";
+import React, { useRef, useState } from "react";
+import {
+ CreateDoctorFormProps,
+ CreateDoctorFormValues,
+} from "./CreateDoctorForm";
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import RichTextEditor, { RichTextEditorHandle } from "@/components/TextEditor";
+import UploadDropzone from "@/components/uploadDropzone";
+
+export default function InnerCreateDoctorForm(
+ props: FormikProps & CreateDoctorFormProps,
+) {
+ const { setFieldValue, ...rest } = props;
+
+ const bioRef = useRef(null);
+ const [bioRefs, setBioRefs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(bioRef))),
+ );
+
+ const excerptRef = useRef(null);
+ const [excerptRefs, setExcerptRefs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(excerptRef))),
+ );
+ const handleSave = () => {
+ bioRefs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.bio`, r.current.getContent());
+ });
+ excerptRefs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.excerpt`, r.current.getContent());
+ });
+ props.handleSubmit();
+ };
+ return (
+
+
+
+ تصویر پزشک
+ props.setFieldValue("imageId", value)}
+ />
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+ شماره موبایل
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+ شماره نظام پزشکی
+ setFieldValue(`medicalNumber`, e.target.value)}
+ />
+
+
+
+ تخصص
+
+
+ setFieldValue(`expertiseId`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props.expertises.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang_id === 1,
+ );
+
+ return (
+
+ {translation?.displayName ?? exp.slug}
+
+ );
+ })}
+
+
+
+
+
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ setFieldValue(
+ `translations.${index}.firstName`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ نام خانوادگی
+
+
+ setFieldValue(
+ `translations.${index}.lastName`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+
+ متن کوتاه پروفایل ( برای نمایش در کارت ها )
+
+
+
+
+
+
+
+ بایوگرافی
+
+
+
+
+
+
+ );
+ })}
+
+ ثبت اطلاعات
+
+
+ );
+}
diff --git a/src/components/forms/doctor/update/InnerUpdateDoctorForm.tsx b/src/components/forms/doctor/update/InnerUpdateDoctorForm.tsx
new file mode 100644
index 0000000..f3a0d78
--- /dev/null
+++ b/src/components/forms/doctor/update/InnerUpdateDoctorForm.tsx
@@ -0,0 +1,262 @@
+"use client";
+import { ErrorMessage, Form, FormikProps } from "formik";
+import React, { useRef, useState } from "react";
+
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { UpdateDoctorFormProps } from "./UpdateDoctorForm";
+import { CreateDoctorFormValues } from "../create/CreateDoctorForm";
+import DeleteFileButton from "@/components/DeleteFileButton";
+import Image from "next/image";
+import RichTextEditor, { RichTextEditorHandle } from "@/components/TextEditor";
+
+export default function InnerUpdateDoctorForm(
+ props: FormikProps & UpdateDoctorFormProps,
+) {
+ const { setFieldValue, ...rest } = props;
+ const bioRef = useRef(null);
+ const [bioRefs, setBioRefs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(bioRef))),
+ );
+
+ const excerptRef = useRef(null);
+ const [excerptRefs, setExcerptRefs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(excerptRef))),
+ );
+ const handleSave = () => {
+ bioRefs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.bio`, r.current.getContent());
+ });
+ excerptRefs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.excerpt`, r.current.getContent());
+ });
+ props.handleSubmit();
+ };
+ return (
+
+
+ {props.values.translations &&
+ props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ setFieldValue(
+ `translations.${index}.firstName`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ نام خانوادگی
+
+
+ setFieldValue(
+ `translations.${index}.lastName`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+
+ متن کوتاه پروفایل ( برای نمایش در کارت ها )
+
+
+
+
+
+
+
+ بایوگرافی
+
+
+
+
+
+ {/* Position */}
+ {/*
+
+ سمت
+
+
+ setFieldValue(
+ `translations.${index}.position`,
+ e.target.value,
+ )
+ }
+ />
+
+ */}
+
+ );
+ })}
+ ثبت اطلاعات
+
+ );
+}
diff --git a/src/components/forms/doctor/update/UpdateDoctorForm.ts b/src/components/forms/doctor/update/UpdateDoctorForm.ts
new file mode 100644
index 0000000..3648559
--- /dev/null
+++ b/src/components/forms/doctor/update/UpdateDoctorForm.ts
@@ -0,0 +1,60 @@
+import { withFormik } from "formik";
+import { AppRouterInstance } from "next/dist/shared/lib/app-router-context.shared-runtime";
+import InnerUpdateDoctorForm from "./InnerUpdateDoctorForm";
+import { CreateUserTranslation, Expertise, getSingleUserDataType, Language } from "@/types";
+import { toast } from "react-toastify";
+import { UseMutateAsyncFunction } from "@tanstack/react-query";
+import { UsersType } from "@/constants";
+
+export interface CreateDoctorFormValues {
+ translations: CreateUserTranslation[];
+ type: UsersType;
+ email: string;
+ phone: string;
+ medicalNumber: string;
+ imageId: number | null;
+ expertiseId: number | null;
+}
+
+export type UpdateUserVariables = {
+ data: CreateDoctorFormValues;
+ id: string;
+};
+export interface UpdateDoctorFormProps {
+ router: AppRouterInstance;
+ preValues: getSingleUserDataType;
+ expertises: Expertise[];
+ languages: Language[];
+ updateFn: UseMutateAsyncFunction<
+ { status: number; data?: getSingleUserDataType[]; message: string },
+ unknown,
+ UpdateUserVariables,
+ unknown
+ >;
+ id: string;
+}
+
+const UpdateDoctorForm = withFormik<
+ UpdateDoctorFormProps,
+ CreateDoctorFormValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: props?.preValues?.translations,
+ email: props?.preValues?.email,
+ phone: props?.preValues?.phone,
+ expertiseId: props.preValues.expertiseId,
+ imageId: null,
+ medicalNumber: props?.preValues?.medicalNumber ?? 0,
+ type: "DOCTOR",
+ }),
+ handleSubmit: async (values, { props }) => {
+ try {
+ const { message } = await props.updateFn({ data: values, id: props.id });
+ toast.success(message);
+ } catch (error) {
+ toast.error("dasd");
+ }
+ },
+})(InnerUpdateDoctorForm);
+
+export default UpdateDoctorForm;
diff --git a/src/components/forms/expertise/edit/InnerUpdateExpertise.tsx b/src/components/forms/expertise/edit/InnerUpdateExpertise.tsx
new file mode 100644
index 0000000..0670e67
--- /dev/null
+++ b/src/components/forms/expertise/edit/InnerUpdateExpertise.tsx
@@ -0,0 +1,81 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {Button} from "@/components/ui/button";
+
+import {
+ CreateExpertiseValues,
+ UpdateExpertiseProps,
+} from "./UpdateExpertiseForm";
+
+export default function InnerUpdateExpertiseForm(
+ props: FormikProps & UpdateExpertiseProps
+) {
+ const {setFieldValue} = props;
+
+ return (
+
+
+
+ اسلاگ
+ props.setFieldValue(`slug`, e.target.value)}
+ />
+
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ props.setFieldValue(
+ `translations.${index}.displayName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ );
+ })}
+
+ ثبت اطلاعات
+
+
+
+ );
+}
diff --git a/src/components/forms/expertise/edit/UpdateExpertiseForm.ts b/src/components/forms/expertise/edit/UpdateExpertiseForm.ts
new file mode 100644
index 0000000..4780114
--- /dev/null
+++ b/src/components/forms/expertise/edit/UpdateExpertiseForm.ts
@@ -0,0 +1,86 @@
+import {withFormik} from "formik";
+import * as Yup from "yup";
+import {Language, ServerResponse} from "@/types";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import InnerUpdateExpertiseForm from "./InnerUpdateExpertise";
+export const translationSchema = Yup.object({
+ lang_id: Yup.number().required("Language is required"),
+
+ displayName: Yup.string()
+ .max(255, "Display name is too long")
+ .required("Display name is required"),
+});
+
+export const CreateExpertiseValidationSchema = Yup.object({
+ slug: Yup.string().required("اسلاگ الزامیست"),
+
+ translations: Yup.array()
+ .of(translationSchema)
+ .min(1, "حداقل یک ترجمه الزامی است")
+ .required("Translations are required"),
+});
+
+export interface CreateExpertiseTranslation {
+ lang_id: number;
+ displayName: string;
+}
+// function buildInitialTranslations(
+// languages: Language[]
+// ): CreateExpertiseTranslation[] {
+// return languages.map((lang) => ({
+// lang_id: lang.id,
+// displayName: "",
+// }));
+// }
+export interface UpdateExpertiseProps {
+ router: AppRouterInstance;
+ languages: Language[];
+ updateFn: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ {data: CreateExpertiseValues; id: string},
+ ServerResponse
+ >;
+ updatePending: boolean;
+ preValues: {
+ translations: {
+ id: number;
+ lang_id: number;
+ expertiseId: number;
+ displayName: string;
+ }[];
+ } & {
+ slug: string;
+ id: number;
+ };
+ id: string;
+}
+export interface CreateExpertiseValues {
+ translations: CreateExpertiseTranslation[];
+ slug: string;
+}
+const UpdateExpertiseForm = withFormik<
+ UpdateExpertiseProps,
+ CreateExpertiseValues
+>({
+ mapPropsToValues: (props) => ({
+ slug: props.preValues.slug,
+ translations: props.preValues.translations,
+ }),
+ validationSchema: CreateExpertiseValidationSchema,
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.updateFn({data: values, id: props.id});
+
+ toast.success(message);
+ props.router.push("/expertise");
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerUpdateExpertiseForm);
+
+export default UpdateExpertiseForm;
diff --git a/src/components/forms/expertise/new/CreateExpertiseForm.ts b/src/components/forms/expertise/new/CreateExpertiseForm.ts
new file mode 100644
index 0000000..c825414
--- /dev/null
+++ b/src/components/forms/expertise/new/CreateExpertiseForm.ts
@@ -0,0 +1,69 @@
+import {withFormik} from "formik";
+import InnerCreateExpertiseForm from "./InnerCreateExpertiseForm";
+import * as Yup from "yup";
+import {Language, ServerResponse} from "@/types";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import { UseMutateAsyncFunction } from "@tanstack/react-query";
+import { toast } from "react-toastify";
+import { handleAxiosError } from "@/lib/utils";
+export const translationSchema = Yup.object({
+ lang_id: Yup.number().required("Language is required"),
+
+ displayName: Yup.string()
+ .max(255, "Display name is too long")
+ .required("Display name is required"),
+});
+
+export const CreateExpertiseValidationSchema = Yup.object({
+ slug: Yup.string().required("اسلاگ الزامیست"),
+
+ translations: Yup.array()
+ .of(translationSchema)
+ .min(1, "حداقل یک ترجمه الزامی است")
+ .required("Translations are required"),
+});
+
+export interface CreateExpertiseTranslation {
+ lang_id: number;
+ displayName: string;
+}
+function buildInitialTranslations(
+ languages: Language[]
+): CreateExpertiseTranslation[] {
+ return languages.map((lang) => ({
+ lang_id: lang.id,
+ displayName: "",
+ }));
+}
+export interface CreateExpertiseProps {
+ router: AppRouterInstance;
+ languages: Language[];
+ createFn:UseMutateAsyncFunction,
+ createPending:boolean
+}
+export interface CreateExpertiseValues {
+ translations: CreateExpertiseTranslation[];
+ slug: string;
+}
+const CreateExpertiseForm = withFormik<
+ CreateExpertiseProps,
+ CreateExpertiseValues
+>({
+ mapPropsToValues: (props) => ({
+ slug: "",
+ translations: buildInitialTranslations(props.languages),
+ }),
+ validationSchema:CreateExpertiseValidationSchema,
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message}=await props.createFn(values)
+
+ toast.success(message)
+ props.router.push('/expertise')
+ } catch (error) {
+ toast.error(handleAxiosError(error))
+ }
+ },
+})(InnerCreateExpertiseForm);
+
+export default CreateExpertiseForm;
diff --git a/src/components/forms/expertise/new/InnerCreateExpertiseForm.tsx b/src/components/forms/expertise/new/InnerCreateExpertiseForm.tsx
new file mode 100644
index 0000000..81c2eae
--- /dev/null
+++ b/src/components/forms/expertise/new/InnerCreateExpertiseForm.tsx
@@ -0,0 +1,78 @@
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+import {
+ CreateExpertiseProps,
+ CreateExpertiseValues,
+} from "./CreateExpertiseForm";
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import { Button } from "@/components/ui/button";
+
+export default function InnerCreateExpertiseForm(
+ props: FormikProps & CreateExpertiseProps
+) {
+ return (
+
+
+
+ اسلاگ
+ props.setFieldValue(`slug`, e.target.value)}
+ />
+
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ props.setFieldValue(
+ `translations.${index}.displayName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ );
+ })}
+
+
+ ثبت اطلاعات
+
+
+
+
+ );
+}
diff --git a/src/components/forms/languages/new/CreateLanguageForm.ts b/src/components/forms/languages/new/CreateLanguageForm.ts
new file mode 100644
index 0000000..6421ca9
--- /dev/null
+++ b/src/components/forms/languages/new/CreateLanguageForm.ts
@@ -0,0 +1,50 @@
+import {withFormik} from "formik";
+import InnerCreateLanguageForm from "./InnerCreateLanguageForm";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {QueryClient, UseMutateAsyncFunction} from "@tanstack/react-query";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import * as yup from 'yup'
+import { ServerResponse } from "@/types";
+
+const CreateLanguageValidationSchema = yup.object({
+ title:yup.string().required("عنوان وارد نشده است"),
+ slug:yup.string().required("اسلاگ وارد نشده است")
+})
+
+
+export interface CreateLanguageFormProps {
+ router: AppRouterInstance;
+ createFn: UseMutateAsyncFunction;
+ createPending: boolean;
+ queryClient: QueryClient;
+}
+
+export interface CreateLanguageFormValues {
+ title: string;
+ slug: string;
+}
+
+const CreateLanguageForm = withFormik<
+ CreateLanguageFormProps,
+ CreateLanguageFormValues
+>({
+ mapPropsToValues: () => ({
+ title: "",
+ slug: "",
+ }),
+ validationSchema:CreateLanguageValidationSchema,
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.createFn(values);
+
+ toast.success(message);
+ props.queryClient.invalidateQueries({queryKey: ["get-all-languages"]});
+ props.router.refresh();
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerCreateLanguageForm);
+
+export default CreateLanguageForm;
diff --git a/src/components/forms/languages/new/InnerCreateLanguageForm.tsx b/src/components/forms/languages/new/InnerCreateLanguageForm.tsx
new file mode 100644
index 0000000..15c17a3
--- /dev/null
+++ b/src/components/forms/languages/new/InnerCreateLanguageForm.tsx
@@ -0,0 +1,92 @@
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+import {
+ CreateLanguageFormProps,
+ CreateLanguageFormValues,
+} from "./CreateLanguageForm";
+import {
+ DialogClose,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "@/components/ui/dialog";
+import {Button} from "@/components/ui/button";
+import {Label} from "@/components/ui/label";
+import {Input} from "@/components/ui/input";
+import {Plus} from "lucide-react";
+import Loader from "@/components/Loader";
+
+export default function InnerCreateLanguageForm(
+ props: FormikProps & CreateLanguageFormProps
+) {
+ console.log(props.errors);
+ return (
+ <>
+
+
+
+
+ افزودن زبان جدید
+
+
+
+
+ زبان جدید
+ {/*
+ Make changes to your profile here. Click save when you're
+ done.
+ */}
+
+
+
+
+
+ انصراف
+
+
+ props.handleSubmit()}
+ disabled={props.createPending}
+ type="submit"
+ >
+ {props.createPending ? : "ایجاد"}
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/forms/languages/update/InnerUpdateLanguageForm.tsx b/src/components/forms/languages/update/InnerUpdateLanguageForm.tsx
new file mode 100644
index 0000000..a4da2e8
--- /dev/null
+++ b/src/components/forms/languages/update/InnerUpdateLanguageForm.tsx
@@ -0,0 +1,11 @@
+import { FormikProps } from 'formik'
+import React from 'react'
+import { UpdateLanguageFormProps, UpdateLanguageFormValues } from './UpdateLanguageForm'
+
+export default function InnerUpdateLanguageForm(props:FormikProps & UpdateLanguageFormProps) {
+ return (
+
+
+
+ )
+}
diff --git a/src/components/forms/languages/update/UpdateLanguageForm.ts b/src/components/forms/languages/update/UpdateLanguageForm.ts
new file mode 100644
index 0000000..2f5d989
--- /dev/null
+++ b/src/components/forms/languages/update/UpdateLanguageForm.ts
@@ -0,0 +1,49 @@
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {QueryClient, UseMutateAsyncFunction} from "@tanstack/react-query";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import InnerUpdateLanguageForm from "./InnerUpdateLanguageForm";
+import {ServerResponse} from "@/types";
+
+export interface UpdateLanguageFormProps {
+ router: AppRouterInstance;
+ updateFn: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ {data: UpdateLanguageFormValues; id: string},
+ ServerResponse
+ >;
+ updatePending: boolean;
+ queryClient: QueryClient;
+ id: string;
+ preValues: {title: string; slug: string};
+}
+
+export interface UpdateLanguageFormValues {
+ title: string;
+ slug: string;
+}
+
+const UpdateLanguageForm = withFormik<
+ UpdateLanguageFormProps,
+ UpdateLanguageFormValues
+>({
+ mapPropsToValues: () => ({
+ title: "",
+ slug: "",
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.updateFn({data: values, id: props.id});
+
+ toast.success(message);
+ props.queryClient.invalidateQueries({queryKey: ["get-all-languages"]});
+ props.router.refresh();
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerUpdateLanguageForm);
+
+export default UpdateLanguageForm;
diff --git a/src/components/forms/login/InnerLoginForm.tsx b/src/components/forms/login/InnerLoginForm.tsx
new file mode 100644
index 0000000..6ba1e35
--- /dev/null
+++ b/src/components/forms/login/InnerLoginForm.tsx
@@ -0,0 +1,82 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React, {useState} from "react";
+import {Field, FieldGroup, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {Button} from "@/components/ui/button";
+import {Label} from "@/components/ui/label";
+import {Checkbox} from "@/components/ui/checkbox";
+import Link from "next/link";
+import { LoginFormProps, LoginFormValues } from "./LoginForm";
+import Loader from "@/components/Loader";
+
+export default function InnerLoginForm(
+ props: FormikProps & LoginFormProps
+) {
+ const isValid = Boolean(
+ props.values.username.trim() && props.values.password.trim()
+ );
+
+ const [showPassword] = useState(false);
+
+ return (
+
+
+
+
+
+ نام کاربری
+
+ props.setFieldValue("username", e.target.value)}
+ />
+
+
+
+
+ رمز عبور
+
+ props.setFieldValue("password", e.target.value)}
+ />
+
+
+
+
+
+ props.setFieldValue("rememberMe", !props.values.rememberMe)
+ }
+ />
+ مرا به خاطر بسپار
+
+
+ رمز عبور را فراموش کرده ام
+
+
+
+ {props.loginPending ? : "ورود"}
+
+
+
+
+ );
+}
diff --git a/src/components/forms/login/LoginForm.ts b/src/components/forms/login/LoginForm.ts
new file mode 100644
index 0000000..97a9aa7
--- /dev/null
+++ b/src/components/forms/login/LoginForm.ts
@@ -0,0 +1,49 @@
+"use client";
+import {withFormik} from "formik";
+import InnerLoginForm from "./InnerLoginForm";
+import * as yup from "yup";
+import {toast} from "react-toastify";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import {handleAxiosError} from "@/lib/utils";
+import {ServerResponse} from "@/types";
+export interface LoginFormValues {
+ username: string;
+ password: string;
+ rememberMe: boolean;
+}
+export interface LoginFormProps {
+ router: AppRouterInstance;
+ loginFn: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ LoginFormValues,
+ ServerResponse
+ >;
+ loginPending: boolean;
+}
+
+const LoginFormValidationSchema = yup.object({
+ username: yup.string().required("نام کاربری وارد نشده است"),
+ password: yup.string().required("کلمه عبور وارد نشده است"),
+});
+
+const LoginForm = withFormik({
+ mapPropsToValues: () => ({
+ username: "",
+ password: "",
+ rememberMe: false,
+ }),
+ validationSchema: LoginFormValidationSchema,
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.loginFn(values);
+ toast.success(message);
+ props.router.refresh();
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerLoginForm);
+
+export default LoginForm;
diff --git a/src/components/forms/medical-packages/edit/InnerUpdateMedicalPackageForm.tsx b/src/components/forms/medical-packages/edit/InnerUpdateMedicalPackageForm.tsx
new file mode 100644
index 0000000..ad51183
--- /dev/null
+++ b/src/components/forms/medical-packages/edit/InnerUpdateMedicalPackageForm.tsx
@@ -0,0 +1,169 @@
+"use client";
+
+import { ErrorMessage, Form, FormikProps } from "formik";
+import React, { useRef, useState } from "react";
+import {
+ UpdateMedicalPackageFormProps,
+ UpdateMedicalPackageFormValues,
+} from "./UpdateMedicalPackageForm";
+import RichTextEditor, { RichTextEditorHandle } from "@/components/TextEditor";
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import { Textarea } from "@/components/ui/textarea";
+import { Switch } from "@/components/ui/switch";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { Button } from "@/components/ui/button";
+import Loader from "@/components/Loader";
+import Image from "next/image";
+import DeleteFileButton from "@/components/DeleteFileButton";
+
+export default function InnerUpdateMedicalPackageForm(
+ props: FormikProps &
+ UpdateMedicalPackageFormProps,
+) {
+ const [isSub, setIsSub] = useState(props.preValues.parent_id ? true : false);
+ const { setFieldValue} = props;
+ const ref = useRef(null);
+ const [refs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(ref))),
+ );
+
+ const handleSave = () => {
+ refs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.content`, r.current.getContent());
+ });
+ props.handleSubmit();
+ };
+ return (
+
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ ردیف برای زبان {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ عنوان
+
+
+ setFieldValue(`translations.${index}.title`, e.target.value)
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ محتوا
+
+
+
+
+
+ );
+ })}
+
+ آیکون (کد svg را در این قسمت قرار دهید)
+ setFieldValue("icon", value)}
+ >
+
+
+
+ آیا میخواهید زیر مجموعه شود
+ setIsSub(checked)}
+ />
+
+ {isSub && (
+
+ انتخاب خدمت پزشکی والد
+
+ setFieldValue(`parent_id`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props?.parents?.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang_id === 1,
+ );
+
+ return (
+
+ {translation?.title}
+
+ );
+ })}
+
+
+
+ )}
+
+
+ {props.updatePending ? : "ثبت اطلاعات"}
+
+
+ );
+}
diff --git a/src/components/forms/medical-packages/edit/UpdateMedicalPackageForm.ts b/src/components/forms/medical-packages/edit/UpdateMedicalPackageForm.ts
new file mode 100644
index 0000000..710c8fe
--- /dev/null
+++ b/src/components/forms/medical-packages/edit/UpdateMedicalPackageForm.ts
@@ -0,0 +1,92 @@
+"use client";
+import {withFormik} from "formik";
+import InnerUpdateMedicalPackageForm from "./InnerUpdateMedicalPackageForm";
+import {Language, ServerResponse} from "@/types";
+import {QueryClient, UseMutateAsyncFunction} from "@tanstack/react-query";
+import {CreateMedicalPackageTranslation} from "../new/CreateMedicalPackageForm";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+
+export interface UpdateMedicalPackageFormProps {
+ queryClient: QueryClient;
+ languages: Language[];
+ parents: {
+ translations: {
+ id: number;
+ lang_id: number | null;
+ title: string;
+ content: string | null;
+ medicalPackageId: number | null;
+ }[];
+ id: number;
+ thumbnail_id: number;
+ icon: string | null;
+ priority: number | null;
+ parent_id: number | null;
+ }[];
+ updateFn: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ {id: string; data: UpdateMedicalPackageFormValues},
+ ServerResponse
+ >;
+ updatePending: boolean;
+ preValues: {
+ translations: {
+ id: number;
+ title: string;
+ content: string | null;
+ lang_id: number | null;
+ medicalPackageId: number | null;
+ }[];
+ id: number;
+ thumbnail_id: number | null;
+ thumbnail: {
+ fileKey: string;
+ filename: string | null;
+ fileUrl: string | null;
+ };
+ icon: string | null;
+ priority: number | null;
+ parent_id: number | null;
+ };
+ id: string;
+ router: AppRouterInstance;
+}
+export interface UpdateMedicalPackageFormValues {
+ translations: {
+ id: number;
+ title: string;
+ content: string | null;
+ lang_id: number | null;
+ medicalPackageId: number | null;
+ }[];
+ icon: string;
+ thumbnail_id: number | null;
+ priority: number;
+ parent_id: number | null;
+}
+
+const UpdateMedicalPackageForm = withFormik<
+ UpdateMedicalPackageFormProps,
+ UpdateMedicalPackageFormValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: props.preValues.translations,
+ icon: props.preValues.icon ?? "",
+ priority: props.preValues.priority ?? 1,
+ thumbnail_id: props.preValues.thumbnail_id ?? null,
+ parent_id: props.preValues.parent_id ?? null,
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.updateFn({id: props.id, data: values});
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerUpdateMedicalPackageForm);
+
+export default UpdateMedicalPackageForm;
diff --git a/src/components/forms/medical-packages/new/CreateMedicalPackageForm.ts b/src/components/forms/medical-packages/new/CreateMedicalPackageForm.ts
new file mode 100644
index 0000000..2c7cce6
--- /dev/null
+++ b/src/components/forms/medical-packages/new/CreateMedicalPackageForm.ts
@@ -0,0 +1,83 @@
+import {withFormik} from "formik";
+import InnerCreateMedicalPackageForm from "./InnerCreateMedicalPackageForm";
+import {QueryClient, UseMutateAsyncFunction} from "@tanstack/react-query";
+import {Language, ServerResponse} from "@/types";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+
+export interface CreateMedicalPackageProps {
+ queryClient: QueryClient;
+ languages: Language[];
+ parents: {
+ translations: {
+ id: number;
+ lang_id: number | null;
+ title: string;
+ content: string | null;
+ medicalPackageId: number | null;
+ }[];
+ id: number;
+ thumbnail_id: number;
+ icon: string | null;
+ priority: number | null;
+ parent_id: number | null;
+ }[];
+ createFn: UseMutateAsyncFunction<
+ ServerResponse,
+ Error,
+ CreateMedicalPackageValues,
+ ServerResponse
+ >;
+ createPending: boolean;
+}
+export interface CreateMedicalPackageValues {
+ translations: CreateMedicalPackageTranslation[];
+ icon: string;
+ thumbnail_id: number | null;
+ priority: number;
+ parent_id: number | null;
+ price:string
+}
+export interface CreateMedicalPackageTranslation {
+ lang_id: number;
+ title: string;
+ content: string;
+}
+function buildInitialTranslations(
+ languages: Language[],
+): CreateMedicalPackageTranslation[] {
+ return languages.map((lang) => ({
+ lang_id: lang.id,
+ title: "",
+ content: "",
+ // position: "",
+ }));
+}
+const CreateMedicalPackageForm = withFormik<
+ CreateMedicalPackageProps,
+ CreateMedicalPackageValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: buildInitialTranslations(props.languages),
+ icon: "",
+ price:"",
+ priority: 1,
+ thumbnail_id: null,
+ parent_id: null,
+ }),
+ handleSubmit: async (values, {props}) => {
+ console.log(values)
+ try {
+ const {message} = await props.createFn(values);
+ toast.success(message);
+
+ props.queryClient.invalidateQueries({
+ queryKey: ["get-all-medical-packages"],
+ });
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerCreateMedicalPackageForm);
+
+export default CreateMedicalPackageForm;
diff --git a/src/components/forms/medical-packages/new/InnerCreateMedicalPackageForm.tsx b/src/components/forms/medical-packages/new/InnerCreateMedicalPackageForm.tsx
new file mode 100644
index 0000000..1772e2b
--- /dev/null
+++ b/src/components/forms/medical-packages/new/InnerCreateMedicalPackageForm.tsx
@@ -0,0 +1,172 @@
+import { ErrorMessage, Form, FormikProps } from "formik";
+import React, { useRef, useState } from "react";
+import {
+ CreateMedicalPackageProps,
+ CreateMedicalPackageValues,
+} from "./CreateMedicalPackageForm";
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import RichTextEditor, { RichTextEditorHandle } from "@/components/TextEditor";
+import { Button } from "@/components/ui/button";
+import { Switch } from "@/components/ui/switch";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import Loader from "@/components/Loader";
+import { Textarea } from "@/components/ui/textarea";
+import UploadDropzone from "@/components/uploadDropzone";
+
+export default function InnerCreateMedicalPackageForm(
+ props: FormikProps & CreateMedicalPackageProps,
+) {
+ const [isSub, setIsSub] = useState(false);
+ const [output, setOutput] = useState("");
+ const { setFieldValue} = props;
+ const ref = useRef(null);
+
+ const [refs] = useState(
+ props.languages.map(() => JSON.parse(JSON.stringify(ref))),
+ );
+
+ const handleSave = () => {
+ refs.forEach((r, i) => {
+ setFieldValue(`translations.${i}.content`, r.current.getContent());
+ });
+ setFieldValue("thumbnail_id", output);
+ props.handleSubmit();
+ };
+ return (
+
+
+ آپلود تصویر شاخص ( تامبنیل )
+
+
+
+ قیمت پکیج (به دلار آمریکا)
+ setFieldValue(`price`, e.target.value)}
+ />
+
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ ردیف برای زبان {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ عنوان
+
+
+ setFieldValue(`translations.${index}.title`, e.target.value)
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ محتوا
+
+
+
+
+
+ );
+ })}
+
+ آیکون (کد svg را در این قسمت قرار دهید)
+ setFieldValue("icon", e.target.value)}
+ >
+
+
+
+ آیا میخواهید زیر مجموعه شود
+ {
+ setIsSub(checked);
+ props.setFieldValue("parent_id", null);
+ }}
+ />
+
+ {isSub && (
+
+ انتخاب خدمت پزشکی والد
+
+ setFieldValue(`parent_id`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props.parents.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang_id === 1,
+ );
+
+ return (
+
+ {translation?.title}
+
+ );
+ })}
+
+
+
+ )}
+
+
+ {props.createPending ? : "ثبت اطلاعات"}
+
+
+ );
+}
diff --git a/src/components/forms/online-case/new/CreateOnlineCaseForm.ts b/src/components/forms/online-case/new/CreateOnlineCaseForm.ts
new file mode 100644
index 0000000..bebd317
--- /dev/null
+++ b/src/components/forms/online-case/new/CreateOnlineCaseForm.ts
@@ -0,0 +1,55 @@
+"use client"
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import InnerCreateOnlineCaseForm from "./InnerCreateOnlineCaseForm";
+import {CreatePatientFormValues} from "../../patients/edit/UpdatePatientForm";
+import { CreatePatientFormProps } from "../../patients/new/CreatePatientForm";
+
+enum CaseStatus {
+ NEW,
+ CONTACTED,
+ DOCS_PENDING,
+ REVIEWING,
+ PRE_APPROVED,
+ REJECTED,
+ CLOSED,
+ CONVERTED_TO_HIS,
+}
+
+export interface CreateOnlineCaseFormProps {
+ router: AppRouterInstance;
+ // patients?:any
+}
+export interface CreateOnlineCaseFormValues {
+ message?: string;
+ specialty?: string;
+
+ status?: CaseStatus;
+}
+
+const CreateOnlineCaseForm = withFormik<
+ CreateOnlineCaseFormProps & CreatePatientFormProps,
+ CreatePatientFormValues & CreateOnlineCaseFormValues
+>({
+ mapPropsToValues: () => ({
+ firstName: "",
+ lastName: "",
+ email: "",
+ sex: "male",
+ address: "",
+ age: null,
+ birthDate: "",
+ nationality: "",
+ nationalityCode: "",
+ passportCode: "",
+ postalCode: "",
+ phone: "",
+ specialty: "",
+ preferredLanguage: "",
+ status: CaseStatus.NEW,
+ message: "",
+ }),
+ handleSubmit: async (values, {props}) => {},
+})(InnerCreateOnlineCaseForm);
+
+export default CreateOnlineCaseForm;
diff --git a/src/components/forms/online-case/new/InnerCreateOnlineCaseForm.tsx b/src/components/forms/online-case/new/InnerCreateOnlineCaseForm.tsx
new file mode 100644
index 0000000..8c0184d
--- /dev/null
+++ b/src/components/forms/online-case/new/InnerCreateOnlineCaseForm.tsx
@@ -0,0 +1,399 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React, {useEffect} from "react";
+import {CreatePatientFormValues} from "../../patients/edit/UpdatePatientForm";
+import {
+ CreateOnlineCaseFormProps,
+ CreateOnlineCaseFormValues,
+} from "./CreateOnlineCaseForm";
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import Loader from "@/components/Loader";
+import {CreatePatientFormProps} from "../../patients/new/CreatePatientForm";
+import SelectCalendar from "@/components/SelectCalendar";
+import {Button} from "@/components/ui/button";
+import SectionTitle from "@/components/SectionTitle";
+import UploadDropzone from "@/components/uploadDropzone";
+import {Textarea} from "@/components/ui/textarea";
+
+export default function InnerCreateOnlineCaseForm(
+ props: FormikProps &
+ CreateOnlineCaseFormProps &
+ CreatePatientFormProps
+) {
+ const {setFieldValue} = props;
+ useEffect(() => {
+ const selectedYear = new Date(props.values.birthDate!).getFullYear();
+ const age = selectedYear
+ ? new Date(Date.now()).getFullYear() - selectedYear
+ : null;
+ setFieldValue("age", age);
+ }, [props.values.birthDate]);
+ return (
+ <>
+
+
+
+
+ نام
+ setFieldValue(`firstName`, e.target.value)}
+ required
+ />
+
+
+
+
+
+ نام خانوادگی
+ setFieldValue(`lastName`, e.target.value)}
+ />
+
+
+
+
+
+ سن
+ setFieldValue(`age`, e.target.value)}
+ />
+
+
+
+
+
+ جنسیت
+ setFieldValue("sex", value)}
+ >
+
+
+
+
+
+ مرد
+ زن
+ سایر
+
+
+
+
+
+
+
+
+ شماره تماس
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+
+
+
+ کد پاسپورت (افرادغیرایرانی)
+
+ setFieldValue(`passportCode`, e.target.value)}
+ />
+
+
+
+
+
+
+ کد ملی (افراد ایرانی)
+
+
+ setFieldValue(`nationalityCode`, e.target.value)
+ }
+ />
+
+
+
+
+
+ ملیت بیمار
+ setFieldValue("nationality", value)}
+ >
+
+
+
+
+
+ {props.fetchCountriesLoading ? (
+
+
+
+ ) : (
+ props.countries?.map((country) => (
+
+ {country.name}
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+
+
+
+ setFieldValue("birthDate", value)}
+ />
+
+
+
+
+
+
+ زبان ترجیحی بیمار
+
+
+ setFieldValue(
+ "preferredLanguage",
+ value as "fa" | "en" | "ar"
+ )
+ }
+ >
+
+
+
+
+ فارسی
+ انگلیسی
+ عربی
+
+
+
+
+
+
+
+ خدمت مورد نظر
+
+ setFieldValue("specialty", value as "fa" | "en" | "ar")
+ }
+ >
+
+
+
+
+ فارسی
+ انگلیسی
+ عربی
+
+
+
+
+
+
+
+ آدرس
+ setFieldValue(`address`, e.target.value)}
+ />
+
+
+
+
+
+ پیام
+ setFieldValue(`message`, e.target.value)}
+ />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ثبت پذیرش
+
+
+
+ >
+ );
+}
diff --git a/src/components/forms/patients/edit/InnerUpdatePatientForm.tsx b/src/components/forms/patients/edit/InnerUpdatePatientForm.tsx
new file mode 100644
index 0000000..f9b6cfb
--- /dev/null
+++ b/src/components/forms/patients/edit/InnerUpdatePatientForm.tsx
@@ -0,0 +1,319 @@
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import SelectCalendar from "@/components/SelectCalendar";
+import {Button} from "@/components/ui/button";
+import Loader from "@/components/Loader";
+
+import {
+ CreatePatientFormValues,
+ UpdatePatientFormProps,
+} from "./UpdatePatientForm";
+
+export default function InnerUpdatePatientForm(
+ props: FormikProps & UpdatePatientFormProps
+) {
+ const {setFieldValue} = props;
+ return (
+ <>
+
+
+
+
+ نام
+ setFieldValue(`firstName`, e.target.value)}
+ required
+ />
+
+
+
+
+
+ نام خانوادگی
+ setFieldValue(`lastName`, e.target.value)}
+ />
+
+
+
+
+
+
+ جنسیت
+ setFieldValue("sex", value)}
+ >
+
+
+
+
+
+ مرد
+ زن
+ سایر
+
+
+
+
+
+
+
+
+ شماره تماس
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+
+
+
+ کد پاسپورت (افرادغیرایرانی)
+
+ setFieldValue(`passportCode`, e.target.value)}
+ />
+
+
+
+
+
+
+ کد ملی (افراد ایرانی)
+
+
+ setFieldValue(`nationalityCode`, e.target.value)
+ }
+ />
+
+
+
+
+
+ ملیت بیمار
+ setFieldValue("nationality", value)}
+ >
+
+
+
+
+
+ {props.fetchCountriesLoading ? (
+
+
+
+ ) : (
+
+ {props.countries?.map((country) => (
+
+ {country.name}
+
+ ))}
+
+ )}
+
+
+
+
+
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+
+
+
+ setFieldValue("birthDate", value)}
+ />
+
+
+
+
+
+
+ زبان ترجیحی بیمار
+
+
+ setFieldValue(
+ "preferredLanguage",
+ value as "fa" | "en" | "ar"
+ )
+ }
+ >
+
+
+
+
+ فارسی
+ انگلیسی
+ عربی
+
+
+
+
+
+
+
+ آدرس
+ setFieldValue(`address`, e.target.value)}
+ />
+
+
+
+ {/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
*/}
+
+ ذخیره اطلاعات
+
+
+
+ >
+ );
+}
diff --git a/src/components/forms/patients/edit/UpdatePatientForm.ts b/src/components/forms/patients/edit/UpdatePatientForm.ts
new file mode 100644
index 0000000..758d42d
--- /dev/null
+++ b/src/components/forms/patients/edit/UpdatePatientForm.ts
@@ -0,0 +1,79 @@
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+
+import {getSinglePatientDataType, ServerResponse} from "@/types";
+import {toast} from "react-toastify";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import InnerUpdatePatientForm from "./InnerUpdatePatientForm";
+
+export interface CreatePatientFormValues {
+ firstName: string;
+ lastName: string;
+ nationality: string | null;
+ nationalityCode: string;
+ passportCode: string;
+ age?: number | null;
+ sex?: "male" | "female" | "other";
+ birthDate?: string | Date;
+ address?: string;
+ phone?: string;
+ email?: string;
+ preferredLanguage?: string;
+ postalCode?: string;
+}
+
+export type UpdatePatientVariables = {
+ data: CreatePatientFormValues;
+ id: string;
+};
+export interface UpdatePatientFormProps {
+ router: AppRouterInstance;
+ preValues: getSinglePatientDataType;
+ countries?: {
+ name: string;
+ callCode: string;
+ id: number;
+ coverId: number | null;
+ createdAt: Date;
+ }[];
+ fetchCountriesLoading:boolean,
+ updateFn: UseMutateAsyncFunction<
+ {status: number; data?: getSinglePatientDataType[]; message: string},
+ Error,
+ UpdatePatientVariables,
+ ServerResponse
+ >;
+ id: string;
+}
+
+const UpdatePatientForm = withFormik<
+ UpdatePatientFormProps,
+ CreatePatientFormValues
+>({
+ mapPropsToValues: (props) => ({
+ firstName: props?.preValues?.firstName || "",
+ lastName: props?.preValues?.lastName || "",
+ nationality: props?.preValues?.nationalityId ? String(props?.preValues?.nationalityId) : null,
+ nationalityCode: props?.preValues?.nationalityCode || "",
+ passportCode: props?.preValues?.passportCode || "",
+ postalCode: props?.preValues?.postalCode || "",
+ sex: props?.preValues?.sex || "male",
+ address: props?.preValues?.address || "",
+ birthDate: props?.preValues?.birthDate || "",
+ email: props?.preValues?.email || "",
+ phone: props?.preValues?.phone || "",
+ preferredLanguage: "en",
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+
+ console.log(props.id)
+ const {message} = await props.updateFn({id: props.id,data: values, });
+ toast.success(message);
+ } catch{
+ toast.error("dasd");
+ }
+ },
+})(InnerUpdatePatientForm);
+
+export default UpdatePatientForm;
diff --git a/src/components/forms/patients/new/CreatePatientForm.ts b/src/components/forms/patients/new/CreatePatientForm.ts
new file mode 100644
index 0000000..d3786d1
--- /dev/null
+++ b/src/components/forms/patients/new/CreatePatientForm.ts
@@ -0,0 +1,175 @@
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {toast} from "react-toastify";
+import privateApi from "@/service/http/privateCall.axios";
+import {handleAxiosError} from "@/lib/utils";
+import * as yup from "yup";
+import InnerCreatePatientForm from "./InnerCreatePatientForm";
+const CreatePatientValidationSchema = yup
+ .object({
+ firstName: yup
+ .string()
+
+ .required("نام بیمار الزامیست")
+ .test(
+ "not-empty",
+ "نام بیمار نمی تواند خالی باشد",
+ (v) => !!v && v.trim().length > 0
+ ),
+
+ lastName: yup
+ .string()
+ .required("نام خانوادگی بیمار الزامیست")
+ .test(
+ "not-empty",
+ "نام خانوادگی بیمار نمی تواند خالی باشد",
+ (v) => !!v && v.trim().length > 0
+ ),
+
+ nationality: yup
+ .string()
+ .required("ملیت بیمار وارد نشده است")
+ .test(
+ "not-empty",
+ "ملیت بیمار نمیتواند خالی باشد",
+ (v) => !!v && v.trim().length > 0
+ ),
+ phone: yup
+ .string()
+ .nullable()
+ .max(20, "شماره تماس بیشتر از حد مجاز است")
+ .optional(),
+
+ email: yup
+ .string()
+ .nullable()
+ .email("ایمیل نامعتبر است")
+ .max(255, "ایمیل نباید بیشتر از 255 کاراکتر باشد")
+ .optional(),
+ sex: yup
+ .mixed<"male" | "female" | "other">()
+ .oneOf(["male", "female", "other"], "جنسیت بیمار نامعتبر است")
+ .required("جنسیت بیمار وارد نشده است"),
+
+ address: yup.string().optional(),
+
+
+
+ preferredLanguage: yup
+ .string()
+ .optional()
+ .typeError("زبان ترجیحی معتبر نیست"),
+
+ birthDate: yup
+ .string()
+ .optional()
+ .test(
+ "is-iso-date",
+ "تاریخ تولد باید به فرمت ISO معتبر باشد",
+ (value) => {
+ if (!value) return true;
+
+ // قبول فقط ISO قابل parse
+ const date = new Date(value);
+ return !isNaN(date.getTime());
+ }
+ ),
+
+ age: yup
+ .number()
+ .integer("سن باید عدد صحیح باشد")
+ .min(0, "سن نمیتواند کمتر از ۰ باشد")
+ .max(150, "سن نمیتواند بیشتر از 150 باشد")
+ .optional()
+ .typeError("سن باید عدد باشد"),
+ })
+ .test(
+ "phone-or-email-required",
+ "وارد کردن تلفن یا ایمیل الزامی است",
+ (value) => {
+ if (!value) return false;
+
+ const hasPhone =
+ typeof value.phone === "string" && value.phone.trim().length > 0;
+
+ const hasEmail =
+ typeof value.email === "string" && value.email.trim().length > 0;
+
+ return hasPhone || hasEmail;
+ }
+ )
+ .test(
+ "passport-nationality-code-required",
+ "وارد کردن کدملی یا کد پاسپورت الزامیست",
+ (value) => {
+ if (!value) return false;
+
+ const hasNCode =
+ typeof value.phone === "string" && value.phone.trim().length > 0;
+
+ const hasPCode =
+ typeof value.email === "string" && value.email.trim().length > 0;
+
+ return hasNCode || hasPCode;
+ }
+ );
+export interface CreatePatientFormValues {
+ firstName: string;
+ lastName: string;
+ nationality: string | null;
+ nationalityCode: string;
+ passportCode: string;
+ age: number | null;
+ sex?: "male" | "female" | "other";
+ birthDate?: string;
+ address?: string;
+ phone?: string;
+ email?: string;
+ preferredLanguage?: string;
+ postalCode?: string;
+}
+export interface CreatePatientFormProps {
+ router: AppRouterInstance;
+ countries?: {
+ name: string;
+ callCode: string;
+ id: number;
+ coverId: number | null;
+ createdAt: Date;
+ }[];
+ fetchCountriesLoading: boolean;
+}
+
+const CreatePatientForm = withFormik<
+ CreatePatientFormProps,
+ CreatePatientFormValues
+>({
+ mapPropsToValues: () => ({
+ firstName: "",
+ lastName: "",
+ age: null,
+ nationality: null,
+ nationalityCode: "",
+ passportCode: "",
+ postalCode: "",
+ sex: "male",
+ address: "",
+ birthDate: "",
+ email: "",
+ phone: "",
+ preferredLanguage: "en",
+ }),
+ validationSchema: CreatePatientValidationSchema,
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await privateApi
+ .post("/patient/create", values)
+ .then((res) => res.data);
+ toast.success(message);
+ } catch (error) {
+ toast?.error(handleAxiosError(error));
+ }
+ },
+})(InnerCreatePatientForm);
+
+export default CreatePatientForm;
diff --git a/src/components/forms/patients/new/InnerCreatePatientForm.tsx b/src/components/forms/patients/new/InnerCreatePatientForm.tsx
new file mode 100644
index 0000000..cb11a50
--- /dev/null
+++ b/src/components/forms/patients/new/InnerCreatePatientForm.tsx
@@ -0,0 +1,334 @@
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+import {
+ CreatePatientFormProps,
+ CreatePatientFormValues,
+} from "./CreatePatientForm";
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import SelectCalendar from "@/components/SelectCalendar";
+import {Button} from "@/components/ui/button";
+import Loader from "@/components/Loader";
+
+
+export default function InnerCreatePatientForm(
+ props: FormikProps & CreatePatientFormProps
+) {
+ const {setFieldValue} = props;
+ return (
+ <>
+
+
+
+
+ نام
+ setFieldValue(`firstName`, e.target.value)}
+ required
+ />
+
+
+
+
+
+ نام خانوادگی
+ setFieldValue(`lastName`, e.target.value)}
+ />
+
+
+
+
+
+ سن
+ setFieldValue(`age`, e.target.value)}
+ />
+
+
+
+
+
+ جنسیت
+ setFieldValue("sex", value)}
+ >
+
+
+
+
+
+ مرد
+ زن
+ سایر
+
+
+
+
+
+
+
+
+ شماره تماس
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+
+
+
+ کد پاسپورت (افرادغیرایرانی)
+
+ setFieldValue(`passportCode`, e.target.value)}
+ />
+
+
+
+
+
+
+ کد ملی (افراد ایرانی)
+
+
+ setFieldValue(`nationalityCode`, e.target.value)
+ }
+ />
+
+
+
+
+
+ ملیت بیمار
+ setFieldValue("nationality", value)}
+ >
+
+
+
+
+
+ {props.fetchCountriesLoading ? (
+
+
+
+ ) : (
+ props.countries?.map((country) => (
+
+ {country.name}
+
+ ))
+ )}
+
+
+
+
+
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+
+
+
+ setFieldValue("birthDate", value)}
+ />
+
+
+
+
+
+
+ زبان ترجیحی بیمار
+
+
+ setFieldValue(
+ "preferredLanguage",
+ value as "fa" | "en" | "ar"
+ )
+ }
+ >
+
+
+
+
+ فارسی
+ انگلیسی
+ عربی
+
+
+
+
+
+
+
+ آدرس
+ setFieldValue(`address`, e.target.value)}
+ />
+
+
+
+ {/*
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
*/}
+
+ ذخیره اطلاعات
+
+
+
+ >
+ );
+}
diff --git a/src/components/forms/reset-password/InnerResetPasswordForm.tsx b/src/components/forms/reset-password/InnerResetPasswordForm.tsx
new file mode 100644
index 0000000..261c095
--- /dev/null
+++ b/src/components/forms/reset-password/InnerResetPasswordForm.tsx
@@ -0,0 +1,57 @@
+"use client"
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+import {ResetPasswordFormValues} from "./ResetPasswordForm";
+import {Field, FieldGroup, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {Button} from "@/components/ui/button";
+
+export default function InnerResetPasswordForm(
+ props: FormikProps
+) {
+ const isValid = Boolean(
+ props.values.username.trim() && props.values.email.trim()
+ );
+ return (
+
+
+
+
+
+ نام کاربری
+
+ props.setFieldValue("username", e.target.value)}
+ />
+
+
+
+ ایمیل
+ props.setFieldValue("email", e.target.value)}
+ />
+
+
+
+
+ ارسال لینک
+
+
+
+
+ );
+}
diff --git a/src/components/forms/reset-password/ResetPasswordForm.ts b/src/components/forms/reset-password/ResetPasswordForm.ts
new file mode 100644
index 0000000..f83f977
--- /dev/null
+++ b/src/components/forms/reset-password/ResetPasswordForm.ts
@@ -0,0 +1,38 @@
+"use client";
+import {withFormik} from "formik";
+import InnerResetPasswordForm from "./InnerResetPasswordForm";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import axios from "axios";
+import {toast} from "react-toastify";
+import privateApi from "@/service/http/privateCall.axios";
+
+export interface ResetPasswordFormValues {
+ username: string;
+ email: string;
+}
+
+export interface ResetPasswordFormProps {
+ router: AppRouterInstance;
+}
+const ResetPasswordForm = withFormik<
+ ResetPasswordFormProps,
+ ResetPasswordFormValues
+>({
+ mapPropsToValues: () => ({
+ username: "",
+ email: "",
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ await privateApi.post("/auth/forgot-password", values);
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ toast.error(error.response?.data?.error?.message);
+ } else {
+ console.error("Unexpected error:", error);
+ }
+ }
+ },
+})(InnerResetPasswordForm);
+
+export default ResetPasswordForm;
diff --git a/src/components/forms/tos/create/CreateTosForm.ts b/src/components/forms/tos/create/CreateTosForm.ts
new file mode 100644
index 0000000..523ed9c
--- /dev/null
+++ b/src/components/forms/tos/create/CreateTosForm.ts
@@ -0,0 +1,47 @@
+"use client";
+import {QueryClient, UseMutateAsyncFunction} from "@tanstack/react-query";
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {SetStateAction} from "react";
+import {toast} from "react-toastify";
+import InnerCreateTosForm from "./InnerCreateTosForm";
+import { ServerResponse } from "@/types";
+
+export interface CreateTosFormValues {
+ title: string;
+ content: string;
+ version: string;
+ isActive: boolean;
+}
+
+export interface CreateFormProps {
+ router: AppRouterInstance;
+ isPending: boolean;
+ createFn: UseMutateAsyncFunction;
+ mode: "new" | "edit" | "form";
+ setMode: React.Dispatch>;
+ queryClient: QueryClient;
+}
+
+const CreateTosForm = withFormik({
+ mapPropsToValues: () => ({
+ title: "",
+ content: "",
+ isActive: false,
+ version: "",
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ await props.createFn(values);
+ toast.success("done");
+
+ props.queryClient.invalidateQueries({queryKey: ["get-all-tos"]});
+ props.setMode("new")
+ props.router.refresh();
+ } catch {
+ toast.error("asdadasd");
+ }
+ },
+})(InnerCreateTosForm);
+
+export default CreateTosForm;
diff --git a/src/components/forms/tos/create/InnerCreateTosForm.tsx b/src/components/forms/tos/create/InnerCreateTosForm.tsx
new file mode 100644
index 0000000..d15f7c8
--- /dev/null
+++ b/src/components/forms/tos/create/InnerCreateTosForm.tsx
@@ -0,0 +1,87 @@
+"use client";
+import {Form, FormikProps} from "formik";
+import React, {Suspense, useRef} from "react";
+import {CreateFormProps, CreateTosFormValues} from "./CreateTosForm";
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import Loader from "@/components/Loader";
+import RichTextEditor, {RichTextEditorHandle} from "@/components/TextEditor";
+import {Button} from "@/components/ui/button";
+
+export default function InnerCreateTosForm(
+ props: FormikProps & CreateFormProps
+) {
+ const editorRef = useRef(null);
+ const handleSave = () => {
+ const html = editorRef.current?.getContent();
+ props.setFieldValue("content", html);
+ props.submitForm();
+ };
+ return (
+
+
+ عنوان
+ props.setFieldValue("title", e.target.value)}
+ className="bg-white"
+ />
+
+
+ ورژن
+ props.setFieldValue("version", e.target.value)}
+ className="bg-white"
+ />
+
+
+ فعال بودن نسخه
+
+ props.setFieldValue("isActive", value)}
+ dir="rtl"
+ >
+
+
+
+
+
+ فعال باشد
+ فعال نباشد
+
+
+
+
+
+ }>
+
+
+
+
+
+
+ {props.isPending ? : "ثبت نسخه جدید"}
+
+
+
+ props.setMode("new")}
+ >
+ انصراف
+
+
+
+
+ );
+}
diff --git a/src/components/forms/tos/update/InnerUpdateTosForm.tsx b/src/components/forms/tos/update/InnerUpdateTosForm.tsx
new file mode 100644
index 0000000..f36a0e3
--- /dev/null
+++ b/src/components/forms/tos/update/InnerUpdateTosForm.tsx
@@ -0,0 +1,87 @@
+import {Form, FormikProps} from "formik";
+import React, {Suspense, useRef} from "react";
+import {CreateTosFormValues} from "../create/CreateTosForm";
+import {UpdateTosFormProps} from "./UpdateTosForm";
+import {Button} from "@/components/ui/button";
+import {Field, FieldLabel} from "@/components/ui/field";
+import Loader from "@/components/Loader";
+import RichTextEditor, {RichTextEditorHandle} from "@/components/TextEditor";
+import {Input} from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {SelectItem} from "@radix-ui/react-select";
+
+export default function InnerUpdateTosForm(
+ props: FormikProps & UpdateTosFormProps
+) {
+ const editorRef = useRef(null);
+
+ return (
+
+
+ عنوان
+ props.setFieldValue("title", e.target.value)}
+ className="bg-white"
+ />
+
+
+ ورژن
+ props.setFieldValue("version", e.target.value)}
+ className="bg-white"
+ />
+
+
+ فعال بودن نسخه
+
+
+ props.setFieldValue("isActive", value)
+ }
+ >
+
+
+
+
+
+ فعال باشد
+ فعال نباشد
+
+
+
+
+
+ }>
+
+
+
+
+
+ ثبت تغییرات
+
+
+ {
+ props.setMode("new");
+ props.setVersion("");
+ }}
+ >
+ انصراف
+
+
+
+
+ );
+}
diff --git a/src/components/forms/tos/update/UpdateTosForm.ts b/src/components/forms/tos/update/UpdateTosForm.ts
new file mode 100644
index 0000000..e3e7855
--- /dev/null
+++ b/src/components/forms/tos/update/UpdateTosForm.ts
@@ -0,0 +1,32 @@
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import InnerUpdateTosForm from "./InnerUpdateTosForm";
+import {QueryClient, UseMutateAsyncFunction} from "@tanstack/react-query";
+import {SetStateAction} from "react";
+import {CreateTosFormValues} from "../create/CreateTosForm";
+import {TOS} from "@/hooks/tos";
+import { ServerResponse } from "@/types";
+
+export interface UpdateTosFormProps {
+ router: AppRouterInstance;
+ updateFn: UseMutateAsyncFunction;
+ updatePending:boolean,
+ mode: "new" | "edit" | "form";
+ setMode: React.Dispatch>;
+ preValues: TOS;
+ queryClient:QueryClient,
+ version:string,
+ setVersion:React.Dispatch>
+}
+
+const UpdateTosForm = withFormik({
+ mapPropsToValues: (props) => ({
+ content: props.preValues.content || "",
+ title: props.preValues.title || "",
+ version: props.preValues.version || "",
+ isActive: props.preValues.isActive || false,
+ }),
+ handleSubmit: async (values, {props}) => {},
+})(InnerUpdateTosForm);
+
+export default UpdateTosForm;
diff --git a/src/components/forms/transfer-packages/new/CreateTransferPackageForm.tsx b/src/components/forms/transfer-packages/new/CreateTransferPackageForm.tsx
new file mode 100644
index 0000000..a047bcf
--- /dev/null
+++ b/src/components/forms/transfer-packages/new/CreateTransferPackageForm.tsx
@@ -0,0 +1,60 @@
+"use client"
+import { withFormik } from "formik";
+import * as Yup from "yup";
+import InnerCreateTransferPackageForm from "./InnerCreateTransferPackageForm";
+export interface CreateTransferPackageValues {
+ location: string;
+ price: string;
+ locationImageIds: string[];
+ galleryImageIds: string[];
+ translations: {
+ lang_id: number;
+ name: string;
+ content: string;
+ }[];
+}
+
+export interface CreateTransferPackageProps {
+ languages: {
+ id: number;
+ title: string;
+ slug: string;
+ }[];
+ createPending: boolean;
+
+}
+
+
+const CreateTransferPackageForm = withFormik<
+ CreateTransferPackageProps,
+ CreateTransferPackageValues
+>({
+ mapPropsToValues: (props) => ({
+ location: "",
+ price: "",
+ locationImageIds: [],
+ galleryImageIds: [],
+ translations: props.languages.map((lang) => ({
+ lang_id: lang.id,
+ name: "",
+ content: "",
+ })),
+ }),
+
+ validationSchema: Yup.object().shape({
+ location: Yup.string().required("موقعیت الزامی است"),
+ price: Yup.string().required("قیمت الزامی است"),
+ translations: Yup.array().of(
+ Yup.object().shape({
+ name: Yup.string().required("نام الزامی است"),
+ content: Yup.string().required("محتوا الزامی است"),
+ }),
+ ),
+ }),
+
+ handleSubmit: (values, { props }) => {
+ console.log(values)
+ },
+})(InnerCreateTransferPackageForm);
+
+export default CreateTransferPackageForm;
diff --git a/src/components/forms/transfer-packages/new/InnerCreateTransferPackageForm.tsx b/src/components/forms/transfer-packages/new/InnerCreateTransferPackageForm.tsx
new file mode 100644
index 0000000..0b06f50
--- /dev/null
+++ b/src/components/forms/transfer-packages/new/InnerCreateTransferPackageForm.tsx
@@ -0,0 +1,159 @@
+"use client"
+import { ErrorMessage, Form, FormikProps } from "formik";
+import React, { useRef, useState } from "react";
+import { Field, FieldLabel } from "@/components/ui/field";
+import { Input } from "@/components/ui/input";
+import RichTextEditor, { RichTextEditorHandle } from "@/components/TextEditor";
+import { Button } from "@/components/ui/button";
+import Loader from "@/components/Loader";
+import UploadDropzone from "@/components/uploadDropzone";
+
+import {
+ CreateTransferPackageValues,
+ CreateTransferPackageProps,
+} from "./CreateTransferPackageForm";
+
+export default function InnerCreateTransferPackageForm(
+ props: FormikProps &
+ CreateTransferPackageProps,
+) {
+ const { setFieldValue } = props;
+
+ const [locationImages, setLocationImages] = useState([]);
+ const [galleryImages, setGalleryImages] = useState([]);
+
+ const editorRefs = useRef([]);
+
+ const handleSave = () => {
+ props.values.translations.forEach((_, i) => {
+ const content = editorRefs.current[i]?.getContent() || "";
+ setFieldValue(`translations.${i}.content`, content);
+ });
+
+ setFieldValue("locationImageIds", locationImages);
+ setFieldValue("galleryImageIds", galleryImages);
+
+ props.handleSubmit();
+ };
+
+ return (
+
+
+ {/* Location */}
+
+
+ موقعیت مکانی محل اقامت ( آدرس گوگل مپ )
+
+ setFieldValue("location", e.target.value)
+ }
+ />
+
+
+
+
+ قیمت پکیج
+
+ setFieldValue("price", e.target.value)
+ }
+ />
+
+
+
+
+ {/* Location Images */}
+
+ تصاویر لوکیشن های تفریحی
+
+
+
+ {/* Gallery Images */}
+
+ تصاویر گالری
+
+
+
+ {/* Translations */}
+ {props.values.translations.map((tr, index) => {
+ const lang = props.languages.find(
+ (l) => l.id === tr.lang_id,
+ );
+
+ return (
+
+
+ ردیف برای زبان {lang?.title} ({lang?.slug})
+
+
+
+ نام پکیج
+
+ setFieldValue(
+ `translations.${index}.name`,
+ e.target.value,
+ )
+ }
+ />
+
+
+
+
+ محتوا
+
+ (editorRefs.current[index] = el!)
+ }
+ value={tr.content}
+ />
+
+
+
+ );
+ })}
+
+
+ {props.createPending ? : "ثبت اطلاعات"}
+
+
+ );
+}
diff --git a/src/components/forms/transfer-team/member/new/CreateTransferTeamMemberForm.ts b/src/components/forms/transfer-team/member/new/CreateTransferTeamMemberForm.ts
new file mode 100644
index 0000000..42ba894
--- /dev/null
+++ b/src/components/forms/transfer-team/member/new/CreateTransferTeamMemberForm.ts
@@ -0,0 +1,127 @@
+"use client";
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import * as Yup from "yup";
+import {UsersType} from "@/constants";
+import {Expertise, Language} from "@/types";
+import {toast} from "react-toastify";
+import axios from "axios";
+import privateApi from "@/service/http/privateCall.axios";
+import InnerCreateTransferTeamMember from "./InnerCreateTransferTeamMember";
+
+export const translationSchema = Yup.object({
+ lang_id: Yup.number().required("Language is required"),
+
+ firstName: Yup.string()
+ .max(255, "First name is too long")
+ .required("First name is required"),
+
+ lastName: Yup.string()
+ .max(255, "Last name is too long")
+ .required("Last name is required"),
+
+ position: Yup.string()
+ .max(255, "Position is too long")
+ .required("Position is required"),
+
+ expertise: Yup.string()
+ .max(500, "Expertise is too long")
+ .nullable()
+ .notRequired(),
+});
+
+export const CreateUserValidationSchema = Yup.object({
+ email: Yup.string().email("فرمت ایمیل اشتباه است").required("ایمیل الزامیست"),
+ // slug: Yup.string().required("اسلاگ الزامیست"),
+
+ medicalNumber: Yup.string().required("شماره نظام پزشکی الزامیست"),
+
+ phone: Yup.string()
+ .matches(/^(?:\+98|0)?9\d{9}$/, "فرمت موبایل اشتباه است")
+ .nullable()
+ .notRequired(),
+
+ type: Yup.mixed()
+ .oneOf(
+ [UsersType.DOCTOR, UsersType.TRANSFER_TEAM, UsersType.DEPARTMENT],
+ "دسته بندی کاربر اشتباه است"
+ )
+ .required("دسته بندی کاربر وارد نشده است"),
+
+ imageId: Yup.number().nullable().notRequired(),
+
+ expertiseId: Yup.number()
+ .typeError("فرمت تخصص اشتباه است")
+ .required("تخصص وارد نشده است"),
+
+ translations: Yup.array()
+ .of(translationSchema)
+ .min(1, "حداقل یک ترجمه الزامی است")
+ .required("Translations are required"),
+});
+export interface CreateUserTranslation {
+ lang_id: number;
+ firstName: string;
+ lastName: string;
+ position: string;
+ expertise?: string | null;
+}
+function buildInitialTranslations(
+ languages: Language[]
+): CreateUserTranslation[] {
+ return languages.map((lang) => ({
+ lang_id: lang.id,
+ firstName: "",
+ lastName: "",
+ position: "",
+ }));
+}
+export interface CreateTransferTeamMemberValues {
+ translations: CreateUserTranslation[];
+ type: UsersType;
+ email: string;
+ phone: string;
+ medicalNumber: string;
+ imageId: number | null;
+ expertiseId: number | null;
+}
+
+export interface CreateTransferTeamMemberProps {
+ router: AppRouterInstance;
+ languages: Language[];
+ expertises: Expertise[];
+ initialValues?: Partial;
+}
+const CreateTransferTeamMember = withFormik<
+ CreateTransferTeamMemberProps,
+ CreateTransferTeamMemberValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: buildInitialTranslations(props.languages),
+ email: "",
+ phone: "",
+ medicalNumber: "",
+ imageId: null,
+ expertiseId: null,
+ type: "TRANSFER_TEAM",
+ ...props.initialValues, // اگر فیلدهای دیگری بخواهی
+ }),
+ // validationSchema: CreateUserValidationSchema,
+ handleSubmit: async (values, {props}) => {
+
+ try {
+ const data = await privateApi.post("/user/create", values);
+ toast.success(data?.data?.message);
+ } catch (error) {
+ if (axios.isAxiosError(error)) {
+ // اینجا میدونیم که خطا از axios است
+ toast.error(error.response?.data?.error?.message);
+ } else {
+ // خطای عمومی
+ console.error("Unexpected error:", error);
+ }
+ }
+ },
+})(InnerCreateTransferTeamMember);
+
+export default CreateTransferTeamMember;
diff --git a/src/components/forms/transfer-team/member/new/InnerCreateTransferTeamMember.tsx b/src/components/forms/transfer-team/member/new/InnerCreateTransferTeamMember.tsx
new file mode 100644
index 0000000..ce062cc
--- /dev/null
+++ b/src/components/forms/transfer-team/member/new/InnerCreateTransferTeamMember.tsx
@@ -0,0 +1,207 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {Button} from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {
+ CreateTransferTeamMemberProps,
+ CreateTransferTeamMemberValues,
+} from "./CreateTransferTeamMemberForm";
+
+export default function InnerCreateTransferTeamMember(
+ props: FormikProps &
+ CreateTransferTeamMemberProps
+) {
+ const {setFieldValue, ...rest} = props;
+
+ console.log(props.errors);
+ return (
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+ شماره موبایل
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+ {props.values.type === "DOCTOR" && (
+
+ شماره نظام پزشکی
+ setFieldValue(`medicalNumber`, e.target.value)}
+ />
+
+
+ )}
+ {props.values.type !== "TRANSFER_TEAM" && (
+
+ تخصص
+
+
+ setFieldValue(`expertiseId`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props.expertises.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang_id === 1
+ );
+
+ return (
+
+ {translation?.displayName ?? exp.slug}
+
+ );
+ })}
+
+
+
+
+
+ )}
+
+ {props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ setFieldValue(
+ `translations.${index}.firstName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ نام خانوادگی
+
+
+ setFieldValue(
+ `translations.${index}.lastName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ {/* Position */}
+
+
+ سمت
+
+
+ setFieldValue(
+ `translations.${index}.position`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ );
+ })}
+ ثبت اطلاعات
+
+ );
+}
diff --git a/src/components/forms/transfer-team/member/update/InnerUpdateTransferTeamMemberForm.tsx b/src/components/forms/transfer-team/member/update/InnerUpdateTransferTeamMemberForm.tsx
new file mode 100644
index 0000000..50aedd4
--- /dev/null
+++ b/src/components/forms/transfer-team/member/update/InnerUpdateTransferTeamMemberForm.tsx
@@ -0,0 +1,200 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React from "react";
+
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {Button} from "@/components/ui/button";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import { CreateDoctorFormValues } from "./UpdateTransferTeamMemberForm";
+import { UpdateDepartmentMemberFormProps } from "../../department/update/UpdateDepartmentMemberForm";
+
+
+export default function InnerUpdateTransferTeamMemberForm(
+ props: FormikProps & UpdateDepartmentMemberFormProps
+) {
+ const {setFieldValue, ...rest} = props;
+
+ return (
+
+
+
+ ایمیل
+ setFieldValue(`email`, e.target.value)}
+ />
+
+
+
+ شماره موبایل
+ setFieldValue(`phone`, e.target.value)}
+ />
+
+
+
+ شماره نظام پزشکی
+ setFieldValue(`medicalNumber`, e.target.value)}
+ />
+
+
+
+ تخصص
+
+
+ setFieldValue(`expertiseId`, value ? Number(value) : null)
+ }
+ >
+
+
+
+
+
+ {props.expertises.map((exp) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = exp.translations.find(
+ (t) => t.lang_id === 1
+ );
+
+ return (
+
+ {translation?.displayName ?? exp.slug}
+
+ );
+ })}
+
+
+
+
+
+
+ {props.values.translations && props.values.translations.map((tr, index) => {
+ const lang = props?.languages.find((l) => l.id === tr.lang_id);
+
+ return (
+
+
+ محتوای {lang?.title} ({lang?.slug})
+
+
+ {/* First Name */}
+
+
+ نام
+
+
+ setFieldValue(
+ `translations.${index}.firstName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ {/* Last Name */}
+
+
+ نام خانوادگی
+
+
+ setFieldValue(
+ `translations.${index}.lastName`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ {/* Position */}
+
+
+ سمت
+
+
+ setFieldValue(
+ `translations.${index}.position`,
+ e.target.value
+ )
+ }
+ />
+
+
+
+ );
+ })}
+ ثبت اطلاعات
+
+ );
+}
diff --git a/src/components/forms/transfer-team/member/update/UpdateTransferTeamMemberForm.ts b/src/components/forms/transfer-team/member/update/UpdateTransferTeamMemberForm.ts
new file mode 100644
index 0000000..a202b9e
--- /dev/null
+++ b/src/components/forms/transfer-team/member/update/UpdateTransferTeamMemberForm.ts
@@ -0,0 +1,62 @@
+import {withFormik} from "formik";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+
+import {Expertise, getSingleUserDataType, Language, ServerResponse} from "@/types";
+import {toast} from "react-toastify";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import InnerUpdateTransferTeamMemberForm from "./InnerUpdateTransferTeamMemberForm";
+import {UsersType} from "@/constants";
+import { CreateUserTranslation } from "../new/CreateTransferTeamMemberForm";
+
+export interface CreateDoctorFormValues {
+ translations: CreateUserTranslation[];
+ type: UsersType;
+ email: string;
+ phone: string;
+ medicalNumber: string;
+ imageId: number | null;
+ expertiseId: number | null;
+}
+
+export type UpdateUserVariables = {
+ data: CreateDoctorFormValues;
+ id: string;
+};
+export interface UpdateTransferTeamMemberFormProps {
+ router: AppRouterInstance;
+ preValues: getSingleUserDataType;
+ expertises: Expertise[];
+ languages: Language[];
+ updateFn: UseMutateAsyncFunction<
+ {status: number; data?: getSingleUserDataType[]; message: string},
+ Error,
+ UpdateUserVariables,
+ ServerResponse
+ >;
+ id: string;
+}
+
+const UpdateTransferTeamMemberForm = withFormik<
+ UpdateTransferTeamMemberFormProps,
+ CreateDoctorFormValues
+>({
+ mapPropsToValues: (props) => ({
+ translations: props?.preValues?.translations,
+ email: props?.preValues?.email,
+ phone: props?.preValues?.phone,
+ expertiseId: props.preValues.expertiseId,
+ imageId: null,
+ medicalNumber: props?.preValues?.medicalNumber,
+ type: "TRANSFER_TEAM",
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.updateFn({data: values, id: props.id});
+ toast.success(message);
+ } catch (error) {
+ toast.error("dasd");
+ }
+ },
+})(InnerUpdateTransferTeamMemberForm);
+
+export default UpdateTransferTeamMemberForm;
diff --git a/src/components/forms/transfer-team/team/new/CreateTransferTeamForm.ts b/src/components/forms/transfer-team/team/new/CreateTransferTeamForm.ts
new file mode 100644
index 0000000..dcce72c
--- /dev/null
+++ b/src/components/forms/transfer-team/team/new/CreateTransferTeamForm.ts
@@ -0,0 +1,70 @@
+"use client";
+import {withFormik} from "formik";
+import InnerCreateTransferTeamForm from "./InnerCreateTransferTeamForm";
+import {AppRouterInstance} from "next/dist/shared/lib/app-router-context.shared-runtime";
+import {UsersType} from "@/constants";
+import {UseMutateAsyncFunction} from "@tanstack/react-query";
+import {toast} from "react-toastify";
+import {handleAxiosError} from "@/lib/utils";
+import { ServerResponse } from "@/types";
+
+export interface CreateTransferTeamFormProps {
+ router: AppRouterInstance;
+ members: {
+ translations: {
+ id: number;
+ lang_id: number | null;
+ firstName: string | null;
+ lastName: string | null;
+ position: string | null;
+ }[];
+
+ id: number;
+ slug: string;
+ type: UsersType;
+ phone: string | null;
+ email: string | null;
+ teamName: string | null;
+ medicalNumber: string | null;
+ expertiseId: number | null;
+ imageId: number | null;
+ createdAt: Date;
+ }[];
+ packages: [];
+ fetchMembersLoading: boolean;
+ fetchPackagesLoading: boolean;
+ createFn: UseMutateAsyncFunction;
+ createPending: boolean;
+}
+
+export interface CreateTransferTeamFormValues {
+ name: string;
+ members: {id: string | null; name: string; index: string}[];
+ introduction: string;
+ services: string;
+ duties: string;
+ packages: [];
+}
+const CreateTransferTeamForm = withFormik<
+ CreateTransferTeamFormProps,
+ CreateTransferTeamFormValues
+>({
+ mapPropsToValues: () => ({
+ name: "",
+ introduction: "",
+ services: "",
+ duties: "",
+ packages: [],
+ members: [],
+ }),
+ handleSubmit: async (values, {props}) => {
+ try {
+ const {message} = await props.createFn(values);
+ toast.success(message);
+ } catch (error) {
+ toast.error(handleAxiosError(error));
+ }
+ },
+})(InnerCreateTransferTeamForm);
+
+export default CreateTransferTeamForm;
diff --git a/src/components/forms/transfer-team/team/new/InnerCreateTransferTeamForm.tsx b/src/components/forms/transfer-team/team/new/InnerCreateTransferTeamForm.tsx
new file mode 100644
index 0000000..fda5c37
--- /dev/null
+++ b/src/components/forms/transfer-team/team/new/InnerCreateTransferTeamForm.tsx
@@ -0,0 +1,237 @@
+"use client";
+import {ErrorMessage, Form, FormikProps} from "formik";
+import React, {useRef, useState} from "react";
+import {
+ CreateTransferTeamFormProps,
+ CreateTransferTeamFormValues,
+} from "./CreateTransferTeamForm";
+import {Field, FieldLabel} from "@/components/ui/field";
+import {Input} from "@/components/ui/input";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
+import {Button} from "@/components/ui/button";
+import {Plus, Trash} from "lucide-react";
+import RichTextEditor, {RichTextEditorHandle} from "@/components/TextEditor";
+import {toast} from "react-toastify";
+
+export default function InnerCreateTransferTeamForm(
+ props: FormikProps & CreateTransferTeamFormProps
+) {
+ const introductionRef = useRef(null);
+ const servicesRef = useRef(null);
+ const dutiesRef = useRef(null);
+
+ const {setFieldValue, ...rest} = props;
+ const [members, setMembers] = useState<
+ {id: string | null; name: string; index: string}[]
+ >([]);
+
+ const handleSave = () => {
+ const introductionHtml = introductionRef.current?.getContent();
+ const servicesHtml = servicesRef.current?.getContent();
+ const dutiesHtml = dutiesRef.current?.getContent();
+ props.setFieldValue("introduction", introductionHtml);
+ props.setFieldValue("services", servicesHtml);
+ props.setFieldValue("duties", dutiesHtml);
+ props.values.members = members;
+ props.submitForm();
+ };
+
+ const handleValueChange = (value: string, index: string) => {
+ const memberSelected = props.members.find((item) => item.id === +value)!;
+ const rowSelected = members.find((item) => item.index === index)!;
+
+ rowSelected.id = String(memberSelected.id!);
+ rowSelected.name = memberSelected.slug;
+ };
+ return (
+
+
+
+ نام تیم
+ setFieldValue("name", e.target.value)}
+ />
+
+
+
+ متن معرفی تیم
+
+
+
+
+ متن خدمات تیم
+
+
+
+
+ متن وظایف تیم
+
+
+
+
+ انتخاب عضو
+
+
+ {members?.map((item, index) => (
+
+ {
+ return handleValueChange(value, item.index);
+ }}
+ >
+
+
+
+
+
+ {props.members?.length > 0 &&
+ props?.members?.map((member) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = member.translations.find(
+ (t) => t.lang_id === 1
+ );
+
+ return (
+
+ {translation?.firstName} - {translation?.lastName}
+
+ );
+ })}
+
+
+ {
+ const filtered = members.filter((item2) => item2 !== item);
+ setMembers(filtered);
+ }}
+ >
+
+
+
+ ))}
+
3}
+ onClick={() => {
+ if (members.length > 3) {
+ toast.error("حداکثر اعضای تیم 4 نفر می باشد");
+ return;
+ }
+ const newMember = {
+ id: "",
+ name: "",
+ index: crypto.randomUUID(),
+ };
+ setMembers((prev) => [...prev, newMember]);
+ }}
+ >
+
+ افزودن عضو
+
+
+
+
+
+ {/*
+ انتخاب پکیج گردشگری
+
+
+ {members?.map((item, index) => (
+
+
+
+
+
+
+
+ {props.packages?.length > 0 &&
+ props?.packages?.map((member) => {
+ // ترجمه مرتبط با همین زبان
+ const translation = member.translations.find(
+ (t) => t.lang_id === 1
+ );
+
+ return (
+
+ {translation?.firstName} - {translation?.lastName}
+
+ );
+ })}
+
+
+ {
+ const filtered = members.filter((item2) => item2 !== item);
+ setMembers(filtered);
+ }}
+ >
+
+
+
+ ))}
+
{
+ const newMember = {
+ id: "",
+ name: "",
+ index: crypto.randomUUID(),
+ };
+ setMembers((prev) => [...prev, newMember]);
+ }}
+ >
+
+ افزودن پکیج
+
+
+
+
+ */}
+
+
+ ثبت نهایی
+
+
+
+
+ );
+}
diff --git a/src/components/imageUploadDropzone.tsx b/src/components/imageUploadDropzone.tsx
new file mode 100644
index 0000000..10d2e26
--- /dev/null
+++ b/src/components/imageUploadDropzone.tsx
@@ -0,0 +1,239 @@
+"use client";
+
+import { useCallback, useState } from "react";
+import { useDropzone, FileRejection } from "react-dropzone";
+import { Button } from "@/components/ui/button";
+import { Progress } from "@/components/ui/progress";
+import { Upload, X, Loader2, FileText } from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import { useQueryClient } from "@tanstack/react-query";
+import { API_URL } from "@/constants";
+import { MeResponse } from "@/hooks";
+
+const ACCEPTED_TYPES = {
+ "application/pdf": [".pdf"],
+ "image/jpeg": [".jpg", ".jpeg"],
+ "image/webp": [".webp"],
+ "image/png": [".png"],
+};
+
+type UploadItem = {
+ file: File;
+ progress: number;
+ status: "idle" | "uploading" | "done" | "error";
+ xhr?: XMLHttpRequest;
+};
+
+type Props = {
+ documentType: "image";
+ maxSize?: number;
+ autoUpload?: boolean;
+};
+
+export default function ImageUploadDropzone({
+ documentType,
+ maxSize = 5 * 1024 * 1024,
+ autoUpload = false,
+}: Props) {
+ const qc = useQueryClient();
+ const user: MeResponse = qc.getQueryData(["me"])!;
+
+ const [items, setItems] = useState([]);
+ const [, setError] = useState(null);
+
+ const getPreviewUrl = (file: File) =>
+ file.type.startsWith("image/")
+ ? URL.createObjectURL(file)
+ : null;
+
+ const onDrop = useCallback(
+ (accepted: File[], rejected: FileRejection[]) => {
+ if (rejected.length) {
+ const reason = rejected[0].errors[0];
+ setError(
+ reason.code === "file-too-large"
+ ? "حجم فایل بیشتر از حد مجاز است"
+ : "فرمت فایل مجاز نیست"
+ );
+ return;
+ }
+
+ const newItems: UploadItem[] = accepted.map((file) => ({
+ file,
+ progress: 0,
+ status: "idle",
+ }));
+
+ setItems((prev) => [...prev, ...newItems]);
+ setError(null);
+
+ if (autoUpload) {
+ newItems.forEach((item) => startUpload(item.file));
+ }
+ },
+ [autoUpload]
+ );
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ multiple: true,
+ maxSize,
+ accept: ACCEPTED_TYPES,
+ });
+
+ const startUpload = async (file: File) => {
+ try {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file ? { ...i, status: "uploading" } : i
+ )
+ );
+
+ const tokenRes = await privateApi.post("/upload/request", {
+ userId: user.data.id,
+ type: documentType,
+ });
+
+ const uploadToken = tokenRes.data?.data?.uploadToken;
+ if (!uploadToken) throw new Error();
+
+ const xhr = new XMLHttpRequest();
+
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file ? { ...i, xhr } : i
+ )
+ );
+
+ const formData = new FormData();
+ formData.append("file", file);
+ formData.append("type", documentType.toString());
+
+ xhr.upload.onprogress = (e) => {
+ if (e.lengthComputable) {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file
+ ? {
+ ...i,
+ progress: Math.round((e.loaded / e.total) * 100),
+ }
+ : i
+ )
+ );
+ }
+ };
+
+ xhr.onload = () => {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file
+ ? { ...i, status: "done", progress: 100 }
+ : i
+ )
+ );
+ };
+
+ xhr.onerror = () => {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file ? { ...i, status: "error" } : i
+ )
+ );
+ };
+
+ xhr.open("POST", `${API_URL}/upload/upload`);
+ xhr.setRequestHeader("x-upload-token", uploadToken);
+ xhr.send(formData);
+ } catch {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file ? { ...i, status: "error" } : i
+ )
+ );
+ }
+ };
+
+ const cancelUpload = (file: File) => {
+ const item = items.find((i) => i.file === file);
+ item?.xhr?.abort();
+
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file
+ ? { ...i, status: "idle", progress: 0 }
+ : i
+ )
+ );
+ };
+
+ return (
+
+
+
+
+
+ فایلها را بکشید یا کلیک کنید
+
+
+
+ {items.map(({ file, progress, status }) => {
+ const preview = getPreviewUrl(file);
+
+ return (
+
+
+
+ {preview ? (
+
+ ) : (
+
+ )}
+
+
+
{file.name}
+
+ {status === "idle" && (
+
+ setItems((prev) =>
+ prev.filter((i) => i.file !== file)
+ )
+ }
+ />
+ )}
+
+
+
+
+ {status === "idle" && !autoUpload && (
+
startUpload(file)}>
+ شروع آپلود
+
+ )}
+
+ {status === "uploading" && (
+
cancelUpload(file)}
+ >
+
+ کنسل
+
+ )}
+
+ {status === "error" && (
+
آپلود ناموفق بود
+ )}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/components/restoreUserButton.tsx b/src/components/restoreUserButton.tsx
new file mode 100644
index 0000000..6fc14f0
--- /dev/null
+++ b/src/components/restoreUserButton.tsx
@@ -0,0 +1,60 @@
+"use client";
+import React from "react";
+import {Button} from "./ui/button";
+import {RefreshCcw, Trash} from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import {usePathname, useRouter} from "next/navigation";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+
+async function restoreUser(id: number | string,route:string) {
+ try {
+ const data = await privateApi.put(`/${route}/restore/${id}`);
+ } catch (error) {
+ console.log(error);
+ }
+}
+export default function RestoreUserButton({id,route}: {id: number | string,route:string}) {
+ const router = useRouter();
+ const pathname = usePathname();
+ const handleDelete = async () => {
+ await restoreUser(id,route);
+ window.location.pathname = pathname;
+ };
+ return (
+ <>
+
+
+
+
+
+
+
+
+ بازگردانی آیتم
+
+ آیا از بازگردانی این آیتم اطمینان دارد ؟
+
+
+
+
+
+ انصراف
+
+
+ بله
+
+
+
+
+ >
+ );
+}
diff --git a/src/components/ui/badge.tsx b/src/components/ui/badge.tsx
new file mode 100644
index 0000000..fd3a406
--- /dev/null
+++ b/src/components/ui/badge.tsx
@@ -0,0 +1,46 @@
+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"
+
+const badgeVariants = cva(
+ "inline-flex items-center justify-center rounded-full border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
+ {
+ variants: {
+ variant: {
+ default:
+ "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
+ secondary:
+ "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
+ destructive:
+ "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ },
+ }
+)
+
+function Badge({
+ className,
+ variant,
+ asChild = false,
+ ...props
+}: React.ComponentProps<"span"> &
+ VariantProps & { asChild?: boolean }) {
+ const Comp = asChild ? Slot : "span"
+
+ return (
+
+ )
+}
+
+export { Badge, badgeVariants }
diff --git a/src/components/ui/button.tsx b/src/components/ui/button.tsx
new file mode 100644
index 0000000..37a7d4b
--- /dev/null
+++ b/src/components/ui/button.tsx
@@ -0,0 +1,62 @@
+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"
+
+const buttonVariants = cva(
+ "inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive",
+ {
+ variants: {
+ variant: {
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
+ outline:
+ "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
+ secondary:
+ "bg-secondary text-secondary-foreground hover:bg-secondary/80",
+ ghost:
+ "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
+ sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
+ icon: "size-9",
+ "icon-sm": "size-8",
+ "icon-lg": "size-10",
+ },
+ },
+ defaultVariants: {
+ variant: "default",
+ size: "default",
+ },
+ }
+)
+
+function Button({
+ className,
+ variant = "default",
+ size = "default",
+ asChild = false,
+ ...props
+}: React.ComponentProps<"button"> &
+ VariantProps & {
+ asChild?: boolean
+ }) {
+ const Comp = asChild ? Slot : "button"
+
+ return (
+
+ )
+}
+
+export { Button, buttonVariants }
diff --git a/src/components/ui/calendar-hijri.tsx b/src/components/ui/calendar-hijri.tsx
new file mode 100644
index 0000000..c373d6f
--- /dev/null
+++ b/src/components/ui/calendar-hijri.tsx
@@ -0,0 +1,207 @@
+"use client";
+
+import * as React from "react";
+import {ChevronDownIcon, ChevronLeftIcon, ChevronRightIcon} from "lucide-react";
+import {getDefaultClassNames, type DayButton} from "react-day-picker";
+import {DayPicker} from "react-day-picker/persian";
+
+import {cn} from "@/lib/utils";
+import {Button, buttonVariants} from "@/components/ui/button";
+
+function CalendarHijriBase({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"];
+}) {
+ const defaultClassNames = getDefaultClassNames();
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString("fa-IR", {month: "short"}),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "flex gap-4 flex-col md:flex-row relative",
+ defaultClassNames.months
+ ),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn(
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({variant: buttonVariant}),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({variant: buttonVariant}),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn("absolute inset-0 opacity-0", defaultClassNames.dropdown),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn(
+ "select-none w-(--cell-size)",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] select-none text-muted-foreground",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "relative w-full h-full p-0 text-center [&:first-child[data-selected=true]_button]:rounded-l-md [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "rounded-l-md bg-accent",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-muted-foreground aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "text-muted-foreground opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({className, rootRef, ...props}) => {
+ return (
+
+ );
+ },
+ Chevron: ({className, orientation, ...props}) => {
+ if (orientation === "left") {
+ return (
+
+ );
+ }
+
+ if (orientation === "right") {
+ return (
+
+ );
+ }
+
+ return (
+
+ );
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({children, ...props}) => {
+ return (
+
+
+ {children}
+
+
+ );
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ );
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames();
+
+ const ref = React.useRef(null);
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus();
+ }, [modifiers.focused]);
+
+ return (
+ span]:text-xs [&>span]:opacity-70",
+ defaultClassNames.day,
+ className
+ )}
+ {...props}
+ />
+ );
+}
+
+export {CalendarHijriBase, CalendarDayButton};
diff --git a/src/components/ui/calendar.tsx b/src/components/ui/calendar.tsx
new file mode 100644
index 0000000..9fb18ca
--- /dev/null
+++ b/src/components/ui/calendar.tsx
@@ -0,0 +1,220 @@
+"use client"
+
+import * as React from "react"
+import {
+ ChevronDownIcon,
+ ChevronLeftIcon,
+ ChevronRightIcon,
+} from "lucide-react"
+import {
+ DayPicker,
+ getDefaultClassNames,
+ type DayButton,
+} from "react-day-picker"
+
+import { cn } from "@/lib/utils"
+import { Button, buttonVariants } from "@/components/ui/button"
+
+function Calendar({
+ className,
+ classNames,
+ showOutsideDays = true,
+ captionLayout = "label",
+ buttonVariant = "ghost",
+ formatters,
+ components,
+ ...props
+}: React.ComponentProps & {
+ buttonVariant?: React.ComponentProps["variant"]
+}) {
+ const defaultClassNames = getDefaultClassNames()
+
+ return (
+ svg]:rotate-180`,
+ String.raw`rtl:**:[.rdp-button\_previous>svg]:rotate-180`,
+ className
+ )}
+ captionLayout={captionLayout}
+ formatters={{
+ formatMonthDropdown: (date) =>
+ date.toLocaleString("default", { month: "short" }),
+ ...formatters,
+ }}
+ classNames={{
+ root: cn("w-fit", defaultClassNames.root),
+ months: cn(
+ "flex gap-4 flex-col md:flex-row relative",
+ defaultClassNames.months
+ ),
+ month: cn("flex flex-col w-full gap-4", defaultClassNames.month),
+ nav: cn(
+ "flex items-center gap-1 w-full absolute top-0 inset-x-0 justify-between",
+ defaultClassNames.nav
+ ),
+ button_previous: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_previous
+ ),
+ button_next: cn(
+ buttonVariants({ variant: buttonVariant }),
+ "size-(--cell-size) aria-disabled:opacity-50 p-0 select-none",
+ defaultClassNames.button_next
+ ),
+ month_caption: cn(
+ "flex items-center justify-center h-(--cell-size) w-full px-(--cell-size)",
+ defaultClassNames.month_caption
+ ),
+ dropdowns: cn(
+ "w-full flex items-center text-sm font-medium justify-center h-(--cell-size) gap-1.5",
+ defaultClassNames.dropdowns
+ ),
+ dropdown_root: cn(
+ "relative has-focus:border-ring border border-input shadow-xs has-focus:ring-ring/50 has-focus:ring-[3px] rounded-md",
+ defaultClassNames.dropdown_root
+ ),
+ dropdown: cn(
+ "absolute bg-popover inset-0 opacity-0",
+ defaultClassNames.dropdown
+ ),
+ caption_label: cn(
+ "select-none font-medium",
+ captionLayout === "label"
+ ? "text-sm"
+ : "rounded-md pl-2 pr-1 flex items-center gap-1 text-sm h-8 [&>svg]:text-muted-foreground [&>svg]:size-3.5",
+ defaultClassNames.caption_label
+ ),
+ table: "w-full border-collapse",
+ weekdays: cn("flex", defaultClassNames.weekdays),
+ weekday: cn(
+ "text-muted-foreground rounded-md flex-1 font-normal text-[0.8rem] select-none",
+ defaultClassNames.weekday
+ ),
+ week: cn("flex w-full mt-2", defaultClassNames.week),
+ week_number_header: cn(
+ "select-none w-(--cell-size)",
+ defaultClassNames.week_number_header
+ ),
+ week_number: cn(
+ "text-[0.8rem] select-none text-muted-foreground",
+ defaultClassNames.week_number
+ ),
+ day: cn(
+ "relative w-full h-full p-0 text-center [&:last-child[data-selected=true]_button]:rounded-r-md group/day aspect-square select-none",
+ props.showWeekNumber
+ ? "[&:nth-child(2)[data-selected=true]_button]:rounded-l-md"
+ : "[&:first-child[data-selected=true]_button]:rounded-l-md",
+ defaultClassNames.day
+ ),
+ range_start: cn(
+ "rounded-l-md bg-accent",
+ defaultClassNames.range_start
+ ),
+ range_middle: cn("rounded-none", defaultClassNames.range_middle),
+ range_end: cn("rounded-r-md bg-accent", defaultClassNames.range_end),
+ today: cn(
+ "bg-accent text-accent-foreground rounded-md data-[selected=true]:rounded-none",
+ defaultClassNames.today
+ ),
+ outside: cn(
+ "text-muted-foreground aria-selected:text-muted-foreground",
+ defaultClassNames.outside
+ ),
+ disabled: cn(
+ "text-muted-foreground opacity-50",
+ defaultClassNames.disabled
+ ),
+ hidden: cn("invisible", defaultClassNames.hidden),
+ ...classNames,
+ }}
+ components={{
+ Root: ({ className, rootRef, ...props }) => {
+ return (
+
+ )
+ },
+ Chevron: ({ className, orientation, ...props }) => {
+ if (orientation === "left") {
+ return (
+
+ )
+ }
+
+ if (orientation === "right") {
+ return (
+
+ )
+ }
+
+ return (
+
+ )
+ },
+ DayButton: CalendarDayButton,
+ WeekNumber: ({ children, ...props }) => {
+ return (
+
+
+ {children}
+
+
+ )
+ },
+ ...components,
+ }}
+ {...props}
+ />
+ )
+}
+
+function CalendarDayButton({
+ className,
+ day,
+ modifiers,
+ ...props
+}: React.ComponentProps) {
+ const defaultClassNames = getDefaultClassNames()
+
+ const ref = React.useRef(null)
+ React.useEffect(() => {
+ if (modifiers.focused) ref.current?.focus()
+ }, [modifiers.focused])
+
+ return (
+ span]:text-xs [&>span]:opacity-70",
+ defaultClassNames.day,
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+export { Calendar, CalendarDayButton }
diff --git a/src/components/ui/checkbox.tsx b/src/components/ui/checkbox.tsx
new file mode 100644
index 0000000..cb0b07b
--- /dev/null
+++ b/src/components/ui/checkbox.tsx
@@ -0,0 +1,32 @@
+"use client"
+
+import * as React from "react"
+import * as CheckboxPrimitive from "@radix-ui/react-checkbox"
+import { CheckIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Checkbox({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+ )
+}
+
+export { Checkbox }
diff --git a/src/components/ui/dialog.tsx b/src/components/ui/dialog.tsx
new file mode 100644
index 0000000..a6f1cfb
--- /dev/null
+++ b/src/components/ui/dialog.tsx
@@ -0,0 +1,143 @@
+"use client"
+
+import * as React from "react"
+import * as DialogPrimitive from "@radix-ui/react-dialog"
+import { XIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Dialog({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogPortal({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogClose({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DialogOverlay({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogContent({
+ className,
+ children,
+ showCloseButton = true,
+ ...props
+}: React.ComponentProps & {
+ showCloseButton?: boolean
+}) {
+ return (
+
+
+
+ {children}
+ {showCloseButton && (
+
+
+ Close
+
+ )}
+
+
+ )
+}
+
+function DialogHeader({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogFooter({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function DialogTitle({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DialogDescription({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogOverlay,
+ DialogPortal,
+ DialogTitle,
+ DialogTrigger,
+}
diff --git a/src/components/ui/dropdown-menu.tsx b/src/components/ui/dropdown-menu.tsx
new file mode 100644
index 0000000..bbe6fb0
--- /dev/null
+++ b/src/components/ui/dropdown-menu.tsx
@@ -0,0 +1,257 @@
+"use client"
+
+import * as React from "react"
+import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
+import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function DropdownMenu({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuPortal({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuTrigger({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuContent({
+ className,
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function DropdownMenuGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuItem({
+ className,
+ inset,
+ variant = "default",
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+ variant?: "default" | "destructive"
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuCheckboxItem({
+ className,
+ children,
+ checked,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuRadioGroup({
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuRadioItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function DropdownMenuLabel({
+ className,
+ inset,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ )
+}
+
+function DropdownMenuSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function DropdownMenuShortcut({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+ )
+}
+
+function DropdownMenuSub({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function DropdownMenuSubTrigger({
+ className,
+ inset,
+ children,
+ ...props
+}: React.ComponentProps & {
+ inset?: boolean
+}) {
+ return (
+
+ {children}
+
+
+ )
+}
+
+function DropdownMenuSubContent({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export {
+ DropdownMenu,
+ DropdownMenuPortal,
+ DropdownMenuTrigger,
+ DropdownMenuContent,
+ DropdownMenuGroup,
+ DropdownMenuLabel,
+ DropdownMenuItem,
+ DropdownMenuCheckboxItem,
+ DropdownMenuRadioGroup,
+ DropdownMenuRadioItem,
+ DropdownMenuSeparator,
+ DropdownMenuShortcut,
+ DropdownMenuSub,
+ DropdownMenuSubTrigger,
+ DropdownMenuSubContent,
+}
diff --git a/src/components/ui/field.tsx b/src/components/ui/field.tsx
new file mode 100644
index 0000000..235d00e
--- /dev/null
+++ b/src/components/ui/field.tsx
@@ -0,0 +1,248 @@
+"use client"
+
+import { useMemo } from "react"
+import { cva, type VariantProps } from "class-variance-authority"
+
+import { cn } from "@/lib/utils"
+import { Label } from "@/components/ui/label"
+import { Separator } from "@/components/ui/separator"
+
+function FieldSet({ className, ...props }: React.ComponentProps<"fieldset">) {
+ return (
+ [data-slot=checkbox-group]]:gap-3 has-[>[data-slot=radio-group]]:gap-3",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function FieldLegend({
+ className,
+ variant = "legend",
+ ...props
+}: React.ComponentProps<"legend"> & { variant?: "legend" | "label" }) {
+ return (
+
+ )
+}
+
+function FieldGroup({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+ [data-slot=field-group]]:gap-4",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+const fieldVariants = cva(
+ "group/field flex w-full gap-3 data-[invalid=true]:text-destructive",
+ {
+ variants: {
+ orientation: {
+ vertical: ["flex-col [&>*]:w-full [&>.sr-only]:w-auto"],
+ horizontal: [
+ "flex-row items-center",
+ "[&>[data-slot=field-label]]:flex-auto",
+ "has-[>[data-slot=field-content]]:items-start has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ ],
+ responsive: [
+ "flex-col [&>*]:w-full [&>.sr-only]:w-auto @md/field-group:flex-row @md/field-group:items-center @md/field-group:[&>*]:w-auto",
+ "@md/field-group:[&>[data-slot=field-label]]:flex-auto",
+ "@md/field-group:has-[>[data-slot=field-content]]:items-start @md/field-group:has-[>[data-slot=field-content]]:[&>[role=checkbox],[role=radio]]:mt-px",
+ ],
+ },
+ },
+ defaultVariants: {
+ orientation: "vertical",
+ },
+ }
+)
+
+function Field({
+ className,
+ orientation = "vertical",
+ ...props
+}: React.ComponentProps<"div"> & VariantProps
) {
+ return (
+
+ )
+}
+
+function FieldContent({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function FieldLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+ [data-slot=field]]:w-full has-[>[data-slot=field]]:flex-col has-[>[data-slot=field]]:rounded-md has-[>[data-slot=field]]:border [&>*]:data-[slot=field]:p-4",
+ "has-data-[state=checked]:bg-primary/5 has-data-[state=checked]:border-primary dark:has-data-[state=checked]:bg-primary/10",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function FieldTitle({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+function FieldDescription({ className, ...props }: React.ComponentProps<"p">) {
+ return (
+ a:hover]:text-primary [&>a]:underline [&>a]:underline-offset-4",
+ className
+ )}
+ {...props}
+ />
+ )
+}
+
+function FieldSeparator({
+ children,
+ className,
+ ...props
+}: React.ComponentProps<"div"> & {
+ children?: React.ReactNode
+}) {
+ return (
+
+
+ {children && (
+
+ {children}
+
+ )}
+
+ )
+}
+
+function FieldError({
+ className,
+ children,
+ errors,
+ ...props
+}: React.ComponentProps<"div"> & {
+ errors?: Array<{ message?: string } | undefined>
+}) {
+ const content = useMemo(() => {
+ if (children) {
+ return children
+ }
+
+ if (!errors?.length) {
+ return null
+ }
+
+ const uniqueErrors = [
+ ...new Map(errors.map((error) => [error?.message, error])).values(),
+ ]
+
+ if (uniqueErrors?.length == 1) {
+ return uniqueErrors[0]?.message
+ }
+
+ return (
+
+ {uniqueErrors.map(
+ (error, index) =>
+ error?.message && {error.message}
+ )}
+
+ )
+ }, [children, errors])
+
+ if (!content) {
+ return null
+ }
+
+ return (
+
+ {content}
+
+ )
+}
+
+export {
+ Field,
+ FieldLabel,
+ FieldDescription,
+ FieldError,
+ FieldGroup,
+ FieldLegend,
+ FieldSeparator,
+ FieldSet,
+ FieldContent,
+ FieldTitle,
+}
diff --git a/src/components/ui/input.tsx b/src/components/ui/input.tsx
new file mode 100644
index 0000000..8916905
--- /dev/null
+++ b/src/components/ui/input.tsx
@@ -0,0 +1,21 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Input({ className, type, ...props }: React.ComponentProps<"input">) {
+ return (
+
+ )
+}
+
+export { Input }
diff --git a/src/components/ui/label.tsx b/src/components/ui/label.tsx
new file mode 100644
index 0000000..fb5fbc3
--- /dev/null
+++ b/src/components/ui/label.tsx
@@ -0,0 +1,24 @@
+"use client"
+
+import * as React from "react"
+import * as LabelPrimitive from "@radix-ui/react-label"
+
+import { cn } from "@/lib/utils"
+
+function Label({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Label }
diff --git a/src/components/ui/pagination.tsx b/src/components/ui/pagination.tsx
new file mode 100644
index 0000000..1dcfb0c
--- /dev/null
+++ b/src/components/ui/pagination.tsx
@@ -0,0 +1,127 @@
+import * as React from "react"
+import {
+ ChevronLeftIcon,
+ ChevronRightIcon,
+ MoreHorizontalIcon,
+} from "lucide-react"
+
+import { cn } from "@/lib/utils"
+import { buttonVariants, type Button } from "@/components/ui/button"
+
+function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
+ return (
+
+ )
+}
+
+function PaginationContent({
+ className,
+ ...props
+}: React.ComponentProps<"ul">) {
+ return (
+
+ )
+}
+
+function PaginationItem({ ...props }: React.ComponentProps<"li">) {
+ return
+}
+
+type PaginationLinkProps = {
+ isActive?: boolean
+} & Pick, "size"> &
+ React.ComponentProps<"a">
+
+function PaginationLink({
+ className,
+ isActive,
+ size = "icon",
+ ...props
+}: PaginationLinkProps) {
+ return (
+
+ )
+}
+
+function PaginationPrevious({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+ Previous
+
+ )
+}
+
+function PaginationNext({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ Next
+
+
+ )
+}
+
+function PaginationEllipsis({
+ className,
+ ...props
+}: React.ComponentProps<"span">) {
+ return (
+
+
+ More pages
+
+ )
+}
+
+export {
+ Pagination,
+ PaginationContent,
+ PaginationLink,
+ PaginationItem,
+ PaginationPrevious,
+ PaginationNext,
+ PaginationEllipsis,
+}
diff --git a/src/components/ui/popover.tsx b/src/components/ui/popover.tsx
new file mode 100644
index 0000000..01e468b
--- /dev/null
+++ b/src/components/ui/popover.tsx
@@ -0,0 +1,48 @@
+"use client"
+
+import * as React from "react"
+import * as PopoverPrimitive from "@radix-ui/react-popover"
+
+import { cn } from "@/lib/utils"
+
+function Popover({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverTrigger({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function PopoverContent({
+ className,
+ align = "center",
+ sideOffset = 4,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function PopoverAnchor({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+export { Popover, PopoverTrigger, PopoverContent, PopoverAnchor }
diff --git a/src/components/ui/progress.tsx b/src/components/ui/progress.tsx
new file mode 100644
index 0000000..e7a416c
--- /dev/null
+++ b/src/components/ui/progress.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as ProgressPrimitive from "@radix-ui/react-progress"
+
+import { cn } from "@/lib/utils"
+
+function Progress({
+ className,
+ value,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { Progress }
diff --git a/src/components/ui/select.tsx b/src/components/ui/select.tsx
new file mode 100644
index 0000000..88302a8
--- /dev/null
+++ b/src/components/ui/select.tsx
@@ -0,0 +1,190 @@
+"use client"
+
+import * as React from "react"
+import * as SelectPrimitive from "@radix-ui/react-select"
+import { CheckIcon, ChevronDownIcon, ChevronUpIcon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Select({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectGroup({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectValue({
+ ...props
+}: React.ComponentProps) {
+ return
+}
+
+function SelectTrigger({
+ className,
+ size = "default",
+ children,
+ ...props
+}: React.ComponentProps & {
+ size?: "sm" | "default"
+}) {
+ return (
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectContent({
+ className,
+ children,
+ position = "item-aligned",
+ align = "center",
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+ {children}
+
+
+
+
+ )
+}
+
+function SelectLabel({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectItem({
+ className,
+ children,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+
+
+
+ {children}
+
+ )
+}
+
+function SelectSeparator({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+function SelectScrollUpButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+function SelectScrollDownButton({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export {
+ Select,
+ SelectContent,
+ SelectGroup,
+ SelectItem,
+ SelectLabel,
+ SelectScrollDownButton,
+ SelectScrollUpButton,
+ SelectSeparator,
+ SelectTrigger,
+ SelectValue,
+}
diff --git a/src/components/ui/separator.tsx b/src/components/ui/separator.tsx
new file mode 100644
index 0000000..275381c
--- /dev/null
+++ b/src/components/ui/separator.tsx
@@ -0,0 +1,28 @@
+"use client"
+
+import * as React from "react"
+import * as SeparatorPrimitive from "@radix-ui/react-separator"
+
+import { cn } from "@/lib/utils"
+
+function Separator({
+ className,
+ orientation = "horizontal",
+ decorative = true,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+ )
+}
+
+export { Separator }
diff --git a/src/components/ui/skeleton.tsx b/src/components/ui/skeleton.tsx
new file mode 100644
index 0000000..32ea0ef
--- /dev/null
+++ b/src/components/ui/skeleton.tsx
@@ -0,0 +1,13 @@
+import { cn } from "@/lib/utils"
+
+function Skeleton({ className, ...props }: React.ComponentProps<"div">) {
+ return (
+
+ )
+}
+
+export { Skeleton }
diff --git a/src/components/ui/spinner.tsx b/src/components/ui/spinner.tsx
new file mode 100644
index 0000000..a70e713
--- /dev/null
+++ b/src/components/ui/spinner.tsx
@@ -0,0 +1,16 @@
+import { Loader2Icon } from "lucide-react"
+
+import { cn } from "@/lib/utils"
+
+function Spinner({ className, ...props }: React.ComponentProps<"svg">) {
+ return (
+
+ )
+}
+
+export { Spinner }
diff --git a/src/components/ui/switch.tsx b/src/components/ui/switch.tsx
new file mode 100644
index 0000000..6a2b524
--- /dev/null
+++ b/src/components/ui/switch.tsx
@@ -0,0 +1,31 @@
+"use client"
+
+import * as React from "react"
+import * as SwitchPrimitive from "@radix-ui/react-switch"
+
+import { cn } from "@/lib/utils"
+
+function Switch({
+ className,
+ ...props
+}: React.ComponentProps) {
+ return (
+
+
+
+ )
+}
+
+export { Switch }
diff --git a/src/components/ui/textarea.tsx b/src/components/ui/textarea.tsx
new file mode 100644
index 0000000..7f21b5e
--- /dev/null
+++ b/src/components/ui/textarea.tsx
@@ -0,0 +1,18 @@
+import * as React from "react"
+
+import { cn } from "@/lib/utils"
+
+function Textarea({ className, ...props }: React.ComponentProps<"textarea">) {
+ return (
+
+ )
+}
+
+export { Textarea }
diff --git a/src/components/uploadDropzone.tsx b/src/components/uploadDropzone.tsx
new file mode 100644
index 0000000..bfccea8
--- /dev/null
+++ b/src/components/uploadDropzone.tsx
@@ -0,0 +1,259 @@
+"use client";
+
+import React, { SetStateAction, useCallback, useState } from "react";
+import { useDropzone, FileRejection } from "react-dropzone";
+import { Button } from "@/components/ui/button";
+import { Progress } from "@/components/ui/progress";
+import { Upload, X, Loader2, FileText } from "lucide-react";
+import privateApi from "@/service/http/privateCall.axios";
+import { useQueryClient } from "@tanstack/react-query";
+import { API_URL } from "@/constants";
+import { DocumentType } from "@/types";
+import { MeResponse } from "@/hooks";
+
+const ACCEPTED_TYPES = {
+ "application/pdf": [".pdf"],
+ "image/jpeg": [".jpg", ".jpeg"],
+ "image/webp": [".webp"],
+ "image/png": [".png"],
+};
+
+type UploadItem = {
+ file: File;
+ progress: number;
+ status: "idle" | "uploading" | "done" | "error";
+ xhr?: XMLHttpRequest;
+};
+
+type BaseProps = {
+ fileType: "image" | "document";
+ documentType: DocumentType;
+ maxSize?: number;
+ autoUpload?: boolean;
+};
+
+type SingleProps = {
+ multiple?: false;
+ output?: string;
+ setOutput?: React.Dispatch>;
+};
+
+type MultipleProps = {
+ multiple: true;
+ output?: string[];
+ setOutput?: React.Dispatch>;
+};
+
+type Props = BaseProps & (SingleProps | MultipleProps);
+
+export default function UploadDropzone({
+ fileType,
+ multiple,
+ documentType,
+ maxSize = 5 * 1024 * 1024,
+ autoUpload = false,
+ output,
+ setOutput,
+}: Props) {
+ const qc = useQueryClient();
+ const user: MeResponse = qc.getQueryData(["me"])!;
+
+ const [items, setItems] = useState([]);
+ const [error, setError] = useState(null);
+
+ const getPreviewUrl = (file: File) =>
+ file.type.startsWith("image/") ? URL.createObjectURL(file) : null;
+
+ const onDrop = useCallback(
+ (accepted: File[], rejected: FileRejection[]) => {
+ if (!multiple && accepted.length > 1) {
+ setError("فقط یک فایل مجاز است");
+ return;
+ }
+
+ if (rejected.length) {
+ const reason = rejected[0].errors[0];
+ setError(
+ reason.code === "file-too-large"
+ ? "حجم فایل بیشتر از حد مجاز است"
+ : "فرمت فایل مجاز نیست",
+ );
+ return;
+ }
+
+ const newItems: UploadItem[] = accepted.map((file) => ({
+ file,
+ progress: 0,
+ status: "idle",
+ }));
+
+ setItems((prev) => (multiple ? [...prev, ...newItems] : newItems));
+ setError(null);
+
+ if (autoUpload) {
+ newItems.forEach((item) => startUpload(item.file));
+ }
+ },
+ [autoUpload],
+ );
+
+ const { getRootProps, getInputProps, isDragActive } = useDropzone({
+ onDrop,
+ multiple: multiple ?? false,
+ maxSize,
+ accept: ACCEPTED_TYPES,
+ });
+
+ const startUpload = async (file: File) => {
+
+ try {
+ console.log('running')
+ setItems((prev) =>
+ prev.map((i) => (i.file === file ? { ...i, status: "uploading" } : i)),
+ );
+
+ const tokenRes = await privateApi.post("/upload/request", {
+ userId: user.data.id,
+ type: fileType,
+ });
+
+ console.log(tokenRes);
+ const uploadToken = tokenRes.data?.data?.uploadToken;
+ if (!uploadToken) throw new Error();
+
+ const xhr = new XMLHttpRequest();
+
+ setItems((prev) =>
+ prev.map((i) => (i.file === file ? { ...i, xhr } : i)),
+ );
+
+ const formData = new FormData();
+ formData.append("file", file);
+ formData.append("type", documentType.toString());
+
+ xhr.upload.onprogress = (e) => {
+ if (e.lengthComputable) {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file
+ ? {
+ ...i,
+ progress: Math.round((e.loaded / e.total) * 100),
+ }
+ : i,
+ ),
+ );
+ }
+ };
+
+ xhr.onload = () => {
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file ? { ...i, status: "done", progress: 100 } : i,
+ ),
+ );
+ const response = JSON.parse(xhr.responseText);
+ const newId = response.data.document.id;
+
+ if (multiple) {
+ setOutput?.((prev: string[]) => [...(prev || []), newId]);
+ } else {
+ setOutput?.(newId);
+ }
+ };
+
+ xhr.onerror = () => {
+ setItems((prev) =>
+ prev.map((i) => (i.file === file ? { ...i, status: "error" } : i)),
+ );
+ };
+
+ xhr.open("POST", `${API_URL}/upload/upload`);
+ xhr.setRequestHeader("x-upload-token", uploadToken);
+ xhr.send(formData);
+ } catch {
+ setItems((prev) =>
+ prev.map((i) => (i.file === file ? { ...i, status: "error" } : i)),
+ );
+ }
+ };
+
+ const cancelUpload = (file: File) => {
+ const item = items.find((i) => i.file === file);
+ item?.xhr?.abort();
+
+ setItems((prev) =>
+ prev.map((i) =>
+ i.file === file ? { ...i, status: "idle", progress: 0 } : i,
+ ),
+ );
+ };
+
+ return (
+
+
+
+
+
+ فایلها را بکشید یا کلیک کنید
+
+
+
+ {items.map(({ file, progress, status }) => {
+ const preview = getPreviewUrl(file);
+
+ return (
+
+
+
+ {preview ? (
+
+ ) : (
+
+ )}
+
+
+
{file.name}
+
+ {status === "idle" && (
+
+ setItems((prev) => prev.filter((i) => i.file !== file))
+ }
+ />
+ )}
+
+
+
+
+ {status === "idle" && !autoUpload && (
+
startUpload(file)}>
+ شروع آپلود
+
+ )}
+
+ {status === "uploading" && (
+
cancelUpload(file)}
+ >
+
+ کنسل
+
+ )}
+
+ {status === "error" && (
+
آپلود ناموفق بود
+ )}
+
+ );
+ })}
+
+ );
+}
diff --git a/src/components/usersTableExport.tsx b/src/components/usersTableExport.tsx
new file mode 100644
index 0000000..c00a53f
--- /dev/null
+++ b/src/components/usersTableExport.tsx
@@ -0,0 +1,157 @@
+"use client";
+import React, {useEffect, useState} from "react";
+import {Button} from "./ui/button";
+import {toast} from "react-toastify";
+import privateApi from "@/service/http/privateCall.axios";
+import {
+ Dialog,
+ DialogClose,
+ DialogContent,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+ DialogTrigger,
+} from "./ui/dialog";
+import {Label} from "./ui/label";
+import {Input} from "./ui/input";
+import {API_URL, UsersType} from "@/constants";
+import {Language} from "@/types";
+import Loader from "./Loader";
+import {
+ Select,
+ SelectContent,
+ SelectItem,
+ SelectTrigger,
+ SelectValue,
+} from "./ui/select";
+async function fetchLanguages() {
+ console.log('users table exports')
+
+ const res = await fetch(`${API_URL}/language/get/all`,{cache:"no-cache"});
+
+ if (!res.ok) {
+ throw new Error("Failed to get data");
+ }
+
+ const data = await res.json();
+
+ return data;
+}
+export default function UsersTableExport({table}: {table: UsersType}) {
+ const [loading, setLoading] = useState(false);
+ const [data, setData] = useState([]);
+ const [limit, setLimit] = useState(null);
+ const [lang, setLang] = useState("fa");
+
+ useEffect(() => {
+ let active = true;
+
+ setLoading(true);
+ fetchLanguages()
+ .then((res) => {
+ if (!active) return;
+ setData(res.data);
+ })
+ .catch(console.error)
+ .finally(() => active && setLoading(false));
+
+ return () => {
+ active = false;
+ };
+ }, []);
+
+ if (loading) {
+ return ;
+ }
+ const exportUsers = async () => {
+ try {
+ await privateApi.get(
+ `/user/export/${table}${lang && `?lang=${lang}`}${
+ limit ? `&limit=${limit}` : ""
+ }`
+ );
+
+ toast.success("دانلود آماده شد!");
+ } catch (error) {
+ toast.error("خطا در خروجی گرفتن اکسل");
+ }
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ خروجی اکسل
+
+
+
+
+ تنظیمات خروجی
+ {/*
+ Make changes to your profile here. Click save when you're
+ done.
+ */}
+
+
+
+ تعداد کاربران برای خروجی
+ setLimit(+e.target.value)}
+ id="name-1"
+ name="name"
+ />
+
+
+ انتخاب زبان
+ setLang(value)}
+ >
+
+
+
+
+
+ {data.map((exp) => {
+ return (
+
+ {exp.title}
+
+ );
+ })}
+
+
+
+
+
+
+ انصراف
+
+
+ ذخیره تغییرات
+
+
+
+
+
+ );
+}
diff --git a/src/config/font.config.ts b/src/config/font.config.ts
new file mode 100644
index 0000000..0a74347
--- /dev/null
+++ b/src/config/font.config.ts
@@ -0,0 +1,48 @@
+import localFont from "next/font/local";
+
+export const FontVazir = localFont({
+ src: [
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-Thin.woff2",
+ weight: "100",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-ExtraLight.woff2",
+ weight: "200",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-Light.woff2",
+ weight: "300",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-Regular.woff2",
+ weight: "400",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-Medium.woff2",
+ weight: "500",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-SemiBold.woff2",
+ weight: "600",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-Bold.woff2",
+ weight: "700",
+ style: "normal",
+ },
+ {
+ path: "../../public/fonts/vazir/Vazirmatn-ExtraBold.woff2",
+ weight: "800",
+ style: "normal",
+ },
+ ],
+ variable: "--font-vazir", // اگر خواستی متغیر CSS بسازی برای Tailwind یا CSS مدول
+ display: "swap", // بهترین گزینه برای نمایش فونت
+});
diff --git a/src/constants/index.ts b/src/constants/index.ts
new file mode 100644
index 0000000..6e50b93
--- /dev/null
+++ b/src/constants/index.ts
@@ -0,0 +1,450 @@
+export const PERSIAN_MONTHS = [
+ "فروردین",
+ "اردیبهشت",
+ "خرداد",
+ "تیر",
+ "مرداد",
+ "شهریور",
+ "مهر",
+ "آبان",
+ "آذر",
+ "دی",
+ "بهمن",
+ "اسفند",
+];
+
+export const API_URL = "http://localhost:3500/api/v1";
+export const API_CDN_URL = "http://localhost:4000";
+export const STAFF_ROLE = ["developer", "admin", "coordinator", "doctor"];
+export type STAFF_ROLE_TYPE = "ADMIN" | "DOCTOR" | "DEVELOPER" | "COORDINATOR";
+export const ROLES = Object.freeze({
+ developer: "DEVELOPER",
+ admin: "ADMIN",
+ coordinator: "COORDINATOR",
+ doctor: "DOCTOR",
+});
+export interface SIDE_MENU_TYPE {
+ id: number;
+ label: string;
+ href: string;
+ sub: {
+ items: {
+ id: number;
+ label: string;
+ hasLine: boolean;
+ href: string;
+ show?: boolean;
+ roles?: STAFF_ROLE_TYPE[];
+ }[];
+ } | null;
+ icon: string;
+ roles?: STAFF_ROLE_TYPE[];
+}
+
+export const SIDE_MENUS: SIDE_MENU_TYPE[] = [
+ {
+ id: 1,
+ label: "پیشخوان",
+ href: "/dashboard",
+ sub: null,
+ icon: "LayoutDashboard",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 2,
+ label: "پزشکان",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست پزشکان",
+ hasLine: true,
+ href: "/doctors",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 2,
+ label: "ایجاد پزشک جدید",
+ hasLine: false,
+ href: "/doctors/new",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: "",
+ hasLine: false,
+ href: "/doctors/edit/:id",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+
+ icon: "Stethoscope",
+ },
+ {
+ id: 3,
+ label: "دپارتمان",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست اعضا",
+ hasLine: true,
+ href: "/department/members",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 2,
+ label: "ایجاد عضو جدید",
+ hasLine: false,
+ href: "/department/members/new",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: "",
+ hasLine: false,
+ href: "/department/members/edit/:id",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+
+ icon: "Building",
+ },
+ {
+ id: 4,
+ label: "گردشگری",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست تیم ها",
+ hasLine: false,
+ href: "/transfer-team/teams",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 2,
+ label: "فهرست افراد",
+ hasLine: true,
+ href: "/transfer-team/members",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: "ایجاد تیم جدید",
+ hasLine: false,
+ href: "/transfer-team/teams/new",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 4,
+ label: "ایجاد فرد جدید",
+ hasLine: false,
+ href: "/transfer-team/members/new",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: "",
+ show: false,
+ hasLine: false,
+ href: "/transfer-team/members/edit/:id",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER"],
+
+ icon: "Luggage",
+ },
+ {
+ id: 5,
+ label: "بیماران",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست بیماران",
+ hasLine: true,
+ href: "/patients",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR", "DOCTOR"],
+ },
+ {
+ id: 2,
+ label: "ایجاد بیمار جدید",
+ hasLine: false,
+ href: "/patients/new",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 3,
+ label: "",
+ hasLine: false,
+ show: false,
+ href: "/patients/edit/:id",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ // {
+ // id: 4,
+ // label: "بازگردانی بیمار",
+ // hasLine: false,
+ // href: "/patients/restore",
+ // roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ // },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR", "DOCTOR"],
+
+ icon: "Users",
+ },
+ {
+ id: 6,
+ label: "پذیرش آنلاین",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست پذیرش ها",
+ hasLine: true,
+ href: "/online-case",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR", "DOCTOR"],
+ },
+ {
+ id: 2,
+ label: "ایجاد پذیرش جدید",
+ hasLine: false,
+ href: "/online-case/new",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR", "DOCTOR"],
+ },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR", "DOCTOR"],
+
+ icon: "FileUser",
+ },
+ {
+ id: 7,
+ label: "خدمات ما",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست خدمات پزشکی",
+ hasLine: false,
+ href: "/medical-packages",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 2,
+ label: "فهرست خدمات گردشگری",
+ hasLine: true,
+ href: "/transfer-packages",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: " خدمت پزشکی جدید",
+ hasLine: false,
+ href: "/medical-packages/new",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 4,
+ label: " خدمت گردشگری جدید",
+ hasLine: false,
+ href: "/transfer-packages/new",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER"],
+
+ icon: "Package",
+ },
+ {
+ id: 8,
+ label: " تخصص ها",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست تخصص ها",
+ hasLine: true,
+ href: "/expertise",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 2,
+ label: "ایجاد تخصص جدید",
+ hasLine: false,
+ href: "/expertise/new",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ ],
+ },
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+
+ icon: "Brain",
+ },
+ {
+ id: 9,
+ label: "تعاریف پایه",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "ویرایش تعاریف",
+ hasLine: false,
+ href: "/default",
+ roles: ["ADMIN", "DEVELOPER", "COORDINATOR"],
+ },
+ {
+ id: 2,
+ label: "ویرایش کانفیگ",
+ hasLine: false,
+ href: "/configs",
+ roles: ["ADMIN", "DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: "ویرایش Terms of Service",
+ hasLine: false,
+ href: "/tos",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 4,
+ label: "ویرایش Privacy Policy",
+ hasLine: false,
+ href: "/privacy-policy",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 5,
+ label: "ویرایش زبان ها",
+ hasLine: false,
+ href: "/languages",
+ roles: ["DEVELOPER"],
+ },
+ ],
+ },
+ roles: ["DEVELOPER", "ADMIN", "COORDINATOR"],
+
+ icon: "EthernetPort",
+ },
+ {
+ id: 10,
+ label: "صفحات",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "فهرست صفحات",
+ hasLine: true,
+ href: "/pages",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 2,
+ label: "ایجاد صفحه جدید",
+ hasLine: false,
+ href: "/pages/new",
+ roles: ["DEVELOPER"],
+ },
+ ],
+ },
+ icon: "Layers",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 11,
+ label: "لاگ ها",
+ href: "#",
+ sub: {
+ items: [
+ {
+ id: 1,
+ label: "لاگ دسترسی ها",
+ hasLine: false,
+ href: "/logs/access",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 2,
+ label: "لاگ بازرسی",
+ hasLine: false,
+ href: "/logs/audit",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 3,
+ label: "لاگ خط مشی",
+ hasLine: false,
+ href: "/logs/policy",
+ roles: ["DEVELOPER"],
+ },
+
+ {
+ id: 5,
+ label: "لاگ درخواست ها",
+ hasLine: false,
+ href: "/logs/requests",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 6,
+ label: "لاگ خطا ها",
+ hasLine: false,
+ href: "/logs/errors",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 7,
+ label: "لاگ پرفورمنس",
+ hasLine: false,
+ href: "/logs/performance",
+ roles: ["DEVELOPER"],
+ },
+ {
+ id: 8,
+ label: "لاگ سرویس آپلود",
+ hasLine: false,
+ href: "/logs/upload-server",
+ roles: ["DEVELOPER"],
+ },
+ ],
+ },
+ icon: "Logs",
+ roles: ["DEVELOPER"],
+ },
+];
+
+export const StaffRoles = {
+ developer: "DEVELOPER",
+ admin: "ADMIN",
+ doctor: "DOCTOR",
+ coordinator: "COORDINATOR",
+} as const;
+
+export type StaffRoles = (typeof StaffRoles)[keyof typeof StaffRoles];
+
+export const UsersType = {
+ DOCTOR: "DOCTOR",
+ TRANSFER_TEAM: "TRANSFER_TEAM",
+ DEPARTMENT: "DEPARTMENT",
+} as const;
+
+export type UsersType = (typeof UsersType)[keyof typeof UsersType];
diff --git a/src/hooks/configs.ts b/src/hooks/configs.ts
new file mode 100644
index 0000000..25283f5
--- /dev/null
+++ b/src/hooks/configs.ts
@@ -0,0 +1,17 @@
+import { ConfigFormItem } from "@/components/forms/configs/ConfigsForm";
+import { getAllConfigs, updateAllConfigs } from "@/service/apis/configs";
+import { ServerResponse } from "@/types";
+import { useMutation, useQuery } from "@tanstack/react-query";
+
+export const useGetAllConfigs = () =>
+ useQuery({
+ queryKey: ["get-all-configs"],
+ queryFn: () => getAllConfigs(),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useUpdateConfigs = () =>
+ useMutation({
+ mutationFn: updateAllConfigs,
+ });
diff --git a/src/hooks/country.ts b/src/hooks/country.ts
new file mode 100644
index 0000000..fdd33fd
--- /dev/null
+++ b/src/hooks/country.ts
@@ -0,0 +1,10 @@
+import { getAllCountries } from "@/service/apis/country";
+import {useQuery} from "@tanstack/react-query";
+
+export const useGetAllCountries = () =>
+ useQuery({
+ queryKey: ["get-all-countries"],
+ queryFn: ()=> getAllCountries(),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
diff --git a/src/hooks/defaults.ts b/src/hooks/defaults.ts
new file mode 100644
index 0000000..4388cea
--- /dev/null
+++ b/src/hooks/defaults.ts
@@ -0,0 +1,30 @@
+import {
+ SiteDefaultDataType,
+ UpdateDefaultFormValues,
+} from "@/components/forms/default/UpdateDefaultsForm";
+import { getAllDefaults, getUpdateDefaults } from "@/service/apis/default";
+import { ServerResponse } from "@/types";
+import {
+ useMutation,
+ UseMutationResult,
+ useQuery,
+} from "@tanstack/react-query";
+
+export const useGetAllDefaults = () =>
+ useQuery({
+ queryKey: ["get-all-defaults"],
+ queryFn: () => getAllDefaults(),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useUpdateDefaults = () => {
+ return useMutation<
+ ServerResponse,
+ Error,
+ { data: UpdateDefaultFormValues },
+ ServerResponse
+ >({
+ mutationFn: (payload) => getUpdateDefaults(payload),
+ });
+};
diff --git a/src/hooks/expertise.ts b/src/hooks/expertise.ts
new file mode 100644
index 0000000..7d514a1
--- /dev/null
+++ b/src/hooks/expertise.ts
@@ -0,0 +1,41 @@
+import {
+ createExpertise,
+ deleteExpertise,
+ getAllExpertise,
+ getExpertiseList,
+ getSingleExpertise,
+ updateExpertise,
+} from "@/service/apis/expertise";
+import {useMutation, useQuery} from "@tanstack/react-query";
+
+export const useCreateExpertise = () =>
+ useMutation({mutationFn: createExpertise});
+
+export const useUpdateExpertise = () =>
+ useMutation({mutationFn: updateExpertise});
+
+export const useDeleteExpertise = () =>
+ useMutation({mutationFn: deleteExpertise});
+
+export const useGetAllExpertise = (lang: string) =>
+ useQuery({
+ queryKey: ["get-all-expertise", lang],
+ queryFn: () => getAllExpertise(lang),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useGetSingleExpertise = (id: string) =>
+ useQuery({
+ queryKey: ["get-single-expertise", id],
+ queryFn: () => getSingleExpertise(id),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useGetAllExpertiseList = () => useQuery({
+ queryKey:['get-list-expertise'],
+ queryFn:getExpertiseList,
+ retry:false,
+ refetchOnWindowFocus:false
+})
\ No newline at end of file
diff --git a/src/hooks/index.ts b/src/hooks/index.ts
new file mode 100644
index 0000000..b9452ed
--- /dev/null
+++ b/src/hooks/index.ts
@@ -0,0 +1,43 @@
+import {LoginFormValues} from "@/components/forms/login/LoginForm";
+import { deleteFile } from "@/service/apis";
+import {userLogin} from "@/service/apis/users";
+import privateApi from "@/service/http/privateCall.axios";
+import {ServerResponse} from "@/types";
+import {useMutation, useQuery} from "@tanstack/react-query";
+export interface Translation {
+ displayName: string;
+}
+export interface MeData {
+ id: string;
+ email: string;
+ username: string;
+ role: "admin" | "developer" | "coordinator" | "doctor";
+ send_notif_with_email: boolean;
+ translations: Translation[];
+}
+export interface MeResponse {
+ status: number;
+ message: string;
+ data: MeData;
+}
+
+export const useMe = () =>
+ useQuery({
+ queryKey: ["me"],
+ queryFn: async () => {
+ const {data} = await privateApi.get("/auth/get/me");
+ return data;
+ },
+ staleTime: 1000 * 60 * 5,
+ refetchOnMount: false,
+ refetchOnWindowFocus: false,
+ retry: false,
+ });
+
+export const useUserLogin = () =>
+ useMutation({
+ mutationFn: userLogin,
+ });
+
+
+export const useDeleteFile = () => useMutation({mutationFn:deleteFile})
\ No newline at end of file
diff --git a/src/hooks/languages.ts b/src/hooks/languages.ts
new file mode 100644
index 0000000..9c0d020
--- /dev/null
+++ b/src/hooks/languages.ts
@@ -0,0 +1,29 @@
+import { UpdateLanguageFormValues } from "@/components/forms/languages/update/UpdateLanguageForm";
+import {
+ createLanguage,
+ deleteLanguage,
+ getAllLanguages,
+ updateLanguage,
+} from "@/service/apis/languages";
+import { ServerResponse } from "@/types";
+import { useMutation, useQuery } from "@tanstack/react-query";
+
+export const useGetLanguages = () =>
+ useQuery({
+ queryKey: ["get-all-languages"],
+ queryFn: () => getAllLanguages(),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useCreateLanguage = () =>
+ useMutation({ mutationFn: createLanguage });
+export const useUpdateLanguage = () =>
+ useMutation<
+ ServerResponse,
+ Error,
+ { data: UpdateLanguageFormValues; id: string },
+ ServerResponse
+ >({ mutationFn: updateLanguage });
+export const useDeleteLanguage = () =>
+ useMutation({ mutationFn: deleteLanguage });
diff --git a/src/hooks/logs.ts b/src/hooks/logs.ts
new file mode 100644
index 0000000..4cb1817
--- /dev/null
+++ b/src/hooks/logs.ts
@@ -0,0 +1,10 @@
+import { getAllUploadServerLogs } from "@/service/apis/logs";
+import { useQuery } from "@tanstack/react-query";
+
+
+export const useGetUploadServerLogs = () => useQuery({
+ queryKey:['get-all-uploadserver-logs'],
+ queryFn:getAllUploadServerLogs,
+ retry:false,
+ refetchOnWindowFocus:false
+})
\ No newline at end of file
diff --git a/src/hooks/medical-package.ts b/src/hooks/medical-package.ts
new file mode 100644
index 0000000..a4fcb48
--- /dev/null
+++ b/src/hooks/medical-package.ts
@@ -0,0 +1,66 @@
+import {UpdateMedicalPackageFormValues} from "@/components/forms/medical-packages/edit/UpdateMedicalPackageForm";
+import {CreateMedicalPackageValues} from "@/components/forms/medical-packages/new/CreateMedicalPackageForm";
+import {
+ createMedicalPackage,
+ deleteMedicalPackage,
+ getAllMedicalPackages,
+ getAllMedicalPackagesParent,
+ getSingleMedicalPackage,
+ updateMedicalPackage,
+} from "@/service/apis/medical-package";
+import {ServerResponse} from "@/types";
+import {useMutation, UseMutationResult, useQuery} from "@tanstack/react-query";
+
+export const useGetAllMedicalPackages = (lang: string) =>
+ useQuery({
+ queryKey: ["get-all-medical-packages", lang],
+ queryFn: () => getAllMedicalPackages(lang),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useGetAllMedicalPackagesParent = (lang: string) =>
+ useQuery({
+ queryKey: ["get-all-medical-packages-parent", lang],
+ queryFn: () => getAllMedicalPackagesParent(lang),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+export const useGetSingleMedicalPackage = ({id,lang}:{id:string,lang:string}) =>
+ useQuery({
+ queryKey: ["get-single-medical-packages", id,lang],
+ queryFn: () => getSingleMedicalPackage(id,lang),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useCreateMedicalPackage = (): UseMutationResult<
+ ServerResponse, // ✅ data returned on success
+ Error, // error type
+ CreateMedicalPackageValues, // variables type,
+ ServerResponse
+> => {
+ return useMutation<
+ ServerResponse,
+ Error,
+ CreateMedicalPackageValues,
+ ServerResponse
+ >({
+ mutationFn: (payload) => createMedicalPackage(payload),
+ });
+};
+export const useDeleteMedicalPackage = () =>
+ useMutation({mutationFn: deleteMedicalPackage});
+export const useUpdateMedicalPackage = (): UseMutationResult<
+ ServerResponse, // ✅ data returned on success
+ Error, // error type
+ {id: string; data: UpdateMedicalPackageFormValues}, // variables type,
+ ServerResponse
+> => {
+ return useMutation<
+ ServerResponse,
+ Error,
+ {id: string; data: UpdateMedicalPackageFormValues},
+ ServerResponse
+ >({mutationFn: (payload) => updateMedicalPackage(payload.id, payload.data)});
+};
diff --git a/src/hooks/online-case.ts b/src/hooks/online-case.ts
new file mode 100644
index 0000000..24939e9
--- /dev/null
+++ b/src/hooks/online-case.ts
@@ -0,0 +1,28 @@
+import {
+ createOnlineCase,
+ getAllOnlineCases,
+ getSingleOnlineCase,
+ updateOnlineCase,
+} from "@/service/apis/online-case";
+import {useMutation, useQuery} from "@tanstack/react-query";
+
+export const useGetAllOnlineCases = () =>
+ useQuery({
+ queryKey: ["get-all-online-cases"],
+ queryFn: getAllOnlineCases,
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useCreateOnlineCase = () =>
+ useMutation({mutationFn: createOnlineCase});
+export const useUpdateOnlineCase = () =>
+ useMutation({mutationFn: updateOnlineCase});
+
+export const useGetSingleOnlineCase = (id: string) =>
+ useQuery({
+ queryKey: ["get-single-case", id],
+ queryFn: () => getSingleOnlineCase(id),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
diff --git a/src/hooks/patients.ts b/src/hooks/patients.ts
new file mode 100644
index 0000000..d47eed5
--- /dev/null
+++ b/src/hooks/patients.ts
@@ -0,0 +1,11 @@
+import {getSinglePatient,updatePatient} from "@/service/apis/patients";
+import {useMutation, useQuery} from "@tanstack/react-query";
+
+export const useGetSinglePatient = (id: string) =>
+ useQuery({
+ queryKey: ["get-single-patient", id],
+ queryFn: () => getSinglePatient(id),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+export const useUpdatePatient = () => useMutation({mutationFn: updatePatient});
diff --git a/src/hooks/privacy-policy.ts b/src/hooks/privacy-policy.ts
new file mode 100644
index 0000000..a50afe0
--- /dev/null
+++ b/src/hooks/privacy-policy.ts
@@ -0,0 +1,14 @@
+import {getPP, updatePrivacyPolicy} from "@/service/apis/privacy-policy";
+import {useMutation, useQuery} from "@tanstack/react-query";
+
+export const useUpdatePrivacyPolicy = () =>
+ useMutation({
+ mutationFn: updatePrivacyPolicy,
+ });
+
+export const useGetPrivacyPolicy = () => useQuery({
+ queryKey:['get-pp'],
+ queryFn:getPP,
+ retry:false,
+ refetchOnWindowFocus:false
+})
\ No newline at end of file
diff --git a/src/hooks/tos.ts b/src/hooks/tos.ts
new file mode 100644
index 0000000..ffd936b
--- /dev/null
+++ b/src/hooks/tos.ts
@@ -0,0 +1,31 @@
+import { CreateTosFormValues } from "@/components/forms/tos/create/CreateTosForm";
+import {createTos, deleteTos, getAllTos, getTosByVersion, toggleTosActive} from "@/service/apis/tos";
+import { ServerResponse } from "@/types";
+import {useMutation, useQuery} from "@tanstack/react-query";
+export type TOS = {
+ isActive: boolean;
+ id: string;
+ title: string;
+ content: string;
+ version: string;
+ createdAt: Date;
+ updatedAt: Date;
+};
+
+export type GetAllTosResponse = {
+ status: number;
+ data: TOS[];
+ message: string;
+};
+export const useGetAllTos = () =>
+ useQuery({
+ queryKey: ["get-all-tos"],
+ queryFn: getAllTos,
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+
+export const useCreateTos = () => useMutation({mutationFn: createTos});
+export const useGetSingleTos = () => useMutation({mutationFn:getTosByVersion})
+export const useTosToggleActive = () => useMutation({mutationFn:toggleTosActive})
+export const useDeleteTos = () => useMutation({mutationFn:deleteTos})
\ No newline at end of file
diff --git a/src/hooks/transfer-team.ts b/src/hooks/transfer-team.ts
new file mode 100644
index 0000000..9017dc5
--- /dev/null
+++ b/src/hooks/transfer-team.ts
@@ -0,0 +1,25 @@
+import { createTransferTeam, deleteTransferTeam, getAllTransferTeam, getAllTransferTeamAllMembers, getSingleTransferTeam, updateTransferTeam } from "@/service/apis/transfer-team";
+import { useMutation, useQuery } from "@tanstack/react-query";
+
+export const useGetAllTransferTeam = () => useQuery({
+ queryKey:['get-all-transfer-team'],
+ queryFn:getAllTransferTeam,
+ retry:false,
+ refetchOnWindowFocus:false
+})
+export const useGetAllTransferTeamAllMembers = () => useQuery({
+ queryKey:['get-all-transfer-team-all-members'],
+ queryFn:getAllTransferTeamAllMembers,
+ retry:false,
+ refetchOnWindowFocus:false
+})
+export const useGetSingleTransferTeam = (id:string) => useQuery({
+ queryKey:['get-single-transfer-team',id],
+ queryFn:()=>getSingleTransferTeam(id),
+ retry:false,
+ refetchOnWindowFocus:false
+})
+
+export const useCreateTransferTeam = ()=>useMutation({mutationFn:createTransferTeam})
+export const useDeleteTransferTeam = ()=>useMutation({mutationFn:deleteTransferTeam})
+export const useUpdateTransferTeam = ()=>useMutation({mutationFn:updateTransferTeam})
\ No newline at end of file
diff --git a/src/hooks/users.ts b/src/hooks/users.ts
new file mode 100644
index 0000000..211bda7
--- /dev/null
+++ b/src/hooks/users.ts
@@ -0,0 +1,12 @@
+import { getSingleUser, logoutUser, updateUser } from "@/service/apis/users";
+import {useMutation, useQuery} from "@tanstack/react-query";
+
+export const useGetSingleUser = (id: string) =>
+ useQuery({
+ queryKey: ["get-single-user", id],
+ queryFn: () => getSingleUser(id),
+ retry: false,
+ refetchOnWindowFocus: false,
+ });
+export const useUpdateUser = () => useMutation({mutationFn: updateUser});
+export const useLogoutUser = () => useMutation({mutationFn:logoutUser})
\ No newline at end of file
diff --git a/src/lib/utils.ts b/src/lib/utils.ts
new file mode 100644
index 0000000..b5558c9
--- /dev/null
+++ b/src/lib/utils.ts
@@ -0,0 +1,168 @@
+import {SIDE_MENU_TYPE, SIDE_MENUS, STAFF_ROLE_TYPE} from "@/constants";
+import axios from "axios";
+import {clsx, type ClassValue} from "clsx";
+import {twMerge} from "tailwind-merge";
+interface ServiceInfo {
+ uptime: number; // seconds
+ pid: number;
+ nodeVersion: string;
+}
+
+interface MemoryInfo {
+ rss: number;
+ heapUsed: number;
+ heapTotal: number;
+ external?: number;
+}
+
+interface CpuInfo {
+ user: number; // microseconds
+ system: number; // microseconds
+}
+
+interface EventLoopInfo {
+ utilization: number; // 0..1
+ active: number;
+ idle: number;
+}
+
+interface SystemInfo {
+ freeMemory: number;
+ totalMemory: number;
+}
+
+export interface StatusResponse {
+ service: ServiceInfo;
+ memory: MemoryInfo;
+ cpu: CpuInfo;
+ eventLoop: EventLoopInfo;
+ system: SystemInfo;
+ timestamp: string;
+}
+
+// output type (UI-ready)
+export interface FormattedSystemUI {
+ uptime: string;
+ pid: number;
+ nodeVersion: string;
+
+ heapUsed: string;
+ heapTotal: string;
+ rss: string;
+
+ cpuUser: string;
+ cpuSystem: string;
+
+ eventLoopUtil: string;
+
+ freeMemory: string;
+ totalMemory: string;
+}
+
+export function cn(...inputs: ClassValue[]) {
+ return twMerge(clsx(inputs));
+}
+export function getSideMenusByRole(
+ menus: SIDE_MENU_TYPE[],
+ role: STAFF_ROLE_TYPE
+): SIDE_MENU_TYPE[] {
+ return menus
+ .map((menu) => {
+ // بررسی دسترسی خود منوی اصلی
+ const isMenuAllowed = !menu.roles || menu.roles.includes(role);
+
+ // اگر submenu داشت، آیتمهاش رو فیلتر کن
+ if (menu.sub?.items) {
+ const allowedSubItems = menu.sub.items.filter(
+ (item) => !item.roles || item.roles.includes(role)
+ );
+
+ // اگر نه خود منو مجازه، نه هیچ sub مجازه → حذف
+ if (!isMenuAllowed && allowedSubItems.length === 0) {
+ return null;
+ }
+
+ return {
+ ...menu,
+ sub: {
+ ...menu.sub,
+ items: allowedSubItems,
+ },
+ };
+ }
+
+ // منوی بدون sub
+ return isMenuAllowed ? menu : null;
+ })
+ .filter(Boolean) as SIDE_MENU_TYPE[];
+}
+export function formatSystemForUI(data: StatusResponse): FormattedSystemUI {
+ // uptime
+ const uptimeSec = data.service.uptime;
+ const h = Math.floor(uptimeSec / 3600);
+ const m = Math.floor((uptimeSec % 3600) / 60);
+ const uptime = h > 0 ? `${h}h ${m}m` : `${m}m`;
+
+ // memory formatter
+ const formatMem = (bytes: number): string => {
+ const gb = bytes / 1024 / 1024 / 1024;
+ if (gb >= 1) return `${gb.toFixed(2)} GB`;
+ return `${(bytes / 1024 / 1024).toFixed(2)} MB`;
+ };
+
+ return {
+ uptime,
+ pid: data.service.pid,
+ nodeVersion: data.service.nodeVersion,
+
+ heapUsed: formatMem(data.memory.heapUsed),
+ heapTotal: formatMem(data.memory.heapTotal),
+ rss: formatMem(data.memory.rss),
+
+ cpuUser: `${(data.cpu.user / 1000).toFixed(1)} ms`,
+ cpuSystem: `${(data.cpu.system / 1000).toFixed(1)} ms`,
+
+ eventLoopUtil: `${(data.eventLoop.utilization * 100).toFixed(2)}%`,
+
+ freeMemory: formatMem(data.system.freeMemory),
+ totalMemory: formatMem(data.system.totalMemory),
+ };
+}
+// تابع کمکی برای تبدیل مسیر dynamic به regex
+export function pathToRegex(path: string) {
+ // تبدیل /patients/edit/:id به /^\/patients\/edit\/[^/]+$/
+ const regexString = "^" + path.replace(/:[^/]+/g, "[^/]+") + "$";
+ return new RegExp(regexString);
+}
+
+// گرفتن تمام مسیرها همراه با roles
+export function getAllPathsWithRoles() {
+ const paths: {path: string; roles: STAFF_ROLE_TYPE[]}[] = [];
+
+ SIDE_MENUS.forEach((menu) => {
+ // مسیر منوی اصلی
+ if (menu.href && menu.href !== "#") {
+ paths.push({path: menu.href, roles: menu.roles!});
+ }
+
+ // مسیرهای sub items
+ if (menu.sub?.items) {
+ menu.sub.items.forEach((item) => {
+ if (item.href && item.href !== "#") {
+ paths.push({path: item.href, roles: item.roles!});
+ }
+ });
+ }
+ });
+
+ return paths;
+}
+
+export function handleAxiosError(error: unknown) {
+ if (axios.isAxiosError(error)) {
+ // اینجا میدونیم که خطا از axios است
+ return error.response?.data?.error?.message;
+ } else {
+ return "Unexpected error";
+ }
+}
diff --git a/src/middleware.ts b/src/middleware.ts
new file mode 100644
index 0000000..d8a9390
--- /dev/null
+++ b/src/middleware.ts
@@ -0,0 +1,103 @@
+import { NextResponse } from "next/server";
+import type { NextRequest } from "next/server";
+import { jwtDecode } from "jwt-decode";
+import { SIDE_MENUS, STAFF_ROLE_TYPE } from "./constants";
+import { jwt_token_payload } from "./types";
+
+function pathToRegex(path: string) {
+ const regexString = "^" + path.replace(/:[^/]+/g, "[^/]+") + "$";
+ return new RegExp(regexString);
+}
+
+function isTokenExpired(exp?: number) {
+ if (!exp) return true;
+ return exp * 1000 < Date.now();
+}
+
+export function middleware(req: NextRequest) {
+ const token = req.cookies.get("accessToken")?.value;
+ const { pathname } = req.nextUrl;
+ const url = req.nextUrl.clone();
+
+ const isAuthRoute = pathname.startsWith("/login");
+
+ const protectedRoutes = [
+ "/dashboard",
+ "/doctors",
+ "/patients",
+ "/staff",
+ "/department",
+ "/online-case",
+ "/transfer-team",
+ "/default",
+ "/pages",
+ ];
+
+ const isProtectedRoute = protectedRoutes.some((path) =>
+ pathname.startsWith(path)
+ );
+
+ let userRole: string | null = null;
+ let isValidToken = false;
+
+ if (token) {
+ try {
+ const decoded = jwtDecode(token);
+
+ if (!isTokenExpired(decoded?.exp)) {
+ userRole = decoded?.role?.toUpperCase() || null;
+ isValidToken = true;
+ }
+ } catch {
+ isValidToken = false;
+ }
+ }
+
+ // 1️⃣ Protected route بدون توکن معتبر
+ if (isProtectedRoute && !isValidToken) {
+ const loginUrl = new URL("/login", req.url);
+ loginUrl.searchParams.set("returnUrl", pathname);
+ return NextResponse.redirect(loginUrl);
+ }
+
+ // 2️⃣ چک دسترسی رول
+ if (isProtectedRoute && userRole) {
+ const isAllowed = SIDE_MENUS.some((menu) => {
+ if (menu.href && pathToRegex(menu.href).test(pathname)) {
+ return (
+ !menu.roles ||
+ menu.roles.includes(userRole as STAFF_ROLE_TYPE)
+ );
+ }
+
+ if (menu.sub?.items) {
+ return menu.sub.items.some(
+ (item) =>
+ item.href &&
+ pathToRegex(item.href).test(pathname) &&
+ (!item.roles ||
+ item.roles.includes(userRole as STAFF_ROLE_TYPE))
+ );
+ }
+
+ return false;
+ });
+
+ if (!isAllowed) {
+ return NextResponse.redirect(new URL("/dashboard", req.url));
+ }
+ }
+
+ // 3️⃣ لاگین شده ولی روی /login است
+ if (isAuthRoute && isValidToken) {
+ const returnUrl = req.nextUrl.searchParams.get("returnUrl");
+ const redirectTo =
+ returnUrl && !returnUrl.startsWith("/login")
+ ? returnUrl
+ : "/dashboard";
+
+ return NextResponse.redirect(new URL(redirectTo, req.url));
+ }
+
+ return NextResponse.next();
+}
diff --git a/src/service/apis/configs.ts b/src/service/apis/configs.ts
new file mode 100644
index 0000000..da06e84
--- /dev/null
+++ b/src/service/apis/configs.ts
@@ -0,0 +1,9 @@
+import { ConfigFormItem } from "@/components/forms/configs/ConfigsForm";
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllConfigs() {
+ return await privateApi.get("/configs/get/all").then((res) => res.data);
+}
+export async function updateAllConfigs(data:ConfigFormItem[]) {
+ return await privateApi.put("/configs/update",data).then((res) => res.data);
+}
diff --git a/src/service/apis/country.ts b/src/service/apis/country.ts
new file mode 100644
index 0000000..68a2e6f
--- /dev/null
+++ b/src/service/apis/country.ts
@@ -0,0 +1,5 @@
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllCountries() {
+ return privateApi.get("/country/get/items").then((res) => res.data);
+}
diff --git a/src/service/apis/default.ts b/src/service/apis/default.ts
new file mode 100644
index 0000000..265898c
--- /dev/null
+++ b/src/service/apis/default.ts
@@ -0,0 +1,9 @@
+import { UpdateDefaultFormValues } from "@/components/forms/default/UpdateDefaultsForm";
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllDefaults() {
+ return await privateApi.get(`/default-info/get/all`).then((res) => res.data);
+}
+export async function getUpdateDefaults({data}:{data:UpdateDefaultFormValues}) {
+ return await privateApi.put(`/default-info/update`,data).then((res) => res.data);
+}
\ No newline at end of file
diff --git a/src/service/apis/expertise.ts b/src/service/apis/expertise.ts
new file mode 100644
index 0000000..1b6127c
--- /dev/null
+++ b/src/service/apis/expertise.ts
@@ -0,0 +1,38 @@
+import { CreateExpertiseValues } from "@/components/forms/expertise/new/CreateExpertiseForm";
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllExpertise(lang: string) {
+ return privateApi.get(`/expertise/${lang}/get/all`).then((res) => res.data);
+}
+export async function getSingleExpertise(id: string) {
+ return privateApi.get(`/expertise/get/single/${id}`).then((res) => res.data);
+}
+
+export async function createExpertise(data: CreateExpertiseValues) {
+ return await privateApi
+ .post(`/expertise/create`, data)
+ .then((res) => res.data);
+}
+export async function updateExpertise({
+ data,
+ id,
+}: {
+ data: CreateExpertiseValues;
+ id: string;
+}) {
+ return await privateApi
+ .put(`/expertise/update/${id}`, data)
+ .then((res) => res.data);
+}
+
+export async function deleteExpertise(id: string) {
+ return await privateApi
+ .delete(`/expertise/delete/${id}`)
+ .then((res) => res.data);
+}
+
+export async function getExpertiseList() {
+ return await privateApi
+ .get(`/expertise/fa/get/all/list`)
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/index.ts b/src/service/apis/index.ts
new file mode 100644
index 0000000..f6edb8c
--- /dev/null
+++ b/src/service/apis/index.ts
@@ -0,0 +1,15 @@
+import privateApi from "../http/privateCall.axios";
+
+export async function deleteFile({
+ fileKey,
+ fileUrl,
+ type,
+}: {
+ fileKey: string;
+ fileUrl: string;
+ type: "image" | "document";
+}) {
+ return await privateApi
+ .post(`/upload/delete`, {fileKey, fileUrl, type})
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/languages.ts b/src/service/apis/languages.ts
new file mode 100644
index 0000000..96ab50b
--- /dev/null
+++ b/src/service/apis/languages.ts
@@ -0,0 +1,21 @@
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllLanguages() {
+ return privateApi.get(`/language/get/all`).then((res) => res.data);
+}
+
+export async function createLanguage(data: {title: string,slug:string}) {
+ return await privateApi
+ .post("/language/create", data)
+ .then((res) => res.data);
+}
+export async function updateLanguage({data,id}:{data:{title:string},id:string}) {
+ return await privateApi
+ .put(`/language/update/${id}"`, data)
+ .then((res) => res.data);
+}
+export async function deleteLanguage(id: string) {
+ return await privateApi
+ .delete(`/language/delete/${id}`)
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/logs.ts b/src/service/apis/logs.ts
new file mode 100644
index 0000000..f0532e6
--- /dev/null
+++ b/src/service/apis/logs.ts
@@ -0,0 +1,5 @@
+import CDNCall from "../http/CdnCall.axios";
+
+export async function getAllUploadServerLogs() {
+ return await CDNCall.get("/logs").then((res) => res.data);
+}
diff --git a/src/service/apis/medical-package.ts b/src/service/apis/medical-package.ts
new file mode 100644
index 0000000..8bc3595
--- /dev/null
+++ b/src/service/apis/medical-package.ts
@@ -0,0 +1,37 @@
+import {CreateMedicalPackageValues} from "@/components/forms/medical-packages/new/CreateMedicalPackageForm";
+import privateApi from "../http/privateCall.axios";
+import { UpdateMedicalPackageFormValues } from "@/components/forms/medical-packages/edit/UpdateMedicalPackageForm";
+
+export async function getAllMedicalPackages(lang: string) {
+ return await privateApi
+ .get(`/medical-packages/${lang}/get/all`)
+ .then((res) => res.data);
+}
+export async function getAllMedicalPackagesParent(lang: string) {
+ return await privateApi
+ .get(`/medical-packages/${lang}/get/all/parent`)
+ .then((res) => res.data);
+}
+export async function getSingleMedicalPackage(id: string,lang:string) {
+ return await privateApi
+ .get(`/medical-packages/${lang}/get/single/${id}`)
+ .then((res) => res.data);
+}
+
+export async function createMedicalPackage(data: CreateMedicalPackageValues) {
+ return await privateApi
+ .post("/medical-packages/create", data)
+ .then((res) => res.data);
+}
+
+export async function updateMedicalPackage(id: string, data:UpdateMedicalPackageFormValues) {
+ return await privateApi
+ .put(`/medical-packages/update/${id}`, data)
+ .then((res) => res.data);
+}
+
+export async function deleteMedicalPackage(id: string) {
+ return await privateApi
+ .delete(`/medical-packages/delete/${id}`)
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/online-case.ts b/src/service/apis/online-case.ts
new file mode 100644
index 0000000..ee4bb25
--- /dev/null
+++ b/src/service/apis/online-case.ts
@@ -0,0 +1,18 @@
+import { CreateOnlineCaseFormValues } from "@/components/forms/online-case/new/CreateOnlineCaseForm";
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllOnlineCases() {
+ return await privateApi.get("/case/get/all").then((res) => res.data);
+}
+export async function getSingleOnlineCase(id: string) {
+ return await privateApi.get(`/case/get/single/${id}`).then((res) => res.data);
+}
+
+export async function createOnlineCase(data: CreateOnlineCaseFormValues) {
+ return await privateApi.post(`/case/create`, data).then((res) => res.data);
+}
+export async function updateOnlineCase({data, id}: {data: CreateOnlineCaseFormValues; id: string}) {
+ return await privateApi
+ .post(`/case/update/${id}`, data)
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/patients.ts b/src/service/apis/patients.ts
new file mode 100644
index 0000000..955d87f
--- /dev/null
+++ b/src/service/apis/patients.ts
@@ -0,0 +1,14 @@
+import { CreatePatientFormValues } from "@/components/forms/patients/new/CreatePatientForm";
+import privateApi from "../http/privateCall.axios";
+
+export async function getSinglePatient(id: string) {
+ return await privateApi
+ .get(`/patient/get/single/${id}`)
+ .then((res) => res.data);
+}
+
+export async function updatePatient(id: string, data: CreatePatientFormValues) {
+ return await privateApi
+ .put(`/patient/update/${id}`, data)
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/privacy-policy.ts b/src/service/apis/privacy-policy.ts
new file mode 100644
index 0000000..d02cf4a
--- /dev/null
+++ b/src/service/apis/privacy-policy.ts
@@ -0,0 +1,11 @@
+import privateApi from "../http/privateCall.axios";
+
+export async function updatePrivacyPolicy(data: string) {
+ return await privateApi
+ .put("/pp/update", {content: data})
+ .then((res) => res.data);
+}
+
+export async function getPP() {
+ return await privateApi.get("/pp/get").then((res) => res.data);
+}
diff --git a/src/service/apis/tos.ts b/src/service/apis/tos.ts
new file mode 100644
index 0000000..4597bdc
--- /dev/null
+++ b/src/service/apis/tos.ts
@@ -0,0 +1,26 @@
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllTos() {
+ return await privateApi.get("/tos/get/all").then((res) => res.data);
+}
+
+export async function getTosByVersion(version: string) {
+ return await privateApi
+ .get(`/tos/get/version/${version}`)
+ .then((res) => res.data);
+}
+
+export async function createTos(data: {
+ title: string;
+ version: string;
+ content: string;
+ isActive: boolean;
+}) {
+ return await privateApi.post("/tos/create", data).then((res) => res.data);
+}
+export async function toggleTosActive(version: string) {
+ return await privateApi.patch(`/tos/toggle`, {version}).then((res) => res.data);
+}
+export async function deleteTos(version: string) {
+ return await privateApi.delete(`/tos/delete/${version}`).then((res) => res.data);
+}
diff --git a/src/service/apis/transfer-team.ts b/src/service/apis/transfer-team.ts
new file mode 100644
index 0000000..0c14c80
--- /dev/null
+++ b/src/service/apis/transfer-team.ts
@@ -0,0 +1,35 @@
+import privateApi from "../http/privateCall.axios";
+
+export async function getAllTransferTeam() {
+ return await privateApi
+ .get("/transfer-team/get/all/teams")
+ .then((res) => res.data);
+}
+export async function getAllTransferTeamAllMembers() {
+ return await privateApi
+ .get("/transfer-team/get/all/members")
+ .then((res) => res.data);
+}
+
+export async function getSingleTransferTeam(id: string) {
+ return await privateApi
+ .get(`/transfer-team/get/single/team/${id}`)
+ .then((res) => res.data);
+}
+
+export async function createTransferTeam(data) {
+ return await privateApi
+ .post(`/transfer-team/create`, data)
+ .then((res) => res.data);
+}
+export async function updateTransferTeam(id: string, data) {
+ return await privateApi
+ .put(`/transfer-team/update/${id}`, data)
+ .then((res) => res.data);
+}
+
+export async function deleteTransferTeam(id: string) {
+ return await privateApi
+ .delete(`/transfer-team/delete/${id}`)
+ .then((res) => res.data);
+}
diff --git a/src/service/apis/users.ts b/src/service/apis/users.ts
new file mode 100644
index 0000000..0493d16
--- /dev/null
+++ b/src/service/apis/users.ts
@@ -0,0 +1,18 @@
+import {UpdateUserVariables} from "@/components/forms/department/update/UpdateDepartmentMemberForm";
+import privateApi from "../http/privateCall.axios";
+
+export async function getSingleUser(id: string) {
+ return await privateApi.get(`/user/get/single/${id}`).then((res) => res.data);
+}
+export async function updateUser({data, id}: UpdateUserVariables) {
+ return await privateApi
+ .put(`/user/update/${id}`, data)
+ .then((res) => res.data);
+}
+
+export async function logoutUser() {
+ return await privateApi.post(`/auth/logout`).then((res) => res.data);
+}
+export async function userLogin(data: {username: string; password: string}) {
+ return await privateApi.post(`/auth/login`, data).then((res) => res.data);
+}
diff --git a/src/service/http/CdnCall.axios.ts b/src/service/http/CdnCall.axios.ts
new file mode 100644
index 0000000..2a2b295
--- /dev/null
+++ b/src/service/http/CdnCall.axios.ts
@@ -0,0 +1,90 @@
+import axios from "axios";
+
+const CDNCallAxios = axios.create({
+ baseURL: "http://localhost:4000",
+ withCredentials: true,
+});
+
+CDNCallAxios.interceptors.request.use(
+ (config) => {
+ return config;
+ },
+ (error) => Promise.reject(error),
+);
+
+CDNCallAxios.interceptors.response.use(
+ (res) => res,
+ (error) => Promise.reject(error),
+
+ // async (error: AxiosError) => {
+ // const originalRequest: any = error.config;
+ // if (
+ // error.response?.status === 503 &&
+ // error.response?.data?.error?.description === "denied" &&
+ // !originalRequest?._retry
+ // ) {
+
+ // originalRequest._retry = true;
+
+ // try {
+ // await axios.post(
+ // `${API_URL}/auth/logout`,
+ // {},
+ // {withCredentials: true}
+ // );
+
+ // processQueue(null);
+ // return privateAxios(originalRequest);
+ // } catch (refreshError) {
+ // processQueue(refreshError);
+ // window.location.href = "/login";
+ // return Promise.reject(refreshError);
+ // } finally {
+ // isRefreshing = false;
+ // }
+ // }
+ // if (
+ // error.response?.status === 401 &&
+ // error.response?.data?.error?.description === "unauthorized" &&
+ // !originalRequest?._retry
+ // ) {
+ // if (isRefreshing) {
+ // return new Promise((resolve, reject) => {
+ // failedQueue.push({resolve, reject});
+ // }).then(() => privateAxios(originalRequest));
+ // }
+
+ // originalRequest._retry = true;
+ // isRefreshing = true;
+
+ // try {
+ // await axios.post(
+ // `${API_URL}/auth/refresh-token`,
+ // {},
+ // {withCredentials: true}
+ // );
+
+ // processQueue(null);
+ // return privateAxios(originalRequest);
+ // } catch (refreshError) {
+ // processQueue(refreshError);
+ // window.location.href = "/login";
+ // return Promise.reject(refreshError);
+ // } finally {
+ // isRefreshing = false;
+ // }
+ // }
+
+ // return Promise.reject(error);
+ // }
+);
+
+const CDNCall = {
+ get: CDNCallAxios.get,
+ post: CDNCallAxios.post,
+ put: CDNCallAxios.put,
+ patch: CDNCallAxios.patch,
+ delete: CDNCallAxios.delete,
+};
+
+export default CDNCall;
diff --git a/src/service/http/privateCall.axios.ts b/src/service/http/privateCall.axios.ts
new file mode 100644
index 0000000..f1da4ff
--- /dev/null
+++ b/src/service/http/privateCall.axios.ts
@@ -0,0 +1,63 @@
+import axios, { AxiosError } from "axios";
+import { API_URL } from "@/constants";
+import { ServerErrorsObject } from "@/types";
+
+let isRefreshing = false;
+let failedQueue: {
+ resolve: (value?: unknown) => void;
+ reject: (reason?: unknown) => void;
+}[] = [];
+
+const processQueue = (error: unknown) => {
+ failedQueue.forEach((promise) => {
+ if (error) promise.reject(error);
+ else promise.resolve(null);
+ });
+ failedQueue = [];
+};
+
+const privateAxios = axios.create({
+ baseURL: API_URL,
+ withCredentials: true,
+});
+privateAxios.interceptors.response.use(
+ (response) => response,
+ async (error) => {
+
+ const err = error as AxiosError;
+
+ const originalRequest = error.config;
+
+ if (err.response?.status !== 401 || (err?.response?.data?.status ===403 && err?.response?.data?.error?.message ==="denied") || originalRequest?._retry) {
+ return Promise.reject(error);
+ }
+
+ if (isRefreshing) {
+ return new Promise((resolve, reject) => {
+ failedQueue.push({ resolve, reject });
+ }).then(() => privateAxios(originalRequest));
+ }
+
+ originalRequest._retry = true;
+ isRefreshing = true;
+
+ try {
+ await axios.post(
+ `${API_URL}/auth/refresh-token`,
+ {},
+ { withCredentials: true }
+ );
+
+ processQueue(null);
+ return privateAxios(originalRequest);
+ } catch (err) {
+ processQueue(err);
+ window.location.href = "/login";
+ return Promise.reject(err);
+ } finally {
+ isRefreshing = false;
+ }
+ }
+);
+
+export default privateAxios;
diff --git a/src/service/http/publicCall.axios.ts b/src/service/http/publicCall.axios.ts
new file mode 100644
index 0000000..a5e6e85
--- /dev/null
+++ b/src/service/http/publicCall.axios.ts
@@ -0,0 +1,25 @@
+// import axios from "axios";
+
+// const publicAxios = axios.create({
+// baseURL: PUBLIC_BASE_URL,
+// withCredentials: true,
+// });
+
+// publicAxios.interceptors.request.use(
+// (res) => res,
+// (err) => Promise.reject(err)
+// );
+
+// publicAxios.interceptors.response.use(
+// (res) => res,
+// async (err) => Promise.reject(err)
+// );
+
+// const publicApi = {
+// post: publicAxios.post,
+// get: publicAxios.get,
+// put: publicAxios.put,
+// delete: publicAxios.delete,
+// };
+
+// export default publicApi;
diff --git a/src/types/index.ts b/src/types/index.ts
new file mode 100644
index 0000000..22bf740
--- /dev/null
+++ b/src/types/index.ts
@@ -0,0 +1,128 @@
+import { UsersType } from "@/constants";
+
+export interface Language {
+ id: number;
+ title: string;
+ slug: string; // fa, en, ...
+}
+
+export interface Expertise {
+ translations: {
+ id: number;
+ displayName: string | null;
+ lang_id: number | null;
+ lang: {
+ id: number;
+ slug: string;
+ title: string;
+ };
+ expertiseId: number;
+ }[];
+ slug: string;
+ id: number;
+}
+
+export interface getSingleUserDataType {
+ id: number;
+ slug: string;
+ type: UsersType;
+ phone: string;
+ email: string;
+ medicalNumber: string;
+ expertiseId: number | null;
+ imageId: number | null;
+ expertise: {
+ id: number;
+ slug: string;
+ translations: {
+ lang_id: number;
+ displayName: string | null;
+ lang: {
+ id: number;
+ slug: string;
+ title: string;
+ } | null;
+ }[];
+ } | null;
+ image: {
+ filename: string | null;
+ fileUrl: string | null;
+ fileKey: string | null;
+ } | null;
+ translations: {
+ id: number;
+ lang_id: number;
+ lang: {
+ id: number;
+ slug: string;
+ title: string;
+ } | null;
+ firstName: string;
+ lastName: string;
+ position: string;
+ bio: string;
+ excerpt: string;
+ }[];
+}
+
+export interface getSinglePatientDataType {
+ id: string;
+ pid: string;
+ firstName: string;
+ lastName: string;
+ birthDate: Date | null;
+ sex: Sex | null;
+ nationalityId: number | null;
+ nationalityCode: string | null;
+ passportCode: string | null;
+ phone: string | null;
+ email: string | null;
+ preferredLanguage: string | null;
+ address: string | null;
+ postalCode: string | null;
+ createdAt: Date;
+ updatedAt: Date;
+ deletedAt: Date | null;
+}
+export interface jwt_token_payload {
+ id: string;
+ role: string;
+ exp: number;
+ iat?: number;
+}
+export interface ServerResponseObject {
+ status: number;
+ data: unknown;
+ message?: string;
+}
+
+export interface ServerErrorsObject {
+ status: number;
+ data?: unknown;
+ error: {
+ message: string;
+ description?: string;
+ };
+}
+
+export type Sex = "male" | "female" | "other";
+
+export type ServerResponse = ServerResponseObject & ServerErrorsObject;
+export type DocumentType =
+ | "MEDICAL_IMAGE"
+ | "LAB_RESULT"
+ | "PRESCRIPTION"
+ | "PASSPORT"
+ | "NATIONAL_ID"
+ | "OTHER"
+ | "OTHER_FILE";
+
+export interface CreateUserTranslation {
+ lang_id: number;
+ firstName: string;
+ lastName: string;
+ bio?: string;
+ excerpt?: string;
+ expertise?: string | null;
+ position?: string | null;
+}
diff --git a/tsconfig.json b/tsconfig.json
index c133409..7ea5d10 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -4,7 +4,7 @@
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
- "strict": true,
+ "strict": false,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
@@ -20,7 +20,10 @@
],
"paths": {
"@/*": ["./src/*"]
- }
+ },
+ "noImplicitAny": false,
+ "noUnusedLocals": false,
+ "noUnusedParameters": false
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]