Skip to content

Commit

Permalink
Merge pull request #57 from AndrewCK24/11-state-use-swr
Browse files Browse the repository at this point in the history
refactor(api): 重構接受邀請功能, 整合更換隊伍與接受邀請功能至 /api/users/teams
  • Loading branch information
AndrewCK24 authored May 24, 2024
2 parents 5baa357 + 9ec6371 commit 4e9c5b1
Show file tree
Hide file tree
Showing 14 changed files with 320 additions and 197 deletions.
89 changes: 39 additions & 50 deletions app/(protected)/team/TeamInfo.jsx
Original file line number Diff line number Diff line change
@@ -1,98 +1,87 @@
"use client";
import { useRouter } from "next/navigation";
import { useDispatch, useSelector } from "react-redux";
import { userActions } from "../user/user-slice";
import { teamActions } from "../team/team-slice";
import { FiFileText, FiCheck, FiX, FiEdit2 } from "react-icons/fi";
import { useUser, useTeam, useUserTeams } from "@/hooks/use-data";
import { FiUsers, FiCheck, FiX, FiEdit2 } from "react-icons/fi";
import { Button } from "@/components/ui/button";
import { CardHeader, CardTitle, CardBtnGroup } from "@/components/ui/card";
import { ListItem, ListItemText } from "@/app/components/common/List";
import {
Card,
CardHeader,
CardTitle,
CardBtnGroup,
} from "@/components/ui/card";
import TeamInfoTable from "@/app/(protected)/team/info/TeamInfoTable";

