Commit d99ad1d3 authored by Chunchi Che's avatar Chunchi Che

Merge branch 'feat/logout' into 'main'

支持萌卡账号登出

See merge request mycard/Neos!276
parents ca83bae4 ce54ee25
Pipeline #23220 passed with stages
in 10 minutes and 14 seconds
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
"stringsUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/strings.conf", "stringsUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/strings.conf",
"lflistUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/lflist.conf", "lflistUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/lflist.conf",
"replayUrl":"replay.neos.moe", "replayUrl":"replay.neos.moe",
"accountUrl":"https://accounts.moecube.com", "loginUrl":"https://accounts.moecube.com/signin",
"logoutUrl":"https://accounts.moecube.com/signout",
"profileUrl":"https://accounts.moecube.com/profiles", "profileUrl":"https://accounts.moecube.com/profiles",
"streamInterval":20, "streamInterval":20,
"startDelay":1000, "startDelay":1000,
......
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
"stringsUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/strings.conf", "stringsUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/strings.conf",
"lflistUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/lflist.conf", "lflistUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/lflist.conf",
"replayUrl":"replay.neos.moe", "replayUrl":"replay.neos.moe",
"accountUrl":"https://accounts.moecube.com", "loginUrl":"https://accounts.moecube.com/signin",
"logoutUrl":"https://accounts.moecube.com/signout",
"profileUrl":"https://accounts.moecube.com/profiles", "profileUrl":"https://accounts.moecube.com/profiles",
"streamInterval":20, "streamInterval":20,
"startDelay":1000, "startDelay":1000,
......
...@@ -15,3 +15,7 @@ export const setCookie = <T>(key: CookieKeys, value: T) => { ...@@ -15,3 +15,7 @@ export const setCookie = <T>(key: CookieKeys, value: T) => {
expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 60), // 两个月的cookie,应该很充裕 expires: new Date(Date.now() + 1000 * 60 * 60 * 24 * 60), // 两个月的cookie,应该很充裕
}); });
}; };
export const removeCookie = (key: CookieKeys) => {
cookies.remove(key);
};
/** 构建一个单点登录(Single Sign-On,简称SSO)的URL */
import { useConfig } from "@/config";
const NeosConfig = useConfig();
export function getSSOSignInUrl(callbackUrl: string): string {
const params = new URLSearchParams({
sso: btoa(new URLSearchParams({ return_sso_url: callbackUrl }).toString()),
});
const url = new URL(NeosConfig.loginUrl);
url.search = params.toString();
return url.toString();
}
export function getSSOSignOutUrl(callbackUrl: string): string {
const params = new URLSearchParams({
redirect: callbackUrl,
});
const url = new URL(NeosConfig.logoutUrl);
url.search = params.toString();
return url.toString();
}
// Collection of APIs provided by MyCard // Collection of APIs provided by MyCard
export * from "./account";
export * from "./match"; export * from "./match";
...@@ -9,6 +9,12 @@ import { ...@@ -9,6 +9,12 @@ import {
} from "react-router-dom"; } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import {
CookieKeys,
getSSOSignInUrl,
getSSOSignOutUrl,
removeCookie,
} from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { accountStore } from "@/stores"; import { accountStore } from "@/stores";
...@@ -49,16 +55,26 @@ const HeaderBtn: React.FC< ...@@ -49,16 +55,26 @@ const HeaderBtn: React.FC<
export const Component = () => { export const Component = () => {
// 捕获SSO登录 // 捕获SSO登录
const location = useLocation(); const routerLocation = useLocation();
useEffect(() => { useEffect(() => {
location.search && handleSSOLogin(location.search); routerLocation.search && handleSSOLogin(routerLocation.search);
}, [location.search]); }, [routerLocation.search]);
// 根据是否登录,显示内容 // 根据是否登录,显示内容
const logined = Boolean(useSnapshot(accountStore).user); const logined = Boolean(useSnapshot(accountStore).user);
const { pathname } = useLocation(); const { pathname } = routerLocation;
const pathnamesHideHeader = ["/waitroom", "/duel"]; const pathnamesHideHeader = ["/waitroom", "/duel"];
const callbackUrl = `${location.origin}/match/`;
const onLogin = () => location.replace(getSSOSignInUrl(callbackUrl));
const onLogout = () => {
removeCookie(CookieKeys.USER);
accountStore.logout();
// 跳转SSO登出
location.replace(getSSOSignOutUrl(callbackUrl));
};
return ( return (
<> <>
{!pathnamesHideHeader.includes(pathname) && ( {!pathnamesHideHeader.includes(pathname) && (
...@@ -112,6 +128,10 @@ export const Component = () => { ...@@ -112,6 +128,10 @@ export const Component = () => {
</a> </a>
), ),
}, },
{
label: logined ? "退出登录" : "登录萌卡",
onClick: logined ? onLogout : onLogin,
},
].map((x, key) => ({ ...x, key })), ].map((x, key) => ({ ...x, key })),
}} }}
> >
......
...@@ -11,7 +11,7 @@ import { useSnapshot } from "valtio"; ...@@ -11,7 +11,7 @@ import { useSnapshot } from "valtio";
import { match } from "@/api"; import { match } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { accountStore, deckStore, IDeck, roomStore } from "@/stores"; import { accountStore, deckStore, roomStore } from "@/stores";
import { Background, IconFont, Select } from "@/ui/Shared"; import { Background, IconFont, Select } from "@/ui/Shared";
import styles from "./index.module.scss"; import styles from "./index.module.scss";
...@@ -27,12 +27,12 @@ export const Component: React.FC = () => { ...@@ -27,12 +27,12 @@ export const Component: React.FC = () => {
const [server, setServer] = useState( const [server, setServer] = useState(
`${serverList[0].ip}:${serverList[0].port}`, `${serverList[0].ip}:${serverList[0].port}`,
); );
const { decks } = useSnapshot(deckStore); const { decks } = deckStore;
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0]))); const [deckName, setDeckName] = useState(decks.at(0)?.deckName ?? "");
const user = accountStore.user; const user = accountStore.user;
const { joined } = useSnapshot(roomStore); const { joined } = useSnapshot(roomStore);
const [singleLoading, setSingleLoading] = useState(false); // 单人模式的loading状态 const [singleLoading, setSingleLoading] = useState(false); // 单人模式的loading状态
const [matchLoading, setMatchLoading] = useState(false); const [matchLoading, setMatchLoading] = useState(false); // 匹配模式的loading状态
const navigate = useNavigate(); const navigate = useNavigate();
// 竞技匹配 // 竞技匹配
...@@ -107,13 +107,13 @@ export const Component: React.FC = () => { ...@@ -107,13 +107,13 @@ export const Component: React.FC = () => {
<Select <Select
title="卡组" title="卡组"
showSearch showSearch
value={deck.deckName} value={deckName}
style={{ width: 200 }} style={{ width: 200 }}
onChange={(value) => { onChange={(value) => {
// @ts-ignore // @ts-ignore
const item = deckStore.get(value); const item = deckStore.get(value);
if (item) { if (item) {
setDeck(item); setDeckName(item.deckName);
} else { } else {
message.error(`Deck ${value} not found`); message.error(`Deck ${value} not found`);
} }
......
...@@ -2,6 +2,7 @@ import { RightOutlined } from "@ant-design/icons"; ...@@ -2,6 +2,7 @@ import { RightOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router-dom"; import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio"; import { useSnapshot } from "valtio";
import { getSSOSignInUrl } from "@/api";
import { useConfig } from "@/config"; import { useConfig } from "@/config";
import { accountStore } from "@/stores"; import { accountStore } from "@/stores";
import { Background, SpecialButton } from "@/ui/Shared"; import { Background, SpecialButton } from "@/ui/Shared";
...@@ -49,7 +50,7 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => { ...@@ -49,7 +50,7 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
const loginViaSSO = () => const loginViaSSO = () =>
// 跳转回match页 // 跳转回match页
location.replace(getSSOUrl(`${location.origin}/match/}`)); location.replace(getSSOSignInUrl(`${location.origin}/match/`));
const goToMatch = () => navigate("/match"); const goToMatch = () => navigate("/match");
...@@ -60,15 +61,3 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => { ...@@ -60,15 +61,3 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
</SpecialButton> </SpecialButton>
); );
}; };
/** 构建一个单点登录(Single Sign-On,简称SSO)的URL */
function getSSOUrl(callbackUrl: string): string {
const params = new URLSearchParams({
sso: btoa(new URLSearchParams({ return_sso_url: callbackUrl }).toString()),
});
const url = new URL(NeosConfig.accountUrl);
url.search = params.toString();
return url.toString();
}
...@@ -43,8 +43,10 @@ export const Component: React.FC = () => { ...@@ -43,8 +43,10 @@ export const Component: React.FC = () => {
const { message } = App.useApp(); const { message } = App.useApp();
const { user } = useSnapshot(accountStore); const { user } = useSnapshot(accountStore);
const [collapsed, setCollapsed] = useState(false); const [collapsed, setCollapsed] = useState(false);
const { decks } = useSnapshot(deckStore); const { decks } = deckStore;
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0]))); const defaultDeck =
decks.length > 0 ? JSON.parse(JSON.stringify(decks[0])) : undefined;
const [deck, setDeck] = useState<IDeck | undefined>(defaultDeck);
const room = useSnapshot(roomStore); const room = useSnapshot(roomStore);
const { errorMsg } = room; const { errorMsg } = room;
const me = room.getMePlayer(); const me = room.getMePlayer();
...@@ -104,12 +106,16 @@ export const Component: React.FC = () => { ...@@ -104,12 +106,16 @@ export const Component: React.FC = () => {
className={styles["btn-join"]} className={styles["btn-join"]}
onClick={() => { onClick={() => {
if (me?.state === PlayerState.NO_READY) { if (me?.state === PlayerState.NO_READY) {
sendUpdateDeck(deck); if (deck) {
// 设置side里面的卡组 sendUpdateDeck(deck);
sideStore.deck = deck; // 设置side里面的卡组
// 设置额外卡组数据 sideStore.deck = deck;
window.myExtraDeckCodes = [...deck.extra]; // 设置额外卡组数据
sendHsReady(); window.myExtraDeckCodes = [...deck.extra];
sendHsReady();
} else {
message.error("请先选择卡组");
}
} else { } else {
sendHsNotReady(); sendHsNotReady();
} }
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment