Login page was deployed with the old unstyled version on main — the Round 5 design system components (AuthShell, AuthCard, AuthBrand) had never been merged. This commit restores the designed login experience with animated gradient background, card chrome, and proper dark-mode support matching the design reference. Additionally, NEXT_PUBLIC_API_URL was hardcoded at build time via the CI --build-arg, making it impossible to override in container env vars. The root layout now injects runtime env vars into window.__MOSAIC_ENV__ via a synchronous script tag, and config.ts reads those values first. This lets deployed containers use their own API URL without rebuilding. Changes: - Add AuthSurface.tsx to @mosaic/ui (AuthShell, AuthCard, AuthBrand, AuthStatusPill, AuthDivider) - Rewrite login page to use design system components with "Command Center" heading matching the design reference - Update config.ts with getEnv() helper that reads window.__MOSAIC_ENV__ - Add runtime env injection script to root layout.tsx - Update all tests to match new component structure and content Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
79 lines
2.0 KiB
TypeScript
79 lines
2.0 KiB
TypeScript
"use client";
|
|
|
|
import type { ReactElement } from "react";
|
|
import { Loader2 } from "lucide-react";
|
|
|
|
export interface OAuthButtonProps {
|
|
providerName: string;
|
|
providerId: string;
|
|
onClick: () => void;
|
|
isLoading?: boolean;
|
|
disabled?: boolean;
|
|
}
|
|
|
|
export function OAuthButton({
|
|
providerName,
|
|
providerId,
|
|
onClick,
|
|
isLoading = false,
|
|
disabled = false,
|
|
}: OAuthButtonProps): ReactElement {
|
|
const accentColor = resolveProviderAccent(providerId);
|
|
const isDisabled = disabled || isLoading;
|
|
|
|
return (
|
|
<button
|
|
type="button"
|
|
role="button"
|
|
onClick={onClick}
|
|
disabled={isDisabled}
|
|
aria-label={isLoading ? "Connecting" : `Continue with ${providerName}`}
|
|
className={[
|
|
"w-full inline-flex items-center justify-center gap-2 rounded-lg",
|
|
"border border-[#b8c4de] bg-[#f8faff]/90 px-4 py-3 text-sm font-semibold text-[#2f3b52]",
|
|
"transition-all duration-200 focus:outline-none focus:ring-2 focus:ring-[#56a0ff]/60",
|
|
"hover:border-[#2f80ff] hover:bg-[#dde4f2] hover:text-[#0f141d]",
|
|
"dark:border-[#2f3b52] dark:bg-[#0f141d]/75 dark:text-[#c5d0e6]",
|
|
"dark:hover:border-[#2f80ff] dark:hover:bg-[#232d3f] dark:hover:text-[#eef3ff]",
|
|
isDisabled ? "opacity-50 pointer-events-none" : "",
|
|
]
|
|
.filter(Boolean)
|
|
.join(" ")}
|
|
>
|
|
{isLoading ? (
|
|
<>
|
|
<Loader2 className="h-4 w-4 animate-spin" aria-hidden="true" />
|
|
<span>Connecting...</span>
|
|
</>
|
|
) : (
|
|
<>
|
|
<span
|
|
aria-hidden="true"
|
|
className="h-2 w-2 rounded-full"
|
|
style={{ backgroundColor: accentColor }}
|
|
/>
|
|
<span>Continue with {providerName}</span>
|
|
</>
|
|
)}
|
|
</button>
|
|
);
|
|
}
|
|
|
|
function resolveProviderAccent(providerId: string): string {
|
|
const normalized = providerId.toLowerCase();
|
|
|
|
if (normalized.includes("github")) {
|
|
return "#8b5cf6";
|
|
}
|
|
|
|
if (normalized.includes("google")) {
|
|
return "#e5484d";
|
|
}
|
|
|
|
if (normalized.includes("ldap")) {
|
|
return "#14b8a6";
|
|
}
|
|
|
|
return "#2f80ff";
|
|
}
|