Skip to content

Commit 8439088

Browse files
authored
feat(project): support bind wechat (#1572)
1 parent d61a4ce commit 8439088

File tree

19 files changed

+559
-39
lines changed

19 files changed

+559
-39
lines changed

desktop/renderer-app/src/api-middleware/flatServer/constants.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ export const FLAT_SERVER_LOGIN = {
1212
WECHAT_CALLBACK: `${FLAT_SERVER_VERSIONS.V1}/login/weChat/web/callback`,
1313
} as const;
1414

15+
export const FLAT_SERVER_USER_BINDING = {
16+
WECHAT_CALLBACK: `${FLAT_SERVER_VERSIONS.V1}/user/bindingWechat/binding/web`,
17+
} as const;
18+
1519
export enum RoomType {
1620
OneToOne = "OneToOne",
1721
SmallClass = "SmallClass",

desktop/renderer-app/src/api-middleware/flatServer/index.ts

Lines changed: 56 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { Region } from "flat-components";
22
import { RoomStatus, RoomType, Week } from "./constants";
3-
import { post, postNotAuth } from "./utils";
3+
import { post, post2, postNotAuth, PROCESSING } from "./utils";
44

55
export interface CreateOrdinaryRoomPayload {
66
title: string;
@@ -465,16 +465,16 @@ export async function loginCheck(): Promise<LoginCheckResult> {
465465
return await post<LoginCheckPayload, LoginCheckResult>("login", {});
466466
}
467467

468-
export interface setAuthUUIDPayload {
468+
export interface SetAuthUUIDPayload {
469469
authUUID: string;
470470
}
471471

472-
export interface setAuthUUIDResult {
472+
export interface SetAuthUUIDResult {
473473
authUUID: string;
474474
}
475475

476-
export async function setAuthUUID(authUUID: string): Promise<setAuthUUIDResult> {
477-
return await postNotAuth<setAuthUUIDPayload, setAuthUUIDResult>("login/set-auth-uuid", {
476+
export async function setAuthUUID(authUUID: string): Promise<SetAuthUUIDResult> {
477+
return await postNotAuth<SetAuthUUIDPayload, SetAuthUUIDResult>("login/set-auth-uuid", {
478478
authUUID,
479479
});
480480
}
@@ -607,3 +607,54 @@ export async function uploadAvatarFinish(fileUUID: string): Promise<UploadAvatar
607607
},
608608
);
609609
}
610+
611+
export interface ListBindingsPayload {}
612+
613+
export interface ListBindingsResult {
614+
wechat: boolean;
615+
phone: boolean;
616+
agora: boolean;
617+
apple: boolean;
618+
github: boolean;
619+
google: boolean;
620+
}
621+
622+
export async function listBindings(): Promise<ListBindingsResult> {
623+
return await post<ListBindingsPayload, ListBindingsResult>("user/binding/list", {});
624+
}
625+
626+
export interface SetBindingAuthUUIDResult {}
627+
628+
export async function setBindingAuthUUID(authUUID: string): Promise<void> {
629+
await post<SetAuthUUIDPayload, SetBindingAuthUUIDResult>("user/binding/set-auth-uuid", {
630+
authUUID,
631+
});
632+
}
633+
634+
export interface BindingProcessResult {
635+
processing: boolean;
636+
status: boolean;
637+
}
638+
639+
export async function bindingProcess(authUUID: string): Promise<BindingProcessResult> {
640+
try {
641+
const ret = await post2<SetAuthUUIDPayload, {}>("user/binding/process", {
642+
authUUID,
643+
});
644+
if (ret === PROCESSING) {
645+
return {
646+
processing: true,
647+
status: false,
648+
};
649+
}
650+
return {
651+
processing: false,
652+
status: true,
653+
};
654+
} catch {
655+
return {
656+
processing: false,
657+
status: false,
658+
};
659+
}
660+
}

desktop/renderer-app/src/api-middleware/flatServer/utils.ts

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,45 @@ export async function post<Payload, Result>(
4545
return res.data;
4646
}
4747

48+
export const PROCESSING = Symbol("Process");
49+
50+
// TODO: Refactor `post` `getNoAuth` APIs to support Status.X
51+
export async function post2<Payload, Result>(
52+
action: string,
53+
payload: Payload,
54+
params?: AxiosRequestConfig["params"],
55+
token?: string,
56+
): Promise<Result | typeof PROCESSING> {
57+
const config: AxiosRequestConfig = {
58+
params,
59+
};
60+
61+
const Authorization = token || globalStore.userInfo?.token;
62+
if (!Authorization) {
63+
throw new ServerRequestError(RequestErrorCode.NeedLoginAgain);
64+
}
65+
66+
config.headers = {
67+
Authorization: "Bearer " + Authorization,
68+
};
69+
70+
const { data: res } = await Axios.post<FlatServerResponse<Result> | { status: Status.Process }>(
71+
`${FLAT_SERVER_VERSIONS.V1}/${action}`,
72+
payload,
73+
config,
74+
);
75+
76+
if (res.status === Status.Process) {
77+
return PROCESSING;
78+
}
79+
80+
if (res.status === Status.Success) {
81+
return res.data;
82+
}
83+
84+
throw new ServerRequestError(res.code);
85+
}
86+
4887
export async function postNotAuth<Payload, Result>(
4988
action: string,
5089
payload: Payload,

desktop/renderer-app/src/pages/LoginPage/WeChatLogin.tsx

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,11 @@ export const WeChatLogin = observer(function WeChatLogin({ setLoginResult }: WeC
7070

7171
export default WeChatLogin;
7272

73-
function getQRCodeURL(authUUID: string): string {
74-
const redirectURL = encodeURIComponent(`${FLAT_SERVER_LOGIN.WECHAT_CALLBACK}`);
73+
export function getQRCodeURL(
74+
authUUID: string,
75+
redirect_uri: string = FLAT_SERVER_LOGIN.WECHAT_CALLBACK,
76+
): string {
77+
const redirectURL = encodeURIComponent(`${redirect_uri}`);
7578
const qrCodeStyle = `
7679
.impowerBox .qrcode {
7780
width: 238px;
Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import classNames from "classnames";
2+
import { v4 } from "uuid";
3+
import React, { useCallback, useEffect, useState } from "react";
4+
import { useTranslation } from "react-i18next";
5+
import { message, Modal } from "antd";
6+
import { WechatFilled } from "@ant-design/icons";
7+
8+
import { getQRCodeURL } from "../../../LoginPage/WeChatLogin";
9+
import { setBindingAuthUUID, bindingProcess } from "../../../../api-middleware/flatServer";
10+
import { FLAT_SERVER_USER_BINDING } from "../../../../api-middleware/flatServer/constants";
11+
import { useSafePromise } from "../../../../utils/hooks/lifecycle";
12+
13+
export interface BindingWeChatProps {
14+
isBind: boolean;
15+
onRefresh: () => void;
16+
}
17+
18+
export const BindingWeChat: React.FC<BindingWeChatProps> = ({ isBind, onRefresh }) => {
19+
const sp = useSafePromise();
20+
const { t } = useTranslation();
21+
const [authUUID, setAuthUUID] = useState("");
22+
const [qrCodeURL, setQRCodeURL] = useState("");
23+
24+
const cancel = useCallback((): void => {
25+
setAuthUUID("");
26+
setQRCodeURL("");
27+
onRefresh();
28+
}, [onRefresh]);
29+
30+
useEffect(() => {
31+
let timer = NaN;
32+
async function waitUntilBindFinish(): Promise<void> {
33+
const result = await sp(bindingProcess(authUUID));
34+
if (result.processing) {
35+
timer = window.setTimeout(waitUntilBindFinish, 2000);
36+
} else {
37+
if (!result.status) {
38+
message.info(t("bind-wechat-failed"));
39+
}
40+
cancel();
41+
}
42+
}
43+
if (qrCodeURL && authUUID) {
44+
waitUntilBindFinish();
45+
return () => {
46+
Number.isNaN(timer) || window.clearTimeout(timer);
47+
};
48+
}
49+
return;
50+
}, [authUUID, cancel, onRefresh, qrCodeURL, sp, t]);
51+
52+
const bindWeChat = async (): Promise<void> => {
53+
const authUUID = v4();
54+
setAuthUUID(authUUID);
55+
await sp(setBindingAuthUUID(authUUID));
56+
setQRCodeURL(getQRCodeURL(authUUID, FLAT_SERVER_USER_BINDING.WECHAT_CALLBACK));
57+
};
58+
59+
const unbind = (): void => {
60+
message.info(t("bind-wechat-not-support-unbind"));
61+
};
62+
63+
return (
64+
<>
65+
<span
66+
className={classNames("binding-wechat", {
67+
"is-bind": isBind,
68+
})}
69+
title={isBind ? t("is-bind") : t("not-bind")}
70+
onClick={isBind ? unbind : bindWeChat}
71+
>
72+
<WechatFilled style={{ color: "#fff" }} />
73+
</span>
74+
<Modal
75+
centered
76+
destroyOnClose
77+
className="binding-wechat-modal"
78+
footer={null}
79+
title={t("bind-wechat")}
80+
visible={!!qrCodeURL}
81+
onCancel={cancel}
82+
>
83+
<iframe
84+
className="binding-wechat-iframe"
85+
frameBorder="0"
86+
scrolling="no"
87+
src={qrCodeURL}
88+
title="wechat"
89+
/>
90+
</Modal>
91+
</>
92+
);
93+
};
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { useCallback, useEffect, useState } from "react";
2+
3+
import { listBindings, ListBindingsResult } from "../../../../api-middleware/flatServer";
4+
import { useSafePromise } from "../../../../utils/hooks/lifecycle";
5+
6+
const defaultBindings: ListBindingsResult = {
7+
wechat: false,
8+
phone: false,
9+
agora: false,
10+
apple: false,
11+
github: false,
12+
google: false,
13+
};
14+
15+
export function useBindingList(): { bindings: ListBindingsResult; refresh: () => void } {
16+
const sp = useSafePromise();
17+
const [bindings, setBindings] = useState<ListBindingsResult>(defaultBindings);
18+
19+
const refresh = useCallback(() => {
20+
sp(listBindings()).then(setBindings);
21+
}, [sp]);
22+
23+
useEffect(refresh, [refresh]);
24+
25+
return { bindings, refresh };
26+
}

desktop/renderer-app/src/pages/UserSettingPage/GeneralSettingPage/index.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { useSafePromise } from "../../../utils/hooks/lifecycle";
1111
import { loginCheck, rename } from "../../../api-middleware/flatServer";
1212
import { ConfirmButtons } from "./ConfirmButtons";
1313
import { UploadAvatar, uploadAvatar } from "./UploadAvatar";
14+
import { useBindingList } from "./binding";
15+
import { BindingWeChat } from "./binding/WeChat";
1416

1517
enum SelectLanguage {
1618
Chinese,
@@ -26,6 +28,7 @@ export const GeneralSettingPage = (): React.ReactElement => {
2628

2729
const [name, setName] = useState(globalStore.userName || "");
2830
const [isRenaming, setRenaming] = useState(false);
31+
const { bindings, refresh: refreshBindings } = useBindingList();
2932

3033
async function changeUserName(): Promise<void> {
3134
if (name !== globalStore.userName) {
@@ -95,6 +98,9 @@ export const GeneralSettingPage = (): React.ReactElement => {
9598
/>
9699
<ConfirmButtons onConfirm={changeUserName} />
97100
</div>
101+
<div className="general-setting-binding-methods">
102+
<BindingWeChat isBind={bindings.wechat} onRefresh={refreshBindings} />
103+
</div>
98104
</div>
99105
<div className="general-setting-checkbox">
100106
<Checkbox checked={openAtLogin} onClick={toggleOpenAtLogin}>

desktop/renderer-app/src/pages/UserSettingPage/GeneralSettingPage/style.less

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,39 @@
108108
}
109109
}
110110

111+
.general-setting-binding-profile {
112+
padding-top: 12px;
113+
}
114+
115+
.general-setting-binding-methods {
116+
line-height: 40px;
117+
}
118+
119+
.binding-wechat {
120+
display: inline-flex;
121+
align-items: center;
122+
justify-content: center;
123+
background-color: var(--grey-6);
124+
width: 24px;
125+
height: 24px;
126+
border-radius: 50%;
127+
cursor: pointer;
128+
129+
&.is-bind {
130+
background-color: var(--green-6);
131+
}
132+
}
133+
134+
.binding-wechat-modal {
135+
.ant-modal-body {
136+
text-align: center;
137+
}
138+
}
139+
140+
.binding-wechat-iframe {
141+
height: 268px;
142+
}
143+
111144
.flat-color-scheme-dark {
112145
.general-setting-container {
113146
color: var(--text-stronger);

packages/flat-i18n/locales/en.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,5 +458,10 @@
458458
"username": "Name",
459459
"avatar": "Avatar",
460460
"upload-avatar": "Upload Avatar",
461-
"upload-avatar-failed": "Upload avatar failed"
461+
"upload-avatar-failed": "Upload avatar failed",
462+
"bind-wechat": "Bind WeChat",
463+
"bind-wechat-failed": "Failed to bind WeChat",
464+
"bind-wechat-not-support-unbind": "Not support unbind WeChat",
465+
"is-bind": "is bind",
466+
"not-bind": "not bind"
462467
}

packages/flat-i18n/locales/zh-CN.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,5 +458,10 @@
458458
"username": "昵称",
459459
"avatar": "头像",
460460
"upload-avatar": "上传头像",
461-
"upload-avatar-failed": "上传头像失败"
461+
"upload-avatar-failed": "上传头像失败",
462+
"bind-wechat": "绑定微信",
463+
"bind-wechat-failed": "绑定失败",
464+
"bind-wechat-not-support-unbind": "暂未支持解绑",
465+
"is-bind": "已绑定",
466+
"not-bind": "未绑定"
462467
}

0 commit comments

Comments
 (0)