Localize order modals, add dual logos, and improve admin order tools.

Farsi business order/variation UI, light/dark logo upload on business profile, payment order steps, variation editing, super-admin site links, and checkout/auth fixes.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-26 00:21:48 +03:30
co-authored by Cursor
parent dc570c1e7f
commit 5e799eddf9
30 changed files with 779 additions and 169 deletions
@@ -44,6 +44,26 @@
grid-column: span 12;
}
.logoModeRow {
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
margin-bottom: 10px;
}
.logoModeLabel {
font-size: 12px;
font-weight: 500;
color: var(--text-muted);
transition: color 0.15s;
}
.logoModeLabelActive {
color: var(--text-primary);
font-weight: 600;
}
.basicAside {
grid-column: span 12;
display: grid;
@@ -14,6 +14,7 @@ import { PageTitle } from '../components/PageTitle'
import { ImageCropper } from '../components/ImageCropper'
import { MultiSelectDropdown } from '../components/MultiSelectDropdown'
import { RichTextEditor } from '../components/RichTextEditor'
import { Switch } from '../components/Switch'
import { useToast } from '../context/ToastContext'
import { useT } from '../i18n/useT'
import { ApiError } from '../lib/api'
@@ -80,6 +81,9 @@ export function BusinessProfilePage() {
const [vision, setVision] = useState('')
const [logo, setLogo] = useState<string | null>(null)
const [logoMediaId, setLogoMediaId] = useState<string | null>(null)
const [logoDark, setLogoDark] = useState<string | null>(null)
const [logoDarkMediaId, setLogoDarkMediaId] = useState<string | null>(null)
const [logoModeDark, setLogoModeDark] = useState(false)
const [addresses, setAddresses] = useState<AddressDraft[]>([createEmptyAddressItem()])
const [phoneNumbers, setPhoneNumbers] = useState<BusinessPhoneNumber[]>([
createEmptyPhone(),
@@ -153,6 +157,8 @@ export function BusinessProfilePage() {
setVision(profileData.profile.vision)
setLogo(profileData.profile.logoUrl)
setLogoMediaId(profileData.profile.logoMediaId)
setLogoDark(profileData.profile.logoDarkUrl)
setLogoDarkMediaId(profileData.profile.logoDarkMediaId)
const nextAddresses =
profileData.addresses.length > 0
@@ -273,6 +279,11 @@ export function BusinessProfilePage() {
'business-logo.png',
logoMediaId,
)
const nextLogoDarkMediaId = await resolveDataUrlToMediaId(
logoDark,
'business-logo-dark.png',
logoDarkMediaId,
)
const payloadAddresses = addresses
.filter(
@@ -309,6 +320,7 @@ export function BusinessProfilePage() {
phoneNumbers: payloadPhones,
socialMedia,
logoMediaId: nextLogoMediaId,
logoDarkMediaId: nextLogoDarkMediaId,
categoryIds,
addresses: payloadAddresses,
})
@@ -358,9 +370,27 @@ export function BusinessProfilePage() {
<div className={formStyles.formGrid}>
<div className={`${formStyles.field} ${styles.logoCol}`}>
<label>{t('bizProfile.logo')}</label>
<div className={styles.logoModeRow}>
<span
className={`${styles.logoModeLabel} ${!logoModeDark ? styles.logoModeLabelActive : ''}`}
>
{t('bizProfile.logo.light')}
</span>
<Switch
checked={logoModeDark}
onChange={setLogoModeDark}
aria-label={t('bizProfile.logo.modeSwitch')}
/>
<span
className={`${styles.logoModeLabel} ${logoModeDark ? styles.logoModeLabelActive : ''}`}
>
{t('bizProfile.logo.dark')}
</span>
</div>
<ImageCropper
value={logo}
onChange={setLogo}
key={logoModeDark ? 'logo-dark' : 'logo-light'}
value={logoModeDark ? logoDark : logo}
onChange={logoModeDark ? setLogoDark : setLogo}
outputFormat="png"
accept="image/png"
uploadLabel={t('bizProfile.logo.upload')}
@@ -40,6 +40,17 @@
color: var(--text-secondary);
}
.masterOffHint {
margin: 0;
padding: 10px 12px;
border-radius: var(--radius-sm);
font-size: 12px;
line-height: 1.5;
background: rgba(217, 119, 6, 0.1);
border: 1px solid rgba(217, 119, 6, 0.28);
color: #b45309;
}
.credentials {
display: grid;
grid-template-columns: repeat(12, minmax(0, 1fr));
+9
View File
@@ -157,6 +157,8 @@ export function SettingsPage() {
async function handleMellatToggle(checked: boolean) {
await patchEPayment('mellat-enabled', {
...ePayment,
// Turning a gateway on implies online payments are allowed at checkout.
enabled: checked ? true : ePayment.enabled,
gateways: {
...ePayment.gateways,
mellat: { ...ePayment.gateways.mellat, enabled: checked },
@@ -168,6 +170,7 @@ export function SettingsPage() {
async function handleZarinpalToggle(checked: boolean) {
await patchEPayment('zarinpal-enabled', {
...ePayment,
enabled: checked ? true : ePayment.enabled,
gateways: {
...ePayment.gateways,
zarinpal: { ...ePayment.gateways.zarinpal, enabled: checked },
@@ -265,6 +268,12 @@ export function SettingsPage() {
onChange={(checked) => void handleMasterToggle(checked)}
/>
</div>
{!ePayment.enabled &&
(ePayment.gateways.mellat.enabled || ePayment.gateways.zarinpal.enabled) ? (
<p className={styles.masterOffHint} role="status">
{t('website.ePayment.masterOffHint')}
</p>
) : null}
</div>
</section>