@frank-auth/react
Version:
Flexible and customizable React UI components for Frank Authentication
478 lines (477 loc) • 15.4 kB
JavaScript
'use client';
import { jsx as a, jsxs as m } from "react/jsx-runtime";
import { Card as G, CardHeader as J, Divider as O, CardBody as T, Avatar as he, Spinner as te, Button as W, Input as A, Switch as ge, Select as ye, SelectItem as Ne, Textarea as be } from "@heroui/react";
import i from "react";
import { useAuth as ke } from "../../../hooks/use-auth.js";
import { useConfig as Pe } from "../../../hooks/use-config.js";
import { useUser as Ce } from "../../../hooks/use-user.js";
import { EmailField as ve } from "../../forms/email-field.js";
import { PhoneField as xe } from "../../forms/phone-field.js";
function Ve({
onSubmit: k,
onUpdate: E,
onSuccess: f,
onError: d,
showSaveButton: H = !0,
autoSave: g = !1,
autoSaveDelay: R = 1e3,
showImageUpload: K = !0,
showEmail: P = !0,
showPhone: Q = !0,
showUsername: X = !0,
showMetadata: le = !1,
customFields: y = [],
hideFields: h = [],
fieldLayout: V = "single",
variant: o = "bordered",
size: u = "md",
isDisabled: c = !1,
isLoading: Y = !1,
className: Z = "",
showVerificationStatus: j = !0,
allowImageRemoval: _ = !0,
onImageUpload: C,
onImageRemove: v,
maxImageSize: N = 5 * 1024 * 1024,
// 5MB
allowedImageTypes: x = ["image/jpeg", "image/png", "image/webp"]
}) {
const { user: l } = ke(), {
updateProfile: w,
updateProfileImage: S,
removeProfileImage: z,
isLoading: ne,
isEmailVerified: ie,
isPhoneVerified: ce
} = Ce(), { components: me } = Pe(), D = me.ProfileForm;
if (D)
return /* @__PURE__ */ a(
D,
{
onSubmit: k,
onUpdate: E,
onSuccess: f,
onError: d,
showSaveButton: H,
autoSave: g,
autoSaveDelay: R,
showImageUpload: K,
showEmail: P,
showPhone: Q,
showUsername: X,
showMetadata: le,
customFields: y,
hideFields: h,
fieldLayout: V,
variant: o,
size: u,
isDisabled: c,
isLoading: Y,
className: Z,
showVerificationStatus: j,
allowImageRemoval: _,
onImageUpload: C,
onImageRemove: v,
maxImageSize: N,
allowedImageTypes: x
}
);
const [s, $] = i.useState({
firstName: l?.firstName || "",
lastName: l?.lastName || "",
email: l?.primaryEmailAddress || "",
phone: l?.primaryPhoneNumber || "",
username: l?.username || "",
profileImageUrl: l?.profileImageUrl || "",
metadata: l?.unsafeMetadata || {},
customFields: {}
}), [M, L] = i.useState(!1), [oe, ee] = i.useState(!1), [I, F] = i.useState(!1), [n, re] = i.useState({}), ue = Y || ne || oe, b = i.useRef();
i.useEffect(() => {
l && $({
firstName: l.firstName || "",
lastName: l.lastName || "",
email: l.primaryEmailAddress || "",
phone: l.primaryPhoneNumber || "",
username: l.username || "",
profileImageUrl: l.profileImageUrl || "",
metadata: l.unsafeMetadata || {},
customFields: {}
});
}, [l]), i.useEffect(() => (g && M && !c && (b.current && clearTimeout(b.current), b.current = setTimeout(() => {
B();
}, R)), () => {
b.current && clearTimeout(b.current);
}), [s, M, g, R, c]);
const p = i.useCallback(
(e, r) => {
$((t) => ({
...t,
[e]: r
})), L(!0), n[e] && re((t) => {
const q = { ...t };
return delete q[e], q;
});
},
[n]
), U = i.useCallback(
(e, r) => {
$((t) => ({
...t,
customFields: {
...t.customFields,
[e]: r
}
})), L(!0);
},
[]
), ae = i.useCallback(() => {
const e = {};
return P && s.email && (/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(s.email) || (e.email = "Please enter a valid email address")), y.forEach((r) => {
if (r.required && !s.customFields?.[r.key] && (e[r.key] = `${r.label} is required`), r.validation && s.customFields?.[r.key]) {
const t = r.validation(
s.customFields[r.key]
);
t && (e[r.key] = t);
}
}), re(e), Object.keys(e).length === 0;
}, [s, P, y]), B = i.useCallback(async () => {
if (ae())
try {
ee(!0);
const e = {
firstName: s.firstName,
lastName: s.lastName,
primaryEmailAddress: s.email,
primaryPhoneNumber: s.phone,
username: s.username,
unsafeMetadata: {
...s.metadata,
...s.customFields
}
};
await w(e), L(!1), E?.(s), f?.("Profile updated successfully");
} catch (e) {
const r = e instanceof Error ? e.message : "Failed to update profile";
d?.(r);
} finally {
ee(!1);
}
}, [s, ae, w, E, f, d]), de = i.useCallback(
(e) => {
e.preventDefault(), k ? k(s) : B();
},
[s, k, B]
), pe = i.useCallback(
async (e) => {
if (!x.includes(e.type)) {
d?.("Please select a valid image file (JPEG, PNG, or WebP)");
return;
}
if (e.size > N) {
d?.(
`Image size must be less than ${Math.round(N / 1024 / 1024)}MB`
);
return;
}
try {
F(!0);
let r;
C ? r = await C(e) : r = await S(URL.createObjectURL(e)), p("profileImageUrl", r), f?.("Profile image updated successfully");
} catch (r) {
const t = r instanceof Error ? r.message : "Failed to upload image";
d?.(t);
} finally {
F(!1);
}
},
[
x,
N,
C,
S,
p,
f,
d
]
), fe = i.useCallback(async () => {
try {
F(!0), v ? await v() : await z(), p("profileImageUrl", ""), f?.("Profile image removed successfully");
} catch (e) {
const r = e instanceof Error ? e.message : "Failed to remove image";
d?.(r);
} finally {
F(!1);
}
}, [
v,
z,
p,
f,
d
]), se = i.useRef(null);
return l ? /* @__PURE__ */ m("form", { onSubmit: de, className: `space-y-6 ${Z}`, children: [
K && !h.includes("profileImage") && /* @__PURE__ */ m(G, { variant: o, children: [
/* @__PURE__ */ a(J, { children: /* @__PURE__ */ a("h4", { className: "text-md font-semibold", children: "Profile Picture" }) }),
/* @__PURE__ */ a(O, {}),
/* @__PURE__ */ m(T, { children: [
/* @__PURE__ */ m("div", { className: "flex items-center gap-4", children: [
/* @__PURE__ */ a(
he,
{
src: s.profileImageUrl,
name: `${s.firstName} ${s.lastName}`,
size: "xl",
isBordered: !0,
fallback: I ? /* @__PURE__ */ a(te, { size: "sm" }) : void 0
}
),
/* @__PURE__ */ m("div", { className: "flex flex-col gap-2", children: [
/* @__PURE__ */ m("div", { className: "flex gap-2", children: [
/* @__PURE__ */ a(
W,
{
size: "sm",
variant: "bordered",
onPress: () => se.current?.click(),
isDisabled: c || I,
isLoading: I,
children: "Upload Photo"
}
),
_ && s.profileImageUrl && /* @__PURE__ */ a(
W,
{
size: "sm",
variant: "light",
color: "danger",
onPress: fe,
isDisabled: c || I,
children: "Remove"
}
)
] }),
/* @__PURE__ */ m("p", { className: "text-xs text-default-500", children: [
"JPG, PNG or WebP. Max size",
" ",
Math.round(N / 1024 / 1024),
"MB."
] })
] })
] }),
/* @__PURE__ */ a(
"input",
{
ref: se,
type: "file",
accept: x.join(","),
onChange: (e) => {
const r = e.target.files?.[0];
r && pe(r), e.target.value = "";
},
className: "hidden"
}
)
] })
] }),
/* @__PURE__ */ m(G, { variant: o, children: [
/* @__PURE__ */ a(J, { children: /* @__PURE__ */ a("h4", { className: "text-md font-semibold", children: "Personal Information" }) }),
/* @__PURE__ */ a(O, {}),
/* @__PURE__ */ a(T, { children: /* @__PURE__ */ m(
"div",
{
className: `grid gap-4 ${V === "double" ? "md:grid-cols-2" : "grid-cols-1"}`,
children: [
!h.includes("firstName") && /* @__PURE__ */ a(
A,
{
label: "First Name",
placeholder: "Enter your first name",
value: s.firstName,
onValueChange: (e) => p("firstName", e),
variant: o,
size: u,
isDisabled: c,
isInvalid: !!n.firstName,
errorMessage: n.firstName
}
),
!h.includes("lastName") && /* @__PURE__ */ a(
A,
{
label: "Last Name",
placeholder: "Enter your last name",
value: s.lastName,
onValueChange: (e) => p("lastName", e),
variant: o,
size: u,
isDisabled: c,
isInvalid: !!n.lastName,
errorMessage: n.lastName
}
),
X && !h.includes("username") && /* @__PURE__ */ a(
A,
{
label: "Username",
placeholder: "Enter your username",
value: s.username,
onValueChange: (e) => p("username", e),
variant: o,
size: u,
isDisabled: c,
isInvalid: !!n.username,
errorMessage: n.username
}
),
P && !h.includes("email") && /* @__PURE__ */ a(
ve,
{
label: "Email Address",
value: s.email,
onChange: (e) => p("email", e),
variant: o,
size: u,
disabled: c,
error: n.email,
showVerificationStatus: j,
isVerified: ie
}
),
Q && !h.includes("phone") && /* @__PURE__ */ a(
xe,
{
label: "Phone Number",
value: s.phone,
onChange: (e) => p("phone", e),
variant: o,
size: u,
disabled: c,
error: n.phone,
showVerificationStatus: j,
isVerified: ce
}
)
]
}
) })
] }),
y.length > 0 && /* @__PURE__ */ m(G, { variant: o, children: [
/* @__PURE__ */ a(J, { children: /* @__PURE__ */ a("h4", { className: "text-md font-semibold", children: "Additional Information" }) }),
/* @__PURE__ */ a(O, {}),
/* @__PURE__ */ a(T, { children: /* @__PURE__ */ a(
"div",
{
className: `grid gap-4 ${V === "double" ? "md:grid-cols-2" : "grid-cols-1"}`,
children: y.map((e) => {
const r = s.customFields?.[e.key] || "";
switch (e.type) {
case "textarea":
return /* @__PURE__ */ a(
be,
{
label: e.label,
placeholder: e.placeholder,
description: e.description,
value: r,
onValueChange: (t) => U(e.key, t),
variant: o,
size: u,
isDisabled: c,
isRequired: e.required,
isInvalid: !!n[e.key],
errorMessage: n[e.key]
},
e.key
);
case "select":
return /* @__PURE__ */ a(
ye,
{
label: e.label,
placeholder: e.placeholder,
description: e.description,
selectedKeys: r ? [r] : [],
onSelectionChange: (t) => {
const q = Array.from(t)[0];
U(e.key, q);
},
variant: o,
size: u,
isDisabled: c,
isRequired: e.required,
isInvalid: !!n[e.key],
errorMessage: n[e.key],
children: e.options?.map((t) => /* @__PURE__ */ a(Ne, { value: t.value, children: t.label }, t.value)) || []
},
e.key
);
case "switch":
return /* @__PURE__ */ m(
"div",
{
className: "flex items-center justify-between",
children: [
/* @__PURE__ */ m("div", { children: [
/* @__PURE__ */ m("label", { className: "text-sm font-medium", children: [
e.label,
e.required && /* @__PURE__ */ a("span", { className: "text-danger ml-1", children: "*" })
] }),
e.description && /* @__PURE__ */ a("p", { className: "text-xs text-default-500 mt-1", children: e.description })
] }),
/* @__PURE__ */ a(
ge,
{
isSelected: !!r,
onValueChange: (t) => U(e.key, t),
size: u,
isDisabled: c
}
)
]
},
e.key
);
default:
return /* @__PURE__ */ a(
A,
{
label: e.label,
placeholder: e.placeholder,
description: e.description,
value: r,
onValueChange: (t) => U(e.key, t),
type: e.type,
variant: o,
size: u,
isDisabled: c,
isRequired: e.required,
isInvalid: !!n[e.key],
errorMessage: n[e.key]
},
e.key
);
}
})
}
) })
] }),
H && !g && /* @__PURE__ */ a("div", { className: "flex justify-end gap-3", children: /* @__PURE__ */ a(
W,
{
type: "submit",
color: "primary",
isDisabled: c || !M,
isLoading: ue,
children: "Save Changes"
}
) }),
g && M && /* @__PURE__ */ m("div", { className: "flex items-center gap-2 text-sm text-default-500", children: [
/* @__PURE__ */ a(te, { size: "sm" }),
/* @__PURE__ */ a("span", { children: "Auto-saving..." })
] })
] }) : null;
}
export {
Ve as ProfileForm
};
//# sourceMappingURL=profile-form.js.map