@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.
- package/lib/@types/index.d.ts +34 -0
- package/lib/@types/index.js +16 -0
- package/lib/@types/shims.d.ts +1 -0
- package/lib/UserCenter/components/config-profile.js +23 -1
- package/lib/UserCenter/components/editable-field.d.ts +22 -0
- package/lib/UserCenter/components/editable-field.js +159 -0
- package/lib/UserCenter/components/nft.d.ts +4 -0
- package/lib/UserCenter/components/nft.js +93 -0
- package/lib/UserCenter/components/settings.js +32 -15
- package/lib/UserCenter/components/status-selector/duration-menu.d.ts +9 -0
- package/lib/UserCenter/components/status-selector/duration-menu.js +75 -0
- package/lib/UserCenter/components/status-selector/index.d.ts +9 -0
- package/lib/UserCenter/components/status-selector/index.js +39 -0
- package/lib/UserCenter/components/status-selector/menu-item.d.ts +24 -0
- package/lib/UserCenter/components/status-selector/menu-item.js +24 -0
- package/lib/UserCenter/components/user-center.js +119 -122
- package/lib/UserCenter/components/user-info/clock.d.ts +4 -0
- package/lib/UserCenter/components/user-info/clock.js +23 -0
- package/lib/UserCenter/components/user-info/link-preview-input.d.ts +5 -0
- package/lib/UserCenter/components/user-info/link-preview-input.js +181 -0
- package/lib/UserCenter/components/user-info/metadata.d.ts +7 -0
- package/lib/UserCenter/components/user-info/metadata.js +458 -0
- package/lib/UserCenter/components/user-info/switch-role.js +2 -3
- package/lib/UserCenter/components/user-info/user-basic-info.d.ts +2 -0
- package/lib/UserCenter/components/user-info/user-basic-info.js +159 -90
- package/lib/UserCenter/components/user-info/user-info.js +2 -16
- package/lib/UserCenter/components/user-info/user-status.d.ts +8 -0
- package/lib/UserCenter/components/user-info/user-status.js +153 -0
- package/lib/UserCenter/components/user-info/utils.d.ts +19 -0
- package/lib/UserCenter/components/user-info/utils.js +86 -0
- package/lib/UserCenter/libs/locales.d.ts +65 -0
- package/lib/UserCenter/libs/locales.js +67 -2
- package/lib/UserSessions/components/user-sessions.js +48 -14
- package/package.json +8 -5
- package/src/@types/index.ts +39 -0
- package/src/@types/shims.d.ts +1 -0
- package/src/UserCenter/components/config-profile.tsx +20 -1
- package/src/UserCenter/components/editable-field.tsx +180 -0
- package/src/UserCenter/components/nft.tsx +122 -0
- package/src/UserCenter/components/settings.tsx +16 -4
- package/src/UserCenter/components/status-selector/duration-menu.tsx +87 -0
- package/src/UserCenter/components/status-selector/index.tsx +52 -0
- package/src/UserCenter/components/status-selector/menu-item.tsx +52 -0
- package/src/UserCenter/components/user-center.tsx +104 -103
- package/src/UserCenter/components/user-info/clock.tsx +29 -0
- package/src/UserCenter/components/user-info/link-preview-input.tsx +227 -0
- package/src/UserCenter/components/user-info/metadata.tsx +465 -0
- package/src/UserCenter/components/user-info/switch-role.tsx +3 -3
- package/src/UserCenter/components/user-info/user-basic-info.tsx +150 -87
- package/src/UserCenter/components/user-info/user-info.tsx +6 -16
- package/src/UserCenter/components/user-info/user-status.tsx +182 -0
- package/src/UserCenter/components/user-info/utils.ts +114 -0
- package/src/UserCenter/libs/locales.ts +65 -0
- 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,
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
33
|
-
|
|
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
|
|
62
|
+
const onSave = async (v) => {
|
|
63
|
+
if (!isMyself) {
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
38
66
|
try {
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
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__ */
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
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 }
|
|
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
|
+
};
|