UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

738 lines (737 loc) 27.6 kB
'use client'; import { jsx as e, jsxs as i, Fragment as F } from "react/jsx-runtime"; import "@emotion/styled"; import { Button as ce } from "../../ui/button/button.js"; import { Input as we } from "../../ui/input/input.js"; import { Checkbox as ee } from "../../ui/checkbox/checkbox.js"; import { Link as R } from "../../ui/link/link.js"; import { Divider as ae } from "../../ui/divider/divider.js"; import { CheckIcon as Ne, EnvelopeIcon as W, LockClosedIcon as se, UserIcon as re, PhoneIcon as be, EyeSlashIcon as te, EyeIcon as ne } from "@heroicons/react/24/outline"; import { motion as ve } from "framer-motion"; import { useTheme as ye } from "../../../theme/context.js"; import { useMemo as ie, useState as oe, useCallback as P } from "react"; import { useAuth as xe } from "../../../hooks/use-auth.js"; import { useConfig as me } from "../../../hooks/use-config.js"; import { useOAuth as Ce } from "../../../hooks/use-oauth.js"; import { usePasskeys as Pe } from "../../../hooks/use-passkeys.js"; import ke from "../../forms/email-field.js"; import Ee from "../../forms/form-wrapper.js"; import Ie, { StyledSvg as le } from "../../forms/password-field.js"; import { DEFAULT_PASSWORD_REQUIREMENTS as Me, formatInvitationData as Se, signUpValidation as I } from "./index.js"; function De({ onSuccess: k, onError: M, redirectUrl: w, organizationId: p, disabled: f, size: m = "md", radius: h = "md" }) { const { signInWithGoogle: _, signInWithGitHub: j, signInWithMicrosoft: H, signInWithApple: $, isLoading: o } = Ce(), { components: d } = me(), x = d.Button ?? ce, C = P( async (n, S) => { try { await S(), k(n, { provider: n }); } catch (D) { M?.( D instanceof Error ? D : new Error(`${n} sign-up failed`) ); } }, [k, M] ); return /* @__PURE__ */ i("div", { className: "space-y-3", children: [ /* @__PURE__ */ e( x, { variant: "bordered", size: m, radius: h, className: "w-full", startContent: /* @__PURE__ */ e(Ae, {}), onPress: () => C( "google", () => _({ redirectUrl: w, organizationId: p }) ), isDisabled: f || o, children: "Continue with Google" } ), /* @__PURE__ */ e( x, { variant: "bordered", size: m, radius: h, className: "w-full", startContent: /* @__PURE__ */ e(Be, {}), onPress: () => C( "github", () => j({ redirectUrl: w, organizationId: p }) ), isDisabled: f || o, children: "Continue with GitHub" } ), /* @__PURE__ */ e( x, { variant: "bordered", size: m, radius: h, className: "w-full", startContent: /* @__PURE__ */ e(_e, {}), onPress: () => C( "microsoft", () => H({ redirectUrl: w, organizationId: p }) ), isDisabled: f || o, children: "Continue with Microsoft" } ), /* @__PURE__ */ e( x, { variant: "bordered", size: m, radius: h, className: "w-full", startContent: /* @__PURE__ */ e(je, {}), onPress: () => C( "apple", () => $({ redirectUrl: w, organizationId: p }) ), isDisabled: f || o, children: "Continue with Apple" } ) ] }); } function aa({ methods: k = ["password", "sso", "magic-link"], email: M = "", organizationId: w, invitationToken: p, redirectUrl: f, onSuccess: m, onError: h, showSignInLink: _ = !0, title: j, subtitle: H, variant: $ = "default", size: o = "md", radius: d = "md", className: x = "", showBranding: C = !0, disabled: n = !1, footer: S, header: D, requireTerms: q = !0, termsUrl: O = "/terms", privacyUrl: V = "/privacy", passwordRequirements: T = Me, autoFocus: U = !0, collectFields: c = ["firstName", "lastName"] }) { const { theme: z } = ye(), { signUp: J, isLoading: l } = xe(), { features: A, organizationSettings: B, components: G, linksPath: de } = me(), { registerPasskey: Y } = Pe(), E = G.Input ?? we, L = G.Button ?? ce, Q = G.SignUpForm; if (Q) return /* @__PURE__ */ e( Q, { methods: k, email: M, organizationId: w, invitationToken: p, redirectUrl: f, onSuccess: m, onError: h, showSignInLink: _, title: j, subtitle: H, variant: $, size: o, className: x, showBranding: C, disabled: n, footer: S, header: D, requireTerms: q, termsUrl: O, privacyUrl: V, passwordRequirements: T, autoFocus: U, collectFields: c } ); const N = ie(() => Se(p), [p]), [a, u] = oe({ step: "form", method: "password", email: M, password: "", confirmPassword: "", firstName: "", lastName: "", username: "", phoneNumber: "", showPassword: !1, showConfirmPassword: !1, acceptTerms: !1, acceptMarketing: !1, fieldErrors: {}, passwordSuggestions: [] }), [K, y] = oe(null), b = ie(() => k.filter((s) => { switch (s) { case "oauth": return A.oauth; case "magic-link": return A.magicLink; case "passkey": return A.passkeys; default: return !0; } }), [k, A]), X = P( (s, t) => { const r = { ...a.fieldErrors }; switch (s) { case "email": I.email(t) ? delete r.email : r.email = "Please enter a valid email address"; break; case "password": I.password(t, T) ? delete r.password : r.password = "Password does not meet requirements", a.confirmPassword && t !== a.confirmPassword ? r.confirmPassword = "Passwords do not match" : a.confirmPassword && delete r.confirmPassword; break; case "confirmPassword": t !== a.password ? r.confirmPassword = "Passwords do not match" : delete r.confirmPassword; break; case "firstName": c.includes("firstName") && !I.firstName(t) ? r.firstName = "Please enter a valid first name" : delete r.firstName; break; case "lastName": c.includes("lastName") && !I.lastName(t) ? r.lastName = "Please enter a valid last name" : delete r.lastName; break; case "username": c.includes("username") && t && !I.username(t) ? r.username = "Username must be at least 3 characters and contain only letters, numbers, - and _" : delete r.username; break; case "phoneNumber": c.includes("phoneNumber") && t && !I.phoneNumber(t) ? r.phoneNumber = "Please enter a valid phone number" : delete r.phoneNumber; break; } u((v) => ({ ...v, fieldErrors: r })); }, [ a.fieldErrors, a.password, a.confirmPassword, T, c ] ), g = P( (s, t) => { u((r) => ({ ...r, [s]: t })), X(s, t); }, [X] ), Z = N ? { title: `Join ${N.organizationName}`, subtitle: N.inviterName ? `${N.inviterName} has invited you to join` : "You've been invited to join" } : B?.branding?.customSignUpText ? { title: B.branding.customSignUpText.title || "Create your account", subtitle: B.branding.customSignUpText.subtitle || "Join us today" } : { title: "Create your account", subtitle: "Join us today" }, ue = j || Z.title, fe = H || Z.subtitle, he = P( async (s) => { s.preventDefault(), y(null); const t = ["email", "password"]; c.includes("firstName") && t.push("firstName"), c.includes("lastName") && t.push("lastName"); for (const r of t) if (!a[r]) { y("Please fill in all required fields"); return; } if (Object.keys(a.fieldErrors).length > 0) { y("Please fix the errors above"); return; } if (a.password !== a.confirmPassword) { y("Passwords do not match"); return; } if (q && !a.acceptTerms) { y("Please accept the terms of service"); return; } try { const r = await J({ emailAddress: a.email, password: a.password, firstName: a.firstName || void 0, lastName: a.lastName || void 0, username: a.username || void 0, organizationId: N?.organizationId || w, invitationToken: p, acceptTerms: a.acceptTerms, marketingConsent: a.acceptMarketing, unsafeMetadata: { phoneNumber: a.phoneNumber || void 0, acceptMarketing: a.acceptMarketing } }); if (r.status === "needs_verification") { u((v) => ({ ...v, step: "verification" })); return; } r.status === "complete" && r.user && (u((v) => ({ ...v, step: "success" })), m?.(r), f && setTimeout(() => { window.location.href = f; }, 1e3)), r.status === "missing_requirements" && r.error && y(r.error?.message); } catch (r) { const v = r instanceof Error ? r : new Error("Sign up failed"); y(v.message), h?.(v); } }, [ a, c, q, J, N?.organizationId, w, p, m, h, f ] ), pe = P( (s, t) => { u((r) => ({ ...r, step: "success" })), m?.(t); }, [m] ); P( (s) => { u((t) => ({ ...t, step: "verification" })), m?.(s); }, [m] ); const ge = P(async () => { try { const s = await Y("Sign-up passkey"); u((t) => ({ ...t, step: "success" })), m?.(s); } catch (s) { const t = s instanceof Error ? s : new Error("Passkey registration failed"); y(t.message), h?.(t); } }, [Y, m, h]); return a.step === "success" ? /* @__PURE__ */ i("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e( ve.div, { initial: { scale: 0 }, animate: { scale: 1 }, className: "mx-auto w-16 h-16 bg-success-100 dark:bg-success-900/30 rounded-full flex items-center justify-center", children: /* @__PURE__ */ e(Ne, { className: "w-8 h-8 text-success-600 dark:text-success-400" }) } ), /* @__PURE__ */ i("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-xl font-semibold text-foreground mb-2", children: "Welcome aboard!" }), /* @__PURE__ */ e("p", { className: "text-default-500 text-sm", children: "Your account has been created successfully." }) ] }), f && /* @__PURE__ */ i("div", { className: "flex items-center justify-center gap-2 text-sm text-default-500", children: [ /* @__PURE__ */ e("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-primary" }), /* @__PURE__ */ e("span", { children: "Setting up your account..." }) ] }) ] }) : a.step === "verification" ? /* @__PURE__ */ i("div", { className: "text-center space-y-4", children: [ /* @__PURE__ */ e("div", { className: "mx-auto w-16 h-16 bg-primary-100 dark:bg-primary-900/30 rounded-full flex items-center justify-center", children: /* @__PURE__ */ e(W, { className: "w-8 h-8 text-primary-600 dark:text-primary-400" }) }), /* @__PURE__ */ i("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-xl font-semibold text-foreground mb-2", children: "Check your email" }), /* @__PURE__ */ i("p", { className: "text-default-500 text-sm", children: [ "We've sent a verification link to ", a.email ] }) ] }), /* @__PURE__ */ e( L, { variant: "light", size: "sm", onPress: () => u((s) => ({ ...s, step: "form" })), children: "Use a different email" } ) ] }) : /* @__PURE__ */ i( Ee, { size: o, radius: d, variant: "flat", disableAnimations: !0, className: `space-y-6 ${x}`, footer: S, header: D, title: ue, onSubmit: he, subtitle: fe, desc: N && /* @__PURE__ */ e("div", { className: "bg-primary-50 dark:bg-primary-900/20 rounded-lg p-3 text-sm", children: /* @__PURE__ */ i("p", { className: "text-primary-600 dark:text-primary-400", children: [ "You're joining as: ", /* @__PURE__ */ e("strong", { children: N.role }) ] }) }), showCard: !1, logo: C && B?.branding?.logoUrl ? /* @__PURE__ */ e( "img", { src: B.branding.logoUrl, alt: "Organization Logo", className: "h-8 w-auto mx-auto mb-4" } ) : void 0, children: [ b.includes("oauth") && /* @__PURE__ */ i(F, { children: [ /* @__PURE__ */ e( De, { onSuccess: pe, onError: h, redirectUrl: f, organizationId: N?.organizationId || w, disabled: n, size: o, radius: d } ), (b.includes("password") || b.includes("magic-link")) && /* @__PURE__ */ e("div", { className: "relative", children: /* @__PURE__ */ e(ae, { className: "my-4", children: /* @__PURE__ */ e("span", { className: "bg-background px-2 text-sm text-default-500", children: "or" }) }) }) ] }), b.includes("passkey") && /* @__PURE__ */ i(F, { children: [ /* @__PURE__ */ e( L, { variant: "bordered", size: o, radius: d, className: "w-full", startContent: /* @__PURE__ */ e(se, { className: "w-4 h-4" }), onPress: ge, isDisabled: n || l, children: "Sign up with passkey" } ), (b.includes("password") || b.includes("magic-link")) && /* @__PURE__ */ i("div", { className: "relative", children: [ /* @__PURE__ */ e(ae, { className: "my-4" }), /* @__PURE__ */ e("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ e("span", { className: "bg-background px-2 text-sm text-default-500", children: "or" }) }) ] }) ] }), b.includes("password") && /* @__PURE__ */ i("div", { className: "space-y-4", children: [ (c.includes("firstName") || c.includes("lastName")) && /* @__PURE__ */ i("div", { className: "grid grid-cols-1 gap-4 sm:grid-cols-2", children: [ c.includes("firstName") && /* @__PURE__ */ e( E, { type: "text", label: "First Name", placeholder: "Enter your first name", value: a.firstName, onChange: (s) => g("firstName", s.target.value), startContent: /* @__PURE__ */ e(re, { className: "w-4 h-4 text-default-400" }), size: o, radius: d, required: !0, disabled: n || l, isRequired: !0, isDisabled: n || l, isInvalid: !!a.fieldErrors.firstName, errorMessage: a.fieldErrors.firstName, autoFocus: U, variant: "bordered" } ), c.includes("lastName") && /* @__PURE__ */ e( E, { type: "text", label: "Last Name", placeholder: "Enter your last name", value: a.lastName, onChange: (s) => g("lastName", s.target.value), startContent: /* @__PURE__ */ e(re, { className: "w-4 h-4 text-default-400" }), size: o, radius: d, required: !0, disabled: n || l, isRequired: !0, isDisabled: n || l, isInvalid: !!a.fieldErrors.lastName, errorMessage: a.fieldErrors.lastName, autoFocus: U && !c.includes("firstName"), variant: "bordered" } ) ] }), /* @__PURE__ */ e( ke, { label: "Email", placeholder: "Enter your email", value: a.email, onChange: (s) => g("email", s), startContent: /* @__PURE__ */ e(W, { className: "w-4 h-4 text-default-400" }), size: o, radius: d, required: !0, disabled: n || l, error: a.fieldErrors.email, autoFocus: U && c.length === 0, showSuggestions: !1, variant: "bordered" } ), c.includes("username") && /* @__PURE__ */ e( E, { type: "text", label: "Username", placeholder: "Choose a username", value: a.username, onChange: (s) => g("username", s.target.value), startContent: /* @__PURE__ */ e("span", { className: "text-default-400 text-sm", children: "@" }), size: o, radius: d, required: !0, disabled: n || l, isDisabled: n || l, isInvalid: !!a.fieldErrors.username, errorMessage: a.fieldErrors.username, variant: "bordered" } ), c.includes("phoneNumber") && /* @__PURE__ */ e( E, { type: "tel", label: "Phone Number", placeholder: "Enter your phone number", value: a.phoneNumber, onChange: (s) => g("phoneNumber", s.target.value), startContent: /* @__PURE__ */ e(be, { className: "w-4 h-4 text-default-400" }), size: o, radius: d, disabled: n || l, isDisabled: n || l, isInvalid: !!a.fieldErrors.phoneNumber, errorMessage: a.fieldErrors.phoneNumber, variant: "bordered" } ), /* @__PURE__ */ i("div", { className: "space-y-2", children: [ /* @__PURE__ */ e( Ie, { label: "Password", placeholder: "Create a password", value: a.password, onChange: (s) => g("password", s), endContent: /* @__PURE__ */ e( "button", { type: "button", onClick: () => u((s) => ({ ...s, showPassword: !s.showPassword })), className: "focus:outline-none", children: a.showPassword ? /* @__PURE__ */ e(te, { className: "w-4 h-4 text-default-400" }) : /* @__PURE__ */ e(ne, { className: "w-4 h-4 text-default-400" }) } ), size: o, radius: d, required: !0, advanceRequirements: T, disabled: n || l, isInvalid: !!a.fieldErrors.password, errorMessage: a.fieldErrors.password, variant: "bordered" } ), a.passwordSuggestions.length > 0 && /* @__PURE__ */ i("div", { className: "space-y-2", children: [ /* @__PURE__ */ e("div", { className: "text-xs text-default-500", children: "Suggested passwords:" }), /* @__PURE__ */ e("div", { className: "space-y-1", children: a.passwordSuggestions.map((s, t) => /* @__PURE__ */ e( "button", { type: "button", onClick: () => { g("password", s), g("confirmPassword", s); }, className: "text-xs font-mono text-primary-600 hover:text-primary-800 hover:underline block", children: s }, t )) }) ] }) ] }), /* @__PURE__ */ e( E, { type: a.showConfirmPassword ? "text" : "password", label: "Confirm Password", placeholder: "Confirm your password", value: a.confirmPassword, onChange: (s) => g("confirmPassword", s.target.value), startContent: /* @__PURE__ */ e(se, { className: "w-4 h-4 text-default-400" }), endContent: /* @__PURE__ */ e( "button", { type: "button", onClick: () => u((s) => ({ ...s, showConfirmPassword: !s.showConfirmPassword })), className: "focus:outline-none", children: a.showConfirmPassword ? /* @__PURE__ */ e(le, { theme: z, as: te }) : /* @__PURE__ */ e(le, { theme: z, as: ne }) } ), size: o, isRequired: !0, isDisabled: n || l, isInvalid: !!a.fieldErrors.confirmPassword, errorMessage: a.fieldErrors.confirmPassword, variant: "bordered" } ), /* @__PURE__ */ i("div", { className: "space-y-3", children: [ q && /* @__PURE__ */ e( ee, { isSelected: a.acceptTerms, onValueChange: (s) => u((t) => ({ ...t, acceptTerms: s })), size: "sm", isDisabled: n || l, isRequired: !0, children: /* @__PURE__ */ i("span", { className: "text-sm", children: [ "I agree to the", " ", /* @__PURE__ */ e(R, { href: O, size: "sm", color: "primary", isExternal: !0, children: "Terms of Service" }), " ", "and", " ", /* @__PURE__ */ e(R, { href: V, size: "sm", color: "primary", isExternal: !0, children: "Privacy Policy" }) ] }) } ), /* @__PURE__ */ e( ee, { isSelected: a.acceptMarketing, onValueChange: (s) => u((t) => ({ ...t, acceptMarketing: s })), size: "sm", isDisabled: n || l, children: /* @__PURE__ */ e("span", { className: "text-sm text-default-500", children: "I'd like to receive product updates and marketing emails" }) } ) ] }), K && /* @__PURE__ */ e("div", { className: "text-danger-600 text-sm bg-danger-50 dark:bg-danger-900/20 rounded-lg p-3", children: K }), /* @__PURE__ */ e( L, { type: "submit", color: "primary", size: o, radius: d, className: "w-full", isLoading: l, isDisabled: n || !a.email || !a.password || !a.acceptTerms, children: l ? "Creating account..." : "Create account" } ) ] }), b.includes("magic-link") && !b.includes("password") && /* @__PURE__ */ i("div", { className: "space-y-4", children: [ /* @__PURE__ */ e( E, { type: "email", label: "Email", placeholder: "Enter your email", value: a.email, onChange: (s) => g("email", s.target.value), startContent: /* @__PURE__ */ e(W, { className: "w-4 h-4 text-default-400" }), size: o, isRequired: !0, isDisabled: n || l, autoFocus: !0, variant: "bordered" } ), /* @__PURE__ */ e( L, { variant: "bordered", size: o, radius: d, className: "w-full", startContent: /* @__PURE__ */ e(W, { className: "w-4 h-4" }), isDisabled: n || l || !a.email, isLoading: l, children: "Send magic link" } ) ] }), _ && A.signIn && /* @__PURE__ */ i("div", { className: "text-center text-sm", children: [ /* @__PURE__ */ e("span", { className: "text-default-500", children: "Already have an account? " }), /* @__PURE__ */ e(R, { href: de?.signIn, color: "primary", children: "Sign in" }) ] }), S ] } ); } function Ae() { return /* @__PURE__ */ i("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", children: [ /* @__PURE__ */ e( "path", { fill: "#4285F4", d: "M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z" } ), /* @__PURE__ */ e( "path", { fill: "#34A853", d: "M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z" } ), /* @__PURE__ */ e( "path", { fill: "#FBBC05", d: "M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z" } ), /* @__PURE__ */ e( "path", { fill: "#EA4335", d: "M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z" } ) ] }); } function Be() { return /* @__PURE__ */ e("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { d: "M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z" }) }); } function _e() { return /* @__PURE__ */ i("svg", { className: "w-4 h-4", viewBox: "0 0 24 24", children: [ /* @__PURE__ */ e("path", { fill: "#f25022", d: "M1 1h10v10H1z" }), /* @__PURE__ */ e("path", { fill: "#00a4ef", d: "M13 1h10v10H13z" }), /* @__PURE__ */ e("path", { fill: "#7fba00", d: "M1 13h10v10H1z" }), /* @__PURE__ */ e("path", { fill: "#ffb900", d: "M13 13h10v10H13z" }) ] }); } function je() { return /* @__PURE__ */ e("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ e("path", { d: "M18.71 19.5c-.83 1.24-1.71 2.45-3.05 2.47-1.34.03-1.77-.79-3.29-.79-1.53 0-2 .77-3.27.82-1.31.05-2.3-1.32-3.14-2.53C4.25 17 2.94 12.45 4.7 9.39c.87-1.52 2.43-2.48 4.12-2.51 1.28-.02 2.5.87 3.29.87.78 0 2.26-1.07 3.81-.91.65.03 2.47.26 3.64 1.98-.09.06-2.17 1.28-2.15 3.81.03 3.02 2.65 4.03 2.68 4.04-.03.07-.42 1.44-1.38 2.83M13 3.5c.73-.83 1.94-1.46 2.94-1.5.13 1.17-.34 2.35-1.04 3.19-.69.85-1.83 1.51-2.95 1.42-.15-1.15.41-2.35 1.05-3.11z" }) }); } export { aa as SignUpForm }; //# sourceMappingURL=sign-up-form.js.map