const TeamInfo = ({ teamData }) => {
const TeamInfo = ({ teamId, className }) => {
const router = useRouter();
const dispatch = useDispatch();
const invitingTeams = useSelector((state) => state.user.teams.inviting);
const isInviting =
invitingTeams.find((team) => team === teamData._id) ||
invitingTeams.find((team) => team._id === teamData._id);
const isDefaultTeamAdmin = useSelector((state) => state.team.admin);
const isAdmin =
teamData._id === useSelector((state) => state.team._id)
? isDefaultTeamAdmin
: false;
const { user, mutate: mutateUser } = useUser();
const { mutate: mutateUserTeams } = useUserTeams();
const { team, isLoading } = useTeam(teamId);
const isInviting = user?.teams.inviting.find((team) => team === teamId);
// TODO: 更新 admin 資料結構
const isAdmin = team?.admins
? team?.admins.find((admin) => admin.user_id === user?._id)
: false;

const handleAccept = async (teamId, accept) => {
if (!window.confirm(accept ? "確認接受邀請?" : "確認拒絕邀請?")) return;
const action = accept ? "accept" : "reject";
try {
const response = await fetch("/api/members", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ teamId, accept }),
});
if (accept) {
const { userData, teamData, membersData } = await response.json();
dispatch(userActions.setUser(userData));
dispatch(teamActions.setTeam({ userData, teamData, membersData }));
router.push("/team");
} else {
const { userData } = await response.json();
dispatch(userActions.setUser(userData));
}
const response = await fetch(
`/api/users/teams?action=${action}&teamId=${teamId}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
}
);
const userTeams = await response.json();
mutateUserTeams();
mutateUser({ ...user, teams: userTeams });

return router.push(accept ? "/team" : "/user/invitations");
} catch (error) {
console.log(error);
}
};

return (
<>
<Card className={className}>
<CardHeader>
<CardTitle>
<FiFileText />
隊伍資訊
<FiUsers />
{isLoading ? "Loading..." : team?.name}
</CardTitle>
<CardBtnGroup>
{isAdmin && (
<Button
variant="link"
size="lg"
onClick={() => router.push(`/team/info/${teamData._id}/edit`)}
onClick={() => router.push(`/team/info/${teamId}/edit`)}
>
<FiEdit2 />
編輯隊伍
</Button>
)}
</CardBtnGroup>
</CardHeader>
<ListItem type="secondary" text>
<ListItemText fit>隊伍名稱</ListItemText>
<ListItemText bold>{teamData.name}</ListItemText>
</ListItem>
<ListItem type="secondary" text>
<ListItemText fit>隊伍簡稱</ListItemText>
<ListItemText bold>{teamData.nickname}</ListItemText>
</ListItem>
<ListItem type="secondary" text>
<ListItemText fit>隊伍人數</ListItemText>
<ListItemText bold>{teamData.members.length}</ListItemText>
</ListItem>
{isLoading ? <>Loading...</> : <TeamInfoTable team={team} />}
{isInviting && (
<>
<p>是否接受此隊伍邀請?</p>
<Button size="lg" onClick={() => handleAccept(teamData._id, true)}>
<Button size="lg" onClick={() => handleAccept(teamId, true)}>
<FiCheck />
同意邀請
</Button>
<Button
variant="destructive"
size="lg"
onClick={() => handleAccept(teamData._id, false)}
onClick={() => handleAccept(teamId, false)}
>
<FiX />
拒絕邀請
</Button>
</>
)}
</>
</Card>
);
};

Expand Down
41 changes: 41 additions & 0 deletions app/(protected)/team/info/TeamInfoTable.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { FiInfo, FiUsers } from "react-icons/fi";
import {
Table,
TableBody,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";

const TeamInfoTable = ({ team, className }) => {
const contents = [
{ key: "簡稱", value: team.nickname, icon: <FiInfo /> },
{ key: "人數", value: team.members.length, icon: <FiUsers /> },
];

return (
<Table className={className}>
<TableHeader className="text-lg">
<TableRow>
<TableHead colSpan={3}>隊伍資訊</TableHead>
</TableRow>
</TableHeader>
<TableBody className="text-xl">
{contents.map(({ key, value, icon }) => (
<TableRow key={key}>
<TableCell className="w-6 [&>svg]:w-6 [&>svg]:h-6">
{icon}
</TableCell>
<TableCell className="text-nowrap text-muted-foreground">
{key}
</TableCell>
<TableCell className="w-full font-medium">{value}</TableCell>
</TableRow>
))}
</TableBody>
</Table>
);
};

export default TeamInfoTable;
14 changes: 1 addition & 13 deletions app/(protected)/team/info/[id]/page.jsx
Original file line number Diff line number Diff line change
@@ -1,20 +1,8 @@
import { Card } from "@/components/ui/card";
import TeamInfo from "../../TeamInfo";

const getTeamData = async (teamId) => {
const response = await fetch(process.env.URL + `/api/teams/${teamId}`);
const { teamData, membersData } = await response.json();
return { teamData, membersData };
};

const TeamInfoPage = async ({ params }) => {
const { id: teamId } = params;
const { teamData, membersData } = await getTeamData(teamId);
return (
<Card className="w-full">
<TeamInfo teamData={teamData} membersData={membersData} />
</Card>
);
return <TeamInfo teamId={teamId} className="w-full" />;
};

export default TeamInfoPage;
52 changes: 28 additions & 24 deletions app/(protected)/user/Menu.jsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,10 @@
"use client";
import Image from "next/image";
import { cn } from "@/lib/utils";
import { useState } from "react";
import { useRouter } from "next/navigation";
import { useDispatch } from "react-redux";
import { useSession } from "next-auth/react";
import { useUserTeams } from "@/hooks/use-data";
import { userActions } from "../user/user-slice";
import { teamActions } from "../team/team-slice";
import {
FiChevronDown,
FiSettings,
Expand All @@ -22,29 +20,32 @@ import { Separator } from "@/components/ui/separator";

const Menu = ({ className }) => {
const router = useRouter();
const dispatch = useDispatch();
const [extendTeams, setExtendTeams] = useState(false);
const { data } = useSession();
const { data, update } = useSession();
const user = data?.user || null;
const { teams, isLoading: isUserTeamsLoading } = useUserTeams();
const {
teams,
isLoading: isUserTeamsLoading,
mutate: mutateUserTeams,
} = useUserTeams();
const [extendTeams, setExtendTeams] = useState(false);

const handleTeamSwitch = async (index, team) => {
if (index === 0) return router.push("/team");

try {
const response = await fetch(`/api/teams/${team._id}?switch=true`, {
method: "GET",
headers: {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*",
},
});
const { userData, teamData, membersData } = await response.json();
dispatch(userActions.setUser(userData));
dispatch(teamActions.setTeam({ userData, teamData, membersData }));
router.push("/team");
const response = await fetch(
`/api/users/teams?action=switch&teamId=${team._id}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
}
);
const userTeams = await response.json();
await update({ ...data, user: { ...data.user, teams: userTeams } });
mutateUserTeams();
return router.push("/team");
} catch (error) {
console.log(error);
// TODO: 錯誤提示訊息
}
};

