UI - МЕЖПРОЕКТНАЯ КОММУНИКАЦИЯ: auth screens, error pages и новый канон внешних контуров

This commit is contained in:
DCCONSTRUCTIONS
2026-04-20 10:05:31 +03:00
parent df68c96795
commit c6ace8b9cc
25 changed files with 564 additions and 360 deletions
@@ -23,21 +23,18 @@ export function AuthBanner(props: TAuthBanner) {
if (!message) return <></>;
return (
<div
role="alert"
className="relative flex items-center gap-2 rounded-md border border-accent-strong/50 bg-accent-primary/10 p-2"
>
<div role="alert" className="nodedc-auth-banner relative flex items-center gap-2 p-3">
<div className="grid size-4 flex-shrink-0 place-items-center">
<Info size={16} className="text-accent-primary" />
<Info size={16} className="text-current" />
</div>
<p className="w-full text-13 font-medium text-accent-primary">{message}</p>
<p className="w-full text-13 font-medium text-current">{message}</p>
<button
type="button"
className="relative ml-auto grid size-6 place-items-center rounded-xs text-accent-primary/80 transition-all hover:bg-accent-primary/20"
className="relative ml-auto grid size-6 place-items-center rounded-full text-current/80 transition-all hover:bg-white/10"
onClick={() => handleBannerData?.(undefined)}
aria-label={t("aria_labels.auth_forms.close_alert")}
>
<CloseIcon className="size-4" />
<CloseIcon className="size-4 text-current" />
</button>
</div>
);
@@ -83,9 +83,9 @@ type TAuthHeaderBase = {
export function AuthHeaderBase(props: TAuthHeaderBase) {
return (
<div className="flex flex-col gap-1">
<span className="text-h4-semibold text-primary">{props.header}</span>
<span className="text-h4-semibold text-placeholder">{props.subHeader}</span>
<div className="flex flex-col gap-2">
<span className="text-[2rem] leading-10 font-semibold text-primary">{props.header}</span>
<span className="text-[1.9rem] leading-10 font-semibold text-placeholder">{props.subHeader}</span>
</div>
);
}
@@ -157,8 +157,8 @@ export const AuthRoot = observer(function AuthRoot(props: TAuthRoot) {
function AuthContainer({ children }: { children: React.ReactNode }) {
return (
<div className="mt-10 flex w-full flex-grow flex-col items-center justify-center py-6">
<div className="relative flex w-full max-w-[22.5rem] flex-col gap-6">{children}</div>
<div className="mt-6 flex w-full flex-grow flex-col items-center justify-center py-8">
<div className="nodedc-auth-shell relative flex w-full max-w-[28rem] flex-col gap-6">{children}</div>
</div>
);
}
@@ -56,9 +56,10 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
</label>
<div
className={cn(
`relative flex items-center rounded-md border bg-surface-1`,
!isFocused && Boolean(emailError?.email) ? `border-danger-strong` : `border-strong`
"nodedc-auth-input-shell relative flex items-center",
!isFocused && Boolean(emailError?.email) && "border-danger-strong/0"
)}
data-error={!isFocused && Boolean(emailError?.email)}
onFocus={() => {
setIsFocused(true);
}}
@@ -73,7 +74,7 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder={t("auth.common.email.placeholder")}
className={`h-10 w-full border-0 disable-autofill-style placeholder:text-placeholder autofill:bg-danger-primary focus:bg-none active:bg-transparent`}
className="nodedc-auth-input h-12 w-full pr-12 disable-autofill-style autofill:bg-danger-primary focus:bg-none active:bg-transparent"
autoComplete="off"
autoFocus
ref={inputRef}
@@ -100,7 +101,7 @@ export const AuthEmailForm = observer(function AuthEmailForm(props: TAuthEmailFo
</p>
)}
</div>
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
<Button type="submit" variant="primary" className="nodedc-auth-primary-button" size="xl" disabled={isButtonDisabled}>
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("common.continue")}
</Button>
</form>
@@ -88,7 +88,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
<Link
data-ph-element={AUTH_TRACKER_ELEMENTS.FORGOT_PASSWORD_FROM_SIGNIN}
href={`/accounts/forgot-password?email=${encodeURIComponent(email)}`}
className="text-11 font-medium text-accent-primary"
className="nodedc-auth-link text-11 font-medium"
>
{t("auth.common.forgot_password")}
</Link>
@@ -174,7 +174,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
<label htmlFor="email" className="text-13 font-medium text-tertiary">
{t("auth.common.email.label")}
</label>
<div className={`relative flex items-center rounded-md border border-strong bg-surface-1`}>
<div className="nodedc-auth-input-shell relative flex items-center">
<Input
id="email"
name="email"
@@ -182,7 +182,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
value={passwordFormData.email}
onChange={(e) => handleFormChange("email", e.target.value)}
placeholder={t("auth.common.email.placeholder")}
className={`h-10 w-full border-0 disable-autofill-style placeholder:text-placeholder`}
className="nodedc-auth-input h-12 w-full pr-12 disable-autofill-style"
disabled
/>
{passwordFormData.email.length > 0 && (
@@ -202,7 +202,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
<label htmlFor="password" className="text-13 font-medium text-tertiary">
{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-surface-1">
<div className="nodedc-auth-input-shell relative flex items-center">
<Input
type={showPassword?.password ? "text" : "password"}
id="password"
@@ -210,7 +210,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
value={passwordFormData.password}
onChange={(e) => handleFormChange("password", e.target.value)}
placeholder={t("auth.common.password.placeholder")}
className="h-10 w-full border border-strong !bg-surface-1 pr-12 disable-autofill-style placeholder:text-placeholder"
className="nodedc-auth-input h-12 w-full pr-12 disable-autofill-style"
onFocus={() => setIsPasswordInputFocused(true)}
onBlur={() => setIsPasswordInputFocused(false)}
autoComplete="off"
@@ -239,7 +239,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
<label htmlFor="confirm-password" className="text-13 font-medium text-tertiary">
{t("auth.common.password.confirm_password.label")}
</label>
<div className="relative flex items-center rounded-md bg-surface-1">
<div className="nodedc-auth-input-shell relative flex items-center">
<Input
type={showPassword?.retypePassword ? "text" : "password"}
id="confirm-password"
@@ -247,7 +247,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
value={passwordFormData.confirm_password}
onChange={(e) => handleFormChange("confirm_password", e.target.value)}
placeholder={t("auth.common.password.confirm_password.placeholder")}
className="h-10 w-full border border-strong !bg-surface-1 pr-12 disable-autofill-style placeholder:text-placeholder"
className="nodedc-auth-input h-12 w-full pr-12 disable-autofill-style"
onFocus={() => setIsRetryPasswordInputFocused(true)}
onBlur={() => setIsRetryPasswordInputFocused(false)}
autoComplete="off"
@@ -280,7 +280,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
<div className="space-y-2.5">
{mode === EAuthModes.SIGN_IN ? (
<>
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
<Button type="submit" variant="primary" className="nodedc-auth-primary-button" size="xl" disabled={isButtonDisabled}>
{isSubmitting ? (
<Spinner height="20px" width="20px" />
) : isSMTPConfigured ? (
@@ -295,7 +295,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
data-ph-element={AUTH_TRACKER_ELEMENTS.SIGN_IN_WITH_UNIQUE_CODE}
onClick={redirectToUniqueCodeSignIn}
variant="secondary"
className="w-full"
className="nodedc-empty-state-secondary w-full"
size="xl"
>
{t("auth.common.sign_in_with_unique_code")}
@@ -303,7 +303,7 @@ export const AuthPasswordForm = observer(function AuthPasswordForm(props: Props)
)}
</>
) : (
<Button type="submit" variant="primary" className="w-full" size="xl" disabled={isButtonDisabled}>
<Button type="submit" variant="primary" className="nodedc-auth-primary-button" size="xl" disabled={isButtonDisabled}>
{isSubmitting ? <Spinner height="20px" width="20px" /> : t("auth_actions.sign_up")}
</Button>
)}
@@ -15,7 +15,7 @@ type AuthBaseProps = {
export function AuthBase({ authType }: AuthBaseProps) {
return (
<div className="relative z-10 flex h-screen w-screen flex-col items-center overflow-hidden overflow-y-auto px-8 pt-6 pb-10">
<div className="relative z-10 flex h-screen w-screen flex-col items-center overflow-hidden overflow-y-auto bg-canvas px-8 pt-8 pb-10">
<AuthHeader type={authType} />
<AuthRoot authMode={authType} />
</div>
@@ -50,7 +50,7 @@ export const AuthHeader = observer(function AuthHeader({ type }: AuthHeaderProps
<Link
data-ph-element={AUTH_TRACKER_ELEMENTS.NAVIGATE_TO_SIGN_UP}
href={authContentMap[type].linkHref}
className="text-body-sm-semibold text-accent-primary hover:underline"
className="nodedc-auth-link text-body-sm-semibold"
>
{t(authContentMap[type].linkText)}
</Link>
@@ -71,9 +71,9 @@ export function AuthHeaderBase(props: TAuthHeaderBase) {
return (
<>
<PageHead title={pageTitle + " - NODE.DC"} />
<div className="sticky top-0 flex w-full flex-shrink-0 items-center justify-between gap-6">
<div className="sticky top-0 flex w-full flex-shrink-0 items-center justify-between gap-6 px-2 py-1">
<Link href="/">
<PlaneLockup height={20} width={95} className="text-primary" />
<PlaneLockup height={40} width={190} className="text-primary transition-opacity hover:opacity-90" />
</Link>
{additionalAction}
</div>