diff --git a/src/components/Expanded.tsx b/src/components/Expanded.tsx
new file mode 100644
index 0000000..3630027
--- /dev/null
+++ b/src/components/Expanded.tsx
@@ -0,0 +1,13 @@
+import styled from "styled-components";
+
+export const Expanded = styled.div`
+ height: 100%;
+ width: 100%;
+`;
+
+export const ExpandedCenter = styled(Expanded)<{ gap?: number }>`
+ display: grid;
+ place-items: center;
+ place-content: center;
+ gap: ${({ gap }) => (gap != null ? `${gap * 4}px` : undefined)};
+`;
diff --git a/src/components/team/Selector.tsx b/src/components/team/Selector.tsx
deleted file mode 100644
index 659db18..0000000
--- a/src/components/team/Selector.tsx
+++ /dev/null
@@ -1,136 +0,0 @@
-import { Button, Flex } from "@radix-ui/themes";
-import { useState, type ReactElement } from "react";
-import { useNavigate } from "react-router-dom";
-import styled from "styled-components";
-import useSWRImmutable from "swr/immutable";
-import { match } from "ts-pattern";
-import { ErrorScreen } from "@/components/ErrorScreen";
-import { useMember } from "@/hooks/member";
-import { S } from "@/lib/consts";
-import { handleSWRError } from "@/lib/utils/swr";
-
-const FlexStyled = styled(Flex)`
- gap: 15rem;
-`;
-
-const ButtonStyle = styled(Button)`
- transform: scale(2);
- padding: 0;
- height: 100px;
- width: 100px;
-`;
-
-const DivCenter = styled.div`
- height: 100vh;
- display: flex;
- justify-content: center;
- align-items: center;
- top: 0;
-`;
-
-const DivStyled = styled.div`
- position: absolute;
- top: calc(50% + 100px);
- margin-top: 20px;
-`;
-
-const ButtonStyled = styled(Button)`
- font-weight: 600;
- font-family: sans-serif;
- font-size: 1rem;
-
- background-color: #e7e7e7;
- color: #00cdc2;
- border: 1px solid #00cdc2;
-
- width: fit-content;
- height: fit-content;
-
- padding: 1.2vh 1.3vw 1.2vh 1.8vw;
- margin-top: 4vh;
- margin-left: 0.3vw;
-
- border-radius: 50px;
- display: flex;
- align-items: center;
- justify-content: center;
- overflow: hidden;
- position: relative;
- z-index: 1;
-
- box-shadow:
- 6px 6px 16px #b5bec9,
- -6px -6px 16px #ffffff;
-
- transform-origin: 50% 50%;
- transition: 300ms;
-
- ::after {
- content: "";
- position: absolute;
- width: 100%;
- height: 120%;
- background-color: #00cdc2;
-
- top: 0;
- left: 0;
- z-index: -1;
- transform-origin: 100% 50%;
- transform: scaleX(0%);
- transition: transform 300ms;
- }
-
- &:hover {
- box-shadow: none;
- transform: scale(1.06);
- }
-
- &:hover ::after {
- transform-origin: 0% 50%;
- transform: scaleX(100%);
- transform: none;
- }
-`;
-
-export function TeamSelector(): ReactElement {
- const navigate = useNavigate();
- const { fetchJoinedTeams, markTeamNameAsSelected } = useMember();
- const swrJoinedTeams = useSWRImmutable("joinedTeams", fetchJoinedTeams);
- const [teamName, setTeamName] = useState("");
-
- return match(swrJoinedTeams)
- .with(S.Loading, () =>
Loading...
)
- .with(S.Success, ({ data }) => (
-
-
- {data.map((team) => (
- {
- markTeamNameAsSelected(team.name);
- setTeamName(team.name);
- }}
- size="4"
- >
-
-
- ))}
-
-
- {teamName !== "" && (
- {
- navigate("/ranking");
- }}
- size="4"
- >
- {teamName}に参加する
-
- )}
-
-
- ))
- .otherwise(({ data, error }) => (
-
- ));
-}
diff --git a/src/hooks/db/_esaDB.ts b/src/hooks/db/_esaDB.ts
index a23bcdb..7573bf6 100644
--- a/src/hooks/db/_esaDB.ts
+++ b/src/hooks/db/_esaDB.ts
@@ -6,6 +6,7 @@ import { type AnySchema } from "yup";
import { type useTeam as _useTeam } from "@/hooks/teams";
import { DB_VERSION, waitMs } from "@/lib/consts";
import { $config } from "@/lib/stores/config";
+import { enableIgnoreResCacheTemporarily } from "@/lib/stores/teams";
import { yPostData, type PostData } from "@/types/post-data/_struct";
import { type Nullable } from "@/types/utils";
@@ -16,6 +17,7 @@ export function useEsaDB(
postName: string;
schema: AnySchema;
atom: WritableAtom>;
+ initData: T;
},
) {
const { baseCategory } = useStore($config);
@@ -30,6 +32,8 @@ export function useEsaDB(
const category = `${baseCategory}/${config.postName}`;
const init = async (): Promise => {
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ using _ = enableIgnoreResCacheTemporarily();
const postId = await searchPostId().catch(() => undefined);
if (postId == null) {
@@ -61,7 +65,7 @@ export function useEsaDB(
const postData = {
_name: "esachievement",
_version: DB_VERSION,
- data: undefined,
+ data: config.initData,
} as const satisfies PostData;
return await __createNewPost({
diff --git a/src/hooks/db/achievements.ts b/src/hooks/db/achievements.ts
index a6624db..0822bcb 100644
--- a/src/hooks/db/achievements.ts
+++ b/src/hooks/db/achievements.ts
@@ -12,4 +12,5 @@ export const useAchievements = (useTeam: typeof _useTeam) =>
postName: "achievements",
schema: yAchievementsPostData,
atom: $currentAchievements,
+ initData: [],
});
diff --git a/src/hooks/db/unlocked-achievements.ts b/src/hooks/db/unlocked-achievements.ts
index cf03bad..56bee87 100644
--- a/src/hooks/db/unlocked-achievements.ts
+++ b/src/hooks/db/unlocked-achievements.ts
@@ -12,4 +12,5 @@ export const useUnlockedAchievements = (useTeam: typeof _useTeam) =>
postName: "unlockedAchievements",
schema: yUnlockedAchievementsPostData,
atom: $currentUnlockedAchievements,
+ initData: [],
});
diff --git a/src/hooks/member.ts b/src/hooks/member.ts
index e54b16c..13a4de8 100644
--- a/src/hooks/member.ts
+++ b/src/hooks/member.ts
@@ -3,7 +3,7 @@
import { useStore } from "@nanostores/react";
import { match } from "ts-pattern";
import { A } from "@/lib/consts";
-import { esaClient } from "@/lib/services/esa";
+import { getEsaClient } from "@/lib/services/esa";
import { $hasAuthenticated } from "@/lib/stores/auth";
import { $selectedTeamName } from "@/lib/stores/teams";
import { type InferResponseType } from "@/types/openapi";
@@ -18,7 +18,7 @@ export function useMember() {
const fetchJoinedTeams = async (): Promise<
InferResponseType<"/teams", "get">["teams"]
> => {
- const result = await esaClient.GET("/teams");
+ const result = await getEsaClient().GET("/teams");
return await match(result)
.with(A.Success, ({ data }) => data.teams)
.otherwise(async ({ response }) => {
@@ -35,7 +35,7 @@ export function useMember() {
const fetchCurrentMember = async (): Promise<
InferResponseType<"/user", "get">
> => {
- const result = await esaClient.GET("/user");
+ const result = await getEsaClient().GET("/user");
return await match(result)
.with(A.Success, ({ data }) => data)
.otherwise(async ({ response }) => {
diff --git a/src/hooks/teams.ts b/src/hooks/teams.ts
index 56af309..2b20f7b 100644
--- a/src/hooks/teams.ts
+++ b/src/hooks/teams.ts
@@ -1,7 +1,7 @@
import { useStore } from "@nanostores/react";
import { match } from "ts-pattern";
import { A } from "@/lib/consts";
-import { esaClient } from "@/lib/services/esa";
+import { getEsaClient } from "@/lib/services/esa";
import { $selectedTeamName } from "@/lib/stores/teams";
import {
type InferRequestBodyType,
@@ -35,7 +35,9 @@ export function useTeam() {
const fetchAbout = async (): Promise<
InferResponseType<"/teams/{team_name}", "get">
> =>
- await match(await esaClient.GET("/teams/{team_name}", paramsWithTeamName))
+ await match(
+ await getEsaClient().GET("/teams/{team_name}", paramsWithTeamName),
+ )
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
@@ -43,7 +45,7 @@ export function useTeam() {
InferResponseType<"/teams/{team_name}/stats", "get">
> =>
await match(
- await esaClient.GET("/teams/{team_name}/stats", paramsWithTeamName),
+ await getEsaClient().GET("/teams/{team_name}/stats", paramsWithTeamName),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
@@ -52,7 +54,10 @@ export function useTeam() {
InferResponseType<"/teams/{team_name}/members", "get">["members"]
> =>
await match(
- await esaClient.GET("/teams/{team_name}/members", paramsWithTeamName),
+ await getEsaClient().GET(
+ "/teams/{team_name}/members",
+ paramsWithTeamName,
+ ),
)
.with(A.Success, ({ data }) => data.members)
.otherwise(handleError);
@@ -61,7 +66,7 @@ export function useTeam() {
postBody: InferRequestBodyType<"/teams/{team_name}/posts", "post">["post"],
): Promise> =>
await match(
- await esaClient.POST("/teams/{team_name}/posts", {
+ await getEsaClient().POST("/teams/{team_name}/posts", {
...paramsWithTeamName,
body: {
post: postBody,
@@ -75,7 +80,7 @@ export function useTeam() {
category: string,
): Promise["posts"]> =>
await match(
- await esaClient.GET("/teams/{team_name}/posts", {
+ await getEsaClient().GET("/teams/{team_name}/posts", {
params: {
...paramsWithTeamName.params,
query: {
@@ -93,7 +98,7 @@ export function useTeam() {
InferResponseType<"/teams/{team_name}/posts/{post_number}", "get">
> =>
await match(
- await esaClient.GET("/teams/{team_name}/posts/{post_number}", {
+ await getEsaClient().GET("/teams/{team_name}/posts/{post_number}", {
params: {
path: {
...paramsWithTeamName.params.path,
@@ -115,7 +120,7 @@ export function useTeam() {
InferResponseType<"/teams/{team_name}/posts/{post_number}", "patch">
> =>
await match(
- await esaClient.PATCH("/teams/{team_name}/posts/{post_number}", {
+ await getEsaClient().PATCH("/teams/{team_name}/posts/{post_number}", {
params: {
path: {
...paramsWithTeamName.params.path,
@@ -130,7 +135,7 @@ export function useTeam() {
const deletePost = async (postNumber: number): Promise => {
await match(
- await esaClient.DELETE("/teams/{team_name}/posts/{post_number}", {
+ await getEsaClient().DELETE("/teams/{team_name}/posts/{post_number}", {
params: {
path: {
...paramsWithTeamName.params.path,
@@ -147,7 +152,7 @@ export function useTeam() {
InferResponseType<"/teams/{team_name}/emojis", "get">
> =>
await match(
- await esaClient.GET("/teams/{team_name}/emojis", paramsWithTeamName),
+ await getEsaClient().GET("/teams/{team_name}/emojis", paramsWithTeamName),
)
.with(A.Success, ({ data }) => data)
.otherwise(handleError);
diff --git a/src/lib/services/esa.ts b/src/lib/services/esa.ts
index 9ba8ff7..2e70471 100644
--- a/src/lib/services/esa.ts
+++ b/src/lib/services/esa.ts
@@ -1,7 +1,8 @@
-import createClient from "openapi-fetch";
+import createClient, { type MiddlewareRequest } from "openapi-fetch";
import { type paths } from "./esa.gen";
import { getEnv } from "@/lib/consts";
import { $accessTokenData } from "@/lib/stores/auth";
+import { $shouldIgnoreResCache } from "@/lib/stores/teams";
import { type AccessTokenData } from "@/types/auth";
export function getAuthorizePageUrl(): string {
@@ -35,16 +36,31 @@ export async function requestAccessTokenData(
return await res.json();
}
-export const esaClient = createClient({
+function processRequest(req: MiddlewareRequest): MiddlewareRequest {
+ const token = $accessTokenData.get();
+ if (token == null) throw new Error("Access token has not been set");
+
+ req.headers.set("Authorization", `Bearer ${token.access_token}`);
+ return req;
+}
+
+const esaClient = createClient({
baseUrl: "/api",
});
esaClient.use({
- onRequest: async (req) => {
- const token = $accessTokenData.get();
- if (token == null) throw new Error("Access token has not been set");
+ onRequest: processRequest,
+});
- req.headers.set("Authorization", `Bearer ${token.access_token}`);
- return req;
- },
+const esaClientUnCached = createClient({
+ baseUrl: "/api",
+ cache: "no-cache",
});
+
+esaClientUnCached.use({
+ onRequest: processRequest,
+});
+
+export function getEsaClient(): typeof esaClient {
+ return $shouldIgnoreResCache.get() ? esaClientUnCached : esaClient;
+}
diff --git a/src/lib/stores/teams.ts b/src/lib/stores/teams.ts
index e10dc47..05d0742 100644
--- a/src/lib/stores/teams.ts
+++ b/src/lib/stores/teams.ts
@@ -1,7 +1,25 @@
+/* eslint-disable no-console */
+
import { persistentAtom } from "@nanostores/persistent";
+import { atom } from "nanostores";
import { getLocalStorageKey } from "@/lib/consts";
export const $selectedTeamName = persistentAtom(
getLocalStorageKey("selectedTeamName"),
undefined,
);
+
+export const $shouldIgnoreResCache = atom(false);
+
+// eslint-disable-next-line @typescript-eslint/explicit-function-return-type
+export function enableIgnoreResCacheTemporarily() {
+ console.warn("Ignore response cache temporarily");
+ $shouldIgnoreResCache.set(true);
+
+ return {
+ [Symbol.dispose]: () => {
+ console.warn("Stop ignoring response cache");
+ $shouldIgnoreResCache.set(false);
+ },
+ };
+}
diff --git a/src/lib/utils/fetchers.ts b/src/lib/utils/fetchers.ts
index a9fab42..9cdfe3f 100644
--- a/src/lib/utils/fetchers.ts
+++ b/src/lib/utils/fetchers.ts
@@ -1,7 +1,5 @@
import { type Member } from "@/types/member";
-import { type Achievement } from "@/types/post-data/achievements";
import { type UnlockedAchievement } from "@/types/post-data/unlocked-achievements";
-import { type Nullable } from "@/types/utils";
export function getUnlockedAchievementsFromMember(
member: Member,
@@ -9,92 +7,3 @@ export function getUnlockedAchievementsFromMember(
): UnlockedAchievement[] {
return unlockedAchievements.filter((u) => u.memberEmail === member.email);
}
-
-export async function fetchMembersAndUnlockedAchievements(
- fetchMembers: () => Promise,
- fetchUnlockedAchievements: () => Promise>,
-): Promise<{
- members: Member[];
- unlockedAchievements: UnlockedAchievement[];
-}> {
- const members = await fetchMembers();
- const unlockedAchievements = await fetchUnlockedAchievements();
-
- if (members == null) {
- throw new Error("`members` is null! Maybe you forgot to call `init()`");
- }
-
- if (unlockedAchievements == null) {
- throw new Error(
- "`unlockedAchievements` is null! Maybe you forgot to call `init()`",
- );
- }
-
- return {
- members,
- unlockedAchievements,
- };
-}
-
-export async function fetchAchievementsWithUnlocked(
- fetchAchievements: () => Promise>,
- fetchUnlockedAchievements: () => Promise>,
-): Promise<{
- achievements: Achievement[];
- unlockedAchievements: UnlockedAchievement[];
-}> {
- const achievements = await fetchAchievements();
- const unlockedAchievements = await fetchUnlockedAchievements();
-
- if (achievements == null) {
- throw new Error(
- "`achievements` is null! Maybe you forgot to call `init()`",
- );
- }
-
- if (unlockedAchievements == null) {
- throw new Error(
- "`unlockedAchievements` is null! Maybe you forgot to call `init()`",
- );
- }
-
- return {
- achievements,
- unlockedAchievements,
- };
-}
-export async function fetchMembersAndUnlockedAchievementsAndAchievements(
- fetchMembers: () => Promise,
- fetchAchievements: () => Promise>,
- fetchUnlockedAchievements: () => Promise>,
-): Promise<{
- members: Member[];
- achievements: Achievement[];
- unlockedAchievements: UnlockedAchievement[];
-}> {
- const members = await fetchMembers();
- const achievements = await fetchAchievements();
- const unlockedAchievements = await fetchUnlockedAchievements();
-
- if (members == null) {
- throw new Error("`members` is null! Maybe you forgot to call `init()`");
- }
-
- if (achievements == null) {
- throw new Error(
- "`achievements` is null! Maybe you forgot to call `init()`",
- );
- }
-
- if (unlockedAchievements == null) {
- throw new Error(
- "`unlockedAchievements` is null! Maybe you forgot to call `init()`",
- );
- }
-
- return {
- members,
- achievements,
- unlockedAchievements,
- };
-}
diff --git a/src/pages/_app.tsx b/src/pages/_app.tsx
index 81ea41e..831dbf2 100644
--- a/src/pages/_app.tsx
+++ b/src/pages/_app.tsx
@@ -4,6 +4,7 @@ import { type ReactElement } from "react";
import { Outlet, useRouteError } from "react-router-dom";
import styled from "styled-components";
import { Center } from "@/components/Center";
+import { Expanded } from "@/components/Expanded";
import { Header } from "@/components/Header";
import { Redirects } from "@/components/Redirects";
@@ -48,7 +49,6 @@ const Main = styled.main`
font-family: "Noto Sans JP Variable";
word-break: keep-all;
`;
-const BodyStyle = styled.div``;
const ThemeStyle = styled(Theme)`
background-color: #e7e7e7;
overflow: hidden;
@@ -64,9 +64,9 @@ export default function Layout(): ReactElement {
-
+
-
+
diff --git a/src/pages/auth/callback/index.tsx b/src/pages/auth/callback/index.tsx
index 4afb883..f770012 100644
--- a/src/pages/auth/callback/index.tsx
+++ b/src/pages/auth/callback/index.tsx
@@ -1,16 +1,249 @@
import { useStore } from "@nanostores/react";
-import { type ReactElement } from "react";
+import { Button, Flex } from "@radix-ui/themes";
+import { type ReactNode, useEffect, useState, type ReactElement } from "react";
+import { useNavigate } from "react-router-dom";
+import styled from "styled-components";
import useSWRImmutable from "swr/immutable";
-import { match } from "ts-pattern";
-import { Center } from "@/components/Center";
+import { match, P } from "ts-pattern";
import { ErrorScreen } from "@/components/ErrorScreen";
-import { TeamSelector } from "@/components/team/Selector";
-import { S } from "@/lib/consts";
+import { Expanded, ExpandedCenter } from "@/components/Expanded";
+import { useAchievements } from "@/hooks/db/achievements";
+import { useUnlockedAchievements } from "@/hooks/db/unlocked-achievements";
+import { useMember } from "@/hooks/member";
+import { useTeam } from "@/hooks/teams";
+import { APP_NAME, S } from "@/lib/consts";
import { requestAccessTokenData } from "@/lib/services/esa";
import { $accessTokenData } from "@/lib/stores/auth";
import { handleSWRError } from "@/lib/utils/swr";
-import { useNavigate } from "@/router";
import { type AccessTokenData } from "@/types/auth";
+import { type ArrayElem } from "@/types/utils";
+
+const Heading = styled.h1`
+ font-size: 1.5rem;
+ font-weight: bold;
+`;
+
+const TeamIcon = styled.img`
+ border-radius: 20px;
+ cursor: pointer;
+ width: 130px;
+ height: 130px;
+
+ &:hover,
+ &[aria-selected="true"] {
+ box-shadow: 0 0 10px #00cdc2;
+ }
+`;
+
+const TeamInfo = styled(Flex)`
+ direction: column;
+ align-items: center;
+
+ > p {
+ min-height: 1lh;
+ }
+
+ p:first-child {
+ font-size: 1.2rem;
+ font-weight: bold;
+ }
+ p:last-child {
+ color: gray;
+ }
+`;
+
+const ButtonStyled = styled(Button)`
+ font-weight: 600;
+ font-family: sans-serif;
+ font-size: 1rem;
+ cursor: pointer;
+
+ background-color: #e7e7e7;
+ color: #00cdc2;
+ border: 1px solid #00cdc2;
+
+ width: fit-content;
+ height: fit-content;
+
+ padding: 1.2vh 1.3vw 1.2vh 1.8vw;
+
+ border-radius: 50px;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ overflow: hidden;
+ position: relative;
+ z-index: 1;
+
+ box-shadow:
+ 6px 6px 16px #b5bec9,
+ -6px -6px 16px #ffffff;
+
+ transform-origin: 50% 50%;
+ transition: 300ms;
+
+ ::after {
+ content: "";
+ position: absolute;
+ width: 100%;
+ height: 120%;
+ background-color: #00cdc2;
+
+ top: 0;
+ left: 0;
+ z-index: -1;
+ transform-origin: 100% 50%;
+ transform: scaleX(0%);
+ transition: transform 300ms;
+ }
+
+ &:hover {
+ box-shadow: none;
+ transform: scale(1.06);
+
+ &::after {
+ transform-origin: 0% 50%;
+ transform: scaleX(100%);
+ transform: none;
+ }
+ }
+
+ &:disabled {
+ cursor: not-allowed;
+ opacity: 0.3;
+ transform: scale(1.06);
+ box-shadow: none;
+ }
+`;
+
+type Team = ArrayElem<
+ Awaited["fetchJoinedTeams"]>>
+>;
+
+type InitStatus =
+ | {
+ type: "READY" | "LOADING" | "SUCCESS";
+ }
+ | {
+ type: "ERROR";
+ error: Error;
+ };
+
+function TeamSelectorLoading({
+ setInitStatus,
+}: {
+ setInitStatus: (status: InitStatus) => void;
+}): ReactNode {
+ const navigate = useNavigate();
+ const { init: initAchievements } = useAchievements(useTeam);
+ const { init: initUnlockedAchievements } = useUnlockedAchievements(useTeam);
+
+ async function initDB(): Promise {
+ await initAchievements();
+ await initUnlockedAchievements();
+
+ navigate("/ranking");
+ }
+
+ useEffect(() => {
+ void initDB()
+ .then(() => {
+ setInitStatus({ type: "SUCCESS" });
+ })
+ .catch((error) => {
+ setInitStatus({ type: "ERROR", error });
+ });
+ });
+
+ return `${APP_NAME} を初期化中...`;
+}
+
+function TeamSelector(): ReactElement {
+ const { fetchJoinedTeams, markTeamNameAsSelected } = useMember();
+
+ const [hoveredTeam, setHoveredTeamName] = useState();
+ const [selectedTeam, setSelectedTeamName] = useState();
+ const [initStatus, setInitStatus] = useState({
+ type: "READY",
+ });
+
+ const swrJoinedTeams = useSWRImmutable("joinedTeams", fetchJoinedTeams);
+ const activeTeam = selectedTeam ?? hoveredTeam;
+
+ return match(swrJoinedTeams)
+ .with(S.Loading, () => Loading...
)
+ .with(S.Success, ({ data }) => (
+
+ チームを選択してください
+
+ {data.map((team) => (
+ {
+ const alreadySelected = selectedTeam === team;
+ setSelectedTeamName(alreadySelected ? undefined : team);
+ }}
+ onMouseEnter={() => {
+ setHoveredTeamName(team);
+ }}
+ onMouseLeave={() => {
+ setHoveredTeamName(undefined);
+ }}
+ src={team.icon}
+ />
+ ))}
+
+
+ {activeTeam?.name}
+ {activeTeam?.description}
+
+
+ {
+ setInitStatus({ type: "LOADING" });
+ }}
+ size="4"
+ >
+ {match({
+ selectedTeam,
+ initStatus,
+ })
+ .with({ initStatus: { type: "LOADING" } }, () => {
+ if (selectedTeam == null) {
+ throw new Error("selectedTeam is null");
+ }
+ markTeamNameAsSelected(selectedTeam.name);
+
+ return ;
+ })
+ .with(
+ { initStatus: { type: "SUCCESS" } },
+ () => `${APP_NAME} を初期化しました!`,
+ )
+ .with(
+ { initStatus: { type: "ERROR" } },
+ ({ initStatus: { error } }) => ,
+ )
+ .with(
+ { selectedTeam: P.nullish },
+ () => "チームを選択してください…",
+ )
+ .with(
+ { selectedTeam: P.nonNullable },
+ () => `${activeTeam?.name} で参加`,
+ )
+ .exhaustive()}
+
+
+
+ ))
+ .otherwise(({ data, error }) => (
+
+ ));
+}
export default function Page(): ReactElement {
const swrTokenAndTeams = useSWRImmutable("tokenAndTeams", fetchTokenAndTeams);
@@ -35,13 +268,13 @@ export default function Page(): ReactElement {
}
return (
-
+
{match(swrTokenAndTeams)
.with(S.Loading, () => Loading...
)
.with(S.Success, () => )
.otherwise(({ data, error }) => (
))}
-
+
);
}
diff --git a/src/pages/members/index.tsx b/src/pages/members/index.tsx
index 9688966..708b4ea 100644
--- a/src/pages/members/index.tsx
+++ b/src/pages/members/index.tsx
@@ -8,10 +8,7 @@ import { MemberCard } from "@/components/member/Card";
import { useUnlockedAchievements } from "@/hooks/db/unlocked-achievements";
import { useTeam } from "@/hooks/teams";
import { S } from "@/lib/consts";
-import {
- fetchMembersAndUnlockedAchievements,
- getUnlockedAchievementsFromMember,
-} from "@/lib/utils/fetchers";
+import { getUnlockedAchievementsFromMember } from "@/lib/utils/fetchers";
import { handleSWRError } from "@/lib/utils/swr";
const BoxStyle = styled(Box)`
@@ -23,16 +20,12 @@ const BoxStyle = styled(Box)`
export default function Page(): ReactElement {
const { fetchMembers } = useTeam();
const { fetch: fetchUnlockedAchievements } = useUnlockedAchievements(useTeam);
- const swrMembersAndUnlockedAchievements = useSWRImmutable(
- "membersAndUnlockedAchievements",
- async () =>
- await fetchMembersAndUnlockedAchievements(
- fetchMembers,
- fetchUnlockedAchievements,
- ),
- );
+ const swrMU = useSWRImmutable("mu", async () => ({
+ members: await fetchMembers(),
+ unlockedAchievements: await fetchUnlockedAchievements(),
+ }));
- return match(swrMembersAndUnlockedAchievements)
+ return match(swrMU)
.with(S.Loading, () => Loading...
)
.with(S.Success, ({ data: { members, unlockedAchievements } }) => (
<>
diff --git a/src/pages/ranking/index.tsx b/src/pages/ranking/index.tsx
index 441a79d..947eb75 100644
--- a/src/pages/ranking/index.tsx
+++ b/src/pages/ranking/index.tsx
@@ -10,10 +10,7 @@ import { useAchievements } from "@/hooks/db/achievements";
import { useUnlockedAchievements } from "@/hooks/db/unlocked-achievements";
import { useTeam } from "@/hooks/teams";
import { S } from "@/lib/consts";
-import {
- fetchMembersAndUnlockedAchievementsAndAchievements,
- getUnlockedAchievementsFromMember,
-} from "@/lib/utils/fetchers";
+import { getUnlockedAchievementsFromMember } from "@/lib/utils/fetchers";
import { handleSWRError } from "@/lib/utils/swr";
const RankingListStyle = styled.div`
@@ -54,17 +51,13 @@ export default function Page(): ReactElement {
const { fetchMembers } = useTeam();
const { fetch: fetchAchievements } = useAchievements(useTeam);
const { fetch: fetchUnlockedAchievements } = useUnlockedAchievements(useTeam);
- const swrMembersAndUnlockedAchievementsAndAchievements = useSWRImmutable(
- "membersAndUnlockedAchievementsAndAchievements",
- async () =>
- await fetchMembersAndUnlockedAchievementsAndAchievements(
- fetchMembers,
- fetchAchievements,
- fetchUnlockedAchievements,
- ),
- );
+ const swrAMU = useSWRImmutable("amu", async () => ({
+ achievements: await fetchAchievements(),
+ members: await fetchMembers(),
+ unlockedAchievements: await fetchUnlockedAchievements(),
+ }));
- return match(swrMembersAndUnlockedAchievementsAndAchievements)
+ return match(swrAMU)
.with(S.Loading, () => Loading...
)
.with(
S.Success,
diff --git a/src/pages/unlocked/index.tsx b/src/pages/unlocked/index.tsx
index e69e85b..ddb457a 100644
--- a/src/pages/unlocked/index.tsx
+++ b/src/pages/unlocked/index.tsx
@@ -10,7 +10,6 @@ import { useUnlockedAchievements } from "@/hooks/db/unlocked-achievements";
import { useMember } from "@/hooks/member";
import { useTeam } from "@/hooks/teams";
import { S } from "@/lib/consts";
-import { fetchAchievementsWithUnlocked } from "@/lib/utils/fetchers";
import { handleSWRError } from "@/lib/utils/swr";
import { type CurrentMember } from "@/types/member";
import { type Achievement } from "@/types/post-data/achievements";
@@ -29,18 +28,11 @@ export default function Page(): ReactElement {
useUnlockedAchievements(useTeam);
const swrAchievementsWithUnlocked = useSWRImmutable(
"achievementsWithUnlocked",
- async () => {
- const achievementsKit = await fetchAchievementsWithUnlocked(
- fetchAchievements,
- fetchUnlockedAchievements,
- );
- const currentMember = await fetchCurrentMember();
-
- return {
- ...achievementsKit,
- currentMember,
- };
- },
+ async () => ({
+ achievements: await fetchAchievements(),
+ unlockedAchievements: await fetchUnlockedAchievements(),
+ currentMember: await fetchCurrentMember(),
+ }),
);
const [isUILocked, setIsUILocked] = useState(false);
diff --git a/src/types/post-data/achievements.ts b/src/types/post-data/achievements.ts
index d6cdb49..917095d 100644
--- a/src/types/post-data/achievements.ts
+++ b/src/types/post-data/achievements.ts
@@ -13,5 +13,5 @@ export const yAchievement = yup.object().shape({
export type Achievement = InferType;
export type AchievementTag = Achievement["tags"];
-export const yAchievementsPostData = array().of(yAchievement);
+export const yAchievementsPostData = array().of(yAchievement).required();
export type AchievementsPostData = InferType;
diff --git a/src/types/post-data/unlocked-achievements.ts b/src/types/post-data/unlocked-achievements.ts
index e63dd73..9c44580 100644
--- a/src/types/post-data/unlocked-achievements.ts
+++ b/src/types/post-data/unlocked-achievements.ts
@@ -8,7 +8,8 @@ export const yUnlockedAchievement = yup.object().shape({
});
export const yUnlockedAchievementsPostData = yup
.array()
- .of(yUnlockedAchievement);
+ .of(yUnlockedAchievement)
+ .required();
export type UnlockedAchievement = InferType;
export type UnlockedAchievementsPostData = InferType<