feat: landing page implemented

This commit is contained in:
Afra ‌ 2026-06-17 14:02:48 +03:30
parent 0171494734
commit 104053f802
13 changed files with 599 additions and 178 deletions

View file

@ -2,14 +2,16 @@ import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{js,jsx}'],
files: ['**/*.{js,jsx,ts,tsx}'],
extends: [
js.configs.recommended,
...tseslint.configs.recommended,
reactHooks.configs.flat.recommended,
reactRefresh.configs.vite,
],

View file

@ -1,5 +1,5 @@
<!doctype html>
<html lang="en">
<html lang="fa" dir="rtl">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />

View file

@ -11,25 +11,27 @@
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.9.0",
"axios": "^1.6.6",
"react": "^19.2.6",
"react-dom": "^19.2.6",
"@tanstack/react-query": "^5.9.0"
"react-dom": "^19.2.6"
},
"devDependencies": {
"@eslint/js": "^10.0.1",
"@tailwindcss/vite": "^4.3.1",
"@types/node": "^20.5.1",
"@types/react": "^19.2.14",
"@types/react-dom": "^19.2.3",
"@vitejs/plugin-react": "^6.0.1",
"autoprefixer": "^10.4.14",
"postcss": "^8.4.26",
"tailwindcss": "^4.3.1",
"typescript": "^5.6.2",
"eslint": "^10.3.0",
"eslint-plugin-react-hooks": "^7.1.1",
"eslint-plugin-react-refresh": "^0.5.2",
"globals": "^17.6.0",
"postcss": "^8.4.26",
"tailwindcss": "^4.3.1",
"typescript": "^5.6.2",
"typescript-eslint": "^8.61.1",
"vite": "^8.0.12"
}
}

433
pnpm-lock.yaml generated
View file