Expand Down Expand Up @@ -73,9 +74,10 @@ const Menu = ({ className }) => {
<span className="flex justify-start flex-1">隊伍與邀請</span>
{teams && teams.inviting.length}
<FiChevronDown
className={`transition-transform duration-200 ${
extendTeams ? "rotate-180" : ""
}`}
className={cn(
"transition-transform duration-200",
extendTeams && "rotate-180"
)}
/>
</Button>
{extendTeams &&
Expand All @@ -94,7 +96,9 @@ const Menu = ({ className }) => {
onClick={() => handleTeamSwitch(index, team)}
>
<FiUsers />
{team.name || ""}
<span className="flex justify-start flex-1">
{team.name || ""}
</span>
{index !== 0 && <GoArrowSwitch />}
</Button>
))}
Expand All @@ -118,7 +122,7 @@ const Menu = ({ className }) => {
<CardDescription>
沒有你的隊伍嗎?你可以聯絡你的隊伍管理者,或...
</CardDescription>
<Link variant="ghost" size="lg" href="/team/invitations">
<Link variant="ghost" size="lg" href="/user/invitations">
<FiPlus />
查看更多
</Link>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";
import { useRouter } from "next/navigation";
import { useUserTeams } from "@/hooks/use-data";
import { FiUsers, FiPlus, FiCheck } from "react-icons/fi";
import { useUser, useUserTeams } from "@/hooks/use-data";
import { FiUsers, FiPlus, FiCheck, FiX } from "react-icons/fi";
import { Button, Link } from "@/components/ui/button";
import {
Card,
Expand All @@ -14,19 +14,25 @@ import { ListItem, ListItemText } from "@/app/components/common/List";

const Invitations = ({ className }) => {
const router = useRouter();
const { teams, isLoading } = useUserTeams();
const { user, mutate: mutateUser } = useUser();
const { teams, isLoading, mutate: mutateUserTeams } = useUserTeams();

const handleAccept = async (teamId) => {
const handleAccept = async (teamId, accept) => {
if (!window.confirm(accept ? "確認接受邀請?" : "確認拒絕邀請?")) return;
const action = accept ? "accept" : "reject";
try {
const response = await fetch("/api/members", {
method: "PATCH",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ teamId }),
});
const { userData, teamData, membersData } = await response.json();
dispatch(userActions.setUser(userData));
dispatch(teamActions.setTeam({ userData, teamData, membersData }));
router.push("/team");
const response = await fetch(
`/api/users/teams?action=${action}&teamId=${teamId}`,
{
method: "PATCH",
headers: { "Content-Type": "application/json" },
}
);
const userTeams = await response.json();
mutateUserTeams();
mutateUser({ ...user, teams: userTeams });

return accept ? router.push("/team") : null;
} catch (error) {
console.log(error);
}
Expand All @@ -47,11 +53,17 @@ const Invitations = ({ className }) => {
<Button
variant="link"
size="icon"
onClick={() => handleAccept(team._id)}
onClick={() => handleAccept(team._id, true)}
>
<FiCheck />
</Button>
{/* TODO: 新增拒絕邀請功能 */}
<Button
variant="link"
size="icon"
onClick={() => handleAccept(team._id, false)}
>
<FiX />
</Button>
</ListItem>
))
)}
Expand Down
File renamed without changes.
Loading

1 comment on commit 4e9c5b1

@vercel
Copy link

@vercel vercel bot commented on 4e9c5b1 May 24, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.