Component Details

Auth Component 02

Auth Component 02

RegisterPage.tsx

Next.jsTailwind 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}