@blocklet/ui-react 2.12.8 → 2.12.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (54) hide show
  1. package/lib/@types/index.d.ts +34 -0
  2. package/lib/@types/index.js +16 -0
  3. package/lib/@types/shims.d.ts +1 -0
  4. package/lib/UserCenter/components/config-profile.js +23 -1
  5. package/lib/UserCenter/components/editable-field.d.ts +22 -0
  6. package/lib/UserCenter/components/editable-field.js +159 -0
  7. package/lib/UserCenter/components/nft.d.ts +4 -0
  8. package/lib/UserCenter/components/nft.js +93 -0
  9. package/lib/UserCenter/components/settings.js +32 -15
  10. package/lib/UserCenter/components/status-selector/duration-menu.d.ts +9 -0
  11. package/lib/UserCenter/components/status-selector/duration-menu.js +75 -0
  12. package/lib/UserCenter/components/status-selector/index.d.ts +9 -0
  13. package/lib/UserCenter/components/status-selector/index.js +39 -0
  14. package/lib/UserCenter/components/status-selector/menu-item.d.ts +24 -0
  15. package/lib/UserCenter/components/status-selector/menu-item.js +24 -0
  16. package/lib/UserCenter/components/user-center.js +119 -122
  17. package/lib/UserCenter/components/user-info/clock.d.ts +4 -0
  18. package/lib/UserCenter/components/user-info/clock.js +23 -0
  19. package/lib/UserCenter/components/user-info/link-preview-input.d.ts +5 -0
  20. package/lib/UserCenter/components/user-info/link-preview-input.js +181 -0
  21. package/lib/UserCenter/components/user-info/metadata.d.ts +7 -0
  22. package/lib/UserCenter/components/user-info/metadata.js +458 -0
  23. package/lib/UserCenter/components/user-info/switch-role.js +2 -3
  24. package/lib/UserCenter/components/user-info/user-basic-info.d.ts +2 -0
  25. package/lib/UserCenter/components/user-info/user-basic-info.js +159 -90
  26. package/lib/UserCenter/components/user-info/user-info.js +2 -16
  27. package/lib/UserCenter/components/user-info/user-status.d.ts +8 -0
  28. package/lib/UserCenter/components/user-info/user-status.js +153 -0
  29. package/lib/UserCenter/components/user-info/utils.d.ts +19 -0
  30. package/lib/UserCenter/components/user-info/utils.js +86 -0
  31. package/lib/UserCenter/libs/locales.d.ts +65 -0
  32. package/lib/UserCenter/libs/locales.js +67 -2
  33. package/lib/UserSessions/components/user-sessions.js +48 -14
  34. package/package.json +8 -5
  35. package/src/@types/index.ts +39 -0
  36. package/src/@types/shims.d.ts +1 -0
  37. package/src/UserCenter/components/config-profile.tsx +20 -1
  38. package/src/UserCenter/components/editable-field.tsx +180 -0
  39. package/src/UserCenter/components/nft.tsx +122 -0
  40. package/src/UserCenter/components/settings.tsx +16 -4
  41. package/src/UserCenter/components/status-selector/duration-menu.tsx +87 -0
  42. package/src/UserCenter/components/status-selector/index.tsx +52 -0
  43. package/src/UserCenter/components/status-selector/menu-item.tsx +52 -0
  44. package/src/UserCenter/components/user-center.tsx +104 -103
  45. package/src/UserCenter/components/user-info/clock.tsx +29 -0
  46. package/src/UserCenter/components/user-info/link-preview-input.tsx +227 -0
  47. package/src/UserCenter/components/user-info/metadata.tsx +465 -0
  48. package/src/UserCenter/components/user-info/switch-role.tsx +3 -3
  49. package/src/UserCenter/components/user-info/user-basic-info.tsx +150 -87
  50. package/src/UserCenter/components/user-info/user-info.tsx +6 -16
  51. package/src/UserCenter/components/user-info/user-status.tsx +182 -0
  52. package/src/UserCenter/components/user-info/utils.ts +114 -0
  53. package/src/UserCenter/libs/locales.ts +65 -0
  54. package/src/UserSessions/components/user-sessions.tsx +68 -18
