diff --git a/.changeset/itchy-goats-juggle.md b/.changeset/itchy-goats-juggle.md new file mode 100644 index 0000000000000..07e96e930c7b7 --- /dev/null +++ b/.changeset/itchy-goats-juggle.md @@ -0,0 +1,5 @@ +--- +"@medusajs/ui": minor +--- + +feat(ui): Add Alert component. diff --git a/packages/admin-next/dashboard/package.json b/packages/admin-next/dashboard/package.json index db37624a6a5f2..74fa8052d7a5f 100644 --- a/packages/admin-next/dashboard/package.json +++ b/packages/admin-next/dashboard/package.json @@ -28,6 +28,7 @@ "@uiw/react-json-view": "2.0.0-alpha.10", "cmdk": "^0.2.0", "date-fns": "^3.2.0", + "framer-motion": "^11.0.3", "i18next": "23.7.11", "i18next-browser-languagedetector": "7.2.0", "i18next-http-backend": "2.4.2", @@ -36,6 +37,7 @@ "react-dom": "18.2.0", "react-hook-form": "7.49.1", "react-i18next": "13.5.0", + "react-jwt": "^1.2.0", "react-router-dom": "6.20.1", "zod": "3.22.4" }, @@ -48,8 +50,8 @@ "@types/react": "18.2.43", "@types/react-dom": "18.2.17", "@vitejs/plugin-react": "4.2.1", - "autoprefixer": "10.4.16", - "postcss": "8.4.32", + "autoprefixer": "^10.4.17", + "postcss": "^8.4.33", "prettier": "^3.1.1", "tailwindcss": "^3.4.1", "typescript": "5.2.2", diff --git a/packages/admin-next/dashboard/public/locales/en/translation.json b/packages/admin-next/dashboard/public/locales/en/translation.json index 713a2c3355155..b84fd893ee27b 100644 --- a/packages/admin-next/dashboard/public/locales/en/translation.json +++ b/packages/admin-next/dashboard/public/locales/en/translation.json @@ -239,6 +239,43 @@ "createdBy": "Created by", "revokedBy": "Revoked by" }, + "login": { + "forgotPassword": "Forgot password? - <0>Reset", + "title": "Log in", + "hint": "to continue to Medusa" + }, + "invite": { + "title": "Create your account", + "hint": "to continue to Medusa", + "createAccount": "Create account", + "alreadyHaveAccount": "Already have an account? - <0>Log in", + "emailTooltip": "Your email cannot be changed. If you would like to use another email, a new invite must be sent.", + "invalidInvite": "The invite is invalid or has expired.", + "successTitle": "Your account has been created", + "successHint": "Get started with Medusa Admin right away.", + "successAction": "Start using Medusa", + "invalidTokenTitle": "Your invite token is invalid", + "invalidTokenHint": "Try requesting a new invite link." + }, + "resetPassword": { + "title": "Reset password", + "hint": "Enter your email below, and we will send you instructions on how to reset your password.", + "email": "Email", + "sendResetInstructions": "Send reset instructions", + "backToLogin": "You can always go back - <0>Log in", + "newPasswordHint": "Choose a new password below.", + "invalidTokenTitle": "Your reset token is invalid", + "invalidTokenHint": "Try requesting a new reset link.", + "expiredTokenTitle": "Your reset token has expired", + "goToResetPassword": "Go to Reset Password", + "resetPassword": "Reset password", + "tokenExpiresIn": "Token expires in <0>{{time}} minutes", + "successfulRequest": "We have sent you an email with instructions on how to reset your password. If you don't receive an email, please check your spam folder or try again." + }, + "errors": { + "serverError": "Server error - Try again later.", + "invalidCredentials": "Wrong email or password" + }, "fields": { "name": "Name", "lastName": "Last Name", @@ -247,7 +284,10 @@ "description": "Description", "email": "Email", "password": "Password", + "repeatPassword": "Repeat Password", "confirmPassword": "Confirm Password", + "newPassword": "New Password", + "repeatNewPassword": "Repeat New Password", "categories": "Categories", "category": "Category", "collection": "Collection", diff --git a/packages/admin-next/dashboard/src/components/common/form/form.tsx b/packages/admin-next/dashboard/src/components/common/form/form.tsx index d6c1d5201f10f..945bbd9b544b3 100644 --- a/packages/admin-next/dashboard/src/components/common/form/form.tsx +++ b/packages/admin-next/dashboard/src/components/common/form/form.tsx @@ -177,7 +177,7 @@ const ErrorMessage = forwardRef< const { error, formErrorMessageId } = useFormField() const msg = error ? String(error?.message) : children - if (!msg) { + if (!msg || msg === "undefined") { return null } diff --git a/packages/admin-next/dashboard/src/components/common/logo-box/index.ts b/packages/admin-next/dashboard/src/components/common/logo-box/index.ts new file mode 100644 index 0000000000000..ee20f53faab37 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/logo-box/index.ts @@ -0,0 +1 @@ +export * from "./logo-box" diff --git a/packages/admin-next/dashboard/src/components/common/logo-box/logo-box.tsx b/packages/admin-next/dashboard/src/components/common/logo-box/logo-box.tsx new file mode 100644 index 0000000000000..a8d417c008722 --- /dev/null +++ b/packages/admin-next/dashboard/src/components/common/logo-box/logo-box.tsx @@ -0,0 +1,74 @@ +import { clx } from "@medusajs/ui" +import { Transition, motion } from "framer-motion" + +type LogoBoxProps = { + className?: string + checked?: boolean + containerTransition?: Transition + pathTransition?: Transition +} + +export const LogoBox = ({ + className, + checked, + containerTransition = { + duration: 0.8, + delay: 0.5, + ease: [0, 0.71, 0.2, 1.01], + }, + pathTransition = { + duration: 0.8, + delay: 0.6, + ease: [0.1, 0.8, 0.2, 1.01], + }, +}: LogoBoxProps) => { + return ( +
+ {checked && ( + + + + + + )} + + + +
+ ) +} diff --git a/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx b/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx index 7150224afb904..a0bcf40fbcd78 100644 --- a/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx +++ b/packages/admin-next/dashboard/src/components/layout/shell/shell.tsx @@ -24,6 +24,7 @@ import { import { Skeleton } from "../../common/skeleton" +import { queryClient } from "../../../lib/medusa" import { useSearch } from "../../../providers/search-provider" import { useSidebar } from "../../../providers/sidebar-provider" import { useTheme } from "../../../providers/theme-provider" @@ -35,7 +36,7 @@ export const Shell = ({ children }: PropsWithChildren) => { {children} {children} -
+
@@ -73,7 +74,7 @@ const Breadcrumbs = () => { }) return ( -
    +
      {crumbs.map((crumb, index) => { const isLast = index === crumbs.length - 1 const isSingle = crumbs.length === 1 @@ -106,7 +107,7 @@ const Breadcrumbs = () => {
)} {/* {!isLast && } */} - {!isLast && } + {!isLast && } ) })} @@ -115,18 +116,22 @@ const Breadcrumbs = () => { } const UserBadge = () => { - const { user, isError, error } = useAdminGetSession() + const { user, isLoading, isError, error } = useAdminGetSession() - const displayName = user - ? user.first_name && user.last_name - ? `${user.first_name} ${user.last_name}` - : user.first_name - ? user.first_name - : user.email - : null + const name = [user?.first_name, user?.last_name].filter(Boolean).join(" ") + const displayName = name || user?.email const fallback = displayName ? displayName[0].toUpperCase() : null + if (isLoading) { + return ( + + ) + } + if (isError) { throw error } @@ -136,13 +141,13 @@ const UserBadge = () => { @@ -203,6 +208,11 @@ const Logout = () => { const handleLayout = async () => { await logoutMutation(undefined, { onSuccess: () => { + /** + * When the user logs out, we want to clear the query cache + */ + queryClient.clear() + navigate("/login") }, }) @@ -297,7 +307,7 @@ const Searchbar = () => { return ( + + + +
+ ) +} + +const SuccessView = () => { + const { t } = useTranslation() + + return ( +
+
+ {t("invite.successTitle")} + + {t("invite.successHint")} + +
+ +
+ ) +} + +const InviteSchema = z.object({ + invite_id: z.string(), + role: z.string(), + user_email: z.string().email(), + iat: z.number(), +}) + +const validateDecodedInvite = (decoded: any): decoded is DecodedInvite => { + return InviteSchema.safeParse(decoded).success +} diff --git a/packages/admin-next/dashboard/src/routes/login/login.tsx b/packages/admin-next/dashboard/src/routes/login/login.tsx index dadf791ba5e0f..8bb2a4f7c460a 100644 --- a/packages/admin-next/dashboard/src/routes/login/login.tsx +++ b/packages/admin-next/dashboard/src/routes/login/login.tsx @@ -1,11 +1,13 @@ import { zodResolver } from "@hookform/resolvers/zod" import { Button, Heading, Input, Text } from "@medusajs/ui" -import { useAdminGetSession, useAdminLogin } from "medusa-react" +import { useAdminLogin } from "medusa-react" import { useForm } from "react-hook-form" -import { Link, Navigate, useLocation, useNavigate } from "react-router-dom" +import { Trans, useTranslation } from "react-i18next" +import { Link, useLocation, useNavigate } from "react-router-dom" import * as z from "zod" import { Form } from "../../components/common/form" +import { LogoBox } from "../../components/common/logo-box" import { isAxiosError } from "../../lib/is-axios-error" const LoginSchema = z.object({ @@ -14,10 +16,11 @@ const LoginSchema = z.object({ }) export const Login = () => { - const navigate = useNavigate() + const { t } = useTranslation() const location = useLocation() + const navigate = useNavigate() - const from = location.state?.from?.pathname || "/" + const from = location.state?.from?.pathname || "/orders" const form = useForm>({ resolver: zodResolver(LoginSchema), @@ -27,94 +30,112 @@ export const Login = () => { }, }) - const { user, isLoading } = useAdminGetSession() - - const { mutateAsync } = useAdminLogin({ + const { mutateAsync, isLoading } = useAdminLogin({ retry: false, }) - const onSubmit = form.handleSubmit(async ({ email, password }) => { + const handleSubmit = form.handleSubmit(async ({ email, password }) => { await mutateAsync( - { - email, - password, - }, + { email, password }, { onSuccess: () => { navigate(from, { replace: true }) }, - onError: (e) => { - if (isAxiosError(e)) { - if (e.response?.status === 401) { + onError: (error) => { + if (isAxiosError(error)) { + if (error.response?.status === 401) { + form.setError("email", { + type: "manual", + }) + form.setError("password", { type: "manual", - message: "Invalid email or password", + message: t("errors.invalidCredentials"), }) return } } - form.setError("password", { + form.setError("root.serverError", { type: "manual", - message: "Something went wrong", + message: t("errors.serverError"), }) }, } ) }) - if (user && !isLoading) { - return - } - return ( -
-
- Login - Welcome back. Login to get started! -
-
- - ( - - Email - - - - - - )} +
+
+ +
+ {t("login.title")} + + {t("login.hint")} + +
+ + +
+ { + return ( + + {t("fields.email")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.password")} + + + + + + ) + }} + /> +
+ + + +
+ + , + ]} /> - ( - -
- Password - - Forgot password? - -
- - - - -
- )} - /> - - - +
+
) } diff --git a/packages/admin-next/dashboard/src/routes/reset-password/reset-password-request/index.ts b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-request/index.ts new file mode 100644 index 0000000000000..0e4c1fa871bff --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-request/index.ts @@ -0,0 +1 @@ +export { ResetPassword as Component } from "../reset-password-request/reset-password" diff --git a/packages/admin-next/dashboard/src/routes/reset-password/reset-password-request/reset-password.tsx b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-request/reset-password.tsx new file mode 100644 index 0000000000000..8464525356838 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-request/reset-password.tsx @@ -0,0 +1,143 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { Alert, Button, Heading, Input, Text } from "@medusajs/ui" +import { motion } from "framer-motion" +import { useAdminSendResetPasswordToken } from "medusa-react" +import { useState } from "react" +import { useForm } from "react-hook-form" +import { Trans, useTranslation } from "react-i18next" +import { Link, useSearchParams } from "react-router-dom" +import * as z from "zod" + +import { Form } from "../../../components/common/form" +import { LogoBox } from "../../../components/common/logo-box" + +const ResetPasswordSchema = z.object({ + email: z.string().email(), +}) + +export const ResetPassword = () => { + const { t } = useTranslation() + const [success, setSuccess] = useState(false) + const [searchParams] = useSearchParams() + + const defaultValue = searchParams.get("email") || "" + + const form = useForm>({ + resolver: zodResolver(ResetPasswordSchema), + defaultValues: { + email: defaultValue, + }, + }) + + const { mutateAsync } = useAdminSendResetPasswordToken({ + retry: false, + }) + + const handleSubmit = form.handleSubmit(async ({ email }) => { + await mutateAsync( + { email }, + { + onSuccess: () => { + setSuccess(true) + }, + onError: (_error) => { + /** + * This endpoint is not supposed to return any errors. + * If it does, we can assume it's a server error. + */ + setSuccess(false) + form.setError("root", { + type: "manual", + message: t("errors.serverError"), + }) + }, + } + ) + }) + + return ( +
+
+ +
+ {t("resetPassword.title")} + + {t("resetPassword.hint")} + +
+
+ +
+ { + return ( + + {t("fields.email")} + + + + + + ) + }} + /> + {success && ( + + + {t("resetPassword.successfulRequest")} + + + )} + {form.formState.errors.root && ( + + {form.formState.errors.root.message} + + )} +
+ +
+ +
+ + , + ]} + /> + +
+
+ ) +} diff --git a/packages/admin-next/dashboard/src/routes/reset-password/reset-password-token/index.ts b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-token/index.ts new file mode 100644 index 0000000000000..e4166b5636af9 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-token/index.ts @@ -0,0 +1 @@ +export { ResetPasswordToken as Component } from "./reset-password-token" diff --git a/packages/admin-next/dashboard/src/routes/reset-password/reset-password-token/reset-password-token.tsx b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-token/reset-password-token.tsx new file mode 100644 index 0000000000000..3ab2ddfcd0992 --- /dev/null +++ b/packages/admin-next/dashboard/src/routes/reset-password/reset-password-token/reset-password-token.tsx @@ -0,0 +1,258 @@ +import { zodResolver } from "@hookform/resolvers/zod" +import { Button, Heading, Input, Text } from "@medusajs/ui" +import { useAdminResetPassword } from "medusa-react" +import { useForm } from "react-hook-form" +import { Trans, useTranslation } from "react-i18next" +import { decodeToken } from "react-jwt" +import { Link, Navigate, useNavigate, useParams } from "react-router-dom" +import * as z from "zod" + +import { Clock } from "@medusajs/icons" +import { useEffect, useState } from "react" +import { Form } from "../../../components/common/form" +import { LogoBox } from "../../../components/common/logo-box" + +const ResetPasswordTokenSchema = z + .object({ + email: z.string().email(), + new_password: z.string(), + repeat_new_password: z.string(), + }) + .superRefine(({ new_password, repeat_new_password }, ctx) => { + if (new_password !== repeat_new_password) { + ctx.addIssue({ + code: z.ZodIssueCode.custom, + message: "Passwords do not match", + path: ["repeat_new_password"], + }) + } + }) + +export const ResetPasswordToken = () => { + const { t } = useTranslation() + const navigate = useNavigate() + const { token } = useParams() + + let tokenStatus: "valid" | "invalid" | "expired" = "valid" + const decodedToken: { email: string; exp: number } | null = token + ? decodeToken(token) + : null + + if (!decodedToken) { + tokenStatus = "invalid" + } + + const expiresAt = decodedToken ? new Date(decodedToken.exp * 1000) : null + + if (expiresAt && expiresAt < new Date()) { + tokenStatus = "expired" + } + + const redirectLink = `/reset-password${ + decodedToken ? `?email=${decodedToken.email}` : "" + }` + + const form = useForm>({ + resolver: zodResolver(ResetPasswordTokenSchema), + defaultValues: { + email: decodedToken?.email || "", + new_password: "", + repeat_new_password: "", + }, + }) + + const { mutateAsync, isLoading } = useAdminResetPassword({ + retry: false, + }) + + const handleSubmit = form.handleSubmit(async ({ new_password }) => { + await mutateAsync( + { password: new_password, token: token! }, + { + onSuccess: () => { + navigate("/orders") + }, + onError: (_error) => { + form.setError("root", { + type: "manual", + message: t("errors.serverError"), + }) + }, + } + ) + }) + + const [title, hint] = { + valid: [t("resetPassword.title"), t("resetPassword.newPasswordHint")], + invalid: [ + t("resetPassword.invalidTokenTitle"), + t("resetPassword.invalidTokenHint"), + ], + expired: [ + t("resetPassword.expiredTokenTitle"), + t("resetPassword.invalidTokenHint"), + ], + }[tokenStatus] + + return ( +
+
+ +
+ {title} + + {hint} + +
+ {tokenStatus === "valid" ? ( +
+ +
+ { + return ( + + {t("fields.email")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.newPassword")} + + + + + + ) + }} + /> + { + return ( + + {t("fields.repeatNewPassword")} + + + + + + ) + }} + /> + {form.formState.errors.root && ( + {form.formState.errors.root.message} + )} +
+ + {expiresAt && } + + + ) : ( + + )} +
+ + , + ]} + /> + +
+
+ ) +} + +const Countdown = ({ expiresAt }: { expiresAt: Date }) => { + const { t } = useTranslation() + const [timeLeft, setTimeLeft] = useState( + Math.floor((expiresAt.getTime() - Date.now()) / 1000) + ) + + // Total time is 15 minutes + const totalTime = 15 * 60 + + useEffect(() => { + const interval = setInterval(() => { + setTimeLeft(Math.floor((expiresAt.getTime() - Date.now()) / 1000)) + }, 1000) + + return () => clearInterval(interval) + }, [expiresAt]) + + const minutes = Math.floor(timeLeft / 60) + const seconds = timeLeft % 60 + const timespan = `${minutes}:${seconds < 10 ? `0${seconds}` : seconds}` + + const percentageLeft = ((timeLeft / totalTime) * 100).toFixed(2) + + const isExpired = timeLeft <= 0 + + if (isExpired) { + return + } + + return ( +
+
+
+
+
+ + + ]} + /> + +
+
+ ) +} diff --git a/packages/design-system/ui/package.json b/packages/design-system/ui/package.json index 9653eff581901..80df7a3763841 100644 --- a/packages/design-system/ui/package.json +++ b/packages/design-system/ui/package.json @@ -60,12 +60,12 @@ "@types/react-dom": "^18.2.0", "@vitejs/plugin-react": "^4.0.1", "@vitest/coverage-v8": "^0.32.2", - "autoprefixer": "^10.4.14", + "autoprefixer": "^10.4.17", "chromatic": "^6.20.0", "eslint": "^7.32.0", "eslint-plugin-storybook": "^0.6.12", "jsdom": "^22.1.0", - "postcss": "^8.4.24", + "postcss": "^8.4.33", "prop-types": "^15.8.1", "react": "^18.2.0", "react-dom": "^18.2.0", @@ -107,7 +107,7 @@ "prism-react-renderer": "^2.0.6", "react-currency-input-field": "^3.6.11", "react-day-picker": "^8.8.0", - "tailwind-merge": "^1.13.2" + "tailwind-merge": "^2.2.1" }, "peerDependencies": { "react": "^18.0.0", diff --git a/packages/design-system/ui/src/components/alert/alert.stories.tsx b/packages/design-system/ui/src/components/alert/alert.stories.tsx new file mode 100644 index 0000000000000..c8d8f4aec8773 --- /dev/null +++ b/packages/design-system/ui/src/components/alert/alert.stories.tsx @@ -0,0 +1,92 @@ +import type { Meta, StoryObj } from "@storybook/react" +import * as React from "react" + +import { Alert } from "./alert" + +const meta: Meta = { + title: "Components/Alert", + component: Alert, + argTypes: { + variant: { + control: { + type: "select", + options: ["info", "error", "success", "warning"], + }, + }, + dismissible: { + control: { + type: "boolean", + }, + }, + }, + parameters: { + layout: "centered", + }, +} + +export default meta + +type Story = StoryObj + +const Text = + "We have sent you an email with instructions on how to reset your password. If you don't receive an email, please check your spam folder or try again." + +export const Info: Story = { + args: { + variant: "info", + children: Text, + }, + render: (args) => ( +
+ +
+ ), +} + +export const Error: Story = { + args: { + variant: "error", + children: Text, + }, + render: (args) => ( +
+ +
+ ), +} + +export const Success: Story = { + args: { + variant: "success", + children: Text, + }, + render: (args) => ( +
+ +
+ ), +} + +export const Warning: Story = { + args: { + variant: "warning", + children: Text, + }, + render: (args) => ( +
+ +
+ ), +} + +export const Dismissible: Story = { + args: { + dismissible: true, + children: Text, + }, + render: (args) => ( +
+ +
+ ), +} diff --git a/packages/design-system/ui/src/components/alert/alert.tsx b/packages/design-system/ui/src/components/alert/alert.tsx new file mode 100644 index 0000000000000..450a1116820a7 --- /dev/null +++ b/packages/design-system/ui/src/components/alert/alert.tsx @@ -0,0 +1,92 @@ +import { clx } from "@/utils/clx" +import { + CheckCircleSolid, + ExclamationCircleSolid, + InformationCircleSolid, + XCircleSolid, + XMarkMini, +} from "@medusajs/icons" +import * as React from "react" + +import { IconButton } from "@/components/icon-button" + +interface AlertProps extends React.ComponentPropsWithoutRef<"div"> { + variant?: "error" | "success" | "warning" | "info" + dismissible?: boolean +} + +/** + * This component is based on the div element and supports all of its props + * + * @excludeExternal + */ +export const Alert = React.forwardRef( + ( + { + /** + * The variant of the alert + */ + variant = "info", + /** + * Whether the alert is dismissible + */ + dismissible = false, + className, + children, + ...props + }, + ref + ) => { + const [dismissed, setDismissed] = React.useState(false) + + const Icon = { + info: InformationCircleSolid, + error: XCircleSolid, + success: CheckCircleSolid, + warning: ExclamationCircleSolid, + }[variant] + + const handleDismiss = () => { + setDismissed(true) + } + + if (dismissed) { + return null + } + + return ( +
+ +
{children}
+ {dismissible && ( + + + + )} +
+ ) + } +) diff --git a/packages/design-system/ui/src/components/alert/index.ts b/packages/design-system/ui/src/components/alert/index.ts new file mode 100644 index 0000000000000..36e19d970b9cf --- /dev/null +++ b/packages/design-system/ui/src/components/alert/index.ts @@ -0,0 +1 @@ +export * from "./alert" diff --git a/packages/design-system/ui/src/components/hint/hint.tsx b/packages/design-system/ui/src/components/hint/hint.tsx index 24845ccbe08ba..2e5dec584b4d5 100644 --- a/packages/design-system/ui/src/components/hint/hint.tsx +++ b/packages/design-system/ui/src/components/hint/hint.tsx @@ -5,7 +5,7 @@ import * as React from "react" import { clx } from "../../utils/clx" const hintVariants = cva({ - base: "txt-small inline-flex items-center gap-x-2", + base: "txt-small inline-flex items-start gap-x-2", variants: { variant: { info: "text-ui-fg-subtle", diff --git a/packages/design-system/ui/src/components/icon-button/icon-button.tsx b/packages/design-system/ui/src/components/icon-button/icon-button.tsx index 539cf6520fce9..06911d5f46de7 100644 --- a/packages/design-system/ui/src/components/icon-button/icon-button.tsx +++ b/packages/design-system/ui/src/components/icon-button/icon-button.tsx @@ -28,6 +28,8 @@ const iconButtonVariants = cva({ ), }, size: { + "2xsmall": "h-5 w-5", + xsmall: "h-6 w-6 p-1", small: "h-7 w-7 p-1", base: "h-8 w-8 p-1.5", large: "h-10 w-10 p-2.5", diff --git a/packages/design-system/ui/src/index.ts b/packages/design-system/ui/src/index.ts index 4f0102399cb93..d77d2a5b9065b 100644 --- a/packages/design-system/ui/src/index.ts +++ b/packages/design-system/ui/src/index.ts @@ -1,3 +1,4 @@ +export { Alert } from "./components/alert" export { Avatar } from "./components/avatar" export { Badge } from "./components/badge" export { Button } from "./components/button" @@ -21,6 +22,7 @@ export { IconButton } from "./components/icon-button" export { Input } from "./components/input" export { Kbd } from "./components/kbd" export { Label } from "./components/label" +export { Popover } from "./components/popover" export { ProgressAccordion } from "./components/progress-accordion" export { ProgressTabs } from "./components/progress-tabs" export { Prompt } from "./components/prompt" @@ -35,7 +37,6 @@ export { Textarea } from "./components/textarea" export { Toast } from "./components/toast" export { Toaster } from "./components/toaster" export { Tooltip } from "./components/tooltip" -export { Popover } from "./components/popover" // Hooks export { usePrompt } from "./hooks/use-prompt" diff --git a/yarn.lock b/yarn.lock index a0c934b17d200..793475f19b566 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3848,6 +3848,15 @@ __metadata: languageName: node linkType: hard +"@babel/runtime@npm:^7.23.7": + version: 7.23.9 + resolution: "@babel/runtime@npm:7.23.9" + dependencies: + regenerator-runtime: ^0.14.0 + checksum: e71205fdd7082b2656512cc98e647d9ea7e222e4fe5c36e9e5adc026446fcc3ba7b3cdff8b0b694a0b78bb85db83e7b1e3d4c56ef90726682b74f13249cf952d + languageName: node + linkType: hard + "@babel/template@npm:^7.12.13, @babel/template@npm:^7.12.7, @babel/template@npm:^7.16.7, @babel/template@npm:^7.22.5, @babel/template@npm:^7.3.3": version: 7.22.5 resolution: "@babel/template@npm:7.22.5" @@ -8058,19 +8067,21 @@ __metadata: "@types/react-dom": 18.2.17 "@uiw/react-json-view": 2.0.0-alpha.10 "@vitejs/plugin-react": 4.2.1 - autoprefixer: 10.4.16 + autoprefixer: ^10.4.17 cmdk: ^0.2.0 date-fns: ^3.2.0 + framer-motion: ^11.0.3 i18next: 23.7.11 i18next-browser-languagedetector: 7.2.0 i18next-http-backend: 2.4.2 medusa-react: "workspace:^" - postcss: 8.4.32 + postcss: ^8.4.33 prettier: ^3.1.1 react: 18.2.0 react-dom: 18.2.0 react-hook-form: 7.49.1 react-i18next: 13.5.0 + react-jwt: ^1.2.0 react-router-dom: 6.20.1 tailwindcss: ^3.4.1 typescript: 5.2.2 @@ -8708,7 +8719,7 @@ __metadata: "@types/react-dom": ^18.2.0 "@vitejs/plugin-react": ^4.0.1 "@vitest/coverage-v8": ^0.32.2 - autoprefixer: ^10.4.14 + autoprefixer: ^10.4.17 chromatic: ^6.20.0 clsx: ^1.2.1 copy-to-clipboard: ^3.3.3 @@ -8717,7 +8728,7 @@ __metadata: eslint: ^7.32.0 eslint-plugin-storybook: ^0.6.12 jsdom: ^22.1.0 - postcss: ^8.4.24 + postcss: ^8.4.33 prism-react-renderer: ^2.0.6 prop-types: ^15.8.1 react: ^18.2.0 @@ -8727,7 +8738,7 @@ __metadata: resize-observer-polyfill: ^1.5.1 rimraf: ^5.0.1 storybook: ^7.0.23 - tailwind-merge: ^1.13.2 + tailwind-merge: ^2.2.1 tailwindcss: ^3.4.1 tsc-alias: ^1.8.7 typescript: ^5.1.6 @@ -20344,7 +20355,25 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:10.4.16, autoprefixer@npm:^10.4.16": +"autoprefixer@npm:^10.1.0, autoprefixer@npm:^10.4.0, autoprefixer@npm:^10.4.13, autoprefixer@npm:^10.4.14": + version: 10.4.14 + resolution: "autoprefixer@npm:10.4.14" + dependencies: + browserslist: ^4.21.5 + caniuse-lite: ^1.0.30001464 + fraction.js: ^4.2.0 + normalize-range: ^0.1.2 + picocolors: ^1.0.0 + postcss-value-parser: ^4.2.0 + peerDependencies: + postcss: ^8.1.0 + bin: + autoprefixer: bin/autoprefixer + checksum: 66ce961b86acd2a46e05ac1eece8657b3d9edfd2ee3abddd6cfcb32755e6865409f57acf11fe05990d6f166afda85a603678435916267a09652265cfff7b5706 + languageName: node + linkType: hard + +"autoprefixer@npm:^10.4.16": version: 10.4.16 resolution: "autoprefixer@npm:10.4.16" dependencies: @@ -20362,13 +20391,13 @@ __metadata: languageName: node linkType: hard -"autoprefixer@npm:^10.1.0, autoprefixer@npm:^10.4.0, autoprefixer@npm:^10.4.13, autoprefixer@npm:^10.4.14": - version: 10.4.14 - resolution: "autoprefixer@npm:10.4.14" +"autoprefixer@npm:^10.4.17": + version: 10.4.17 + resolution: "autoprefixer@npm:10.4.17" dependencies: - browserslist: ^4.21.5 - caniuse-lite: ^1.0.30001464 - fraction.js: ^4.2.0 + browserslist: ^4.22.2 + caniuse-lite: ^1.0.30001578 + fraction.js: ^4.3.7 normalize-range: ^0.1.2 picocolors: ^1.0.0 postcss-value-parser: ^4.2.0 @@ -20376,7 +20405,7 @@ __metadata: postcss: ^8.1.0 bin: autoprefixer: bin/autoprefixer - checksum: 66ce961b86acd2a46e05ac1eece8657b3d9edfd2ee3abddd6cfcb32755e6865409f57acf11fe05990d6f166afda85a603678435916267a09652265cfff7b5706 + checksum: 1d21cc8edb7bf993682094ceed03a32c18f5293f071182a64c2c6defb44bbe91d576ad775d2347469a81997b80cea0bbc4ad3eeb5b12710f9feacf2e6c04bb51 languageName: node linkType: hard @@ -22292,6 +22321,13 @@ __metadata: languageName: node linkType: hard +"caniuse-lite@npm:^1.0.30001578": + version: 1.0.30001583 + resolution: "caniuse-lite@npm:1.0.30001583" + checksum: 9ce61437e7a0c8e69bdb3480962dca7d7a9e9fa8d7c56ae411454d8b101d7c83d71f6c7fa74450c131f6c14e35e347042b3e1787b15edbfaba0e05fd75ab2625 + languageName: node + linkType: hard + "capital-case@npm:^1.0.4": version: 1.0.4 resolution: "capital-case@npm:1.0.4" @@ -28530,7 +28566,7 @@ __metadata: languageName: node linkType: hard -"fraction.js@npm:^4.3.6": +"fraction.js@npm:^4.3.6, fraction.js@npm:^4.3.7": version: 4.3.7 resolution: "fraction.js@npm:4.3.7" checksum: df291391beea9ab4c263487ffd9d17fed162dbb736982dee1379b2a8cc94e4e24e46ed508c6d278aded9080ba51872f1bc5f3a5fd8d7c74e5f105b508ac28711 @@ -28546,6 +28582,27 @@ __metadata: languageName: node linkType: hard +"framer-motion@npm:^11.0.3": + version: 11.0.3 + resolution: "framer-motion@npm:11.0.3" + dependencies: + "@emotion/is-prop-valid": ^0.8.2 + tslib: ^2.4.0 + peerDependencies: + react: ^18.0.0 + react-dom: ^18.0.0 + dependenciesMeta: + "@emotion/is-prop-valid": + optional: true + peerDependenciesMeta: + react: + optional: true + react-dom: + optional: true + checksum: a9a70196ea5d264f749a7dea1ccb7c33decf969680a907c595eb48d84510b60fb51e3e0d16d5bcac0997eea041f796f2cba4a5505e0a00f373ffbadfb5f36564 + languageName: node + linkType: hard + "framer-motion@npm:^9.1.6": version: 9.1.7 resolution: "framer-motion@npm:9.1.7" @@ -42103,7 +42160,7 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.31, postcss@npm:^8.2.14, postcss@npm:^8.4.23, postcss@npm:^8.4.24, postcss@npm:^8.4.27": +"postcss@npm:8.4.31, postcss@npm:^8.2.14, postcss@npm:^8.4.23, postcss@npm:^8.4.27": version: 8.4.31 resolution: "postcss@npm:8.4.31" dependencies: @@ -42114,17 +42171,6 @@ __metadata: languageName: node linkType: hard -"postcss@npm:8.4.32, postcss@npm:^8.4.32": - version: 8.4.32 - resolution: "postcss@npm:8.4.32" - dependencies: - nanoid: ^3.3.7 - picocolors: ^1.0.0 - source-map-js: ^1.0.2 - checksum: 39308a9195fa34d4dbdd7b58a896cff0c7809f84f7a4ac1b95b68ca86c9138a395addff33075668ed3983d41b90aac05754c445237a9365eb1c3a5602ebd03ad - languageName: node - linkType: hard - "postcss@npm:^7.0.14, postcss@npm:^7.0.26, postcss@npm:^7.0.32, postcss@npm:^7.0.36, postcss@npm:^7.0.5, postcss@npm:^7.0.6": version: 7.0.39 resolution: "postcss@npm:7.0.39" @@ -42146,6 +42192,28 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.4.32": + version: 8.4.32 + resolution: "postcss@npm:8.4.32" + dependencies: + nanoid: ^3.3.7 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 39308a9195fa34d4dbdd7b58a896cff0c7809f84f7a4ac1b95b68ca86c9138a395addff33075668ed3983d41b90aac05754c445237a9365eb1c3a5602ebd03ad + languageName: node + linkType: hard + +"postcss@npm:^8.4.33": + version: 8.4.33 + resolution: "postcss@npm:8.4.33" + dependencies: + nanoid: ^3.3.7 + picocolors: ^1.0.0 + source-map-js: ^1.0.2 + checksum: 16eda83458fcd8a91bece287b5920c7f57164c3ea293e6c80d0ea71ce7843007bcd8592260a5160b9a7f02693e6ac93e2495b02d8c7596d3f3f72c1447e3ba79 + languageName: node + linkType: hard + "postgres-array@npm:~2.0.0": version: 2.0.0 resolution: "postgres-array@npm:2.0.0" @@ -43577,7 +43645,7 @@ __metadata: languageName: node linkType: hard -"react-jwt@npm:^1.1.4": +"react-jwt@npm:^1.1.4, react-jwt@npm:^1.2.0": version: 1.2.0 resolution: "react-jwt@npm:1.2.0" dependencies: @@ -47873,10 +47941,12 @@ __metadata: languageName: node linkType: hard -"tailwind-merge@npm:^1.13.2": - version: 1.13.2 - resolution: "tailwind-merge@npm:1.13.2" - checksum: 0b4bac1c45769c099336ec767fad6e3ed559e9037d3e70bfafd820b2f8139dbaf6dec03e24b1a60ad38dceaa0272573175472ae7498fd745e97792195ea184f6 +"tailwind-merge@npm:^2.2.1": + version: 2.2.1 + resolution: "tailwind-merge@npm:2.2.1" + dependencies: + "@babel/runtime": ^7.23.7 + checksum: 14ab965ec897e9377484b7593f7a700dde09b8035b762ad42652622a3ed1f202b203f48c0f235c0b1b38e9390470d94458f6f9010d33a5a18d71b15f38b986a6 languageName: node linkType: hard