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:
co-authored by
Cursor
parent
832ac32a09
commit
f8a9a28346
@@ -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">
|
||||
|
||||
Reference in New Issue
Block a user