[WEB-4197] chore: auth forms semantics and accessibility #7128

This commit is contained in:
Aaryan Khandelwal
2025-05-30 18:22:20 +05:30
committed by GitHub
parent 01b685ea57
commit cb92108bf4
27 changed files with 252 additions and 75 deletions

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Sbalit postranní panel",
"expand_sidebar": "Rozbalit postranní panel",
"edition_badge": "Otevřít modal placených plánů"
},
"auth_forms": {
"clear_email": "Vymazat e-mail",
"show_password": "Zobrazit heslo",
"hide_password": "Skrýt heslo",
"close_alert": "Zavřít upozornění",
"close_popover": "Zavřít vyskakovací okno"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Seitenleiste einklappen",
"expand_sidebar": "Seitenleiste ausklappen",
"edition_badge": "Modal für kostenpflichtige Pläne öffnen"
},
"auth_forms": {
"clear_email": "E-Mail löschen",
"show_password": "Passwort anzeigen",
"hide_password": "Passwort verbergen",
"close_alert": "Warnung schließen",
"close_popover": "Popover schließen"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Collapse sidebar",
"expand_sidebar": "Expand sidebar",
"edition_badge": "Open paid plans' modal"
},
"auth_forms": {
"clear_email": "Clear email",
"show_password": "Show password",
"hide_password": "Hide password",
"close_alert": "Close alert",
"close_popover": "Close popover"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Colapsar barra lateral",
"expand_sidebar": "Expandir barra lateral",
"edition_badge": "Abrir modal de planes de pago"
},
"auth_forms": {
"clear_email": "Limpiar correo electrónico",
"show_password": "Mostrar contraseña",
"hide_password": "Ocultar contraseña",
"close_alert": "Cerrar alerta",
"close_popover": "Cerrar ventana emergente"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Réduire la barre latérale",
"expand_sidebar": "Étendre la barre latérale",
"edition_badge": "Ouvrir le modal des plans payants"
},
"auth_forms": {
"clear_email": "Effacer l'e-mail",
"show_password": "Afficher le mot de passe",
"hide_password": "Masquer le mot de passe",
"close_alert": "Fermer l'alerte",
"close_popover": "Fermer la fenêtre contextuelle"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Tutup sidebar",
"expand_sidebar": "Perluas sidebar",
"edition_badge": "Buka modal paket berbayar"
},
"auth_forms": {
"clear_email": "Hapus email",
"show_password": "Tampilkan kata sandi",
"hide_password": "Sembunyikan kata sandi",
"close_alert": "Tutup peringatan",
"close_popover": "Tutup popover"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Comprimi barra laterale",
"expand_sidebar": "Espandi barra laterale",
"edition_badge": "Apri modal piani a pagamento"
},
"auth_forms": {
"clear_email": "Cancella email",
"show_password": "Mostra password",
"hide_password": "Nascondi password",
"close_alert": "Chiudi avviso",
"close_popover": "Chiudi popover"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "サイドバーを折りたたむ",
"expand_sidebar": "サイドバーを展開",
"edition_badge": "有料プランのモーダルを開く"
},
"auth_forms": {
"clear_email": "メールをクリア",
"show_password": "パスワードを表示",
"hide_password": "パスワードを非表示",
"close_alert": "アラートを閉じる",
"close_popover": "ポップオーバーを閉じる"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "사이드바 축소",
"expand_sidebar": "사이드바 확장",
"edition_badge": "유료 플랜 모달 열기"
},
"auth_forms": {
"clear_email": "이메일 지우기",
"show_password": "비밀번호 표시",
"hide_password": "비밀번호 숨기기",
"close_alert": "알림 닫기",
"close_popover": "팝오버 닫기"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Zwiń pasek boczny",
"expand_sidebar": "Rozwiń pasek boczny",
"edition_badge": "Otwórz modal płatnych planów"
},
"auth_forms": {
"clear_email": "Wyczyść e-mail",
"show_password": "Pokaż hasło",
"hide_password": "Ukryj hasło",
"close_alert": "Zamknij alert",
"close_popover": "Zamknij popover"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Recolher barra lateral",
"expand_sidebar": "Expandir barra lateral",
"edition_badge": "Abrir modal de planos pagos"
},
"auth_forms": {
"clear_email": "Limpar e-mail",
"show_password": "Mostrar senha",
"hide_password": "Ocultar senha",
"close_alert": "Fechar alerta",
"close_popover": "Fechar popover"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Restrânge bara laterală",
"expand_sidebar": "Extinde bara laterală",
"edition_badge": "Deschide modalul planurilor plătite"
},
"auth_forms": {
"clear_email": "Șterge e-mailul",
"show_password": "Afișează parola",
"hide_password": "Ascunde parola",
"close_alert": "Închide alerta",
"close_popover": "Închide popover-ul"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Свернуть боковую панель",
"expand_sidebar": "Развернуть боковую панель",
"edition_badge": "Открыть модал платных планов"
},
"auth_forms": {
"clear_email": "Очистить email",
"show_password": "Показать пароль",
"hide_password": "Скрыть пароль",
"close_alert": "Закрыть уведомление",
"close_popover": "Закрыть всплывающее окно"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Zbaliť bočný panel",
"expand_sidebar": "Rozbaliť bočný panel",
"edition_badge": "Otvoriť modal platených plánov"
},
"auth_forms": {
"clear_email": "Vymazať e-mail",
"show_password": "Zobraziť heslo",
"hide_password": "Skryť heslo",
"close_alert": "Zavrieť upozornenie",
"close_popover": "Zavrieť vyskakovacie okno"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Kenar çubuğunu daralt",
"expand_sidebar": "Kenar çubuğunu genişlet",
"edition_badge": "Ücretli planlar modalını aç"
},
"auth_forms": {
"clear_email": "E-postayı temizle",
"show_password": "Şifreyi göster",
"hide_password": "Şifreyi gizle",
"close_alert": "Uyarıyı kapat",
"close_popover": "Açılır pencereyi kapat"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Згорнути бічну панель",
"expand_sidebar": "Розгорнути бічну панель",
"edition_badge": "Відкрити модал платних планів"
},
"auth_forms": {
"clear_email": "Очистити email",
"show_password": "Показати пароль",
"hide_password": "Приховати пароль",
"close_alert": "Закрити сповіщення",
"close_popover": "Закрити спливаюче вікно"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "Thu gọn thanh bên",
"expand_sidebar": "Mở rộng thanh bên",
"edition_badge": "Mở modal gói trả phí"
},
"auth_forms": {
"clear_email": "Xóa email",
"show_password": "Hiển thị mật khẩu",
"hide_password": "Ẩn mật khẩu",
"close_alert": "Đóng cảnh báo",
"close_popover": "Đóng popover"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "折叠侧边栏",
"expand_sidebar": "展开侧边栏",
"edition_badge": "打开付费计划模态框"
},
"auth_forms": {
"clear_email": "清除邮箱",
"show_password": "显示密码",
"hide_password": "隐藏密码",
"close_alert": "关闭警告",
"close_popover": "关闭弹出框"
}
}
}
}

