diff --git a/package-lock.json b/package-lock.json index 79e0bd5..01a03fd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,8 @@ "clsx": "^2.1.1", "next": "14.2.20", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "sharp": "^0.33.5" }, "devDependencies": { "@types/node": "^20", @@ -69,6 +70,15 @@ "postcss-selector-parser": "^7.0.0" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", @@ -163,6 +173,348 @@ "deprecated": "Use @eslint/object-schema instead", "dev": true }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@isaacs/cliui": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", @@ -1098,11 +1450,22 @@ "node": ">=6" } }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1113,8 +1476,16 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } }, "node_modules/concat-map": { "version": "0.0.1", @@ -1268,6 +1639,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -2424,6 +2803,11 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-arrayish": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", + "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" + }, "node_modules/is-async-function": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.0.0.tgz", @@ -3754,7 +4138,6 @@ "version": "7.6.3", "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, "bin": { "semver": "bin/semver.js" }, @@ -3794,6 +4177,44 @@ "node": ">= 0.4" } }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", @@ -3899,6 +4320,14 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/simple-swizzle": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", + "integrity": "sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", diff --git a/package.json b/package.json index 5573408..c476a36 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,8 @@ "clsx": "^2.1.1", "next": "14.2.20", "react": "^18", - "react-dom": "^18" + "react-dom": "^18", + "sharp": "^0.33.5" }, "devDependencies": { "@types/node": "^20", diff --git a/src/app/doctors/[[...slug]]/components/filter/clear-all-filter.component.tsx b/src/app/doctors/[[...slug]]/components/filter/clear-all-filter.component.tsx new file mode 100644 index 0000000..ff20fa3 --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/filter/clear-all-filter.component.tsx @@ -0,0 +1,25 @@ +"use client"; + +import { ReactElement, useContext } from "react"; + +import { FiltersContext } from "../../providers/Filters/filters.provider"; + +import CardComponent from "@/components/card/card.component"; + +import styles from "./clear-all-filter.module.css"; + +const ClearAllFilterComponent = (): ReactElement => { + const { clearAll } = useContext(FiltersContext); + + return ( + +
+ +
+
+ ); +}; + +export default ClearAllFilterComponent; diff --git a/src/app/doctors/[[...slug]]/components/filter/clear-all-filter.module.css b/src/app/doctors/[[...slug]]/components/filter/clear-all-filter.module.css new file mode 100644 index 0000000..7b5a487 --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/filter/clear-all-filter.module.css @@ -0,0 +1,12 @@ +.clear { + width: 100%; + border-radius: var(--border-radius); + + background-color: var(--color-danger); + + cursor: pointer; + + &:hover { + background-color: var(--color-danger-darker); + } +} diff --git a/src/app/doctors/[[...slug]]/components/filter/filter-wraper.component.module.css b/src/app/doctors/[[...slug]]/components/filter/filter-wraper.component.module.css new file mode 100644 index 0000000..b5388df --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/filter/filter-wraper.component.module.css @@ -0,0 +1,4 @@ +.filters { + display: grid; + gap: 1rem; +} diff --git a/src/app/doctors/[[...slug]]/components/filter/filter-wraper.component.tsx b/src/app/doctors/[[...slug]]/components/filter/filter-wraper.component.tsx new file mode 100644 index 0000000..484910d --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/filter/filter-wraper.component.tsx @@ -0,0 +1,69 @@ +import { ReactElement } from "react"; + +import ClearAllFilterComponent from "./clear-all-filter.component"; +import FilterComponent from "./filter.component"; + +import { specialties } from "@/models/specialties"; +import { degrees } from "@/models/degrees"; + +import { GenderEnums } from "@/enums/gender"; +import { FilterEnums } from "../../enums/filter.enum"; + +import { isEmpty } from "@/utils/isEmpty"; +import { ResultObject } from "@/utils/convertListToObject"; + +import styles from "./filter-wraper.component.module.css"; + +type Props = { + selectedFilters: ResultObject; +}; + +const FilterWraperComponent = ({ + selectedFilters = {}, +}: Props): ReactElement => { + return ( +
+ {!isEmpty(selectedFilters) && } + + + + + + + + +
+ ); +}; + +export default FilterWraperComponent; diff --git a/src/app/doctors/[[...slug]]/components/filter/filter.component.tsx b/src/app/doctors/[[...slug]]/components/filter/filter.component.tsx new file mode 100644 index 0000000..d30a44b --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/filter/filter.component.tsx @@ -0,0 +1,50 @@ +"use client"; + +import { ReactElement, useContext } from "react"; + +import { FiltersContext } from "../../providers/Filters/filters.provider"; + +import CardComponent from "@/components/card/card.component"; +import FilterButtonComponent from "@/components/filter-button/filter-button.component"; + +import { ResultObject } from "@/utils/convertListToObject"; +import { FilterEnums } from "../../enums/filter.enum"; + +import styles from "./filter.module.css"; + +type Props = { + filterName: FilterEnums; + title: string; + options?: { id: string; en: string; fa: string }[]; + selectedFilters: ResultObject; +}; + +const FilterComponent = ({ + filterName, + title, + options = [], + selectedFilters = {}, +}: Props): ReactElement => { + const { changeFilter } = useContext(FiltersContext); + + return ( + +
+
{title}
+
+ {options.map((option) => ( + changeFilter(filterName, option.en)} + > + {option.fa} + + ))} +
+
+
+ ); +}; + +export default FilterComponent; diff --git a/src/app/doctors/[[...slug]]/components/filter/filter.module.css b/src/app/doctors/[[...slug]]/components/filter/filter.module.css new file mode 100644 index 0000000..66b142e --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/filter/filter.module.css @@ -0,0 +1,17 @@ +.filter { + .title { + font-weight: 900; + } + + .buttons { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + + margin-block-start: 0.5rem; + + > * { + flex: 1 0 auto; + } + } +} diff --git a/src/app/doctors/[[...slug]]/components/item/item.component.tsx b/src/app/doctors/[[...slug]]/components/item/item.component.tsx new file mode 100644 index 0000000..ab85083 --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/item/item.component.tsx @@ -0,0 +1,69 @@ +import { ReactElement } from "react"; + +import Image from "next/image"; + +import { MingcuteStarFill } from "@/icons/MingcuteStarFill"; + +import { DoctorModel } from "@/types/doctor.type"; +import { GenderEnums } from "@/enums/gender"; + +import maleImg from "@/assets/fallback-images/portrait-3d-male-doctor.jpg"; +import femaleImg from "@/assets/fallback-images/portrait-3d-female-doctor.jpg"; + +import styles from "./item.module.css"; +import { MingcuteLocationFill } from "@/icons/MingcuteLocationFill"; +import { MingcuteArrowLeftLine } from "@/icons/MingcuteArrowLeftLine"; + +type Props = { + item: DoctorModel; +}; + +const ItemComponent = ({ item }: Props): ReactElement => { + return ( +
  • +
    + {item.name} +
    +

    {item.name}

    +

    {item.brief}

    + + + + + {item.averageRating} +   ({item.totalVotes} نظر) + +
    +
    +
    + {item.badges.map((item, index) => ( + + {item} + + ))} +
    +
    +
    + + {item.address} +
    +
    + + +
  • + ); +}; + +export default ItemComponent; diff --git a/src/app/doctors/[[...slug]]/components/item/item.module.css b/src/app/doctors/[[...slug]]/components/item/item.module.css new file mode 100644 index 0000000..997af0f --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/item/item.module.css @@ -0,0 +1,116 @@ +.item { + position: relative; + + padding: 1rem; + height: auto; + + background-color: var(--color-gray-20); + + border-radius: 0.5rem; + + .info { + display: flex; + justify-content: start; + align-items: start; + gap: 0.8rem; + } + + .image { + border-radius: 0.7rem; + border: 0.24rem solid var(--color-gray-98); + } + + .name { + font-size: var(--fz-400); + } + + .brief { + margin-top: 0.2rem; + font-size: var(--fz-200); + } + + .rate { + display: flex; + align-items: center; + gap: 0.15rem; + + background-color: var(--color-gray-30); + + width: fit-content; + + padding-block: 0.1rem; + padding-inline: 0.2rem; + margin-top: 0.4rem; + border-radius: 0.2rem; + + font-size: var(--fz-100); + + .star { + color: var(--color-warning); + margin-top: 2px; + } + } + + .badge { + display: flex; + justify-content: start; + gap: 0.4rem; + + margin-block-start: 2rem; + + .badge_item { + background-color: var(--color-gray-40); + width: fit-content; + + font-size: var(--fz-100); + + padding-block: 0.1rem; + padding-inline: 0.2rem; + border-radius: 0.2rem; + } + } + + .address { + margin-block-start: 1.5rem; + + address { + font-style: normal; + font-size: var(--fz-200); + + display: flex; + align-items: start; + gap: 0.3rem; + + svg { + flex-shrink: 0; + margin-top: 0.25rem; + } + } + } + + button { + position: absolute; + top: 1rem; + left: 1rem; + + display: flex; + align-items: center; + gap: 0.15rem; + + background-color: var(--color-primary); + + border: none; + border-radius: 0.2rem; + + padding: 0.3rem; + cursor: pointer; + + transition: 0.2s ease-in-out; + transition-property: background-color, color; + + &:hover { + background-color: var(--color-primary-fade); + color: var(--color-primary-opposite); + } + } +} diff --git a/src/app/doctors/[[...slug]]/components/list/list.component.tsx b/src/app/doctors/[[...slug]]/components/list/list.component.tsx new file mode 100644 index 0000000..78ac2a2 --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/list/list.component.tsx @@ -0,0 +1,30 @@ +import { ReactElement } from "react"; + +import ItemComponent from "../item/item.component"; + +import { DoctorModel } from "@/types/doctor.type"; + +import styles from "./list.module.css"; +import Link from "next/link"; + +const ListComponent = ({ + doctors, +}: { + doctors: DoctorModel[]; +}): ReactElement => { + if (doctors.length === 0) { + return

    متاسفیم. درحال حاظر پزشکی وجود ندارد!

    ; + } + + return ( + + ); +}; + +export default ListComponent; diff --git a/src/app/doctors/[[...slug]]/components/list/list.module.css b/src/app/doctors/[[...slug]]/components/list/list.module.css new file mode 100644 index 0000000..8a6b6ea --- /dev/null +++ b/src/app/doctors/[[...slug]]/components/list/list.module.css @@ -0,0 +1,4 @@ +.container { + display: grid; + gap: 1rem; +} diff --git a/src/app/doctors/[[...slug]]/enums/filter.enum.ts b/src/app/doctors/[[...slug]]/enums/filter.enum.ts new file mode 100644 index 0000000..0056279 --- /dev/null +++ b/src/app/doctors/[[...slug]]/enums/filter.enum.ts @@ -0,0 +1,7 @@ +export enum FilterEnums { + GENDER = "gender", + SPECIALTY = "specialty", + CITY = "city", + DEGREE = "degree", + NEXT_APPOINTMENT = "nextAppointments", +} diff --git a/src/app/doctors/[[...slug]]/page.module.css b/src/app/doctors/[[...slug]]/page.module.css new file mode 100644 index 0000000..7e66acc --- /dev/null +++ b/src/app/doctors/[[...slug]]/page.module.css @@ -0,0 +1,14 @@ +.page { + display: grid; + grid-template-columns: 1.5fr 4fr; + align-items: start; + gap: 1rem; + + margin-block-end: 2rem; +} + +.search { + display: flex; + justify-content: center; + margin-block-end: 3rem; +} diff --git a/src/app/doctors/[[...slug]]/page.tsx b/src/app/doctors/[[...slug]]/page.tsx new file mode 100644 index 0000000..a08d2e2 --- /dev/null +++ b/src/app/doctors/[[...slug]]/page.tsx @@ -0,0 +1,76 @@ +import { ReactElement, useMemo } from "react"; + +import ListComponent from "./components/list/list.component"; +import GlobalSearchBoxComponent from "@/components/global-search-box/global-search-box.component"; +import FilterWraperComponent from "./components/filter/filter-wraper.component"; + +import { doctorsData } from "@/models/doctors"; +import { DoctorModel } from "@/types/doctor.type"; + +import convertListToObject from "@/utils/convertListToObject"; + +import FiltersProvider from "./providers/Filters/filters.provider"; + +import styles from "./page.module.css"; + +type SearchPageType = { + params: { slug: string[] }; + searchParams?: { [key: string]: string | string[] | undefined }; +}; + +const doctors: DoctorModel[] = doctorsData; + +const SearchPage: React.FC = ({ + params, + searchParams, +}): ReactElement => { + const getValueFromUrl = useMemo( + () => convertListToObject(params.slug, searchParams || {}), + [params.slug, searchParams], + ); + + const filteredDoctors: DoctorModel[] = useMemo(() => { + return doctors.filter((doctor) => { + if (getValueFromUrl.city && doctor.city.en !== getValueFromUrl.city) { + return false; + } + + if ( + getValueFromUrl.specialty && + doctor.specialty.en !== getValueFromUrl.specialty + ) { + return false; + } + + if ( + getValueFromUrl.degree && + doctor.degree.en !== getValueFromUrl.degree + ) { + return false; + } + + if ( + getValueFromUrl.gender && + doctor.gender.en !== getValueFromUrl.gender + ) { + return false; + } + + return true; + }); + }, [getValueFromUrl]); + + return ( + +
    + +
    +
    + + +
    +
    + ); +}; + +export default SearchPage; diff --git a/src/app/doctors/[[...slug]]/providers/Filters/filters.provider.tsx b/src/app/doctors/[[...slug]]/providers/Filters/filters.provider.tsx new file mode 100644 index 0000000..7e391a1 --- /dev/null +++ b/src/app/doctors/[[...slug]]/providers/Filters/filters.provider.tsx @@ -0,0 +1,61 @@ +"use client"; + +import { createContext, PropsWithChildren, ReactElement } from "react"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; + +import { FilterEnums } from "../../enums/filter.enum"; +import { GenderEnums } from "@/enums/gender"; + +type ContextValue = { + changeFilter: (key: FilterEnums, value: string) => void; + clearAll: () => void; +}; + +export const FiltersContext = createContext({ + changeFilter: () => {}, + clearAll: () => {}, +}); + +type Props = PropsWithChildren; + +const FiltersProvider = ({ children }: Props): ReactElement => { + const pathname = usePathname(); + const { replace } = useRouter(); + const searchParams = useSearchParams(); + const params = new URLSearchParams(searchParams); + + const changeFilter = (key: FilterEnums, value: string): void => { + const segments = pathname.split("/"); + + if (key === FilterEnums.GENDER && value !== GenderEnums.BOTH) { + params.set(FilterEnums.GENDER, value); + } else { + params.delete(FilterEnums.GENDER); + } + + if (key !== FilterEnums.GENDER) { + const filterIndex = segments.indexOf(key.toLowerCase()); + + if (filterIndex !== -1) { + segments[filterIndex + 1] = value; + } else { + segments.push(key.toLowerCase(), value); + } + } + + const newPathname = segments.join("/"); + replace(`${newPathname}?${params.toString()}`); + }; + + const clearAll = (): void => { + replace("/doctors"); + }; + + return ( + + {children} + + ); +}; + +export default FiltersProvider; diff --git a/src/app/doctors/[[...slug]]/types/filters.type.ts b/src/app/doctors/[[...slug]]/types/filters.type.ts new file mode 100644 index 0000000..829662e --- /dev/null +++ b/src/app/doctors/[[...slug]]/types/filters.type.ts @@ -0,0 +1,6 @@ +export type FiltersType = { + gender?: boolean; + specialty?: boolean; + academicDegree?: boolean; + nextAppointments?: boolean; +}; diff --git a/src/app/globals.css b/src/app/globals.css index a58948f..4606fa3 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -19,6 +19,10 @@ --color-primary-darker: hsl(120deg 50% 44%); --color-primary-opposite: var(--color-gray-10); + --color-danger: hsl(0, 47.5%, 50%); + --color-danger-darker: hsl(360, 61.5%, 42.5%); + --color-warning: hsl(57, 100%, 50%); + --border-radius: 0.875rem; --full-width: 75rem; @@ -90,4 +94,8 @@ body { padding-block: 1rem; text-align: center; } + + main { + padding-block: 4rem; + } } diff --git a/src/assets/fallback-images/portrait-3d-female-doctor.jpg b/src/assets/fallback-images/portrait-3d-female-doctor.jpg new file mode 100644 index 0000000..4a0bf5d Binary files /dev/null and b/src/assets/fallback-images/portrait-3d-female-doctor.jpg differ diff --git a/src/assets/fallback-images/portrait-3d-male-doctor.jpg b/src/assets/fallback-images/portrait-3d-male-doctor.jpg new file mode 100644 index 0000000..e24dd7a Binary files /dev/null and b/src/assets/fallback-images/portrait-3d-male-doctor.jpg differ diff --git a/src/components/card/card.component.tsx b/src/components/card/card.component.tsx new file mode 100644 index 0000000..8df8f0b --- /dev/null +++ b/src/components/card/card.component.tsx @@ -0,0 +1,11 @@ +import { PropsWithChildren, ReactElement } from "react"; + +import styles from "@/components/card/card.module.css"; + +type Props = PropsWithChildren; + +const CardComponent = ({ children }: Props): ReactElement => { + return
    {children}
    ; +}; + +export default CardComponent; diff --git a/src/components/card/card.module.css b/src/components/card/card.module.css new file mode 100644 index 0000000..d74b286 --- /dev/null +++ b/src/components/card/card.module.css @@ -0,0 +1,7 @@ +.card { + background-color: var(--color-gray-16); + + padding: 1rem; + + border-radius: var(--border-radius); +} diff --git a/src/components/filter-button/filter-button.component.tsx b/src/components/filter-button/filter-button.component.tsx new file mode 100644 index 0000000..13dd89c --- /dev/null +++ b/src/components/filter-button/filter-button.component.tsx @@ -0,0 +1,31 @@ +import { ComponentProps, ReactElement } from "react"; + +import clsx from "clsx"; + +import styles from "@/components/filter-button/filter-button.module.css"; + +type Props = ComponentProps<"button"> & { + isActive?: boolean; +}; + +const FilterButtonComponent = ({ + className, + children, + isActive = false, + ...otherProps +}: Props): ReactElement => { + return ( + + ); +}; + +export default FilterButtonComponent; diff --git a/src/components/filter-button/filter-button.module.css b/src/components/filter-button/filter-button.module.css new file mode 100644 index 0000000..071931e --- /dev/null +++ b/src/components/filter-button/filter-button.module.css @@ -0,0 +1,29 @@ +.filter-button { + background-color: transparent; + color: inherit; + + display: grid; + place-content: center; + + padding: 0.5rem 1rem; + + border: 1px solid currentcolor; + border-radius: var(--border-radius); + + transition: 0.2s ease-in-out; + transition-property: background-color, color; + + cursor: pointer; + + &:hover { + background-color: var(--color-primary-fade); + color: var(--color-primary-opposite); + } + + &.active { + background-color: var(--color-primary); + color: var(--color-primary-opposite); + + border-color: var(--color-primary); + } +} diff --git a/src/constant/header/menu.ts b/src/constant/header/menu.ts index 15c49ef..7706abe 100644 --- a/src/constant/header/menu.ts +++ b/src/constant/header/menu.ts @@ -1,6 +1,5 @@ export const menu = [ { title: "خانه", href: "" }, - { title: "جستجو", href: "search" }, { title: "پزشکان", href: "doctors" }, ]; diff --git a/src/enums/gender.ts b/src/enums/gender.ts new file mode 100644 index 0000000..e5e662c --- /dev/null +++ b/src/enums/gender.ts @@ -0,0 +1,5 @@ +export enum GenderEnums { + MALE = "male", + FEMALE = "female", + BOTH = "both", +} diff --git a/src/icons/MingcuteArrowLeftLine.tsx b/src/icons/MingcuteArrowLeftLine.tsx new file mode 100644 index 0000000..e058112 --- /dev/null +++ b/src/icons/MingcuteArrowLeftLine.tsx @@ -0,0 +1,21 @@ +import { SVGProps } from "react"; + +export function MingcuteArrowLeftLine(props: SVGProps) { + return ( + + + + + + + ); +} diff --git a/src/icons/MingcuteLinkedinFill.tsx b/src/icons/MingcuteLinkedinFill.tsx index 8d883a1..c0fcd0d 100644 --- a/src/icons/MingcuteLinkedinFill.tsx +++ b/src/icons/MingcuteLinkedinFill.tsx @@ -12,7 +12,7 @@ export function MingcuteLinkedinFill(props: SVGProps) { diff --git a/src/icons/MingcuteLocationFill.tsx b/src/icons/MingcuteLocationFill.tsx new file mode 100644 index 0000000..b71f29e --- /dev/null +++ b/src/icons/MingcuteLocationFill.tsx @@ -0,0 +1,21 @@ +import React, { SVGProps } from "react"; + +export function MingcuteLocationFill(props: SVGProps) { + return ( + + + + + + + ); +} diff --git a/src/icons/MingcuteStarFill.tsx b/src/icons/MingcuteStarFill.tsx new file mode 100644 index 0000000..b8ac9ed --- /dev/null +++ b/src/icons/MingcuteStarFill.tsx @@ -0,0 +1,21 @@ +import React, { SVGProps } from "react"; + +export function MingcuteStarFill(props: SVGProps) { + return ( + + + + + + + ); +} diff --git a/src/icons/MingcuteTelegramFill.tsx b/src/icons/MingcuteTelegramFill.tsx index 8c3ab69..f4ef862 100644 --- a/src/icons/MingcuteTelegramFill.tsx +++ b/src/icons/MingcuteTelegramFill.tsx @@ -12,7 +12,7 @@ export function MingcuteTelegramFill(props: SVGProps) { diff --git a/src/icons/MingcuteYoutubeFill.tsx b/src/icons/MingcuteYoutubeFill.tsx index b3c45e0..b7e70c3 100644 --- a/src/icons/MingcuteYoutubeFill.tsx +++ b/src/icons/MingcuteYoutubeFill.tsx @@ -12,7 +12,7 @@ export function MingcuteYoutubeFill(props: SVGProps) { diff --git a/src/models/degrees.ts b/src/models/degrees.ts new file mode 100644 index 0000000..78cdca5 --- /dev/null +++ b/src/models/degrees.ts @@ -0,0 +1,14 @@ +export type specialtyType = { id: string; en: string; fa: string }; +export type specialtyEnType = (typeof degrees)[number]["en"]; + +export const degrees = [ + { id: "degrie-1", en: "expert", fa: "متخصص" }, + + { id: "degrie-2", en: "fellowship", fa: "فلوشیپ" }, + + { id: "degrie-3", en: "specialized-doctorate", fa: "دکترای تخصصی" }, + + { id: "degrie-4", en: "subspecialist", fa: "فوق تخصص" }, + + { id: "degrie-5", en: "PhD", fa: "دکتری" }, +]; diff --git a/src/models/doctors.ts b/src/models/doctors.ts new file mode 100644 index 0000000..6745609 --- /dev/null +++ b/src/models/doctors.ts @@ -0,0 +1,167 @@ +import { DoctorModel } from "@/types/doctor.type"; + +export const doctorsData: DoctorModel[] = [ + { + id: "97420f0d-b576-4f65-9ffc-a81b4b1b4e77", + name: "پوراندخت جعفري", + image: "", + isVerified: true, + averageRating: 4.99, + totalVotes: 294, + address: + "تهران، بین چهارراه بانک و میدان شهدا،ساختمان پزشکان مانا طبقه چهارم", + nextAppointments: [ + { date: "2025-01-07", time: "12:00" }, + { date: "2025-01-08", time: "09:00" }, + ], + brief: "متخصص بیماری‌های داخلی", + badges: ["خوش برخورد", "کمترین معطلی"], + degree: { + en: "expert", + fa: "متخصص", + }, + specialty: { + en: "internist", + fa: "بیماری‌های داخلی", + }, + gender: { + en: "female", + fa: "مرد", + }, + city: { + en: "tehran", + fa: "تهران", + }, + }, + { + id: "e6719f23-e846-4a95-88f9-c013c5d9cb4f", + name: "مجتبی قدسی", + image: "", + isVerified: true, + averageRating: 4.92, + totalVotes: 1487, + address: + "رشت، اول خیابان والی، خیابان شهیدان نوعی اقدم، نرسیده به بیمارستان امام خمینی، جنب داروخانه دکترنصیرپور", + nextAppointments: [ + { date: "2025-01-08", time: "11:00" }, + { date: "2025-01-09", time: "14:00" }, + ], + brief: "فلوشیپ بیماری‌های کودکان", + badges: ["خوش برخورد", "کمترین معطلی"], + degree: { + en: "fellowship", + fa: "فلوشیپ", + }, + specialty: { + en: "pediatric", + fa: "بیماری‌های کودکان", + }, + gender: { + en: "male", + fa: "مرد", + }, + city: { + en: "rasht", + fa: "رشت", + }, + }, + { + id: "4a7403d4-e0a2-406c-8dea-3e557bae54d2", + name: "امیرحسین پورداود", + image: "", + isVerified: true, + averageRating: 5, + totalVotes: 190, + address: "بندرعباس، خیابان ۲۲ بهمن ،جنب بانک مسکن ،ساختمان حکیم ،طبقه سوم", + nextAppointments: [ + { date: "2025-01-08", time: "11:00" }, + { date: "2025-01-09", time: "14:00" }, + ], + brief: "بیماری‌های پوست، زیبایی،درمان هموروئید و فیشر و...", + badges: ["خوش برخورد", "کمترین معطلی"], + degree: { + en: "specialized-doctorate", + fa: "دکترای تخصصی", + }, + specialty: { + en: "dermatologist", + fa: "بیماری‌های پوست", + }, + gender: { + en: "male", + fa: "مرد", + }, + city: { + en: "bandarabas", + fa: "بندرعباس", + }, + }, + { + id: "06d3a495-160d-4722-815e-286ff5d82ed2", + name: "اعظم قهساره اردستانی", + image: "", + isVerified: true, + averageRating: 4.95, + totalVotes: 759, + address: + "زاهدان, شهرک ولی عصر .بیمارستان فوق تخصصی میلاد کلینیک اطفال ونوزادان", + nextAppointments: [ + { date: "2025-01-08", time: "11:00" }, + { date: "2025-01-09", time: "14:00" }, + ], + brief: "فوق تخصص چشم پزشکی", + badges: ["فعال شدن نوبت‌دهی اینترنتی 12 دی 23:59", "خوش برخورد"], + degree: { + en: "subspecialist", + fa: "فوق تخصص", + }, + specialty: { + en: "ophthalmologist", + fa: "چشم پزشکی", + }, + gender: { + en: "female", + fa: "مرد", + }, + city: { + en: "zahedan", + fa: "زاهدان", + }, + }, + { + id: "7f39ff5b-4c81-4c59-80fa-7872b675bb18", + name: "رضا پورعلی", + image: "", + isVerified: true, + averageRating: 4.8259, + totalVotes: 305, + address: + "کلینیک خیام بیمارستان تخصصی و فوق تخصصی حکیم|اردبیل, میدان بسیج، ابتدای جاده باغرود، مرکز اموزشی پژوهشی و درمانی حکیم (درمانگاه طب سنتی ; آدرس: کلینیک امام علی : بلوار جمهوری - بین جمهوری 6و 8)", + nextAppointments: [ + { date: "2025-01-08", time: "11:00" }, + { date: "2025-01-09", time: "14:00" }, + ], + brief: "دکتری بیماری‌های قبل و عروق", + badges: [ + "فعال شدن نوبت‌دهی اینترنتی 13 دی 08:00", + "خوش برخورد", + "کمترین معطلی", + ], + degree: { + en: "PhD", + fa: "دکتری", + }, + specialty: { + en: "cardiovascular-specialist", + fa: "بیماری‌های قبل و عروق", + }, + gender: { + en: "male", + fa: "مرد", + }, + city: { + en: "ardebil", + fa: "اردبیل", + }, + }, +]; diff --git a/src/models/specialties.ts b/src/models/specialties.ts new file mode 100644 index 0000000..dbbf498 --- /dev/null +++ b/src/models/specialties.ts @@ -0,0 +1,18 @@ +export type specialtyType = { id: string; en: string; fa: string }; +export type specialtyEnType = (typeof specialties)[number]["en"]; + +export const specialties: specialtyType[] = [ + { id: "specialty-1", en: "internist", fa: "بیماری‌های داخلی" }, + + { id: "specialty-2", en: "dermatologist", fa: "بیماری‌های پوست" }, + + { id: "specialty-3", en: "ophthalmologist", fa: "چشم پزشکی" }, + + { + id: "specialty-4", + en: "cardiovascular-specialist", + fa: "بیماری‌های قبل و عروق", + }, + + { id: "specialty-5", en: "pediatric", fa: "بیماری‌های کودکان" }, +] as const; diff --git a/src/styles/typography.css b/src/styles/typography.css index 0ca2f2a..5399d0e 100644 --- a/src/styles/typography.css +++ b/src/styles/typography.css @@ -1,11 +1,11 @@ :root { - --fz-100: ; - --fz-200: ; - --fz-300: clamp(0.75rem, 0.6429rem + 0.4762vw, 1rem); - --fz-400: clamp(1rem, 1rem + 0vw, 1rem); - --fz-500: clamp(1.25rem, 1.1429rem + 0.4762vw, 1.5rem); - --fz-600: clamp(1.5rem, 1.2857rem + 0.9524vw, 2rem); - --fz-700: clamp(1.75rem, 1.4286rem + 1.4286vw, 2.5rem); - --fz-800: clamp(2.5rem, 1.8571rem + 2.8571vw, 4rem); - --fz-900: clamp(3rem, 1.7143rem + 5.7143vw, 6rem); + --fz-100: clamp(0.625rem, 0.5714rem + 0.2381vw, 0.75rem); /* 10px to 12px */ + --fz-200: clamp(0.6875rem, 0.6071rem + 0.3571vw, 0.875rem); /* 11px to 14px */ + --fz-300: clamp(0.75rem, 0.6429rem + 0.4762vw, 1rem); /* 12px to 16px */ + --fz-400: clamp(1rem, 1rem + 0vw, 1rem); /* 16px */ + --fz-500: clamp(1.25rem, 1.1429rem + 0.4762vw, 1.5rem); /* 20px to 24px */ + --fz-600: clamp(1.5rem, 1.2857rem + 0.9524vw, 2rem); /* 24px to 32px */ + --fz-700: clamp(1.75rem, 1.4286rem + 1.4286vw, 2.5rem); /* 28px to 40px */ + --fz-800: clamp(2.5rem, 1.8571rem + 2.8571vw, 4rem); /* 40px to 64px */ + --fz-900: clamp(3rem, 1.7143rem + 5.7143vw, 6rem); /* 48px to 96px */ } diff --git a/src/types/doctor.type.ts b/src/types/doctor.type.ts new file mode 100644 index 0000000..2deb582 --- /dev/null +++ b/src/types/doctor.type.ts @@ -0,0 +1,32 @@ +import { GenderEnums } from "@/enums/gender"; + +export interface Localized { + en: T; + fa: string; +} + +export type AcademicDegree = Localized; +export type Specialty = Localized; +export type City = Localized; +export type Gender = Localized<`${GenderEnums}`>; +export interface Appointment { + date: string; + time: string; +} + +export interface DoctorModel { + id: string; + name: string; + image: string; + isVerified: boolean; + averageRating: number; + totalVotes: number; + address: string; + nextAppointments: Appointment[]; + brief: string; + badges: string[]; + degree: AcademicDegree; + specialty: Specialty; + gender: Gender; + city: City; +} diff --git a/src/utils/convertListToObject.ts b/src/utils/convertListToObject.ts new file mode 100644 index 0000000..96282b9 --- /dev/null +++ b/src/utils/convertListToObject.ts @@ -0,0 +1,45 @@ +type ListItems = "city" | "specialty" | "degree" | "gender"; +type SearchParams = { [key: string]: string | string[] | undefined }; +export type ResultObject = Partial>; +type ConvertListToObject = ( + list: (ListItems | string)[], + searchParams: SearchParams, +) => ResultObject; + +const convertListToObject: ConvertListToObject = ( + list, + searchParams, +): ResultObject => { + const result: ResultObject = {}; + let currentKey: ListItems | null = null; + + if (list) { + list.forEach((item) => { + if (["city", "specialty", "degree"].includes(item)) { + currentKey = item as ListItems; + result[currentKey] = ""; + } else if (currentKey) { + result[currentKey] = item; + currentKey = null; + } + }); + } + + const filteredSearchParams: Record = {}; + Object.keys(searchParams).forEach((key) => { + const value = searchParams[key]; + if (typeof value === "string") { + if (key === "city" && result.city !== undefined) { + // Skip adding the city from searchParams if city is already in the list + return; + } + filteredSearchParams[key] = value; + } else if (Array.isArray(value)) { + filteredSearchParams[key] = value.join(","); + } + }); + + return { ...result, ...filteredSearchParams }; +}; + +export default convertListToObject; diff --git a/src/utils/isEmpty.ts b/src/utils/isEmpty.ts new file mode 100644 index 0000000..f7be8a3 --- /dev/null +++ b/src/utils/isEmpty.ts @@ -0,0 +1,3 @@ +export const isEmpty = (obj: Record): boolean => { + return Object.keys(obj).length === 0; +};