feat(admin-ui): Implements redesign of public pages (#3504)

* redesign public pages

* rm build files

* fix size of button
This commit is contained in:
Kasper Fabricius Kristensen
2023-03-17 10:17:47 +01:00
committed by GitHub
parent 0d1b63d773
commit 9ad15d3a88
13 changed files with 418 additions and 405 deletions
@@ -1,7 +1,11 @@
import React from "react"
import IconProps from "../types/icon-type"
const MedusaIcon: React.FC<IconProps> = ({ size = "48", ...attributes }) => {
const MedusaIcon: React.FC<IconProps> = ({
size = "48",
color = "#8B5CF6",
...attributes
}) => {
const width = +size * 0.9375 // width relative to height (from size prop)
return (
<svg
@@ -14,7 +18,7 @@ const MedusaIcon: React.FC<IconProps> = ({ size = "48", ...attributes }) => {
>
<path
d="M38.8535 7.79156L28.0165 1.5383C24.4707 -0.512767 20.1259 -0.512767 16.5802 1.5383L5.69318 7.79156C2.19737 9.84263 0 13.6446 0 17.6967V30.2533C0 34.3554 2.19737 38.1073 5.69318 40.1584L16.5302 46.4617C20.076 48.5128 24.4208 48.5128 27.9665 46.4617L38.8036 40.1584C42.3493 38.1073 44.4967 34.3554 44.4967 30.2533V17.6967C44.5966 13.6446 42.3992 9.84263 38.8535 7.79156ZM22.2733 35.1558C16.1307 35.1558 11.1367 30.1532 11.1367 24C11.1367 17.8468 16.1307 12.8442 22.2733 12.8442C28.416 12.8442 33.4599 17.8468 33.4599 24C33.4599 30.1532 28.4659 35.1558 22.2733 35.1558Z"
fill="#8B5CF6"
fill={color}
/>
</svg>
)
@@ -24,7 +24,6 @@ const SigninInput = React.forwardRef(
placeholder,
name,
key,
required,
onChange,
onFocus,
className,
@@ -52,20 +51,17 @@ const SigninInput = React.forwardRef(
return (
<div
className={clsx(
"mb-xsmall rounded-rounded h-[48px] w-[320px] overflow-hidden border last:mb-0",
"rounded-rounded h-[40px] w-[280px] overflow-hidden border",
"bg-grey-5 inter-base-regular placeholder:text-grey-40",
"focus-within:shadow-input focus-within:border-violet-60",
"flex items-center",
{
"text-grey-40 pointer-events-none focus-within:border-none focus-within:shadow-none":
"text-grey-40 pl-xsmall pointer-events-none focus-within:border-none focus-within:shadow-none":
props.readOnly,
},
className
)}
>
{props.readOnly && (
<LockIcon size={16} className="text-grey-40 ml-base" />
)}
<input
className={clsx(
"remove-number-spinner leading-base w-full bg-transparent py-3 px-4 outline-none outline-0",
@@ -88,12 +84,17 @@ const SigninInput = React.forwardRef(
onClick={() => setShowPassword(!showPassword)}
className="text-grey-40 focus:text-violet-60 px-4 focus:outline-none"
>
{showPassword ? <EyeIcon /> : <EyeOffIcon />}
{showPassword ? <EyeIcon size={16} /> : <EyeOffIcon size={16} />}
</button>
)}
{props.readOnly && (
<LockIcon size={16} className="text-grey-40 mr-base" />
)}
</div>
)
}
)
SigninInput.displayName = "SigninInput"
export default SigninInput
@@ -1,7 +1,7 @@
import { useAdminLogin } from "medusa-react"
import React, { useState } from "react"
import { useForm } from "react-hook-form"
import { useNavigate } from "react-router-dom"
import InputError from "../../atoms/input-error"
import Button from "../../fundamentals/button"
import SigninInput from "../../molecules/input-signin"
@@ -14,57 +14,63 @@ type LoginCardProps = {
toResetPassword: () => void
}
const LoginCard: React.FC<LoginCardProps> = ({ toResetPassword }) => {
const [isInvalidLogin, setIsInvalidLogin] = useState(false)
const { register, handleSubmit, reset } = useForm<FormValues>()
const LoginCard = ({ toResetPassword }: LoginCardProps) => {
const {
register,
handleSubmit,
setError,
formState: { errors },
} = useForm<FormValues>()
const navigate = useNavigate()
const login = useAdminLogin()
const { mutate, isLoading } = useAdminLogin()
const onSubmit = (values: FormValues) => {
login.mutate(values, {
mutate(values, {
onSuccess: () => {
navigate("/a/orders")
},
onError: () => {
setIsInvalidLogin(true)
reset()
setError(
"password",
{
type: "manual",
message: "These credentials do not match our records.",
},
{
shouldFocus: true,
}
)
},
})
}
return (
<form onSubmit={handleSubmit(onSubmit)}>
<div className="flex flex-col items-center">
<span className="inter-2xlarge-semibold text-grey-90 mt-4">
Welcome back!
</span>
<span className="inter-base-regular text-grey-50 mt-2">
It's great to see you 👋🏼
</span>
<span className="inter-base-regular text-grey-50 mb-xlarge">
Log in to your account below
</span>
<SigninInput
placeholder="Email..."
{...register("email", { required: true })}
autoComplete="email"
/>
<SigninInput
placeholder="Password..."
type={"password"}
{...register("password", { required: true })}
autoComplete="current-password"
/>
{isInvalidLogin && (
<span className="inter-small-regular mt-2 w-full text-rose-50">
These credentials do not match our records
</span>
)}
<h1 className="inter-xlarge-semibold text-grey-90 mb-large text-[20px]">
Log in to Medusa
</h1>
<div>
<SigninInput
placeholder="Email"
{...register("email", { required: true })}
autoComplete="email"
className="mb-small"
/>
<SigninInput
placeholder="Password"
type={"password"}
{...register("password", { required: true })}
autoComplete="current-password"
className="mb-xsmall"
/>
<InputError errors={errors} name="password" />
</div>
<Button
className="rounded-rounded inter-base-regular mt-4 w-[320px]"
variant="primary"
size="large"
className="rounded-rounded inter-base-regular mt-4 w-[280px]"
variant="secondary"
size="medium"
type="submit"
loading={login.isLoading}
loading={isLoading}
>
Continue
</Button>
@@ -72,7 +78,7 @@ const LoginCard: React.FC<LoginCardProps> = ({ toResetPassword }) => {
className="inter-small-regular text-grey-50 mt-8 cursor-pointer"
onClick={toResetPassword}
>
Reset password
Forgot your password?
</span>
</div>
</form>
@@ -1,7 +1,12 @@
import { useAdminSendResetPasswordToken } from "medusa-react"
import React, { useState } from "react"
import { useForm } from "react-hook-form"
import CheckCircleIcon from "../../fundamentals/icons/check-circle-icon"
import useNotification from "../../../hooks/use-notification"
import { getErrorMessage } from "../../../utils/error-messages"
import FormValidator from "../../../utils/form-validator"
import InputError from "../../atoms/input-error"
import Button from "../../fundamentals/button"
import CheckCircleFillIcon from "../../fundamentals/icons/check-circle-fill-icon"
import SigninInput from "../../molecules/input-signin"
type ResetTokenCardProps = {
@@ -12,26 +17,23 @@ type FormValues = {
email: string
}
const checkMail = /^\S+@\S+$/i
const emailRegex = new RegExp(
"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$"
)
const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
const [unrecognizedEmail, setUnrecognizedEmail] = useState(false)
const [invalidEmail, setInvalidEmail] = useState(false)
const [mailSent, setSentMail] = useState(false)
const { register, handleSubmit } = useForm<FormValues>()
const {
register,
handleSubmit,
formState: { errors },
} = useForm<FormValues>()
const sendEmail = useAdminSendResetPasswordToken()
const { mutate, isLoading } = useAdminSendResetPasswordToken()
const notification = useNotification()
const onSubmit = (values: FormValues) => {
if (!checkMail.test(values.email)) {
setInvalidEmail(true)
return
}
setInvalidEmail(false)
setUnrecognizedEmail(false)
sendEmail.mutate(
const onSubmit = handleSubmit((values: FormValues) => {
mutate(
{
email: values.email,
},
@@ -39,66 +41,60 @@ const ResetTokenCard: React.FC<ResetTokenCardProps> = ({ goBack }) => {
onSuccess: () => {
setSentMail(true)
},
onError: () => {
setUnrecognizedEmail(true)
onError: (error) => {
notification("Error", getErrorMessage(error), "error")
},
}
)
}
})
return (
<form onSubmit={handleSubmit(onSubmit)}>
<form onSubmit={onSubmit}>
<div className="flex flex-col items-center">
<span className="inter-2xlarge-semibold mt-base text-grey-90">
<h1 className="inter-xlarge-semibold text-grey-90 mb-xsmall text-[20px]">
Reset your password
</span>
<span className="inter-base-regular text-grey-50 mt-xsmall text-center">
Enter your email address below, and we'll send you
</h1>
<span className="inter-base-regular text-grey-50 mb-large text-center">
Enter your email address below, and we&apos;ll
<br />
instructions on how to reset your password.
send you instructions on how to reset
<br />
your password.
</span>
{!mailSent ? (
<>
<SigninInput
placeholder="lebron@james.com..."
{...register("email", { required: true })}
className="mt-xlarge mb-0"
/>
{unrecognizedEmail && (
<div className="mt-xsmall w-[318px]">
<span className="inter-small-regular text-left text-rose-50">
We can't find a user with that email address
</span>
</div>
)}
{invalidEmail && (
<div className="mt-xsmall w-[318px]">
<span className="inter-small-regular text-left text-rose-50">
Not a valid email address
</span>
</div>
)}
<button
className="text-grey-0 rounded-rounded inter-base-regular mt-4 h-[48px] w-[320px] border bg-violet-50 py-3 px-4"
<div className="w-[280px]">
<SigninInput
placeholder="Email"
{...register("email", {
required: FormValidator.required("Email"),
pattern: {
value: emailRegex,
message: "This is not a valid email",
},
})}
/>
<InputError errors={errors} name="email" />
</div>
<Button
variant="secondary"
size="medium"
className="mt-large w-[280px]"
type="submit"
loading={isLoading}
>
Send reset instructions
</button>
</Button>
</>
) : (
<div className="text-violet-60 rounded-rounded bg-violet-10 p-base gap-x-small mt-large flex">
<div className="text-grey-60 rounded-rounded bg-grey-5 border-grey-20 p-base gap-x-small flex w-[280px] items-center border">
<div>
<CheckCircleIcon size={20} />
<CheckCircleFillIcon className="text-blue-50" size={20} />
</div>
<div className="gap-y-2xsmall flex flex-col">
<span className="inter-small-semibold">
<span className="inter-base-regular">
Succesfully sent you an email
</span>
<span className="inter-small-regular">
We've sent you an email which you can use to reset your
password. Check your spam folder if you haven't received it
after a few minutes.
</span>
</div>
</div>
)}
@@ -1,27 +1,62 @@
const LoginLayout = ({ children }) => {
import { PropsWithChildren } from "react"
import { Toaster } from "react-hot-toast"
const PublicLayout = ({ children }: PropsWithChildren) => {
return (
<div className="flex min-h-screen flex-col">
<div className="grid min-h-screen grid-cols-1 grid-rows-1">
<div
className="flex flex-col items-center"
style={{
background: "linear-gradient(73.29deg, #7C53FF 0%, #F796FF 100%)",
}}
>
{children}
<div className="text-grey-0 inter-base-regular pb-12">
© Medusa Commerce <span>&#183;</span>{" "}
<a
style={{ color: "inherit", textDecoration: "none" }}
href="mailto:hello@medusajs.com"
>
Contact
</a>
</div>
</div>
<div className="flex min-h-screen flex-col items-center justify-center">
<Toaster
containerStyle={{
top: 24,
left: 24,
bottom: 24,
right: 24,
}}
position="bottom-right"
/>
<div className="mb-large">
<Logo />
</div>
{children}
</div>
)
}
export default LoginLayout
const Logo = () => {
return (
<div className="w-5xlarge h-5xlarge flex items-center justify-center rounded-full bg-gradient-to-t from-[#26292B] via-[#151718] to-[#151718]">
<SVG />
</div>
)
}
const SVG = () => {
return (
<svg
width="40"
height="40"
viewBox="0 0 40 40"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M32.4895 7.84367L24.3377 3.15373C21.6705 1.61542 18.4022 1.61542 15.7351 3.15373L7.5457 7.84367C4.91608 9.38197 3.26318 12.2335 3.26318 15.2725V24.6899C3.26318 27.7665 4.91608 30.5805 7.5457 32.1188L15.6975 36.8463C18.3647 38.3846 21.6329 38.3846 24.3001 36.8463L32.4519 32.1188C35.1191 30.5805 36.7344 27.7665 36.7344 24.6899V15.2725C36.8095 12.2335 35.1566 9.38197 32.4895 7.84367ZM20.0176 28.3669C15.397 28.3669 11.6404 24.6149 11.6404 20C11.6404 15.3851 15.397 11.6331 20.0176 11.6331C24.6382 11.6331 28.4323 15.3851 28.4323 20C28.4323 24.6149 24.6758 28.3669 20.0176 28.3669Z"
fill="url(#paint0_linear_7693_9181)"
/>
<defs>
<linearGradient
id="paint0_linear_7693_9181"
x1="20"
y1="2"
x2="20"
y2="38"
gradientUnits="userSpaceOnUse"
>
<stop stopColor="white" />
<stop offset="1" stopColor="white" stopOpacity="0.8" />
</linearGradient>
</defs>
</svg>
)
}
export default PublicLayout
+125 -154
View File
@@ -1,21 +1,19 @@
import ConfettiGenerator from "confetti-js"
import { useAdminAcceptInvite } from "medusa-react"
import qs from "qs"
import { useEffect, useState } from "react"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { decodeToken } from "react-jwt"
import { Link, useLocation, useNavigate } from "react-router-dom"
import { useLocation, useNavigate } from "react-router-dom"
import InputError from "../components/atoms/input-error"
import Button from "../components/fundamentals/button"
import LongArrowRightIcon from "../components/fundamentals/icons/long-arrow-right-icon"
import MedusaIcon from "../components/fundamentals/icons/medusa-icon"
import MedusaVice from "../components/fundamentals/icons/medusa-vice"
import SigninInput from "../components/molecules/input-signin"
import SEO from "../components/seo"
import LoginLayout from "../components/templates/login-layout"
import PublicLayout from "../components/templates/login-layout"
import useNotification from "../hooks/use-notification"
import { getErrorMessage } from "../utils/error-messages"
import FormValidator from "../utils/form-validator"
type formValues = {
type FormValues = {
password: string
repeat_password: string
first_name: string
@@ -36,33 +34,12 @@ const InvitePage = () => {
}
}
const [passwordMismatch, setPasswordMismatch] = useState(false)
const [ready, setReady] = useState(false)
useEffect(() => {
const confettiSettings = {
target: "confetti-canvas",
start_from_edge: true,
size: 3,
clock: 25,
colors: [
[251, 146, 60],
[167, 139, 250],
[251, 146, 60],
[96, 165, 250],
[45, 212, 191],
[250, 204, 21],
[232, 121, 249],
],
max: 26,
}
const confetti = new ConfettiGenerator(confettiSettings)
confetti.render()
return () => confetti.clear()
}, [])
const { register, handleSubmit, formState } = useForm<formValues>({
const {
register,
handleSubmit,
formState: { errors },
setError,
} = useForm<FormValues>({
defaultValues: {
first_name: "",
last_name: "",
@@ -71,19 +48,27 @@ const InvitePage = () => {
},
})
const accept = useAdminAcceptInvite()
const { mutate, isLoading } = useAdminAcceptInvite()
const navigate = useNavigate()
const notification = useNotification()
const handleAcceptInvite = (data: formValues) => {
setPasswordMismatch(false)
const handleAcceptInvite = handleSubmit((data: FormValues) => {
if (data.password !== data.repeat_password) {
setPasswordMismatch(true)
setError(
"repeat_password",
{
type: "manual",
message: "Passwords do not match",
},
{
shouldFocus: true,
}
)
return
}
accept.mutate(
mutate(
{
token: parsed.token as string,
user: {
@@ -101,128 +86,114 @@ const InvitePage = () => {
},
}
)
})
if (!token) {
return (
<PublicLayout>
<SEO title="Create Account" />
<div className="gap-y-xsmall flex flex-col items-center">
<h1 className="inter-xlarge-semibold mb- text-[20px]">
Invalid invite
</h1>
<p className="inter-base-regular text-grey-50 w-[280px] text-center">
The invite link you have used is invalid. Please contact your
administrator.
</p>
<p className="inter-small-regular text-grey-40 mt-xlarge">
Already have an account? <a href="/login">Log in</a>
</p>
</div>
</PublicLayout>
)
}
useEffect(() => {
if (
formState.dirtyFields.password &&
formState.dirtyFields.repeat_password &&
formState.dirtyFields.first_name &&
formState.dirtyFields.last_name
) {
setReady(true)
} else {
setReady(false)
}
}, [formState])
return (
<>
<PublicLayout>
<SEO title="Create Account" />
{signUp ? (
<LoginLayout>
<SEO title="Create Account" />
<div className="flex h-full w-full items-center justify-center">
<div className="bg-grey-0 rounded-rounded flex min-h-[600px] justify-center">
<form
className="flex w-full flex-col items-center py-12 px-[120px]"
onSubmit={handleSubmit(handleAcceptInvite)}
>
<MedusaIcon />
{!token ? (
<div className="flex h-full flex-col items-center justify-center gap-y-2 text-center">
<span className="inter-large-semibold text-grey-90">
You signup link is invalid
</span>
<span className="inter-base-regular text-grey-50 mt-2">
Contact your administrator to obtain a valid signup link
</span>
</div>
) : (
<>
<span className="inter-2xlarge-semibold text-grey-90 mt-4">
Welcome to the team!
</span>
<span className="inter-base-regular text-grey-50 mb-large mt-2">
Create your account below👇🏼
</span>
<SigninInput
placeholder="First name"
{...register("first_name", { required: true })}
autoComplete="given-name"
/>
<SigninInput
placeholder="Last name"
{...register("last_name", { required: true })}
autoComplete="family-name"
/>
<SigninInput
placeholder="Password"
type={"password"}
{...register("password", { required: true })}
autoComplete="new-password"
/>
<SigninInput
placeholder="Repeat password"
type={"password"}
{...register("repeat_password", { required: true })}
autoComplete="new-password"
/>
{passwordMismatch && (
<span className="inter-small-regular mt-2 w-full text-rose-50">
The two passwords are not the same
</span>
)}
<Button
variant="primary"
size="large"
type="submit"
className="mt-base w-full"
loading={formState.isSubmitting}
disabled={!ready}
>
Create account
</Button>
<Link
to="/login"
className="inter-small-regular text-grey-50 mt-large"
>
Already signed up? Log in
</Link>
</>
)}
</form>
<form onSubmit={handleAcceptInvite}>
<div className="flex flex-col items-center">
<h1 className="inter-xlarge-semibold mb-large text-[20px]">
Create your Medusa account
</h1>
<div className="gap-y-small flex flex-col">
<div>
<SigninInput
placeholder="First name"
{...register("first_name", {
required: FormValidator.required("First name"),
})}
autoComplete="given-name"
/>
<InputError errors={errors} name="first_name" />
</div>
<div>
<SigninInput
placeholder="Last name"
{...register("last_name", {
required: FormValidator.required("Last name"),
})}
autoComplete="family-name"
/>
<InputError errors={errors} name="last_name" />
</div>
<div>
<SigninInput
placeholder="Password"
type={"password"}
{...register("password", {
required: FormValidator.required("Password"),
})}
autoComplete="new-password"
/>
</div>
<div>
<SigninInput
placeholder="Confirm password"
type={"password"}
{...register("repeat_password", {
required: "You must confirm your password",
})}
autoComplete="new-password"
/>
<InputError errors={errors} name="repeat_password" />
</div>
</div>
<Button
variant="secondary"
size="medium"
className="mt-large w-[280px]"
loading={isLoading}
>
Create account
</Button>
<p className="inter-small-regular text-grey-50 mt-xlarge">
Already signed up? <a href="/login">Log in</a>
</p>
</div>
</LoginLayout>
</form>
) : (
<div className="bg-grey-90 h-screen w-full overflow-hidden">
<div className="absolute inset-0 z-10 mx-auto flex h-full max-w-[1080px] flex-grow flex-col items-center justify-center">
<MedusaVice className="mb-3xlarge" />
<div className="flex max-w-3xl flex-col items-center text-center">
<h1 className="inter-3xlarge-semibold text-grey-0 mb-base">
You have been invited to join the team
</h1>
<p className="inter-xlarge-regular text-grey-50">
You can now join the Medusa Store team. Sign up below and get
started with your Medusa Admin account right away.
</p>
</div>
<div className="mt-4xlarge">
<Button
size="large"
variant="primary"
className="w-[280px]"
onClick={() => setSignUp(true)}
>
Sign up
<LongArrowRightIcon size={20} className="pt-1" />
</Button>
</div>
</div>
<canvas id="confetti-canvas" />
<div className="flex flex-col items-center text-center">
<h1 className="inter-xlarge-semibold text-[20px]">
You have been invited to join the team
</h1>
<p className="inter-base-regular text-grey-50 mt-xsmall">
You can now join the team. Sign up below and get started
<br />
with your Medusa account right away.
</p>
<Button
variant="secondary"
size="medium"
className="mt-xlarge w-[280px]"
onClick={() => setSignUp(true)}
>
Sign up
</Button>
</div>
)}
</>
</PublicLayout>
)
}
+38 -25
View File
@@ -1,37 +1,50 @@
import clsx from "clsx"
import { useState } from "react"
import MedusaIcon from "../components/fundamentals/icons/medusa-icon"
import { useAdminGetSession } from "medusa-react"
import { useEffect, useState } from "react"
import { useNavigate } from "react-router-dom"
import LoginCard from "../components/organisms/login-card"
import ResetTokenCard from "../components/organisms/reset-token-card"
import SEO from "../components/seo"
import LoginLayout from "../components/templates/login-layout"
import PublicLayout from "../components/templates/login-layout"
const LoginPage = () => {
const [resetPassword, setResetPassword] = useState(false)
const { user } = useAdminGetSession()
const navigate = useNavigate()
// Redirect to dashboard if user is logged in
useEffect(() => {
if (user) {
navigate("/")
}
}, [user, navigate])
useEffect(() => {
if (window.location.search.includes("reset-password")) {
setResetPassword(true)
}
}, [])
const showLogin = () => {
setResetPassword(false)
navigate("/login", { replace: true })
}
const showResetPassword = () => {
setResetPassword(true)
}
return (
<LoginLayout>
<PublicLayout>
<SEO title="Login" />
<div className="flex h-full w-full items-center justify-center">
<div
className={clsx(
"bg-grey-0 rounded-rounded flex min-h-[600px] w-[640px] justify-center transition-['min-height'] duration-300",
{
"min-h-[480px]": resetPassword,
}
)}
>
<div className="flex w-full flex-col items-center px-[120px] pt-12">
<MedusaIcon />
{resetPassword ? (
<ResetTokenCard goBack={() => setResetPassword(false)} />
) : (
<LoginCard toResetPassword={() => setResetPassword(true)} />
)}
</div>
</div>
</div>
</LoginLayout>
{resetPassword ? (
<ResetTokenCard goBack={showLogin} />
) : (
<LoginCard toResetPassword={showResetPassword} />
)}
</PublicLayout>
)
}
@@ -1,15 +1,16 @@
import { useAdminResetPassword } from "medusa-react"
import qs from "qs"
import { useEffect, useState } from "react"
import { useForm } from "react-hook-form"
import { decodeToken } from "react-jwt"
import { useLocation, useNavigate } from "react-router-dom"
import InputError from "../components/atoms/input-error"
import Button from "../components/fundamentals/button"
import MedusaIcon from "../components/fundamentals/icons/medusa-icon"
import SigninInput from "../components/molecules/input-signin"
import SEO from "../components/seo"
import LoginLayout from "../components/templates/login-layout"
import PublicLayout from "../components/templates/login-layout"
import useNotification from "../hooks/use-notification"
import { getErrorMessage } from "../utils/error-messages"
import FormValidator from "../utils/form-validator"
type formValues = {
password: string
@@ -30,25 +31,34 @@ const ResetPasswordPage = () => {
}
}
const [passwordMismatch, setPasswordMismatch] = useState(false)
const [error, setError] = useState<string | null>(null)
const [ready, setReady] = useState(false)
const email = (token?.email || parsed?.email || "") as string
const { register, handleSubmit, formState } = useForm<formValues>({
const {
register,
handleSubmit,
formState: { errors },
setError,
} = useForm<formValues>({
defaultValues: {
password: "",
repeat_password: "",
},
})
const reset = useAdminResetPassword()
const notification = useNotification()
const handleAcceptInvite = (data: formValues) => {
setPasswordMismatch(false)
setError(null)
const onSubmit = handleSubmit((data: formValues) => {
if (data.password !== data.repeat_password) {
setPasswordMismatch(true)
setError(
"repeat_password",
{
type: "manual",
message: "Passwords do not match",
},
{
shouldFocus: true,
}
)
return
}
@@ -63,95 +73,78 @@ const ResetPasswordPage = () => {
navigate("/login")
},
onError: (err) => {
setError(getErrorMessage(err))
notification("Error", getErrorMessage(err), "error")
},
}
)
}
useEffect(() => {
if (
formState.dirtyFields.password &&
formState.dirtyFields.repeat_password
) {
setReady(true)
} else {
setReady(false)
}
}, [formState])
})
return (
<LoginLayout>
<PublicLayout>
<SEO title="Reset Password" />
<div className="flex h-full w-full items-center justify-center">
<div className="bg-grey-0 rounded-rounded flex min-h-[540px] justify-center">
<form
className="flex w-full flex-col items-center py-12 px-[120px]"
onSubmit={handleSubmit(handleAcceptInvite)}
>
<MedusaIcon />
{!token ? (
<div className="flex h-full flex-col items-center justify-center gap-y-2 text-center">
<span className="inter-large-semibold text-grey-90">
You reset link is invalid
</span>
<span className="inter-base-regular text-grey-50 mt-2">
Please try resetting your password again
</span>
</div>
) : (
<>
<span className="inter-2xlarge-semibold text-grey-90 mt-4">
Reset your password
</span>
<span className="inter-base-regular text-grey-50 mb-xlarge mt-2">
Choose a new password below 👇🏼
</span>
<div className="flex flex-col items-center justify-center">
{!token ? (
<form onSubmit={onSubmit}>
<div className="gap-y-large flex flex-col items-center">
<h1 className="inter-xlarge-semibold">Reset your password</h1>
<div className="gap-y-small flex flex-col items-center">
<SigninInput
placeholder="Email"
name="first_name"
value={email}
disabled
readOnly
value={email}
/>
<SigninInput
placeholder="Password"
type={"password"}
{...register("password", { required: true })}
autoComplete="new-password"
/>
<SigninInput
placeholder="Confirm password"
type={"password"}
{...register("repeat_password", { required: true })}
autoComplete="new-password"
className="mb-0"
/>
{error && (
<span className="mt-xsmall inter-small-regular w-full text-rose-50">
The two passwords are not the same
</span>
)}
{passwordMismatch && (
<span className="mt-xsmall inter-small-regular w-full text-rose-50">
The two passwords are not the same
</span>
)}
<Button
variant="primary"
size="large"
type="submit"
className="mt-base rounded-rounded w-full"
loading={formState.isSubmitting}
disabled={!ready}
>
Reset Password
</Button>
</>
)}
<div>
<SigninInput
placeholder="Password (8+ characters)"
type="password"
{...register("password", {
required: FormValidator.required("Password"),
})}
/>
<InputError errors={errors} name="password" />
</div>
<div>
<SigninInput
placeholder="Confirm password"
type="password"
{...register("repeat_password", {
required: "You must confirm your password",
})}
/>
<InputError errors={errors} name="repeat_password" />
</div>
</div>
<Button variant="secondary" size="medium" className="w-[280px]">
Reset password
</Button>
<a
href="/login"
className="inter-small-regular text-grey-40 mt-xsmall"
>
Go back to sign in
</a>
</div>
</form>
</div>
) : (
<div className="gap-y-large flex flex-col items-center">
<div className="gap-y-xsmall flex flex-col text-center">
<h1 className="inter-xlarge-semibold text-[20px]">
Your reset link is invalid
</h1>
<p className="text-grey-50 inter-base-regular">
Try resetting your password again.
</p>
</div>
<a href="/login?reset-password=true">
<Button variant="secondary" size="medium" className="w-[280px]">
Go to reset password
</Button>
</a>
</div>
)}
</div>
</LoginLayout>
</PublicLayout>
)
}