@arcblock/ux 3.1.28 → 3.1.31
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/SessionUser/components/logged-in.js +41 -39
- package/lib/UserCard/Cards/avatar-only.js +11 -22
- package/lib/UserCard/Cards/index.js +4 -12
- package/lib/UserCard/Cards/social-actions.d.ts +7 -0
- package/lib/UserCard/Cards/social-actions.js +112 -0
- package/lib/UserCard/Content/minimal.js +62 -47
- package/lib/UserCard/index.d.ts +2 -0
- package/lib/UserCard/index.js +31 -28
- package/lib/UserCard/types.d.ts +8 -1
- package/lib/UserCard/types.js +4 -4
- package/lib/UserCard/use-follow.d.ts +16 -0
- package/lib/UserCard/use-follow.js +67 -0
- package/lib/Util/index.d.ts +15 -0
- package/lib/Util/index.js +191 -171
- package/lib/package.json.js +9 -0
- package/lib/withTracker/action/bind-wallet.d.ts +14 -0
- package/lib/withTracker/action/bind-wallet.js +1 -0
- package/lib/withTracker/action/login.d.ts +15 -0
- package/lib/withTracker/action/login.js +1 -0
- package/lib/withTracker/action/pay.d.ts +12 -0
- package/lib/withTracker/action/pay.js +1 -0
- package/lib/withTracker/action/switch-passport.d.ts +18 -0
- package/lib/withTracker/action/switch-passport.js +1 -0
- package/lib/withTracker/constant/index.d.ts +3 -0
- package/lib/withTracker/constant/index.js +6 -0
- package/lib/withTracker/env.d.ts +1 -0
- package/lib/withTracker/env.js +4 -0
- package/lib/withTracker/index.d.ts +2 -0
- package/lib/withTracker/index.js +21 -17
- package/package.json +10 -8
- package/src/SessionUser/components/logged-in.tsx +2 -0
- package/src/UserCard/Cards/avatar-only.tsx +1 -15
- package/src/UserCard/Cards/index.tsx +2 -11
- package/src/UserCard/Cards/social-actions.tsx +196 -0
- package/src/UserCard/Content/minimal.tsx +43 -31
- package/src/UserCard/UserCard.stories.jsx +1 -0
- package/src/UserCard/index.tsx +6 -0
- package/src/UserCard/types.ts +10 -1
- package/src/UserCard/use-follow.tsx +119 -0
- package/src/Util/index.ts +67 -0
- package/src/withTracker/action/bind-wallet.tsx +17 -0
- package/src/withTracker/action/login.tsx +18 -0
- package/src/withTracker/action/pay.tsx +14 -0
- package/src/withTracker/action/switch-passport.tsx +20 -0
- package/src/withTracker/constant/index.tsx +3 -0
- package/src/withTracker/env.tsx +1 -0
- package/src/withTracker/index.tsx +8 -11
- package/lib/UserCard/Cards/name-only.d.ts +0 -5
- package/lib/UserCard/Cards/name-only.js +0 -13
- package/src/UserCard/Cards/name-only.tsx +0 -17
@@ -0,0 +1,119 @@
|
|
1
|
+
import { useEffect, useMemo, useState } from 'react';
|
2
|
+
import { useMemoizedFn } from 'ahooks';
|
3
|
+
import isNil from 'lodash/isNil';
|
4
|
+
|
5
|
+
import type { AxiosError } from 'axios';
|
6
|
+
import { BlockletSDK } from '@blocklet/js-sdk';
|
7
|
+
import Toast from '../Toast';
|
8
|
+
import type { User } from './types';
|
9
|
+
|
10
|
+
export const formatAxiosError = (err: AxiosError) => {
|
11
|
+
const { response } = err;
|
12
|
+
|
13
|
+
if (response) {
|
14
|
+
return `Request failed: ${response.status} ${response.statusText}: ${JSON.stringify(response.data)}`;
|
15
|
+
}
|
16
|
+
|
17
|
+
return err.message;
|
18
|
+
};
|
19
|
+
|
20
|
+
/**
|
21
|
+
* 登录用户与当前用户(userDid)的关注关系
|
22
|
+
*/
|
23
|
+
export default function useFollow({
|
24
|
+
user,
|
25
|
+
t,
|
26
|
+
isMySelf,
|
27
|
+
visible,
|
28
|
+
}: {
|
29
|
+
user: User;
|
30
|
+
t: (k: string) => string;
|
31
|
+
isMySelf: boolean;
|
32
|
+
visible: boolean;
|
33
|
+
}) {
|
34
|
+
const [followed, setFollowed] = useState(false);
|
35
|
+
|
36
|
+
const userDid = useMemo(() => user?.did, [user]);
|
37
|
+
|
38
|
+
// 判断 user 对象中 isFollowing 值是否有效,有isFollowed字段并且值不为 undefined 或 null
|
39
|
+
// 如果为 true 则不需要通过 api 查询,直接使用 user.isFollowing 值
|
40
|
+
// 如果为 false 则需要通过 api 查询,并设置 user.isFollowing 值
|
41
|
+
const hasFollowedField = useMemo(() => {
|
42
|
+
return Object.prototype.hasOwnProperty.call(user, 'isFollowing') && !isNil((user as any).isFollowing);
|
43
|
+
}, [user]);
|
44
|
+
|
45
|
+
const client = useMemo(() => {
|
46
|
+
let _client: BlockletSDK | null = null;
|
47
|
+
try {
|
48
|
+
_client = new BlockletSDK();
|
49
|
+
} catch (error) {
|
50
|
+
console.error('Failed to initialize BlockletSDK:', error);
|
51
|
+
_client = null;
|
52
|
+
}
|
53
|
+
return _client;
|
54
|
+
}, []);
|
55
|
+
|
56
|
+
const isFollowingUser = useMemoizedFn(async () => {
|
57
|
+
if (!client) {
|
58
|
+
setFollowed(false);
|
59
|
+
return;
|
60
|
+
}
|
61
|
+
try {
|
62
|
+
if (isMySelf) {
|
63
|
+
setFollowed(true);
|
64
|
+
return;
|
65
|
+
}
|
66
|
+
const res = await client.user.isFollowingUser({ userDid });
|
67
|
+
setFollowed(res);
|
68
|
+
} catch (error) {
|
69
|
+
console.error(error);
|
70
|
+
}
|
71
|
+
});
|
72
|
+
|
73
|
+
const followUser = useMemoizedFn(async (followUserDid: string = userDid) => {
|
74
|
+
if (!client || (isMySelf && followUserDid === userDid)) {
|
75
|
+
return;
|
76
|
+
}
|
77
|
+
try {
|
78
|
+
await client.user.followUser({ userDid: followUserDid });
|
79
|
+
Toast.success(t('follow_success'));
|
80
|
+
isFollowingUser();
|
81
|
+
} catch (error) {
|
82
|
+
console.error(error);
|
83
|
+
Toast.error(formatAxiosError(error as AxiosError));
|
84
|
+
}
|
85
|
+
});
|
86
|
+
const unfollowUser = useMemoizedFn(async (unfollowUserDid: string = userDid) => {
|
87
|
+
if (!client || (isMySelf && unfollowUserDid === userDid)) {
|
88
|
+
return;
|
89
|
+
}
|
90
|
+
try {
|
91
|
+
await client.user.unfollowUser({ userDid: unfollowUserDid });
|
92
|
+
Toast.success(t('unfollow_success'));
|
93
|
+
isFollowingUser();
|
94
|
+
} catch (error) {
|
95
|
+
console.error(error);
|
96
|
+
Toast.error(formatAxiosError(error as AxiosError));
|
97
|
+
}
|
98
|
+
});
|
99
|
+
|
100
|
+
useEffect(() => {
|
101
|
+
// 如果 user 对象中已经存在了 isFollowing 字段,则不进行查询
|
102
|
+
if (visible && userDid && !isMySelf && !!client && !hasFollowedField) {
|
103
|
+
isFollowingUser();
|
104
|
+
}
|
105
|
+
}, [isFollowingUser, userDid, isMySelf, client, hasFollowedField, visible]);
|
106
|
+
|
107
|
+
// 如果 user 对象中已经存在了 isFollowing 字段,则直接设置 followed 状态
|
108
|
+
useEffect(() => {
|
109
|
+
if (hasFollowedField) {
|
110
|
+
setFollowed((user as any)?.isFollowing || false);
|
111
|
+
}
|
112
|
+
}, [hasFollowedField, user]);
|
113
|
+
|
114
|
+
return {
|
115
|
+
followed,
|
116
|
+
followUser,
|
117
|
+
unfollowUser,
|
118
|
+
};
|
119
|
+
}
|
package/src/Util/index.ts
CHANGED
@@ -15,8 +15,10 @@ import timezone from 'dayjs/plugin/timezone';
|
|
15
15
|
import relativeTime from 'dayjs/plugin/relativeTime';
|
16
16
|
import updateLocale from 'dayjs/plugin/updateLocale';
|
17
17
|
import localizedFormat from 'dayjs/plugin/localizedFormat';
|
18
|
+
import semver from 'semver';
|
18
19
|
|
19
20
|
import { DID_PREFIX, BLOCKLET_SERVICE_PATH_PREFIX } from './constant';
|
21
|
+
import pkg from '../../package.json';
|
20
22
|
import type { $TSFixMe, Locale } from '../type';
|
21
23
|
import { getFederatedEnabled } from './federated';
|
22
24
|
|
@@ -658,3 +660,68 @@ export function hexToRgba(hex: string, alpha = 1) {
|
|
658
660
|
// 返回 RGBA 格式
|
659
661
|
return `rgba(${r}, ${g}, ${b}, ${alpha})`;
|
660
662
|
}
|
663
|
+
|
664
|
+
// 获取 server 的版本号
|
665
|
+
export const getServerVersion = () => {
|
666
|
+
return window.blocklet?.serverVersion || '';
|
667
|
+
};
|
668
|
+
|
669
|
+
// 获取 UX 包版本号
|
670
|
+
export const getUxPackageVersion = () => {
|
671
|
+
return pkg.version || '';
|
672
|
+
};
|
673
|
+
|
674
|
+
export const getJsSdkVersion = () => {
|
675
|
+
// 从 package.json 的 dependencies 中获取 @blocklet/js-sdk 的版本
|
676
|
+
const jsSdkVersion = pkg.dependencies?.['@blocklet/js-sdk'];
|
677
|
+
if (!jsSdkVersion) {
|
678
|
+
return '';
|
679
|
+
}
|
680
|
+
// 移除版本前缀符号 (^, ~, >=, 等)
|
681
|
+
return jsSdkVersion.replace(/^[\^~>=<]+/, '');
|
682
|
+
};
|
683
|
+
|
684
|
+
/**
|
685
|
+
* 比较两个版本号,version1 是否大于等于 version2
|
686
|
+
* @param {*} version1
|
687
|
+
* @param {*} version2
|
688
|
+
* @returns boolean
|
689
|
+
*/
|
690
|
+
export const compareVersions = (version1: string, version2: string) => {
|
691
|
+
const getDateVersion = (version: string) => {
|
692
|
+
const match = version.match(/^(\d+\.\d+\.\d+(?:-[^-]+?-\d{8}))/);
|
693
|
+
return match ? match[1] : version;
|
694
|
+
};
|
695
|
+
|
696
|
+
const dateVersion1 = getDateVersion(version1);
|
697
|
+
const dateVersion2 = getDateVersion(version2);
|
698
|
+
|
699
|
+
// 如果基础版本相同,但完整版本不同(意味着有额外部分),返回false
|
700
|
+
if (dateVersion1 === dateVersion2 && version1 !== version2) {
|
701
|
+
return false;
|
702
|
+
}
|
703
|
+
|
704
|
+
// 其他情况正常比较
|
705
|
+
return semver.gte(dateVersion1, dateVersion2);
|
706
|
+
};
|
707
|
+
|
708
|
+
/**
|
709
|
+
* 是否支持用户的 follow 关系
|
710
|
+
* 通过 server 的版本和 ux 的版本共同决定
|
711
|
+
*/
|
712
|
+
export const isSupportFollow = () => {
|
713
|
+
const serverVersion = getServerVersion();
|
714
|
+
const uxVersion = getUxPackageVersion();
|
715
|
+
const jsSdkVersion = getJsSdkVersion();
|
716
|
+
|
717
|
+
if (!serverVersion || !uxVersion || !jsSdkVersion) {
|
718
|
+
return false;
|
719
|
+
}
|
720
|
+
// UX 包支持
|
721
|
+
const uxVersionSupport = compareVersions(uxVersion, '3.1.29');
|
722
|
+
// 服务端接口实现支持
|
723
|
+
const serverVersionSupport = compareVersions(serverVersion, '1.16.49-beta-20250822-070545-6d3344cc');
|
724
|
+
// SDK 接口实现支持
|
725
|
+
const jsSdkVersionSupport = compareVersions(jsSdkVersion, '1.16.49-beta-20250822-070545-6d3344cc');
|
726
|
+
return uxVersionSupport && serverVersionSupport && jsSdkVersionSupport;
|
727
|
+
};
|
@@ -0,0 +1,17 @@
|
|
1
|
+
import type { LiteralUnion } from 'type-fest';
|
2
|
+
|
3
|
+
export interface BindWalletEvent {
|
4
|
+
action: 'bindWalletSuccess' | 'bindWalletFailed';
|
5
|
+
provider: LiteralUnion<'wallet' | 'apple' | 'github' | 'google' | 'email' | 'twitter' | 'passkey', string>;
|
6
|
+
}
|
7
|
+
|
8
|
+
export interface BindWalletSuccessEvent extends BindWalletEvent {
|
9
|
+
action: 'bindWalletSuccess';
|
10
|
+
success: true;
|
11
|
+
}
|
12
|
+
|
13
|
+
export interface BindWalletFailedEvent extends BindWalletEvent {
|
14
|
+
action: 'bindWalletFailed';
|
15
|
+
success: false;
|
16
|
+
errorMessage: string;
|
17
|
+
}
|
@@ -0,0 +1,18 @@
|
|
1
|
+
import type { LiteralUnion } from 'type-fest';
|
2
|
+
|
3
|
+
export interface LoginEvent {
|
4
|
+
action: 'loginSuccess' | 'loginFailed';
|
5
|
+
provider: LiteralUnion<'wallet' | 'apple' | 'github' | 'google' | 'email' | 'twitter' | 'passkey', string>;
|
6
|
+
success: boolean;
|
7
|
+
}
|
8
|
+
|
9
|
+
export interface LoginSuccessEvent extends LoginEvent {
|
10
|
+
action: 'loginSuccess';
|
11
|
+
success: true;
|
12
|
+
}
|
13
|
+
|
14
|
+
export interface LoginFailedEvent extends LoginEvent {
|
15
|
+
action: 'loginFailed';
|
16
|
+
success: false;
|
17
|
+
errorMessage: string;
|
18
|
+
}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
export interface PayEvent {
|
2
|
+
action: 'paySuccess' | 'payFailed';
|
3
|
+
checkoutSessionMode: string;
|
4
|
+
success: boolean;
|
5
|
+
}
|
6
|
+
|
7
|
+
export interface PaySuccessEvent extends PayEvent {
|
8
|
+
success: true;
|
9
|
+
}
|
10
|
+
|
11
|
+
export interface PayFailedEvent extends PayEvent {
|
12
|
+
success: false;
|
13
|
+
errorMessage: string;
|
14
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
export interface SwitchPassportEvent {
|
2
|
+
action: 'switchPassportSuccess' | 'switchPassportFailed';
|
3
|
+
success: boolean;
|
4
|
+
}
|
5
|
+
|
6
|
+
export interface SwitchPassportSuccessEvent extends SwitchPassportEvent {
|
7
|
+
success: true;
|
8
|
+
/**
|
9
|
+
*
|
10
|
+
* @example guest -> admin
|
11
|
+
* @type {string}
|
12
|
+
* @memberof SwitchPassportEvent
|
13
|
+
*/
|
14
|
+
change: string;
|
15
|
+
}
|
16
|
+
|
17
|
+
export interface SwitchPassportFailedEvent extends SwitchPassportEvent {
|
18
|
+
success: false;
|
19
|
+
errorMessage: string;
|
20
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export const googleAnalyticsMeasurementId: string = window.blocklet?.GA_MEASUREMENT_ID || '';
|
@@ -1,29 +1,26 @@
|
|
1
1
|
import { useLocation } from 'react-router-dom';
|
2
2
|
import { useMount, useDeepCompareEffect } from 'ahooks';
|
3
3
|
import ReactGA from 'react-ga4';
|
4
|
+
import { googleAnalyticsMeasurementId } from './env';
|
4
5
|
|
6
|
+
if (googleAnalyticsMeasurementId) {
|
7
|
+
ReactGA.initialize(googleAnalyticsMeasurementId);
|
8
|
+
}
|
9
|
+
|
10
|
+
export { ReactGA };
|
5
11
|
export default <P extends object>(WrappedComponent: React.ComponentType<P>) => {
|
6
|
-
const trackingId = window.blocklet?.GA_MEASUREMENT_ID || '';
|
7
12
|
const trackPage = (page: string, title = document.title) => {
|
8
|
-
if (
|
9
|
-
return;
|
10
|
-
}
|
11
|
-
|
12
|
-
if (trackingId) {
|
13
|
+
if (googleAnalyticsMeasurementId) {
|
13
14
|
ReactGA.send({ hitType: 'pageview', page, title });
|
14
15
|
}
|
15
16
|
};
|
16
17
|
|
17
18
|
const trackEvent = (category: string, action: string, label: string, value: number) => {
|
18
|
-
if (
|
19
|
+
if (googleAnalyticsMeasurementId) {
|
19
20
|
ReactGA.event({ category, action, label, value, transport: 'beacon' });
|
20
21
|
}
|
21
22
|
};
|
22
23
|
|
23
|
-
if (trackingId) {
|
24
|
-
ReactGA.initialize(trackingId);
|
25
|
-
}
|
26
|
-
|
27
24
|
// @ts-ignore
|
28
25
|
window.trackPage = trackPage;
|
29
26
|
// @ts-ignore
|
@@ -1,13 +0,0 @@
|
|
1
|
-
import { jsxs as o, Fragment as i, jsx as n } from "react/jsx-runtime";
|
2
|
-
import { Typography as m } from "@mui/material";
|
3
|
-
import { renderAvatar as d } from "../components.js";
|
4
|
-
function c(a) {
|
5
|
-
const { user: r, avatarSize: t = 48, onAvatarClick: e } = a;
|
6
|
-
return /* @__PURE__ */ o(i, { children: [
|
7
|
-
d(r, t, a.avatarProps, e),
|
8
|
-
/* @__PURE__ */ n(m, { variant: "body1", children: r.fullName || r.email || r.did })
|
9
|
-
] });
|
10
|
-
}
|
11
|
-
export {
|
12
|
-
c as default
|
13
|
-
};
|
@@ -1,17 +0,0 @@
|
|
1
|
-
import { Typography } from '@mui/material';
|
2
|
-
import { UserCardProps, User } from '../types';
|
3
|
-
import { renderAvatar } from '../components';
|
4
|
-
|
5
|
-
// 详细卡片模式下的NameOnly渲染组件
|
6
|
-
function NameOnlyCard(props: Omit<UserCardProps, 'user'> & { user: User }) {
|
7
|
-
const { user, avatarSize = 48, onAvatarClick } = props;
|
8
|
-
|
9
|
-
return (
|
10
|
-
<>
|
11
|
-
{renderAvatar(user, avatarSize, props.avatarProps, onAvatarClick)}
|
12
|
-
<Typography variant="body1">{user.fullName || user.email || user.did}</Typography>
|
13
|
-
</>
|
14
|
-
);
|
15
|
-
}
|
16
|
-
|
17
|
-
export default NameOnlyCard;
|