fix(portal): corporate landing SEO, favicon, and public home UX
Some checks failed
API CI / API Lint (push) Successful in 53s
API CI / API Type Check (push) Failing after 51s
API CI / API Test (push) Successful in 1m32s
API CI / API Build (push) Failing after 1m0s
API CI / Build Docker Image (push) Has been skipped
CD Pipeline / Deploy to Staging (push) Failing after 32s
CI Pipeline / Lint and Type Check (push) Failing after 33s
CI Pipeline / Build (push) Has been skipped
CI Pipeline / Test Backend (push) Failing after 2m1s
CI Pipeline / Test Frontend (push) Failing after 35s
CI Pipeline / Security Scan (push) Failing after 1m12s
Deploy to Staging / Deploy to Staging (push) Failing after 28s
Portal CI / Portal Lint (push) Failing after 19s
Portal CI / Portal Type Check (push) Failing after 18s
Portal CI / Portal Test (push) Failing after 19s
Portal CI / Portal Build (push) Failing after 18s
Test Suite / frontend-tests (push) Failing after 30s
Test Suite / api-tests (push) Failing after 44s
Test Suite / blockchain-tests (push) Failing after 27s
Type Check / type-check (map[directory:. name:root]) (push) Failing after 18s
Type Check / type-check (map[directory:api name:api]) (push) Failing after 19s
Type Check / type-check (map[directory:portal name:portal]) (push) Failing after 18s
CD Pipeline / Deploy to Production (push) Has been skipped
Some checks failed
API CI / API Lint (push) Successful in 53s
API CI / API Type Check (push) Failing after 51s
API CI / API Test (push) Successful in 1m32s
API CI / API Build (push) Failing after 1m0s
API CI / Build Docker Image (push) Has been skipped
CD Pipeline / Deploy to Staging (push) Failing after 32s
CI Pipeline / Lint and Type Check (push) Failing after 33s
CI Pipeline / Build (push) Has been skipped
CI Pipeline / Test Backend (push) Failing after 2m1s
CI Pipeline / Test Frontend (push) Failing after 35s
CI Pipeline / Security Scan (push) Failing after 1m12s
Deploy to Staging / Deploy to Staging (push) Failing after 28s
Portal CI / Portal Lint (push) Failing after 19s
Portal CI / Portal Type Check (push) Failing after 18s
Portal CI / Portal Test (push) Failing after 19s
Portal CI / Portal Build (push) Failing after 18s
Test Suite / frontend-tests (push) Failing after 30s
Test Suite / api-tests (push) Failing after 44s
Test Suite / blockchain-tests (push) Failing after 27s
Type Check / type-check (map[directory:. name:root]) (push) Failing after 18s
Type Check / type-check (map[directory:api name:api]) (push) Failing after 19s
Type Check / type-check (map[directory:portal name:portal]) (push) Failing after 18s
CD Pipeline / Deploy to Production (push) Has been skipped
Add sankofa.nexus marketing site with institutional grade scorecards, server-side metadata/title, dynamic icons, favicon rewrite, and instant landing render without session-loading flash; split authenticated AppShell from unauthenticated corporate chrome. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
61
portal/src/components/auth/PortalSignInCard.tsx
Normal file
61
portal/src/components/auth/PortalSignInCard.tsx
Normal file
@@ -0,0 +1,61 @@
|
||||
'use client';
|
||||
|
||||
import { signIn } from 'next-auth/react';
|
||||
import { useCallback, useState, type ReactNode } from 'react';
|
||||
|
||||
import { CloudflareTurnstile } from '@/components/security/CloudflareTurnstile';
|
||||
|
||||
const TURNSTILE_SITE_KEY = process.env.NEXT_PUBLIC_CLOUDFLARE_TURNSTILE_SITE_KEY;
|
||||
|
||||
export interface PortalSignInCardProps {
|
||||
badge?: string;
|
||||
title: string;
|
||||
subtitle: string;
|
||||
callbackUrl?: string;
|
||||
/** Extra hint under the sign-in button (e.g. dev IdP note) */
|
||||
footer?: ReactNode;
|
||||
}
|
||||
|
||||
export function PortalSignInCard({
|
||||
badge = 'Sankofa Phoenix',
|
||||
title,
|
||||
subtitle,
|
||||
callbackUrl = '/',
|
||||
footer,
|
||||
}: PortalSignInCardProps) {
|
||||
const [turnstileToken, setTurnstileToken] = useState<string | null>(() =>
|
||||
TURNSTILE_SITE_KEY ? null : ''
|
||||
);
|
||||
|
||||
const onTurnstileToken = useCallback((token: string | null) => {
|
||||
setTurnstileToken(token);
|
||||
}, []);
|
||||
|
||||
const canSignIn = !TURNSTILE_SITE_KEY || Boolean(turnstileToken);
|
||||
|
||||
return (
|
||||
<div className="w-full max-w-md rounded-2xl border border-gray-800 bg-gray-900/80 p-8 shadow-xl shadow-black/40 backdrop-blur-sm">
|
||||
<p className="mb-1 text-center text-sm font-medium uppercase tracking-wide text-orange-400">{badge}</p>
|
||||
<h1 className="mb-2 text-center text-2xl font-bold text-white">{title}</h1>
|
||||
<p className="mb-8 text-center text-gray-400">{subtitle}</p>
|
||||
|
||||
{TURNSTILE_SITE_KEY ? (
|
||||
<div className="mb-6">
|
||||
<p className="mb-2 text-center text-sm text-gray-500">Verification</p>
|
||||
<CloudflareTurnstile siteKey={TURNSTILE_SITE_KEY} onToken={onTurnstileToken} />
|
||||
</div>
|
||||
) : null}
|
||||
|
||||
<button
|
||||
type="button"
|
||||
disabled={!canSignIn}
|
||||
onClick={() => signIn(undefined, { callbackUrl })}
|
||||
className="w-full rounded-lg bg-gradient-to-r from-orange-500 to-amber-500 px-6 py-3 font-semibold text-gray-950 shadow-lg transition hover:from-orange-400 hover:to-amber-400 focus:outline-none focus:ring-2 focus:ring-orange-400 focus:ring-offset-2 focus:ring-offset-gray-900 disabled:cursor-not-allowed disabled:opacity-50"
|
||||
>
|
||||
Sign In
|
||||
</button>
|
||||
|
||||
{footer}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user