Expand special-order form with selects and success screen.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
co-authored by
Cursor
parent
389fc6fc51
commit
cc4334e048
Generated
+31
@@ -11,6 +11,7 @@
|
|||||||
"lucide-react": "^0.511.0",
|
"lucide-react": "^0.511.0",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.2.8",
|
||||||
|
"react-multi-date-picker": "^4.5.2",
|
||||||
"react-router-dom": "^7.18.2"
|
"react-router-dom": "^7.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
@@ -1116,6 +1117,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-date-object": {
|
||||||
|
"version": "2.1.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-date-object/-/react-date-object-2.1.9.tgz",
|
||||||
|
"integrity": "sha512-BHxD/quWOTo9fLKV/cfL/M31ePoj4a1JaJ/CnOf8Ndg3mrkh4x9wEMMkCfTrzduxDOgU8ZgR8uarhqI5G71sTg==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/react-dom": {
|
"node_modules/react-dom": {
|
||||||
"version": "19.2.8",
|
"version": "19.2.8",
|
||||||
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.8.tgz",
|
||||||
@@ -1128,6 +1135,30 @@
|
|||||||
"react": "^19.2.8"
|
"react": "^19.2.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/react-element-popper": {
|
||||||
|
"version": "2.1.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-element-popper/-/react-element-popper-2.1.7.tgz",
|
||||||
|
"integrity": "sha512-tuM2OxKlW32h+6uFSK6EENHPeZ2OGgOipHfOAl+VLWEv9/j3QkSGbD+ADX3A9uJlmq24i37n28RjJmAbGTfpEg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-multi-date-picker": {
|
||||||
|
"version": "4.5.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-multi-date-picker/-/react-multi-date-picker-4.5.2.tgz",
|
||||||
|
"integrity": "sha512-FgWjZB3Z6IA6XpcWiLPk85PwcRUhOiYhKK42o5k672gD/n2I6rzPfQ8bUrldOIiF/Z7FfOCdH7a6FeubzqteLg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"react-date-object": "^2.1.8",
|
||||||
|
"react-element-popper": "^2.1.6"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": ">=16.8.0",
|
||||||
|
"react-dom": ">=16.8.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/react-router": {
|
"node_modules/react-router": {
|
||||||
"version": "7.18.2",
|
"version": "7.18.2",
|
||||||
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.18.2.tgz",
|
||||||
|
|||||||
+2
-1
@@ -10,9 +10,10 @@
|
|||||||
"preview": "vite preview"
|
"preview": "vite preview"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"lucide-react": "^0.511.0",
|
||||||
"react": "^19.2.8",
|
"react": "^19.2.8",
|
||||||
"react-dom": "^19.2.8",
|
"react-dom": "^19.2.8",
|
||||||
"lucide-react": "^0.511.0",
|
"react-multi-date-picker": "^4.5.2",
|
||||||
"react-router-dom": "^7.18.2"
|
"react-router-dom": "^7.18.2"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|||||||
@@ -0,0 +1,75 @@
|
|||||||
|
import MultiDatePicker from 'react-multi-date-picker'
|
||||||
|
import DateObjectModule from 'react-date-object'
|
||||||
|
import persian from 'react-date-object/calendars/persian'
|
||||||
|
import persian_fa from 'react-date-object/locales/persian_fa'
|
||||||
|
|
||||||
|
const DatePicker =
|
||||||
|
(MultiDatePicker as unknown as { default?: typeof MultiDatePicker }).default ??
|
||||||
|
MultiDatePicker
|
||||||
|
const DateObject =
|
||||||
|
(DateObjectModule as unknown as { default?: typeof DateObjectModule })
|
||||||
|
.default ?? DateObjectModule
|
||||||
|
|
||||||
|
type PersianDateInputProps = {
|
||||||
|
id?: string
|
||||||
|
value: string
|
||||||
|
onChange: (isoDate: string) => void
|
||||||
|
disabled?: boolean
|
||||||
|
placeholder?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function isoToPickerValue(iso: string) {
|
||||||
|
if (!iso) return null
|
||||||
|
const date = new Date(iso)
|
||||||
|
if (Number.isNaN(date.getTime())) return null
|
||||||
|
return new DateObject({
|
||||||
|
date,
|
||||||
|
calendar: persian,
|
||||||
|
locale: persian_fa,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function pickerValueToIso(value: InstanceType<typeof DateObject> | null) {
|
||||||
|
if (!value) return ''
|
||||||
|
const date = value.toDate()
|
||||||
|
date.setHours(12, 0, 0, 0)
|
||||||
|
return date.toISOString()
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function PersianDateInput({
|
||||||
|
id,
|
||||||
|
value,
|
||||||
|
onChange,
|
||||||
|
disabled = false,
|
||||||
|
placeholder = 'انتخاب تاریخ',
|
||||||
|
}: PersianDateInputProps) {
|
||||||
|
return (
|
||||||
|
<DatePicker
|
||||||
|
id={id}
|
||||||
|
value={isoToPickerValue(value)}
|
||||||
|
onChange={(
|
||||||
|
date:
|
||||||
|
| InstanceType<typeof DateObject>
|
||||||
|
| InstanceType<typeof DateObject>[]
|
||||||
|
| null,
|
||||||
|
) => {
|
||||||
|
if (date instanceof DateObject) {
|
||||||
|
onChange(pickerValueToIso(date))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onChange('')
|
||||||
|
}}
|
||||||
|
calendar={persian}
|
||||||
|
locale={persian_fa}
|
||||||
|
format="YYYY/MM/DD"
|
||||||
|
calendarPosition="bottom-center"
|
||||||
|
containerClassName="persian-date-input"
|
||||||
|
inputClass="persian-date-input__field"
|
||||||
|
portal
|
||||||
|
zIndex={200}
|
||||||
|
disabled={disabled}
|
||||||
|
placeholder={placeholder}
|
||||||
|
editable={false}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -70,6 +70,7 @@ export type CreateMyOrderPayload = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export type SpecialOrderCategory = 'specialCake' | 'specialPastry'
|
export type SpecialOrderCategory = 'specialCake' | 'specialPastry'
|
||||||
|
export type SpecialOrderDeliveryType = 'pickup' | 'shipping'
|
||||||
|
|
||||||
export type SpecialOrderImageInput = {
|
export type SpecialOrderImageInput = {
|
||||||
url: string
|
url: string
|
||||||
@@ -80,6 +81,18 @@ export type CreateMySpecialOrderPayload = {
|
|||||||
category: SpecialOrderCategory
|
category: SpecialOrderCategory
|
||||||
name: string
|
name: string
|
||||||
description?: string
|
description?: string
|
||||||
|
neededDate?: string
|
||||||
|
receiveTime?: string
|
||||||
|
servings?: number
|
||||||
|
cakeFlavor?: string
|
||||||
|
filling?: string
|
||||||
|
baloutSuggestedCombo?: boolean
|
||||||
|
coating?: string
|
||||||
|
designStyle?: string
|
||||||
|
cakeText?: string
|
||||||
|
decorations?: string
|
||||||
|
deliveryType?: SpecialOrderDeliveryType
|
||||||
|
branchId?: string
|
||||||
images?: SpecialOrderImageInput[]
|
images?: SpecialOrderImageInput[]
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -91,9 +104,25 @@ export type MediaUploadResult = {
|
|||||||
|
|
||||||
export type SpecialOrder = {
|
export type SpecialOrder = {
|
||||||
id: string
|
id: string
|
||||||
|
number: number
|
||||||
|
code: string
|
||||||
category: SpecialOrderCategory
|
category: SpecialOrderCategory
|
||||||
name: string
|
name: string
|
||||||
description: string
|
description: string
|
||||||
|
neededDate: string | null
|
||||||
|
receiveTime: string
|
||||||
|
servings: number | null
|
||||||
|
cakeFlavor: string
|
||||||
|
filling: string
|
||||||
|
baloutSuggestedCombo: boolean
|
||||||
|
coating: string
|
||||||
|
designStyle: string
|
||||||
|
cakeText: string
|
||||||
|
decorations: string
|
||||||
|
deliveryType: SpecialOrderDeliveryType | null
|
||||||
|
deliveryTypeLabel: string | null
|
||||||
|
branchId: string | null
|
||||||
|
branchName: string
|
||||||
createdAt: string
|
createdAt: string
|
||||||
images: Array<{
|
images: Array<{
|
||||||
id: string
|
id: string
|
||||||
|
|||||||
@@ -0,0 +1,52 @@
|
|||||||
|
export const SPONGE_FLAVORS = [
|
||||||
|
'وانیلی',
|
||||||
|
'شکلاتی',
|
||||||
|
'ردولوت',
|
||||||
|
'نسکافه',
|
||||||
|
'سایر',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const FILLINGS = [
|
||||||
|
'چیپس شکلات',
|
||||||
|
'گاناش',
|
||||||
|
'فندق',
|
||||||
|
'پسته',
|
||||||
|
'کارامل',
|
||||||
|
'توتفرنگی',
|
||||||
|
'موز',
|
||||||
|
'موز و گردو',
|
||||||
|
'گردو',
|
||||||
|
'سایر',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const COATINGS = [
|
||||||
|
'خامه شکلاتی',
|
||||||
|
'خامه وانیلی',
|
||||||
|
'خامه نسکافهای',
|
||||||
|
'فوندانت',
|
||||||
|
'روکش خاص',
|
||||||
|
'بدون روکش',
|
||||||
|
'انتخاب توسط قناد',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const DESIGN_STYLES = [
|
||||||
|
'مینیمال',
|
||||||
|
'لوکس',
|
||||||
|
'کلاسیک',
|
||||||
|
'مدرن',
|
||||||
|
'رمانتیک',
|
||||||
|
'گل و طبیعت',
|
||||||
|
'فانتزی',
|
||||||
|
'کودک',
|
||||||
|
'ایرانی / سنتی',
|
||||||
|
'سازمانی',
|
||||||
|
'سایر',
|
||||||
|
] as const
|
||||||
|
|
||||||
|
export const OTHER_OPTION = 'سایر'
|
||||||
|
|
||||||
|
export function resolveSelectValue(selected: string, custom: string) {
|
||||||
|
if (!selected) return ''
|
||||||
|
if (selected === OTHER_OPTION) return custom.trim() || OTHER_OPTION
|
||||||
|
return selected
|
||||||
|
}
|
||||||
@@ -15,6 +15,109 @@
|
|||||||
gap: 20px;
|
gap: 20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.special-order-section {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 18px 16px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid var(--color-gray-100);
|
||||||
|
background: rgba(255, 255, 255, 0.35);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-section__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-hint {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
line-height: 1.8;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
padding-inline: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 10px;
|
||||||
|
padding: 12px 14px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid var(--color-gray-100);
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice input {
|
||||||
|
margin-top: 3px;
|
||||||
|
accent-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice span {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 4px;
|
||||||
|
min-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice strong {
|
||||||
|
font-size: 0.92rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice em {
|
||||||
|
font-style: normal;
|
||||||
|
font-size: 0.82rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-choice.is-active {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.persian-date-input {
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.persian-date-input__field {
|
||||||
|
width: 100%;
|
||||||
|
border: 1px solid var(--color-gray-100);
|
||||||
|
border-radius: 12px;
|
||||||
|
background: transparent;
|
||||||
|
padding: 12px 14px;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: 15px;
|
||||||
|
color: #000;
|
||||||
|
outline: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: border-color 0.2s ease;
|
||||||
|
direction: ltr;
|
||||||
|
text-align: center;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
|
.persian-date-input__field:focus {
|
||||||
|
border-color: var(--color-primary);
|
||||||
|
}
|
||||||
|
|
||||||
.special-order-form__alert {
|
.special-order-form__alert {
|
||||||
padding: 12px 14px;
|
padding: 12px 14px;
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -31,6 +134,67 @@
|
|||||||
color: #2f6b4f;
|
color: #2f6b4f;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.special-order-success {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 28px 24px;
|
||||||
|
border-radius: 16px;
|
||||||
|
border: 1px solid rgba(47, 107, 79, 0.18);
|
||||||
|
background: rgba(47, 107, 79, 0.06);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-success__icon {
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 50%;
|
||||||
|
color: #2f6b4f;
|
||||||
|
background: rgba(47, 107, 79, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-success__title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: clamp(20px, 3vw, 28px);
|
||||||
|
font-weight: 700;
|
||||||
|
color: #2f6b4f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-success__text {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
line-height: 1.9;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
text-align: justify;
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-success__code {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 6px;
|
||||||
|
width: 100%;
|
||||||
|
padding: 14px 16px;
|
||||||
|
border-radius: 12px;
|
||||||
|
border: 1px solid rgba(47, 107, 79, 0.16);
|
||||||
|
background: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-success__code span {
|
||||||
|
font-size: 0.78rem;
|
||||||
|
color: var(--color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
.special-order-success__code strong {
|
||||||
|
font-size: 1.35rem;
|
||||||
|
font-weight: 700;
|
||||||
|
letter-spacing: 0.04em;
|
||||||
|
color: #000;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
|
||||||
.special-order-field {
|
.special-order-field {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
@@ -47,7 +211,8 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.special-order-field input,
|
.special-order-field input,
|
||||||
.special-order-field textarea {
|
.special-order-field textarea,
|
||||||
|
.special-order-field select {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
border: 1px solid var(--color-gray-100);
|
border: 1px solid var(--color-gray-100);
|
||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
@@ -62,12 +227,20 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
.special-order-field input:focus,
|
.special-order-field input:focus,
|
||||||
.special-order-field textarea:focus {
|
.special-order-field textarea:focus,
|
||||||
|
.special-order-field select:focus {
|
||||||
border-color: var(--color-primary);
|
border-color: var(--color-primary);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.special-order-field input[type='time'] {
|
||||||
|
direction: ltr;
|
||||||
|
text-align: center;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
min-height: 46px;
|
||||||
|
}
|
||||||
|
|
||||||
.special-order-field--textarea textarea {
|
.special-order-field--textarea textarea {
|
||||||
min-height: 160px;
|
min-height: 120px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.special-order-radios {
|
.special-order-radios {
|
||||||
@@ -309,6 +482,10 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 640px) {
|
@media (max-width: 640px) {
|
||||||
|
.special-order-row {
|
||||||
|
grid-template-columns: 1fr;
|
||||||
|
}
|
||||||
|
|
||||||
.special-order-gallery__grid {
|
.special-order-gallery__grid {
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
grid-template-columns: repeat(2, minmax(0, 1fr));
|
||||||
}
|
}
|
||||||
|
|||||||
+412
-46
@@ -2,11 +2,15 @@ import { useEffect, useState, type FormEvent } from 'react'
|
|||||||
import { ChevronLeft, ImagePlus, LogIn, Sparkles, X } from 'lucide-react'
|
import { ChevronLeft, ImagePlus, LogIn, Sparkles, X } from 'lucide-react'
|
||||||
import PageHeader from '../components/PageHeader'
|
import PageHeader from '../components/PageHeader'
|
||||||
import OffersHours from '../components/OffersHours'
|
import OffersHours from '../components/OffersHours'
|
||||||
|
import PersianDateInput from '../components/PersianDateInput'
|
||||||
import cakeIcon from '../assets/images/Cake.svg'
|
import cakeIcon from '../assets/images/Cake.svg'
|
||||||
import {
|
import {
|
||||||
createMySpecialOrder,
|
createMySpecialOrder,
|
||||||
|
listBranches,
|
||||||
uploadMedia,
|
uploadMedia,
|
||||||
|
type Branch,
|
||||||
type SpecialOrderCategory,
|
type SpecialOrderCategory,
|
||||||
|
type SpecialOrderDeliveryType,
|
||||||
} from '../lib/api'
|
} from '../lib/api'
|
||||||
import {
|
import {
|
||||||
consumeAuthHashFromUrl,
|
consumeAuthHashFromUrl,
|
||||||
@@ -15,6 +19,14 @@ import {
|
|||||||
isAuthenticated,
|
isAuthenticated,
|
||||||
type AuthUser,
|
type AuthUser,
|
||||||
} from '../lib/auth'
|
} from '../lib/auth'
|
||||||
|
import {
|
||||||
|
COATINGS,
|
||||||
|
DESIGN_STYLES,
|
||||||
|
FILLINGS,
|
||||||
|
OTHER_OPTION,
|
||||||
|
resolveSelectValue,
|
||||||
|
SPONGE_FLAVORS,
|
||||||
|
} from '../lib/specialOrderOptions'
|
||||||
import './SpecialOrderPage.css'
|
import './SpecialOrderPage.css'
|
||||||
|
|
||||||
const CATEGORIES: { id: SpecialOrderCategory; label: string }[] = [
|
const CATEGORIES: { id: SpecialOrderCategory; label: string }[] = [
|
||||||
@@ -22,6 +34,11 @@ const CATEGORIES: { id: SpecialOrderCategory; label: string }[] = [
|
|||||||
{ id: 'specialPastry', label: 'شیرینی ویژه' },
|
{ id: 'specialPastry', label: 'شیرینی ویژه' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
const DELIVERY_OPTIONS: { id: SpecialOrderDeliveryType; label: string }[] = [
|
||||||
|
{ id: 'pickup', label: 'دریافت حضوری' },
|
||||||
|
{ id: 'shipping', label: 'ارسال با پیک' },
|
||||||
|
]
|
||||||
|
|
||||||
const SUBMIT_COOLDOWN_MS = 60_000
|
const SUBMIT_COOLDOWN_MS = 60_000
|
||||||
const COOLDOWN_STORAGE_KEY = 'balout.specialOrderCooldownUntil'
|
const COOLDOWN_STORAGE_KEY = 'balout.specialOrderCooldownUntil'
|
||||||
|
|
||||||
@@ -31,8 +48,8 @@ const SIDE_NOTES = [
|
|||||||
text: 'این فرم فقط برای ثبت درخواست است؛ هزینه بعداً با هماهنگی فروشگاه مشخص میشود.',
|
text: 'این فرم فقط برای ثبت درخواست است؛ هزینه بعداً با هماهنگی فروشگاه مشخص میشود.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'کیک یا شیرینی سفارشی',
|
title: 'جزئیات دقیقتر = ساخت بهتر',
|
||||||
text: 'ایده، تم تولد، تعداد و زمان تحویل را در توضیحات بنویسید تا دقیقتر راهنمایی شوید.',
|
text: 'مناسبت، تاریخ، تعداد نفرات و طعم را کامل بنویسید تا تیم بلوط دقیقتر راهنمایی کند.',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'تصاویر ایده',
|
title: 'تصاویر ایده',
|
||||||
@@ -80,11 +97,29 @@ function cooldownSecondsLeft(until = readCooldownUntil()) {
|
|||||||
export default function SpecialOrderPage() {
|
export default function SpecialOrderPage() {
|
||||||
const [user, setUser] = useState<AuthUser | null>(() => getAuthUser())
|
const [user, setUser] = useState<AuthUser | null>(() => getAuthUser())
|
||||||
const [category, setCategory] = useState<SpecialOrderCategory>('specialCake')
|
const [category, setCategory] = useState<SpecialOrderCategory>('specialCake')
|
||||||
const [name, setName] = useState('')
|
const [occasion, setOccasion] = useState('')
|
||||||
const [description, setDescription] = useState('')
|
const [neededDate, setNeededDate] = useState('')
|
||||||
|
const [receiveTime, setReceiveTime] = useState('')
|
||||||
|
const [servings, setServings] = useState('')
|
||||||
|
const [spongeFlavor, setSpongeFlavor] = useState('')
|
||||||
|
const [spongeFlavorCustom, setSpongeFlavorCustom] = useState('')
|
||||||
|
const [filling, setFilling] = useState('')
|
||||||
|
const [fillingCustom, setFillingCustom] = useState('')
|
||||||
|
const [coating, setCoating] = useState('')
|
||||||
|
const [designStyle, setDesignStyle] = useState('')
|
||||||
|
const [designStyleCustom, setDesignStyleCustom] = useState('')
|
||||||
|
const [cakeText, setCakeText] = useState('')
|
||||||
|
const [decorations, setDecorations] = useState('')
|
||||||
|
const [deliveryType, setDeliveryType] =
|
||||||
|
useState<SpecialOrderDeliveryType>('pickup')
|
||||||
|
const [branchId, setBranchId] = useState('')
|
||||||
|
const [branches, setBranches] = useState<Branch[]>([])
|
||||||
const [gallery, setGallery] = useState<string[]>([])
|
const [gallery, setGallery] = useState<string[]>([])
|
||||||
const [error, setError] = useState('')
|
const [error, setError] = useState('')
|
||||||
const [success, setSuccess] = useState('')
|
const [submittedOrder, setSubmittedOrder] = useState<{
|
||||||
|
id: string
|
||||||
|
code: string
|
||||||
|
} | null>(null)
|
||||||
const [saving, setSaving] = useState(false)
|
const [saving, setSaving] = useState(false)
|
||||||
const [cooldownLeft, setCooldownLeft] = useState(() => cooldownSecondsLeft())
|
const [cooldownLeft, setCooldownLeft] = useState(() => cooldownSecondsLeft())
|
||||||
|
|
||||||
@@ -105,11 +140,50 @@ export default function SpecialOrderPage() {
|
|||||||
}, [cooldownLeft])
|
}, [cooldownLeft])
|
||||||
|
|
||||||
const authenticated = Boolean(user) || isAuthenticated()
|
const authenticated = Boolean(user) || isAuthenticated()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (!authenticated) {
|
||||||
|
setBranches([])
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let cancelled = false
|
||||||
|
void listBranches()
|
||||||
|
.then((items) => {
|
||||||
|
if (!cancelled) setBranches(items)
|
||||||
|
})
|
||||||
|
.catch(() => {
|
||||||
|
if (!cancelled) setBranches([])
|
||||||
|
})
|
||||||
|
return () => {
|
||||||
|
cancelled = true
|
||||||
|
}
|
||||||
|
}, [authenticated])
|
||||||
|
|
||||||
const loginUrl = getCustomerLoginUrl(
|
const loginUrl = getCustomerLoginUrl(
|
||||||
`${window.location.origin}/special-order`,
|
`${window.location.origin}/special-order`,
|
||||||
)
|
)
|
||||||
const submitDisabled = saving || cooldownLeft > 0
|
const submitDisabled = saving || cooldownLeft > 0
|
||||||
|
|
||||||
|
function resetForm() {
|
||||||
|
setCategory('specialCake')
|
||||||
|
setOccasion('')
|
||||||
|
setNeededDate('')
|
||||||
|
setReceiveTime('')
|
||||||
|
setServings('')
|
||||||
|
setSpongeFlavor('')
|
||||||
|
setSpongeFlavorCustom('')
|
||||||
|
setFilling('')
|
||||||
|
setFillingCustom('')
|
||||||
|
setCoating('')
|
||||||
|
setDesignStyle('')
|
||||||
|
setDesignStyleCustom('')
|
||||||
|
setCakeText('')
|
||||||
|
setDecorations('')
|
||||||
|
setDeliveryType('pickup')
|
||||||
|
setBranchId('')
|
||||||
|
setGallery([])
|
||||||
|
}
|
||||||
|
|
||||||
async function handleFiles(event: React.ChangeEvent<HTMLInputElement>) {
|
async function handleFiles(event: React.ChangeEvent<HTMLInputElement>) {
|
||||||
const files = Array.from(event.target.files ?? [])
|
const files = Array.from(event.target.files ?? [])
|
||||||
event.target.value = ''
|
event.target.value = ''
|
||||||
@@ -121,7 +195,6 @@ export default function SpecialOrderPage() {
|
|||||||
async function handleSubmit(event: FormEvent) {
|
async function handleSubmit(event: FormEvent) {
|
||||||
event.preventDefault()
|
event.preventDefault()
|
||||||
setError('')
|
setError('')
|
||||||
setSuccess('')
|
|
||||||
|
|
||||||
if (!authenticated) {
|
if (!authenticated) {
|
||||||
window.location.assign(loginUrl)
|
window.location.assign(loginUrl)
|
||||||
@@ -131,10 +204,48 @@ export default function SpecialOrderPage() {
|
|||||||
setError(`لطفاً ${cooldownLeft} ثانیه دیگر دوباره تلاش کنید`)
|
setError(`لطفاً ${cooldownLeft} ثانیه دیگر دوباره تلاش کنید`)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (!name.trim()) {
|
if (!occasion.trim()) {
|
||||||
setError('نام سفارش الزامی است')
|
setError('مناسبت سفارش الزامی است')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if (!neededDate) {
|
||||||
|
setError('تاریخ موردنیاز الزامی است')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!receiveTime.trim()) {
|
||||||
|
setError('ساعت دریافت الزامی است')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (deliveryType === 'pickup' && !branchId) {
|
||||||
|
setError('برای دریافت حضوری، انتخاب شعبه الزامی است')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const servingsValue = servings.trim() ? Number(servings) : undefined
|
||||||
|
if (
|
||||||
|
servings.trim() &&
|
||||||
|
(!Number.isInteger(servingsValue) || (servingsValue ?? 0) < 1)
|
||||||
|
) {
|
||||||
|
setError('تعداد نفرات باید عدد صحیح و حداقل ۱ باشد')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if (spongeFlavor === OTHER_OPTION && !spongeFlavorCustom.trim()) {
|
||||||
|
setError('لطفاً طعم اسفنج سفارشی را بنویسید')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (filling === OTHER_OPTION && !fillingCustom.trim()) {
|
||||||
|
setError('لطفاً فیلینگ / مغزی سفارشی را بنویسید')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (designStyle === OTHER_OPTION && !designStyleCustom.trim()) {
|
||||||
|
setError('لطفاً سبک طراحی سفارشی را بنویسید')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const cakeFlavorValue = resolveSelectValue(spongeFlavor, spongeFlavorCustom)
|
||||||
|
const fillingValue = resolveSelectValue(filling, fillingCustom)
|
||||||
|
const designStyleValue = resolveSelectValue(designStyle, designStyleCustom)
|
||||||
|
|
||||||
setSaving(true)
|
setSaving(true)
|
||||||
try {
|
try {
|
||||||
@@ -145,20 +256,28 @@ export default function SpecialOrderPage() {
|
|||||||
images.push({ url: uploaded.url, storageKey: uploaded.storageKey })
|
images.push({ url: uploaded.url, storageKey: uploaded.storageKey })
|
||||||
}
|
}
|
||||||
|
|
||||||
await createMySpecialOrder({
|
const created = await createMySpecialOrder({
|
||||||
category,
|
category,
|
||||||
name: name.trim(),
|
name: occasion.trim(),
|
||||||
description: description.trim(),
|
neededDate,
|
||||||
|
receiveTime: receiveTime.trim(),
|
||||||
|
servings: servingsValue,
|
||||||
|
cakeFlavor: cakeFlavorValue || undefined,
|
||||||
|
filling: fillingValue || undefined,
|
||||||
|
coating: coating || undefined,
|
||||||
|
designStyle: designStyleValue || undefined,
|
||||||
|
cakeText: cakeText.trim() || undefined,
|
||||||
|
decorations: decorations.trim() || undefined,
|
||||||
|
deliveryType,
|
||||||
|
branchId: branchId || undefined,
|
||||||
images,
|
images,
|
||||||
})
|
})
|
||||||
|
|
||||||
setName('')
|
resetForm()
|
||||||
setDescription('')
|
setSubmittedOrder({
|
||||||
setGallery([])
|
id: created.id,
|
||||||
setCategory('specialCake')
|
code: created.code || `BL-S-${created.number}`,
|
||||||
setSuccess(
|
})
|
||||||
'سفارش ویژه شما ثبت شد. تیم بلوط بهزودی برای هماهنگی با شما تماس میگیرد.',
|
|
||||||
)
|
|
||||||
const until = Date.now() + SUBMIT_COOLDOWN_MS
|
const until = Date.now() + SUBMIT_COOLDOWN_MS
|
||||||
writeCooldownUntil(until)
|
writeCooldownUntil(until)
|
||||||
setCooldownLeft(cooldownSecondsLeft(until))
|
setCooldownLeft(cooldownSecondsLeft(until))
|
||||||
@@ -189,7 +308,26 @@ export default function SpecialOrderPage() {
|
|||||||
|
|
||||||
<div className="max-container special-order-page__body">
|
<div className="max-container special-order-page__body">
|
||||||
<div className="special-order-page__grid">
|
<div className="special-order-page__grid">
|
||||||
{authenticated ? (
|
{authenticated && submittedOrder ? (
|
||||||
|
<div className="special-order-success" role="status">
|
||||||
|
<div className="special-order-success__icon" aria-hidden>
|
||||||
|
<Sparkles size={28} strokeWidth={1.5} />
|
||||||
|
</div>
|
||||||
|
<h2 className="special-order-success__title">سفارش ویژه ثبت شد</h2>
|
||||||
|
<p className="special-order-success__text">
|
||||||
|
درخواست شما با موفقیت ثبت شد. تیم بلوط بهزودی برای هماهنگی با شما
|
||||||
|
تماس میگیرد.
|
||||||
|
</p>
|
||||||
|
<div className="special-order-success__code">
|
||||||
|
<span>کد سفارش</span>
|
||||||
|
<strong dir="ltr">{submittedOrder.code}</strong>
|
||||||
|
</div>
|
||||||
|
<a href="/" className="btn-primary">
|
||||||
|
بازگشت به صفحه اصلی
|
||||||
|
<ChevronLeft size={14} />
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
) : authenticated ? (
|
||||||
<form
|
<form
|
||||||
className="special-order-form"
|
className="special-order-form"
|
||||||
onSubmit={(e) => void handleSubmit(e)}
|
onSubmit={(e) => void handleSubmit(e)}
|
||||||
@@ -199,14 +337,6 @@ export default function SpecialOrderPage() {
|
|||||||
{error}
|
{error}
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
{success ? (
|
|
||||||
<div
|
|
||||||
className="special-order-form__alert special-order-form__alert--ok"
|
|
||||||
role="status"
|
|
||||||
>
|
|
||||||
{success}
|
|
||||||
</div>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<div className="special-order-field">
|
<div className="special-order-field">
|
||||||
<span>دستهبندی</span>
|
<span>دستهبندی</span>
|
||||||
@@ -227,26 +357,262 @@ export default function SpecialOrderPage() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<label className="special-order-field">
|
<section className="special-order-section">
|
||||||
<span>نام سفارش</span>
|
<h2 className="special-order-section__title">۱. اطلاعات سفارش</h2>
|
||||||
<input
|
<label className="special-order-field">
|
||||||
value={name}
|
<span>مناسبت سفارش</span>
|
||||||
onChange={(e) => setName(e.target.value)}
|
<input
|
||||||
placeholder="مثلاً کیک تولد با تم خاص"
|
value={occasion}
|
||||||
disabled={saving}
|
onChange={(e) => setOccasion(e.target.value)}
|
||||||
/>
|
placeholder="مثلاً تولد، سالگرد، مراسم کاری"
|
||||||
</label>
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div className="special-order-row">
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>تاریخ موردنیاز (شمسی)</span>
|
||||||
|
<PersianDateInput
|
||||||
|
value={neededDate}
|
||||||
|
onChange={setNeededDate}
|
||||||
|
disabled={saving}
|
||||||
|
placeholder="انتخاب تاریخ"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>ساعت دریافت</span>
|
||||||
|
<input
|
||||||
|
type="time"
|
||||||
|
value={receiveTime}
|
||||||
|
onChange={(e) => setReceiveTime(e.target.value)}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
|
||||||
<label className="special-order-field special-order-field--textarea">
|
<section className="special-order-section">
|
||||||
<span>توضیحات</span>
|
<h2 className="special-order-section__title">۲. اندازه کیک</h2>
|
||||||
<textarea
|
<label className="special-order-field">
|
||||||
value={description}
|
<span>تعداد نفرات</span>
|
||||||
onChange={(e) => setDescription(e.target.value)}
|
<input
|
||||||
placeholder="جزئیات، سلیقه، تعداد، زمان تحویل و ..."
|
type="number"
|
||||||
rows={6}
|
min={1}
|
||||||
disabled={saving}
|
max={500}
|
||||||
/>
|
inputMode="numeric"
|
||||||
</label>
|
value={servings}
|
||||||
|
onChange={(e) => setServings(e.target.value)}
|
||||||
|
placeholder="مثلاً ۱۲"
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="special-order-section">
|
||||||
|
<h2 className="special-order-section__title">۳. طعم کیک</h2>
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>طعم اسفنج</span>
|
||||||
|
<select
|
||||||
|
value={spongeFlavor}
|
||||||
|
onChange={(e) => {
|
||||||
|
setSpongeFlavor(e.target.value)
|
||||||
|
if (e.target.value !== OTHER_OPTION) {
|
||||||
|
setSpongeFlavorCustom('')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
<option value="">انتخاب کنید</option>
|
||||||
|
{SPONGE_FLAVORS.map((option) => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{spongeFlavor === OTHER_OPTION ? (
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>طعم اسفنج (سایر)</span>
|
||||||
|
<input
|
||||||
|
value={spongeFlavorCustom}
|
||||||
|
onChange={(e) => setSpongeFlavorCustom(e.target.value)}
|
||||||
|
placeholder="طعم موردنظر را بنویسید"
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>فیلینگ / مغزی</span>
|
||||||
|
<select
|
||||||
|
value={filling}
|
||||||
|
onChange={(e) => {
|
||||||
|
setFilling(e.target.value)
|
||||||
|
if (e.target.value !== OTHER_OPTION) {
|
||||||
|
setFillingCustom('')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
<option value="">انتخاب کنید</option>
|
||||||
|
{FILLINGS.map((option) => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{filling === OTHER_OPTION ? (
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>فیلینگ / مغزی (سایر)</span>
|
||||||
|
<input
|
||||||
|
value={fillingCustom}
|
||||||
|
onChange={(e) => setFillingCustom(e.target.value)}
|
||||||
|
placeholder="فیلینگ موردنظر را بنویسید"
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
) : null}
|
||||||
|
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>روکش کیک</span>
|
||||||
|
<select
|
||||||
|
value={coating}
|
||||||
|
onChange={(e) => setCoating(e.target.value)}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
<option value="">انتخاب کنید</option>
|
||||||
|
{COATINGS.map((option) => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="special-order-section">
|
||||||
|
<h2 className="special-order-section__title">۴. نوشته و تزئینات</h2>
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>متن روی کیک</span>
|
||||||
|
<input
|
||||||
|
value={cakeText}
|
||||||
|
onChange={(e) => setCakeText(e.target.value)}
|
||||||
|
placeholder="متنی که روی کیک نوشته شود"
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<label className="special-order-field special-order-field--textarea">
|
||||||
|
<span>تزئینات خاص در صورت نیاز</span>
|
||||||
|
<textarea
|
||||||
|
value={decorations}
|
||||||
|
onChange={(e) => setDecorations(e.target.value)}
|
||||||
|
placeholder="توضیح تم، رنگ، گل، فیگور و ..."
|
||||||
|
rows={4}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="special-order-section">
|
||||||
|
<h2 className="special-order-section__title">۵. سبک طراحی</h2>
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>سبک طراحی</span>
|
||||||
|
<select
|
||||||
|
value={designStyle}
|
||||||
|
onChange={(e) => {
|
||||||
|
setDesignStyle(e.target.value)
|
||||||
|
if (e.target.value !== OTHER_OPTION) {
|
||||||
|
setDesignStyleCustom('')
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
disabled={saving}
|
||||||
|
>
|
||||||
|
<option value="">انتخاب کنید</option>
|
||||||
|
{DESIGN_STYLES.map((option) => (
|
||||||
|
<option key={option} value={option}>
|
||||||
|
{option}
|
||||||
|
</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
|
</label>
|
||||||
|
{designStyle === OTHER_OPTION ? (
|
||||||
|
<label className="special-order-field">
|
||||||
|
<span>سبک طراحی (سایر)</span>
|
||||||
|
<input
|
||||||
|
value={designStyleCustom}
|
||||||
|
onChange={(e) => setDesignStyleCustom(e.target.value)}
|
||||||
|
placeholder="سبک موردنظر را بنویسید"
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
) : null}
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section className="special-order-section">
|
||||||
|
<h2 className="special-order-section__title">
|
||||||
|
۶. اطلاعات دریافت و تماس
|
||||||
|
</h2>
|
||||||
|
<div className="special-order-field">
|
||||||
|
<span>نحوه دریافت</span>
|
||||||
|
<div className="special-order-radios" role="radiogroup">
|
||||||
|
{DELIVERY_OPTIONS.map((item) => (
|
||||||
|
<label key={item.id} className="special-order-radio">
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="special-order-delivery"
|
||||||
|
value={item.id}
|
||||||
|
checked={deliveryType === item.id}
|
||||||
|
onChange={() => {
|
||||||
|
setDeliveryType(item.id)
|
||||||
|
if (item.id === 'shipping') setBranchId('')
|
||||||
|
}}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
<span>{item.label}</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{deliveryType === 'pickup' ? (
|
||||||
|
<div className="special-order-field">
|
||||||
|
<span>انتخاب شعبه</span>
|
||||||
|
{branches.length === 0 ? (
|
||||||
|
<p className="special-order-hint">شعبهای ثبت نشده است.</p>
|
||||||
|
) : (
|
||||||
|
<div className="special-order-choice-list">
|
||||||
|
{branches.map((branch) => (
|
||||||
|
<label
|
||||||
|
key={branch.id}
|
||||||
|
className={`special-order-choice${
|
||||||
|
branchId === branch.id ? ' is-active' : ''
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
type="radio"
|
||||||
|
name="special-order-branch"
|
||||||
|
checked={branchId === branch.id}
|
||||||
|
onChange={() => setBranchId(branch.id)}
|
||||||
|
disabled={saving}
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
<strong>{branch.name}</strong>
|
||||||
|
<em>
|
||||||
|
{branch.district} — {branch.address}
|
||||||
|
</em>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<p className="special-order-hint">
|
||||||
|
پس از ثبت درخواست، برای هماهنگی آدرس و زمان ارسال با شما تماس
|
||||||
|
گرفته میشود.
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</section>
|
||||||
|
|
||||||
<div className="special-order-gallery">
|
<div className="special-order-gallery">
|
||||||
<div className="special-order-gallery__header">
|
<div className="special-order-gallery__header">
|
||||||
|
|||||||
Reference in New Issue
Block a user