Files
mockupAWS/frontend/src/pages/Register.tsx
Luca Sacchi Ricciardi cc60ba17ea
Some checks failed
E2E Tests / Run E2E Tests (push) Has been cancelled
E2E Tests / Visual Regression Tests (push) Has been cancelled
E2E Tests / Smoke Tests (push) Has been cancelled
release: v0.5.0 - Authentication, API Keys & Advanced Features
Complete v0.5.0 implementation:

Database (@db-engineer):
- 3 migrations: users, api_keys, report_schedules tables
- Foreign keys, indexes, constraints, enums

Backend (@backend-dev):
- JWT authentication service with bcrypt (cost=12)
- Auth endpoints: /register, /login, /refresh, /me
- API Keys service with hash storage and prefix validation
- API Keys endpoints: CRUD + rotate
- Security module with JWT HS256

Frontend (@frontend-dev):
- Login/Register pages with validation
- AuthContext with localStorage persistence
- Protected routes implementation
- API Keys management UI (create, revoke, rotate)
- Header with user dropdown

DevOps (@devops-engineer):
- .env.example and .env.production.example
- docker-compose.scheduler.yml
- scripts/setup-secrets.sh
- INFRASTRUCTURE_SETUP.md

QA (@qa-engineer):
- 85 E2E tests: auth.spec.ts, apikeys.spec.ts, scenarios.spec.ts, regression-v050.spec.ts
- auth-helpers.ts with 20+ utility functions
- Test plans and documentation

Architecture (@spec-architect):
- SECURITY.md with best practices
- SECURITY-CHECKLIST.md pre-deployment
- Updated architecture.md with auth flows
- Updated README.md with v0.5.0 features

Documentation:
- Updated todo.md with v0.5.0 status
- Added docs/README.md index
- Complete setup instructions

Dependencies added:
- bcrypt, python-jose, passlib, email-validator

Tested: JWT auth flow, API keys CRUD, protected routes, 85 E2E tests ready

Closes: v0.5.0 milestone
2026-04-07 19:22:47 +02:00

186 lines
6.3 KiB
TypeScript

import { useState } from 'react';
import { Link, useNavigate } from 'react-router-dom';
import { useAuth } from '@/contexts/AuthContext';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Label } from '@/components/ui/label';
import { Card, CardContent, CardDescription, CardFooter, CardHeader, CardTitle } from '@/components/ui/card';
import { Cloud, Loader2 } from 'lucide-react';
import { showToast } from '@/components/ui/toast-utils';
export function Register() {
const [email, setEmail] = useState('');
const [fullName, setFullName] = useState('');
const [password, setPassword] = useState('');
const [confirmPassword, setConfirmPassword] = useState('');
const [isSubmitting, setIsSubmitting] = useState(false);
const [errors, setErrors] = useState<Record<string, string>>({});
const { register } = useAuth();
const navigate = useNavigate();
const validateForm = (): boolean => {
const newErrors: Record<string, string> = {};
// Email validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
newErrors.email = 'Please enter a valid email address';
}
// Password validation
if (password.length < 8) {
newErrors.password = 'Password must be at least 8 characters';
}
// Confirm password
if (password !== confirmPassword) {
newErrors.confirmPassword = 'Passwords do not match';
}
// Full name
if (!fullName.trim()) {
newErrors.fullName = 'Full name is required';
}
setErrors(newErrors);
return Object.keys(newErrors).length === 0;
};
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault();
if (!validateForm()) {
showToast({
title: 'Validation Error',
description: 'Please fix the errors in the form',
variant: 'destructive'
});
return;
}
setIsSubmitting(true);
const success = await register(email, password, fullName);
if (success) {
navigate('/');
}
setIsSubmitting(false);
};
return (
<div className="min-h-screen flex items-center justify-center bg-muted/50 p-4">
<div className="w-full max-w-md">
<div className="flex items-center justify-center gap-2 mb-8">
<Cloud className="h-8 w-8 text-primary" />
<span className="text-2xl font-bold">mockupAWS</span>
</div>
<Card>
<CardHeader className="space-y-1">
<CardTitle className="text-2xl text-center">Create account</CardTitle>
<CardDescription className="text-center">
Enter your details to create a new account
</CardDescription>
</CardHeader>
<form onSubmit={handleSubmit}>
<CardContent className="space-y-4">
<div className="space-y-2">
<Label htmlFor="fullName">Full Name</Label>
<Input
id="fullName"
type="text"
placeholder="John Doe"
value={fullName}
onChange={(e) => setFullName(e.target.value)}
required
autoComplete="name"
/>
{errors.fullName && (
<p className="text-sm text-destructive">{errors.fullName}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="email">Email</Label>
<Input
id="email"
type="email"
placeholder="name@example.com"
value={email}
onChange={(e) => setEmail(e.target.value)}
required
autoComplete="email"
/>
{errors.email && (
<p className="text-sm text-destructive">{errors.email}</p>
)}
</div>
<div className="space-y-2">
<Label htmlFor="password">Password</Label>
<Input
id="password"
type="password"
placeholder="••••••••"
value={password}
onChange={(e) => setPassword(e.target.value)}
required
autoComplete="new-password"
/>
{errors.password && (
<p className="text-sm text-destructive">{errors.password}</p>
)}
<p className="text-xs text-muted-foreground">
Must be at least 8 characters
</p>
</div>
<div className="space-y-2">
<Label htmlFor="confirmPassword">Confirm Password</Label>
<Input
id="confirmPassword"
type="password"
placeholder="••••••••"
value={confirmPassword}
onChange={(e) => setConfirmPassword(e.target.value)}
required
autoComplete="new-password"
/>
{errors.confirmPassword && (
<p className="text-sm text-destructive">{errors.confirmPassword}</p>
)}
</div>
</CardContent>
<CardFooter className="flex flex-col space-y-4">
<Button
type="submit"
className="w-full"
disabled={isSubmitting}
>
{isSubmitting ? (
<>
<Loader2 className="mr-2 h-4 w-4 animate-spin" />
Creating account...
</>
) : (
'Create account'
)}
</Button>
<p className="text-sm text-center text-muted-foreground">
Already have an account?{' '}
<Link to="/login" className="text-primary hover:underline">
Sign in
</Link>
</p>
</CardFooter>
</form>
</Card>
<p className="text-center text-sm text-muted-foreground mt-8">
AWS Cost Simulator & Backend Profiler
</p>
</div>
</div>
);
}