@blocklet/ui-react
Version:
Some useful front-end web components that can be used in Blocklets.
506 lines (504 loc) • 15 kB
JavaScript
import { jsxs as y, Fragment as B, jsx as t } from "react/jsx-runtime";
import { useState as R, useRef as te, useEffect as re, useMemo as D, memo as ie, createElement as ae } from "react";
import { Icon as le } from "@iconify/react";
import { useMediaQuery as de, SwipeableDrawer as ce, Box as m, Typography as L, Tooltip as se, Backdrop as pe } from "@mui/material";
import me from "@emotion/styled";
import N from "@arcblock/ux/lib/Button";
import F, { detectCountryFromPhone as T, getCountryNameByCountry as ue, validatePhoneNumber as he, getDialCodeByCountry as fe } from "@arcblock/ux/lib/PhoneInput";
import v from "lodash/cloneDeep";
import ye from "lodash/omit";
import { mergeSx as xe } from "@arcblock/ux/lib/Util/style";
import { LOGIN_PROVIDER as M } from "@arcblock/ux/lib/Util/constant";
import { useReactive as w, useCreation as k, useMemoizedFn as be } from "ahooks";
import { translate as ge } from "@arcblock/ux/lib/Locale/util";
import Ce from "validator/lib/isEmail";
import $ from "validator/lib/isPostalCode";
import { useLocaleContext as ve } from "@arcblock/ux/lib/Locale/context";
import { useBrowser as we } from "@arcblock/react-hooks";
import ke from "@arcblock/ux/lib/UserCard/Content/clock";
import Ee from "@arcblock/icons/lib/Email";
import Ie from "@arcblock/icons/lib/Phone";
import Pe from "@arcblock/icons/lib/Timezone";
import ze from "@arcblock/icons/lib/Location";
import { translations as Ve } from "../../libs/locales.js";
import g, { inputFieldStyle as Be, commonInputStyle as Le } from "../editable-field.js";
import { LinkPreviewInput as Ne } from "./link-preview-input.js";
import { defaultButtonStyle as G, primaryButtonStyle as je } from "./utils.js";
import { TimezoneSelect as Ae } from "./timezone-select.js";
import Se from "./address.js";
const E = {
width: 20,
height: 20
}, H = 200, Q = ie(function({
ref: r,
...I
}) {
return /* @__PURE__ */ ae(
pe,
{
ref: r,
component: "div",
style: {
backgroundColor: "rgba(0, 0, 0, 0.6)",
backdropFilter: "blur(3px)",
touchAction: "none"
},
...I,
key: "background"
}
);
});
Q.displayName = "BackdropWrap";
function mo({
isMyself: s,
user: r,
onSave: I,
isMobile: x
}) {
const [U, P] = R(!1), [Y, C] = R(!1), _ = te(null), q = we(), b = de("(max-width:640px)") || q.mobile.any, c = w({
email: "",
phone: ""
}), u = w({
country: "",
province: "",
city: "",
line1: "",
line2: "",
postalCode: ""
});
re(() => {
b || C(!1);
}, [b]);
const j = k(() => r?.phoneVerified ?? !1, [r?.phoneVerified]), A = k(() => r?.emailVerified ?? !1, [r?.emailVerified]), { locale: z } = ve(), d = be((o, n = {}) => ge(Ve, o, z, "en", n)), a = w(
r?.metadata ? v(r.metadata) : {
joinedAt: r?.createdAt,
email: r?.email,
phone: {
country: "cn",
phoneNumber: r?.phone ?? ""
}
}
), l = w(
r?.address ? v(r.address) : {
country: "",
province: "",
city: "",
line1: "",
line2: "",
postalCode: ""
}
), J = k(() => !!r?.address, [r?.address?.country]), h = D(() => r?.address?.country ? r.address.country : z === "zh" ? "cn" : "us", [r?.address?.country, z]), V = k(() => {
const o = a.phone ?? r?.phone ?? {
country: h,
phone: ""
};
if (o && typeof o == "string")
return {
country: T(o) || h,
phone: o
};
if (o && typeof o == "object") {
const { country: n = "", phoneNumber: e = "" } = o;
return {
country: n || T(e) || "",
phone: e || ""
};
}
return {
country: h,
phone: ""
};
}, [a.phone, r?.phone, h]), p = (o, n) => {
a[n] = o;
}, K = (o, n) => {
l[o] = n, o === "city" && p(n, "location"), o === "postalCode" ? u.postalCode = n && !$(n, "any") ? d("profile.address.invalidPostalCode") : "" : u[o] = "";
}, S = () => {
b ? C(!0) : P(!0);
}, O = () => {
const o = v(r?.metadata) ?? {};
o && Object.keys(a).forEach((e) => {
const i = e;
a[i] = o[i];
});
const n = v(r?.address) ?? {};
n && Object.keys(l).forEach((e) => {
const i = e;
l[i] = n[i];
}), [c, u].forEach((e) => {
Object.keys(e).forEach((i) => {
e[i] = "";
});
}), b ? C(!1) : P(!1);
}, X = D(() => {
const o = a?.links?.map((n) => n.url) || [""];
return o.length > 0 ? o : [""];
}, [a.links]), Z = (o) => {
const n = o.map((e) => {
if (!e)
return {
url: e
};
const i = a.links?.find((f) => f.url === e);
return i ? {
...i,
url: e
} : {
url: e,
favicon: e
};
});
p(n, "links");
}, ee = () => {
if (Object.keys(a).forEach((n) => {
const e = n, i = a[e];
if (i && typeof i == "string" && (a[e] = i.trim()), e === "bio" && (a[e] = a[e]?.slice(0, H)), e === "timezone" && (a[e] = i || ""), e === "phone" && i && typeof i == "object") {
const f = i, oe = fe(f.country), ne = (f.phoneNumber?.replace(new RegExp(`^\\+${oe}`), "") || "")?.trim().length > 0;
a[e] = {
country: f.country,
...ne ? { phoneNumber: f.phoneNumber } : {}
};
}
}), l.postalCode && !$(l.postalCode, "any") && (u.postalCode = d("profile.address.invalidPostalCode")), [c, u].some((n) => Object.values(n).some((e) => e)))
return;
const o = ye(l, "detailedAddress");
o.country || (o.country = h), I({ metadata: a, address: o }), P(!1), C(!1);
}, W = (o, n = "self") => /* @__PURE__ */ y(
Oe,
{
pt: 2,
display: "flex",
flexDirection: "column",
justifyContent: "space-between",
alignItems: "flex-start",
gap: x ? "4px" : "16px",
children: [
/* @__PURE__ */ t(
g,
{
value: a.bio ?? "",
onChange: (e) => p(e, "bio"),
editable: o,
placeholder: "Bio",
component: "textarea",
inline: !1,
rows: 3,
label: d("profile.bio"),
maxLength: H,
style: {
...o ? { marginBottom: 8 } : {}
}
}
),
!o && s ? /* @__PURE__ */ t(
N,
{
size: x ? "small" : "large",
variant: "outlined",
sx: {
...G,
mb: x ? "4px" : 2,
mt: x ? "4px" : 2,
height: x ? "32px" : "40px"
},
onClick: S,
fullWidth: !0,
children: d("profile.editProfile")
}
) : null,
o && s && J ? /* @__PURE__ */ t(
Se,
{
address: l,
errors: u,
handleChange: K,
defaultCountry: h
}
) : /* @__PURE__ */ t(
g,
{
value: a.location ?? r?.address?.city ?? "",
onChange: (e) => p(e, "location"),
editable: o,
placeholder: "Location",
label: d("profile.location"),
tooltip: s && (l.line1 || l.line2) ? /* @__PURE__ */ t(B, { children: ["line1", "line2"].map((e) => l[e] ? /* @__PURE__ */ y(m, { children: [
/* @__PURE__ */ t(
L,
{
variant: "caption",
component: "p",
sx: {
fontWeight: 600,
mb: 0
},
children: d(`profile.address.${e}`)
}
),
/* @__PURE__ */ t(L, { variant: "caption", component: "span", children: l[e] })
] }, e) : null) }) : null,
renderValue: () => {
const i = [l?.country ? ue(l?.country) : "", l.province, l.city || a.location || ""].filter(Boolean).join(" ");
return /* @__PURE__ */ t(L, { component: "span", children: i });
},
icon: /* @__PURE__ */ t(ze, { ...E })
}
),
/* @__PURE__ */ t(
g,
{
value: a.timezone || "",
onChange: (e) => p(e, "timezone"),
editable: o,
placeholder: "timezone",
icon: /* @__PURE__ */ t(Pe, { ...E }),
label: d("profile.timezone"),
renderValue: (e) => /* @__PURE__ */ t(ke, { value: e }),
children: /* @__PURE__ */ t(
Ae,
{
value: a.timezone ?? "",
onChange: (e) => p(e, "timezone"),
disabled: !o,
mode: n
}
)
}
),
/* @__PURE__ */ t(
g,
{
value: a.email ?? r?.email ?? "",
editable: o,
hidePreview: !s,
disabled: r?.sourceProvider === M.EMAIL,
canEdit: !A,
verified: A,
placeholder: "Email",
icon: /* @__PURE__ */ t(Ee, { ...E }),
label: /* @__PURE__ */ y(B, { children: [
d("profile.email"),
r?.sourceProvider === M.EMAIL ? /* @__PURE__ */ t(se, { title: d("profile.emailSourceProviderNotAllowEdit"), children: /* @__PURE__ */ t(
m,
{
component: le,
icon: "mdi:info-outline",
sx: {
verticalAlign: "middle",
ml: 0.25
}
}
) }) : null
] }),
onChange: (e) => p(e, "email"),
errorMsg: c.email,
renderValue: (e) => s ? /* @__PURE__ */ t(
"a",
{
href: `mailto:${e}`,
style: {
color: "inherit",
textDecoration: "none"
},
children: e
}
) : null,
onValueValidate: (e) => {
let i = "";
e && !Ce(e) && (i = d("profile.emailInvalid")), c.email = i;
}
}
),
/* @__PURE__ */ t(
g,
{
value: V.phone,
editable: o,
hidePreview: !s,
canEdit: !j,
verified: j,
placeholder: "Phone",
icon: /* @__PURE__ */ t(Ie, { ...E }),
onChange: (e) => p(e, "phone"),
label: d("profile.phone"),
renderValue: () => s ? /* @__PURE__ */ t(F, { value: V, preview: !0 }) : null,
children: /* @__PURE__ */ t(
F,
{
variant: "outlined",
className: "editable-field",
slotProps: {
input: {
sx: { backgroundColor: "transparent" },
placeholder: "Phone"
}
},
value: V,
error: !!c.phone,
helperText: c.phone,
sx: xe(Be, c.phone ? {} : Le),
onChange: (e) => {
he(e.phone, e.country) ? c.phone = "" : c.phone = d("profile.phoneInvalid"), p(
{
country: e.country,
phoneNumber: e.phone
},
"phone"
);
}
}
)
}
),
/* @__PURE__ */ t(Ne, { editable: o, links: X, onChange: Z }),
o && s ? /* @__PURE__ */ y(
m,
{
style: { width: "100%" },
sx: {
display: "flex",
gap: 1,
justifyContent: "flex-end",
flexDirection: n === "drawer" ? "column" : "row"
},
children: [
/* @__PURE__ */ t(
N,
{
fullWidth: n === "drawer",
size: "small",
variant: "outlined",
sx: { ...G, minWidth: "54px" },
onClick: O,
children: d("common.cancel")
}
),
/* @__PURE__ */ t(
N,
{
fullWidth: n === "drawer",
size: "small",
disabled: !!c.email || !!c.phone,
variant: "outlined",
sx: {
...je,
minWidth: "54px",
"&.Mui-disabled": {
backgroundColor: "rgba(0, 0, 0, 0.12)"
}
},
onClick: ee,
children: d("common.save")
}
)
]
}
) : null
]
}
);
return /* @__PURE__ */ y(B, { children: [
W(U),
b && /* @__PURE__ */ y(
ce,
{
sx: {
zIndex: 9999
},
disableSwipeToOpen: !0,
onOpen: S,
open: Y,
anchor: "bottom",
onClose: O,
slots: {
backdrop: Q
},
slotProps: {
paper: {
sx: {
boxShadow: "0px -2px 16px 0px rgba(0, 0, 0, 0.08)",
borderRadius: 1.5,
// 保持跟 DID Wallet 一致
borderBottomLeftRadius: 0,
borderBottomRightRadius: 0
}
}
},
children: [
/* @__PURE__ */ t(
m,
{
ref: _,
sx: {
padding: "16px 32px",
margin: "-8px auto -16px",
zIndex: 1
},
children: /* @__PURE__ */ t(
m,
{
sx: {
width: "48px",
height: "4px",
borderRadius: "100vw",
backgroundColor: "rgba(0, 0, 0, 0.2)"
}
}
)
}
),
/* @__PURE__ */ t(
m,
{
sx: {
p: 2,
maxHeight: "500px",
overflowY: "auto"
},
children: W(!0, "drawer")
}
)
]
}
)
] });
}
const Oe = me(m)`
width: 100%;
.MuiOutlinedInput-root {
padding: 8px;
.MuiOutlinedInput-input {
padding: 0;
}
}
.timezone-select {
min-width: 150px;
&.disabled {
padding: 4px 8px;
fieldset {
border: unset;
}
svg {
display: none;
}
}
}
.info-row {
display: flex;
flex-direction: row;
align-items: center;
justify-content: center;
gap: 4px;
margin: 0;
p {
display: flex;
align-items: center;
margin: 0;
font-size: 16px;
font-weight: 400;
color: #666;
}
}
`;
export {
mo as default
};