View File

@@ -22,6 +22,13 @@
"collapse_sidebar": "摺疊側邊欄",
"expand_sidebar": "展開側邊欄",
"edition_badge": "打開付費計劃模態框"
},
"auth_forms": {
"clear_email": "清除電子郵件",
"show_password": "顯示密碼",
"hide_password": "隱藏密碼",
"close_alert": "關閉警告",
"close_popover": "關閉彈出框"
}
}
}
}

View File

@@ -69,7 +69,7 @@ export default function RootLayout({ children }: { children: React.ReactNode })
"app-container"
)}
>
<div className="w-full h-full overflow-hidden relative">{children}</div>
<main className="w-full h-full overflow-hidden relative">{children}</main>
</div>
</AppProvider>
</body>

View File

@@ -1,5 +1,7 @@
import { FC } from "react";
import { Info, X } from "lucide-react";
// plane imports
import { useTranslation } from "@plane/i18n";
// helpers
import { TAuthErrorInfo } from "@/helpers/authentication.helper";
@@ -10,20 +12,28 @@ type TAuthBanner = {
export const AuthBanner: FC<TAuthBanner> = (props) => {
const { bannerData, handleBannerData } = props;
// translation
const { t } = useTranslation();
if (!bannerData) return <></>;
return (
<div className="relative flex items-center p-2 rounded-md gap-2 border border-custom-primary-100/50 bg-custom-primary-100/10">
<div className="w-4 h-4 flex-shrink-0 relative flex justify-center items-center">
<div
role="alert"
className="relative flex items-center p-2 rounded-md gap-2 border border-custom-primary-100/50 bg-custom-primary-100/10"
>
<div className="size-4 flex-shrink-0 grid place-items-center">
<Info size={16} className="text-custom-primary-100" />
</div>
<div className="w-full text-sm font-medium text-custom-primary-100">{bannerData?.message}</div>
<div
className="relative ml-auto w-6 h-6 rounded-sm flex justify-center items-center transition-all cursor-pointer hover:bg-custom-primary-100/20 text-custom-primary-100/80"
onClick={() => handleBannerData && handleBannerData(undefined)}
<p className="w-full text-sm font-medium text-custom-primary-100">{bannerData?.message}</p>
<button
type="button"
className="relative ml-auto size-6 rounded-sm grid place-items-center transition-all hover:bg-custom-primary-100/20 text-custom-primary-100/80"
onClick={() => handleBannerData?.(undefined)}
aria-label={t("aria_labels.auth_forms.close_alert")}
>
<X className="w-4 h-4 flex-shrink-0" />
</div>
<X className="size-4" />
</button>
</div>
);
};

View File

@@ -102,9 +102,9 @@ export const AuthHeader: FC<TAuthHeader> = observer((props) => {
return (
<>
<div className="space-y-1 text-center">
<h3 className="text-3xl font-bold text-onboarding-text-100">
<h1 className="text-3xl font-bold text-onboarding-text-100">
{typeof header === "string" ? t(header) : header}
</h3>
</h1>
<p className="font-medium text-onboarding-text-400">{t(subHeader)}</p>
</div>
{children}

View File

@@ -47,7 +47,7 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
return (
<form onSubmit={handleFormSubmit} className="mt-5 space-y-4">
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="email">
<label htmlFor="email" className="text-sm text-onboarding-text-300 font-medium">
{t("auth.common.email.label")}
</label>
<div
@@ -76,13 +76,17 @@ export const AuthEmailForm: FC<TAuthEmailForm> = observer((props) => {
ref={inputRef}
/>
{email.length > 0 && (
<XCircle
className="h-[46px] w-11 px-3 stroke-custom-text-400 hover:cursor-pointer text-xs"
<button
type="button"
onClick={() => {
setEmail("");
inputRef.current?.focus();
}}
/>
className="absolute right-3 size-5 grid place-items-center"
aria-label={t("aria_labels.auth_forms.clear_email")}
>
<XCircle className="size-5 stroke-custom-text-400" />
</button>
)}
</div>
{emailError?.email && !isFocused && (

View File

@@ -45,8 +45,13 @@ export const ForgotPasswordPopover = () => {
>
<span className="flex-shrink-0">🤥</span>
<p className="text-xs">{t("auth.forgot_password.errors.smtp_not_enabled")}</p>
<button type="button" className="flex-shrink-0" onClick={() => close()}>
<X className="h-3 w-3 text-onboarding-text-200" />
<button
type="button"
className="flex-shrink-0 size-3 grid place-items-center"
onClick={() => close()}
aria-label={t("aria_labels.auth_forms.close_popover")}
>
<X className="size-3 text-onboarding-text-200" />
</button>
</div>
)}

View File

@@ -167,7 +167,7 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
<input type="hidden" value={passwordFormData.email} name="email" />
{nextPath && <input type="hidden" value={nextPath} name="next_path" />}
<div className="space-y-1">
<label className="text-sm font-medium text-onboarding-text-300" htmlFor="email">
<label htmlFor="email" className="text-sm font-medium text-onboarding-text-300">
{t("auth.common.email.label")}
</label>
<div
@@ -184,21 +184,26 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
disabled
/>
{passwordFormData.email.length > 0 && (
<XCircle
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
<button
type="button"
className="absolute right-3 size-5"
onClick={handleEmailClear}
/>
aria-label={t("aria_labels.auth_forms.clear_email")}
>
<XCircle className="size-5 stroke-custom-text-400" />
</button>
)}
</div>
</div>
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="password">
<label htmlFor="password" className="text-sm text-onboarding-text-300 font-medium">
{mode === EAuthModes.SIGN_IN ? t("auth.common.password.label") : t("auth.common.password.set_password")}
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword?.password ? "text" : "password"}
id="password"
name="password"
value={passwordFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
@@ -209,29 +214,33 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
autoComplete="on"
autoFocus
/>
{showPassword?.password ? (
<EyeOff
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("password")}
/>
) : (
<Eye
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("password")}
/>
)}
<button
type="button"
onClick={() => handleShowPassword("password")}
className="absolute right-3 size-5 grid place-items-center"
aria-label={t(
showPassword?.password ? "aria_labels.auth_forms.hide_password" : "aria_labels.auth_forms.show_password"
)}
>
{showPassword?.password ? (
<EyeOff className="size-5 stroke-custom-text-400" />
) : (
<Eye className="size-5 stroke-custom-text-400" />
)}
</button>
</div>
{passwordSupport}
</div>
{mode === EAuthModes.SIGN_UP && (
<div className="space-y-1">
<label className="text-sm text-onboarding-text-300 font-medium" htmlFor="confirm_password">
<label htmlFor="confirm-password" className="text-sm text-onboarding-text-300 font-medium">
{t("auth.common.password.confirm_password.label")}
</label>
<div className="relative flex items-center rounded-md bg-onboarding-background-200">
<Input
type={showPassword?.retypePassword ? "text" : "password"}
id="confirm-password"
name="confirm_password"
value={passwordFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
@@ -240,17 +249,22 @@ export const AuthPasswordForm: React.FC<Props> = observer((props: Props) => {
onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)}
/>
{showPassword?.retypePassword ? (
<EyeOff
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("retypePassword")}
/>
) : (
<Eye
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
onClick={() => handleShowPassword("retypePassword")}
/>
)}
<button
type="button"
className="absolute right-3 size-5 grid place-items-center"
aria-label={t(
showPassword?.retypePassword
? "aria_labels.auth_forms.hide_password"
: "aria_labels.auth_forms.show_password"
)}
onClick={() => handleShowPassword("retypePassword")}
>
{showPassword?.retypePassword ? (
<EyeOff className="size-5 stroke-custom-text-400" />
) : (
<Eye className="size-5 stroke-custom-text-400" />
)}
</button>
</div>
{!!passwordFormData.confirm_password &&
passwordFormData.password !== passwordFormData.confirm_password &&

View File

@@ -96,7 +96,7 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
<input type="hidden" value={uniqueCodeFormData.email} name="email" />
{nextPath && <input type="hidden" value={nextPath} name="next_path" />}
<div className="space-y-1">
<label className="text-sm font-medium text-onboarding-text-300" htmlFor="email">
<label htmlFor="email" className="text-sm font-medium text-onboarding-text-300">
{t("auth.common.email.label")}
</label>
<div
@@ -109,25 +109,30 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
value={uniqueCodeFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder={t("auth.common.email.placeholder")}
className={`disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0`}
className="disable-autofill-style h-[46px] w-full placeholder:text-onboarding-text-400 border-0"
autoComplete="on"
disabled
/>
{uniqueCodeFormData.email.length > 0 && (
<XCircle
className="absolute right-3 h-5 w-5 stroke-custom-text-400 hover:cursor-pointer"
<button
type="button"
className="absolute right-3 size-5 grid place-items-center"
aria-label={t("aria_labels.auth_forms.clear_email")}
onClick={handleEmailClear}
/>
>
<XCircle className="size-5 stroke-custom-text-400" />
</button>
)}
</div>
</div>
<div className="space-y-1">
<label className="text-sm font-medium text-onboarding-text-300" htmlFor="code">
<label htmlFor="unique-code" className="text-sm font-medium text-onboarding-text-300">
{t("auth.common.unique_code.label")}
</label>
<Input
name="code"
id="unique-code"
value={uniqueCodeFormData.code}
onChange={(e) => handleFormChange("code", e.target.value)}
placeholder={t("auth.common.unique_code.placeholder")}
@@ -142,11 +147,11 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
<button
type="button"
onClick={() => generateNewCode(uniqueCodeFormData.email)}
className={`${
className={
isRequestNewCodeDisabled
? "text-onboarding-text-400"
: "font-medium text-custom-primary-300 hover:text-custom-primary-200"
}`}
}
disabled={isRequestNewCodeDisabled}
>
{resendTimerCode > 0
@@ -160,7 +165,13 @@ export const AuthUniqueCodeForm: React.FC<TAuthUniqueCodeForm> = (props) => {
<div className="space-y-2.5">
<Button type="submit" variant="primary" className="w-full" size="lg" disabled={isButtonDisabled}>
{isRequestingNewCode ? t("auth.common.unique_code.sending_code") : isSubmitting ? <Spinner height="20px" width="20px" /> : t("common.continue")}
{isRequestingNewCode ? (
t("auth.common.unique_code.sending_code")
) : isSubmitting ? (
<Spinner height="20px" width="20px" />
) : (
t("common.continue")
)}
</Button>
</div>
</form>

View File

@@ -8,7 +8,7 @@ type Props = {
export const TermsAndConditions: FC<Props> = (props) => {
const { isSignUp = false } = props;
return (
<span className="flex items-center justify-center py-6">
<div className="flex items-center justify-center py-6">
<p className="text-center text-sm text-onboarding-text-200 whitespace-pre-line">
{isSignUp ? "By creating an account" : "By signing in"}, you agree to our{" \n"}
<Link href="https://plane.so/legals/terms-and-conditions" target="_blank" rel="noopener noreferrer">
@@ -20,6 +20,6 @@ export const TermsAndConditions: FC<Props> = (props) => {
</Link>
{"."}
</p>
</span>
</div>
);
};