@ -23,7 +23,10 @@ importers:
devDependencies:
'@eslint/js':
specifier: ^10.0.1
version: 10.0.1(eslint@10.5.0)
version: 10.0.1(eslint@10.5.0(jiti@2.7.0))
'@tailwindcss/vite':
specifier: ^4.3.1
version: 4.3.1(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))
'@types/node':
specifier: ^20.5.1
version: 20.19.43
@ -35,19 +38,19 @@ importers:
version: 19.2.3(@types/react@19.2.17)
'@vitejs/plugin-react':
specifier: ^6.0.1
version: 6.0.2(vite@8.0.16(@types/node@20.19.43))
version: 6.0.2(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))
autoprefixer:
specifier: ^10.4.14
version: 10.5.0(postcss@8.5.15)
eslint:
specifier: ^10.3.0
version: 10.5.0
version: 10.5.0(jiti@2.7.0)
eslint-plugin-react-hooks:
specifier: ^7.1.1
version: 7.1.1(eslint@10.5.0)
version: 7.1.1(eslint@10.5.0(jiti@2.7.0))
eslint-plugin-react-refresh:
specifier: ^0.5.2
version: 0.5.2(eslint@10.5.0)
version: 0.5.2(eslint@10.5.0(jiti@2.7.0))
globals:
specifier: ^17.6.0
version: 17.6.0
@ -60,9 +63,12 @@ importers:
typescript:
specifier: ^5.6.2
version: 5.9.3
typescript-eslint:
specifier: ^8.61.1
version: 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
vite:
specifier: ^8.0.12
version: 8.0.16(@types/node@20.19.43)
version: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)
packages:
@ -324,6 +330,100 @@ packages:
'@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@tailwindcss/node@4.3.1':
resolution: {integrity: sha512-6NDaqRoAMSXD1mr/RXu0HBvNE9a2n5tHPsxu9XHLws8o4Twes5rBM2205SUUiJ9goAtadrN6xTGX0UDEwp/N4A==}
'@tailwindcss/oxide-android-arm64@4.3.1':
resolution: {integrity: sha512-SVlyf61g374l5cHyg8x9kf5xmLcOaxvOTsbsqDnSsDJaKOEFZ7GCvi84VAVGpxojYOs1+3K6M0UjXfqPU8vmOQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [android]
'@tailwindcss/oxide-darwin-arm64@4.3.1':
resolution: {integrity: sha512-hVnWLwv+e/l7c4WKyVtHVrIPvYdqWHjRB3MDIqARynzFtnQg85kmQEFCbV9Ja0VVx4xXTIiDWY60Y7iz/iNoDA==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [darwin]
'@tailwindcss/oxide-darwin-x64@4.3.1':
resolution: {integrity: sha512-Cf7abu0WVgbhU7ANgPUnSAvm7nCvMweusHb8FnaHlLfv/Caq4GYaEZg7ZImzzmjx4lIAfuS8q+eLIS7A7IzxIg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [darwin]
'@tailwindcss/oxide-freebsd-x64@4.3.1':
resolution: {integrity: sha512-ZZqzX2Y+GXtXXfqSfpJhDm60OoZfvLHLCgm+J7NVqgHHJjG/m9ugZI77RwTsVd4fnBJuCFP6Ae6kTJb71UdS8g==}
engines: {node: '>= 20'}
cpu: [x64]
os: [freebsd]
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1':
resolution: {integrity: sha512-/Ah/xik0LaMYfv9DZ0S/t4pBlBNYOcqtRwusjgovHkvT8ixueWCLyJjsaF5kQIckjb4IT8Q6K6p/iPmZMixYgg==}
engines: {node: '>= 20'}
cpu: [arm]
os: [linux]
'@tailwindcss/oxide-linux-arm64-gnu@4.3.1':
resolution: {integrity: sha512-gqdFoVJlw444GvpnheZLHmvTzSxI/cOUUh2KSNejQjTcYkW062SVD+En0rUgD+QV91bz1XGIGtt1HJd48xUGbQ==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-arm64-musl@4.3.1':
resolution: {integrity: sha512-Bwv9KwOvE0VKa86xPFif9b9c3Y1NxOV1P0gLti/IYaWEsQYZXDlxfGEtA8mdDZ7SG3wyNXAWYT5SIn3giL57oA==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-linux-x64-gnu@4.3.1':
resolution: {integrity: sha512-Ymi8O8T15HYQdOUWUtTI6ldN0neHP85FC+Qz32xTcZ7iJXtem/x8ITev0o1e9e5rkqj4lONZfTRLvkmin1+tKg==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@tailwindcss/oxide-linux-x64-musl@4.3.1':
resolution: {integrity: sha512-M+P/91qJ6uILLw4k2G93GMDRAXj61SMvFQYt39AqvUqYgExXpLL5aepfns7sj4HiAQeolirQF9E0lzRvdf4zPQ==}
engines: {node: '>= 20'}
cpu: [x64]
os: [linux]
libc: [musl]
'@tailwindcss/oxide-wasm32-wasi@4.3.1':
resolution: {integrity: sha512-zsM8uOeqvVGHsAXsJxsT28ttosFahLJKCLOTUBqRAtKnVgGSRitds9T432QiT8b77Yga7JIBkulIRRlJPtYhRA==}
engines: {node: '>=14.0.0'}
cpu: [wasm32]
bundledDependencies:
- '@napi-rs/wasm-runtime'
- '@emnapi/core'
- '@emnapi/runtime'
- '@tybys/wasm-util'
- '@emnapi/wasi-threads'
- tslib
'@tailwindcss/oxide-win32-arm64-msvc@4.3.1':
resolution: {integrity: sha512-aiNvSq9BsVk8V513lDKlrCFAgf8qBMPZTpgEhInL+NwQqs97mYmupVMrPrgBBSL8Pv/0zXu9MrMF9rMun1ZeNg==}
engines: {node: '>= 20'}
cpu: [arm64]
os: [win32]
'@tailwindcss/oxide-win32-x64-msvc@4.3.1':
resolution: {integrity: sha512-xDEyu1rg290472FEGaKHnzyDyh5QH+AlWvsU5hMoMtPpzmKlRI0jaYKCgSHDYtaQWZOYbMaduSyCwFwY4n1HmA==}
engines: {node: '>= 20'}
cpu: [x64]
os: [win32]
'@tailwindcss/oxide@4.3.1':
resolution: {integrity: sha512-yVPyo8RNkabVr3O2EhHEE0Rewu7YKzc1DhIqfL46LKveFrmu9XbDazNOJY7/GRuvw1h6u3utWnR29H/p5JPlgA==}
engines: {node: '>= 20'}
'@tailwindcss/vite@4.3.1':
resolution: {integrity: sha512-hItDHuIIlEV61R+faXu66s1K36aTurO/Qw0e45Vskz57gXl9pWOT6eg3zmcEui6CZXddbN7zd41bwmvag4JGwQ==}
peerDependencies:
vite: ^5.2.0 || ^6 || ^7 || ^8
'@tanstack/query-core@5.101.0':
resolution: {integrity: sha512-cQetA74EB+seWySv1TTKr828TnP0u39m6LykwDXIo84SNortpDkp30TMEjkqtYCNP9c40uT/iwl6MLiufEt0Ow==}
@ -355,6 +455,65 @@ packages:
'@types/react@19.2.17':
resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==}
'@typescript-eslint/eslint-plugin@8.61.1':
resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
'@typescript-eslint/parser': ^8.61.1
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/parser@8.61.1':
resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/project-service@8.61.1':
resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/scope-manager@8.61.1':
resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/tsconfig-utils@8.61.1':
resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/type-utils@8.61.1':
resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/types@8.61.1':
resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@typescript-eslint/typescript-estree@8.61.1':
resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/utils@8.61.1':
resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
'@typescript-eslint/visitor-keys@8.61.1':
resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
'@vitejs/plugin-react@6.0.2':
resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==}
engines: {node: ^20.19.0 || >=22.12.0}
@ -464,6 +623,10 @@ packages:
electron-to-chromium@1.5.372:
resolution: {integrity: sha512-M3yhbAlilnwqC8D21t28UCDGHyitShTmmLRU/H+b74P6Ski16Nb9HONYEaVpMj/pwC7BEo5B95FpjODLCWbtfA==}
enhanced-resolve@5.21.6:
resolution: {integrity: sha512-aNnGCvbJ/RIyWo1IuhNdVjnNF+EjH9wpzpNHt+ci/m9He9LJvUN8wrCcXjp9cWsGNAuvSpVFTx/vraAFQ8qGjQ==}
engines: {node: '>=10.13.0'}
es-define-property@1.0.1:
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
engines: {node: '>= 0.4'}
@ -622,6 +785,9 @@ packages:
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
engines: {node: '>= 0.4'}
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
has-symbols@1.1.0:
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
engines: {node: '>= 0.4'}
@ -648,6 +814,10 @@ packages:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'}
ignore@7.0.5:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==}
engines: {node: '>= 4'}
imurmurhash@0.1.4:
resolution: {integrity: sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==}
engines: {node: '>=0.8.19'}
@ -663,6 +833,10 @@ packages:
isexe@2.0.0:
resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==}
jiti@2.7.0:
resolution: {integrity: sha512-AC/7JofJvZGrrneWNaEnJeOLUx+JlGt7tNa0wZiRPT4MY1wmfKjt2+6O2p2uz2+skll8OZZmJMNqeke7kKbNgQ==}
hasBin: true
js-tokens@4.0.0:
resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==}
@ -773,6 +947,9 @@ packages:
lru-cache@5.1.1:
resolution: {integrity: sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==}
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@ -871,6 +1048,11 @@ packages:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
semver@7.8.4:
resolution: {integrity: sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==}
engines: {node: '>=10'}
hasBin: true
shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
engines: {node: '>=8'}
@ -886,10 +1068,20 @@ packages:
tailwindcss@4.3.1:
resolution: {integrity: sha512-hk+TB1m+K8CYNrP6rjQaq/Y+4Zylwpa87mLYBKCunwnnQ9p+fHb7kmSfGqyEJoxF/O6CDyABWVFEafNSYKll+Q==}
tapable@2.3.3:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'}
tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'}
ts-api-utils@2.5.0:
resolution: {integrity: sha512-OJ/ibxhPlqrMM0UiNHJ/0CKQkoKF243/AEmplt3qpRgkW8VG7IfOS41h7V8TjITqdByHzrjcS/2si+y4lIh8NA==}
engines: {node: '>=18.12'}
peerDependencies:
typescript: '>=4.8.4'
tslib@2.8.1:
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
@ -897,6 +1089,13 @@ packages:
resolution: {integrity: sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==}
engines: {node: '>= 0.8.0'}
typescript-eslint@8.61.1:
resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies:
eslint: ^8.57.0 || ^9.0.0 || ^10.0.0
typescript: '>=4.8.4 <6.1.0'
typescript@5.9.3:
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
engines: {node: '>=14.17'}
@ -1100,9 +1299,9 @@ snapshots:
tslib: 2.8.1
optional: true
'@eslint-community/eslint-utils@4.9.1(eslint@10.5.0)':
'@eslint-community/eslint-utils@4.9.1(eslint@10.5.0(jiti@2.7.0))':
dependencies:
eslint: 10.5.0
eslint: 10.5.0(jiti@2.7.0)
eslint-visitor-keys: 3.4.3
'@eslint-community/regexpp@4.12.2': {}
@ -1123,9 +1322,9 @@ snapshots:
dependencies:
'@types/json-schema': 7.0.15
'@eslint/js@10.0.1(eslint@10.5.0)':
'@eslint/js@10.0.1(eslint@10.5.0(jiti@2.7.0))':
optionalDependencies:
eslint: 10.5.0
eslint: 10.5.0(jiti@2.7.0)
'@eslint/object-schema@3.0.5': {}
@ -1229,6 +1428,74 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {}
'@tailwindcss/node@4.3.1':
dependencies:
'@jridgewell/remapping': 2.3.5
enhanced-resolve: 5.21.6
jiti: 2.7.0
lightningcss: 1.32.0
magic-string: 0.30.21
source-map-js: 1.2.1
tailwindcss: 4.3.1
'@tailwindcss/oxide-android-arm64@4.3.1':
optional: true
'@tailwindcss/oxide-darwin-arm64@4.3.1':
optional: true
'@tailwindcss/oxide-darwin-x64@4.3.1':
optional: true
'@tailwindcss/oxide-freebsd-x64@4.3.1':
optional: true
'@tailwindcss/oxide-linux-arm-gnueabihf@4.3.1':
optional: true
'@tailwindcss/oxide-linux-arm64-gnu@4.3.1':
optional: true
'@tailwindcss/oxide-linux-arm64-musl@4.3.1':
optional: true
'@tailwindcss/oxide-linux-x64-gnu@4.3.1':
optional: true
'@tailwindcss/oxide-linux-x64-musl@4.3.1':
optional: true
'@tailwindcss/oxide-wasm32-wasi@4.3.1':
optional: true
'@tailwindcss/oxide-win32-arm64-msvc@4.3.1':
optional: true
'@tailwindcss/oxide-win32-x64-msvc@4.3.1':
optional: true
'@tailwindcss/oxide@4.3.1':
optionalDependencies:
'@tailwindcss/oxide-android-arm64': 4.3.1
'@tailwindcss/oxide-darwin-arm64': 4.3.1
'@tailwindcss/oxide-darwin-x64': 4.3.1
'@tailwindcss/oxide-freebsd-x64': 4.3.1
'@tailwindcss/oxide-linux-arm-gnueabihf': 4.3.1
'@tailwindcss/oxide-linux-arm64-gnu': 4.3.1
'@tailwindcss/oxide-linux-arm64-musl': 4.3.1
'@tailwindcss/oxide-linux-x64-gnu': 4.3.1
'@tailwindcss/oxide-linux-x64-musl': 4.3.1
'@tailwindcss/oxide-wasm32-wasi': 4.3.1
'@tailwindcss/oxide-win32-arm64-msvc': 4.3.1
'@tailwindcss/oxide-win32-x64-msvc': 4.3.1
'@tailwindcss/vite@4.3.1(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))':
dependencies:
'@tailwindcss/node': 4.3.1
'@tailwindcss/oxide': 4.3.1
tailwindcss: 4.3.1
vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)
'@tanstack/query-core@5.101.0': {}
'@tanstack/react-query@5.101.0(react@19.2.7)':
@ -1259,10 +1526,101 @@ snapshots:
dependencies:
csstype: 3.2.3
'@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@20.19.43))':
'@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
'@eslint-community/regexpp': 4.12.2
'@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
'@typescript-eslint/scope-manager': 8.61.1
'@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.61.1
eslint: 10.5.0(jiti@2.7.0)
ignore: 7.0.5
natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/scope-manager': 8.61.1
'@typescript-eslint/types': 8.61.1
'@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
'@typescript-eslint/visitor-keys': 8.61.1
debug: 4.4.3
eslint: 10.5.0(jiti@2.7.0)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/project-service@8.61.1(typescript@5.9.3)':
dependencies:
'@typescript-eslint/tsconfig-utils': 8.61.1(typescript@5.9.3)
'@typescript-eslint/types': 8.61.1
debug: 4.4.3
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/scope-manager@8.61.1':
dependencies:
'@typescript-eslint/types': 8.61.1
'@typescript-eslint/visitor-keys': 8.61.1
'@typescript-eslint/tsconfig-utils@8.61.1(typescript@5.9.3)':
dependencies:
typescript: 5.9.3
'@typescript-eslint/type-utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
'@typescript-eslint/types': 8.61.1
'@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
debug: 4.4.3
eslint: 10.5.0(jiti@2.7.0)
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/types@8.61.1': {}
'@typescript-eslint/typescript-estree@8.61.1(typescript@5.9.3)':
dependencies:
'@typescript-eslint/project-service': 8.61.1(typescript@5.9.3)
'@typescript-eslint/tsconfig-utils': 8.61.1(typescript@5.9.3)
'@typescript-eslint/types': 8.61.1
'@typescript-eslint/visitor-keys': 8.61.1
debug: 4.4.3
minimatch: 10.2.5
semver: 7.8.4
tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@5.9.3)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/utils@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)':
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
'@typescript-eslint/scope-manager': 8.61.1
'@typescript-eslint/types': 8.61.1
'@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
eslint: 10.5.0(jiti@2.7.0)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
'@typescript-eslint/visitor-keys@8.61.1':
dependencies:
'@typescript-eslint/types': 8.61.1
eslint-visitor-keys: 5.0.1
'@vitejs/plugin-react@6.0.2(vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0))':
dependencies:
'@rolldown/pluginutils': 1.0.1
vite: 8.0.16(@types/node@20.19.43)
vite: 8.0.16(@types/node@20.19.43)(jiti@2.7.0)
acorn-jsx@5.3.2(acorn@8.17.0):
dependencies:
@ -1359,6 +1717,11 @@ snapshots:
electron-to-chromium@1.5.372: {}
enhanced-resolve@5.21.6:
dependencies:
graceful-fs: 4.2.11
tapable: 2.3.3
es-define-property@1.0.1: {}
es-errors@1.3.0: {}
@ -1378,20 +1741,20 @@ snapshots:
escape-string-regexp@4.0.0: {}
eslint-plugin-react-hooks@7.1.1(eslint@10.5.0):
eslint-plugin-react-hooks@7.1.1(eslint@10.5.0(jiti@2.7.0)):
dependencies:
'@babel/core': 7.29.7
'@babel/parser': 7.29.7
eslint: 10.5.0
eslint: 10.5.0(jiti@2.7.0)
hermes-parser: 0.25.1
zod: 4.4.3
zod-validation-error: 4.0.2(zod@4.4.3)
transitivePeerDependencies:
- supports-color
eslint-plugin-react-refresh@0.5.2(eslint@10.5.0):
eslint-plugin-react-refresh@0.5.2(eslint@10.5.0(jiti@2.7.0)):
dependencies:
eslint: 10.5.0
eslint: 10.5.0(jiti@2.7.0)
eslint-scope@9.1.2:
dependencies:
@ -1404,9 +1767,9 @@ snapshots:
eslint-visitor-keys@5.0.1: {}
eslint@10.5.0:
eslint@10.5.0(jiti@2.7.0):
dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0)
'@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0(jiti@2.7.0))
'@eslint-community/regexpp': 4.12.2
'@eslint/config-array': 0.23.5
'@eslint/config-helpers': 0.6.0
@ -1436,6 +1799,8 @@ snapshots:
minimatch: 10.2.5
natural-compare: 1.4.0
optionator: 0.9.4
optionalDependencies:
jiti: 2.7.0
transitivePeerDependencies:
- supports-color
@ -1528,6 +1893,8 @@ snapshots:
gopd@1.2.0: {}
graceful-fs@4.2.11: {}
has-symbols@1.1.0: {}
has-tostringtag@1.0.2:
@ -1553,6 +1920,8 @@ snapshots:
ignore@5.3.2: {}
ignore@7.0.5: {}
imurmurhash@0.1.4: {}
is-extglob@2.1.1: {}
@ -1563,6 +1932,8 @@ snapshots:
isexe@2.0.0: {}
jiti@2.7.0: {}
js-tokens@4.0.0: {}
jsesc@3.1.0: {}
@ -1641,6 +2012,10 @@ snapshots:
dependencies:
yallist: 3.1.1
magic-string@0.30.21:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
math-intrinsics@1.1.0: {}
mime-db@1.52.0: {}
@ -1732,6 +2107,8 @@ snapshots:
semver@6.3.1: {}
semver@7.8.4: {}
shebang-command@2.0.0:
dependencies:
shebang-regex: 3.0.0
@ -1742,11 +2119,17 @@ snapshots:
tailwindcss@4.3.1: {}
tapable@2.3.3: {}
tinyglobby@0.2.17:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
ts-api-utils@2.5.0(typescript@5.9.3):
dependencies:
typescript: 5.9.3
tslib@2.8.1:
optional: true
@ -1754,6 +2137,17 @@ snapshots:
dependencies:
prelude-ls: 1.2.1
typescript-eslint@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3):
dependencies:
'@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3))(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
'@typescript-eslint/parser': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
'@typescript-eslint/typescript-estree': 8.61.1(typescript@5.9.3)
'@typescript-eslint/utils': 8.61.1(eslint@10.5.0(jiti@2.7.0))(typescript@5.9.3)
eslint: 10.5.0(jiti@2.7.0)
typescript: 5.9.3
transitivePeerDependencies:
- supports-color
typescript@5.9.3: {}
undici-types@6.21.0: {}
@ -1768,7 +2162,7 @@ snapshots:
dependencies:
punycode: 2.3.1
vite@8.0.16(@types/node@20.19.43):
vite@8.0.16(@types/node@20.19.43)(jiti@2.7.0):
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
@ -1778,6 +2172,7 @@ snapshots:
optionalDependencies:
'@types/node': 20.19.43
fsevents: 2.3.3
jiti: 2.7.0
which@2.0.2:
dependencies:

