Add shared empty states, customer product filters, and SSO handoff across dashboards.

Standardize list empty UI, searchable country fields, customer-products filter row, password/login polish, and one-time business dashboard handoff from the customer app.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Alireza Hassani
2026-08-19 19:46:57 +03:30
co-authored by Cursor
parent 832ac32a09
commit f8a9a28346
55 changed files with 1264 additions and 907 deletions
+89 -78
View File
@@ -140,6 +140,10 @@ export function LoginPage() {
err.message.includes('Unable to send SMS')
) {
setError(t('login.error.smsProvider'))
} else if (
err.message.includes('Invalid cell number or password')
) {
setError(t('login.error.invalidCredentials'))
} else {
setError(err.message)
}
@@ -371,15 +375,51 @@ export function LoginPage() {
}
}
const heading =
view === 'login'
? { title: t('login.welcome'), subtitle: t('login.subtitle') }
: view === 'signup'
? { title: t('signup.title'), subtitle: null }
: view === 'forgot'
? {
title: t('forgot.title'),
subtitle: smsStep === 'phone' ? t('forgot.subtitlePhone') : t('forgot.subtitleCode'),
}
: {
title: t('otp.title'),
subtitle: smsStep === 'phone' ? t('otp.subtitlePhone') : t('otp.subtitleCode'),
}
return (
<div className={styles.page}>
<BackgroundOrbs />
<div className={styles.brand}>
<img src={logoUrl || meshkeeLogo} alt="" className={styles.logo} />
<div className={styles.brandText}>
<span className={styles.domain} lang={locale === 'fa' ? 'fa' : undefined}>
{businessName || tenantDomain}
</span>
<span className={styles.appName} lang="en" dir="ltr">
{businessNameEn || tenantDomain}
</span>
</div>
</div>
<div className={styles.card}>
<div className={styles.brand}>
<img src={logoUrl || meshkeeLogo} alt="" className={styles.logo} />
<div className={styles.brandText}>
<span className={styles.domain}>{businessName || tenantDomain}</span>
<span className={styles.appName}>{businessNameEn || tenantDomain}</span>
{(view === 'forgot' || view === 'otp') && (
<button
type="button"
className={styles.backBtn}
onClick={() => switchView('login')}
disabled={isSubmitting}
>
<ArrowLeft size={18} />
{view === 'forgot' ? t('forgot.back') : t('otp.back')}
</button>
)}
<div className={styles.cardHeader}>
<div className={styles.heading}>
<h1 className={styles.title}>{heading.title}</h1>
{heading.subtitle ? <p className={styles.subtitle}>{heading.subtitle}</p> : null}
</div>
<div className={styles.langSelect}>
<LanguageSelect />
@@ -388,9 +428,6 @@ export function LoginPage() {
{view === 'login' && (
<>
<h1 className={styles.title}>{t('login.welcome')}</h1>
<p className={styles.subtitle}>{t('login.subtitle')}</p>
<form className={styles.form} onSubmit={handleLogin}>
{error && (
<div className={styles.error} role="alert">
@@ -490,8 +527,6 @@ export function LoginPage() {
{view === 'signup' && (
<>
<h1 className={styles.title}>{t('signup.title')}</h1>
<form className={styles.form} onSubmit={handleSignup}>
{error && (
<div className={styles.error} role="alert">
@@ -578,24 +613,6 @@ export function LoginPage() {
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
<PasswordStrengthMeter
password={password}
id="signup-password-strength"
labels={{
title: t('password.strength'),
empty: t('password.strength.empty'),
weak: t('password.strength.weak'),
fair: t('password.strength.fair'),
good: t('password.strength.good'),
strong: t('password.strength.strong'),
rules: {
length: t('password.rule.length'),
upper: t('password.rule.upper'),
number: t('password.rule.number'),
special: t('password.rule.special'),
},
}}
/>
</div>
<div className={styles.field}>
@@ -613,14 +630,36 @@ export function LoginPage() {
required
minLength={8}
disabled={isSubmitting}
aria-describedby="signup-password-strength signup-password-strength-rules"
/>
</div>
<PasswordStrengthMeter
password={password}
confirmPassword={confirmPassword}
id="signup-password-strength"
labels={{
title: t('password.strength'),
empty: t('password.strength.empty'),
weak: t('password.strength.weak'),
fair: t('password.strength.fair'),
good: t('password.strength.good'),
strong: t('password.strength.strong'),
rules: {
length: t('password.rule.length'),
upper: t('password.rule.upper'),
number: t('password.rule.number'),
match: t('password.rule.match'),
},
}}
/>
</div>
<button
type="submit"
className={styles.submitBtn}
disabled={isSubmitting || !isPasswordStrong(password)}
disabled={
isSubmitting || !isPasswordStrong(password) || password !== confirmPassword
}
>
{isSubmitting ? t('signup.creating') : t('signup.create')}
</button>
@@ -642,21 +681,6 @@ export function LoginPage() {
{view === 'forgot' && (
<>
<button
type="button"
className={styles.backBtn}
onClick={() => switchView('login')}
disabled={isSubmitting}
>
<ArrowLeft size={18} />
{t('forgot.back')}
</button>
<h1 className={styles.title}>{t('forgot.title')}</h1>
<p className={styles.subtitle}>
{smsStep === 'phone' ? t('forgot.subtitlePhone') : t('forgot.subtitleCode')}
</p>
<form className={styles.form} onSubmit={handleResetPassword}>
{error && (
<div className={styles.error} role="alert">
@@ -746,24 +770,6 @@ export function LoginPage() {
{showPassword ? <EyeOff size={18} /> : <Eye size={18} />}
</button>
</div>
<PasswordStrengthMeter
password={newPassword}
id="forgot-password-strength"
labels={{
title: t('password.strength'),
empty: t('password.strength.empty'),
weak: t('password.strength.weak'),
fair: t('password.strength.fair'),
good: t('password.strength.good'),
strong: t('password.strength.strong'),
rules: {
length: t('password.rule.length'),
upper: t('password.rule.upper'),
number: t('password.rule.number'),
special: t('password.rule.special'),
},
}}
/>
</div>
<div className={styles.field}>
@@ -781,8 +787,28 @@ export function LoginPage() {
required
minLength={8}
disabled={isSubmitting}
aria-describedby="forgot-password-strength"
/>
</div>
<PasswordStrengthMeter
password={newPassword}
confirmPassword={confirmNewPassword}
id="forgot-password-strength"
labels={{
title: t('password.strength'),
empty: t('password.strength.empty'),
weak: t('password.strength.weak'),
fair: t('password.strength.fair'),
good: t('password.strength.good'),
strong: t('password.strength.strong'),
rules: {
length: t('password.rule.length'),
upper: t('password.rule.upper'),
number: t('password.rule.number'),
match: t('password.rule.match'),
},
}}
/>
</div>
<div className={styles.resendRow}>
@@ -803,7 +829,7 @@ export function LoginPage() {
<button
type="submit"
className={styles.submitBtn}
disabled={isSubmitting || !isPasswordStrong(newPassword)}
disabled={isSubmitting || !isPasswordStrong(newPassword) || newPassword !== confirmNewPassword}
>
{isSubmitting ? t('forgot.verifying') : t('forgot.reset')}
</button>
@@ -815,21 +841,6 @@ export function LoginPage() {
{view === 'otp' && (
<>
<button
type="button"
className={styles.backBtn}
onClick={() => switchView('login')}
disabled={isSubmitting}
>
<ArrowLeft size={18} />
{t('otp.back')}
</button>
<h1 className={styles.title}>{t('otp.title')}</h1>
<p className={styles.subtitle}>
{smsStep === 'phone' ? t('otp.subtitlePhone') : t('otp.subtitleCode')}
</p>
<form className={styles.form} onSubmit={handleOtpLogin}>
{error && (
<div className={styles.error} role="alert">