import { zodResolver } from "@hookform/resolvers/zod"
import { Alert, Button, Heading, Hint, Input, Text, toast } from "@medusajs/ui"
import i18n from "i18next"
import { AnimatePresence, motion } from "motion/react"
import { useState } from "react"
import { useForm } from "react-hook-form"
import { useTranslation } from "react-i18next"
import { decodeToken } from "react-jwt"
import { Link, useSearchParams } from "react-router-dom"
import * as z from "zod"
import { Form } from "../../components/common/form"
import AvatarBox from "../../components/common/logo-box/avatar-box"
import { useSignUpWithEmailPass } from "../../hooks/api/auth"
import { useAcceptInvite } from "../../hooks/api/invites"
import { isFetchError } from "../../lib/is-fetch-error"
const CreateAccountSchema = z
.object({
email: z.string().email(),
first_name: z.string().min(1),
last_name: z.string().min(1),
password: z.string().min(1),
repeat_password: z.string().min(1),
})
.superRefine(({ password, repeat_password }, ctx) => {
if (password !== repeat_password) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: i18n.t("invite.passwordMismatch"),
path: ["repeat_password"],
})
}
})
// TODO: Update to V2 format
type DecodedInvite = {
id: string
jti: any
exp: string
iat: number
email: string
}
export const Invite = () => {
const [searchParams] = useSearchParams()
const [success, setSuccess] = useState(false)
const token = searchParams.get("token")
const invite: DecodedInvite | null = token ? decodeToken(token) : null
const isValidInvite = invite && validateDecodedInvite(invite)
return (
{isValidInvite ? (
{!success ? (
setSuccess(true)}
token={token!}
invite={invite}
/>
) : (
)}
) : (
)}
)
}
const LoginLink = () => {
const { t } = useTranslation()
return (
{t("invite.backToLogin")}
)
}
const InvalidView = () => {
const { t } = useTranslation()
return (
{t("invite.invalidTokenTitle")}
{t("invite.invalidTokenHint")}
)
}
const CreateView = ({
onSuccess,
token,
invite,
}: {
onSuccess: () => void
token: string
invite: DecodedInvite
}) => {
const { t } = useTranslation()
const [invalid, setInvalid] = useState(false)
const [params] = useSearchParams()
const isFirstRun = params.get("first_run") === "true" // true when the invite page is open during a "create medusa app" run
const form = useForm>({
resolver: zodResolver(CreateAccountSchema),
defaultValues: {
email: isFirstRun ? "" : invite.email || "",
first_name: "",
last_name: "",
password: "",
repeat_password: "",
},
})
const { mutateAsync: signUpEmailPass, isPending: isCreatingAuthUser } =
useSignUpWithEmailPass()
const { mutateAsync: acceptInvite, isPending: isAcceptingInvite } =
useAcceptInvite(token)
const handleSubmit = form.handleSubmit(async (data) => {
try {
const authToken = await signUpEmailPass({
email: data.email,
password: data.password,
})
const invitePayload = {
email: data.email,
first_name: data.first_name,
last_name: data.last_name,
}
await acceptInvite({
...invitePayload,
auth_token: authToken,
})
toast.success(t("invite.toast.accepted"))
onSuccess()
} catch (error) {
if (isFetchError(error) && error.status === 400) {
form.setError("root", {
type: "manual",
message: t("invite.invalidInvite"),
})
setInvalid(true)
return
}
form.setError("root", {
type: "manual",
message: t("errors.serverError"),
})
}
})
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 (
{t("invite.title")}
{t("invite.hint")}
)
}
const SuccessView = () => {
const { t } = useTranslation()
return (
{t("invite.successTitle")}
{t("invite.successHint")}
{t("invite.backToLogin")}
)
}
const InviteSchema = z.object({
id: z.string(),
jti: z.string(),
exp: z.number(),
iat: z.number(),
})
const validateDecodedInvite = (decoded: any): decoded is DecodedInvite => {
return InviteSchema.safeParse(decoded).success
}