5
src/assets/map.svg Normal file
View file

@ -0,0 +1,5 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1.52734 5.186V11.6727C1.52734 12.9393 2.42734 13.4593 3.52068 12.8327L5.08734 11.9393C5.42734 11.746 5.99401 11.726 6.34734 11.906L9.84734 13.6593C10.2007 13.8327 10.7673 13.8193 11.1073 13.626L13.994 11.9727C14.3607 11.7593 14.6673 11.2393 14.6673 10.8127V4.326C14.6673 3.05933 13.7673 2.53933 12.674 3.166L11.1073 4.05933C10.7673 4.25267 10.2007 4.27267 9.84734 4.09267L6.34734 2.346C5.99401 2.17267 5.42734 2.186 5.08734 2.37933L2.20068 4.03267C1.82734 4.246 1.52734 4.766 1.52734 5.186Z" stroke="#223D5D" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M5.70703 2.66602V11.3327" stroke="#223D5D" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M10.4863 4.41406V13.3341" stroke="#223D5D" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 933 B

8
src/assets/task.svg Normal file
View file

@ -0,0 +1,8 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M7.33398 13H14.0007" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.33398 8.33398H14.0007" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M7.33398 3.66602H14.0007" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 3.66732L2.66667 4.33398L4.66667 2.33398" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 8.33333L2.66667 9L4.66667 7" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 12.9993L2.66667 13.666L4.66667 11.666" stroke="white" stroke-width="1.25" stroke-linecap="round" stroke-linejoin="round"/>
</svg>

