@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
@@ -1,20 +1,23 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
- import { Box, CircularProgress, Tooltip, Typography } from "@mui/material";
2
+ import { Box, Divider, Typography } from "@mui/material";
3
3
  import Avatar from "@arcblock/ux/lib/Avatar";
4
- import { useTheme } from "@arcblock/ux/lib/Theme";
5
4
  import DID from "@arcblock/ux/lib/DID";
6
5
  import { useMemoizedFn } from "ahooks";
7
6
  import { translate } from "@arcblock/ux/lib/Locale/util";
8
7
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
9
8
  import noop from "lodash/noop";
10
- import RefreshOutlinedIcon from "@mui/icons-material/RefreshOutlined";
11
- import { SessionContext } from "@arcblock/did-connect/lib/Session";
12
- import { useContext, useState } from "react";
9
+ import { useEffect, useState } from "react";
13
10
  import Toast from "@arcblock/ux/lib/Toast";
14
- import isEmpty from "lodash/isEmpty";
11
+ import { temp as colors } from "@arcblock/ux/lib/Colors";
12
+ import { parseURL, joinURL } from "ufo";
15
13
  import { translations } from "../../libs/locales.js";
16
14
  import { formatAxiosError } from "../../libs/utils.js";
15
+ import { getStatusDuration, isValidUrl } from "./utils.js";
17
16
  import SwitchRole from "./switch-role.js";
