Component Details
Auth Component 02

RegisterPage.tsx
Next.js•Tailwind CSS
1// app/auth/register/page.tsx
2"use client";
3
4import type React from "react";
5import { useState } from "react";
6import { useRouter } from "next/navigation";
7import Link from "next/link";
8import { Button } from "@/components/ui/button";
9import { Input } from "@/components/ui/input";
10import {
11 Card,
12 CardContent,
13 CardHeader,
14 CardTitle,
15 CardDescription,
16} from "@/components/ui/card";
17import { Alert, AlertDescription } from "@/components/ui/alert";
18import { Checkbox } from "@/components/ui/checkbox";
19import { Label } from "@/components/ui/label";
20import {
21 AlertCircle,
22 Loader2,
23 UserPlus,
24 Eye,
25 EyeOff,
26 Mail,
27 Lock,
28 User,
29 Check,
30} from "lucide-react";
31
32export default function RegisterPage() {
33 const [formData, setFormData] = useState({
34 name: "",
35 email: "",
36 password: "",
37 confirmPassword: "",
38 });
39 const [acceptTerms, setAcceptTerms] = useState(false);
40 const [error, setError] = useState("");
41 const [success, setSuccess] = useState("");
42 const [isLoading, setIsLoading] = useState(false);
43 const [showPassword, setShowPassword] = useState(false);
44 const [showConfirmPassword, setShowConfirmPassword] = useState(false);
45 const [passwordStrength, setPasswordStrength] = useState(0);
46 const router = useRouter();
47
48 const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
49 const { id, value } = e.target;
50 setFormData((prev) => ({
51 ...prev,
52 [id]: value,
53 }));
54
55 // Clear errors when typing
56 if (error) setError("");
57 if (success) setSuccess("");
58
59 // Calculate password strength
60 if (id === "password") {
61 calculatePasswordStrength(value);
62 }
63 };
64
65 const calculatePasswordStrength = (password: string) => {
66 let strength = 0;
67
68 // Length check
69 if (password.length >= 8) strength += 1;
70 if (password.length >= 12) strength += 1;
71
72 // Character type checks
73 if (/[A-Z]/.test(password)) strength += 1; // Uppercase
74 if (/[a-z]/.test(password)) strength += 1; // Lowercase
75 if (/[0-9]/.test(password)) strength += 1; // Numbers
76 if (/[^A-Za-z0-9]/.test(password)) strength += 1; // Special chars
77
78 setPasswordStrength(Math.min(strength, 5)); // Max 5
79 };
80
81 const getPasswordStrengthColor = () => {
82 if (passwordStrength <= 1) return "bg-red-500";
83 if (passwordStrength <= 2) return "bg-orange-500";
84 if (passwordStrength <= 3) return "bg-yellow-500";
85 if (passwordStrength <= 4) return "bg-blue-500";
86 return "bg-green-500";
87 };
88
89 const getPasswordStrengthText = () => {
90 if (passwordStrength === 0) return "Very Weak";
91 if (passwordStrength === 1) return "Weak";
92 if (passwordStrength === 2) return "Fair";
93 if (passwordStrength === 3) return "Good";
94 if (passwordStrength === 4) return "Strong";
95 return "Very Strong";
96 };
97
98 // Calculate width class based on percentage
99 const getWidthClass = () => {
100 const percentage = (passwordStrength / 5) * 100;
101 return `w-[${percentage}%]`;
102 };
103
104 const handleRegister = async (e: React.FormEvent) => {
105 e.preventDefault();
106 setError("");
107 setSuccess("");
108 setIsLoading(true);
109
110 // Simulate API call delay
111 await new Promise((resolve) => setTimeout(resolve, 1000));
112
113 // Validation
114 if (!formData.name.trim()) {
115 setError("Please enter your full name");
116 setIsLoading(false);
117 return;
118 }
119
120 if (!formData.email.trim()) {
121 setError("Please enter your email address");
122 setIsLoading(false);
123 return;
124 }
125
126 // Email format validation
127 const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
128 if (!emailRegex.test(formData.email)) {
129 setError("Please enter a valid email address");
130 setIsLoading(false);
131 return;
132 }
133
134 if (!formData.password) {
135 setError("Please create a password");
136 setIsLoading(false);
137 return;
138 }
139
140 if (formData.password.length < 8) {
141 setError("Password must be at least 8 characters long");
142 setIsLoading(false);
143 return;
144 }
145
146 if (formData.password !== formData.confirmPassword) {
147 setError("Passwords do not match");
148 setIsLoading(false);
149 return;
150 }
151
152 if (!acceptTerms) {
153 setError("You must accept the Terms of Service and Privacy Policy");
154 setIsLoading(false);
155 return;
156 }
157
158 // Registration successful
159 setSuccess("Account created successfully! Redirecting...");
160
161 // Store user session in localStorage
162 localStorage.setItem(
163 "userSession",
164 JSON.stringify({
165 name: formData.name,
166 email: formData.email,
167 isAuthenticated: true,
168 isNewUser: true,
169 timestamp: new Date().toISOString(),
170 })
171 );
172
173 // Redirect to home page after a short delay
174 setTimeout(() => {
175 router.push("/");
176 }, 1500);
177
178 setIsLoading(false);
179 };
180
181 return (
182 <div className="flex flex-col">
183 <main className="flex-1 flex items-center justify-center px-4 py-12">
184 <div className="w-full max-w-md">
185 <div className="text-center mb-8">
186 <h1 className="text-3xl font-bold mb-2">Create an account</h1>
187 <p className="text-muted-foreground">
188 Join our community to start your journey
189 </p>
190 </div>
191
192 <Card className="border shadow-sm">
193 <CardHeader className="space-y-1">
194 <CardTitle className="text-2xl">Sign up</CardTitle>
195 <CardDescription>
196 Enter your details to create your account
197 </CardDescription>
198 </CardHeader>
199
200 <CardContent>
201 <form onSubmit={handleRegister} className="space-y-4">
202 {/* Name Field */}
203 <div className="space-y-2">
204 <Label htmlFor="name">Full name</Label>
205 <div className="relative">
206 <User className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
207 <Input
208 id="name"
209 type="text"
210 placeholder="John Doe"
211 className="pl-10"
212 value={formData.name}
213 onChange={handleInputChange}
214 disabled={isLoading}
215 autoComplete="name"
216 />
217 </div>
218 </div>
219
220 {/* Email Field */}
221 <div className="space-y-2">
222 <Label htmlFor="email">Email address</Label>
223 <div className="relative">
224 <Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
225 <Input
226 id="email"
227 type="email"
228 placeholder="name@example.com"
229 className="pl-10"
230 value={formData.email}
231 onChange={handleInputChange}
232 disabled={isLoading}
233 autoComplete="email"
234 />
235 </div>
236 </div>
237
238 {/* Password Field */}
239 <div className="space-y-2">
240 <Label htmlFor="password">Password</Label>
241 <div className="relative">
242 <Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
243 <Input
244 id="password"
245 type={showPassword ? "text" : "password"}
246 placeholder="Create a password (min. 8 characters)"
247 className="pl-10 pr-10"
248 value={formData.password}
249 onChange={handleInputChange}
250 disabled={isLoading}
251 autoComplete="new-password"
252 />
253 <Button
254 type="button"
255 variant="ghost"
256 size="sm"
257 className="absolute right-0 top-0 h-full px-3"
258 onClick={() => setShowPassword(!showPassword)}
259 >
260 {showPassword ? (
261 <EyeOff className="h-4 w-4" />
262 ) : (
263 <Eye className="h-4 w-4" />
264 )}
265 </Button>
266 </div>
267
268 {/* Password Strength Indicator */}
269 {formData.password && (
270 <div className="space-y-1">
271 <div className="flex justify-between text-xs">
272 <span>Password strength:</span>
273 <span
274 className={`font-medium ${
275 passwordStrength <= 1
276 ? "text-red-600"
277 : passwordStrength <= 2
278 ? "text-orange-600"
279 : passwordStrength <= 3
280 ? "text-yellow-600"
281 : passwordStrength <= 4
282 ? "text-blue-600"
283 : "text-green-600"
284 }`}
285 >
286 {getPasswordStrengthText()}
287 </span>
288 </div>
289 <div className="h-1 w-full bg-muted rounded-full overflow-hidden">
290 <div
291 className={`h-full ${getPasswordStrengthColor()} ${getWidthClass()} transition-all duration-300`}
292 />
293 </div>
294 <ul className="text-xs text-muted-foreground space-y-1 mt-2">
295 <li className="flex items-center gap-1">
296 {formData.password.length >= 8 ? (
297 <Check className="h-3 w-3 text-green-600" />
298 ) : (
299 <div className="h-3 w-3 rounded-full bg-muted" />
300 )}
301 At least 8 characters
302 </li>
303 <li className="flex items-center gap-1">
304 {/[A-Z]/.test(formData.password) ? (
305 <Check className="h-3 w-3 text-green-600" />
306 ) : (
307 <div className="h-3 w-3 rounded-full bg-muted" />
308 )}
309 Contains uppercase letter
310 </li>
311 <li className="flex items-center gap-1">
312 {/[0-9]/.test(formData.password) ? (
313 <Check className="h-3 w-3 text-green-600" />
314 ) : (
315 <div className="h-3 w-3 rounded-full bg-muted" />
316 )}
317 Contains number
318 </li>
319 </ul>
320 </div>
321 )}
322 </div>
323
324 {/* Confirm Password Field */}
325 <div className="space-y-2">
326 <Label htmlFor="confirmPassword">Confirm password</Label>
327 <div className="relative">
328 <Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
329 <Input
330 id="confirmPassword"
331 type={showConfirmPassword ? "text" : "password"}
332 placeholder="Confirm your password"
333 className="pl-10 pr-10"
334 value={formData.confirmPassword}
335 onChange={handleInputChange}
336 disabled={isLoading}
337 autoComplete="new-password"
338 />
339 <Button
340 type="button"
341 variant="ghost"
342 size="sm"
343 className="absolute right-0 top-0 h-full px-3"
344 onClick={() =>
345 setShowConfirmPassword(!showConfirmPassword)
346 }
347 >
348 {showConfirmPassword ? (
349 <EyeOff className="h-4 w-4" />
350 ) : (
351 <Eye className="h-4 w-4" />
352 )}
353 </Button>
354 </div>
355 </div>
356
357 {/* Terms and Conditions */}
358 <div className="space-y-3">
359 <div className="flex items-start space-x-2">
360 <Checkbox
361 id="terms"
362 checked={acceptTerms}
363 onCheckedChange={(checked) =>
364 setAcceptTerms(checked as boolean)
365 }
366 disabled={isLoading}
367 className="mt-1"
368 />
369 <Label
370 htmlFor="terms"
371 className="text-sm font-normal leading-tight cursor-pointer"
372 >
373 I agree to the{" "}
374 <Link
375 href="/terms"
376 className="text-primary hover:underline"
377 onClick={(e) => e.preventDefault()}
378 >
379 Terms of Service
380 </Link>{" "}
381 and{" "}
382 <Link
383 href="/privacy"
384 className="text-primary hover:underline"
385 onClick={(e) => e.preventDefault()}
386 >
387 Privacy Policy
388 </Link>
389 </Label>
390 </div>
391 </div>
392
393 {/* Error and Success Messages */}
394 {error && (
395 <Alert variant="destructive">
396 <AlertCircle className="h-4 w-4" />
397 <AlertDescription>{error}</AlertDescription>
398 </Alert>
399 )}
400
401 {success && (
402 <Alert
403 variant="default"
404 className="bg-green-50 border-green-200"
405 >
406 <Check className="h-4 w-4 text-green-600" />
407 <AlertDescription className="text-green-800">
408 {success}
409 </AlertDescription>
410 </Alert>
411 )}
412
413 {/* Submit Button */}
414 <Button type="submit" className="w-full" disabled={isLoading}>
415 {isLoading ? (
416 <>
417 <Loader2 className="mr-2 h-4 w-4 animate-spin" />
418 Creating account...
419 </>
420 ) : (
421 <>
422 <UserPlus className="mr-2 h-4 w-4" />
423 Create account
424 </>
425 )}
426 </Button>
427
428 {/* Divider */}
429 <div className="relative">
430 <div className="absolute inset-0 flex items-center">
431 <div className="w-full border-t"></div>
432 </div>
433 <div className="relative flex justify-center text-xs">
434 <span className="bg-background px-2 text-muted-foreground">
435 Already have an account?
436 </span>
437 </div>
438 </div>
439
440 {/* Sign In Link */}
441 <div className="text-center">
442 <Link href="/signin">
443 <Button type="button" variant="outline" className="w-full">
444 Sign in to existing account
445 </Button>
446 </Link>
447 </div>
448 </form>
449
450 {/* Benefits Section */}
451 <div className="mt-6 p-4 bg-primary/5 rounded-lg">
452 <p className="text-sm font-medium text-primary mb-2">
453 Benefits of joining:
454 </p>
455 <ul className="text-xs text-muted-foreground space-y-1">
456 <li className="flex items-start gap-2">
457 <Check className="h-3 w-3 mt-0.5 text-primary shrink-0" />
458 Save and bookmark your favorite articles
459 </li>
460 <li className="flex items-start gap-2">
461 <Check className="h-3 w-3 mt-0.5 text-primary shrink-0" />
462 Comment and join discussions
463 </li>
464 <li className="flex items-start gap-2">
465 <Check className="h-3 w-3 mt-0.5 text-primary shrink-0" />
466 Receive personalized recommendations
467 </li>
468 <li className="flex items-start gap-2">
469 <Check className="h-3 w-3 mt-0.5 text-primary shrink-0" />
470 Get notified about new content
471 </li>
472 </ul>
473 </div>
474 </CardContent>
475 </Card>
476 </div>
477 </main>
478 </div>
479 );
480}