mirror of
https://github.com/Portabase/portabase.git
synced 2026-07-14 11:16:13 +02:00
Adding reset password feature.
This commit is contained in:
@@ -0,0 +1,113 @@
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Form} from "@/components/ui/form";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {toast} from "sonner";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
import Link from "next/link";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||
import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/login/button-auth/social-auth-button";
|
||||
import {authClient, signIn} from "@/lib/auth/auth-client";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Icon} from "@iconify/react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {
|
||||
ForgotPasswordSchema,
|
||||
ForgotPasswordType
|
||||
} from "@/components/wrappers/auth/forgot-password/forgot-password.schema";
|
||||
import {ArrowLeft} from "lucide-react";
|
||||
import {getServerUrl} from "@/utils/get-server-url";
|
||||
|
||||
export type ForgotPasswordFormProps = {};
|
||||
|
||||
export const ForgotPasswordForm = (props: ForgotPasswordFormProps) => {
|
||||
const router = useRouter();
|
||||
|
||||
const form = useZodForm({
|
||||
schema: ForgotPasswordSchema,
|
||||
});
|
||||
|
||||
|
||||
const mutation = useMutation({
|
||||
mutationFn: async (values: ForgotPasswordType) => {
|
||||
try {
|
||||
const {data, error} = await authClient.requestPasswordReset({
|
||||
email: values.email,
|
||||
redirectTo: `${getServerUrl()}/reset-password`,
|
||||
});
|
||||
if (error) {
|
||||
toast.error(error.message);
|
||||
} else {
|
||||
// @ts-ignore
|
||||
toast.success(data.message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
toast.error("Unexpected client error during login");
|
||||
}
|
||||
},
|
||||
onError: (err: any) => {
|
||||
toast.error(err.message || "Client error");
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<TooltipProvider>
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="grid gap-2 text-center mb-2">
|
||||
<h1 className="text-3xl font-bold">Forgot Password</h1>
|
||||
<p className="text-balance text-muted-foreground">
|
||||
Enter your email to reset your password
|
||||
</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form
|
||||
form={form}
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={async (values) => {
|
||||
await mutation.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="email"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
autoComplete="email"
|
||||
placeholder="example@portabase.io"
|
||||
{...field}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
Send reset link
|
||||
</Button>
|
||||
</Form>
|
||||
|
||||
<div className="mt-4 text-center text-sm flex items-center justify-center gap-1">
|
||||
<ArrowLeft size={14} className="text-gray-400"/>
|
||||
<Link href="/login" className="underline">
|
||||
Go back
|
||||
</Link>
|
||||
</div>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const ForgotPasswordSchema = z.object({
|
||||
email: z
|
||||
.string()
|
||||
.min(1, "Email is required")
|
||||
.email("Invalid email address"),
|
||||
});
|
||||
|
||||
export type ForgotPasswordType = z.infer<typeof ForgotPasswordSchema>;
|
||||
@@ -1,150 +1,3 @@
|
||||
// "use client";
|
||||
//
|
||||
// import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
// import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
// import {Input} from "@/components/ui/input";
|
||||
// import {Form} from "@/components/ui/form";
|
||||
// import {Button} from "@/components/ui/button";
|
||||
// import {toast} from "sonner";
|
||||
// import {useMutation} from "@tanstack/react-query";
|
||||
// import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
// import Link from "next/link";
|
||||
// import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
// import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||
// import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/login/button-auth/social-auth-button";
|
||||
// import {signIn} from "@/lib/auth/auth-client";
|
||||
// import {useRouter} from "next/navigation";
|
||||
// import {Icon} from "@iconify/react";
|
||||
// import {useEffect, useState} from "react";
|
||||
//
|
||||
// export type loginFormProps = {
|
||||
// defaultValues?: LoginType;
|
||||
// authGoogleEnabled: boolean;
|
||||
//
|
||||
// };
|
||||
//
|
||||
// export const LoginForm = (props: loginFormProps) => {
|
||||
// const router = useRouter();
|
||||
//
|
||||
// const form = useZodForm({
|
||||
// schema: LoginSchema,
|
||||
// });
|
||||
//
|
||||
// const [urlParams, setUrlParams] = useState<URLSearchParams>();
|
||||
//
|
||||
// useEffect(() => {
|
||||
// const urlParams = new URLSearchParams(window.location.search);
|
||||
// console.log(urlParams);
|
||||
// setUrlParams(urlParams);
|
||||
// const error = urlParams.get("error");
|
||||
// console.log(urlParams.get("redirect"));
|
||||
// if (error?.includes("pending")) {
|
||||
// toast.error("Your account is not active.");
|
||||
// urlParams.delete("error");
|
||||
// window.history.replaceState({}, document.title, window.location.pathname + "?" + urlParams.toString());
|
||||
// }
|
||||
// }, []);
|
||||
//
|
||||
//
|
||||
// const mutation = useMutation({
|
||||
// mutationFn: async (values: LoginType) => {
|
||||
// const {error} = await signIn.email(
|
||||
// {
|
||||
// password: values.password,
|
||||
// email: values.email,
|
||||
// callbackURL: urlParams?.get("redirect") ?? "/dashboard/profile",
|
||||
// }, {
|
||||
// onSuccess: () => {
|
||||
// toast.success("Login success");
|
||||
// },
|
||||
// });
|
||||
// if (error) {
|
||||
// toast.error(error.message);
|
||||
// }
|
||||
// },
|
||||
// });
|
||||
//
|
||||
// const availableProviders: SocialProviderType[] = [];
|
||||
//
|
||||
// if (props.authGoogleEnabled) {
|
||||
// availableProviders.push(
|
||||
// {
|
||||
// id: "google",
|
||||
// name: "Google",
|
||||
// icon: <Icon icon={"logos:google-icon"} width="25" height="25"/>,
|
||||
// },
|
||||
// )
|
||||
// }
|
||||
//
|
||||
//
|
||||
// return (
|
||||
// <TooltipProvider>
|
||||
// <Card>
|
||||
// <CardHeader>
|
||||
// <div className="grid gap-2 text-center mb-2">
|
||||
// <h1 className="text-3xl font-bold">Login</h1>
|
||||
// <p className="text-balance text-muted-foreground">Enter your informations below to login</p>
|
||||
// </div>
|
||||
// </CardHeader>
|
||||
// <CardContent>
|
||||
// <Form
|
||||
// form={form}
|
||||
// className="flex flex-col gap-4"
|
||||
// onSubmit={async (values) => {
|
||||
// await mutation.mutateAsync(values);
|
||||
// }}
|
||||
// >
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="email"
|
||||
// defaultValue=""
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <FormLabel>Email</FormLabel>
|
||||
// <FormControl>
|
||||
// <Input autoComplete="email webauthn"
|
||||
// placeholder="exemple@portabase.io" {...field} />
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <FormField
|
||||
// control={form.control}
|
||||
// name="password"
|
||||
// defaultValue=""
|
||||
// render={({field}) => (
|
||||
// <FormItem>
|
||||
// <div className="flex items-center">
|
||||
// <FormLabel>Password</FormLabel>
|
||||
// {/* <Link href={"/forgot-password"} className="ml-auto inline-block text-sm underline">
|
||||
// Forgot your password?
|
||||
// </Link>*/}
|
||||
// </div>
|
||||
// <FormControl>
|
||||
// <PasswordInput autoComplete="current-password webauthn"
|
||||
// placeholder="Your password" {...field} />
|
||||
// </FormControl>
|
||||
// <FormMessage/>
|
||||
// </FormItem>
|
||||
// )}
|
||||
// />
|
||||
// <Button>Sign in</Button>
|
||||
// <div className="mt-4 text-center text-sm">
|
||||
// Don't have an account?{" "}
|
||||
// <Link href={"/register"} className="underline">
|
||||
// Sign up
|
||||
// </Link>
|
||||
// </div>
|
||||
// </Form>
|
||||
// <SocialAuthButton
|
||||
// callBackURL={urlParams?.get("redirect") ?? "/dashboard/profile"}
|
||||
// providers={availableProviders}/>
|
||||
// </CardContent>
|
||||
// </Card>
|
||||
// </TooltipProvider>
|
||||
// );
|
||||
// };
|
||||
"use client";
|
||||
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
@@ -156,13 +9,14 @@ import {toast} from "sonner";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {TooltipProvider} from "@/components/ui/tooltip";
|
||||
import Link from "next/link";
|
||||
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {LoginSchema, LoginType} from "@/components/wrappers/auth/login/login-form/login-form.schema";
|
||||
import {SocialAuthButton, SocialProviderType} from "@/components/wrappers/auth/login/button-auth/social-auth-button";
|
||||
import {signIn} from "@/lib/auth/auth-client";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Icon} from "@iconify/react";
|
||||
import {useEffect, useState} from "react";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
|
||||
export type loginFormProps = {
|
||||
defaultValues?: LoginType;
|
||||
@@ -271,9 +125,14 @@ export const LoginForm = (props: loginFormProps) => {
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<div className="flex items-center">
|
||||
<div className="flex items-center justify-between">
|
||||
<FormLabel>Password</FormLabel>
|
||||
{/* Optional forgot password link */}
|
||||
<div className="text-center text-sm">
|
||||
<Link href="/forgot-password" className="hover:underline">
|
||||
Forgot your password ?
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
<FormControl>
|
||||
<PasswordInput
|
||||
@@ -289,18 +148,26 @@ export const LoginForm = (props: loginFormProps) => {
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
{mutation.isPending ? "Signing in..." : "Sign in"}
|
||||
</Button>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account?{" "}
|
||||
<Link href="/register" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
</Form>
|
||||
|
||||
<div className="relative my-4 flex items-center justify-center overflow-hidden">
|
||||
<Separator/>
|
||||
<div className="px-2 text-center bg-card text-sm">OR</div>
|
||||
<Separator/>
|
||||
</div>
|
||||
|
||||
<SocialAuthButton
|
||||
callBackURL={urlParams.get("redirect") ?? "/dashboard/profile"}
|
||||
providers={availableProviders}
|
||||
/>
|
||||
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Don't have an account ?{" "}
|
||||
<Link href="/register" className="underline">
|
||||
Sign up
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</CardContent>
|
||||
</Card>
|
||||
</TooltipProvider>
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
"use client";
|
||||
|
||||
import * as React from "react";
|
||||
import { EyeIcon, EyeOffIcon } from "lucide-react";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import type { InputHTMLAttributes } from "react";
|
||||
|
||||
type InputProps = InputHTMLAttributes<HTMLInputElement>;
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const PasswordInput = React.forwardRef<HTMLInputElement, InputProps>(({ className, ...props }, ref) => {
|
||||
const [showPassword, setShowPassword] = React.useState(false);
|
||||
const disabled = props.value === "" || props.value === undefined || props.disabled;
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<Input type={showPassword ? "text" : "password"} className={cn("hide-password-toggle pr-10", className)} ref={ref} {...props} />
|
||||
<Button
|
||||
type="button"
|
||||
variant="ghost"
|
||||
size="sm"
|
||||
className="absolute right-0 top-0 h-full px-3 py-2 hover:bg-transparent"
|
||||
onClick={() => setShowPassword((prev) => !prev)}
|
||||
disabled={disabled}
|
||||
>
|
||||
{showPassword && !disabled ? <EyeIcon className="h-4 w-4" aria-hidden="true" /> : <EyeOffIcon className="h-4 w-4" aria-hidden="true" />}
|
||||
<span className="sr-only">{showPassword ? "Hide password" : "Show password"}</span>
|
||||
</Button>
|
||||
<style>{`
|
||||
.hide-password-toggle::-ms-reveal,
|
||||
.hide-password-toggle::-ms-clear {
|
||||
visibility: hidden;
|
||||
pointer-events: none;
|
||||
display: none;
|
||||
}
|
||||
`}</style>
|
||||
</div>
|
||||
);
|
||||
});
|
||||
PasswordInput.displayName = "PasswordInput";
|
||||
|
||||
export { PasswordInput };
|
||||
@@ -1,19 +1,20 @@
|
||||
"use client";
|
||||
|
||||
import { useRouter } from "next/navigation";
|
||||
import { Info } from "lucide-react";
|
||||
import { useMutation } from "@tanstack/react-query";
|
||||
import { toast } from "sonner";
|
||||
import {useRouter} from "next/navigation";
|
||||
import {Info} from "lucide-react";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {toast} from "sonner";
|
||||
|
||||
import { Card, CardContent, CardHeader } from "@/components/ui/card";
|
||||
import { FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm } from "@/components/ui/form";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Form } from "@/components/ui/form";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { TooltipProvider, TooltipTrigger, Tooltip, TooltipContent } from "@/components/ui/tooltip";
|
||||
import { RegisterSchema, RegisterType } from "@/components/wrappers/auth/register/register-form/register-form.schema";
|
||||
import { PasswordInput } from "@/components/wrappers/auth/password-input/password-input";
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {FormControl, FormField, FormItem, FormLabel, FormMessage, useZodForm} from "@/components/ui/form";
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {Form} from "@/components/ui/form";
|
||||
import {Button} from "@/components/ui/button";
|
||||
import {TooltipProvider, TooltipTrigger, Tooltip, TooltipContent} from "@/components/ui/tooltip";
|
||||
import {RegisterSchema, RegisterType} from "@/components/wrappers/auth/register/register-form/register-form.schema";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {signUp} from "@/lib/auth/auth-client";
|
||||
import Link from "next/link";
|
||||
|
||||
export type registerFormProps = {
|
||||
defaultValues?: RegisterType;
|
||||
@@ -29,7 +30,7 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
mutationFn: async (values: RegisterType) => {
|
||||
await signUp.email(values, {
|
||||
onSuccess: () => {
|
||||
toast.success(`Success`);
|
||||
toast.success(`Account successfully created`);
|
||||
router.refresh();
|
||||
router.push(`/login`);
|
||||
},
|
||||
@@ -63,13 +64,13 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
control={form.control}
|
||||
name="name"
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Name</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="Your name" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -77,13 +78,13 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
control={form.control}
|
||||
name="email"
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Email</FormLabel>
|
||||
<FormControl>
|
||||
<Input placeholder="exemple@portabase.io" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -91,18 +92,19 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
control={form.control}
|
||||
name="password"
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel className="flex">
|
||||
Password
|
||||
<TooltipProvider>
|
||||
<Tooltip>
|
||||
<TooltipTrigger asChild>
|
||||
<Info className="ml-3" size="15" />
|
||||
<Info className="ml-3" size="15"/>
|
||||
</TooltipTrigger>
|
||||
<TooltipContent>
|
||||
<p>
|
||||
Min. 8 characters, 1 uppercase (A-Z), 1 lowercase (a-z), 1 number (0-9), 1 special character (!, @,
|
||||
Min. 8 characters, 1 uppercase (A-Z), 1 lowercase (a-z), 1
|
||||
number (0-9), 1 special character (!, @,
|
||||
etc.)
|
||||
</p>
|
||||
</TooltipContent>
|
||||
@@ -112,7 +114,7 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
<FormControl>
|
||||
<PasswordInput placeholder="Your password" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
@@ -120,17 +122,26 @@ export const RegisterForm = (props: registerFormProps) => {
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
defaultValue=""
|
||||
render={({ field }) => (
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Password Confirmation</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder="Conform your password" {...field} />
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
<FormMessage/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<Button>Sign up</Button>
|
||||
<Button type="submit" disabled={mutation.isPending}>
|
||||
Sign up
|
||||
</Button>
|
||||
<div className="mt-4 text-center text-sm">
|
||||
Already have an account ?{" "}
|
||||
<Link href="/login" className="underline">
|
||||
Sign in
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
|
||||
@@ -0,0 +1,94 @@
|
||||
"use client";
|
||||
|
||||
import {useRouter} from "next/navigation";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {Form, FormControl, FormField, FormItem, FormLabel, useZodForm} from "@/components/ui/form";
|
||||
import {PasswordStrengthInput} from "@/components/ui/password-input-indicator";
|
||||
import {ResetPasswordSchema, ResetPasswordType} from "@/components/wrappers/auth/reset-password/reset-password-schema";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {Card, CardContent, CardHeader} from "@/components/ui/card";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {authClient} from "@/lib/auth/auth-client";
|
||||
import {toast} from "sonner";
|
||||
|
||||
type ResetPasswordFormProps = {
|
||||
token: string;
|
||||
};
|
||||
|
||||
export const ResetPasswordForm = ({token}: ResetPasswordFormProps) => {
|
||||
|
||||
const router = useRouter();
|
||||
const form = useZodForm({
|
||||
schema: ResetPasswordSchema,
|
||||
});
|
||||
|
||||
const mutationResetPassword = useMutation({
|
||||
mutationFn: async (data: ResetPasswordType) => {
|
||||
|
||||
await authClient.resetPassword({
|
||||
newPassword: data.password,
|
||||
token,
|
||||
}, {
|
||||
onSuccess: (response) => {
|
||||
console.log(response);
|
||||
toast.success("Password changed successfully.");
|
||||
setTimeout(() => router.push("/"), 1000);
|
||||
},
|
||||
onError: (error) => {
|
||||
console.error(error);
|
||||
toast.error(error.error.message);
|
||||
},
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
return (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<div className="grid gap-2 text-center mb-2">
|
||||
<h1 className="text-3xl font-bold">Reset Password</h1>
|
||||
<p className="text-balance text-muted-foreground">Fill information bellow to change your
|
||||
password</p>
|
||||
</div>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<Form
|
||||
form={form}
|
||||
className="flex flex-col gap-4"
|
||||
onSubmit={async (values) => {
|
||||
await mutationResetPassword.mutateAsync(values);
|
||||
}}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="password"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<PasswordStrengthInput label={"Enter new password"} field={field}/>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="confirmPassword"
|
||||
defaultValue=""
|
||||
render={({field}) => (
|
||||
<FormItem>
|
||||
<FormLabel>Confirmation Password</FormLabel>
|
||||
<FormControl>
|
||||
<PasswordInput placeholder={"Confirm password"} {...field}
|
||||
value={field.value ?? ""}/>
|
||||
</FormControl>
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
|
||||
<ButtonWithLoading type="submit" isPending={mutationResetPassword.isPending}>
|
||||
Validate
|
||||
</ButtonWithLoading>
|
||||
</Form>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,22 @@
|
||||
import {z} from "zod";
|
||||
|
||||
const passwordRegex = /^(?=.*?[A-Z])(?=.*?[a-z])(?=.*?[0-9])(?=.*?[#?!@$%^&*-])/;
|
||||
|
||||
const zPassword = () => z.string().min(8, {message: "New Password too short"}).regex(passwordRegex, {message: "New Password too weak"});
|
||||
|
||||
export const ResetPasswordSchema = z
|
||||
.object({
|
||||
password: zPassword(),
|
||||
confirmPassword: zPassword(),
|
||||
})
|
||||
.superRefine(({confirmPassword, password}, ctx) => {
|
||||
if (confirmPassword !== password) {
|
||||
ctx.addIssue({
|
||||
code: "custom",
|
||||
message: "Passwords don't match.",
|
||||
path: ["confirmPassword"],
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export type ResetPasswordType = z.infer<typeof ResetPasswordSchema>;
|
||||
@@ -0,0 +1,37 @@
|
||||
"use client"
|
||||
|
||||
import {useRouter, useSearchParams} from "next/navigation";
|
||||
import {ResetPasswordForm} from "@/components/wrappers/auth/reset-password/reset-password-form";
|
||||
import {toast} from "sonner";
|
||||
import {useEffect} from "react";
|
||||
import {LoadingSpinner} from "@/components/wrappers/common/loading/loading-spinner";
|
||||
|
||||
export const ResetPasswordSection = () => {
|
||||
const router = useRouter();
|
||||
const searchParams = useSearchParams();
|
||||
|
||||
const token = searchParams.get("token");
|
||||
const error = searchParams.get("error");
|
||||
|
||||
useEffect(() => {
|
||||
if (error || !token) {
|
||||
if (error == "INVALID_TOKEN") {
|
||||
toast.error("Invalid reset token");
|
||||
} else {
|
||||
toast.error("An error occurred.");
|
||||
}
|
||||
setTimeout(() => router.push("/"), 1000);
|
||||
}
|
||||
}, [error, token, router]);
|
||||
|
||||
|
||||
if (!token) return (
|
||||
<div className="w-full h-full flex flex-col items-center justify-center">
|
||||
<LoadingSpinner size={50}/>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<ResetPasswordForm token={token}/>
|
||||
)
|
||||
}
|
||||
+1
-1
@@ -20,7 +20,7 @@ import {
|
||||
EmailFormSchema,
|
||||
EmailFormType
|
||||
} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
|
||||
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {
|
||||
updateEmailSettingsAction
|
||||
} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.action";
|
||||
|
||||
@@ -2,12 +2,13 @@ import {EmailForm} from "@/components/wrappers/dashboard/admin/tabs/admin-email-
|
||||
import {Send} from "lucide-react";
|
||||
import {ButtonWithLoading} from "@/components/wrappers/common/button/button-with-loading";
|
||||
import {useMutation} from "@tanstack/react-query";
|
||||
import {sendEmail} from "@/utils/email-helper";
|
||||
import {sendEmail} from "@/lib/email/email-helper";
|
||||
import {render} from "@react-email/render";
|
||||
import {toast} from "sonner";
|
||||
import {Setting} from "@/db/schema/01_setting";
|
||||
import {EmailFormType} from "@/components/wrappers/dashboard/admin/tabs/admin-email-tab/email-form/email-form.schema";
|
||||
import TestEmailSettings from "../../../../../../../emails/TestEmailSettings";
|
||||
import TestEmailSettings from "@/components/emails/email-settings-test"
|
||||
|
||||
|
||||
export type SettingsEmailTabProps = {
|
||||
settings: Setting;
|
||||
|
||||
+1
-1
@@ -12,7 +12,7 @@ import { S3FormSchema, S3FormType } from "@/components/wrappers/dashboard/admin/
|
||||
import { useRouter } from "next/navigation";
|
||||
import { toast } from "sonner";
|
||||
import { updateS3SettingsAction } from "@/components/wrappers/dashboard/admin/tabs/admin-storage-tab/storage-s3/s3-form.action";
|
||||
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
|
||||
export type S3FormProps = {
|
||||
defaultValues?: S3FormType;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
"use client";
|
||||
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
import {useState} from "react";
|
||||
import {CopyButton} from "@/components/wrappers/common/button/copy-button";
|
||||
|
||||
|
||||
+1
-1
@@ -2,7 +2,7 @@ import {FormControl, FormField, FormItem, FormLabel, FormMessage} from "@/compon
|
||||
import {Input} from "@/components/ui/input";
|
||||
import {UseFormReturn} from "react-hook-form";
|
||||
import {Separator} from "@/components/ui/separator";
|
||||
import {PasswordInput} from "@/components/wrappers/auth/password-input/password-input";
|
||||
import {PasswordInput} from "@/components/ui/password-input";
|
||||
|
||||
|
||||
type NotifierSmtpFormProps = {
|
||||
|
||||
Reference in New Issue
Block a user