@@ -0,0 +1,458 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ import { createElement } from "react";
3
+ import Box from "@mui/material/Box";
4
+ import MenuItem from "@mui/material/MenuItem";
5
+ import Select from "@mui/material/Select";
6
+ import useMediaQuery from "@mui/material/useMediaQuery";
7
+ import SwipeableDrawer from "@mui/material/SwipeableDrawer";
8
+ import Backdrop from "@mui/material/Backdrop";
9
+ import styled from "@emotion/styled";
10
+ import { joinURL } from "ufo";
11
+ import Button from "@arcblock/ux/lib/Button";
12
+ import cloneDeep from "lodash/cloneDeep";
13
+ import { temp as colors } from "@arcblock/ux/lib/Colors";
14
+ import { useCreation, useMemoizedFn, useReactive } from "ahooks";
15
+ import { useMemo, useRef, useState, memo, forwardRef, useEffect, lazy } from "react";
16
+ import { translate } from "@arcblock/ux/lib/Locale/util";
17
+ import isEmail from "validator/lib/isEmail";
18
+ import isMobilePhone from "validator/lib/isMobilePhone";
19
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
20
+ import { useBrowser } from "@arcblock/react-hooks";
21
+ import { translations } from "../../libs/locales.js";
22
+ import EditableField from "../editable-field.js";
23
+ import { LinkPreviewInput } from "./link-preview-input.js";
24
+ import { getTimezones } from "./utils.js";
25
+ import Clock from "./clock.js";
26
+ const LocationIcon = lazy(() => import("@arcblock/icons/lib/Location"));
27
+ const TimezoneIcon = lazy(() => import("@arcblock/icons/lib/Timezone"));
28
+ const EmailIcon = lazy(() => import("@arcblock/icons/lib/Email"));
29
+ const PhoneIcon = lazy(() => import("@arcblock/icons/lib/Phone"));
30
+ const ArrowDownwardIcon = lazy(() => import("@arcblock/icons/lib/ArrowDown"));
31
+ const defaultButtonStyle = {
32
+ color: colors.foregroundsFgBase,
33
+ borderColor: colors.strokeBorderBase,
34
+ backgroundColor: colors.buttonsButtonNeutral,
35
+ "&:hover": {
36
+ borderColor: colors.strokeBorderBase,
37
+ backgroundColor: colors.buttonsButtonNeutralHover
38
+ },
39
+ py: 0.5,
40
+ borderRadius: 2
41
+ };
42
+ const primaryButtonStyle = {
43
+ color: colors.buttonsButtonNeutral,
44
+ borderColor: colors.foregroundsFgInteractive,
45
+ backgroundColor: colors.foregroundsFgInteractive,
46
+ "&:hover": {
47
+ borderColor: colors.foregroundsFgInteractive,
48
+ backgroundColor: colors.foregroundsFgInteractive
49
+ },
50
+ py: 0.5,
51
+ borderRadius: 2
52
+ };
53
+ const iconSize = {
54
+ width: 20,
55
+ height: 20
56
+ };
57
+ const bioMaxLength = 200;
58
+ const BackdropWrap = memo(
59
+ forwardRef(function BackdropWrap2(backdropProps, ref) {
60
+ return /* @__PURE__ */ createElement(
61
+ Backdrop,
62
+ {
63
+ ref,
64
+ component: "div",
65
+ style: {
66
+ backgroundColor: "rgba(0, 0, 0, 0.6)",
67
+ backdropFilter: "blur(3px)",
68
+ touchAction: "none"
69
+ },
70
+ ...backdropProps,
71
+ key: "background"
72
+ }
73
+ );
74
+ })
75
+ );
76
+ BackdropWrap.displayName = "BackdropWrap";
77
+ export default function UserMetadataComponent({
78
+ isMyself,
79
+ user,
80
+ onSave
81
+ }) {
82
+ const [editable, setEditable] = useState(false);
83
+ const [visible, setVisible] = useState(false);
84
+ const drawerDragger = useRef(null);
85
+ const browser = useBrowser();
86
+ const isMobileView = useMediaQuery("(max-width:640px)") || browser.mobile.any;
87
+ const validateMsg = useReactive({
88
+ email: "",
89
+ phone: ""
90
+ });
91
+ useEffect(() => {
92
+ if (!isMobileView) {
93
+ setVisible(false);
94
+ }
95
+ }, [isMobileView]);
96
+ const phoneVerified = useCreation(() => {
97
+ return user?.phoneVerified ?? false;
98
+ }, [user?.phoneVerified]);
99
+ const emailVerified = useCreation(() => {
100
+ return user?.emailVerified ?? false;
101
+ }, [user?.emailVerified]);
102
+ const { locale } = useLocaleContext();
103
+ const t = useMemoizedFn((key, data = {}) => {
104
+ return translate(translations, key, locale, "en", data);
105
+ });
106
+ const metadata = useReactive(
107
+ user?.metadata ? cloneDeep(user.metadata) : {
108
+ joinedAt: user?.createdAt,
109
+ email: user?.email,
110
+ phone: user?.phone
111
+ }
112
+ );
113
+ const onChange = (v, field) => {
114
+ metadata[field] = v;
115
+ };
116
+ const timezones = getTimezones();
117
+ const onEdit = () => {
118
+ if (!isMobileView) {
119
+ setEditable(true);
120
+ } else {
121
+ setVisible(true);
122
+ }
123
+ };
124
+ const onCancel = () => {
125
+ const defaultMetadata = cloneDeep(user?.metadata) ?? {};
126
+ if (defaultMetadata) {
127
+ Object.keys(metadata).forEach((key) => {
128
+ const k = key;
129
+ metadata[k] = defaultMetadata[k];
130
+ });
131
+ }
132
+ validateMsg.email = "";
133
+ validateMsg.phone = "";
134
+ if (!isMobileView) {
135
+ setEditable(false);
136
+ } else {
137
+ setVisible(false);
138
+ }
139
+ };
140
+ const links = useMemo(() => {
141
+ const defaultLinks = metadata?.links?.map((link) => link.url) || [""];
142
+ return defaultLinks.length > 0 ? defaultLinks : [""];
143
+ }, [metadata.links]);
144
+ const handleLinksChange = (values) => {
145
+ const newLinks = values.map((link) => {
146
+ if (!link) {
147
+ return {
148
+ url: link
149
+ };
150
+ }
151
+ const targetLink = metadata.links?.find((l) => l.url === link);
152
+ if (targetLink) {
153
+ return {
154
+ ...targetLink,
155
+ url: link
156
+ };
157
+ }
158
+ return {
159
+ url: link,
160
+ favicon: joinURL(link, "favicon.ico")
161
+ };
162
+ });
163
+ onChange(newLinks, "links");
164
+ };
165
+ const handleSave = () => {
166
+ Object.keys(metadata).forEach((key) => {
167
+ const k = key;
168
+ const value = metadata[k];
169
+ if (value && typeof value === "string") {
170
+ metadata[k] = value.trim();
171
+ }
172
+ if (k === "bio") {
173
+ metadata[k] = metadata[k]?.slice(0, bioMaxLength);
174
+ }
175
+ });
176
+ onSave(metadata);
177
+ setEditable(false);
178
+ setVisible(false);
179
+ };
180
+ const renderEdit = (editing, mode = "self") => {
181
+ return /* @__PURE__ */ jsxs(MetadataInfo, { pt: 2, display: "flex", flexDirection: "column", children: [
182
+ /* @__PURE__ */ jsx(
183
+ EditableField,
184
+ {
185
+ value: metadata.bio ?? "",
186
+ onChange: (value) => onChange(value, "bio"),
187
+ editable: editing,
188
+ placeholder: "Bio",
189
+ component: "textarea",
190
+ inline: false,
191
+ rows: 3,
192
+ maxLength: bioMaxLength,
193
+ style: {
194
+ ...editing ? { marginBottom: 8 } : {}
195
+ }
196
+ }
197
+ ),
198
+ !editing && isMyself ? /* @__PURE__ */ jsx(
199
+ Button,
200
+ {
201
+ size: "large",
202
+ variant: "outlined",
203
+ sx: {
204
+ ...defaultButtonStyle,
205
+ mb: 2,
206
+ mt: 2,
207
+ height: "40px"
208
+ },
209
+ onClick: onEdit,
210
+ fullWidth: true,
211
+ children: t("profile.editProfile")
212
+ }
213
+ ) : null,
214
+ /* @__PURE__ */ jsx(
215
+ EditableField,
216
+ {
217
+ value: metadata.location ?? "",
218
+ onChange: (value) => onChange(value, "location"),
219
+ editable: editing,
220
+ placeholder: "Location",
221
+ label: t("profile.location"),
222
+ icon: /* @__PURE__ */ jsx(LocationIcon, { ...iconSize })
223
+ }
224
+ ),
225
+ /* @__PURE__ */ jsx(
226
+ EditableField,
227
+ {
228
+ value: metadata.timezone ?? "",
229
+ onChange: (value) => onChange(value, "timezone"),
230
+ editable: editing,
231
+ placeholder: "timezone",
232
+ icon: /* @__PURE__ */ jsx(TimezoneIcon, { ...iconSize }),
233
+ label: t("profile.timezone"),
234
+ tooltip: /* @__PURE__ */ jsxs("p", { style: { display: "flex", margin: 0 }, children: [
235
+ /* @__PURE__ */ jsx("span", { style: { marginRight: "4px" }, children: t("profile.localTime") }),
236
+ /* @__PURE__ */ jsx(Clock, { timezone: metadata.timezone, locale })
237
+ ] }),
238
+ children: /* @__PURE__ */ jsx(
239
+ Select,
240
+ {
241
+ className: `timezone-select ${editing ? "" : "disabled"}`,
242
+ value: metadata.timezone,
243
+ onChange: (e) => onChange(e.target.value, "timezone"),
244
+ disabled: !editing,
245
+ displayEmpty: true,
246
+ variant: "outlined",
247
+ placeholder: "Timezone",
248
+ IconComponent: (props) => /* @__PURE__ */ jsx(ArrowDownwardIcon, { ...props, width: 20, height: 20 }),
249
+ MenuProps: {
250
+ style: {
251
+ zIndex: mode === "drawer" ? 9999 : 1300
252
+ }
253
+ },
254
+ sx: {
255
+ width: "100%",
256
+ "&:hover": {
257
+ "fieldset.MuiOutlinedInput-notchedOutline": {
258
+ borderColor: colors.dividerColor
259
+ }
260
+ },
261
+ fieldset: {
262
+ borderColor: colors.dividerColor
263
+ }
264
+ },
265
+ children: timezones.map((tz) => /* @__PURE__ */ jsx(MenuItem, { value: tz.value, children: tz.label }, tz.value))
266
+ }
267
+ )
268
+ }
269
+ ),
270
+ /* @__PURE__ */ jsx(
271
+ EditableField,
272
+ {
273
+ value: metadata.email ?? user?.email ?? "",
274
+ editable: editing && !emailVerified,
275
+ verified: emailVerified,
276
+ placeholder: "Email",
277
+ icon: /* @__PURE__ */ jsx(EmailIcon, { ...iconSize }),
278
+ label: t("profile.email"),
279
+ onChange: (value) => onChange(value, "email"),
280
+ errorMsg: validateMsg.email,
281
+ onValueValidate: (value) => {
282
+ let msg = "";
283
+ if (!!value && !isEmail(value)) {
284
+ msg = t("profile.emailInvalid");
285
+ }
286
+ validateMsg.email = msg;
287
+ }
288
+ }
289
+ ),
290
+ /* @__PURE__ */ jsx(
291
+ EditableField,
292
+ {
293
+ value: metadata.phone ?? user?.phone ?? "",
294
+ editable: editing && !phoneVerified,
295
+ verified: phoneVerified,
296
+ placeholder: "Phone",
297
+ icon: /* @__PURE__ */ jsx(PhoneIcon, { ...iconSize }),
298
+ onChange: (value) => onChange(value, "phone"),
299
+ label: t("profile.phone"),
300
+ errorMsg: validateMsg.phone,
301
+ onValueValidate: (value) => {
302
+ let msg = "";
303
+ if (!!value && !isMobilePhone(value)) {
304
+ msg = t("profile.phoneInvalid");
305
+ }
306
+ validateMsg.phone = msg;
307
+ }
308
+ }
309
+ ),
310
+ /* @__PURE__ */ jsx(LinkPreviewInput, { editable: editing, links, onChange: handleLinksChange }),
311
+ editing && isMyself ? /* @__PURE__ */ jsxs(
312
+ Box,
313
+ {
314
+ display: "flex",
315
+ gap: 1,
316
+ style: { width: "100%" },
317
+ justifyContent: "flex-end",
318
+ flexDirection: mode === "drawer" ? "column" : "row",
319
+ children: [
320
+ /* @__PURE__ */ jsx(
321
+ Button,
322
+ {
323
+ fullWidth: mode === "drawer",
324
+ size: "small",
325
+ variant: "outlined",
326
+ sx: { ...defaultButtonStyle, minWidth: "54px" },
327
+ onClick: onCancel,
328
+ children: t("common.cancel")
329
+ }
330
+ ),
331
+ /* @__PURE__ */ jsx(
332
+ Button,
333
+ {
334
+ fullWidth: mode === "drawer",
335
+ size: "small",
336
+ disabled: !!validateMsg.email || !!validateMsg.phone,
337
+ variant: "outlined",
338
+ sx: {
339
+ ...primaryButtonStyle,
340
+ minWidth: "54px",
341
+ "&.Mui-disabled": {
342
+ backgroundColor: "rgba(0, 0, 0, 0.12)"
343
+ }
344
+ },
345
+ onClick: handleSave,
346
+ children: t("common.save")
347
+ }
348
+ )
349
+ ]
350
+ }
351
+ ) : null
352
+ ] });
353
+ };
354
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
355
+ renderEdit(editable),
356
+ isMobileView && /* @__PURE__ */ jsxs(
357
+ SwipeableDrawer,
358
+ {
359
+ sx: {
360
+ zIndex: 9999
361
+ },
362
+ disableSwipeToOpen: true,
363
+ onOpen: onEdit,
364
+ open: visible,
365
+ anchor: "bottom",
366
+ onClose: onCancel,
367
+ slots: {
368
+ backdrop: BackdropWrap
369
+ },
370
+ PaperProps: {
371
+ sx: {
372
+ boxShadow: "0px -2px 16px 0px rgba(0, 0, 0, 0.08)",
373
+ borderRadius: 3,
374
+ // 保持跟 DID Wallet 一致
375
+ borderBottomLeftRadius: 0,
376
+ borderBottomRightRadius: 0
377
+ }
378
+ },
379
+ children: [
380
+ /* @__PURE__ */ jsx(
381
+ Box,
382
+ {
383
+ ref: drawerDragger,
384
+ sx: {
385
+ padding: "16px 32px",
386
+ margin: "-8px auto -16px",
387
+ zIndex: 1
388
+ },
389
+ children: /* @__PURE__ */ jsx(
390
+ Box,
391
+ {
392
+ sx: {
393
+ width: "48px",
394
+ height: "4px",
395
+ borderRadius: "100vw",
396
+ backgroundColor: "rgba(0, 0, 0, 0.2)"
397
+ }
398
+ }
399
+ )
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsx(
403
+ Box,
404
+ {
405
+ p: 2,
406
+ sx: {
407
+ maxHeight: "500px",
408
+ overflowY: "auto"
409
+ },
410
+ children: renderEdit(true, "drawer")
411
+ }
412
+ )
413
+ ]
414
+ }
415
+ )
416
+ ] });
417
+ }
418
+ const MetadataInfo = styled(Box)`
419
+ gap: 16px;
420
+ justify-content: space-between;
421
+ align-items: flex-start;
422
+ width: 100%;
423
+
424
+ .MuiOutlinedInput-root {
425
+ padding: 8px;
426
+ .MuiOutlinedInput-input {
427
+ padding: 0;
428
+ }
429
+ }
430
+ .timezone-select {
431
+ min-width: 150px;
432
+ &.disabled {
433
+ padding: 4px 8px;
434
+ fieldset {
435
+ border: unset;
436
+ }
437
+ svg {
438
+ display: none;
439
+ }
440
+ }
441
+ }
442
+ .info-row {
443
+ display: flex;
444
+ flex-direction: row;
445
+ align-items: center;
446
+ justify-content: center;
447
+ gap: 4px;
448
+ margin: 0;
449
+ p {
450
+ display: flex;
451
+ align-items: center;
452
+ margin: 0;
453
+ font-size: 16px;
454
+ font-weight: 400;
455
+ color: #666;
456
+ }
457
+ }
458
+ `;
@@ -1,9 +1,8 @@
1
1
  import { jsx } from "react/jsx-runtime";
