Files
loyaly_cutomerweb/src/features/auth/components/LoginSplit.tsx
2026-09-25 16:31:10 +05:30

136 lines
5.8 KiB
TypeScript

'use client';
import Link from 'next/link';
import {BrandLogo} from '@/shared/components/brand/BrandLogo';
import {LoginCredentialsForm} from './LoginCredentialsForm';
import {LoginHeroPanel} from './LoginHeroPanel';
/**
* The sign-in screen's frame: the ivory canvas, the ambient yellow shapes, the
* split card, and the two halves that fill it.
*
* This file used to be 314 lines holding the photograph, the form, the fake
* submit and the page chrome at once. It is now the composition only —
* behaviour lives in useLoginForm, the form markup in LoginCredentialsForm,
* the carousel in LoginHeroPanel — which is what makes each of them separately
* readable and separately changeable.
*
* ── On the literal colour values here ────────────────────────────────────
* The workspace is monochrome and token-driven; this screen deliberately is
* not. It predates the workspace, it is the only page a merchant sees before
* the product, it renders outside the Astryx shell, and it is pinned to the
* light palette whatever the theme cookie says (see `.login-surface` in
* globals.css). The one hue used is #F4C430 — the same Loyaly yellow as
* `--color-brand-warm`, kept literal because no Astryx token is in scope here.
*/
/**
* `/join` renders in the same frame: redeeming an invitation is the other way
* into the console, and a second design for it would be a second thing to
* keep in step. Called with no props, this is exactly the sign-in screen.
*/
export function LoginSplit({
title = 'Welcome back',
description = 'Access your Merchant Operating System to manage stores, rewards, staff and AI insights.',
children,
}: {
title?: string;
description?: string;
children?: React.ReactNode;
} = {}) {
return (
<div className="login-surface min-h-screen w-full relative flex flex-col items-center justify-center gap-4 p-4 sm:p-6 lg:px-10 lg:py-8 bg-[#FAF9F4] overflow-hidden selection:bg-[#F4C430] selection:text-[#14161F]">
{/*
The ambient layer: a warm wash at the outer edges, then three large
curved shapes cropped by the viewport so only an arc of each is ever
visible. The brief is "felt, not seen" — nothing here should read as a
distinct object behind the card.
Painted with radial gradients rather than blurred elements, and that is
a performance fix rather than a style choice. Three `blur-[110px]`
divs are three compositing layers the size of the viewport, and the
carousel crossfading on top of them invalidated all three every frame
for a full second every five seconds — enough to lock the renderer.
A gradient is soft for free: no filter, no layer, no repaint.
*/}
<div
aria-hidden
className="absolute inset-0 pointer-events-none"
style={{
background:
// Top-right arc.
'radial-gradient(60rem 46rem at 104% -14%, rgba(244,196,48,0.30), rgba(244,196,48,0.10) 46%, transparent 72%),' +
// Bottom-left arc.
'radial-gradient(58rem 44rem at -8% 112%, rgba(244,196,48,0.26), rgba(244,196,48,0.09) 46%, transparent 72%),' +
// Left-edge sliver — the tallest and faintest of the three.
'radial-gradient(30rem 40rem at -12% 42%, rgba(244,196,48,0.20), transparent 68%),' +
// The wash that keeps the four corners off flat white.
'radial-gradient(ellipse 80% 70% at 50% 50%, transparent 45%, rgba(244,196,48,0.05) 100%)',
}}
/>
<div className="relative z-10 w-full max-w-[1120px] rounded-[28px] bg-white border border-[#EDEAE0] shadow-[0_28px_70px_-30px_rgba(31,29,20,0.22),0_2px_8px_-2px_rgba(31,29,20,0.06)] flex flex-col md:flex-row overflow-hidden">
<LoginHeroPanel />
<div className="w-full md:w-1/2 p-8 sm:p-10 lg:px-12 lg:py-10 flex flex-col justify-center">
<div className="flex items-center gap-3">
<BrandLogo height={34} priority />
<div className="h-4 w-px bg-[#E2DFD4]" />
<span className="text-[11px] font-semibold tracking-[0.14em] text-[#8A8577] uppercase">
Merchant OS
</span>
</div>
<div className="mt-9 lg:mt-10">
<h1 className="text-[30px] sm:text-[36px] font-bold tracking-[-0.02em] leading-[1.1] text-[#14161F]">
{title}
</h1>
<p className="mt-3 text-[15px] leading-relaxed text-[#6B6960] max-w-[46ch]">
{description}
</p>
</div>
<div className="mt-7">
{children ?? (
<>
<LoginCredentialsForm />
<p className="mt-6 text-[13px] text-[#6B6960]">
Got an invitation code?{' '}
<Link
href="/join"
className="font-medium text-[#14161F] underline underline-offset-2 hover:text-[#8A6300] transition-colors"
>
Join your team
</Link>
</p>
</>
)}
</div>
</div>
</div>
{/*
In normal flow rather than pinned to the viewport bottom: the card grows
with the form's error messages, and an absolutely positioned footer sat
on top of it as soon as it did.
*/}
<p className="relative z-10 text-[11px] text-[#9C978A] text-center">
By continuing, you agree to Loyaly&apos;s{' '}
<a
href="#"
className="underline underline-offset-2 hover:text-[#6B6960] transition-colors"
>
Terms of Service
</a>{' '}
and{' '}
<a
href="#"
className="underline underline-offset-2 hover:text-[#6B6960] transition-colors"
>
Privacy Policy
</a>
.
</p>
</div>
);
}