diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b333aa03a..9cd5cad75 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -5,10 +5,19 @@ 3. 새로운 모듈 설치시 PR message에 기재할 것. 4. PR 올리기전에 branch 반드시 확인할 것. --> - ## 📌 개요 - - - ## 💻 작업사항 - - - ## ✅ 변경로직 - - - + +## 📌 개요 + +- + +## 💻 작업사항 + +- + +## ✅ 변경로직 + +- + +## 💡관련 Issue + +- diff --git a/.github/workflows/main-deploy.yml b/.github/workflows/main-deploy.yml index 7b6452000..4e5685cb6 100644 --- a/.github/workflows/main-deploy.yml +++ b/.github/workflows/main-deploy.yml @@ -20,6 +20,7 @@ jobs: - name: Build env: NEXT_PUBLIC_SERVER_ENDPOINT: ${{ secrets.NEXT_PUBLIC_SERVER_ENDPOINT }} + NEXT_PUBLIC_AGENDA_SERVER_ENDPOINT: ${{ secrets.NEXT_PUBLIC_AGENDA_SERVER_ENDPOINT }} NEXT_PUBLIC_CLIENT_ENDPOINT: ${{ secrets.NEXT_PUBLIC_CLIENT_ENDPOINT }} NEXT_PUBLIC_MANAGE_SERVER_ENDPOINT: ${{ secrets.NEXT_PUBLIC_MANAGE_SERVER_ENDPOINT }} NEXT_PUBLIC_PARTY_MANAGE_SERVER_ENDPOINT: ${{ secrets.NEXT_PUBLIC_PARTY_MANAGE_SERVER_ENDPOINT }} diff --git a/.github/workflows/test-deploy.yml b/.github/workflows/test-deploy.yml index 872c885ca..f7b363a68 100644 --- a/.github/workflows/test-deploy.yml +++ b/.github/workflows/test-deploy.yml @@ -23,6 +23,7 @@ jobs: env: NEXT_PUBLIC_SERVER_ENDPOINT: ${{ secrets.NEXT_DEV_PUBLIC_SERVER_ENDPOINT }} NEXT_PUBLIC_CLIENT_ENDPOINT: ${{ secrets.DEV_NEXT_PUBLIC_CLIENT_ENDPOINT }} + NEXT_PUBLIC_AGENDA_SERVER_ENDPOINT: ${{ secrets.DEV_NEXT_PUBLIC_AGENDA_SERVER_ENDPOINT }} NEXT_PUBLIC_PARTY_MANAGE_SERVER_ENDPOINT: ${{ secrets.DEV_NEXT_PUBLIC_PARTY_MANAGE_SERVER_ENDPOINT }} NEXT_PUBLIC_MANAGE_SERVER_ENDPOINT: ${{ secrets.NEXT_DEV_PUBLIC_MANAGE_SERVER_ENDPOINT }} GENERATE_SOURCEMAP: ${{ secrets.GENERATE_SOURCEMAP }} diff --git a/.gitignore b/.gitignore index e3a4e3ac6..8537bf7a5 100644 --- a/.gitignore +++ b/.gitignore @@ -42,4 +42,7 @@ yarn-error.log* .eslintcache #cypressConfig -cypress.config.* \ No newline at end of file +cypress.config.* + +#siwolee: ignore local file I can't remove +*anima* \ No newline at end of file diff --git a/.storybook/preview.tsx b/.storybook/preview.tsx index 263ca1299..858b3d5ed 100644 --- a/.storybook/preview.tsx +++ b/.storybook/preview.tsx @@ -2,6 +2,11 @@ import React from 'react'; import type { Preview } from '@storybook/react'; import { StoryFn } from '@storybook/react'; import { RecoilRoot } from 'recoil'; +import { QueryClient } from 'react-query'; +import { QueryClientProvider } from 'react-query'; +import 'styles/globals.css'; + +const queryClient = new QueryClient(); const preview: Preview = { parameters: { @@ -13,14 +18,39 @@ const preview: Preview = { }, }, backgrounds: { - default: 'purple', - values: [{ name: 'purple', value: '#301451' }], + default: 'light-bg', + values: [ + { name: 'purple', value: '#301451' }, + { + name: 'light-bg', + value: 'linear-gradient(180deg, #c9c9c9 0%, #6d5b93 100%)', + }, + { + name: 'dark-bg', + value: 'linear-gradient(180deg, #6d5b93 0%, #301451 100%)', + }, + ], }, }, decorators: [ (Story: StoryFn) => ( - + + + + ), ], diff --git a/Layout/AdminLayout.tsx b/Layout/AdminLayout.tsx new file mode 100644 index 000000000..6befb7a24 --- /dev/null +++ b/Layout/AdminLayout.tsx @@ -0,0 +1,37 @@ +import { usePathname } from 'next/navigation'; +import AdminReject from 'components/admin/AdminReject'; +import AdminLayout from 'components/admin/Layout'; +import AgendaModalProvider from 'components/agenda/modal/AgendaModalProvider'; +import ModalProvider from 'components/takgu/modal/ModalProvider'; +import { useUser } from 'hooks/agenda/Layout/useUser'; + +type AdminLayoutProps = { + children: React.ReactNode; +}; + +function AdminAppLayout({ children }: AdminLayoutProps) { + const user = useUser(); + const presentPath = usePathname(); + + // 사용자 정보가 없거나 관리자가 아닐 경우 + if (!user || !user.intraId) return null; + + if (!user.isAdmin) return ; + + // 모달 제공자 결정 + let ModalProviderComponent; + if (presentPath.includes('admin/takgu')) { + ModalProviderComponent = ModalProvider; + } else if (presentPath.includes('admin/agenda')) { + ModalProviderComponent = AgendaModalProvider; + } + + return ( + <> + {children} + {ModalProviderComponent && } + + ); +} + +export default AdminAppLayout; diff --git a/Layout/AgendaLayout.tsx b/Layout/AgendaLayout.tsx new file mode 100644 index 000000000..f84499d2c --- /dev/null +++ b/Layout/AgendaLayout.tsx @@ -0,0 +1,40 @@ +import { instanceInAgenda } from 'utils/axios'; +import AgendaHeader from 'components/agenda/Layout/AgendaHeader'; +import AgendaModalProvider from 'components/agenda/modal/AgendaModalProvider'; +import Footer from 'components/takgu/Layout/Footer'; +import { useUser } from 'hooks/agenda/Layout/useUser'; +import useAxiosWithToast from 'hooks/useAxiosWithToast'; +import styles from 'styles/agenda/Layout/Layout.module.scss'; + +type AgendaLayoutProps = { + children: React.ReactNode; +}; + +function AgendaAppLayout({ children }: AgendaLayoutProps) { + useAxiosWithToast(instanceInAgenda); // API의 성공 실패 스낵바로 알리는 기능 + + const user = useUser(); + + if (!user || !user.intraId) return null; + + const scrollToTop = () => { + window.scrollTo({ top: 0, behavior: 'smooth' }); + }; + + return ( + <> + +
+
+ {children} +
+
+ + + + ); +} + +export default AgendaAppLayout; diff --git a/Layout/LayoutProvider.tsx b/Layout/LayoutProvider.tsx new file mode 100644 index 000000000..d60953b20 --- /dev/null +++ b/Layout/LayoutProvider.tsx @@ -0,0 +1,35 @@ +import { useRecoilValue } from 'recoil'; +import { loginState } from 'utils/recoil/login'; +import AdminAppLayout from 'Layout/AdminLayout'; +import AgendaAppLayout from 'Layout/AgendaLayout'; +import TakguAppLayout from 'Layout/TakguLayout'; +import { usePathname } from 'hooks/agenda/Layout/usePathname'; +import useAxiosAgendaError from 'hooks/useAxiosAgendaError'; +import useAxiosResponse from 'hooks/useAxiosResponse'; + +type LayoutProviderProps = { + children: React.ReactNode; +}; + +// 현재 페이지가 어떤 레이아웃을 사용할지 결정 +// 로그인 스테이트 등은 각 레이아웃에서 확인 +const LayoutProvider = ({ children }: LayoutProviderProps) => { + useRecoilValue(loginState); + useAxiosResponse(); // takgu axios response interceptor + useAxiosAgendaError(); // agenda axios response interceptor + + const app = usePathname(); + switch (app) { + case '': + case 'agenda': + return {children}; + case 'takgu': + return {children}; + case 'admin': + return {children}; + default: + return <>{children}; + } +}; + +export default LayoutProvider; diff --git a/Layout/TakguLayout.tsx b/Layout/TakguLayout.tsx new file mode 100644 index 000000000..ea2c1a20c --- /dev/null +++ b/Layout/TakguLayout.tsx @@ -0,0 +1,94 @@ +import { useRouter } from 'next/router'; +import { useRecoilValue } from 'recoil'; +import { openCurrentMatchState } from 'utils/recoil/takgu/match'; +import CurrentMatch from 'components/takgu/Layout/CurrentMatch'; +import Footer from 'components/takgu/Layout/Footer'; +import Header from 'components/takgu/Layout/Header'; +import HeaderStateContext from 'components/takgu/Layout/HeaderContext'; +import MainPageProfile from 'components/takgu/Layout/MainPageProfile'; +import Megaphone from 'components/takgu/Layout/MegaPhone'; +import RecruitLayout from 'components/takgu/recruit/RecruitLayout'; +import Statistics from 'pages/takgu/statistics'; +import { usePathname } from 'hooks/agenda/Layout/usePathname'; +import useAnnouncementCheck from 'hooks/takgu/Layout/useAnnouncementCheck'; +import useGetUserSeason from 'hooks/takgu/Layout/useGetUserSeason'; +import useLiveCheck from 'hooks/takgu/Layout/useLiveCheck'; +import useSetAfterGameModal from 'hooks/takgu/Layout/useSetAfterGameModal'; +import { useUser } from 'hooks/takgu/Layout/useUser'; +import styles from 'styles/takgu/Layout/Layout.module.scss'; +import PlayButton from '../components/takgu/Layout/PlayButton'; +import UserLayout from '../components/takgu/Layout/UserLayout'; +import ModalProvider from '../components/takgu/modal/ModalProvider'; +import CustomizedSnackbars from '../components/toastmsg/toastmsg'; + +type TakguLayoutProps = { + children: React.ReactNode; +}; + +const TakguLayout = ({ children }: TakguLayoutProps) => { + const user = useUser(); + const presentPath = usePathname(); + const path = useRouter().pathname; + const openCurrentMatch = useRecoilValue(openCurrentMatchState); + + useGetUserSeason(presentPath); + useSetAfterGameModal(); + useLiveCheck(presentPath); + useAnnouncementCheck(presentPath); + + if (!user || !user.intraId) return null; + + const renderContent = () => { + if (path.includes('takgu/recruit')) { + return {children}; + } + + if (path.includes('takgu/statistics') && user.isAdmin) { + return ( + + + + ); + } + + if (presentPath.includes('takgu')) { + return ( + <> + + +
+ + +
+ + {openCurrentMatch ? : ''} + {presentPath === '/' ? : ''} +
+ {children} +