@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
465 lines (464 loc) • 17 kB
JavaScript
'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