diff --git a/src/components/PrimaryButton.css b/src/components/PrimaryButton.css
index b5ff70c..9d2c0be 100644
--- a/src/components/PrimaryButton.css
+++ b/src/components/PrimaryButton.css
@@ -2,17 +2,26 @@
--btn-fill: var(--color-navy);
position: relative;
display: inline-flex;
- min-width: 270px;
+ min-width: 220px;
align-items: center;
justify-content: space-between;
overflow: hidden;
+ padding: 0;
border: 1px solid var(--color-navy);
border-radius: var(--radius);
background: var(--color-white);
color: var(--color-navy);
+ font: inherit;
+ font-size: 0.92rem;
+ cursor: pointer;
transition: color 0.35s ease;
}
+button.primary-btn:disabled {
+ opacity: 0.7;
+ cursor: wait;
+}
+
.primary-btn--dark {
--btn-fill: #000;
border-color: #000;
@@ -30,19 +39,22 @@
}
.primary-btn__label {
- padding: 0.7rem 1rem;
+ padding: 0.55rem 0.9rem;
font-weight: 500;
}
.primary-btn__icon {
display: grid;
place-items: center;
- width: 52px;
- height: 45px;
+ align-self: stretch;
+ width: 44px;
+ min-height: 38px;
background: var(--btn-fill);
}
.primary-btn__icon img {
+ width: 15px;
+ height: 15px;
filter: brightness(0) invert(1);
}
diff --git a/src/components/PrimaryButton.tsx b/src/components/PrimaryButton.tsx
index 93706f1..82060ec 100644
--- a/src/components/PrimaryButton.tsx
+++ b/src/components/PrimaryButton.tsx
@@ -2,16 +2,31 @@ import { Link } from 'react-router-dom'
import arrowLeft from '../assets/images/icons/arrow-left.svg'
import './PrimaryButton.css'
-type Props = {
- to: string
+type SharedProps = {
title?: string
variant?: 'default' | 'dark'
full?: boolean
- download?: boolean
iconSrc?: string
className?: string
+ disabled?: boolean
}
+type LinkProps = SharedProps & {
+ to: string
+ download?: boolean
+ type?: never
+ onClick?: never
+}
+
+type ButtonProps = SharedProps & {
+ to?: undefined
+ download?: never
+ type?: 'button' | 'submit' | 'reset'
+ onClick?: () => void
+}
+
+type Props = LinkProps | ButtonProps
+
export default function PrimaryButton({
to,
title = 'دوست دارم بیشتر بدونم',
@@ -20,6 +35,9 @@ export default function PrimaryButton({
download = false,
iconSrc = arrowLeft,
className = '',
+ type = 'button',
+ disabled = false,
+ onClick,
}: Props) {
const classes = [
'primary-btn',
@@ -30,6 +48,23 @@ export default function PrimaryButton({
.filter(Boolean)
.join(' ')
+ const content = (
+ <>
+ {title}
+
+
+
+ >
+ )
+
+ if (!to) {
+ return (
+
+ )
+ }
+
if (download || to.startsWith('http') || to.startsWith('#')) {
return (
- {title}
-
-
-
+ {content}
)
}
return (
- {title}
-
-
-
+ {content}
)
}
diff --git a/src/lib/phones.ts b/src/lib/phones.ts
index 558e4e2..93c0828 100644
--- a/src/lib/phones.ts
+++ b/src/lib/phones.ts
@@ -1,3 +1,5 @@
+import { toFaDigits } from './faDigits'
+
const FA_TO_EN: Record = {
'۰': '0',
'۱': '1',
@@ -21,6 +23,56 @@ const FA_TO_EN: Record = {
'٩': '9',
}
+/** Common Iranian city / province landline area codes (with leading 0). */
+const AREA_CODES: Record = {
+ تهران: '021',
+ کرج: '026',
+ البرز: '026',
+ اصفهان: '031',
+ مبارکه: '031',
+ شیراز: '071',
+ فارس: '071',
+ مشهد: '051',
+ نیشابور: '051',
+ 'خراسان رضوی': '051',
+ تبریز: '041',
+ 'آذربایجان شرقی': '041',
+ ارومیه: '044',
+ 'آذربایجان غربی': '044',
+ رشت: '013',
+ گیلان: '013',
+ ساری: '011',
+ چالوس: '011',
+ مازندران: '011',
+ اهواز: '061',
+ شوش: '061',
+ خوزستان: '061',
+ کرمان: '034',
+ یزد: '035',
+ قم: '025',
+ قزوین: '028',
+ کرمانشاه: '083',
+ بندرعباس: '076',
+ کیش: '076',
+ هرمزگان: '076',
+ بوشهر: '077',
+ گرگان: '017',
+ 'بندر ترکمن': '017',
+ گلستان: '017',
+ اراک: '086',
+ محلات: '086',
+ مرکزی: '086',
+ همدان: '081',
+ زنجان: '024',
+ سمنان: '023',
+ اردبیل: '045',
+ بجنورد: '058',
+ ایلام: '084',
+ یاسوج: '074',
+ شهرکرد: '038',
+ بیرجند: '056',
+}
+
export type PhoneKind = 'mobile' | 'landline'
export type DealerPhone = {
@@ -42,21 +94,54 @@ export function isMobileDigits(digits: string): boolean {
return /^09\d{9}$/.test(digits)
}
-export function dealerPhones(mobile: string, landline: string): DealerPhone[] {
+function areaCodeFor(city?: string, province?: string): string | undefined {
+ const keys = [city, province].map((v) => v?.trim()).filter(Boolean) as string[]
+ for (const key of keys) {
+ if (AREA_CODES[key]) return AREA_CODES[key]
+ }
+ return undefined
+}
+
+function ensureLandlineAreaCode(digits: string, city?: string, province?: string): string {
+ if (isMobileDigits(digits)) return digits
+ if (digits.startsWith('0') && digits.length >= 10) return digits
+
+ const area = areaCodeFor(city, province)
+ if (!area) {
+ if (digits.length === 10) return `0${digits}`
+ return digits
+ }
+
+ if (digits.startsWith(area.slice(1))) return `0${digits}`
+ if (digits.startsWith(area)) return digits
+ if (digits.length <= 8) return `${area}${digits}`
+ return digits.startsWith('0') ? digits : `0${digits}`
+}
+
+export function dealerPhones(
+ mobile: string,
+ landline: string,
+ opts?: { city?: string; province?: string },
+): DealerPhone[] {
const seen = new Set()
const list: DealerPhone[] = []
const add = (raw: string | null | undefined) => {
const trimmed = (raw ?? '').trim()
if (!trimmed || trimmed === '\\N') return
- const digits = normalizePhoneDigits(trimmed)
+ let digits = normalizePhoneDigits(trimmed)
if (digits.length < 8) return
+
+ if (!isMobileDigits(digits)) {
+ digits = ensureLandlineAreaCode(digits, opts?.city, opts?.province)
+ }
+
if (seen.has(digits)) return
seen.add(digits)
const kind: PhoneKind = isMobileDigits(digits) ? 'mobile' : 'landline'
list.push({
kind,
- display: trimmed,
+ display: toFaDigits(digits),
href: `tel:${digits}`,
})
}
diff --git a/src/pages/CatalogPages.css b/src/pages/CatalogPages.css
index 15f4db5..dfa3033 100644
--- a/src/pages/CatalogPages.css
+++ b/src/pages/CatalogPages.css
@@ -24,22 +24,44 @@
}
.dealers-search {
+ position: relative;
display: block;
width: 100%;
- margin-top: 8px;
+ margin-top: 12px;
+}
+
+.dealers-search__icon {
+ position: absolute;
+ top: 50%;
+ inset-inline-start: 14px;
+ transform: translateY(-50%);
+ display: grid;
+ place-items: center;
+ color: #8b95a8;
+ pointer-events: none;
}
.dealers-search input {
width: 100%;
box-sizing: border-box;
- border: 1px solid var(--color-navy);
- border-radius: var(--radius, 8px);
+ border: 1px solid #d7dde8;
+ border-radius: 12px;
background: #fff;
padding: 14px 16px;
+ padding-inline-start: 44px;
font: inherit;
font-size: 15px;
color: var(--color-navy);
outline: none;
+ transition:
+ border-color 0.2s ease,
+ box-shadow 0.2s ease;
+}
+
+.dealers-search input::-webkit-search-decoration,
+.dealers-search input::-webkit-search-cancel-button {
+ -webkit-appearance: none;
+ appearance: none;
}
.dealers-search input::placeholder {
@@ -51,6 +73,10 @@
box-shadow: 0 0 0 3px rgba(37, 99, 176, 0.12);
}
+.dealers-search:focus-within .dealers-search__icon {
+ color: var(--color-blue);
+}
+
.visually-hidden {
position: absolute;
width: 1px;
@@ -172,12 +198,16 @@
padding-top: 10px;
}
+.catalog--flush-cta {
+ padding-bottom: 0;
+}
+
.dealers-cta {
margin-top: 36px;
- background: #fff;
- border-top: 1px solid #e8e8e8;
- border-bottom: 1px solid #e8e8e8;
- padding: 18px 0;
+ background: #eef3f9;
+ border-top: 1px solid #dbe4f0;
+ border-bottom: 0;
+ padding: 22px 0;
}
.dealers-cta__inner {
@@ -268,7 +298,10 @@
text-decoration: none;
font-family: var(--font-en);
font-size: 14px;
- line-height: 1;
+ font-weight: 400;
+ letter-spacing: 0.02em;
+ line-height: 1.2;
+ font-variant-numeric: tabular-nums;
}
.dealer-row__phone--mobile {
diff --git a/src/pages/DealersPage.tsx b/src/pages/DealersPage.tsx
index f05a557..3e6e267 100644
--- a/src/pages/DealersPage.tsx
+++ b/src/pages/DealersPage.tsx
@@ -1,5 +1,4 @@
import { useEffect, useMemo, useState } from 'react'
-import { Link } from 'react-router-dom'
import PrimaryButton from '../components/PrimaryButton'
import SeoHead from '../components/SeoHead'
import {
@@ -7,7 +6,6 @@ import {
listPublicRepresentations,
type PublicRepresentation,
} from '../lib/api'
-import { toFaDigits } from '../lib/faDigits'
import { paths } from '../lib/paths'
import { dealerPhones, type PhoneKind } from '../lib/phones'
import './CatalogPages.css'
@@ -80,7 +78,7 @@ export default function DealersPage() {
}, [items, city, query])
return (
-
+
نمایندگیهای فعال در سراسر ایران