UNPKG

@blocklet/ui-react

Version:

Some useful front-end web components that can be used in Blocklets.

506 lines (504 loc) 15 kB
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 };