feat(dashboard): login and invite redesign (#9214)
**What** - new UI for login and invite pages ---   --- CLOSES CC-131
This commit is contained in:
Binary file not shown.
|
After Width: | Height: | Size: 4.4 KiB |
@@ -0,0 +1,51 @@
|
|||||||
|
import { motion } from "framer-motion"
|
||||||
|
|
||||||
|
import { IconAvatar } from "../icon-avatar"
|
||||||
|
|
||||||
|
export default function AvatarBox({ checked }: { checked?: boolean }) {
|
||||||
|
return (
|
||||||
|
<IconAvatar
|
||||||
|
size="xlarge"
|
||||||
|
className="bg-ui-button-neutral shadow-buttons-neutral after:button-neutral-gradient relative mb-4 flex size-14 items-center justify-center rounded-xl after:inset-0 after:content-['']"
|
||||||
|
>
|
||||||
|
{checked && (
|
||||||
|
<motion.div
|
||||||
|
className="absolute -right-[5px] -top-1 flex size-5 items-center justify-center rounded-full border-[0.5px] border-[rgba(3,7,18,0.2)] bg-[#3B82F6] bg-gradient-to-b from-white/0 to-white/20 shadow-[0px_1px_2px_0px_rgba(3,7,18,0.12),0px_1px_2px_0px_rgba(255,255,255,0.10)_inset,0px_-1px_5px_0px_rgba(255,255,255,0.10)_inset,0px_0px_0px_0px_rgba(3,7,18,0.06)_inset]"
|
||||||
|
initial={{ opacity: 0, scale: 0.5 }}
|
||||||
|
animate={{ opacity: 1, scale: 1 }}
|
||||||
|
transition={{
|
||||||
|
duration: 1.2,
|
||||||
|
delay: 0.8,
|
||||||
|
ease: [0, 0.71, 0.2, 1.01],
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<svg
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="20"
|
||||||
|
height="20"
|
||||||
|
viewBox="0 0 20 20"
|
||||||
|
fill="none"
|
||||||
|
>
|
||||||
|
<motion.path
|
||||||
|
d="M5.8335 10.4167L9.16683 13.75L14.1668 6.25"
|
||||||
|
stroke="white"
|
||||||
|
strokeWidth="1.5"
|
||||||
|
strokeLinecap="round"
|
||||||
|
strokeLinejoin="round"
|
||||||
|
initial={{ pathLength: 0, opacity: 0 }}
|
||||||
|
animate={{ pathLength: 1, opacity: 1 }}
|
||||||
|
transition={{
|
||||||
|
duration: 1.3,
|
||||||
|
delay: 1.1,
|
||||||
|
bounce: 0.6,
|
||||||
|
ease: [0.1, 0.8, 0.2, 1.01],
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</svg>
|
||||||
|
</motion.div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<img width={44} height={44} src="/medusa-avatar.png" />
|
||||||
|
</IconAvatar>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -1 +1,2 @@
|
|||||||
export * from "./logo-box"
|
export * from "./logo-box"
|
||||||
|
export * from "./avatar-box"
|
||||||
|
|||||||
@@ -115,6 +115,7 @@
|
|||||||
"close": "Close",
|
"close": "Close",
|
||||||
"showMore": "Show more",
|
"showMore": "Show more",
|
||||||
"continue": "Continue",
|
"continue": "Continue",
|
||||||
|
"continueWithEmail": "Continue with Email",
|
||||||
"addReason": "Add Reason",
|
"addReason": "Add Reason",
|
||||||
"addNote": "Add Note",
|
"addNote": "Add Note",
|
||||||
"reset": "Reset",
|
"reset": "Reset",
|
||||||
@@ -546,7 +547,7 @@
|
|||||||
"inventory": {
|
"inventory": {
|
||||||
"notManaged": "Not managed",
|
"notManaged": "Not managed",
|
||||||
"manageItems": "Manage inventory items",
|
"manageItems": "Manage inventory items",
|
||||||
"notManagedDesc":"Inventory is not managed for this variant. Turn on ‘Manage Inventory’ to track the variant's inventory.",
|
"notManagedDesc": "Inventory is not managed for this variant. Turn on ‘Manage Inventory’ to track the variant's inventory.",
|
||||||
"manageKit": "Manage inventory kit",
|
"manageKit": "Manage inventory kit",
|
||||||
"navigateToItem": "Go to inventory item",
|
"navigateToItem": "Go to inventory item",
|
||||||
"actions": {
|
"actions": {
|
||||||
@@ -2338,19 +2339,20 @@
|
|||||||
},
|
},
|
||||||
"login": {
|
"login": {
|
||||||
"forgotPassword": "Forgot password? - <0>Reset</0>",
|
"forgotPassword": "Forgot password? - <0>Reset</0>",
|
||||||
"title": "Log in",
|
"title": "Welcome to Medusa",
|
||||||
"hint": "to continue to Medusa"
|
"hint": "Sign in to access the account area"
|
||||||
},
|
},
|
||||||
"invite": {
|
"invite": {
|
||||||
"title": "Create your account",
|
"title": "Welcome to Medusa",
|
||||||
"hint": "to continue to Medusa",
|
"hint": "Create you account below",
|
||||||
|
"backToLogin": "Back to login",
|
||||||
"createAccount": "Create account",
|
"createAccount": "Create account",
|
||||||
"alreadyHaveAccount": "Already have an account? - <0>Log in</0>",
|
"alreadyHaveAccount": "Already have an account? - <0>Log in</0>",
|
||||||
"emailTooltip": "Your email cannot be changed. If you would like to use another email, a new invite must be sent.",
|
"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.",
|
"invalidInvite": "The invite is invalid or has expired.",
|
||||||
"successTitle": "Your account has been created",
|
"successTitle": "Your account has is registered",
|
||||||
"successHint": "Get started with Medusa Admin right away.",
|
"successHint": "Get started with Medusa Admin right away.",
|
||||||
"successAction": "Sign in to start using Medusa",
|
"successAction": "Start Medusa Admin",
|
||||||
"invalidTokenTitle": "Your invite token is invalid",
|
"invalidTokenTitle": "Your invite token is invalid",
|
||||||
"invalidTokenHint": "Try requesting a new invite link.",
|
"invalidTokenHint": "Try requesting a new invite link.",
|
||||||
"passwordMismatch": "Passwords do not match",
|
"passwordMismatch": "Passwords do not match",
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { Alert, Button, Heading, Input, Text, toast } from "@medusajs/ui"
|
import { Alert, Button, Heading, Hint, Input, Text, toast } from "@medusajs/ui"
|
||||||
import { AnimatePresence, motion } from "framer-motion"
|
import { AnimatePresence, motion } from "framer-motion"
|
||||||
import i18n from "i18next"
|
import i18n from "i18next"
|
||||||
import { useState } from "react"
|
import { useState } from "react"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { Trans, useTranslation } from "react-i18next"
|
import { useTranslation } from "react-i18next"
|
||||||
import { decodeToken } from "react-jwt"
|
import { decodeToken } from "react-jwt"
|
||||||
import { Link, useSearchParams } from "react-router-dom"
|
import { Link, useSearchParams } from "react-router-dom"
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
import { Form } from "../../components/common/form"
|
import { Form } from "../../components/common/form"
|
||||||
import { LogoBox } from "../../components/common/logo-box"
|
|
||||||
import { useSignUpWithEmailPass } from "../../hooks/api/auth"
|
import { useSignUpWithEmailPass } from "../../hooks/api/auth"
|
||||||
import { useAcceptInvite } from "../../hooks/api/invites"
|
import { useAcceptInvite } from "../../hooks/api/invites"
|
||||||
import { isFetchError } from "../../lib/is-fetch-error"
|
import { isFetchError } from "../../lib/is-fetch-error"
|
||||||
|
import AvatarBox from "../../components/common/logo-box/avatar-box"
|
||||||
|
|
||||||
const CreateAccountSchema = z
|
const CreateAccountSchema = z
|
||||||
.object({
|
.object({
|
||||||
@@ -50,23 +50,9 @@ export const Invite = () => {
|
|||||||
const isValidInvite = invite && validateDecodedInvite(invite)
|
const isValidInvite = invite && validateDecodedInvite(invite)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-ui-bg-base relative flex min-h-dvh w-dvw items-center justify-center p-4">
|
<div className="bg-ui-bg-subtle relative flex min-h-dvh w-dvw items-center justify-center p-4">
|
||||||
<div className="flex w-full max-w-[300px] flex-col items-center">
|
<div className="flex w-full max-w-[280px] flex-col items-center">
|
||||||
<LogoBox
|
<AvatarBox checked={success} />
|
||||||
className="mb-4"
|
|
||||||
checked={success}
|
|
||||||
containerTransition={{
|
|
||||||
duration: 1.2,
|
|
||||||
delay: 0.8,
|
|
||||||
ease: [0, 0.71, 0.2, 1.01],
|
|
||||||
}}
|
|
||||||
pathTransition={{
|
|
||||||
duration: 1.3,
|
|
||||||
delay: 1.1,
|
|
||||||
bounce: 0.6,
|
|
||||||
ease: [0.1, 0.8, 0.2, 1.01],
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
<div className="max-h-[557px] w-full will-change-contents">
|
<div className="max-h-[557px] w-full will-change-contents">
|
||||||
{isValidInvite ? (
|
{isValidInvite ? (
|
||||||
<AnimatePresence>
|
<AnimatePresence>
|
||||||
@@ -150,19 +136,13 @@ const LoginLink = () => {
|
|||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center">
|
<div className="flex w-full flex-col items-center">
|
||||||
<div className="my-6 h-px w-full border-b border-dotted" />
|
<div className="my-6 h-px w-full border-b border-dotted" />
|
||||||
<span className="text-ui-fg-subtle txt-small">
|
<Link
|
||||||
<Trans
|
key="login-link"
|
||||||
t={t}
|
to="/login"
|
||||||
i18nKey="invite.alreadyHaveAccount"
|
className="text-ui-fg-interactive txt-small !text-ui-fg-base font-medium transition-fg hover:text-ui-fg-interactive-hover focus-visible:text-ui-fg-interactive-hover outline-none"
|
||||||
components={[
|
>
|
||||||
<Link
|
{t("invite.backToLogin")}
|
||||||
key="login-link"
|
</Link>
|
||||||
to="/login"
|
|
||||||
className="text-ui-fg-interactive transition-fg hover:text-ui-fg-interactive-hover focus-visible:text-ui-fg-interactive-hover outline-none"
|
|
||||||
/>,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -250,6 +230,14 @@ const CreateView = ({
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const serverError = form.formState.errors.root?.message
|
||||||
|
const validationError =
|
||||||
|
form.formState.errors.email?.message ||
|
||||||
|
form.formState.errors.password?.message ||
|
||||||
|
form.formState.errors.repeat_password?.message ||
|
||||||
|
form.formState.errors.first_name?.message ||
|
||||||
|
form.formState.errors.last_name?.message
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col items-center">
|
<div className="flex w-full flex-col items-center">
|
||||||
<div className="mb-4 flex flex-col items-center">
|
<div className="mb-4 flex flex-col items-center">
|
||||||
@@ -260,18 +248,21 @@ const CreateView = ({
|
|||||||
</div>
|
</div>
|
||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form onSubmit={handleSubmit} className="flex w-full flex-col gap-y-6">
|
<form onSubmit={handleSubmit} className="flex w-full flex-col gap-y-6">
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-2">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.email")}</Form.Label>
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input autoComplete="off" {...field} />
|
<Input
|
||||||
|
autoComplete="off"
|
||||||
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.email")}
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -282,11 +273,14 @@ const CreateView = ({
|
|||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.firstName")}</Form.Label>
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input autoComplete="given-name" {...field} />
|
<Input
|
||||||
|
autoComplete="given-name"
|
||||||
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.firstName")}
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -297,11 +291,14 @@ const CreateView = ({
|
|||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.lastName")}</Form.Label>
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input autoComplete="family-name" {...field} />
|
<Input
|
||||||
|
autoComplete="family-name"
|
||||||
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.lastName")}
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -312,15 +309,15 @@ const CreateView = ({
|
|||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.password")}</Form.Label>
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input
|
<Input
|
||||||
autoComplete="new-password"
|
autoComplete="new-password"
|
||||||
type="password"
|
type="password"
|
||||||
{...field}
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.password")}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -331,22 +328,33 @@ const CreateView = ({
|
|||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.repeatPassword")}</Form.Label>
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input autoComplete="off" type="password" {...field} />
|
<Input
|
||||||
|
autoComplete="off"
|
||||||
|
type="password"
|
||||||
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.repeatPassword")}
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
{form.formState.errors.root && (
|
{validationError && (
|
||||||
|
<div className="text-center mt-6">
|
||||||
|
<Hint className="inline-flex" variant={"error"}>
|
||||||
|
{validationError}
|
||||||
|
</Hint>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{serverError && (
|
||||||
<Alert
|
<Alert
|
||||||
|
className="p-2 bg-white items-center"
|
||||||
|
dismissible
|
||||||
variant="error"
|
variant="error"
|
||||||
dismissible={false}
|
|
||||||
className="text-balance"
|
|
||||||
>
|
>
|
||||||
{form.formState.errors.root.message}
|
{serverError}
|
||||||
</Alert>
|
</Alert>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -381,6 +389,14 @@ const SuccessView = () => {
|
|||||||
{t("invite.successAction")}
|
{t("invite.successAction")}
|
||||||
</Link>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<Link
|
||||||
|
key="login-link"
|
||||||
|
to="/login"
|
||||||
|
className="text-ui-fg-interactive txt-small !text-ui-fg-base transition-fg hover:text-ui-fg-interactive-hover focus-visible:text-ui-fg-interactive-hover mt-3 font-medium outline-none"
|
||||||
|
>
|
||||||
|
{t("invite.backToLogin")}
|
||||||
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,18 +1,18 @@
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod"
|
import { zodResolver } from "@hookform/resolvers/zod"
|
||||||
import { Alert, Button, Heading, Input, Text } from "@medusajs/ui"
|
import { Alert, Button, Heading, Hint, Input, Text } from "@medusajs/ui"
|
||||||
import { useForm } from "react-hook-form"
|
import { useForm } from "react-hook-form"
|
||||||
import { Trans, useTranslation } from "react-i18next"
|
import { Trans, useTranslation } from "react-i18next"
|
||||||
import { Link, useLocation, useNavigate } from "react-router-dom"
|
import { Link, useLocation, useNavigate } from "react-router-dom"
|
||||||
import * as z from "zod"
|
import * as z from "zod"
|
||||||
|
|
||||||
import { Divider } from "../../components/common/divider"
|
|
||||||
import { Form } from "../../components/common/form"
|
import { Form } from "../../components/common/form"
|
||||||
import { LogoBox } from "../../components/common/logo-box"
|
|
||||||
import { useSignInWithEmailPassword } from "../../hooks/api/auth"
|
import { useSignInWithEmailPassword } from "../../hooks/api/auth"
|
||||||
|
|
||||||
import after from "virtual:medusa/widgets/login/after"
|
import after from "virtual:medusa/widgets/login/after"
|
||||||
import before from "virtual:medusa/widgets/login/before"
|
import before from "virtual:medusa/widgets/login/before"
|
||||||
import { isFetchError } from "../../lib/is-fetch-error"
|
import { isFetchError } from "../../lib/is-fetch-error"
|
||||||
|
import AvatarBox from "../../components/common/logo-box/avatar-box"
|
||||||
|
|
||||||
const LoginSchema = z.object({
|
const LoginSchema = z.object({
|
||||||
email: z.string().email(),
|
email: z.string().email(),
|
||||||
@@ -68,11 +68,14 @@ export const Login = () => {
|
|||||||
})
|
})
|
||||||
|
|
||||||
const serverError = form.formState.errors?.root?.serverError?.message
|
const serverError = form.formState.errors?.root?.serverError?.message
|
||||||
|
const validationError =
|
||||||
|
form.formState.errors.email?.message ||
|
||||||
|
form.formState.errors.password?.message
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="bg-ui-bg-base flex min-h-dvh w-dvw items-center justify-center">
|
<div className="bg-ui-bg-subtle flex min-h-dvh w-dvw items-center justify-center">
|
||||||
<div className="m-4 flex w-full max-w-[300px] flex-col items-center">
|
<div className="m-4 flex w-full max-w-[280px] flex-col items-center">
|
||||||
<LogoBox className="mb-4" />
|
<AvatarBox />
|
||||||
<div className="mb-4 flex flex-col items-center">
|
<div className="mb-4 flex flex-col items-center">
|
||||||
<Heading>{t("login.title")}</Heading>
|
<Heading>{t("login.title")}</Heading>
|
||||||
<Text size="small" className="text-ui-fg-subtle text-center">
|
<Text size="small" className="text-ui-fg-subtle text-center">
|
||||||
@@ -92,18 +95,21 @@ export const Login = () => {
|
|||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
className="flex w-full flex-col gap-y-6"
|
className="flex w-full flex-col gap-y-6"
|
||||||
>
|
>
|
||||||
<div className="flex flex-col gap-y-4">
|
<div className="flex flex-col gap-y-1">
|
||||||
<Form.Field
|
<Form.Field
|
||||||
control={form.control}
|
control={form.control}
|
||||||
name="email"
|
name="email"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.email")}</Form.Label>
|
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input autoComplete="email" {...field} />
|
<Input
|
||||||
|
autoComplete="email"
|
||||||
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.email")}
|
||||||
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
@@ -114,29 +120,41 @@ export const Login = () => {
|
|||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
return (
|
return (
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Form.Label>{t("fields.password")}</Form.Label>
|
<Form.Label>{}</Form.Label>
|
||||||
<Form.Control>
|
<Form.Control>
|
||||||
<Input
|
<Input
|
||||||
type="password"
|
type="password"
|
||||||
autoComplete="current-password"
|
autoComplete="current-password"
|
||||||
{...field}
|
{...field}
|
||||||
|
className="bg-ui-bg-field-component"
|
||||||
|
placeholder={t("fields.password")}
|
||||||
/>
|
/>
|
||||||
</Form.Control>
|
</Form.Control>
|
||||||
<Form.ErrorMessage />
|
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
)
|
)
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{validationError && (
|
||||||
|
<div className="text-center">
|
||||||
|
<Hint className="inline-flex" variant={"error"}>
|
||||||
|
{validationError}
|
||||||
|
</Hint>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{serverError && (
|
||||||
|
<Alert
|
||||||
|
className="p-2 bg-white items-center"
|
||||||
|
dismissible
|
||||||
|
variant="error"
|
||||||
|
>
|
||||||
|
{serverError}
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
<Button className="w-full" type="submit" isLoading={isPending}>
|
<Button className="w-full" type="submit" isLoading={isPending}>
|
||||||
{t("actions.continue")}
|
{t("actions.continueWithEmail")}
|
||||||
</Button>
|
</Button>
|
||||||
</form>
|
</form>
|
||||||
{serverError && (
|
|
||||||
<Alert className="mt-4" dismissible variant="error">
|
|
||||||
{serverError}
|
|
||||||
</Alert>
|
|
||||||
)}
|
|
||||||
</Form>
|
</Form>
|
||||||
{after.widgets.map((w, i) => {
|
{after.widgets.map((w, i) => {
|
||||||
return (
|
return (
|
||||||
@@ -146,15 +164,14 @@ export const Login = () => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</div>
|
</div>
|
||||||
<Divider variant="dashed" className="my-6" />
|
<span className="text-ui-fg-muted txt-small my-6">
|
||||||
<span className="text-ui-fg-subtle txt-small">
|
|
||||||
<Trans
|
<Trans
|
||||||
i18nKey="login.forgotPassword"
|
i18nKey="login.forgotPassword"
|
||||||
components={[
|
components={[
|
||||||
<Link
|
<Link
|
||||||
key="reset-password-link"
|
key="reset-password-link"
|
||||||
to="/reset-password"
|
to="/reset-password"
|
||||||
className="text-ui-fg-interactive transition-fg hover:text-ui-fg-interactive-hover focus-visible:text-ui-fg-interactive-hover outline-none"
|
className="text-ui-fg-interactive transition-fg hover:text-ui-fg-interactive-hover focus-visible:text-ui-fg-interactive-hover font-medium outline-none"
|
||||||
/>,
|
/>,
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
|||||||
Reference in New Issue
Block a user