Styling updates en sign-in component
- Shadcn UI design tokens toevoegen aan globals.css - Dark mode CSS variabelen voor consistente theming - AnimatedSignIn component met collage layout - Theme toggle en Google OAuth placeholder 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
321
components/ui/sign-in.tsx
Normal file
321
components/ui/sign-in.tsx
Normal file
@@ -0,0 +1,321 @@
|
||||
import React, { useState, useEffect } from 'react';
|
||||
import { Eye, EyeOff, LogIn } from 'lucide-react';
|
||||
|
||||
const AnimatedSignIn: React.FC = () => {
|
||||
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
||||
const [showPassword, setShowPassword] = useState(false);
|
||||
const [email, setEmail] = useState('');
|
||||
const [password, setPassword] = useState('');
|
||||
const [isLoading, setIsLoading] = useState(false);
|
||||
const [mounted, setMounted] = useState(false);
|
||||
|
||||
// Animation states
|
||||
const [formVisible, setFormVisible] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
setMounted(true);
|
||||
setTimeout(() => setFormVisible(true), 300);
|
||||
}, []);
|
||||
|
||||
const toggleTheme = () => {
|
||||
setTheme(theme === 'light' ? 'dark' : 'light');
|
||||
};
|
||||
|
||||
const handleSignIn = (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
setIsLoading(true);
|
||||
|
||||
// Simulate API call
|
||||
setTimeout(() => {
|
||||
console.log('Sign in attempt with:', { email, password });
|
||||
setIsLoading(false);
|
||||
}, 1500);
|
||||
};
|
||||
|
||||
// Only show the component once mounted to avoid hydration issues
|
||||
if (!mounted) return null;
|
||||
|
||||
return (
|
||||
<div className={`min-h-screen w-full transition-colors duration-300 ${theme === 'dark' ? 'bg-slate-900' : 'bg-[#e8f4ef]'}`}>
|
||||
<div className="flex min-h-screen items-center justify-center p-4 md:p-0">
|
||||
<div className={`w-full max-w-6xl overflow-hidden rounded-2xl transition-all duration-500 ${
|
||||
theme === 'dark' ? 'bg-slate-800 shadow-xl shadow-slate-700/20' : 'bg-white shadow-xl shadow-gray-200'
|
||||
} ${formVisible ? 'opacity-100 scale-100' : 'opacity-0 scale-95'}`}>
|
||||
|
||||
{/* Theme toggle */}
|
||||
<button
|
||||
onClick={toggleTheme}
|
||||
className={`absolute right-4 top-4 rounded-full p-2 transition-colors z-10 ${
|
||||
theme === 'dark'
|
||||
? 'bg-slate-700 text-yellow-400 hover:bg-slate-600'
|
||||
: 'bg-gray-100 text-gray-600 hover:bg-gray-200'
|
||||
}`}
|
||||
aria-label="Toggle theme"
|
||||
>
|
||||
{theme === 'dark' ? (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" className="animate-pulse"><circle cx="12" cy="12" r="4"></circle><path d="M12 2v2"></path><path d="M12 20v2"></path><path d="m4.93 4.93 1.41 1.41"></path><path d="m17.66 17.66 1.41 1.41"></path><path d="M2 12h2"></path><path d="M20 12h2"></path><path d="m6.34 17.66-1.41 1.41"></path><path d="m19.07 4.93-1.41 1.41"></path></svg>
|
||||
) : (
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"><path d="M12 3a6 6 0 0 0 9 9 9 9 0 1 1-9-9Z"></path></svg>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="flex flex-col md:flex-row">
|
||||
{/* Left side - Statistics and Images Collage */}
|
||||
<div className="hidden md:block w-full md:w-3/5 bg-gray-100 p-6 animate-fade-in">
|
||||
<div className="grid grid-cols-2 grid-rows-3 gap-4 h-full overflow-hidden">
|
||||
{/* Top left - Person working */}
|
||||
<div className="overflow-hidden rounded-xl">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1486312338219-ce68d2c6f44d"
|
||||
alt="Person working"
|
||||
className="w-full h-full object-cover"
|
||||
style={{ opacity: 0.9 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Top right - Orange stat */}
|
||||
<div
|
||||
className={`rounded-xl flex flex-col justify-center items-center p-6 text-white ${
|
||||
theme === 'dark' ? 'bg-orange-600' : 'bg-orange-500'
|
||||
}`}
|
||||
style={{
|
||||
transform: formVisible ? 'translateY(0)' : 'translateY(20px)',
|
||||
opacity: formVisible ? 1 : 0,
|
||||
transition: 'transform 0.6s ease-out, opacity 0.6s ease-out',
|
||||
transitionDelay: '0.2s',
|
||||
}}
|
||||
>
|
||||
<h2 className="text-5xl font-bold mb-2">41%</h2>
|
||||
<p className="text-center text-sm">of recruiters say entry-level positions are the hardest to fill.</p>
|
||||
</div>
|
||||
|
||||
{/* Middle left - Person at computer */}
|
||||
<div className="overflow-hidden rounded-xl">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1581091226825-a6a2a5aee158"
|
||||
alt="Person at computer"
|
||||
className="w-full h-full object-cover"
|
||||
style={{ opacity: 0.9 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Middle right - Office space */}
|
||||
<div className="overflow-hidden rounded-xl">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1605810230434-7631ac76ec81"
|
||||
alt="Office space"
|
||||
className="w-full h-full object-cover"
|
||||
style={{ opacity: 0.9 }}
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Bottom left - Green stat */}
|
||||
<div
|
||||
className={`rounded-xl flex flex-col justify-center items-center p-6 text-white ${
|
||||
theme === 'dark' ? 'bg-green-600' : 'bg-green-500'
|
||||
}`}
|
||||
style={{
|
||||
transform: formVisible ? 'translateY(0)' : 'translateY(20px)',
|
||||
opacity: formVisible ? 1 : 0,
|
||||
transition: 'transform 0.6s ease-out, opacity 0.6s ease-out',
|
||||
transitionDelay: '0.4s',
|
||||
}}
|
||||
>
|
||||
<h2 className="text-5xl font-bold mb-2">76%</h2>
|
||||
<p className="text-center text-sm">of hiring managers admit attracting the right job candidates is their greatest challenge.</p>
|
||||
</div>
|
||||
|
||||
{/* Bottom right - Library */}
|
||||
<div className="overflow-hidden rounded-xl">
|
||||
<img
|
||||
src="https://images.unsplash.com/photo-1498050108023-c5249f4df085"
|
||||
alt="Desk setup"
|
||||
className="w-full h-full object-cover"
|
||||
style={{ opacity: 0.9 }}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Right side - Sign in form */}
|
||||
<div
|
||||
className={`w-full md:w-2/5 p-8 md:p-12 ${
|
||||
theme === 'dark' ? 'bg-slate-800 text-white' : 'bg-white text-gray-900'
|
||||
}`}
|
||||
style={{
|
||||
transform: formVisible ? 'translateX(0)' : 'translateX(20px)',
|
||||
opacity: formVisible ? 1 : 0,
|
||||
transition: 'transform 0.6s ease-out, opacity 0.6s ease-out'
|
||||
}}
|
||||
>
|
||||
<div className="flex justify-end mb-6">
|
||||
<p className={`text-sm ${theme === 'dark' ? 'text-gray-300' : 'text-gray-600'}`}>
|
||||
Don't have an account?
|
||||
<a
|
||||
href="#"
|
||||
className={`ml-1 font-medium ${
|
||||
theme === 'dark' ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-500'
|
||||
}`}
|
||||
>
|
||||
Sign up
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="mb-8">
|
||||
<h1 className={`text-2xl font-bold mb-1 ${theme === 'dark' ? 'text-white' : 'text-gray-900'}`}>
|
||||
Sign in to <span className="text-blue-500">Jobsly</span>
|
||||
</h1>
|
||||
<p className={`text-sm ${theme === 'dark' ? 'text-gray-300' : 'text-gray-600'}`}>
|
||||
Welcome to Jobsly, please enter your login details below to using the app.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<form onSubmit={handleSignIn} className="space-y-6">
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="email"
|
||||
className={`block text-sm font-medium ${
|
||||
theme === 'dark' ? 'text-gray-200' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Email Address
|
||||
</label>
|
||||
<div className={`relative rounded-md shadow-sm transition-all duration-300`}>
|
||||
<input
|
||||
type="email"
|
||||
name="email"
|
||||
id="email"
|
||||
value={email}
|
||||
onChange={(e) => setEmail(e.target.value)}
|
||||
className={`block w-full rounded-md border py-3 px-4 focus:outline-none focus:ring-2 sm:text-sm ${
|
||||
theme === 'dark'
|
||||
? 'bg-slate-700 border-slate-600 text-white placeholder:text-gray-400 focus:ring-blue-500'
|
||||
: 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-400 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="your.email@example.com"
|
||||
required
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="space-y-1">
|
||||
<label
|
||||
htmlFor="password"
|
||||
className={`block text-sm font-medium ${
|
||||
theme === 'dark' ? 'text-gray-200' : 'text-gray-700'
|
||||
}`}
|
||||
>
|
||||
Password
|
||||
</label>
|
||||
<div className={`relative rounded-md shadow-sm transition-all duration-300`}>
|
||||
<input
|
||||
type={showPassword ? 'text' : 'password'}
|
||||
name="password"
|
||||
id="password"
|
||||
value={password}
|
||||
onChange={(e) => setPassword(e.target.value)}
|
||||
className={`block w-full rounded-md border py-3 px-4 pr-10 focus:outline-none focus:ring-2 sm:text-sm ${
|
||||
theme === 'dark'
|
||||
? 'bg-slate-700 border-slate-600 text-white placeholder:text-gray-400 focus:ring-blue-500'
|
||||
: 'bg-white border-gray-300 text-gray-900 placeholder:text-gray-400 focus:ring-blue-500'
|
||||
}`}
|
||||
placeholder="••••••••"
|
||||
required
|
||||
/>
|
||||
<button
|
||||
type="button"
|
||||
className={`absolute inset-y-0 right-0 flex items-center pr-3 ${
|
||||
theme === 'dark' ? 'text-gray-300' : 'text-gray-500'
|
||||
}`}
|
||||
onClick={() => setShowPassword(!showPassword)}
|
||||
>
|
||||
{showPassword ? (
|
||||
<EyeOff size={18} className="hover:text-gray-700 transition-colors" />
|
||||
) : (
|
||||
<Eye size={18} className="hover:text-gray-700 transition-colors" />
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="flex justify-end">
|
||||
<a
|
||||
href="#"
|
||||
className={`text-sm font-medium ${
|
||||
theme === 'dark' ? 'text-blue-400 hover:text-blue-300' : 'text-blue-500 hover:text-blue-600'
|
||||
}`}
|
||||
>
|
||||
Forgot the password?
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="submit"
|
||||
disabled={isLoading}
|
||||
className={`flex w-full justify-center rounded-md py-3 px-4 text-sm font-semibold text-white shadow-sm transition-all duration-300 ${
|
||||
theme === 'dark'
|
||||
? 'bg-blue-600 hover:bg-blue-500 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500'
|
||||
: 'bg-blue-600 hover:bg-blue-700 focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-600'
|
||||
} ${isLoading ? 'cursor-not-allowed opacity-70' : ''}`}
|
||||
>
|
||||
{isLoading ? (
|
||||
<span className="flex items-center">
|
||||
<svg className="mr-2 h-4 w-4 animate-spin" viewBox="0 0 24 24">
|
||||
<circle
|
||||
className="opacity-25"
|
||||
cx="12"
|
||||
cy="12"
|
||||
r="10"
|
||||
stroke="currentColor"
|
||||
strokeWidth="4"
|
||||
fill="none"
|
||||
/>
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
Signing in...
|
||||
</span>
|
||||
) : (
|
||||
<span className="flex items-center justify-center">
|
||||
Login
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
|
||||
<div className="relative flex items-center py-2">
|
||||
<div className={`flex-grow border-t ${theme === 'dark' ? 'border-gray-700' : 'border-gray-300'}`}></div>
|
||||
<span className={`flex-shrink mx-4 text-sm ${theme === 'dark' ? 'text-gray-400' : 'text-gray-500'}`}>OR</span>
|
||||
<div className={`flex-grow border-t ${theme === 'dark' ? 'border-gray-700' : 'border-gray-300'}`}></div>
|
||||
</div>
|
||||
|
||||
<button
|
||||
type="button"
|
||||
className={`flex w-full items-center justify-center gap-2 rounded-md py-3 px-4 text-sm font-medium transition-colors ${
|
||||
theme === 'dark'
|
||||
? 'border border-gray-700 bg-gray-800 text-white hover:bg-gray-700'
|
||||
: 'border border-gray-300 bg-white text-gray-700 hover:bg-gray-50'
|
||||
}`}
|
||||
>
|
||||
<svg className="h-5 w-5" viewBox="0 0 24 24" fill="currentColor">
|
||||
<path d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" fill="#4285F4" />
|
||||
<path d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" fill="#34A853" />
|
||||
<path d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" fill="#FBBC05" />
|
||||
<path d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" fill="#EA4335" />
|
||||
<path d="M1 1h22v22H1z" fill="none" />
|
||||
</svg>
|
||||
Sign in with Google
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { AnimatedSignIn };
|
||||
Reference in New Issue
Block a user