Make public invoices RTL for Farsi businesses and fix invoice back-arrow direction.

Also add optional English name fields on the add-customer modal to match the API.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-11 15:46:18 +03:30
co-authored by Cursor
parent a50685ec69
commit f21041b950
9 changed files with 231 additions and 44 deletions
@@ -1,3 +1,7 @@
.wideModal {
max-width: min(800px, calc(100vw - 32px));
}
.formGrid {
display: grid;
grid-template-columns: repeat(2, 1fr);
@@ -8,15 +12,29 @@
grid-column: 1 / -1;
}
.hint {
margin-bottom: 12px;
font-size: 12px;
line-height: 1.45;
.keyPoints {
margin: 0 0 14px;
padding-inline-start: 1.15em;
list-style: disc;
font-size: 12.5px;
line-height: 1.5;
color: var(--text-secondary);
}
@media (max-width: 520px) {
.keyPoints li {
white-space: nowrap;
}
.keyPoints li + li {
margin-top: 4px;
}
@media (max-width: 640px) {
.formGrid {
grid-template-columns: 1fr;
}
.keyPoints li {
white-space: normal;
}
}
@@ -28,6 +28,8 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
const [closing, setClosing] = useState(false)
const [firstName, setFirstName] = useState('')
const [lastName, setLastName] = useState('')
const [firstNameEn, setFirstNameEn] = useState('')
const [lastNameEn, setLastNameEn] = useState('')
const [cellNumber, setCellNumber] = useState('')
const [password, setPassword] = useState('')
const [passwordConfirm, setPasswordConfirm] = useState('')
@@ -40,6 +42,8 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
setClosing(false)
setFirstName('')
setLastName('')
setFirstNameEn('')
setLastNameEn('')
setCellNumber('')
setPassword('')
setPasswordConfirm('')
@@ -86,6 +90,16 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
}
}
const trimmedFirstEn = firstNameEn.trim()
const trimmedLastEn = lastNameEn.trim()
if (
(trimmedFirstEn && trimmedFirstEn.length < 2) ||
(trimmedLastEn && trimmedLastEn.length < 2)
) {
setError(t('customers.addModal.errorNameEnLength'))
return
}
setIsSubmitting(true)
setError('')
try {
@@ -93,6 +107,8 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
cellNumber: normalizedCell,
firstName: firstName.trim(),
lastName: lastName.trim(),
...(trimmedFirstEn ? { firstNameEn: trimmedFirstEn } : {}),
...(trimmedLastEn ? { lastNameEn: trimmedLastEn } : {}),
...(trimmedPassword ? { password: trimmedPassword } : {}),
})
onCreated({
@@ -119,7 +135,7 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
onClick={onClose}
>
<div
className={`${modalStyles.modal} ${closing ? modalStyles.modalOut : modalStyles.modalIn}`}
className={`${modalStyles.modal} ${formStyles.wideModal} ${closing ? modalStyles.modalOut : modalStyles.modalIn}`}
onClick={(e) => e.stopPropagation()}
role="dialog"
aria-modal="true"
@@ -132,7 +148,6 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
<h3 id="add-customer-title" className={modalStyles.title}>
{t('customers.addModal.title')}
</h3>
<p className={modalStyles.subtitle}>{t('customers.addModal.subtitle')}</p>
</div>
<button
type="button"
@@ -145,29 +160,58 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
</div>
<div className={modalStyles.body}>
<p className={formStyles.hint}>{t('customers.addModal.hint')}</p>
<ul className={formStyles.keyPoints}>
<li>{t('customers.addModal.pointCreate')}</li>
<li>{t('customers.addModal.pointPassword')}</li>
</ul>
{error && <p className={modalStyles.errorText}>{error}</p>}
<div className={formStyles.formGrid}>
<div className={modalStyles.field}>
<label htmlFor="add-customer-first-name">{t('customers.addModal.firstName')}</label>
<label htmlFor="add-customer-first-name">{t('customers.addModal.firstNameFa')}</label>
<input
id="add-customer-first-name"
value={firstName}
onChange={(e) => setFirstName(e.target.value)}
disabled={isSubmitting}
dir={isFa ? 'rtl' : 'ltr'}
dir="rtl"
lang="fa"
className="faText"
/>
</div>
<div className={modalStyles.field}>
<label htmlFor="add-customer-last-name">{t('customers.addModal.lastName')}</label>
<label htmlFor="add-customer-last-name">{t('customers.addModal.lastNameFa')}</label>
<input
id="add-customer-last-name"
value={lastName}
onChange={(e) => setLastName(e.target.value)}
disabled={isSubmitting}
dir={isFa ? 'rtl' : 'ltr'}
dir="rtl"
lang="fa"
className="faText"
/>
</div>
<div className={modalStyles.field}>
<label htmlFor="add-customer-first-name-en">{t('customers.addModal.firstNameEn')}</label>
<input
id="add-customer-first-name-en"
value={firstNameEn}
onChange={(e) => setFirstNameEn(e.target.value)}
disabled={isSubmitting}
dir="ltr"
lang="en"
/>
</div>
<div className={modalStyles.field}>
<label htmlFor="add-customer-last-name-en">{t('customers.addModal.lastNameEn')}</label>
<input
id="add-customer-last-name-en"
value={lastNameEn}
onChange={(e) => setLastNameEn(e.target.value)}
disabled={isSubmitting}
dir="ltr"
lang="en"
/>
</div>
<div className={`${modalStyles.field} ${formStyles.formFull}`}>
@@ -233,8 +277,7 @@ export function AddCustomerModal({ open, onClose, onCreated }: AddCustomerModalP
</div>
</div>
</div>
</div>
,
document.body,
</div>,
document.body,
)
}
@@ -192,6 +192,10 @@
text-decoration: underline;
}
:global([dir='rtl']) .backLink svg {
transform: scaleX(-1);
}
.section {
padding: 16px;
background: var(--glass-bg);
+18
View File
@@ -833,8 +833,16 @@ const en = {
'Creates a verified customer account or links an existing user to your business.',
'customers.addModal.hint':
'Password is required only for new accounts. Existing users are added as verified customers.',
'customers.addModal.pointCreate':
'Creates a verified customer account or links an existing user to your business.',
'customers.addModal.pointPassword':
'Password is required only for new accounts. Existing users are added as verified customers.',
'customers.addModal.firstName': 'First name',
'customers.addModal.lastName': 'Last name',
'customers.addModal.firstNameFa': 'First name (FA)',
'customers.addModal.lastNameFa': 'Last name (FA)',
'customers.addModal.firstNameEn': 'First name (EN)',
'customers.addModal.lastNameEn': 'Last name (EN)',
'customers.addModal.cell': 'Cell number',
'customers.addModal.cellPlaceholder': '0912...',
'customers.addModal.password': 'Password (new users)',
@@ -846,6 +854,7 @@ const en = {
'customers.addModal.error': 'Unable to add customer.',
'customers.addModal.errorPasswordMatch': 'Passwords do not match.',
'customers.addModal.errorPasswordLength': 'Password must be at least 8 characters.',
'customers.addModal.errorNameEnLength': 'English names must be at least 2 characters.',
'customers.access.title': 'Change access',
'customers.access.subtitle': 'Choose customer or manager access for {name}.',
'customers.access.customer': 'Customer',
@@ -2434,8 +2443,16 @@ const fa: Record<MessageKey, string> = {
'یک حساب تأییدشده مشتری می‌سازد یا کاربر موجود را به کسب‌وکار شما وصل می‌کند.',
'customers.addModal.hint':
'رمز عبور فقط برای حساب‌های جدید لازم است. کاربران موجود به‌عنوان مشتری تأییدشده اضافه می‌شوند.',
'customers.addModal.pointCreate':
'یک حساب تأییدشده مشتری می‌سازد یا کاربر موجود را به کسب‌وکار شما وصل می‌کند.',
'customers.addModal.pointPassword':
'رمز عبور فقط برای حساب‌های جدید لازم است. کاربران موجود به‌عنوان مشتری تأییدشده اضافه می‌شوند.',
'customers.addModal.firstName': 'نام',
'customers.addModal.lastName': 'نام خانوادگی',
'customers.addModal.firstNameFa': 'نام (فارسی)',
'customers.addModal.lastNameFa': 'نام خانوادگی (فارسی)',
'customers.addModal.firstNameEn': 'نام (انگلیسی)',
'customers.addModal.lastNameEn': 'نام خانوادگی (انگلیسی)',
'customers.addModal.cell': 'شماره موبایل',
'customers.addModal.cellPlaceholder': '۰۹۱۲...',
'customers.addModal.password': 'رمز عبور (کاربران جدید)',
@@ -2447,6 +2464,7 @@ const fa: Record<MessageKey, string> = {
'customers.addModal.error': 'افزودن مشتری ممکن نشد.',
'customers.addModal.errorPasswordMatch': 'رمزهای عبور یکسان نیستند.',
'customers.addModal.errorPasswordLength': 'رمز عبور باید حداقل ۸ کاراکتر باشد.',
'customers.addModal.errorNameEnLength': 'نام انگلیسی باید حداقل ۲ کاراکتر باشد.',
'customers.access.title': 'تغییر دسترسی',
'customers.access.subtitle': 'دسترسی مشتری یا مدیر را برای {name} انتخاب کنید.',
'customers.access.customer': 'مشتری',
@@ -76,6 +76,8 @@ export interface CreateCustomerPayload {
cellNumber: string
firstName: string
lastName: string
firstNameEn?: string
lastNameEn?: string
password?: string
email?: string
}