Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/#239 초대코드 기능 #264

Merged
merged 14 commits into from
Jun 29, 2024
Merged
5 changes: 4 additions & 1 deletion src/app/api/fetcher.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
type AllowObjectBodyRequestInit = Omit<RequestInit, 'body'> & { body?: RequestInit['body'] | object | FormData };

export type FetchProps = [string, AllowObjectBodyRequestInit?];

export type FetchResult = { ok: boolean; body: any };

export type FetcherOptions = {
baseUrl?: string;
headers?: HeadersInit;
Expand Down Expand Up @@ -30,7 +33,7 @@ const defaultOptions: FetcherOptions = {
export const fetcher = (options?: FetcherOptions) => {
const { baseUrl, headers, interceptors } = { ...defaultOptions, ...options };

return async (...props: FetchProps) => {
return async (...props: FetchProps): Promise<FetchResult> => {
try {
let [url, config] = props;
if (interceptors?.request) {
Expand Down
11 changes: 8 additions & 3 deletions src/app/api/team.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,21 @@ const deleteTeam = (teamId: number) =>
method: 'DELETE',
});

const postInviteTeam = (teamId: number, code: string) =>
const postInviteTeam = (token: string, teamId: number) =>
teamFetcher(`/teams/${teamId}/invite-code`, {
method: 'POST',
body: code,
headers: {
Authorization: `Bearer ${token}`,
},
});

const postJoinTeam = (teamId: number, code: string) =>
const postJoinTeam = (token: string, teamId: number, code: string) =>
teamFetcher(`/teams/${teamId}/join`, {
method: 'POST',
body: code,
headers: {
Authorization: `Bearer ${token}`,
},
});

const getTeamMembers = (teamId: number) => teamFetcher(`/teams/${teamId}/members`);
Expand Down
10 changes: 6 additions & 4 deletions src/app/oauth2/code/google/page.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
'use client';

import { useSetAtom } from 'jotai';
import { useAtomValue, useSetAtom } from 'jotai';
import { useRouter } from 'next/navigation';
import { useEffect } from 'react';

import { postGoogleLogin } from '@/app/api/login';
import { userAtom } from '@/atom';
import { loginBackPathAtom, userAtom } from '@/atom';

const Page = ({ searchParams }: { searchParams: { code: string } }) => {
const { code } = searchParams;
const router = useRouter();

const setUser = useSetAtom(userAtom);
const loginBackPath = useAtomValue(loginBackPathAtom);

useEffect(() => {
if (code) {
Expand All @@ -22,13 +23,14 @@ const Page = ({ searchParams }: { searchParams: { code: string } }) => {
token: res.body?.token,
isLogin: true,
});
router.replace(loginBackPath);
} else {
alert(res?.body?.message || '알 수 없는 오류가 발생했습니다.');
router.replace('/');
}
router.replace('/');
});
}
}, [code, router, setUser]);
}, [code, router, setUser, loginBackPath]);

return <div />;
};
Expand Down
43 changes: 43 additions & 0 deletions src/app/team/[teamId]/join/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
'use client';

import { useSetAtom } from 'jotai';
import { useParams, useRouter } from 'next/navigation';
import { useEffect } from 'react';

import { postJoinTeam } from '@/app/api/team';
import { loginBackPathAtom } from '@/atom';
import GOOGLE_LOGIN_URL from '@/constants/googleLoginUrl';
import { useMutateWithToken } from '@/hooks/useFetchWithToken';
import useGetUser from '@/hooks/useGetUser';

const Page = ({ searchParams }: { searchParams: { code: string } }) => {
const params = useParams<{ teamId: string }>();
const teamId = parseInt(params.teamId, 10);
const { code } = searchParams;
const router = useRouter();
const user = useGetUser();
const setLoginBackPath = useSetAtom(loginBackPathAtom);
const joinTeam = useMutateWithToken(postJoinTeam);

useEffect(() => {
if (user) {
if (user.isLogin) {
joinTeam(teamId, code).then((res) => {
if (res?.ok) {
router.replace(`/team/${teamId}`);
} else {
alert('유효하지 않은 초대링크입니다.');
router.replace('/');
}
});
} else {
setLoginBackPath(`/team/${teamId}/join?code=${code}`);
window.location.href = GOOGLE_LOGIN_URL;
}
}
}, [user, teamId, code, router, setLoginBackPath, joinTeam]);

return <div />;
};

export default Page;
26 changes: 25 additions & 1 deletion src/app/team/[teamId]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Box, Button, Flex, useBreakpointValue } from '@chakra-ui/react';
import { useEffect, useState } from 'react';
import { BsLink45Deg } from 'react-icons/bs';

