style(auth): modernize login, setup, and onboarding screens

This commit is contained in:
Haileyesus
2026-06-30 11:45:14 +03:00
parent 7d5bd753d4
commit 1a7f0291b2
11 changed files with 163 additions and 91 deletions

View File

@@ -1,3 +1,5 @@
import { AlertCircle } from 'lucide-react';
type AuthErrorAlertProps = { type AuthErrorAlertProps = {
errorMessage: string; errorMessage: string;
}; };
@@ -8,8 +10,9 @@ export default function AuthErrorAlert({ errorMessage }: AuthErrorAlertProps) {
} }
return ( return (
<div className="rounded-md border border-red-300 bg-red-100 p-3 dark:border-red-800 dark:bg-red-900/20"> <div className="flex items-start gap-2.5 rounded-xl border border-destructive/30 bg-destructive/10 p-3 text-destructive">
<p className="text-sm text-red-700 dark:text-red-400">{errorMessage}</p> <AlertCircle className="mt-0.5 h-4 w-4 flex-shrink-0" />
<p className="text-sm leading-relaxed">{errorMessage}</p>
</div> </div>
); );
} }

View File

@@ -1,3 +1,7 @@
import { useState } from 'react';
import type { ComponentType } from 'react';
import { Eye, EyeOff } from 'lucide-react';
type AuthInputFieldProps = { type AuthInputFieldProps = {
id: string; id: string;
label: string; label: string;
@@ -8,13 +12,14 @@ type AuthInputFieldProps = {
type?: 'text' | 'password' | 'email'; type?: 'text' | 'password' | 'email';
name?: string; name?: string;
autoComplete?: string; autoComplete?: string;
icon?: ComponentType<{ className?: string }>;
}; };
/** /**
* A labelled input field for authentication forms. * A labelled input field for authentication forms.
* Renders a `<label>` / `<input>` pair and forwards browser autofill hints * Renders a `<label>` / `<input>` pair and forwards browser autofill hints
* (`name`, `autoComplete`) so that password managers can identify and fill * (`name`, `autoComplete`) so that password managers can identify and fill
* the field correctly. * the field correctly. Password fields gain a show/hide visibility toggle.
*/ */
export default function AuthInputField({ export default function AuthInputField({
id, id,
@@ -26,24 +31,49 @@ export default function AuthInputField({
type = 'text', type = 'text',
name, name,
autoComplete, autoComplete,
icon: Icon,
}: AuthInputFieldProps) { }: AuthInputFieldProps) {
const [isPasswordVisible, setIsPasswordVisible] = useState(false);
const isPasswordField = type === 'password';
const resolvedType = isPasswordField && isPasswordVisible ? 'text' : type;
return ( return (
<div> <div>
<label htmlFor={id} className="mb-1 block text-sm font-medium text-foreground"> <label htmlFor={id} className="mb-1.5 block text-sm font-medium text-foreground">
{label} {label}
</label> </label>
<input <div className="group relative">
id={id} {Icon && (
type={type} <Icon className="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-muted-foreground transition-colors group-focus-within:text-primary" />
name={name ?? id} )}
autoComplete={autoComplete} <input
value={value} id={id}
onChange={(event) => onChange(event.target.value)} type={resolvedType}
className="w-full rounded-md border border-border bg-background px-3 py-2 text-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" name={name ?? id}
placeholder={placeholder} autoComplete={autoComplete}
required value={value}
disabled={isDisabled} onChange={(event) => onChange(event.target.value)}
/> className={`w-full rounded-xl border border-border bg-background/60 py-2.5 text-foreground shadow-sm transition-colors placeholder:text-muted-foreground/60 hover:border-foreground/20 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/30 disabled:cursor-not-allowed disabled:opacity-60 ${
Icon ? 'pl-10' : 'pl-3.5'
} ${isPasswordField ? 'pr-11' : 'pr-3.5'}`}
placeholder={placeholder}
required
disabled={isDisabled}
/>
{isPasswordField && (
<button
type="button"
onClick={() => setIsPasswordVisible((previous) => !previous)}
disabled={isDisabled}
tabIndex={-1}
aria-label={isPasswordVisible ? 'Hide password' : 'Show password'}
className="absolute right-2 top-1/2 flex h-7 w-7 -translate-y-1/2 items-center justify-center rounded-lg text-muted-foreground transition-colors hover:bg-muted hover:text-foreground disabled:opacity-60"
>
{isPasswordVisible ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
</button>
)}
</div>
</div> </div>
); );
} }

View File

@@ -1,30 +1,30 @@
import { MessageSquare } from 'lucide-react'; const loadingDotAnimationDelays = ['0s', '0.15s', '0.3s'];
const loadingDotAnimationDelays = ['0s', '0.1s', '0.2s'];
export default function AuthLoadingScreen() { export default function AuthLoadingScreen() {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="relative flex min-h-screen items-center justify-center overflow-hidden bg-background p-4">
<div className="text-center"> <div aria-hidden className="pointer-events-none absolute inset-0">
<div className="mb-4 flex justify-center"> <div className="absolute -top-40 left-1/2 h-[36rem] w-[36rem] -translate-x-1/2 rounded-full bg-primary/10 blur-3xl" />
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-primary shadow-sm"> </div>
<MessageSquare className="h-8 w-8 text-primary-foreground" />
<div className="relative text-center">
<div className="mb-5 flex justify-center">
<div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-primary to-primary/80 shadow-lg shadow-primary/25 ring-1 ring-inset ring-white/20">
<img src="/logo.svg" alt="CloudCLI" className="h-9 w-9" />
</div> </div>
</div> </div>
<h1 className="mb-2 text-2xl font-bold text-foreground">CloudCLI</h1> <h1 className="mb-4 font-serif text-2xl font-bold tracking-tight text-foreground">CloudCLI</h1>
<div className="flex items-center justify-center space-x-2"> <div className="flex items-center justify-center gap-2">
{loadingDotAnimationDelays.map((delay) => ( {loadingDotAnimationDelays.map((delay) => (
<div <div
key={delay} key={delay}
className="h-2 w-2 animate-bounce rounded-full bg-blue-500" className="h-2 w-2 animate-bounce rounded-full bg-primary"
style={{ animationDelay: delay }} style={{ animationDelay: delay }}
/> />
))} ))}
</div> </div>
<p className="mt-2 text-muted-foreground">Loading...</p>
</div> </div>
</div> </div>
); );

View File

@@ -1,5 +1,4 @@
import type { ReactNode } from 'react'; import type { ReactNode } from 'react';
import { MessageSquare } from 'lucide-react';
import { IS_PLATFORM } from '../../../constants/config'; import { IS_PLATFORM } from '../../../constants/config';
type AuthScreenLayoutProps = { type AuthScreenLayoutProps = {
@@ -18,29 +17,38 @@ export default function AuthScreenLayout({
logo, logo,
}: AuthScreenLayoutProps) { }: AuthScreenLayoutProps) {
return ( return (
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="relative h-screen overflow-y-auto bg-background">
<div className="w-full max-w-md"> {/* Ambient, on-brand backdrop that gives the screen depth without
<div className="space-y-6 rounded-lg border border-border bg-card p-8 shadow-lg"> competing with the card content. Fixed so it stays put while the
form scrolls on short viewports. */}
<div aria-hidden className="pointer-events-none fixed inset-0">
<div className="absolute -top-40 left-1/2 h-[36rem] w-[36rem] -translate-x-1/2 rounded-full bg-primary/10 blur-3xl" />
<div className="absolute -bottom-32 -left-24 h-[26rem] w-[26rem] rounded-full bg-primary/5 blur-3xl" />
<div className="absolute inset-0 bg-[radial-gradient(hsl(var(--foreground)/0.04)_1px,transparent_1px)] [background-size:22px_22px] opacity-60" />
</div>
<div className="relative mx-auto flex min-h-full w-full max-w-md items-center justify-center p-4 py-8">
<div className="w-full rounded-2xl border border-border/70 bg-card/90 p-8 shadow-[0_24px_60px_-20px_hsl(var(--foreground)/0.18)] ring-1 ring-foreground/5 backdrop-blur-xl sm:p-10">
<div className="text-center"> <div className="text-center">
<div className="mb-4 flex justify-center"> <div className="mb-5 flex justify-center">
{logo ?? ( {logo ?? (
<div className="flex h-16 w-16 items-center justify-center rounded-lg bg-primary shadow-sm"> <div className="flex h-16 w-16 items-center justify-center rounded-2xl bg-gradient-to-br from-primary to-primary/80 shadow-lg shadow-primary/25 ring-1 ring-inset ring-white/20">
<MessageSquare className="h-8 w-8 text-primary-foreground" /> <img src="/logo.svg" alt="CloudCLI" className="h-9 w-9" />
</div> </div>
)} )}
</div> </div>
<h1 className="text-2xl font-bold text-foreground">{title}</h1> <h1 className="font-serif text-3xl font-bold tracking-tight text-foreground">{title}</h1>
<p className="mt-2 text-muted-foreground">{description}</p> <p className="mx-auto mt-2 max-w-xs text-sm leading-relaxed text-muted-foreground">{description}</p>
</div> </div>
{children} <div className="mt-8">{children}</div>
<div className="text-center"> <div className="mt-6 border-t border-border/60 pt-5 text-center">
<p className="text-sm text-muted-foreground">{footerText}</p> <p className="text-xs leading-relaxed text-muted-foreground">{footerText}</p>
</div> </div>
{!IS_PLATFORM && ( {!IS_PLATFORM && (
<div className="flex items-center justify-center gap-1.5 pt-2"> <div className="mt-4 flex items-center justify-center gap-1.5">
<svg className="h-3.5 w-3.5 text-muted-foreground/50" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true"> <svg className="h-3.5 w-3.5 text-muted-foreground/50" fill="currentColor" viewBox="0 0 24 24" aria-hidden="true">
<path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" /> <path d="M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z" />
</svg> </svg>

View File

@@ -1,6 +1,7 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { Loader2, Lock, User } from 'lucide-react';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import AuthErrorAlert from './AuthErrorAlert'; import AuthErrorAlert from './AuthErrorAlert';
import AuthInputField from './AuthInputField'; import AuthInputField from './AuthInputField';
@@ -69,6 +70,7 @@ export default function LoginForm() {
placeholder={t('login.placeholders.username')} placeholder={t('login.placeholders.username')}
isDisabled={isSubmitting} isDisabled={isSubmitting}
autoComplete="username" autoComplete="username"
icon={User}
/> />
<AuthInputField <AuthInputField
@@ -80,6 +82,7 @@ export default function LoginForm() {
isDisabled={isSubmitting} isDisabled={isSubmitting}
type="password" type="password"
autoComplete="current-password" autoComplete="current-password"
icon={Lock}
/> />
<AuthErrorAlert errorMessage={errorMessage} /> <AuthErrorAlert errorMessage={errorMessage} />
@@ -87,9 +90,16 @@ export default function LoginForm() {
<button <button
type="submit" type="submit"
disabled={isSubmitting} disabled={isSubmitting}
className="w-full rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition-colors duration-200 hover:bg-blue-700 disabled:bg-blue-400" className="flex w-full items-center justify-center gap-2 rounded-xl bg-primary px-4 py-2.5 font-medium text-primary-foreground shadow-lg shadow-primary/25 transition-all duration-200 hover:brightness-110 hover:shadow-primary/30 focus:outline-none focus:ring-2 focus:ring-primary/40 focus:ring-offset-2 focus:ring-offset-card active:scale-[0.99] disabled:cursor-not-allowed disabled:opacity-60"
> >
{isSubmitting ? t('login.loading') : t('login.submit')} {isSubmitting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
{t('login.loading')}
</>
) : (
t('login.submit')
)}
</button> </button>
</form> </form>
</AuthScreenLayout> </AuthScreenLayout>

View File

@@ -1,5 +1,6 @@
import { useCallback, useState } from 'react'; import { useCallback, useState } from 'react';
import type { FormEvent } from 'react'; import type { FormEvent } from 'react';
import { Loader2, Lock, ShieldCheck, User } from 'lucide-react';
import { useAuth } from '../context/AuthContext'; import { useAuth } from '../context/AuthContext';
import AuthErrorAlert from './AuthErrorAlert'; import AuthErrorAlert from './AuthErrorAlert';
import AuthInputField from './AuthInputField'; import AuthInputField from './AuthInputField';
@@ -85,7 +86,6 @@ export default function SetupForm() {
title="Welcome to CloudCLI" title="Welcome to CloudCLI"
description="Set up your account to get started" description="Set up your account to get started"
footerText="This is a single-user system. Only one account can be created." footerText="This is a single-user system. Only one account can be created."
logo={<img src="/logo.svg" alt="CloudCLI" className="h-16 w-16" />}
> >
<form onSubmit={handleSubmit} className="space-y-4"> <form onSubmit={handleSubmit} className="space-y-4">
<AuthInputField <AuthInputField
@@ -94,9 +94,10 @@ export default function SetupForm() {
label="Username" label="Username"
value={formState.username} value={formState.username}
onChange={(value) => updateField('username', value)} onChange={(value) => updateField('username', value)}
placeholder="Enter your username" placeholder="Choose a username"
isDisabled={isSubmitting} isDisabled={isSubmitting}
autoComplete="username" autoComplete="username"
icon={User}
/> />
<AuthInputField <AuthInputField
@@ -105,10 +106,11 @@ export default function SetupForm() {
label="Password" label="Password"
value={formState.password} value={formState.password}
onChange={(value) => updateField('password', value)} onChange={(value) => updateField('password', value)}
placeholder="Enter your password" placeholder="Create a password"
isDisabled={isSubmitting} isDisabled={isSubmitting}
type="password" type="password"
autoComplete="new-password" autoComplete="new-password"
icon={Lock}
/> />
<AuthInputField <AuthInputField
@@ -117,20 +119,33 @@ export default function SetupForm() {
label="Confirm Password" label="Confirm Password"
value={formState.confirmPassword} value={formState.confirmPassword}
onChange={(value) => updateField('confirmPassword', value)} onChange={(value) => updateField('confirmPassword', value)}
placeholder="Confirm your password" placeholder="Re-enter your password"
isDisabled={isSubmitting} isDisabled={isSubmitting}
type="password" type="password"
autoComplete="new-password" autoComplete="new-password"
icon={ShieldCheck}
/> />
<p className="flex items-center gap-1.5 text-xs text-muted-foreground">
<ShieldCheck className="h-3.5 w-3.5" />
At least 3 characters for username, 6 for password.
</p>
<AuthErrorAlert errorMessage={errorMessage} /> <AuthErrorAlert errorMessage={errorMessage} />
<button <button
type="submit" type="submit"
disabled={isSubmitting} disabled={isSubmitting}
className="w-full rounded-md bg-blue-600 px-4 py-2 font-medium text-white transition-colors duration-200 hover:bg-blue-700 disabled:bg-blue-400" className="flex w-full items-center justify-center gap-2 rounded-xl bg-primary px-4 py-2.5 font-medium text-primary-foreground shadow-lg shadow-primary/25 transition-all duration-200 hover:brightness-110 hover:shadow-primary/30 focus:outline-none focus:ring-2 focus:ring-primary/40 focus:ring-offset-2 focus:ring-offset-card active:scale-[0.99] disabled:cursor-not-allowed disabled:opacity-60"
> >
{isSubmitting ? 'Setting up...' : 'Create Account'} {isSubmitting ? (
<>
<Loader2 className="h-4 w-4 animate-spin" />
Setting up...
</>
) : (
'Create Account'
)}
</button> </button>
</form> </form>
</AuthScreenLayout> </AuthScreenLayout>

View File

@@ -148,11 +148,18 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
return ( return (
<> <>
<div className="flex min-h-screen items-center justify-center bg-background p-4"> <div className="relative h-screen overflow-y-auto bg-background">
<div className="w-full max-w-2xl"> <div aria-hidden className="pointer-events-none fixed inset-0">
<div className="absolute -top-40 left-1/2 h-[36rem] w-[36rem] -translate-x-1/2 rounded-full bg-primary/10 blur-3xl" />
<div className="absolute -bottom-32 -left-24 h-[26rem] w-[26rem] rounded-full bg-primary/5 blur-3xl" />
<div className="absolute inset-0 bg-[radial-gradient(hsl(var(--foreground)/0.04)_1px,transparent_1px)] [background-size:22px_22px] opacity-60" />
</div>
<div className="relative mx-auto flex min-h-full w-full max-w-2xl items-center justify-center p-4">
<div className="w-full py-6">
<OnboardingStepProgress currentStep={currentStep} /> <OnboardingStepProgress currentStep={currentStep} />
<div className="rounded-lg border border-border bg-card p-8 shadow-lg"> <div className="rounded-2xl border border-border/70 bg-card/90 p-6 shadow-[0_24px_60px_-20px_hsl(var(--foreground)/0.18)] ring-1 ring-foreground/5 backdrop-blur-xl">
{currentStep === 0 ? ( {currentStep === 0 ? (
<GitConfigurationStep <GitConfigurationStep
gitName={gitName} gitName={gitName}
@@ -169,12 +176,12 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
)} )}
{errorMessage && ( {errorMessage && (
<div className="mt-6 rounded-lg border border-red-300 bg-red-100 p-4 dark:border-red-800 dark:bg-red-900/20"> <div className="mt-5 rounded-xl border border-destructive/30 bg-destructive/10 p-3.5">
<p className="text-sm text-red-700 dark:text-red-400">{errorMessage}</p> <p className="text-sm text-destructive">{errorMessage}</p>
</div> </div>
)} )}
<div className="mt-8 flex items-center justify-between border-t border-border pt-6"> <div className="mt-6 flex items-center justify-between border-t border-border pt-5">
<button <button
onClick={handlePreviousStep} onClick={handlePreviousStep}
disabled={currentStep === 0 || isSubmitting} disabled={currentStep === 0 || isSubmitting}
@@ -189,7 +196,7 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
<button <button
onClick={handleNextStep} onClick={handleNextStep}
disabled={!isCurrentStepValid || isSubmitting} disabled={!isCurrentStepValid || isSubmitting}
className="flex items-center gap-2 rounded-lg bg-blue-600 px-6 py-3 font-medium text-white transition-colors duration-200 hover:bg-blue-700 disabled:cursor-not-allowed disabled:bg-blue-400" className="flex items-center gap-2 rounded-xl bg-primary px-6 py-2.5 font-medium text-primary-foreground shadow-lg shadow-primary/25 transition-all duration-200 hover:brightness-110 active:scale-[0.99] disabled:cursor-not-allowed disabled:opacity-60 disabled:shadow-none"
> >
{isSubmitting ? ( {isSubmitting ? (
<> <>
@@ -207,7 +214,7 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
<button <button
onClick={handleFinish} onClick={handleFinish}
disabled={isSubmitting} disabled={isSubmitting}
className="flex items-center gap-2 rounded-lg bg-green-600 px-6 py-3 font-medium text-white transition-colors duration-200 hover:bg-green-700 disabled:cursor-not-allowed disabled:bg-green-400" className="flex items-center gap-2 rounded-xl bg-emerald-600 px-6 py-2.5 font-medium text-white shadow-lg shadow-emerald-600/25 transition-all duration-200 hover:bg-emerald-700 active:scale-[0.99] disabled:cursor-not-allowed disabled:opacity-60 disabled:shadow-none"
> >
{isSubmitting ? ( {isSubmitting ? (
<> <>
@@ -225,6 +232,7 @@ export default function Onboarding({ onComplete }: OnboardingProps) {
</div> </div>
</div> </div>
</div> </div>
</div>
</div> </div>
</div> </div>

View File

@@ -31,26 +31,26 @@ export default function AgentConnectionCard({
: status.error || 'Not connected'; : status.error || 'Not connected';
return ( return (
<div className={`rounded-lg border p-4 transition-colors ${containerClassName}`}> <div className={`rounded-xl border px-3 py-2.5 transition-colors ${containerClassName}`}>
<div className="flex items-center justify-between"> <div className="flex items-center justify-between gap-3">
<div className="flex items-center gap-3"> <div className="flex min-w-0 items-center gap-3">
<div className={`flex h-10 w-10 items-center justify-center rounded-full ${iconContainerClassName}`}> <div className={`flex h-9 w-9 flex-shrink-0 items-center justify-center rounded-full ${iconContainerClassName}`}>
<SessionProviderLogo provider={provider} className="h-5 w-5" /> <SessionProviderLogo provider={provider} className="h-5 w-5" />
</div> </div>
<div> <div className="min-w-0">
<div className="flex items-center gap-2 font-medium text-foreground"> <div className="flex items-center gap-1.5 text-sm font-medium text-foreground">
{title} {title}
{status.authenticated && <Check className="h-4 w-4 text-green-500" />} {status.authenticated && <Check className="h-3.5 w-3.5 flex-shrink-0 text-emerald-500" />}
</div> </div>
<div className="text-xs text-muted-foreground">{statusText}</div> <div className="truncate text-xs text-muted-foreground">{statusText}</div>
</div> </div>
</div> </div>
{!status.authenticated && !status.loading && ( {!status.authenticated && !status.loading && (
<button <button
onClick={onLogin} onClick={onLogin}
className={`${loginButtonClassName} rounded-lg px-4 py-2 text-sm font-medium text-white transition-colors`} className={`${loginButtonClassName} flex-shrink-0 rounded-lg px-4 py-1.5 text-sm font-medium text-white transition-colors`}
> >
Login Login
</button> </button>

View File

@@ -51,15 +51,15 @@ export default function AgentConnectionsStep({
onOpenProviderLogin, onOpenProviderLogin,
}: AgentConnectionsStepProps) { }: AgentConnectionsStepProps) {
return ( return (
<div className="space-y-6"> <div className="space-y-4">
<div className="mb-6 text-center"> <div className="text-center">
<h2 className="mb-2 text-2xl font-bold text-foreground">Connect Your AI Agents</h2> <h2 className="font-serif text-xl font-bold tracking-tight text-foreground">Connect Your AI Agents</h2>
<p className="text-muted-foreground"> <p className="mx-auto mt-1 max-w-sm text-sm leading-relaxed text-muted-foreground">
Login to one or more AI coding assistants. All are optional. Login to one or more AI coding assistants. All are optional.
</p> </p>
</div> </div>
<div className="space-y-3"> <div className="-mr-1 max-h-[38vh] space-y-2 overflow-y-auto pr-1">
{providerCards.map((providerCard) => ( {providerCards.map((providerCard) => (
<AgentConnectionCard <AgentConnectionCard
key={providerCard.provider} key={providerCard.provider}
@@ -74,9 +74,7 @@ export default function AgentConnectionsStep({
))} ))}
</div> </div>
<div className="pt-2 text-center text-sm text-muted-foreground"> <p className="text-center text-xs text-muted-foreground">You can configure these later in Settings.</p>
<p>You can configure these later in Settings.</p>
</div>
</div> </div>
); );
} }

View File

@@ -16,13 +16,13 @@ export default function GitConfigurationStep({
onGitEmailChange, onGitEmailChange,
}: GitConfigurationStepProps) { }: GitConfigurationStepProps) {
return ( return (
<div className="space-y-6"> <div className="space-y-5">
<div className="mb-8 text-center"> <div className="text-center">
<div className="mx-auto mb-4 flex h-16 w-16 items-center justify-center rounded-full bg-blue-100 dark:bg-blue-900/30"> <div className="mx-auto mb-3 flex h-14 w-14 items-center justify-center rounded-2xl bg-primary/10 ring-1 ring-inset ring-primary/20">
<GitBranch className="h-8 w-8 text-blue-600 dark:text-blue-400" /> <GitBranch className="h-7 w-7 text-primary" />
</div> </div>
<h2 className="mb-2 text-2xl font-bold text-foreground">Git Configuration</h2> <h2 className="font-serif text-xl font-bold tracking-tight text-foreground">Git Configuration</h2>
<p className="text-muted-foreground"> <p className="mx-auto mt-1 max-w-sm text-sm leading-relaxed text-muted-foreground">
Configure your git identity to ensure proper attribution for commits. Configure your git identity to ensure proper attribution for commits.
</p> </p>
</div> </div>
@@ -38,7 +38,7 @@ export default function GitConfigurationStep({
id="gitName" id="gitName"
value={gitName} value={gitName}
onChange={(event) => onGitNameChange(event.target.value)} onChange={(event) => onGitNameChange(event.target.value)}
className="w-full rounded-lg border border-border bg-background px-4 py-3 text-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full rounded-xl border border-border bg-background/60 px-4 py-2.5 text-foreground shadow-sm transition-colors placeholder:text-muted-foreground/60 hover:border-foreground/20 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/30"
placeholder="John Doe" placeholder="John Doe"
required required
disabled={isSubmitting} disabled={isSubmitting}
@@ -56,7 +56,7 @@ export default function GitConfigurationStep({
id="gitEmail" id="gitEmail"
value={gitEmail} value={gitEmail}
onChange={(event) => onGitEmailChange(event.target.value)} onChange={(event) => onGitEmailChange(event.target.value)}
className="w-full rounded-lg border border-border bg-background px-4 py-3 text-foreground focus:border-transparent focus:outline-none focus:ring-2 focus:ring-blue-500" className="w-full rounded-xl border border-border bg-background/60 px-4 py-2.5 text-foreground shadow-sm transition-colors placeholder:text-muted-foreground/60 hover:border-foreground/20 focus:border-primary focus:outline-none focus:ring-2 focus:ring-primary/30"
placeholder="john@example.com" placeholder="john@example.com"
required required
disabled={isSubmitting} disabled={isSubmitting}

View File

@@ -11,7 +11,7 @@ const onboardingSteps = [
export default function OnboardingStepProgress({ currentStep }: OnboardingStepProgressProps) { export default function OnboardingStepProgress({ currentStep }: OnboardingStepProgressProps) {
return ( return (
<div className="mb-8"> <div className="mb-5">
<div className="flex items-center justify-between"> <div className="flex items-center justify-between">
{onboardingSteps.map((step, index) => { {onboardingSteps.map((step, index) => {
const isCompleted = index < currentStep; const isCompleted = index < currentStep;
@@ -22,18 +22,18 @@ export default function OnboardingStepProgress({ currentStep }: OnboardingStepPr
<div key={step.title} className="contents"> <div key={step.title} className="contents">
<div className="flex flex-1 flex-col items-center"> <div className="flex flex-1 flex-col items-center">
<div <div
className={`flex h-12 w-12 items-center justify-center rounded-full border-2 transition-colors duration-200 ${ className={`flex h-10 w-10 items-center justify-center rounded-full border-2 transition-all duration-200 ${
isCompleted isCompleted
? 'border-green-500 bg-green-500 text-white' ? 'border-emerald-500 bg-emerald-500 text-white shadow-lg shadow-emerald-500/25'
: isActive : isActive
? 'border-blue-600 bg-blue-600 text-white' ? 'border-primary bg-primary text-primary-foreground shadow-lg shadow-primary/25'
: 'border-border bg-background text-muted-foreground' : 'border-border bg-card text-muted-foreground'
}`} }`}
> >
{isCompleted ? <Check className="h-6 w-6" /> : <Icon className="h-6 w-6" />} {isCompleted ? <Check className="h-5 w-5" /> : <Icon className="h-5 w-5" />}
</div> </div>
<div className="mt-2 text-center"> <div className="mt-1.5 text-center">
<p className={`text-sm font-medium ${isActive ? 'text-foreground' : 'text-muted-foreground'}`}> <p className={`text-sm font-medium ${isActive ? 'text-foreground' : 'text-muted-foreground'}`}>
{step.title} {step.title}
</p> </p>
@@ -42,7 +42,7 @@ export default function OnboardingStepProgress({ currentStep }: OnboardingStepPr
</div> </div>
{index < onboardingSteps.length - 1 && ( {index < onboardingSteps.length - 1 && (
<div className={`mx-2 h-0.5 flex-1 transition-colors duration-200 ${isCompleted ? 'bg-green-500' : 'bg-border'}`} /> <div className={`mx-2 h-0.5 flex-1 transition-colors duration-200 ${isCompleted ? 'bg-emerald-500' : 'bg-border'}`} />
)} )}
</div> </div>
); );