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 @@
"stringsUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/strings.conf",
"lflistUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/lflist.conf",
"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",
"streamInterval":20,
"startDelay":1000,
......
......@@ -13,7 +13,8 @@
"stringsUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/strings.conf",
"lflistUrl":"https://cdn02.moecube.com:444/ygopro-database/zh-CN/lflist.conf",
"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",
"streamInterval":20,
"startDelay":1000,
......
......@@ -15,3 +15,7 @@ export const setCookie = <T>(key: CookieKeys, value: T) => {
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
export * from "./account";
export * from "./match";
......@@ -9,6 +9,12 @@ import {
} from "react-router-dom";
import { useSnapshot } from "valtio";
import {
CookieKeys,
getSSOSignInUrl,
getSSOSignOutUrl,
removeCookie,
} from "@/api";
import { useConfig } from "@/config";
import { accountStore } from "@/stores";
......@@ -49,16 +55,26 @@ const HeaderBtn: React.FC<
export const Component = () => {
// 捕获SSO登录
const location = useLocation();
const routerLocation = useLocation();
useEffect(() => {
location.search && handleSSOLogin(location.search);
}, [location.search]);
routerLocation.search && handleSSOLogin(routerLocation.search);
}, [routerLocation.search]);
// 根据是否登录,显示内容
const logined = Boolean(useSnapshot(accountStore).user);
const { pathname } = useLocation();
const { pathname } = routerLocation;
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 (
<>
{!pathnamesHideHeader.includes(pathname) && (
......@@ -112,6 +128,10 @@ export const Component = () => {
</a>
),
},
{
label: logined ? "退出登录" : "登录萌卡",
onClick: logined ? onLogout : onLogin,
},
].map((x, key) => ({ ...x, key })),
}}
>
......
......@@ -11,7 +11,7 @@ import { useSnapshot } from "valtio";
import { match } from "@/api";
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 styles from "./index.module.scss";
......@@ -27,12 +27,12 @@ export const Component: React.FC = () => {
const [server, setServer] = useState(
`${serverList[0].ip}:${serverList[0].port}`,
);
const { decks } = useSnapshot(deckStore);
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0])));
const { decks } = deckStore;
const [deckName, setDeckName] = useState(decks.at(0)?.deckName ?? "");
const user = accountStore.user;
const { joined } = useSnapshot(roomStore);
const [singleLoading, setSingleLoading] = useState(false); // 单人模式的loading状态
const [matchLoading, setMatchLoading] = useState(false);
const [matchLoading, setMatchLoading] = useState(false); // 匹配模式的loading状态
const navigate = useNavigate();
// 竞技匹配
......@@ -107,13 +107,13 @@ export const Component: React.FC = () => {
<Select
title="卡组"
showSearch
value={deck.deckName}
value={deckName}
style={{ width: 200 }}
onChange={(value) => {
// @ts-ignore
const item = deckStore.get(value);
if (item) {
setDeck(item);
setDeckName(item.deckName);
} else {
message.error(`Deck ${value} not found`);
}
......
......@@ -2,6 +2,7 @@ import { RightOutlined } from "@ant-design/icons";
import { useNavigate } from "react-router-dom";
import { useSnapshot } from "valtio";
import { getSSOSignInUrl } from "@/api";
import { useConfig } from "@/config";
import { accountStore } from "@/stores";
import { Background, SpecialButton } from "@/ui/Shared";
......@@ -49,7 +50,7 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
const loginViaSSO = () =>
// 跳转回match页
location.replace(getSSOUrl(`${location.origin}/match/}`));
location.replace(getSSOSignInUrl(`${location.origin}/match/`));
const goToMatch = () => navigate("/match");
......@@ -60,15 +61,3 @@ const LoginBtn: React.FC<{ logined: boolean }> = ({ logined }) => {
</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 = () => {
const { message } = App.useApp();
const { user } = useSnapshot(accountStore);
const [collapsed, setCollapsed] = useState(false);
const { decks } = useSnapshot(deckStore);
const [deck, setDeck] = useState<IDeck>(JSON.parse(JSON.stringify(decks[0])));
const { decks } = deckStore;
const defaultDeck =
decks.length > 0 ? JSON.parse(JSON.stringify(decks[0])) : undefined;
const [deck, setDeck] = useState<IDeck | undefined>(defaultDeck);
const room = useSnapshot(roomStore);
const { errorMsg } = room;
const me = room.getMePlayer();
......@@ -104,12 +106,16 @@ export const Component: React.FC = () => {
className={styles["btn-join"]}
onClick={() => {
if (me?.state === PlayerState.NO_READY) {
if (deck) {
sendUpdateDeck(deck);
// 设置side里面的卡组
sideStore.deck = deck;
// 设置额外卡组数据
window.myExtraDeckCodes = [...deck.extra];
sendHsReady();
} else {
message.error("请先选择卡组");
}
} else {
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