2
2
  import { memo } from "react";
3
3
  import { Chip } from "@mui/material";
4
- import { Icon } from "@iconify/react";
5
4
  import { useCreation } from "ahooks";
6
- import SwapHorizRoundedIcon from "@iconify-icons/material-symbols/swap-horiz-rounded";
5
+ import SwitchIcon from "@arcblock/icons/lib/Switch";
7
6
  import { temp as colors } from "@arcblock/ux/lib/Colors";
8
7
  function SwitchRole({ user, switchPassport }) {
9
8
  const currentRole = useCreation(
@@ -34,7 +33,7 @@ function SwitchRole({ user, switchPassport }) {
34
33
  }
35
34
  },
36
35
  clickable: true,
37
- deleteIcon: /* @__PURE__ */ jsx(Icon, { icon: SwapHorizRoundedIcon, color: colors.textBase }),
36
+ deleteIcon: /* @__PURE__ */ jsx(SwitchIcon, { color: colors.textBase, width: 20, height: 20 }),
38
37
  onDelete: switchPassport,
39
38
  onClick: switchPassport
40
39
  }
@@ -6,4 +6,6 @@ export default function UserBasicInfo({ user, isMyself, showFullDid, switchPassp
6
6
  showFullDid?: boolean;
7
7
  switchPassport: () => void;
8
8
  switchProfile: () => void;
9
+ size?: number;
10
+ isMobile?: boolean;
9
11
  } & BoxProps): import("react").JSX.Element | null;