diff --git a/output/README.md b/output/README.md new file mode 100644 index 0000000..df8c92f --- /dev/null +++ b/output/README.md @@ -0,0 +1,88 @@ +## Getting Started + +First, run the development server: + +```bash +yarn dev +``` + +Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. + +This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. + +## Next.js 사용 팁 + +1. 페이지 컴포넌트 파일들은 src/app 하위에 생성하면 됨 + +- 라우팅은 폴더를 생성하면 사용 가능, 동적 라우팅의 경우 `/[파라미터]`로 생성할 것 +- 이 밖에도 지정 파일들이 있으니(page.tsx, layout.tsx, error.tsx 등) 공식문서 참고할 것 + +2. components, utils, assets 등의 파일 폴더들은 src 아래 app 폴더와 동등한 레벨에 생성 및 사용할 것 + +3. 기본값으로 server component로 사용됨. useState등의 훅을 사용하려면 'use client'를 파일 최상단에 기입할 것 + +## 간단한 코드 컨벤션 + +### 커밋 컨벤션 + +| 태그 이름 | 설명 | +| ---------------- | -------------------------------------------------------------------------------------------------------- | +| Feat | 새로운 기능을 추가할 경우 | +| Fix | 버그를 고친 경우 | +| Design | CSS 등 사용자 UI 디자인 변경 | +| !BREAKING CHANGE | 커다란 API 변경의 경우 | +| !HOTFIX | 급하게 치명적인 버그를 고쳐야하는 경우 | +| Style | 코드 포맷 변경, 세미 콜론 누락, 오타 수정, 탭 사이즈 변경, 변수명 변경 등 코어 로직을 안건드는 변경 사항 | +| Refactor | 프로덕션 코드 리팩토링 | +| Comment | 필요한 주석 추가 및 변경 | +| Docs | 문서(Readme.md)를 수정한 경우 | +| Rename | 파일 혹은 폴더명을 수정하거나 옮기는 작업만인 경우 | +| Remove | 파일을 삭제하는 작업만 수행한 경우 | +| Test | 테스트 추가, 테스트 리팩토링(프로덕션 코드 변경 X) | +| Chore | 빌드 태스트 업데이트, 패키지 매니저를 설정하는 경우(프로덕션 코드 변경 X) | + +### 네이밍 컨벤션 + +1. **\*.tsx** : PascalCase +2. **\*.ts** : camelCase +3. 페이지 폴더 (**pages/**/**\***.tsx\*\*) : index.tsx +4. 나머지 폴더 : **kebab-case** +5. constants : camelCase +6. git branch name : kebab-case + +### 폴더 구조 + +``` +├── 📁 node_modules +├── 📁 public +├── 📁 src +│ ├── 📁 assets +│ │ ├── 📁 contants +│ │ ├── 📁 fonts +│ │ └── 📁 images +│ ├── 📁 components +│ │ ├── 📁 layout +│ │ ├── 📁 ... +│ │ └── ... +│ ├── 📁 app +│ │ ├── 📁 home +│ │ ├── 📁 ... +│ │ ├── 📁 ... +│ │ ├── page.tsx +│ │ ├── layout.tsx +│ │ └── global.css +│ └── 📁 utils +│ ├── 📁 hooks +│ ├── 📁 recoil +│ └── 📁 types +├── .eslintrc.json +├── .gitgnore +├── next-env.d.ts +├── next.config.js +├── package.json +├── postcss.config.js +├── README.md +├── tailwind.config.ts +├── tsconfjg.json +└── yarn.lock +``` diff --git a/output/build.sh b/output/build.sh new file mode 100644 index 0000000..cfecf99 --- /dev/null +++ b/output/build.sh @@ -0,0 +1,5 @@ +#!/bin/sh +cd ../ +mkdir output +cp -R ./client/* ./output +cp -R ./output ./client/ \ No newline at end of file diff --git a/output/next.config.js b/output/next.config.js new file mode 100644 index 0000000..1ba0668 --- /dev/null +++ b/output/next.config.js @@ -0,0 +1,47 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + images: { + domains: [ + 'hufstreaming.s3.ap-northeast-2.amazonaws.com', + 'hufscheer-server.s3.ap-northeast-2.amazonaws.com', + ], + }, +}; + +module.exports = nextConfig; + +// Injected content via Sentry wizard below + +const { withSentryConfig } = require('@sentry/nextjs'); + +module.exports = withSentryConfig( + module.exports, + { + // For all available options, see: + // https://github.com/getsentry/sentry-webpack-plugin#options + + // Suppresses source map uploading logs during build + silent: true, + org: 'hufs-td', + project: 'hufstreaming', + }, + { + // For all available options, see: + // https://docs.sentry.io/platforms/javascript/guides/nextjs/manual-setup/ + + // Upload a larger set of source maps for prettier stack traces (increases build time) + widenClientFileUpload: true, + + // Transpiles SDK to be compatible with IE11 (increases bundle size) + transpileClientSDK: true, + + // Routes browser requests to Sentry through a Next.js rewrite to circumvent ad-blockers (increases server load) + tunnelRoute: '/monitoring', + + // Hides source maps from generated client bundles + hideSourceMaps: true, + + // Automatically tree-shake Sentry logger statements to reduce bundle size + disableLogger: true, + }, +); diff --git a/output/package.json b/output/package.json new file mode 100644 index 0000000..dbe5580 --- /dev/null +++ b/output/package.json @@ -0,0 +1,57 @@ +{ + "name": "hufs-sports-live-client", + "version": "0.1.0", + "private": true, + "scripts": { + "dev": "next dev", + "build": "next build", + "start": "next start", + "lint": "next lint", + "prepare": "chmod ug+x .husky/* && husky install" + }, + "lint-staged": { + "**/*.{js,jsx,ts,tsx}": [ + "eslint", + "prettier --config ./.prettierrc --write -u" + ] + }, + "dependencies": { + "@radix-ui/react-dialog": "^1.0.5", + "@radix-ui/react-icons": "^1.3.0", + "@sentry/nextjs": "^7.73.0", + "@stomp/stompjs": "^7.0.0", + "@tanstack/react-query": "^5.8.2", + "@tanstack/react-query-devtools": "^5.8.2", + "axios": "^1.5.1", + "clsx": "^2.0.0", + "next": "13.5.4", + "react": "^18", + "react-dom": "^18", + "tailwind-merge": "^2.0.0" + }, + "devDependencies": { + "@commitlint/cli": "^18.2.0", + "@commitlint/config-conventional": "^18.1.0", + "@tanstack/eslint-plugin-query": "^5.6.0", + "@types/node": "^20", + "@types/react": "^18", + "@types/react-dom": "^18", + "@typescript-eslint/eslint-plugin": "^6.9.1", + "@typescript-eslint/parser": "^6.9.1", + "autoprefixer": "^10", + "eslint": "^8", + "eslint-config-next": "13.5.4", + "eslint-config-prettier": "^9.0.0", + "eslint-plugin-import": "^2.29.0", + "eslint-plugin-jsx-a11y": "^6.8.0", + "eslint-plugin-prettier": "^5.0.1", + "eslint-plugin-simple-import-sort": "^10.0.0", + "husky": "^8.0.3", + "lint-staged": "^15.1.0", + "postcss": "^8", + "prettier": "^3.0.3", + "prettier-plugin-tailwindcss": "^0.5.7", + "tailwindcss": "^3", + "typescript": "^5" + } +} diff --git a/output/postcss.config.js b/output/postcss.config.js new file mode 100644 index 0000000..33ad091 --- /dev/null +++ b/output/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +} diff --git a/output/public/icon_hufstreaming.svg b/output/public/icon_hufstreaming.svg new file mode 100644 index 0000000..5673d73 --- /dev/null +++ b/output/public/icon_hufstreaming.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/output/public/images/not-found.png b/output/public/images/not-found.png new file mode 100644 index 0000000..1bd5ad3 Binary files /dev/null and b/output/public/images/not-found.png differ diff --git a/output/public/logo_hufstreaming.png b/output/public/logo_hufstreaming.png new file mode 100644 index 0000000..137f156 Binary files /dev/null and b/output/public/logo_hufstreaming.png differ diff --git a/output/sentry.client.config.ts b/output/sentry.client.config.ts new file mode 100644 index 0000000..0ac2b1f --- /dev/null +++ b/output/sentry.client.config.ts @@ -0,0 +1,30 @@ +// This file configures the initialization of Sentry on the client. +// The config you add here will be used whenever a users loads a page in their browser. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: 'https://cc1dcf1845d69a9079c615d462e122b2@o4506026798088192.ingest.sentry.io/4506026816503808', + + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 0.2, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, + + replaysOnErrorSampleRate: 1.0, + + // This sets the sample rate to be 10%. You may want this to be 100% while + // in development and sample at a lower rate in production + replaysSessionSampleRate: 0.1, + + // You can remove this option if you're not planning to use the Sentry Session Replay feature: + integrations: [ + new Sentry.Replay({ + // Additional Replay configuration goes in here, for example: + maskAllText: true, + blockAllMedia: true, + }), + ], +}); diff --git a/output/sentry.edge.config.ts b/output/sentry.edge.config.ts new file mode 100644 index 0000000..8d9aaf5 --- /dev/null +++ b/output/sentry.edge.config.ts @@ -0,0 +1,16 @@ +// This file configures the initialization of Sentry for edge features (middleware, edge routes, and so on). +// The config you add here will be used whenever one of the edge features is loaded. +// Note that this config is unrelated to the Vercel Edge Runtime and is also required when running locally. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from "@sentry/nextjs"; + +Sentry.init({ + dsn: "https://cc1dcf1845d69a9079c615d462e122b2@o4506026798088192.ingest.sentry.io/4506026816503808", + + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 1, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, +}); diff --git a/output/sentry.server.config.ts b/output/sentry.server.config.ts new file mode 100644 index 0000000..0a9a7d1 --- /dev/null +++ b/output/sentry.server.config.ts @@ -0,0 +1,15 @@ +// This file configures the initialization of Sentry on the server. +// The config you add here will be used whenever the server handles a request. +// https://docs.sentry.io/platforms/javascript/guides/nextjs/ + +import * as Sentry from '@sentry/nextjs'; + +Sentry.init({ + dsn: 'https://cc1dcf1845d69a9079c615d462e122b2@o4506026798088192.ingest.sentry.io/4506026816503808', + + // Adjust this value in production, or use tracesSampler for greater control + tracesSampleRate: 0.2, + + // Setting this option to true will print useful information to the console while you're setting up Sentry. + debug: false, +}); diff --git a/output/src/api/admin.ts b/output/src/api/admin.ts new file mode 100644 index 0000000..308802a --- /dev/null +++ b/output/src/api/admin.ts @@ -0,0 +1,42 @@ +import * as Sentry from '@sentry/nextjs'; +import { AxiosError } from 'axios'; + +import { GameScorePayload, NewGamePayload } from '@/types/admin'; + +import { adminInstance } from '.'; + +export const createNewGame = (body: NewGamePayload) => { + try { + return adminInstance.post('/manage/game/register/', body); + } catch (error) { + const axiosError = error as AxiosError; + + Sentry.captureException(axiosError); + + if (axiosError.response) { + throw new Error(axiosError.response.statusText); + } else { + throw new Error('경기를 생성하는 데에 실패했습니다!'); + } + } +}; + +export const postGameScore = (id: number, body: GameScorePayload) => { + try { + return adminInstance.post(`/manage/game/score/${id}/`, body); + } catch (error) { + const axiosError = error as AxiosError; + + Sentry.captureException(axiosError); + + if (axiosError.response) { + throw new Error(axiosError.response.statusText); + } else { + throw new Error('경기 점수를 변경하는 데에 실패했습니다!'); + } + } +}; + +export const postBlockComment = (id: number) => { + return adminInstance.post(`/manage/comments/block/${id}/`); +}; diff --git a/output/src/api/admin/league.ts b/output/src/api/admin/league.ts new file mode 100644 index 0000000..5355da2 --- /dev/null +++ b/output/src/api/admin/league.ts @@ -0,0 +1,48 @@ +import { adminInstance } from '@/api'; +import { + DeleteLeaguePayload, + LeagueIdType, + LeagueType, + NewLeaguePayload, + PutLeaguePayload, + SportsCategoriesType, + SportsQuarterType, +} from '@/types/admin/league'; + +export const getAllLeaguesWithAuth = async () => { + const { data } = await adminInstance.get('/league/all/'); + + return data; +}; + +export const postNewLeagueWithAuth = async (body: NewLeaguePayload) => { + const { data } = await adminInstance.post('/league/', body); + + return data; +}; + +export const deleteLeagueByIdWithAuth = async (body: DeleteLeaguePayload) => { + const { status } = await adminInstance.delete('/league/', { data: body }); + + return status; +}; + +export const putLeagueWithAuth = async (data: PutLeaguePayload) => { + await adminInstance.put('/league/', data); + + return data.leagueId; +}; + +export const getSportsCategoriesWithAuth = async () => { + const { data } = await adminInstance.get('/sport/'); + + return data; +}; + +export const getSportsQuarterByIdWithAuth = async (sportId: string) => { + const { data } = await adminInstance.get( + `/sport/${sportId}/quarter/`, + ); + + return data; +}; diff --git a/output/src/api/admin/team.ts b/output/src/api/admin/team.ts new file mode 100644 index 0000000..c3d8a03 --- /dev/null +++ b/output/src/api/admin/team.ts @@ -0,0 +1,46 @@ +import { isAxiosError } from 'axios'; + +import { adminInstance } from '@/api'; +import { TeamErrorType, TeamType } from '@/types/admin/team'; + +/* 리그 내 팀 관리 API */ + +export const getTeamListByLeagueIdWithAuth = async (leagueId: string) => { + try { + const { data } = await adminInstance.get(`/team/${leagueId}/`); + + return data; + } catch (error) { + if (isAxiosError(error)) { + return error.response?.data.detail; + } else { + throw new Error('리그 내 팀을 조회하는데 실패했습니다.'); + } + } +}; + +export const postTeamByLeagueIdWithAuth = async (payload: { + leagueId: string; + body: FormData; +}) => { + await adminInstance.post( + `/team/register/${payload.leagueId}/`, + payload.body, + { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }, + ); +}; + +export const putTeamByIdWithAuth = async (payload: { + teamId: string; + body: FormData; +}) => { + await adminInstance.put(`/team/${payload.teamId}/change/`, payload.body, { + headers: { + 'Content-Type': 'multipart/form-data', + }, + }); +}; diff --git a/output/src/api/auth.ts b/output/src/api/auth.ts new file mode 100644 index 0000000..2219952 --- /dev/null +++ b/output/src/api/auth.ts @@ -0,0 +1,31 @@ +import * as Sentry from '@sentry/nextjs'; +import { AxiosError } from 'axios'; + +import { AuthPayload, AuthType } from '@/types/auth'; + +import { adminInstance } from '.'; + +export const postLogin = async (body: AuthPayload) => { + try { + const response = await adminInstance.post( + '/accounts/login/', + body, + ); + + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + + Sentry.captureException(axiosError); + + if (axiosError.response) { + throw new Error(axiosError.response.statusText); + } else { + throw new Error('팀 목록을 불러오는 데에 실패했습니다!'); + } + } +}; + +export const postGameStatus = async (id: number, gameStatus: string) => { + adminInstance.post(`/manage/game/statustype/${id}/`, { gameStatus }); +}; diff --git a/output/src/api/index.ts b/output/src/api/index.ts new file mode 100644 index 0000000..f4bb269 --- /dev/null +++ b/output/src/api/index.ts @@ -0,0 +1,26 @@ +import axios from 'axios'; + +import LocalStorage from '@/utils/LocalStorage'; + +const instance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_API_BASE_URL, + headers: { + 'Content-Type': 'application/json', + }, +}); + +export const adminInstance = axios.create({ + baseURL: process.env.NEXT_PUBLIC_BACK_OFFICE_BASE_URL, + headers: { + Authorization: `Bearer `, + 'Content-Type': 'application/json', + }, +}); + +adminInstance.interceptors.request.use(config => { + config.headers.Authorization = `Bearer ${LocalStorage.getItem('token')}`; + + return config; +}); + +export default instance; diff --git a/output/src/api/league.ts b/output/src/api/league.ts new file mode 100644 index 0000000..6cf7adf --- /dev/null +++ b/output/src/api/league.ts @@ -0,0 +1,32 @@ +import * as Sentry from '@sentry/nextjs'; +import { AxiosError } from 'axios'; + +import { LeagueType, SportsType } from '@/types/league'; + +import instance from '.'; + +export const getAllLeagues = async () => { + try { + const response = await instance.get('/leagues'); + + return response.data; + } catch (error) { + const axiosError = error as AxiosError; + + Sentry.captureException(axiosError); + + if (axiosError.response) { + throw new Error(axiosError.response.statusText); + } else { + throw new Error('리그 목록을 불러오는 데에 실패했습니다!'); + } + } +}; + +export const getSportsListByLeagueId = async (leagueId: string) => { + const { data } = await instance.get( + `/leagues/${leagueId}/sports`, + ); + + return data; +}; diff --git a/output/src/api/match.ts b/output/src/api/match.ts new file mode 100644 index 0000000..a6bb35c --- /dev/null +++ b/output/src/api/match.ts @@ -0,0 +1,100 @@ +import { + MatchCheerType, + MatchCommentPayload, + MatchCommentType, + MatchLineupType, + MatchListType, + MatchStatus, + MatchTimelineType, + MatchType, + MatchVideoType, +} from '@/types/match'; +import { convertObjectToQueryString } from '@/utils/queryString'; + +import instance from '.'; + +export type MatchListParams = { + sportsId?: string[]; + status: MatchStatus; + leagueId?: string; + cursor?: number; +}; + +export const getMatchList = async ( + { cursor, ...params }: MatchListParams, + size = 3, +) => { + const queryString = convertObjectToQueryString(params); + + const { data } = await instance.get( + `games?${queryString}&cursor=${cursor || ''}&size=${size}`, + ); + + return data; +}; + +export const getMatchById = async (gameId: string) => { + const { data } = await instance.get(`/games/${gameId}`); + + return data; +}; + +export const getMatchCheerById = async (matchId: string) => { + const { data } = await instance.get( + `/games/${matchId}/cheer`, + ); + + return data; +}; + +export const getGameComments = async (gameId: string, cursor = 1) => { + const response = await instance.get( + `/games/${gameId}/comments?cursor=${cursor}`, + ); + + return response.data; +}; + +export const getMatchTimelineById = async (matchId: string) => { + const { data } = await instance.get( + `/games/${matchId}/timeline`, + ); + + return data; +}; + +export const getMatchLineupById = async (matchId: string) => { + const { data } = await instance.get( + `/games/${matchId}/lineup`, + ); + + return data; +}; + +export const getMatchVideoById = async (matchId: string) => { + const { data } = await instance.get( + `/games/${matchId}/video`, + ); + + return data; +}; + +export const getMatchCommentById = async ( + matchId: string, + cursor: number | string, + size = 20, +) => { + const { data } = await instance.get( + `/games/${matchId}/comments?cursor=${cursor}&size=${size}`, + ); + + return data; +}; + +export const postMatchComment = async (payload: MatchCommentPayload) => { + await instance.post(`/comments`, payload); +}; + +export const postReportComment = async (payload: { commentId: number }) => { + await instance.post(`/reports`, payload); +}; diff --git a/output/src/app/ReactQueryProvider.tsx b/output/src/app/ReactQueryProvider.tsx new file mode 100644 index 0000000..ff525a8 --- /dev/null +++ b/output/src/app/ReactQueryProvider.tsx @@ -0,0 +1,33 @@ +'use client'; + +import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; +import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; +import { ReactNode, useState } from 'react'; + +type ReactQueryProviderProps = { + children: ReactNode; +}; + +export default function ReactQueryProvider({ + children, +}: ReactQueryProviderProps) { + const [queryClient] = useState( + () => + new QueryClient({ + defaultOptions: { + queries: { + refetchOnWindowFocus: false, + refetchInterval: false, + staleTime: 1000 * 60 * 10, + }, + }, + }), + ); + + return ( + + {children} + + + ); +} diff --git a/output/src/app/_error.tsx b/output/src/app/_error.tsx new file mode 100644 index 0000000..4631f54 --- /dev/null +++ b/output/src/app/_error.tsx @@ -0,0 +1,38 @@ +/** + * NOTE: This requires `@sentry/nextjs` version 7.3.0 or higher. + * + * This page is loaded by Nextjs: + * - on the server, when data-fetching methods throw or reject + * - on the client, when `getInitialProps` throws or rejects + * - on the client, when a React lifecycle method throws or rejects, and it's + * caught by the built-in Nextjs error boundary + * + * See: + * - https://nextjs.org/docs/basic-features/data-fetching/overview + * - https://nextjs.org/docs/api-reference/data-fetching/get-initial-props + * - https://reactjs.org/docs/error-boundaries.html + */ + +import * as Sentry from '@sentry/nextjs'; +import type { NextPage } from 'next'; +import type { ErrorProps } from 'next/error'; +import NextErrorComponent from 'next/error'; + +const CustomErrorComponent: NextPage = props => { + // If you're using a Nextjs version prior to 12.2.1, uncomment this to + // compensate for https://github.com/vercel/next.js/issues/8592 + // Sentry.captureUnderscoreErrorException(props); + + return ; +}; + +CustomErrorComponent.getInitialProps = async contextData => { + // In case this is running in a serverless function, await this in order to give Sentry + // time to send the error before the lambda exits + await Sentry.captureUnderscoreErrorException(contextData); + + // This will contain the status code of the response + return NextErrorComponent.getInitialProps(contextData); +}; + +export default CustomErrorComponent; diff --git a/output/src/app/admin/league/page.tsx b/output/src/app/admin/league/page.tsx new file mode 100644 index 0000000..01ca8cf --- /dev/null +++ b/output/src/app/admin/league/page.tsx @@ -0,0 +1,33 @@ +'use client'; + +import dynamic from 'next/dynamic'; +import Link from 'next/link'; +import { Suspense } from 'react'; + +import Button from '@/components/common/Button'; + +const LeagueListFetcher = dynamic( + () => import('@/queries/admin/useLeagueList/Fetcher'), + { ssr: false }, +); +const LeagueList = dynamic( + () => import('@/components/admin/league/LeagueList'), +); + +export default function LeaguePage() { + return ( +
+
전체 리그
+ 리그 로딩중...
}> + + {data => } + + + + + ); +} diff --git a/output/src/app/admin/register/[leagueId]/page.tsx b/output/src/app/admin/register/[leagueId]/page.tsx new file mode 100644 index 0000000..daa3a9a --- /dev/null +++ b/output/src/app/admin/register/[leagueId]/page.tsx @@ -0,0 +1,26 @@ +'use client'; + +import { Suspense } from 'react'; + +import EditLeague from '@/components/admin/register/League/edit'; +import LeagueRegisterFetcher from '@/queries/admin/useLeagueRegister/Fetcher'; +import useSportsListByLeagueId from '@/queries/useSportsListByLeagueId/query'; + +export default function Edit({ params }: { params: { leagueId: string } }) { + const { leagueId } = params; + const { sportsList: leagueSportsData } = useSportsListByLeagueId(leagueId); + return ( +
+ 리그 정보 로딩중...
}> + + {data => ( + + )} + + + + ); +} diff --git a/output/src/app/admin/register/page.tsx b/output/src/app/admin/register/page.tsx new file mode 100644 index 0000000..c8b9a2c --- /dev/null +++ b/output/src/app/admin/register/page.tsx @@ -0,0 +1,44 @@ +'use client'; + +import { Suspense } from 'react'; + +import RegisterWrapper from '@/components/admin/register/context/RegisterWrapper'; +import RegisterLeague from '@/components/admin/register/League/'; +import RegisterTeam from '@/components/admin/register/Team'; +import { useFunnel } from '@/hooks/useFunnel'; +import LeagueRegisterFetcher from '@/queries/admin/useLeagueRegister/Fetcher'; +import TeamRegisterFetcher from '@/queries/admin/useTeamRegister/Fetcher'; + +export default function Register() { + const [Funnel, setStep] = useFunnel(['league', 'team', 'player'], 'league'); + + return ( + + + + 리그 정보 로딩중...}> + + {data => ( + setStep('team')} /> + )} + + + + + 팀 정보 로딩중...}> + + {data => } + + + + + 선수 정보 로딩중...}> + {/* + {data => } + */} + + + + + ); +} diff --git a/output/src/app/globals.css b/output/src/app/globals.css new file mode 100644 index 0000000..b5c61c9 --- /dev/null +++ b/output/src/app/globals.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/output/src/app/layout.tsx b/output/src/app/layout.tsx new file mode 100644 index 0000000..42b8485 --- /dev/null +++ b/output/src/app/layout.tsx @@ -0,0 +1,40 @@ +import './globals.css'; + +import type { Metadata } from 'next'; +import { Noto_Sans_KR } from 'next/font/google'; + +import Footer from '@/components/layout/Footer'; +import Header from '@/components/layout/Header'; + +import ReactQueryProvider from './ReactQueryProvider'; + +const inter = Noto_Sans_KR({ + subsets: ['latin'], + weight: ['400', '500', '700'], +}); + +export const metadata: Metadata = { + title: 'HUFStreaming', + description: '한국외대 스포츠 경기 중계 플랫폼', + icons: { + icon: '/icon_hufstreaming.svg', + }, +}; + +export default function RootLayout({ + children, +}: { + children: React.ReactNode; +}) { + return ( + + + +
+
{children}
+