diff --git a/src/app/[locale]/(main)/order/[id]/confirmed/page.tsx b/src/app/[locale]/(main)/order/[id]/confirmed/page.tsx index f6d8ad38..0a88a0d1 100644 --- a/src/app/[locale]/(main)/order/[id]/confirmed/page.tsx +++ b/src/app/[locale]/(main)/order/[id]/confirmed/page.tsx @@ -8,7 +8,7 @@ type Props = { } export const metadata: Metadata = { title: "Order Confirmed", - description: "You purchase was successful", + description: "Your purchase was successful", } export default async function OrderConfirmedPage(props: Props) { diff --git a/src/app/[locale]/(main)/user/forgot-password/page.tsx b/src/app/[locale]/(main)/user/forgot-password/page.tsx new file mode 100644 index 00000000..8f587156 --- /dev/null +++ b/src/app/[locale]/(main)/user/forgot-password/page.tsx @@ -0,0 +1,16 @@ +import { ForgotPasswordForm } from "@/components/molecules/ForgotPasswordForm/ForgotPasswordForm" +import { Metadata } from "next" + +export const metadata: Metadata = { + title: "Forgot password", + description: "Create a new password", +} + +export default function ForgotPasswordPage() { + + return ( +
+ +
+ ) +} diff --git a/src/components/cells/LabeledInput/LabeledInput.tsx b/src/components/cells/LabeledInput/LabeledInput.tsx index d8be642d..cd524825 100644 --- a/src/components/cells/LabeledInput/LabeledInput.tsx +++ b/src/components/cells/LabeledInput/LabeledInput.tsx @@ -16,7 +16,7 @@ export const LabeledInput = ({ }: LabeledInputProps) => ( ) diff --git a/src/components/molecules/ForgotPasswordForm/ForgotPasswordForm.tsx b/src/components/molecules/ForgotPasswordForm/ForgotPasswordForm.tsx new file mode 100644 index 00000000..75e1e1f4 --- /dev/null +++ b/src/components/molecules/ForgotPasswordForm/ForgotPasswordForm.tsx @@ -0,0 +1,90 @@ +"use client" +import { + FieldError, + FormProvider, + useForm, + useFormContext, +} from "react-hook-form" +import { Button } from "@/components/atoms" +import { zodResolver } from "@hookform/resolvers/zod" +import { LabeledInput } from "@/components/cells" +import { forgotPasswordSchema, ForgotPasswordFormData } from "./schema" +import { sendResetPasswordEmail } from "@/lib/data/customer" +import { toast } from "@/lib/helpers/toast" +import Link from "next/link" + +export const ForgotPasswordForm = () => { + const methods = useForm({ + resolver: zodResolver(forgotPasswordSchema), + defaultValues: { + email: "", + }, + }) + + return ( + +
+ + ) +} + +const Form = () => { + const { + handleSubmit, + register, + formState: { errors, isSubmitting }, + reset, + } = useFormContext() + + const submit = async (data: ForgotPasswordFormData) => { + if (!data.email) return + + const result = await sendResetPasswordEmail(data.email) + + if (!result.success) { + toast.error({ title: result.error || "An error occurred. Please try again." }) + return + } + + reset({ email: "" }) + + toast.success({ + title: `A password reset has been requested for ${data.email}. Check your inbox and spam folder. Remember, the link is only active for one hour.`, + }) + } + + return ( +
+

Forgot your password?

+

+ Enter the email you used to sign up and we’ll send you a password reset + email. +

+ +
+ +
+ +
+ + + + + +
+ +
+ ) +} diff --git a/src/components/molecules/ForgotPasswordForm/schema.ts b/src/components/molecules/ForgotPasswordForm/schema.ts new file mode 100644 index 00000000..144057ce --- /dev/null +++ b/src/components/molecules/ForgotPasswordForm/schema.ts @@ -0,0 +1,7 @@ +import { z } from "zod" + +export const forgotPasswordSchema = z.object({ + email: z.string().nonempty("Please enter email").email("Please enter a valid email"), +}) + +export type ForgotPasswordFormData = z.infer diff --git a/src/components/molecules/LoginForm/LoginForm.tsx b/src/components/molecules/LoginForm/LoginForm.tsx index f0b47b51..1f730ba1 100644 --- a/src/components/molecules/LoginForm/LoginForm.tsx +++ b/src/components/molecules/LoginForm/LoginForm.tsx @@ -8,12 +8,13 @@ import { } from "react-hook-form" import { Button } from "@/components/atoms" import { zodResolver } from "@hookform/resolvers/zod" -import LocalizedClientLink from "@/components/molecules/LocalizedLink/LocalizedLink" import { LabeledInput } from "@/components/cells" import { loginFormSchema, LoginFormData } from "./schema" import { useState } from "react" import { login } from "@/lib/data/customer" import { useRouter } from "next/navigation" +import Link from "next/link" +import { toast } from "@/lib/helpers/toast" export const LoginForm = () => { const methods = useForm({ @@ -32,7 +33,7 @@ export const LoginForm = () => { } const Form = () => { - const [error, setError] = useState("") + const [isAuthError, setIsAuthError] = useState(false); const { handleSubmit, register, @@ -47,45 +48,78 @@ const Form = () => { const res = await login(formData) if (res) { - setError(res) + // Temporary solution. API returns 200 code in case of auth error. To change when API is updated. + const isCredentialsError = + res.toLowerCase().includes("invalid email or password") || + res.toLowerCase().includes("unauthorized") || + res.toLowerCase().includes("incorrect") || + res.toLowerCase().includes("credentials"); + + setIsAuthError(isCredentialsError); + toast.error({ title: res || "An error occurred. Please try again." }) return } - setError("") + setIsAuthError(false); router.push("/user") } + const clearApiError = () => { + isAuthError && setIsAuthError(false); + } + return (
-

- Log in to your account -

-
-
- - - {error &&

{error}

} - -

- Don't have an account yet?{" "} - - Sign up! - -

+
+
+

Log in

+ +
+ + +
+ + + Forgot your password? + + + +
- + +
+

+ Don't have an account yet? +

+ + + +
+
) -} +} \ No newline at end of file diff --git a/src/components/molecules/LoginForm/schema.ts b/src/components/molecules/LoginForm/schema.ts index e5f56675..4de5c439 100644 --- a/src/components/molecules/LoginForm/schema.ts +++ b/src/components/molecules/LoginForm/schema.ts @@ -1,7 +1,7 @@ import { z } from "zod" export const loginFormSchema = z.object({ - email: z.string().nonempty("Please enter email").email("Invalid email"), + email: z.string().nonempty("Please enter email").email("Please enter a valid email"), password: z.string().nonempty("Please enter password"), }) diff --git a/src/components/molecules/ProfilePasswordForm/ProfilePasswordForm.tsx b/src/components/molecules/ProfilePasswordForm/ProfilePasswordForm.tsx index 3a702a1a..c1382d50 100644 --- a/src/components/molecules/ProfilePasswordForm/ProfilePasswordForm.tsx +++ b/src/components/molecules/ProfilePasswordForm/ProfilePasswordForm.tsx @@ -1,9 +1,8 @@ "use client" -import { Button, Card } from "@/components/atoms" +import { Button } from "@/components/atoms" import { LabeledInput } from "@/components/cells" import { zodResolver } from "@hookform/resolvers/zod" -import { CheckCircle } from "@medusajs/icons" import { FieldError, FieldValues, @@ -13,7 +12,7 @@ import { UseFormReturn, } from "react-hook-form" import { ProfilePasswordFormData, profilePasswordSchema } from "./schema" -import { useEffect, useState } from "react" +import { useState } from "react" import { updateCustomerPassword } from "@/lib/data/customer" import { Heading, toast } from "@medusajs/ui" import LocalizedClientLink from "../LocalizedLink/LocalizedLink" diff --git a/src/components/molecules/RegisterForm/RegisterForm.tsx b/src/components/molecules/RegisterForm/RegisterForm.tsx index 2f456118..a8498425 100644 --- a/src/components/molecules/RegisterForm/RegisterForm.tsx +++ b/src/components/molecules/RegisterForm/RegisterForm.tsx @@ -129,19 +129,17 @@ const Form = () => { -

+

Already have an account? -

-

- - - -

+ + + +
) diff --git a/src/lib/helpers/toast.ts b/src/lib/helpers/toast.tsx similarity index 56% rename from src/lib/helpers/toast.ts rename to src/lib/helpers/toast.tsx index 3c1c5bee..b23490c3 100644 --- a/src/lib/helpers/toast.ts +++ b/src/lib/helpers/toast.tsx @@ -1,3 +1,4 @@ +import { DoneIcon, ErrorIcon } from "@/icons"; import { toast as sonnerToast } from "sonner" export const toast = { @@ -15,14 +16,25 @@ export const toast = { title: string }) => { sonnerToast.success(title, { - className: "bg-green-100 text-green-900", description, + duration: 10000, + icon: , + classNames: { + icon: "self-start pt-2", + toast: "items-start gap-3", + title: "text-md text-primary", + }, }) }, error: ({ description, title }: { description?: string; title: string }) => { sonnerToast.error(title, { - className: "bg-red-100 text-red-900", description, + icon: , + classNames: { + icon: "self-start pt-2", + toast: "items-start gap-3", + title: "text-md text-primary" + } }) }, }