import { postInviteTeam } from '@/app/api/team';
import { DocumentCardProps } from '@/components/DocumentCard/types';
import Garden3D from '@/components/Garden3D';
import { StudyCardProps } from '@/components/StudyCard/types';
Expand All @@ -19,6 +20,7 @@ import NavigationButton from '@/containers/team/NavigationButton';
import StudyGridView from '@/containers/team/StudyGridView';
import TeamControlPanel from '@/containers/team/TeamControlPanel';
import TeamMember from '@/containers/team/teamMember';
import { useMutateWithToken } from '@/hooks/useFetchWithToken';
import documentCardData from '@/mocks/documentCard';
import { gardenInfos1 } from '@/mocks/Garden3D';
import studyCardData from '@/mocks/studyCard';
Expand All @@ -38,6 +40,8 @@ const Page = ({ params }: { params: { teamId: number } }) => {

const [isCreateStudyModalOpen, setIsCreateStudyModalOpen] = useState<boolean>(false);

const inviteTeam = useMutateWithToken(postInviteTeam);

const getCardData = (start: number) => {
if (category === '스터디') {
// TODO: 스터디 목록 조회하기.
Expand Down Expand Up @@ -85,6 +89,19 @@ const Page = ({ params }: { params: { teamId: number } }) => {
setCardIdx(0);
};

const handleInviteClick = () => {
inviteTeam(params.teamId).then((res) => {
if (res.ok) {
navigator.clipboard.writeText(
`${process.env.NEXT_PUBLIC_DEPLOY_URL}/team/${params.teamId}/join?code=${res.body.code}`,
);
alert('초대 링크가 복사되었습니다.');
} else {
alert('초대 링크 생성에 실패했습니다.');
}
});
};

return (
<>
<Flex direction="column" gap="8" w="100%" p="8">
Expand All @@ -93,7 +110,14 @@ const Page = ({ params }: { params: { teamId: number } }) => {
{/* TODO 팀원 목록, 초대링크 버튼 */}
<Flex align="center" gap={{ base: '2', lg: '8' }}>
<TeamMember teamId={params.teamId} />
<Button color="white" bg="orange_dark" rightIcon={<BsLink45Deg size="24px" />} rounded="full" size="sm">
<Button
color="white"
bg="orange_dark"
onClick={handleInviteClick}
rightIcon={<BsLink45Deg size="24px" />}
rounded="full"
size="sm"
>
초대
</Button>
</Flex>
Expand Down
2 changes: 2 additions & 0 deletions src/atom.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,5 @@ export const userAtom = atomWithStorage('user', {
token: '',
isLogin: false,
});

export const loginBackPathAtom = atomWithStorage('loginBackPath', '/');
8 changes: 8 additions & 0 deletions src/constants/googleLoginUrl.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
const GOOGLE_LOGIN_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
`client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&` +
`redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URL}&` +
`response_type=code&` +
`scope=${process.env.NEXT_PUBLIC_GOOGLE_SCOPE}`;

export default GOOGLE_LOGIN_URL;
8 changes: 1 addition & 7 deletions src/containers/main/GoogleLoginButton/index.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,8 @@
import { Image, Button, Box } from '@chakra-ui/react';

import GOOGLE_LOGIN_URL from '@/constants/googleLoginUrl';
import useGetUser from '@/hooks/useGetUser';

const GOOGLE_LOGIN_URL =
'https://accounts.google.com/o/oauth2/v2/auth?' +
`client_id=${process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID}&` +
`redirect_uri=${process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URL}&` +
`response_type=code&` +
`scope=${process.env.NEXT_PUBLIC_GOOGLE_SCOPE}`;

const GoogleLoginButton = () => {
const user = useGetUser();

Expand Down
5 changes: 3 additions & 2 deletions src/hooks/useFetchWithToken.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import { useEffect, useState } from 'react';

import { FetchResult } from '@/app/api/fetcher';
import useGetUser from '@/hooks/useGetUser';

export function useGetFetchWithToken(fetch: (token: string, ...props: any[]) => any, props: any[], originUser?: any) {
Expand All @@ -25,8 +26,8 @@ export function useGetFetchWithToken(fetch: (token: string, ...props: any[]) =>
return result;
}

export function useMutateWithToken(fetch: (token: string, ...props: any[]) => any) {
export function useMutateWithToken(fetch: (token: string, ...props: any[]) => Promise<FetchResult>) {
const user = useGetUser();

return (props: any[]) => fetch(user?.token || '', ...props);
return (...props: any[]) => fetch(user?.token || '', ...props);
}