Frontend revamp: Animal Crossing × Claude design with shadcn components
- New palette: deep nook green sidebar, sage user bubbles, warm cream answer cards - shadcn-style UI primitives: Button (CVA variants), Textarea, Input, Badge, Table - cn() utility (clsx + tailwind-merge) - lucide-react icons throughout (no more text-only buttons) - Simba mode: custom CSS toggle switch - Send button: circular amber button with arrow icon - AnswerBubble: amber gradient accent bar, loading dots animation - QuestionBubble: sage green pill with rounded-3xl - ToolBubble: centered leaf-green badge pill - ConversationList: active item highlighting, proper selectedId prop - Sidebar: collapsible with PanelLeftClose/Open icons, icon-only collapsed state - LoginScreen: decorative background blobs, refined rounded card - AdminPanel: proper icon buttons, leaf-green save confirmation - Fonts: Playfair Display (brand) + Nunito 800 weight added Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,6 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import { userService } from "../api/userService";
|
||||
import { oidcService } from "../api/oidcService";
|
||||
import catIcon from "../assets/cat.png";
|
||||
import { cn } from "../lib/utils";
|
||||
|
||||
type LoginScreenProps = {
|
||||
setAuthenticated: (isAuth: boolean) => void;
|
||||
@@ -14,25 +15,17 @@ export const LoginScreen = ({ setAuthenticated }: LoginScreenProps) => {
|
||||
|
||||
useEffect(() => {
|
||||
const initAuth = async () => {
|
||||
// First, check for OIDC callback parameters
|
||||
const callbackParams = oidcService.getCallbackParamsFromURL();
|
||||
|
||||
if (callbackParams) {
|
||||
// Handle OIDC callback
|
||||
try {
|
||||
setIsLoggingIn(true);
|
||||
const result = await oidcService.handleCallback(
|
||||
callbackParams.code,
|
||||
callbackParams.state
|
||||
callbackParams.state,
|
||||
);
|
||||
|
||||
// Store tokens
|
||||
localStorage.setItem("access_token", result.access_token);
|
||||
localStorage.setItem("refresh_token", result.refresh_token);
|
||||
|
||||
// Clear URL parameters
|
||||
oidcService.clearCallbackParams();
|
||||
|
||||
setAuthenticated(true);
|
||||
setIsChecking(false);
|
||||
return;
|
||||
@@ -45,15 +38,10 @@ export const LoginScreen = ({ setAuthenticated }: LoginScreenProps) => {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Check if user is already authenticated
|
||||
const isValid = await userService.validateToken();
|
||||
if (isValid) {
|
||||
setAuthenticated(true);
|
||||
}
|
||||
if (isValid) setAuthenticated(true);
|
||||
setIsChecking(false);
|
||||
};
|
||||
|
||||
initAuth();
|
||||
}, [setAuthenticated]);
|
||||
|
||||
@@ -61,29 +49,34 @@ export const LoginScreen = ({ setAuthenticated }: LoginScreenProps) => {
|
||||
try {
|
||||
setIsLoggingIn(true);
|
||||
setError("");
|
||||
|
||||
// Get authorization URL from backend
|
||||
const authUrl = await oidcService.initiateLogin();
|
||||
|
||||
// Redirect to Authelia
|
||||
window.location.href = authUrl;
|
||||
} catch (err) {
|
||||
} catch {
|
||||
setError("Failed to initiate login. Please try again.");
|
||||
console.error("OIDC login error:", err);
|
||||
setIsLoggingIn(false);
|
||||
}
|
||||
};
|
||||
|
||||
// Show loading state while checking authentication or processing callback
|
||||
if (isChecking || isLoggingIn) {
|
||||
return (
|
||||
<div className="h-screen flex flex-col items-center justify-center bg-cream gap-4">
|
||||
<img
|
||||
src={catIcon}
|
||||
alt="Simba"
|
||||
className="w-16 h-16 animate-bounce"
|
||||
{/* Subtle dot grid */}
|
||||
<div
|
||||
className="fixed inset-0 pointer-events-none opacity-[0.035]"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle, var(--color-charcoal) 1px, transparent 0)`,
|
||||
backgroundSize: "22px 22px",
|
||||
}}
|
||||
/>
|
||||
<p className="text-warm-gray font-medium text-lg tracking-wide">
|
||||
<div className="relative">
|
||||
<div className="absolute -inset-4 bg-amber-soft/30 rounded-full blur-2xl" />
|
||||
<img
|
||||
src={catIcon}
|
||||
alt="Simba"
|
||||
className="relative w-14 h-14 animate-bounce drop-shadow"
|
||||
/>
|
||||
</div>
|
||||
<p className="text-warm-gray text-sm tracking-wide font-medium">
|
||||
{isLoggingIn ? "letting you in..." : "checking credentials..."}
|
||||
</p>
|
||||
</div>
|
||||
@@ -91,27 +84,35 @@ export const LoginScreen = ({ setAuthenticated }: LoginScreenProps) => {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="h-screen bg-cream flex items-center justify-center p-4">
|
||||
{/* Decorative background texture */}
|
||||
<div className="fixed inset-0 opacity-[0.03] pointer-events-none"
|
||||
<div className="h-screen bg-cream flex items-center justify-center p-4 relative overflow-hidden">
|
||||
{/* Background dot texture */}
|
||||
<div
|
||||
className="fixed inset-0 pointer-events-none opacity-[0.04]"
|
||||
style={{
|
||||
backgroundImage: `radial-gradient(circle at 1px 1px, var(--color-charcoal) 1px, transparent 0)`,
|
||||
backgroundSize: '24px 24px'
|
||||
backgroundImage: `radial-gradient(circle, var(--color-charcoal) 1px, transparent 0)`,
|
||||
backgroundSize: "22px 22px",
|
||||
}}
|
||||
/>
|
||||
|
||||
{/* Decorative background blobs */}
|
||||
<div className="absolute top-1/4 -left-20 w-72 h-72 rounded-full bg-leaf-pale/60 blur-3xl pointer-events-none" />
|
||||
<div className="absolute bottom-1/4 -right-20 w-64 h-64 rounded-full bg-amber-pale/70 blur-3xl pointer-events-none" />
|
||||
|
||||
<div className="relative w-full max-w-sm">
|
||||
{/* Cat icon & branding */}
|
||||
{/* Branding */}
|
||||
<div className="flex flex-col items-center mb-8">
|
||||
<div className="relative mb-4">
|
||||
<div className="absolute -inset-3 bg-amber-soft/40 rounded-full blur-xl" />
|
||||
<div className="relative mb-5">
|
||||
<div className="absolute -inset-5 bg-amber-soft/30 rounded-full blur-2xl" />
|
||||
<img
|
||||
src={catIcon}
|
||||
alt="Simba"
|
||||
className="relative w-20 h-20 drop-shadow-lg"
|
||||
/>
|
||||
</div>
|
||||
<h1 className="font-[family-name:var(--font-display)] text-4xl font-bold text-charcoal tracking-tight">
|
||||
<h1
|
||||
className="text-4xl font-bold text-charcoal tracking-tight"
|
||||
style={{ fontFamily: "var(--font-display)" }}
|
||||
>
|
||||
asksimba
|
||||
</h1>
|
||||
<p className="text-warm-gray text-sm mt-1.5 tracking-wide">
|
||||
@@ -119,10 +120,15 @@ export const LoginScreen = ({ setAuthenticated }: LoginScreenProps) => {
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{/* Login card */}
|
||||
<div className="bg-warm-white rounded-2xl shadow-lg shadow-sand/40 border border-sand-light/60 p-8">
|
||||
{/* Card */}
|
||||
<div
|
||||
className={cn(
|
||||
"bg-warm-white rounded-3xl border border-sand-light",
|
||||
"shadow-xl shadow-sand/30 p-8",
|
||||
)}
|
||||
>
|
||||
{error && (
|
||||
<div className="mb-4 text-sm bg-red-50 text-red-700 p-3 rounded-xl border border-red-200">
|
||||
<div className="mb-5 text-sm bg-red-50 text-red-600 px-4 py-3 rounded-2xl border border-red-200">
|
||||
{error}
|
||||
</div>
|
||||
)}
|
||||
@@ -132,21 +138,23 @@ export const LoginScreen = ({ setAuthenticated }: LoginScreenProps) => {
|
||||
</p>
|
||||
|
||||
<button
|
||||
className="w-full py-3.5 px-4 bg-forest text-white font-semibold rounded-xl
|
||||
hover:bg-forest-light transition-all duration-200
|
||||
active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed
|
||||
shadow-md shadow-forest/20 hover:shadow-lg hover:shadow-forest/30
|
||||
cursor-pointer text-sm tracking-wide"
|
||||
onClick={handleOIDCLogin}
|
||||
disabled={isLoggingIn}
|
||||
className={cn(
|
||||
"w-full py-3.5 px-4 rounded-2xl text-sm font-semibold tracking-wide",
|
||||
"bg-forest text-cream",
|
||||
"shadow-md shadow-forest/20",
|
||||
"hover:bg-forest-mid hover:shadow-lg hover:shadow-forest/30",
|
||||
"active:scale-[0.98] disabled:opacity-50 disabled:cursor-not-allowed",
|
||||
"transition-all duration-200 cursor-pointer",
|
||||
)}
|
||||
>
|
||||
{isLoggingIn ? "Redirecting..." : "Sign in with Authelia"}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{/* Footer paw prints */}
|
||||
<p className="text-center text-sand mt-6 text-xs tracking-widest select-none">
|
||||
~ meow ~
|
||||
<p className="text-center text-sand mt-5 text-xs tracking-widest select-none">
|
||||
✦ meow ✦
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user