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.brief}
+
+
+
+
+ {item.averageRating}
+ ({item.totalVotes} نظر)
+
+
+
+
+ {item.badges.map((item, index) => (
+
+ {item}
+
+ ))}
+
+
+
+
+
+ );
+};
+
+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 (
+
+ {doctors.map((item: DoctorModel) => (
+
+
+
+ ))}
+
+ );
+};
+
+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;
+};