From 9ad15d3a8852f5dd392f98a5c3a69d7539f794c7 Mon Sep 17 00:00:00 2001 From: Kasper Fabricius Kristensen <45367945+kasperkristensen@users.noreply.github.com> Date: Fri, 17 Mar 2023 10:17:47 +0100 Subject: [PATCH] feat(admin-ui): Implements redesign of public pages (#3504) * redesign public pages * rm build files * fix size of button --- .changeset/pretty-clouds-fix.md | 5 + packages/admin-ui/package.json | 1 - .../fundamentals/icons/medusa-icon/index.tsx | 8 +- .../molecules/input-signin/index.tsx | 15 +- .../components/organisms/login-card/index.tsx | 82 ++--- .../organisms/reset-token-card/index.tsx | 106 ++++--- .../src/components/templates/login-layout.tsx | 77 +++-- packages/admin-ui/ui/src/pages/invite.tsx | 279 ++++++++---------- packages/admin-ui/ui/src/pages/login.tsx | 63 ++-- .../admin-ui/ui/src/pages/reset-password.tsx | 171 +++++------ packages/medusa-core-utils/package.json | 1 - .../medusa-js/src/resources/admin/variants.ts | 7 +- yarn.lock | 8 - 13 files changed, 418 insertions(+), 405 deletions(-) create mode 100644 .changeset/pretty-clouds-fix.md diff --git a/.changeset/pretty-clouds-fix.md b/.changeset/pretty-clouds-fix.md new file mode 100644 index 0000000000..99921157f9 --- /dev/null +++ b/.changeset/pretty-clouds-fix.md @@ -0,0 +1,5 @@ +--- +"@medusajs/admin-ui": patch +--- + +feat(admin-ui): Implements redesigned public facing pages of admin UI. diff --git a/packages/admin-ui/package.json b/packages/admin-ui/package.json index 835f85614c..a4e1b4a3e5 100644 --- a/packages/admin-ui/package.json +++ b/packages/admin-ui/package.json @@ -46,7 +46,6 @@ "@vitejs/plugin-react": "^3.1.0", "autoprefixer": "^10.4.13", "clsx": "^1.2.1", - "confetti-js": "^0.0.18", "copy-to-clipboard": "^3.3.1", "emoji-picker-react": "^4.4.3", "framer-motion": "^9.1.6", diff --git a/packages/admin-ui/ui/src/components/fundamentals/icons/medusa-icon/index.tsx b/packages/admin-ui/ui/src/components/fundamentals/icons/medusa-icon/index.tsx index aede6fea44..32b60582f5 100644 --- a/packages/admin-ui/ui/src/components/fundamentals/icons/medusa-icon/index.tsx +++ b/packages/admin-ui/ui/src/components/fundamentals/icons/medusa-icon/index.tsx @@ -1,7 +1,11 @@ import React from "react" import IconProps from "../types/icon-type" -const MedusaIcon: React.FC = ({ size = "48", ...attributes }) => { +const MedusaIcon: React.FC = ({ + size = "48", + color = "#8B5CF6", + ...attributes +}) => { const width = +size * 0.9375 // width relative to height (from size prop) return ( = ({ size = "48", ...attributes }) => { > ) diff --git a/packages/admin-ui/ui/src/components/molecules/input-signin/index.tsx b/packages/admin-ui/ui/src/components/molecules/input-signin/index.tsx index 0290b9234f..bfad9be7b2 100644 --- a/packages/admin-ui/ui/src/components/molecules/input-signin/index.tsx +++ b/packages/admin-ui/ui/src/components/molecules/input-signin/index.tsx @@ -24,7 +24,6 @@ const SigninInput = React.forwardRef( placeholder, name, key, - required, onChange, onFocus, className, @@ -52,20 +51,17 @@ const SigninInput = React.forwardRef( return (
- {props.readOnly && ( - - )} setShowPassword(!showPassword)} className="text-grey-40 focus:text-violet-60 px-4 focus:outline-none" > - {showPassword ? : } + {showPassword ? : } )} + {props.readOnly && ( + + )}
) } ) +SigninInput.displayName = "SigninInput" + export default SigninInput diff --git a/packages/admin-ui/ui/src/components/organisms/login-card/index.tsx b/packages/admin-ui/ui/src/components/organisms/login-card/index.tsx index 28bc559a37..2c7b852781 100644 --- a/packages/admin-ui/ui/src/components/organisms/login-card/index.tsx +++ b/packages/admin-ui/ui/src/components/organisms/login-card/index.tsx @@ -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 = ({ toResetPassword }) => { - const [isInvalidLogin, setIsInvalidLogin] = useState(false) - const { register, handleSubmit, reset } = useForm() +const LoginCard = ({ toResetPassword }: LoginCardProps) => { + const { + register, + handleSubmit, + setError, + formState: { errors }, + } = useForm() 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 (
- - Welcome back! - - - It's great to see you ๐Ÿ‘‹๐Ÿผ - - - Log in to your account below - - - - {isInvalidLogin && ( - - These credentials do not match our records - - )} +

+ Log in to Medusa +

+
+ + + +
@@ -72,7 +78,7 @@ const LoginCard: React.FC = ({ toResetPassword }) => { className="inter-small-regular text-grey-50 mt-8 cursor-pointer" onClick={toResetPassword} > - Reset password + Forgot your password?
diff --git a/packages/admin-ui/ui/src/components/organisms/reset-token-card/index.tsx b/packages/admin-ui/ui/src/components/organisms/reset-token-card/index.tsx index de42662288..2f9a747959 100644 --- a/packages/admin-ui/ui/src/components/organisms/reset-token-card/index.tsx +++ b/packages/admin-ui/ui/src/components/organisms/reset-token-card/index.tsx @@ -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 = ({ goBack }) => { - const [unrecognizedEmail, setUnrecognizedEmail] = useState(false) - const [invalidEmail, setInvalidEmail] = useState(false) const [mailSent, setSentMail] = useState(false) - const { register, handleSubmit } = useForm() + const { + register, + handleSubmit, + formState: { errors }, + } = useForm() - 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 = ({ goBack }) => { onSuccess: () => { setSentMail(true) }, - onError: () => { - setUnrecognizedEmail(true) + onError: (error) => { + notification("Error", getErrorMessage(error), "error") }, } ) - } + }) return ( -
+
- +

Reset your password - - - Enter your email address below, and we'll send you +

+ + Enter your email address below, and we'll
- instructions on how to reset your password. + send you instructions on how to reset +
+ your password.
{!mailSent ? ( <> - - {unrecognizedEmail && ( -
- - We can't find a user with that email address - -
- )} - {invalidEmail && ( -
- - Not a valid email address - -
- )} -
+ + ) : ( -
+
- +
- + Succesfully sent you an email - - 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. -
)} diff --git a/packages/admin-ui/ui/src/components/templates/login-layout.tsx b/packages/admin-ui/ui/src/components/templates/login-layout.tsx index a68e0878f3..acc2bb4024 100644 --- a/packages/admin-ui/ui/src/components/templates/login-layout.tsx +++ b/packages/admin-ui/ui/src/components/templates/login-layout.tsx @@ -1,27 +1,62 @@ -const LoginLayout = ({ children }) => { +import { PropsWithChildren } from "react" +import { Toaster } from "react-hot-toast" + +const PublicLayout = ({ children }: PropsWithChildren) => { return ( -
-
-
- {children} -
- ยฉ Medusa Commerce ·{" "} - - Contact - -
-
+
+ +
+
+ {children}
) } -export default LoginLayout +const Logo = () => { + return ( +
+ +
+ ) +} + +const SVG = () => { + return ( + + + + + + + + + + ) +} + +export default PublicLayout diff --git a/packages/admin-ui/ui/src/pages/invite.tsx b/packages/admin-ui/ui/src/pages/invite.tsx index 66dc67574b..2a9cee062f 100644 --- a/packages/admin-ui/ui/src/pages/invite.tsx +++ b/packages/admin-ui/ui/src/pages/invite.tsx @@ -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({ + const { + register, + handleSubmit, + formState: { errors }, + setError, + } = useForm({ 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 ( + + +
+

+ Invalid invite +

+

+ The invite link you have used is invalid. Please contact your + administrator. +

+

+ Already have an account? Log in +

+
+
+ ) } - useEffect(() => { - if ( - formState.dirtyFields.password && - formState.dirtyFields.repeat_password && - formState.dirtyFields.first_name && - formState.dirtyFields.last_name - ) { - setReady(true) - } else { - setReady(false) - } - }, [formState]) - return ( - <> + + {signUp ? ( - - -
-
- - - {!token ? ( -
- - You signup link is invalid - - - Contact your administrator to obtain a valid signup link - -
- ) : ( - <> - - Welcome to the team! - - - Create your account below๐Ÿ‘‡๐Ÿผ - - - - - - {passwordMismatch && ( - - The two passwords are not the same - - )} - - - Already signed up? Log in - - - )} - +
+
+

+ Create your Medusa account +

+
+
+ + +
+
+ + +
+
+ +
+
+ + +
+ +

+ Already signed up? Log in +

- +
) : ( -
-
- -
-

- You have been invited to join the team -

-

- You can now join the Medusa Store team. Sign up below and get - started with your Medusa Admin account right away. -

-
-
- -
-
- +
+

+ You have been invited to join the team +

+

+ You can now join the team. Sign up below and get started +
+ with your Medusa account right away. +

+
)} - + ) } diff --git a/packages/admin-ui/ui/src/pages/login.tsx b/packages/admin-ui/ui/src/pages/login.tsx index 38a9bd4c79..6e2815120f 100644 --- a/packages/admin-ui/ui/src/pages/login.tsx +++ b/packages/admin-ui/ui/src/pages/login.tsx @@ -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 ( - + -
-
-
- - {resetPassword ? ( - setResetPassword(false)} /> - ) : ( - setResetPassword(true)} /> - )} -
-
-
-
+ + {resetPassword ? ( + + ) : ( + + )} + ) } diff --git a/packages/admin-ui/ui/src/pages/reset-password.tsx b/packages/admin-ui/ui/src/pages/reset-password.tsx index 26cfbba764..bb1b81ff2a 100644 --- a/packages/admin-ui/ui/src/pages/reset-password.tsx +++ b/packages/admin-ui/ui/src/pages/reset-password.tsx @@ -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(null) - const [ready, setReady] = useState(false) const email = (token?.email || parsed?.email || "") as string - const { register, handleSubmit, formState } = useForm({ + const { + register, + handleSubmit, + formState: { errors }, + setError, + } = useForm({ 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 ( - + -
-
-
- - {!token ? ( -
- - You reset link is invalid - - - Please try resetting your password again - -
- ) : ( - <> - - Reset your password - - - Choose a new password below ๐Ÿ‘‡๐Ÿผ - +
+ {!token ? ( + +
+

Reset your password

+
- - - {error && ( - - The two passwords are not the same - - )} - {passwordMismatch && ( - - The two passwords are not the same - - )} - - - )} +
+ + +
+
+ + +
+
+ + + Go back to sign in + +
-
+ ) : ( +
+
+

+ Your reset link is invalid +

+

+ Try resetting your password again. +

+
+ + + +
+ )}
- + ) } diff --git a/packages/medusa-core-utils/package.json b/packages/medusa-core-utils/package.json index dc10c81e0a..f14d3dbc6f 100644 --- a/packages/medusa-core-utils/package.json +++ b/packages/medusa-core-utils/package.json @@ -25,6 +25,5 @@ "ts-jest": "^25.5.1", "typescript": "^4.4.4" }, - "dependencies": {}, "gitHead": "a69b1e85be1da3b1b5bc4c5446471252623c8808" } diff --git a/packages/medusa-js/src/resources/admin/variants.ts b/packages/medusa-js/src/resources/admin/variants.ts index 68ec146dd0..694611fa6a 100644 --- a/packages/medusa-js/src/resources/admin/variants.ts +++ b/packages/medusa-js/src/resources/admin/variants.ts @@ -1,9 +1,8 @@ import { - AdminGetVariantsVariantInventoryRes, - AdminGetVariantsParams, - AdminVariantsListRes, - StoreGetVariantsVariantParams, AdminGetVariantParams, + AdminGetVariantsParams, + AdminGetVariantsVariantInventoryRes, + AdminVariantsListRes, AdminVariantsRes, } from "@medusajs/medusa" import qs from "qs" diff --git a/yarn.lock b/yarn.lock index fd9f733b90..6a62685a60 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5639,7 +5639,6 @@ __metadata: "@vitejs/plugin-react": ^3.1.0 autoprefixer: ^10.4.13 clsx: ^1.2.1 - confetti-js: ^0.0.18 copy-to-clipboard: ^3.3.1 emoji-picker-react: ^4.4.3 framer-motion: ^9.1.6 @@ -16209,13 +16208,6 @@ __metadata: languageName: node linkType: hard -"confetti-js@npm:^0.0.18": - version: 0.0.18 - resolution: "confetti-js@npm:0.0.18" - checksum: 06dd454eab703a72e0189a2923bee22dc54adfff73fb99075e6c3202f18faf40aef0d081c9993b7acc640949d80306d93b433d228a9683be93f2790427304cef - languageName: node - linkType: hard - "configstore@npm:5.0.1, configstore@npm:^5.0.1": version: 5.0.1 resolution: "configstore@npm:5.0.1"