Component Details

Auth Component 04

Auth Component 04

ResetPasswordContent.tsx

Next.jsTailwind CSS
1// app/auth/reset-password/reset-password-content.tsx
2"use client";
3
4import type React from "react";
5import { useState, useEffect } from "react";
6import { useRouter, useSearchParams } 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  CardFooter,
17} from "@/components/ui/card";
18import { Alert, AlertDescription } from "@/components/ui/alert";
19import { Label } from "@/components/ui/label";
20import {
21  AlertCircle,
22  Loader2,
23  CheckCircle,
24  Eye,
25  EyeOff,
26  ArrowLeft,
27} from "lucide-react";
28
29export default function ResetPasswordContent() {
30  const [newPassword, setNewPassword] = useState("");
31  const [confirmPassword, setConfirmPassword] = useState("");
32  const [showNewPassword, setShowNewPassword] = useState(false);
33  const [showConfirmPassword, setShowConfirmPassword] = useState(false);
34  const [error, setError] = useState("");
35  const [success, setSuccess] = useState("");
36  const [isLoading, setIsLoading] = useState(false);
37  const [isValidToken, setIsValidToken] = useState(false);
38  const [email, setEmail] = useState("");
39  const router = useRouter();
40  const searchParams = useSearchParams();
41
42  useEffect(() => {
43    // Check for token in URL
44    const token = searchParams.get("token");
45
46    // Simulate token verification
47    setTimeout(() => {
48      if (token === "demo-token" || !token) {
49        setIsValidToken(true);
50        // Get email from localStorage (simulated)
51        const savedEmail =
52          typeof window !== "undefined"
53            ? localStorage.getItem("passwordResetEmail") || "user@example.com"
54            : "user@example.com";
55        setEmail(savedEmail);
56      } else {
57        setError(
58          "Invalid or expired reset token. Please request a new reset link."
59        );
60      }
61    }, 500);
62  }, [searchParams]);
63
64  const handleResetPassword = async (e: React.FormEvent) => {
65    e.preventDefault();
66    setError("");
67    setIsLoading(true);
68
69    // Validate
70    if (!newPassword || !confirmPassword) {
71      setError("Please enter and confirm your new password");
72      setIsLoading(false);
73      return;
74    }
75
76    if (newPassword.length < 8) {
77      setError("Password must be at least 8 characters long");
78      setIsLoading(false);
79      return;
80    }
81
82    if (newPassword !== confirmPassword) {
83      setError("Passwords do not match");
84      setIsLoading(false);
85      return;
86    }
87
88    // Simulate API call
89    await new Promise((resolve) => setTimeout(resolve, 1500));
90
91    // Success
92    setSuccess(
93      "Password reset successfully! You can now sign in with your new password."
94    );
95
96    // Clear the reset email from storage
97    if (typeof window !== "undefined") {
98      localStorage.removeItem("passwordResetEmail");
99
100      // Store demo password reset
101      localStorage.setItem(
102        "passwordReset",
103        JSON.stringify({
104          email: email,
105          resetAt: new Date().toISOString(),
106        })
107      );
108    }
109
110    // Redirect to login after delay
111    setTimeout(() => {
112      router.push("/signin");
113    }, 2000);
114
115    setIsLoading(false);
116  };
117
118  if (!isValidToken && error) {
119    return (
120      <div className="flex flex-col">
121        <div className="flex-1 flex items-center justify-center px-4 py-12">
122          <div className="w-full max-w-md">
123            <Card className="border shadow-sm">
124              <CardHeader>
125                <CardTitle className="text-2xl">Invalid Reset Link</CardTitle>
126                <CardDescription>
127                  This password reset link is invalid or has expired
128                </CardDescription>
129              </CardHeader>
130              <CardContent>
131                <Alert variant="destructive">
132                  <AlertCircle className="h-4 w-4" />
133                  <AlertDescription>{error}</AlertDescription>
134                </Alert>
135
136                <div className="mt-4 space-y-3">
137                  <p className="text-sm text-muted-foreground">
138                    Please request a new password reset link.
139                  </p>
140                  <Button className="w-full" asChild>
141                    <Link href="/forgot-password">Request New Reset Link</Link>
142                  </Button>
143                </div>
144              </CardContent>
145              <CardFooter>
146                <Button variant="outline" className="w-full" asChild>
147                  <Link href="/">
148                    <ArrowLeft className="mr-2 h-4 w-4" />
149                    Back to Home
150                  </Link>
151                </Button>
152              </CardFooter>
153            </Card>
154          </div>
155        </div>
156      </div>
157    );
158  }
159
160  return (
161    <div className="flex flex-col">
162      <main className="flex-1 flex items-center justify-center px-4 py-12">
163        <div className="w-full max-w-md">
164          <div className="text-center mb-8">
165            <h1 className="text-3xl font-bold mb-2">Create new password</h1>
166            <p className="text-muted-foreground">
167              Set a new password for your account
168            </p>
169            {email && (
170              <p className="text-sm text-muted-foreground mt-2">
171                Resetting password for:{" "}
172                <span className="font-medium">{email}</span>
173              </p>
174            )}
175          </div>
176
177          <Card className="border shadow-sm">
178            <CardHeader className="space-y-1">
179              <CardTitle className="text-2xl">Reset Password</CardTitle>
180              <CardDescription>
181                Enter a new password for your account
182              </CardDescription>
183            </CardHeader>
184
185            <CardContent>
186              <form onSubmit={handleResetPassword} className="space-y-4">
187                <div className="space-y-2">
188                  <Label htmlFor="newPassword">New password</Label>
189                  <div className="relative">
190                    <Input
191                      id="newPassword"
192                      type={showNewPassword ? "text" : "password"}
193                      placeholder="Enter new password (min. 8 characters)"
194                      className="pr-10"
195                      value={newPassword}
196                      onChange={(e) => setNewPassword(e.target.value)}
197                      disabled={isLoading}
198                      autoComplete="new-password"
199                    />
200                    <Button
201                      type="button"
202                      variant="ghost"
203                      size="sm"
204                      className="absolute right-0 top-0 h-full px-3"
205                      onClick={() => setShowNewPassword(!showNewPassword)}
206                    >
207                      {showNewPassword ? (
208                        <EyeOff className="h-4 w-4" />
209                      ) : (
210                        <Eye className="h-4 w-4" />
211                      )}
212                    </Button>
213                  </div>
214                </div>
215
216                <div className="space-y-2">
217                  <Label htmlFor="confirmPassword">Confirm new password</Label>
218                  <div className="relative">
219                    <Input
220                      id="confirmPassword"
221                      type={showConfirmPassword ? "text" : "password"}
222                      placeholder="Confirm your new password"
223                      className="pr-10"
224                      value={confirmPassword}
225                      onChange={(e) => setConfirmPassword(e.target.value)}
226                      disabled={isLoading}
227                      autoComplete="new-password"
228                    />
229                    <Button
230                      type="button"
231                      variant="ghost"
232                      size="sm"
233                      className="absolute right-0 top-0 h-full px-3"
234                      onClick={() =>
235                        setShowConfirmPassword(!showConfirmPassword)
236                      }
237                    >
238                      {showConfirmPassword ? (
239                        <EyeOff className="h-4 w-4" />
240                      ) : (
241                        <Eye className="h-4 w-4" />
242                      )}
243                    </Button>
244                  </div>
245                </div>
246
247                {error && (
248                  <Alert variant="destructive">
249                    <AlertCircle className="h-4 w-4" />
250                    <AlertDescription>{error}</AlertDescription>
251                  </Alert>
252                )}
253
254                {success && (
255                  <Alert
256                    variant="default"
257                    className="bg-green-50 border-green-200 dark:bg-green-900/20 dark:border-green-800"
258                  >
259                    <CheckCircle className="h-4 w-4 text-green-600 dark:text-green-400" />
260                    <AlertDescription className="text-green-800 dark:text-green-300">
261                      {success}
262                    </AlertDescription>
263                  </Alert>
264                )}
265
266                <Button type="submit" className="w-full" disabled={isLoading}>
267                  {isLoading ? (
268                    <>
269                      <Loader2 className="mr-2 h-4 w-4 animate-spin" />
270                      Resetting password...
271                    </>
272                  ) : (
273                    "Reset Password"
274                  )}
275                </Button>
276              </form>
277            </CardContent>
278
279            <CardFooter className="flex flex-col space-y-3">
280              <div className="text-center text-sm text-muted-foreground w-full">
281                <div className="space-y-3">
282                  <div className="relative">
283                    <div className="absolute inset-0 flex items-center">
284                      <div className="w-full border-t"></div>
285                    </div>
286                    <div className="relative flex justify-center text-xs">
287                      <span className="bg-background px-2 text-muted-foreground">
288                        Remember your password?
289                      </span>
290                    </div>
291                  </div>
292
293                  <Button
294                    type="button"
295                    variant="outline"
296                    className="w-full"
297                    asChild
298                  >
299                    <Link href="/signin">Back to Sign in</Link>
300                  </Button>
301                </div>
302              </div>
303            </CardFooter>
304          </Card>
305        </div>
306      </main>
307    </div>
308  );
309}
310
311// app/auth/reset-password/page.tsx - Main page with Suspense
312import { Suspense } from "react";
313import ResetPasswordContent from "./reset-password-content";
314
315export default function ResetPasswordPage() {
316  return (
317    <Suspense fallback={<ResetPasswordSkeleton />}>
318      <ResetPasswordContent />
319    </Suspense>
320  );
321}
322
323// Skeleton loading component
324function ResetPasswordSkeleton() {
325  return (
326    <div className="flex flex-col">
327      <main className="flex-1 flex items-center justify-center px-4 py-12">
328        <div className="w-full max-w-md">
329          <div className="text-center mb-8 animate-pulse">
330            <div className="h-8 bg-gray-200 dark:bg-gray-800 rounded w-3/4 mx-auto mb-2"></div>
331            <div className="h-4 bg-gray-200 dark:bg-gray-800 rounded w-1/2 mx-auto"></div>
332          </div>
333          <div className="border shadow-sm rounded-lg p-6 space-y-6 animate-pulse">
334            <div className="h-6 bg-gray-200 dark:bg-gray-800 rounded w-1/2"></div>
335            <div className="space-y-4">
336              <div className="h-10 bg-gray-200 dark:bg-gray-800 rounded"></div>
337              <div className="h-10 bg-gray-200 dark:bg-gray-800 rounded"></div>
338              <div className="h-12 bg-gray-200 dark:bg-gray-800 rounded"></div>
339            </div>
340          </div>
341        </div>
342      </main>
343    </div>
344  );
345}