After

Width:  |  Height:  |  Size: 852 B

View file

@ -2,8 +2,8 @@ import React from 'react'
type State = { hasError: boolean; error?: Error }
export default class ErrorBoundary extends React.Component<React.PropsWithChildren<{}>, State> {
constructor(props: {}) {
export default class ErrorBoundary extends React.Component<React.PropsWithChildren, State> {
constructor(props: React.PropsWithChildren) {
super(props)
this.state = { hasError: false }
}
@ -14,7 +14,6 @@ export default class ErrorBoundary extends React.Component<React.PropsWithChildr
componentDidCatch(error: Error, info: unknown) {
// send to remote logging if desired
// eslint-disable-next-line no-console
console.error('Uncaught error in component tree:', error, info)
}

View file

@ -1,16 +1,6 @@
import React, { createContext, useCallback, useEffect, useRef, useState, ReactNode } from "react";
import { useCallback, useEffect, useRef, useState, ReactNode } from "react";
import { Location } from "../types/location";
type LocationError = "permission_denied" | "location_error" | null;
type LocationContextValue = {
location: Location | null;
loading: boolean;
error: LocationError;
requestLocation: () => void;
};
export const LocationContext = createContext<LocationContextValue | undefined>(undefined);
import { LocationContext, LocationError } from "./LocationContextValue";
export const LocationProvider = ({ children }: { children: ReactNode }) => {
const [location, setLocation] = useState<Location | null>(null);
@ -27,8 +17,6 @@ export const LocationProvider = ({ children }: { children: ReactNode }) => {
setLocation(loc);
setError(null);
setLoading(false);
// Log once received
// eslint-disable-next-line no-console
console.info("User location obtained", loc);
};
@ -37,11 +25,9 @@ export const LocationProvider = ({ children }: { children: ReactNode }) => {
setLoading(false);
if (err.code === err.PERMISSION_DENIED) {
setError("permission_denied");
// eslint-disable-next-line no-console
console.warn("Geolocation permission denied", err.message);
} else {
setError("location_error");
// eslint-disable-next-line no-console
console.warn("Geolocation error", err.message);
}
};
@ -74,26 +60,29 @@ export const LocationProvider = ({ children }: { children: ReactNode }) => {
const requestLocation = useCallback(() => {
// If Permissions API exists, read the state first to provide immediate feedback
if ((navigator as any).permissions && typeof (navigator as any).permissions.query === 'function') {
if (navigator.permissions && typeof navigator.permissions.query === "function") {
try {
;(navigator as any).permissions.query({ name: 'geolocation' }).then((status: PermissionStatus) => {
if (status.state === 'granted') {
// permission already granted
navigator.permissions
.query({ name: "geolocation" })
.then((status) => {
if (status.state === "granted") {
// permission already granted
performGetPosition();
} else if (status.state === "denied") {
// permission denied
setLoading(false);
setError("permission_denied");
setLocation(null);
} else {
// prompt
performGetPosition();
}
})
.catch(() => {
// fallback to direct request
performGetPosition();
} else if (status.state === 'denied') {
// permission denied
setLoading(false);
setError('permission_denied');
setLocation(null);
} else {
// prompt
performGetPosition();
}
}).catch(() => {
// fallback to direct request
performGetPosition();
});
} catch (e) {
});
} catch {
performGetPosition();
}
} else {
@ -103,9 +92,8 @@ export const LocationProvider = ({ children }: { children: ReactNode }) => {
// Automatically request location on provider mount
useEffect(() => {
requestLocation();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);
queueMicrotask(requestLocation);
}, [requestLocation]);
return (
<LocationContext.Provider value={{ location, loading, error, requestLocation }}>

View file

@ -0,0 +1,13 @@
import { createContext } from "react";
import { Location } from "../types/location";
export type LocationError = "permission_denied" | "location_error" | null;
export type LocationContextValue = {
location: Location | null;
loading: boolean;
error: LocationError;
requestLocation: () => void;
};
export const LocationContext = createContext<LocationContextValue | undefined>(undefined);

View file

@ -1,5 +1,5 @@
import { useContext } from 'react'
import { LocationContext } from '../context/LocationContext'
import { LocationContext } from '../context/LocationContextValue'
export function useUserLocation() {
const ctx = useContext(LocationContext)

View file

@ -1,111 +1,18 @@
:root {
--text: #6b6375;
--text-h: #08060d;
--bg: #fff;
--border: #e5e4e7;
--code-bg: #f4f3ec;
--accent: #aa3bff;
--accent-bg: rgba(170, 59, 255, 0.1);
--accent-border: rgba(170, 59, 255, 0.5);
--social-bg: rgba(244, 243, 236, 0.5);
--shadow:
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
@import url('https://fonts.googleapis.com/css2?family=Vazirmatn:wght@400;500;600;700&display=swap');
@import 'tailwindcss';
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
--mono: ui-monospace, Consolas, monospace;
font: 18px/145% var(--sans);
letter-spacing: 0.18px;
color-scheme: light dark;
color: var(--text);
background: var(--bg);
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
@media (max-width: 1024px) {
font-size: 16px;
}
@theme {
--font-sans: 'Vazirmatn', system-ui, 'Segoe UI', Roboto, sans-serif;
}
@media (prefers-color-scheme: dark) {
:root {
--text: #9ca3af;
--text-h: #f3f4f6;
--bg: #16171d;
--border: #2e303a;
--code-bg: #1f2028;
--accent: #c084fc;
--accent-bg: rgba(192, 132, 252, 0.15);
--accent-border: rgba(192, 132, 252, 0.5);
--social-bg: rgba(47, 48, 58, 0.5);
--shadow:
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
}
#social .button-icon {
filter: invert(1) brightness(2);
}
html,
body,
#root {
height: 100%;
}
body {
margin: 0;
}
#root {
width: 1126px;
max-width: 100%;
margin: 0 auto;
text-align: center;
border-inline: 1px solid var(--border);
min-height: 100svh;
display: flex;
flex-direction: column;
box-sizing: border-box;
}
h1,
h2 {
font-family: var(--heading);
font-weight: 500;
color: var(--text-h);
}
h1 {
font-size: 56px;
letter-spacing: -1.68px;
margin: 32px 0;
@media (max-width: 1024px) {
font-size: 36px;
margin: 20px 0;
}
}
h2 {
font-size: 24px;
line-height: 118%;
letter-spacing: -0.24px;
margin: 0 0 8px;
@media (max-width: 1024px) {
font-size: 20px;
}
}
p {
margin: 0;
}
code,
.counter {
font-family: var(--mono);
display: inline-flex;
border-radius: 4px;
color: var(--text-h);
}
code {
font-size: 15px;
line-height: 135%;
padding: 4px 8px;
background: var(--code-bg);
font-family: var(--font-sans);
color-scheme: light;
}

View file

@ -1,12 +1,113 @@
import React from 'react'
import LocationStatus from '../components/LocationStatus'
import { useState } from 'react'
export default function Home() {
type Cafe = {
id: number
name: string
seats: number
image: string | null
}
const mockCafes: Cafe[] = [
{ id: 1, name: 'کافه بی‌بای', seats: 8, image: null },
{ id: 2, name: 'کافه بی‌بای', seats: 10, image: null },
{ id: 3, name: 'کافه ستاره', seats: 15, image: null },
]
function ChevronDownIcon() {
return (
<div className="min-h-screen flex items-center justify-center">
<div className="w-full max-w-sm p-4">
<LocationStatus />
</div>
<svg className="w-5 h-5 text-gray-400 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
<path strokeLinecap="round" strokeLinejoin="round" d="M6 9l6 6 6-6" />
</svg>
)
}
function MapPlaceholder() {
return (
<div className="w-full h-[35vh] bg-gray-200 flex items-center justify-center text-gray-500">
نقشه
</div>
)
}
function SelectorRow({ label, value }: { label: string; value: string }) {
return (
<div className="px-4 mt-4">
<p className="text-sm text-gray-500 mb-1">{label}</p>
<button
type="button"
className="w-full flex items-center justify-between gap-2 rounded-xl border border-gray-200 bg-white px-4 py-3"
>
<span className="text-gray-900">{value}</span>
<ChevronDownIcon />
</button>
</div>
)
}
function CafeCard({ cafe }: { cafe: Cafe }) {
return (
<div className="flex items-center justify-between gap-3 rounded-2xl bg-white p-3 shadow-sm">
<div className="flex flex-col items-start gap-2">
<h3 className="font-semibold text-gray-900">{cafe.name}</h3>
<span className="rounded-full bg-gray-100 px-3 py-1 text-sm text-gray-600">{cafe.seats} صندلی</span>
</div>
<div className="h-24 w-28 shrink-0 rounded-xl bg-gray-200" />
</div>
)
}
export default function Home() {
const [view, setView] = useState<'list' | 'map'>('list')
return (
<div className="min-h-screen bg-white pb-24">
<MapPlaceholder />
<SelectorRow label="شهر" value="تهران" />
<SelectorRow label="بازی انتخابی" value="برزیل-مراکش (۳ روز دیگر-۲:۰۰)" />
<div className="mt-6 flex items-center justify-between px-4">
<h2 className="font-semibold text-gray-900">کافه‌ها</h2>
<div className="flex rounded-full bg-gray-100 p-1">
<button
type="button"
onClick={() => setView('map')}
className={`flex items-center gap-1 rounded-full px-3 py-1.5 text-sm ${
view === 'map' ? 'bg-[#1e2d4a] text-white' : 'text-gray-600'
}`}
>
نقشه
</button>
<button
type="button"
onClick={() => setView('list')}
className={`flex items-center gap-1 rounded-full px-3 py-1.5 text-sm ${
view === 'list' ? 'bg-[#1e2d4a] text-white' : 'text-gray-600'
}`}
>
لیست
</button>
</div>
</div>
<div className="mt-3 px-4">
{view === 'list' ? (
<div className="space-y-3">
{mockCafes.map((cafe) => (
<CafeCard key={cafe.id} cafe={cafe} />
))}
</div>
) : (
<MapPlaceholder />
)}
</div>
<button
type="button"
className="fixed bottom-4 right-4 rounded-full bg-[#1e2d4a] px-5 py-3 text-white shadow-lg"
>
پشتیبانی
</button>
</div>
)
}

View file

@ -1,7 +1,8 @@
import { defineConfig } from 'vite'
import react from '@vitejs/plugin-react'
import tailwindcss from '@tailwindcss/vite'
// https://vite.dev/config/
export default defineConfig({
plugins: [react()],
plugins: [react(), tailwindcss()],
})