17
+ import UserMetadataComponent from "./metadata.js";
18
+ import UserStatus from "./user-status.js";
19
+ import UserInfo from "./user-info.js";
20
+ import { client } from "../../../libs/client.js";
18
21
  export default function UserBasicInfo({
19
22
  user,
20
23
  isMyself = true,
@@ -24,27 +27,62 @@ export default function UserBasicInfo({
24
27
  ...rest
25
28
  }) {
26
29
  const { locale } = useLocaleContext();
27
- const { session } = useContext(SessionContext);
28
- const [loading, setLoading] = useState(false);
30
+ const [userStatus, setUserStatus] = useState(void 0);
29
31
  const t = useMemoizedFn((key, data = {}) => {
30
32
  return translate(translations, key, locale, "en", data);
31
33
  });
32
- const theme = useTheme();
33
- const isSmallView = theme.breakpoints.down("md");
34
+ useEffect(() => {
35
+ setUserStatus(user?.metadata?.status);
36
+ }, [user]);
37
+ const onUpdateUserStatus = async (v) => {
38
+ if (!isMyself) {
39
+ return;
40
+ }
41
+ try {
42
+ if (v) {
43
+ const dateRange = getStatusDuration(v);
44
+ v.dateRange = dateRange.length > 0 ? dateRange : userStatus?.dateRange ?? [];
45
+ }
46
+ setUserStatus(v);
47
+ await client.user.saveProfile({
48
+ // @ts-ignore
49
+ metadata: {
50
+ ...user?.metadata ?? { joinedAt: user?.createdAt, email: user?.email, phone: user?.phone },
51
+ status: v
52
+ }
53
+ });
54
+ } catch (err) {
55
+ console.error(err);
56
+ Toast.error(formatAxiosError(err));
57
+ }
58
+ };
34
59
  if (!user) {
35
60
  return null;
36
61
  }
37
- const refreshProfile = async () => {
62
+ const onSave = async (v) => {
63
+ if (!isMyself) {
64
+ return;
65
+ }
38
66
  try {
39
- setLoading(true);
40
- await session.refreshProfile();
41
- session.refresh();
42
- Toast.success(translate(translations, "refreshProfile.successfully", locale));
67
+ const newLinks = v?.links?.map((link) => {
68
+ if (!link.url || !isValidUrl(link.url))
69
+ return null;
70
+ try {
71
+ const parsedUrl = parseURL(link.url);
72
+ if (!parsedUrl.protocol) {
73
+ link.url = joinURL("https://", link.url);
74
+ }
75
+ return link;
76
+ } catch (e) {
77
+ console.error("Invalid URL:", link.url);
78
+ return null;
79
+ }
80
+ }).filter((l) => !!l) || [];
81
+ v.links = newLinks;
82
+ await client.user.saveProfile({ metadata: v });
43
83
  } catch (err) {
44
84
  console.error(err);
45
85
  Toast.error(formatAxiosError(err));
46
- } finally {
47
- setLoading(false);
48
86
  }
49
87
  };
50
88
  return /* @__PURE__ */ jsxs(
@@ -53,83 +91,114 @@ export default function UserBasicInfo({
53
91
  ...rest,
54
92
  sx: {
55
93
  position: "relative",
56
- ...rest.sx
94
+ ...rest.sx ?? {}
57
95
  },
58
96
  children: [
59
- /* @__PURE__ */ jsx(
60
- Avatar,
61
- {
62
- src: user?.avatar,
63
- did: user?.did,
64
- size: isSmallView ? 64 : 80,
65
- variant: "circle",
66
- shape: "circle",
67
- sx: {
68
- borderRadius: "100%",
69
- backgroundColor: "#fff",
97
+ /* @__PURE__ */ jsxs(Box, { className: "user-info", display: "flex", flexDirection: rest.isMobile ? "row" : "column", gap: 2, children: [
98
+ /* @__PURE__ */ jsxs(
99
+ Box,
100
+ {
101
+ className: "user-avatar",
70
102
  position: "relative",
71
- overflow: "hidden",
72
- flexShrink: 0,
73
- ...isMyself ? {
74
- cursor: "pointer",
75
- "&::after": {
76
- content: `"${t("switchProfile")}"`,
77
- color: "white",
78
- position: "absolute",
79
- fontSize: "12px",
80
- bottom: 0,
81
- left: 0,
82
- right: 0,
83
- height: "50%",
84
- backgroundColor: "rgba(0, 0, 0, 0.3)",
85
- display: "flex",
86
- justifyContent: "center",
87
- alignItems: "center"
88
- }
89
- } : {}
90
- },
91
- onClick: isMyself ? switchProfile : noop
92
- }
93
- ),
94
- /* @__PURE__ */ jsxs(
95
- Box,
96
- {
97
- sx: {
98
- flex: 1,
99
- overflow: "hidden"
100
- },
101
- children: [
102
- /* @__PURE__ */ jsxs(
103
- Typography,
104
- {
105
- variant: "h6",
106
- sx: {
107
- fontWeight: "bold",
108
- display: "flex",
109
- alignItems: "center",
110
- gap: 1,
111
- fontSize: "24px !important"
112
- },
113
- children: [
114
- user?.fullName,
115
- isMyself ? /* @__PURE__ */ jsxs(Fragment, { children: [
116
- /* @__PURE__ */ jsx(SwitchRole, { user, switchPassport }),
117
- !isEmpty(user.url) && /* @__PURE__ */ jsx(
118
- Tooltip,
119
- {
120
- title: !loading && translate(translations, "refreshProfile.title", locale),
121
- sx: { display: "flex", alignItems: "center" },
122
- children: loading ? /* @__PURE__ */ jsx(CircularProgress, { size: "16px", sx: { color: theme?.colors?.primary, ml: 0.5 } }) : /* @__PURE__ */ jsx(RefreshOutlinedIcon, { onClick: refreshProfile })
103
+ display: "flex",
104
+ alignItems: "center",
105
+ justifyContent: "space-between",
106
+ children: [
107
+ /* @__PURE__ */ jsx(
108
+ Avatar,
109
+ {
110
+ src: user?.avatar,
111
+ did: user?.did,
112
+ size: rest.size || (rest.isMobile ? 64 : 100),
113
+ variant: "circle",
114
+ shape: "circle",
115
+ sx: {
116
+ borderRadius: "100%",
117
+ backgroundColor: "#fff",
118
+ position: "relative",
119
+ overflow: "hidden",
120
+ flexShrink: 0,
121
+ ...isMyself ? {
122
+ cursor: "pointer",
123
+ "&::after": {
124
+ content: `"${t("switchProfile")}"`,
125
+ color: "white",
126
+ position: "absolute",
127
+ fontSize: "12px",
128
+ bottom: 0,
129
+ left: 0,
130
+ right: 0,
131
+ height: "50%",
132
+ backgroundColor: "rgba(0, 0, 0, 0.3)",
133
+ display: "flex",
134
+ justifyContent: "center",
135
+ alignItems: "center"
123
136
  }
124
- )
125
- ] }) : null
126
- ]
127
- }
128
- ),
129
- /* @__PURE__ */ jsx(DID, { did: user.did, showQrcode: true, copyable: true, compact: !showFullDid, responsive: !showFullDid, locale })
130
- ]
131
- }
132
- )
137
+ } : {}
138
+ },
139
+ onClick: isMyself ? switchProfile : noop
140
+ }
141
+ ),
142
+ /* @__PURE__ */ jsx(
143
+ UserStatus,
144
+ {
145
+ isMobile: rest.isMobile,
146
+ size: rest.size || (rest.isMobile ? 64 : 100),
147
+ isMyself,
148
+ status: userStatus,
149
+ onChange: onUpdateUserStatus
150
+ }
151
+ )
152
+ ]
153
+ }
154
+ ),
155
+ /* @__PURE__ */ jsxs(
156
+ Box,
157
+ {
158
+ sx: {
159
+ flex: 1,
160
+ overflow: "hidden"
161
+ },
162
+ children: [
163
+ /* @__PURE__ */ jsxs(
164
+ Typography,
165
+ {
166
+ variant: "h6",
167
+ sx: {
168
+ fontWeight: 600,
169
+ display: "flex",
170
+ alignItems: "center",
171
+ gap: 1,
172
+ fontSize: "24px !important"
173
+ },
174
+ children: [
175
+ user?.fullName,
176
+ isMyself ? /* @__PURE__ */ jsx(SwitchRole, { user, switchPassport }) : null
177
+ ]
178
+ }
179
+ ),
180
+ /* @__PURE__ */ jsx(
181
+ DID,
182
+ {
183
+ did: user.did,
184
+ showQrcode: true,
185
+ copyable: true,
186
+ compact: !showFullDid,
187
+ responsive: !showFullDid,
188
+ locale,
189
+ style: { maxWidth: 260 }
190
+ }
191
+ )
192
+ ]
193
+ }
194
+ )
195
+ ] }),
196
+ /* @__PURE__ */ jsx(UserMetadataComponent, { isMobile: rest.isMobile, isMyself, user, onSave }),
197
+ isMyself ? /* @__PURE__ */ jsxs(Fragment, { children: [
198
+ /* @__PURE__ */ jsx(Divider, { sx: { my: 3, borderColor: colors.dividerColor } }),
199
+ /* @__PURE__ */ jsx(Typography, { component: "p", color: "text.secondary", fontSize: "14px", mb: 2, children: t("profile.justForYou") }),
200
+ /* @__PURE__ */ jsx(UserInfo, { user, isMySelf: isMyself })
201
+ ] }) : null
133
202
  ]
134
203
  }
135
204
  );
@@ -3,8 +3,6 @@ import { Box } from "@mui/material";
3
3
  import { Icon } from "@iconify/react";
4
4
  import { useMemoizedFn, useCreation } from "ahooks";
5
5
  import DID from "@arcblock/ux/lib/DID";
6
- import MailOutlineRoundedIcon from "@iconify-icons/material-symbols/mail-outline-rounded";
7
- import PhoneOutlineRoundedIcon from "@iconify-icons/material-symbols/phone-android-outline-rounded";
8
6
  import ScheduleOutlineRoundedIcon from "@iconify-icons/material-symbols/schedule-outline-rounded";
9
7
  import MoreTimeRoundedIcon from "@iconify-icons/material-symbols/more-time-rounded";
10
8
  import CaptivePortalRoundedIcon from "@iconify-icons/material-symbols/captive-portal-rounded";
@@ -29,18 +27,6 @@ export default function UserInfo({
29
27
  }, [user?.sourceProvider]);
30
28
  const userInfoListData = [];
31
29
  if (isMySelf) {
32
- userInfoListData.push({
33
- icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: MailOutlineRoundedIcon }),
34
- title: t("email"),
35
- content: user?.email || t("emptyField"),
36
- verified: user?.emailVerified
37
- });
38
- userInfoListData.push({
39
- icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: PhoneOutlineRoundedIcon }),
40
- title: t("phone"),
41
- content: user?.phone || t("emptyField"),
42
- verified: user?.phoneVerified
43
- });
44
30
  userInfoListData.push({
45
31
  icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: ScheduleOutlineRoundedIcon }),
46
32
  title: t("lastLoginAt"),
@@ -70,7 +56,7 @@ export default function UserInfo({
70
56
  userInfoListData.push({
71
57
  icon: /* @__PURE__ */ jsx(Icon, { fontSize: 16, icon: CaptivePortalRoundedIcon }),
72
58
  title: t("invitedBy"),
73
- content: user?.inviter ? /* @__PURE__ */ jsx(DID, { did: user.inviter, showQrcode: true, copyable: true, compact: true, responsive: true, locale }) : "-"
59
+ content: user?.inviter ? /* @__PURE__ */ jsx(DID, { style: { maxWidth: 260 }, did: user.inviter, showQrcode: true, copyable: true, compact: true, responsive: true, locale }) : "-"
74
60
  });
75
61
  }
76
62
  return /* @__PURE__ */ jsx(
@@ -83,7 +69,7 @@ export default function UserInfo({
83
69
  gap: 1.5,
84
70
  ...rest?.sx
85
71
  },
86
- children: userInfoListData.map((item) => /* @__PURE__ */ jsx(UserInfoItem, { data: item, sx: { flex: 1 }, verified: item.verified }, item.title))
72
+ children: userInfoListData.map((item) => /* @__PURE__ */ jsx(UserInfoItem, { data: item, sx: { flex: 1 } }, item.title))
87
73
  }
88
74
  );
89
75
  }
@@ -0,0 +1,8 @@
1
+ import type { UserMetadata } from '../../../@types';
2
+ export default function UserStatus({ isMobile, size, isMyself, status, onChange, }: {
3
+ isMobile?: boolean;
4
+ size: number;
5
+ isMyself: boolean;
6
+ status: UserMetadata['status'];
7
+ onChange: (v: UserMetadata['status']) => void;
8
+ }): import("react").JSX.Element;
@@ -0,0 +1,153 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import Badge from "@mui/material/Badge";
3
+ import Box from "@mui/material/Box";
4
+ import styled from "@emotion/styled";
5
+ import { lazy, useCallback, useEffect, useMemo, useState } from "react";
6
+ import { useCreation, useMemoizedFn, useInterval, useUnmount } from "ahooks";
7
+ import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
8
+ import Tooltip from "@mui/material/Tooltip";
9
+ import { translate } from "@arcblock/ux/lib/Locale/util";
10
+ import { formatToDatetime } from "@arcblock/ux/lib/Util";
11
+ import { DurationEnum, StatusEnum } from "../../../@types/index.js";
12
+ import StatusSelector from "../status-selector/index.js";
13
+ import { translations } from "../../libs/locales.js";
14
+ import { getTimeRemaining, isWithinTimeRange } from "./utils.js";
15
+ const MeetingIcon = lazy(() => import("@arcblock/icons/lib/Meeting"));
16
+ const CommunityIcon = lazy(() => import("@arcblock/icons/lib/Community"));
17
+ const HolidayIcon = lazy(() => import("@arcblock/icons/lib/Holiday"));
18
+ const OffSickIcon = lazy(() => import("@arcblock/icons/lib/OffSick"));
19
+ const WorkingRemotelyIcon = lazy(() => import("@arcblock/icons/lib/WorkingRemotely"));
20
+ const StatusIconMap = {
21
+ [StatusEnum.Meeting]: MeetingIcon,
22
+ [StatusEnum.Community]: CommunityIcon,
23
+ [StatusEnum.Holiday]: HolidayIcon,
24
+ [StatusEnum.OffSick]: OffSickIcon,
25
+ [StatusEnum.WorkingRemotely]: WorkingRemotelyIcon
26
+ };
27
+ export default function UserStatus({
28
+ isMobile,
29
+ size,
30
+ isMyself,
31
+ status,
32
+ onChange
33
+ }) {
34
+ const { locale } = useLocaleContext();
35
+ const t = useMemoizedFn((key, data = {}) => {
36
+ return translate(translations, key, locale, "en", data);
37
+ });
38
+ const [interval, setInterval] = useState(void 0);
39
+ const pauseInterval = useMemoizedFn(() => {
40
+ setInterval(void 0);
41
+ });
42
+ useEffect(() => {
43
+ setInterval(1e3);
44
+ }, [status]);
45
+ const clear = useInterval(() => {
46
+ if (status?.value && status?.dateRange?.length === 2) {
47
+ const isWithin = isWithinTimeRange(status.dateRange);
48
+ if (!isWithin) {
49
+ pauseInterval();
50
+ onChange(void 0);
51
+ } else {
52
+ const timeRemaining = getTimeRemaining(status.dateRange[1]);
53
+ if (timeRemaining > 0) {
54
+ setInterval(timeRemaining);
55
+ } else {
56
+ pauseInterval();
57
+ onChange(void 0);
58
+ }
59
+ }
60
+ } else {
61
+ pauseInterval();
62
+ }
63
+ }, interval);
64
+ useUnmount(() => {
65
+ clear();
66
+ });
67
+ const [anchorEl, setAnchorEl] = useState(null);
68
+ const getDurationData = useCallback(() => {
69
+ const data = Object.keys(DurationEnum).map((key) => ({
70
+ id: DurationEnum[key],
71
+ name: t(`userStatus.duration.${key}`)
72
+ }));
73
+ return data;
74
+ }, [t]);
75
+ const statusData = useCreation(() => {
76
+ const durationData = getDurationData();
77
+ return Object.keys(StatusEnum).map((key) => ({
78
+ id: StatusEnum[key],
79
+ name: t(`userStatus.${key}`),
80
+ icon: StatusIconMap[StatusEnum[key]],
81
+ children: durationData
82
+ }));
83
+ }, [t, getDurationData]);
84
+ const onOpenStatusSelector = (event) => {
85
+ if (!isMyself) {
86
+ return;
87
+ }
88
+ setAnchorEl(event.currentTarget);
89
+ };
90
+ const onCloseStatusSelector = () => {
91
+ setAnchorEl(null);
92
+ };
93
+ const onStatusChange = (v) => {
94
+ if (!isMyself) {
95
+ return;
96
+ }
97
+ if (v) {
98
+ onChange(v);
99
+ }
100
+ onCloseStatusSelector();
101
+ };
102
+ const StatusIcon = StatusIconMap[status?.value];
103
+ const tooltipTitle = useMemo(() => {
104
+ const currentStatus = statusData.find((item) => item.id === status?.value);
105
+ if (currentStatus) {
106
+ const localeOption = locale === "zh" ? "zh-cn" : "en-us";
107
+ const range = status?.dateRange?.map((item) => {
108
+ return formatToDatetime(item, { locale: localeOption });
109
+ });
110
+ return `${currentStatus?.name}: ${range?.join("~")}`;
111
+ }
112
+ return null;
113
+ }, [status, statusData, locale]);
114
+ const open = Boolean(anchorEl);
115
+ return /* @__PURE__ */ jsxs(StatusDiv, { size, isMobile, children: [
116
+ /* @__PURE__ */ jsx(Tooltip, { title: tooltipTitle, children: /* @__PURE__ */ jsx(
117
+ Box,
118
+ {
119
+ className: "status-icon",
120
+ display: "flex",
121
+ alignItems: "center",
122
+ justifyContent: "center",
123
+ onClick: onOpenStatusSelector,
124
+ children: StatusIcon ? /* @__PURE__ */ jsx(StatusIcon, { style: { width: 16, height: 16 } }) : /* @__PURE__ */ jsx(Badge, { color: "success", variant: "dot" })
125
+ }
126
+ ) }),
127
+ /* @__PURE__ */ jsx(StatusSelector, { selected: status, data: statusData, open, onSelect: onStatusChange, anchorEl })
128
+ ] });
129
+ }
130
+ const StatusDiv = styled(Box)`
131
+ position: absolute;
132
+ left: ${({ size }) => `${size * 3 / 4}px`};
133
+ top: ${({ size }) => `${size * 0.65}px`};
134
+ width: ${({ isMobile }) => isMobile ? "22px" : "32px"};
135
+ height: ${({ isMobile }) => isMobile ? "22px" : "32px"};
136
+ border-radius: ${({ isMobile }) => isMobile ? "11px" : "16px"};
137
+ display: flex;
138
+ align-items: center;
139
+ justify-content: center;
140
+ background-color: #ffffff;
141
+ overflow: hidden;
142
+ white-space: nowrap;
143
+ cursor: pointer;
144
+
145
+ .status-icon {
146
+ flex-shrink: 0;
147
+ width: ${({ isMobile }) => isMobile ? "16px" : "26px"};
148
+ height: ${({ isMobile }) => isMobile ? "16px" : "26px"};
149
+ border-radius: ${({ isMobile }) => isMobile ? "8px" : "13px"};
150
+ background-color: #eff1f5;
151
+ // background-color: #39b380;
152
+ }
153
+ `;
@@ -0,0 +1,19 @@
1
+ import { UserMetadata } from '../../../@types';
2
+ export declare const getTimezones: () => any;
3
+ export declare const isValidUrl: (url: string) => boolean;
4
+ /**
5
+ * 根据 duration 类型,计算出date range
6
+ * @param status
7
+ * @returns
8
+ */
9
+ export declare const getStatusDuration: (status: UserMetadata["status"]) => any[];
10
+ /**
11
+ * 根据状态的 duration,判断是否在时间范围内
12
+ * @param status
13
+ * @returns
14
+ */
15
+ export declare const isWithinTimeRange: (dateRange: [Date, Date]) => any;
16
+ /**
17
+ * 获取当前时间距离结束时间还有多久
18
+ */
19
+ export declare const getTimeRemaining: (date: Date) => number;
@@ -0,0 +1,86 @@
1
+ import moment from "moment-timezone";
2
+ import dayjs from "dayjs";
3
+ import { DurationEnum } from "../../../@types/index.js";
4
+ const HOUR = 3600;
5
+ const MINUTES_30 = 1800;
6
+ const MINUTES_10 = 600;
7
+ const MINUTES_5 = 300;
8
+ const MINUTES_1 = 60;
9
+ const SECOND = 1;
10
+ export const getTimezones = () => {
11
+ const timezones = moment.tz.names();
12
+ const formattedTimezones = timezones.map((tz) => {
13
+ const offset = moment.tz(tz).utcOffset() / 60;
14
+ const hours = Math.floor(offset);
15
+ const minutes = offset % 1 * 60;
16
+ const label = `GMT${hours >= 0 ? "+" : ""}${hours}:${minutes === 30 ? "30" : "00"}`;
17
+ return { label, value: tz };
18
+ });
19
+ return formattedTimezones.sort((a, b) => {
20
+ const [hoursA, minutesA] = a.label.replace("GMT", "").split(":").map(Number);
21
+ const [hoursB, minutesB] = b.label.replace("GMT", "").split(":").map(Number);
22
+ const totalOffsetA = hoursA * 60 + minutesA;
23
+ const totalOffsetB = hoursB * 60 + minutesB;
24
+ return totalOffsetB - totalOffsetA;
25
+ }).map((tz) => ({
26
+ label: `(${tz.label}) ${tz.value}`,
27
+ value: tz.value
28
+ }));
29
+ };
30
+ export const isValidUrl = (url) => {
31
+ const urlPattern = /^(https?:\/\/)?((([a-zA-Z\d]([a-zA-Z\d-]*[a-zA-Z\d])*)\.)+[a-zA-Z]{2,}|((\d{1,3}\.){3}\d{1,3}))(:\d+)?(\/[-a-zA-Z\d%_.~+]*)*(\?[;&a-zA-Z\d%_.~+=-]*)?(#[a-zA-Z\d_]*)?$/;
32
+ return urlPattern.test(url);
33
+ };
34
+ export const getStatusDuration = (status) => {
35
+ let dateRange = [];
36
+ const current = dayjs();
37
+ switch (status?.duration) {
38
+ case DurationEnum.ThirtyMinutes:
39
+ dateRange = [current, current.add(30, "minutes")];
40
+ break;
41
+ case DurationEnum.OneHour:
42
+ dateRange = [current, current.add(1, "hour")];
43
+ break;
44
+ case DurationEnum.FourHours:
45
+ dateRange = [current, current.add(4, "hours")];
46
+ break;
47
+ case DurationEnum.Today:
48
+ dateRange = [current, current.endOf("day")];
49
+ break;
50
+ case DurationEnum.ThisWeek:
51
+ dateRange = [current, current.endOf("week")];
52
+ break;
53
+ default:
54
+ break;
55
+ }
56
+ return dateRange.map((d) => d.toDate());
57
+ };
58
+ export const isWithinTimeRange = (dateRange) => {
59
+ const current = dayjs();
60
+ return current.isAfter(dayjs(dateRange[0])) && current.isBefore(dayjs(dateRange[1]));
61
+ };
62
+ export const getTimeRemaining = (date) => {
63
+ const now = dayjs();
64
+ const end = dayjs(date);
65
+ const diffSeconds = end.diff(now, "seconds");
66
+ const toMilliseconds = (seconds) => seconds * 1e3;
67
+ if (diffSeconds >= HOUR) {
68
+ return toMilliseconds(HOUR);
69
+ }
70
+ if (diffSeconds >= MINUTES_30) {
71
+ return toMilliseconds(MINUTES_30);
72
+ }
73
+ if (diffSeconds >= MINUTES_10) {
74
+ return toMilliseconds(MINUTES_10);
75
+ }
76
+ if (diffSeconds >= MINUTES_5) {
77
+ return toMilliseconds(MINUTES_5);
78
+ }
79
+ if (diffSeconds >= MINUTES_1) {
80
+ return toMilliseconds(MINUTES_1);
81
+ }
82
+ if (diffSeconds >= SECOND) {
83
+ return toMilliseconds(SECOND);
84
+ }
85
+ return 0;
86
+ };