@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.
Files changed (50) hide show
  1. package/lib/SessionUser/components/logged-in.js +41 -39
  2. package/lib/UserCard/Cards/avatar-only.js +11 -22
  3. package/lib/UserCard/Cards/index.js +4 -12
  4. package/lib/UserCard/Cards/social-actions.d.ts +7 -0
  5. package/lib/UserCard/Cards/social-actions.js +112 -0
  6. package/lib/UserCard/Content/minimal.js +62 -47
  7. package/lib/UserCard/index.d.ts +2 -0
  8. package/lib/UserCard/index.js +31 -28
  9. package/lib/UserCard/types.d.ts +8 -1
  10. package/lib/UserCard/types.js +4 -4
  11. package/lib/UserCard/use-follow.d.ts +16 -0
  12. package/lib/UserCard/use-follow.js +67 -0
  13. package/lib/Util/index.d.ts +15 -0
  14. package/lib/Util/index.js +191 -171
  15. package/lib/package.json.js +9 -0
  16. package/lib/withTracker/action/bind-wallet.d.ts +14 -0
  17. package/lib/withTracker/action/bind-wallet.js +1 -0
  18. package/lib/withTracker/action/login.d.ts +15 -0
  19. package/lib/withTracker/action/login.js +1 -0
  20. package/lib/withTracker/action/pay.d.ts +12 -0
  21. package/lib/withTracker/action/pay.js +1 -0
  22. package/lib/withTracker/action/switch-passport.d.ts +18 -0
  23. package/lib/withTracker/action/switch-passport.js +1 -0
  24. package/lib/withTracker/constant/index.d.ts +3 -0
  25. package/lib/withTracker/constant/index.js +6 -0
  26. package/lib/withTracker/env.d.ts +1 -0
  27. package/lib/withTracker/env.js +4 -0
  28. package/lib/withTracker/index.d.ts +2 -0
  29. package/lib/withTracker/index.js +21 -17
  30. package/package.json +10 -8
  31. package/src/SessionUser/components/logged-in.tsx +2 -0
  32. package/src/UserCard/Cards/avatar-only.tsx +1 -15
  33. package/src/UserCard/Cards/index.tsx +2 -11
  34. package/src/UserCard/Cards/social-actions.tsx +196 -0
  35. package/src/UserCard/Content/minimal.tsx +43 -31
  36. package/src/UserCard/UserCard.stories.jsx +1 -0
  37. package/src/UserCard/index.tsx +6 -0
  38. package/src/UserCard/types.ts +10 -1
  39. package/src/UserCard/use-follow.tsx +119 -0
  40. package/src/Util/index.ts +67 -0
  41. package/src/withTracker/action/bind-wallet.tsx +17 -0
  42. package/src/withTracker/action/login.tsx +18 -0
  43. package/src/withTracker/action/pay.tsx +14 -0
  44. package/src/withTracker/action/switch-passport.tsx +20 -0
  45. package/src/withTracker/constant/index.tsx +3 -0
  46. package/src/withTracker/env.tsx +1 -0
  47. package/src/withTracker/index.tsx +8 -11
  48. package/lib/UserCard/Cards/name-only.d.ts +0 -5
  49. package/lib/UserCard/Cards/name-only.js +0 -13
  50. 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,3 @@
1
+ export const GA_LAST_LOGIN_METHOD = 'ga-last-login-method';
2
+ export const GA_LAST_ROLE = 'ga-last-role';
3
+ export const GA_LAST_SOURCE_PROVIDER = 'ga-last-source-provider';
@@ -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 (!process.env.NODE_ENV || process.env.NODE_ENV === 'development') {
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 (trackingId) {
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,5 +0,0 @@
1
- import { UserCardProps, User } from '../types';
2
- declare function NameOnlyCard(props: Omit<UserCardProps, 'user'> & {
3
- user: User;
4
- }): import("react/jsx-runtime").JSX.Element;
5
- export default NameOnlyCard;
@@ -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;