Add logo aspect ratios and businesses renewal sort with low-SMS alert.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-09-18 17:17:59 +03:30
co-authored by Cursor
parent e020c74fb1
commit c24f6c06f7
7 changed files with 229 additions and 34 deletions
+2
View File
@@ -150,6 +150,7 @@ const en = {
'bizProfile.logo.change': 'Change logo',
'bizProfile.logo.lightTheme': 'Logo for light theme',
'bizProfile.logo.darkTheme': 'Logo for dark theme',
'bizProfile.logo.aspectAria': 'Logo aspect ratio',
'bizProfile.favicon': 'Favicon',
'bizProfile.favicon.upload': 'Upload favicon',
'bizProfile.favicon.hint': 'Square PNG recommended',
@@ -2695,6 +2696,7 @@ const fa: Record<MessageKey, string> = {
'bizProfile.logo.change': 'تغییر لوگو',
'bizProfile.logo.lightTheme': 'لوگو در تم روشن',
'bizProfile.logo.darkTheme': 'لوگو در تم تیره',
'bizProfile.logo.aspectAria': 'نسبت تصویر لوگو',
'bizProfile.favicon': 'فاوآیکن',
'bizProfile.favicon.upload': 'آپلود فاوآیکن',
'bizProfile.favicon.hint': 'PNG مربعی توصیه می‌شود',
@@ -40,8 +40,30 @@
margin-bottom: 0;
}
.logoLightCol,
.logoDarkCol,
.logosBlock {
grid-column: span 12;
min-width: 0;
display: flex;
flex-direction: column;
align-items: center;
gap: 8px;
}
.logosPair {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 12px 16px;
width: 100%;
min-width: 0;
}
.logoField {
min-width: 0;
margin-bottom: 0;
display: flex;
flex-direction: column;
}
.faviconCol {
grid-column: span 12;
min-width: 0;
@@ -229,12 +251,8 @@
}
@media (min-width: 1280px) {
.logoLightCol {
grid-column: span 2;
}
.logoDarkCol {
grid-column: span 2;
.logosBlock {
grid-column: span 4;
}
.faviconCol {
+79 -25
View File
@@ -39,6 +39,14 @@ import styles from './BusinessProfilePage.module.css'
type AddressDraft = AddressListItem
type LogoAspectId = '1:1' | '3:2' | '16:9'
const LOGO_ASPECTS: { id: LogoAspectId; aspect: number }[] = [
{ id: '1:1', aspect: 1 },
{ id: '3:2', aspect: 3 / 2 },
{ id: '16:9', aspect: 16 / 9 },
]
const EMPTY_SOCIAL: BusinessSocialMedia = {
whatsapp: '',
telegram: '',
@@ -82,6 +90,7 @@ export function BusinessProfilePage() {
const [logoMediaId, setLogoMediaId] = useState<string | null>(null)
const [logoDark, setLogoDark] = useState<string | null>(null)
const [logoDarkMediaId, setLogoDarkMediaId] = useState<string | null>(null)
const [logoAspect, setLogoAspect] = useState<LogoAspectId>('1:1')
const [favicon, setFavicon] = useState<string | null>(null)
const [faviconMediaId, setFaviconMediaId] = useState<string | null>(null)
const [addresses, setAddresses] = useState<AddressDraft[]>([createEmptyAddressItem()])
@@ -94,6 +103,22 @@ export function BusinessProfilePage() {
() => flattenBusinessActivityCategories(activityCategories),
[activityCategories],
)
const selectedLogoAspect =
LOGO_ASPECTS.find((option) => option.id === logoAspect)?.aspect ?? 1
function handleLogoAspectChange(next: LogoAspectId) {
if (next === logoAspect) return
setLogoAspect(next)
// Applied crops are locked to the old ratio — drop them so the user recrops.
if (logo) {
setLogo(null)
setLogoMediaId(null)
}
if (logoDark) {
setLogoDark(null)
setLogoDarkMediaId(null)
}
}
const addressLabels = useMemo(
() => ({
@@ -378,32 +403,60 @@ export function BusinessProfilePage() {
<section className={styles.section}>
<h3 className={styles.sectionTitle}>{t('bizProfile.section.basic')}</h3>
<div className={formStyles.formGrid}>
<div className={`${formStyles.field} ${styles.logoLightCol}`}>
<label className={styles.mediaFieldLabel}>{t('bizProfile.logo.lightTheme')}</label>
<ImageCropper
compact
value={logo}
onChange={setLogo}
outputFormat="png"
accept="image/png"
uploadLabel={t('bizProfile.logo.upload')}
hint={t('bizProfile.logo.hint')}
changeLabel={t('bizProfile.logo.change')}
/>
</div>
<div className={styles.logosBlock}>
<div className={styles.logosPair}>
<div className={`${formStyles.field} ${styles.logoField}`}>
<label className={styles.mediaFieldLabel}>{t('bizProfile.logo.lightTheme')}</label>
<ImageCropper
compact
value={logo}
onChange={setLogo}
aspect={selectedLogoAspect}
outputFormat="png"
accept="image/png"
uploadLabel={t('bizProfile.logo.upload')}
hint={t('bizProfile.logo.hint')}
changeLabel={t('bizProfile.logo.change')}
/>
</div>
<div className={`${formStyles.field} ${styles.logoDarkCol}`}>
<label className={styles.mediaFieldLabel}>{t('bizProfile.logo.darkTheme')}</label>
<ImageCropper
compact
value={logoDark}
onChange={setLogoDark}
outputFormat="png"
accept="image/png"
uploadLabel={t('bizProfile.logo.upload')}
hint={t('bizProfile.logo.hint')}
changeLabel={t('bizProfile.logo.change')}
/>
<div className={`${formStyles.field} ${styles.logoField}`}>
<label className={styles.mediaFieldLabel}>{t('bizProfile.logo.darkTheme')}</label>
<ImageCropper
compact
value={logoDark}
onChange={setLogoDark}
aspect={selectedLogoAspect}
outputFormat="png"
accept="image/png"
uploadLabel={t('bizProfile.logo.upload')}
hint={t('bizProfile.logo.hint')}
changeLabel={t('bizProfile.logo.change')}
/>
</div>
</div>
<div
className={formStyles.aspectSwitch}
role="radiogroup"
aria-label={t('bizProfile.logo.aspectAria')}
>
{LOGO_ASPECTS.map((option) => (
<button
key={option.id}
type="button"
role="radio"
aria-checked={logoAspect === option.id}
className={`${formStyles.aspectSwitchBtn} ${
logoAspect === option.id ? formStyles.aspectSwitchBtnSelected : ''
}`}
onClick={() => handleLogoAspectChange(option.id)}
disabled={isSaving}
>
{option.id}
</button>
))}
</div>
</div>
<div className={`${formStyles.field} ${styles.faviconCol}`}>
@@ -412,6 +465,7 @@ export function BusinessProfilePage() {
compact
value={favicon}
onChange={setFavicon}
aspect={1}
outputFormat="png"
accept="image/png"
uploadLabel={t('bizProfile.favicon.upload')}