feat(admin-ui): Implements redesign of public pages (#3504)
* redesign public pages * rm build files * fix size of button
This commit is contained in:
@@ -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'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>·</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
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user