UNPKG

@frank-auth/react

Version:

Flexible and customizable React UI components for Frank Authentication

465 lines (464 loc) 17 kB
'use client'; import { jsx as e, jsxs as l, Fragment as H } from "react/jsx-runtime"; import "@emotion/styled"; import { Button as b } from "../../ui/button/button.js"; import { Input as I } from "../../ui/input/input.js"; import { Card as z, CardBody as D, CardHeader as $ } from "../../ui/card/card.js"; import { Divider as k } from "../../ui/divider/divider.js"; import { Badge as j } from "../../ui/badge/badge.js"; import { Avatar as F } from "../../ui/avatar/avatar.js"; import { ClockIcon as q, ExclamationTriangleIcon as S, XCircleIcon as E, CheckCircleIcon as A, BuildingOfficeIcon as T, ShieldCheckIcon as O } from "@heroicons/react/24/outline"; import { useState as y, useCallback as P, useEffect as B } from "react"; import { useAuth as M } from "../../../hooks/use-auth.js"; import { useConfig as V } from "../../../hooks/use-config.js"; import { withErrorBoundary as C } from "../common/error-boundary.js"; function J({ token: v, onAcceptSuccess: a, onDeclineSuccess: m, onError: i }) { const { client: n, user: p } = M(), [t, x] = y(null), [d, o] = y("idle"), [u, f] = y(null), g = P( async (c) => { try { o("validating"), f(null); const s = await n.invitations.validateInvitation({ invitationValidationRequest: { token: c } }); s.valid && s.invitation ? (x(s.invitation), new Date(s.invitation.expiresAt) < /* @__PURE__ */ new Date() ? (o("expired"), f("This invitation has expired")) : o("valid")) : (o("invalid"), f(s.message || "Invalid invitation token")); } catch (s) { const h = s instanceof Error ? s : new Error("Failed to validate invitation"); o("error"), f(h.message), i?.(h); } }, [n, i] ), r = P( async (c) => { if (!t) { f("No invitation data available"); return; } try { o("accepting"), f(null); const s = { token: t.token }; c && (s.userData = c); const h = await n.invitations.acceptInvitation({ acceptInvitationRequest: s }); if (h.success) o("accepted"), a?.({ organizationId: t.organizationId, userId: h.userId || p?.id || "" }), t.redirectUrl && setTimeout(() => { window.location.href = t.redirectUrl; }, 2e3); else throw new Error(h.message || "Failed to accept invitation"); } catch (s) { const h = s instanceof Error ? s : new Error("Failed to accept invitation"); o("error"), f(h.message), i?.(h); } }, [t, n, p, a, i] ), w = P(async () => { if (!t) { f("No invitation data available"); return; } try { o("declining"), f(null); const c = await n.invitations.declineInvitation({ declineInvitationRequest: { token: t.token } }); if (c.success) o("declined"), m?.(); else throw new Error(c.message || "Failed to decline invitation"); } catch (c) { const s = c instanceof Error ? c : new Error("Failed to decline invitation"); o("error"), f(s.message), i?.(s); } }, [t, n, m, i]); return B(() => { v && d === "idle" && g(v); }, [v, d, g]), B(() => { if (!v && typeof window < "u") { const c = new URLSearchParams(window.location.search), s = c.get("invitation_token") || c.get("invite"); s && d === "idle" && g(s); } }, [v, d, g]), { invitation: t, status: d, error: u, validateInvitation: g, acceptInvitation: r, declineInvitation: w, isLoading: d === "validating" || d === "accepting" || d === "declining" }; } function L(v) { const a = new Date(v), m = /* @__PURE__ */ new Date(), i = a.getTime() - m.getTime(), n = Math.ceil(i / (1e3 * 60 * 60)), p = Math.ceil(i / (1e3 * 60 * 60 * 24)); return i <= 0 ? "Expired" : n <= 24 ? `Expires in ${n} hour${n !== 1 ? "s" : ""}` : `Expires in ${p} day${p !== 1 ? "s" : ""}`; } function W(v) { const a = new Date(v), m = /* @__PURE__ */ new Date(), i = a.getTime() - m.getTime(), n = i / (1e3 * 60 * 60); return i <= 0 ? "expired" : n <= 24 ? "expiring" : "active"; } const X = C(function({ token: a, onAcceptSuccess: m, onDeclineSuccess: i, onError: n, className: p, style: t }) { const { user: x } = M(), { config: d } = V(), { invitation: o, status: u, error: f, acceptInvitation: g, declineInvitation: r, isLoading: w } = J({ token: a, onAcceptSuccess: m, onDeclineSuccess: i, onError: n }), c = async (Y) => { await g(Y); }, s = async () => { await r(); }; return /* @__PURE__ */ e("div", { className: p, style: t, children: u === "validating" ? /* @__PURE__ */ e(N, { status: "validating" }) : u === "invalid" || u === "error" ? /* @__PURE__ */ e( N, { status: u === "invalid" ? "invalid" : "error", error: f || void 0 } ) : u === "expired" ? /* @__PURE__ */ e(N, { status: "expired", invitation: o || void 0 }) : u === "accepted" ? /* @__PURE__ */ e(N, { status: "accepted", invitation: o || void 0 }) : u === "declined" ? /* @__PURE__ */ e(N, { status: "declined", invitation: o || void 0 }) : o && u === "valid" ? /* @__PURE__ */ l("div", { className: "space-y-6", children: [ /* @__PURE__ */ e( R, { invitation: o, onAccept: () => c(), onDecline: s, isLoading: w } ), !x && /* @__PURE__ */ e( U, { invitation: o, onSubmit: c, isLoading: w, requiresSignUp: !0 } ) ] }) : null }); }), R = C(function({ invitation: a, onAccept: m, onDecline: i, isLoading: n = !1, className: p }) { const t = W(a.expiresAt), x = L(a.expiresAt); return /* @__PURE__ */ l(z, { className: `max-w-md mx-auto ${p || ""}`, variant: "shadow", children: [ /* @__PURE__ */ l($, { className: "flex flex-col items-center pb-2", children: [ /* @__PURE__ */ e("div", { className: "flex items-center justify-center w-16 h-16 bg-primary-100 rounded-full mb-4", children: /* @__PURE__ */ e(T, { className: "h-8 w-8 text-primary" }) }), /* @__PURE__ */ e("h2", { className: "text-xl font-bold text-center", children: "Organization Invitation" }), /* @__PURE__ */ e( j, { color: t === "expired" ? "danger" : t === "expiring" ? "warning" : "success", variant: "flat", className: "mt-2", children: x } ) ] }), /* @__PURE__ */ l(D, { className: "space-y-6", children: [ /* @__PURE__ */ e("div", { className: "text-center", children: /* @__PURE__ */ l("div", { className: "flex items-center justify-center mb-3", children: [ a.organizationLogo ? /* @__PURE__ */ e( F, { src: a.organizationLogo, alt: a.organizationName, size: "lg", className: "mr-3" } ) : /* @__PURE__ */ e("div", { className: "w-12 h-12 bg-default-200 rounded-full flex items-center justify-center mr-3", children: /* @__PURE__ */ e(T, { className: "h-6 w-6 text-default-500" }) }), /* @__PURE__ */ l("div", { children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: a.organizationName }), /* @__PURE__ */ e("p", { className: "text-sm text-default-500", children: "wants you to join their organization" }) ] }) ] }) }), /* @__PURE__ */ e(k, {}), /* @__PURE__ */ l("div", { className: "space-y-4", children: [ /* @__PURE__ */ l("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ e("span", { className: "text-sm text-default-600", children: "Email:" }), /* @__PURE__ */ e("span", { className: "text-sm font-medium", children: a.email }) ] }), /* @__PURE__ */ l("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ e("span", { className: "text-sm text-default-600", children: "Role:" }), /* @__PURE__ */ e( j, { color: "primary", variant: "flat", startContent: /* @__PURE__ */ e(O, { className: "h-3 w-3" }), children: a.roleName } ) ] }), a.invitedByName && /* @__PURE__ */ l("div", { className: "flex items-center justify-between", children: [ /* @__PURE__ */ e("span", { className: "text-sm text-default-600", children: "Invited by:" }), /* @__PURE__ */ l("div", { className: "flex items-center gap-2", children: [ a.invitedByAvatar && /* @__PURE__ */ e(F, { src: a.invitedByAvatar, size: "sm" }), /* @__PURE__ */ e("span", { className: "text-sm font-medium", children: a.invitedByName }) ] }) ] }) ] }), a.message && /* @__PURE__ */ l(H, { children: [ /* @__PURE__ */ e(k, {}), /* @__PURE__ */ e("div", { className: "bg-default-50 p-4 rounded-lg", children: /* @__PURE__ */ l("p", { className: "text-sm text-default-700 italic", children: [ '"', a.message, '"' ] }) }) ] }), /* @__PURE__ */ l("div", { className: "flex gap-3 pt-4", children: [ /* @__PURE__ */ e( b, { color: "success", variant: "solid", className: "flex-1", onClick: m, isLoading: n, disabled: t === "expired", startContent: /* @__PURE__ */ e(A, { className: "h-4 w-4" }), children: "Accept Invitation" } ), /* @__PURE__ */ e( b, { color: "danger", variant: "flat", className: "flex-1", onClick: i, isLoading: n, disabled: t === "expired", startContent: /* @__PURE__ */ e(E, { className: "h-4 w-4" }), children: "Decline" } ) ] }) ] }) ] }); }), U = C(function({ invitation: a, onSubmit: m, isLoading: i = !1, requiresSignUp: n = !1, className: p }) { const [t, x] = y({ firstName: "", lastName: "", password: "", confirmPassword: "" }), [d, o] = y({}), u = (r, w) => { x((c) => ({ ...c, [r]: w })), d[r] && o((c) => ({ ...c, [r]: "" })); }, f = () => { const r = {}; return n && (t.firstName.trim() || (r.firstName = "First name is required"), t.lastName.trim() || (r.lastName = "Last name is required"), t.password ? t.password.length < 8 && (r.password = "Password must be at least 8 characters") : r.password = "Password is required", t.password !== t.confirmPassword && (r.confirmPassword = "Passwords do not match")), o(r), Object.keys(r).length === 0; }, g = (r) => { if (r.preventDefault(), !f()) return; const w = n ? { firstName: t.firstName, lastName: t.lastName, password: t.password } : void 0; m(w); }; return n ? /* @__PURE__ */ l(z, { className: `max-w-md mx-auto ${p || ""}`, variant: "flat", children: [ /* @__PURE__ */ l($, { children: [ /* @__PURE__ */ e("h3", { className: "text-lg font-semibold", children: "Complete Your Profile" }), /* @__PURE__ */ l("p", { className: "text-sm text-default-500", children: [ "Create your account to join ", a.organizationName ] }) ] }), /* @__PURE__ */ e(D, { children: /* @__PURE__ */ l("form", { onSubmit: g, className: "space-y-4", children: [ /* @__PURE__ */ l("div", { className: "grid grid-cols-2 gap-3", children: [ /* @__PURE__ */ e( I, { label: "First Name", placeholder: "Enter your first name", value: t.firstName, onChange: (r) => u("firstName", r.target.value), isInvalid: !!d.firstName, errorMessage: d.firstName, disabled: i, required: !0 } ), /* @__PURE__ */ e( I, { label: "Last Name", placeholder: "Enter your last name", value: t.lastName, onChange: (r) => u("lastName", r.target.value), isInvalid: !!d.lastName, errorMessage: d.lastName, disabled: i, required: !0 } ) ] }), /* @__PURE__ */ e( I, { type: "password", label: "Password", placeholder: "Create a strong password", value: t.password, onChange: (r) => u("password", r.target.value), isInvalid: !!d.password, errorMessage: d.password, disabled: i, required: !0 } ), /* @__PURE__ */ e( I, { type: "password", label: "Confirm Password", placeholder: "Confirm your password", value: t.confirmPassword, onChange: (r) => u("confirmPassword", r.target.value), isInvalid: !!d.confirmPassword, errorMessage: d.confirmPassword, disabled: i, required: !0 } ), /* @__PURE__ */ e( b, { type: "submit", color: "primary", className: "w-full", isLoading: i, disabled: i, children: "Join Organization" } ) ] }) }) ] }) : null; }), N = C(function({ status: a, invitation: m, error: i, className: n }) { const t = (() => { switch (a) { case "validating": return { icon: /* @__PURE__ */ e(q, { className: "h-16 w-16 text-primary animate-spin" }), title: "Validating Invitation", message: "Please wait while we verify your invitation...", color: "primary" }; case "valid": return { icon: /* @__PURE__ */ e(A, { className: "h-16 w-16 text-success" }), title: "Valid Invitation", message: "Your invitation is valid and ready to accept.", color: "success" }; case "accepted": return { icon: /* @__PURE__ */ e(A, { className: "h-16 w-16 text-success" }), title: "Invitation Accepted!", message: m ? `Welcome to ${m.organizationName}! You've been added as ${m.roleName}.` : "Your invitation has been accepted successfully.", color: "success" }; case "declined": return { icon: /* @__PURE__ */ e(E, { className: "h-16 w-16 text-default-500" }), title: "Invitation Declined", message: "You have declined this organization invitation.", color: "default" }; case "expired": return { icon: /* @__PURE__ */ e(S, { className: "h-16 w-16 text-warning" }), title: "Invitation Expired", message: "This invitation has expired. Please request a new invitation from the organization.", color: "warning" }; case "invalid": return { icon: /* @__PURE__ */ e(E, { className: "h-16 w-16 text-danger" }), title: "Invalid Invitation", message: "This invitation link is invalid or has already been used.", color: "danger" }; case "error": return { icon: /* @__PURE__ */ e(S, { className: "h-16 w-16 text-danger" }), title: "Error", message: i || "An error occurred while processing your invitation.", color: "danger" }; default: return { icon: /* @__PURE__ */ e(q, { className: "h-16 w-16 text-default-500" }), title: "Unknown Status", message: "Unable to determine invitation status.", color: "default" }; } })(); return /* @__PURE__ */ e(z, { className: `max-w-md mx-auto ${n || ""}`, variant: "flat", children: /* @__PURE__ */ l(D, { className: "text-center py-8", children: [ /* @__PURE__ */ e("div", { className: "flex justify-center mb-6", children: t.icon }), /* @__PURE__ */ e("h2", { className: "text-xl font-semibold mb-3", children: t.title }), /* @__PURE__ */ e("p", { className: "text-default-600 mb-6", children: t.message }), a === "accepted" && m?.redirectUrl && /* @__PURE__ */ e("p", { className: "text-sm text-default-500", children: "Redirecting you to the organization dashboard..." }), (a === "expired" || a === "invalid" || a === "error") && /* @__PURE__ */ e( b, { color: "primary", variant: "flat", onClick: () => window.location.href = "/", children: "Return to Home" } ) ] }) }); }), ce = { InviteAcceptance: X, InvitePreview: R, InviteAcceptForm: U, InviteStatus: N }; export { ce as InvitationComponents, U as InviteAcceptForm, X as InviteAcceptance, R as InvitePreview, N as InviteStatus }; //# sourceMappingURL=invitations.js.map