From 700258ae4eb9ca49fc498c5a5334dbca4bf9f1d1 Mon Sep 17 00:00:00 2001 From: HiimKwak <> Date: Tue, 28 Nov 2023 13:20:26 +0000 Subject: [PATCH] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=AA=A9=EB=A1=9D?= =?UTF-8?q?=20=EB=AC=B4=ED=95=9C=20=EC=8A=A4=ED=81=AC=EB=A1=A4=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore: convertObjectToQS 매개변수 타입 변경 --- output/README.md | 88 + output/build.sh | 5 + output/next.config.js | 47 + output/package.json | 57 + output/postcss.config.js | 6 + output/public/icon_hufstreaming.svg | 9 + output/public/images/not-found.png | Bin 0 -> 2403 bytes output/public/logo_hufstreaming.png | Bin 0 -> 15075 bytes output/sentry.client.config.ts | 30 + output/sentry.edge.config.ts | 16 + output/sentry.server.config.ts | 15 + output/src/api/admin.ts | 42 + output/src/api/auth.ts | 31 + output/src/api/index.ts | 24 + output/src/api/league.ts | 32 + output/src/api/match.ts | 100 + output/src/app/ReactQueryProvider.tsx | 33 + output/src/app/_error.tsx | 38 + output/src/app/admin/page.tsx | 126 + output/src/app/globals.css | 3 + output/src/app/layout.tsx | 40 + output/src/app/login/page.tsx | 53 + output/src/app/match/[id]/modify/page.tsx | 92 + output/src/app/match/[id]/page.tsx | 127 + output/src/app/page.tsx | 81 + .../src/components/common/Dropdown/index.tsx | 8 + .../components/common/Dropdown/units/Item.tsx | 11 + .../components/common/Dropdown/units/Menu.tsx | 15 + .../common/Dropdown/units/Wrapper.tsx | 23 + output/src/components/common/Icon/IconMap.ts | 35 + .../src/components/common/Icon/icon.type.ts | 3 + output/src/components/common/Icon/iconMap.ts | 35 + output/src/components/common/Icon/index.tsx | 18 + .../common/Icon/svg/BackgroundLogo.tsx | 18 + .../components/common/Icon/svg/Calendar.tsx | 20 + .../components/common/Icon/svg/CaretDown.tsx | 17 + .../components/common/Icon/svg/CaretUp.tsx | 22 + .../src/components/common/Icon/svg/Clip.tsx | 22 + .../src/components/common/Icon/svg/Cross.tsx | 22 + .../common/Icon/svg/HamburgerMenu.tsx | 22 + output/src/components/common/Icon/svg/Hcc.tsx | 34 + .../src/components/common/Icon/svg/Image.tsx | 22 + .../components/common/Icon/svg/PaperPlane.tsx | 22 + .../src/components/common/Icon/svg/Pencil.tsx | 22 + .../common/Icon/svg/PlusCircled.tsx | 22 + .../src/components/common/Icon/svg/Symbol.tsx | 28 + .../components/common/Icon/svg/Thumbsup.tsx | 28 + .../src/components/common/Icon/svg/Trash.tsx | 22 + .../src/components/common/Icon/svg/Write.tsx | 22 + output/src/components/common/Input/Input.tsx | 17 + .../src/components/common/MatchCard/index.ts | 14 + .../common/MatchCard/pieces/Background.tsx | 21 + .../common/MatchCard/pieces/Label.tsx | 24 + .../common/MatchCard/pieces/Score.tsx | 14 + .../common/MatchCard/pieces/Status.tsx | 14 + .../common/MatchCard/pieces/Team.tsx | 28 + .../common/MatchCard/pieces/Wrapper.tsx | 25 + output/src/components/common/Modal/index.tsx | 49 + .../src/components/common/Sidebar/index.tsx | 68 + output/src/components/layout/Footer.tsx | 3 + output/src/components/layout/Header.tsx | 57 + .../components/league/SportsList/index.tsx | 38 + output/src/components/match/Banner/index.tsx | 23 + output/src/components/match/Cheer/index.tsx | 27 + .../src/components/match/CheerTeam/index.tsx | 23 + .../components/match/CommentForm/index.tsx | 54 + .../components/match/CommentItem/index.tsx | 72 + .../components/match/CommentList/index.tsx | 60 + .../src/components/match/LineupItem/index.tsx | 18 + .../src/components/match/LineupList/index.tsx | 16 + .../src/components/match/MatchList/index.tsx | 66 + output/src/components/match/Panel/index.tsx | 45 + .../src/components/match/RecordItem/index.tsx | 18 + .../src/components/match/RecordList/index.tsx | 19 + output/src/components/match/Video/index.tsx | 20 + output/src/constants/gameStatus.ts | 7 + output/src/constants/queryParams.ts | 5 + output/src/constants/teams.ts | 12 + output/src/constants/videoSrc.ts | 1 + output/src/hooks/useDate.ts | 9 + output/src/hooks/useFunnel.tsx | 69 + output/src/hooks/useInfiniteObserver.ts | 33 + output/src/hooks/useMatchCardContext.ts | 14 + output/src/hooks/useQueryParams.ts | 62 + output/src/hooks/useSocket.ts | 44 + output/src/hooks/useValidate.ts | 17 + output/src/queries/useMatchById/Fetcher.tsx | 21 + output/src/queries/useMatchById/query.ts | 19 + .../src/queries/useMatchCheerById/Fetcher.tsx | 21 + output/src/queries/useMatchCheerById/query.ts | 15 + .../queries/useMatchCommentById/Fetcher.tsx | 33 + .../src/queries/useMatchCommentById/query.ts | 24 + .../queries/useMatchLineupById/Fetcher.tsx | 21 + .../src/queries/useMatchLineupById/query.ts | 15 + output/src/queries/useMatchList/Fetcher.tsx | 34 + output/src/queries/useMatchList/query.ts | 31 + .../queries/useMatchTimelineById/Fetcher.tsx | 21 + .../src/queries/useMatchTimelineById/query.ts | 15 + .../src/queries/useMatchVideoById/Fetcher.tsx | 21 + output/src/queries/useMatchVideoById/query.ts | 15 + .../queries/useReportCommentMutation/query.ts | 10 + .../queries/useSaveCommentMutation/query.ts | 10 + .../useSportsListByLeagueId/Fetcher.tsx | 36 + .../queries/useSportsListByLeagueId/query.ts | 15 + output/src/types/admin.ts | 13 + output/src/types/auth.ts | 7 + output/src/types/league.ts | 9 + output/src/types/match.ts | 66 + output/src/utils/core.ts | 6 + output/src/utils/queryString.ts | 15 + output/src/utils/time.ts | 16 + output/tailwind.config.ts | 60 + output/tsconfig.json | 27 + output/yarn.lock | 4778 +++++++++++++++++ 114 files changed, 8088 insertions(+) create mode 100644 output/README.md create mode 100644 output/build.sh create mode 100644 output/next.config.js create mode 100644 output/package.json create mode 100644 output/postcss.config.js create mode 100644 output/public/icon_hufstreaming.svg create mode 100644 output/public/images/not-found.png create mode 100644 output/public/logo_hufstreaming.png create mode 100644 output/sentry.client.config.ts create mode 100644 output/sentry.edge.config.ts create mode 100644 output/sentry.server.config.ts create mode 100644 output/src/api/admin.ts create mode 100644 output/src/api/auth.ts create mode 100644 output/src/api/index.ts create mode 100644 output/src/api/league.ts create mode 100644 output/src/api/match.ts create mode 100644 output/src/app/ReactQueryProvider.tsx create mode 100644 output/src/app/_error.tsx create mode 100644 output/src/app/admin/page.tsx create mode 100644 output/src/app/globals.css create mode 100644 output/src/app/layout.tsx create mode 100644 output/src/app/login/page.tsx create mode 100644 output/src/app/match/[id]/modify/page.tsx create mode 100644 output/src/app/match/[id]/page.tsx create mode 100644 output/src/app/page.tsx create mode 100644 output/src/components/common/Dropdown/index.tsx create mode 100644 output/src/components/common/Dropdown/units/Item.tsx create mode 100644 output/src/components/common/Dropdown/units/Menu.tsx create mode 100644 output/src/components/common/Dropdown/units/Wrapper.tsx create mode 100644 output/src/components/common/Icon/IconMap.ts create mode 100644 output/src/components/common/Icon/icon.type.ts create mode 100644 output/src/components/common/Icon/iconMap.ts create mode 100644 output/src/components/common/Icon/index.tsx create mode 100644 output/src/components/common/Icon/svg/BackgroundLogo.tsx create mode 100644 output/src/components/common/Icon/svg/Calendar.tsx create mode 100644 output/src/components/common/Icon/svg/CaretDown.tsx create mode 100644 output/src/components/common/Icon/svg/CaretUp.tsx create mode 100644 output/src/components/common/Icon/svg/Clip.tsx create mode 100644 output/src/components/common/Icon/svg/Cross.tsx create mode 100644 output/src/components/common/Icon/svg/HamburgerMenu.tsx create mode 100644 output/src/components/common/Icon/svg/Hcc.tsx create mode 100644 output/src/components/common/Icon/svg/Image.tsx create mode 100644 output/src/components/common/Icon/svg/PaperPlane.tsx create mode 100644 output/src/components/common/Icon/svg/Pencil.tsx create mode 100644 output/src/components/common/Icon/svg/PlusCircled.tsx create mode 100644 output/src/components/common/Icon/svg/Symbol.tsx create mode 100644 output/src/components/common/Icon/svg/Thumbsup.tsx create mode 100644 output/src/components/common/Icon/svg/Trash.tsx create mode 100644 output/src/components/common/Icon/svg/Write.tsx create mode 100644 output/src/components/common/Input/Input.tsx create mode 100644 output/src/components/common/MatchCard/index.ts create mode 100644 output/src/components/common/MatchCard/pieces/Background.tsx create mode 100644 output/src/components/common/MatchCard/pieces/Label.tsx create mode 100644 output/src/components/common/MatchCard/pieces/Score.tsx create mode 100644 output/src/components/common/MatchCard/pieces/Status.tsx create mode 100644 output/src/components/common/MatchCard/pieces/Team.tsx create mode 100644 output/src/components/common/MatchCard/pieces/Wrapper.tsx create mode 100644 output/src/components/common/Modal/index.tsx create mode 100644 output/src/components/common/Sidebar/index.tsx create mode 100644 output/src/components/layout/Footer.tsx create mode 100644 output/src/components/layout/Header.tsx create mode 100644 output/src/components/league/SportsList/index.tsx create mode 100644 output/src/components/match/Banner/index.tsx create mode 100644 output/src/components/match/Cheer/index.tsx create mode 100644 output/src/components/match/CheerTeam/index.tsx create mode 100644 output/src/components/match/CommentForm/index.tsx create mode 100644 output/src/components/match/CommentItem/index.tsx create mode 100644 output/src/components/match/CommentList/index.tsx create mode 100644 output/src/components/match/LineupItem/index.tsx create mode 100644 output/src/components/match/LineupList/index.tsx create mode 100644 output/src/components/match/MatchList/index.tsx create mode 100644 output/src/components/match/Panel/index.tsx create mode 100644 output/src/components/match/RecordItem/index.tsx create mode 100644 output/src/components/match/RecordList/index.tsx create mode 100644 output/src/components/match/Video/index.tsx create mode 100644 output/src/constants/gameStatus.ts create mode 100644 output/src/constants/queryParams.ts create mode 100644 output/src/constants/teams.ts create mode 100644 output/src/constants/videoSrc.ts create mode 100644 output/src/hooks/useDate.ts create mode 100644 output/src/hooks/useFunnel.tsx create mode 100644 output/src/hooks/useInfiniteObserver.ts create mode 100644 output/src/hooks/useMatchCardContext.ts create mode 100644 output/src/hooks/useQueryParams.ts create mode 100644 output/src/hooks/useSocket.ts create mode 100644 output/src/hooks/useValidate.ts create mode 100644 output/src/queries/useMatchById/Fetcher.tsx create mode 100644 output/src/queries/useMatchById/query.ts create mode 100644 output/src/queries/useMatchCheerById/Fetcher.tsx create mode 100644 output/src/queries/useMatchCheerById/query.ts create mode 100644 output/src/queries/useMatchCommentById/Fetcher.tsx create mode 100644 output/src/queries/useMatchCommentById/query.ts create mode 100644 output/src/queries/useMatchLineupById/Fetcher.tsx create mode 100644 output/src/queries/useMatchLineupById/query.ts create mode 100644 output/src/queries/useMatchList/Fetcher.tsx create mode 100644 output/src/queries/useMatchList/query.ts create mode 100644 output/src/queries/useMatchTimelineById/Fetcher.tsx create mode 100644 output/src/queries/useMatchTimelineById/query.ts create mode 100644 output/src/queries/useMatchVideoById/Fetcher.tsx create mode 100644 output/src/queries/useMatchVideoById/query.ts create mode 100644 output/src/queries/useReportCommentMutation/query.ts create mode 100644 output/src/queries/useSaveCommentMutation/query.ts create mode 100644 output/src/queries/useSportsListByLeagueId/Fetcher.tsx create mode 100644 output/src/queries/useSportsListByLeagueId/query.ts create mode 100644 output/src/types/admin.ts create mode 100644 output/src/types/auth.ts create mode 100644 output/src/types/league.ts create mode 100644 output/src/types/match.ts create mode 100644 output/src/utils/core.ts create mode 100644 output/src/utils/queryString.ts create mode 100644 output/src/utils/time.ts create mode 100644 output/tailwind.config.ts create mode 100644 output/tsconfig.json create mode 100644 output/yarn.lock 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 0000000000000000000000000000000000000000..1bd5ad3d094a4c2999f66c59161ff41a911a4677 GIT binary patch literal 2403 zcmV-p37qzcP)A;Jgp??ED1GLs`3>**K?VOL~?*?uJ zN(}`7Sd6UI$gr)zWw>Qjs8MhXvJ3{4zBRz8AqT)s`4!5LJ;1dKLU7E+3*%Hv+e-6k=M>Y3rtAhZ+6S% z8E0^_`~6d{|32U#_uCmt?mvL90Amwm8{nI2N$dhnt~UUB+IIsZD)5a4z7A~nHpjyp zy~!E>In`$}@V3Xl*8z7*f=!nJ?>qi&!1%fV;6^es?70NzXCM)scH~uHL5it84p;`f z;PvkZu1@H8t>S$qnpK4XfE;zvZr=wSQNfUF+_cs}EdYjPHbVA;dnrF<9;U*s58S2r z?kfxcx9+2JejhL+pu7e6nKEDxa7C?C(aAm)cumQE88{)pH=KBX8UUsj1;8Su{A(4A z{DtD#223f!c_sf~&Ui!%Y$XnFUk6$ON|!0aHUk|+I9?p9@Z&fxu8lWGf2CWAeo(llCuHSTm zUh+3GM}LF(3_h{m&O5!sfb|N5`2n6Q6n$=90A?#8gN`s@siH3)3g?yXF>W8Q$Oc{V z=&iuJm1ap=0PZH<(MNa++LQq=_yz=~RbiLn_dt( z;AY~@G4&Y>obQ~EK$u;t^RE9&*YC{$O&^CRrIlGi^7bH^Tfapq?jCFH`vH2eS!>q< z7rD`B0Uk5PEC4nDr#Q9;kRAO%TDeHj1JG`aY4T`W)c0{|1(uNXG{|eM-?|cLiOpL3 zd*DK15FVs9=d~RMDYWR;ufDgY6(79R z>ia8c1!@37PX5b%jy1*{l}2C%=NDRQ4_Is00P~4K_^mN!c3NYBsqXXj0PSPyd#0M= z_leKzR_|<`OnkOplj59Cba!w1k1|!gX=8vW5Sp(Ho)chr#nINM z5VpCR^NM%22SQ?M4XHmyvd<3CBjBIR3P77OWL1FScH$)IQ3--Nhzl2~B0S(crTh9S zyivru6IXOmw8NF|8!`dl1gv$P-v}r^NeREdijkKBSEg}Z^_ha)F*rDZXSy2wI|KCV z6n%0Y0KTXM464ik4>{Ta;GVY z7}wGHC2DYP3D8ee^qX=45E$i9G2%x`(a(p-`A-t(BYFs=yVTy$kC)HL3c%57lvf87 zH33g55U#7U^OJ~kk^R6Y0zBs^dj=hinE-GCS1Ey~1r&A==jJSux6fP)OgZv>O3%Uo z&v4?x>Mn8?X?&rbZlkftZHz(1N#JD9xb?u80-aw%dC@hvt|>3$eJ8iRfM8v2T1}wl z1{{f$t0KNWBz2v8b3~51CzQB1n=L0tZ$9beJ$V<^S zXC(@Zr|U4_a^Nu}9?x?G>gQ!-!yEKgS1%9>S+`DEE0XM776&w{1>%HvfZrk0 z{!FCQ2Qmw$Hhk3TI1}z5oQ$!~`QxmuVwN002ovPDHLkV1k4zY8?Oo literal 0 HcmV?d00001 diff --git a/output/public/logo_hufstreaming.png b/output/public/logo_hufstreaming.png new file mode 100644 index 0000000000000000000000000000000000000000..137f156b85d747343fce33a43c8269252d448976 GIT binary patch literal 15075 zcmYjYcRbtO+eSl!s>Gf}irTYQYYVlh8mUpSmD<#%_Fk>6cGV2g+I!PdtHh?LJ=@r$ z-fw^J`#itbACgb_o}6=^bMA3n*Bzy;`GkasjtB<_heSmgu7iVvhXCGNf(d}%hH{$s zffrmiohR}*mBS2MI5^BWDsVYnuNQm%gkHu5-Y4h7*<;p&sr9n@pCDi}H37ng7ec9) z^cL^yK+iami)8bXvvpLNd(50Tcx47jDTu;!l)*s|TL!fz&%XFHkN_)BIEZYwCyM;1 zVzh;taHemY^nr8LBf60xDbI&*mBu}D9%%iBZ{)b``wq`vE;@P3=!}DKp!(eb{ znQ)%a`~Q9Fe;<}d|Ge|xk=`(C+kirmleF5jn*aYhSrD!S|9@sB!g%uN?K>rR9{j&) zyn*mY-1=uue@CC7#brTw5a@=J|Fa%o3}hd91!=C!>oWJODrq#o#pmPIMvS%J*8TsE zBupMetA&<+eKGS=f1CBK-n6F96kU-f%;;{pJ(r1vGOCWvRv0@lU_$s`6HwrN&O$2Y zKU+UI2((>_?XMs}{3QFYIXL8z`iWr)IRTfRlZ0x^JO3Riy%9X*7p(WHmB_pjqM(=JrMLA!5Fvcp&+N(m z&k`nxgJu-ac&9Id5cnjx`Iy82YNfh@`Cap2vKL6cvC+(XGDOdd{yBL9FF@9k zom{V>cs%sxdPH`DV_d~Q<0a(NEUK+4rp?0ze~DFF$DIy*J^pv~tM>&~3H3`>UNAh? zOvxuuc~5Ydbh?h}J_3(_ z;ok+njO>VIN8nSx(B`sf`At-`8Y;KfyWf^58RppKD)-B5^xA7mm}DfA=lQ>zECSrT zPt)5g>OF}xVbumHE2UqdXlJfO5f%hhhZ^<2+m{LZyT;N{Ti(lus%X{u5E^1oxy}!t zqKeM0Ol606$Wz1D3YE}*ZWA5T-+Q9E1I57JY4iOl^P1O@2zQ=S7#mxWP!M9D35~m1 z{XbI(B!S&MJ&P|cE`-P>B4VXHz(Zca__hkfx+3|bsz%ydv*y8i4)fZt9$V-3D!@yQTu?gObQK{P!tfdm`)p{J2<2m8LAPO|VEj!b;VW|r zd%pPAGFAaPf||6LQZBtD{>liB{mv+fQK7Q`Y<*m$tc}gadZKoau^`q8cZ>_d)9+GA zGB71Nf*m!OefR{4fmGNL?W~=kjgh`D+jY40OV(e!5}|j~NV=B_sd)KkFlmkW@7^M# zaCi(h?P56xjwp<(4wulzl-DTPhVd=;e5J~ot(cAq7Q`}DTs2opwKXNVAblFFFNDso z#9qWOFj4PJfli7s?CoQ+d1?PZ-cPRo*#L8xwq+w-t>H_QK}2ZTS-VoC1&X}&*}imKQGkZm+cw7d{23i>PD(5?5b!#Rp~Ie=DJ~zqUI`E zhd?jeWb%?9O)M_{=^za}>n1-IAITDRaJ{&^)Y4PUM%6mntbf4xL}rk37u`FSJjgHc z7Ij*z@Z6iN70(@!$;g9f9 z&kN*^8Zt4`(Op~*XFpDOJ}j4J8gM?}&lGq?Q8eKPv(r~)^IP)dsY=)lzcycg1xxbs z>I}vwUGQ3s3;3z{z%x&mE3B6HHAC^Y(`!*!W?`-4GgT3K3xS9Oam_^<;euKMs$1}f zUT25Pg}x`dw54Xlf@w%6;fx{qewR0wl1ryqwHOJDp{M;WcTbLwr34lwe)`BEVGs@% z;xd>JWuIxB3uS60?ll{`J?yo$Yu|ep5O<9%CHcoMLF108qtTNCa{J7ZAt50)aj~%# zECp#CML8dn@o@3v8nrl&iHjysjmG;=9>3ldxAWXs^L3bSsNMrYYT(CY!;zt#^m886 zF+-ww(Zmajp#;YM#(>^vN>8W7mYHOFtM4B*G_J3&(;mft4$rtT=JZI_vCGlnDo*y% z=JcQz4s0zdvg%LeHM&=B)mo~{wJbi|*q82zl!sFbV9gV05@oI13^+YP1g+KB-$`g& z-JNZ4vDk?se~>dgG;+TnCgYr;NH;=<=mXpH!3!?qZn%4LhNm#rp}V_#YoXbbbSBnR z-7Z#8>wW=o)Miv)`U7BP%5b$vTik&Z9j@|ZI(=0}1J&!NoX5UJ6W8z~BWr^5nFH^| z?!b%is42dd!>*6dw4{z_I%gV!BXcN_Vg4;oIBVxYY zJWjbD?y)tSl$5mN)P8;P#}HE=aDB19yu8c@yAUgyc*YeQly>?W^ycK`WS`4lCFBe; zrUSdE(Aj?LY5_?S?CT&pf%dyNZtm>R$2PuLSXl567bFNOuBr_FB53_57;~XkxwB|$ zeHRfz#YPr27$;bztE!iX6d3jEUDS1{p(P+4HqpQlB&~PtR81YH; z_~fLEOlVfW(zuZ|L*4w329>>d&ybU9@prN3M~r))UhmCHocsN8O3}%!ZrB%xUiWu} z5ijZ4J;kl-Phh&SFQP!Bii^IQKPO84Xv+5P%P`8Q{vM}i=XjCYJ7OvxFTjSZWL`Pe z!f3_Z;1u8zC|V3@WT>wjP)(Ika_jTh+Sy%{=xiUrE-v9mZbcI@D-iK45oIR1JbjQn zENsWDwyV8cz0+A;S(w6#it3N2a^EO*sM-g5yM>_Ac1I;h#kf~-X@tlk5qrTaO%AoE znlB1ww=osZ-7*g@rBq9cjpp%}{vJ~^jr zXqkD$T%Ch2J9NQHf}n!2b<6!iG4+~w;Z2m_x~O%)$2E^j@~EC7DJd!Ev@ne-!`f}h zewQ`{IQ8@RX9tegLszrzkFI5kZgo?7&b}-*v5WOC*4geo&Jm{}b?h;U6FeBP6l#x4 zzlCYT)?l2U)Eic82tZpGJWS@WmVzJLp`gBqj_t|v@A(wBl;qX*l{Qgt zXNS4l<;7B+nfpJvByn7#w2MNXML!vGDJ?JG@v{yO>%?^ZMhku%TnUq~{!*%2reJ?9 zS48n*Q`8|n($Up~y0TXOmdZ2L>qg9lPZloT>56|06>k%>Qp;U))?xgl8ox0M&V)zt zZi-G$2gA-LgQ>js4liGNOH>%8+Gj^Ph74Ve0M=OT4e8d)`ew}!jP#wgffst~wSt=) zcCNg)8^3aC4Q^TP8h2Jz<&|ULT(3+HqQeb(dsZed>)XIb9~{=U0(%vjUEx3?kGAVZ z?Hj~RMeA`A@ZRkt!fQ+u#GcTIopC^0B^|E@FOGN8EI-dw-EUZMY}w*K~~nO)&mpRXae-8V<`VFfcAhJ^aM z&VdZUVXs;gBJQ%N?XyzrP=XFW`Dr}LZWFnyYGPudCD7-W7O9VTk#_rwi;3s+>A>rY zW2|fAyo>;LGSz|uCa=HTTe$;MmidIAN@01q7rA>C$S81PBsFsR`mEi#edvm@Xrk=z zQn*<~^HA6==N}z!BO@*?4Fkd>HcuW)~$e z3($h!AkA`^bIJ~v*g)lorsWT8gq@kOCYcQqz(O)BJNJr=4As!halX1>4B|s^G}7bs z>zv9zAL>tjMqNr6Fl9vEwL_UM&dtSi7&ahSq+AGvL$vwRdpuZv1SD*DkB|q8M3oHc zzKfTo#Xs$$i?*mz{rZ#Q6S$lG5#n8&l}ENVSl?v1!XGUTDsO;mO~lHh=|Yemo}Rf@ zv2qO5M~xo~@E@lQ! zwlhm1t1uS|tLTJ95j=IxMVn;24+=wS+#OE(JPoPf@I*GMgd#-b=lEIh^ryz-tZ_TmlA}QWY|R9A9(BP^E?wS3 z|3E(B2XDXp9Q|Z9NWeunXO2p&v2}3Berz-RKJ}I8PEB2%_36QqOoQjai^#e7a+b$+ z_4S^bLK&}4{AISp;|puo;V{7!@w?{VKC!6Wn}WZlrbf#Z1^G$mf~5OsTp4UYJ726n zH!Lkb(p8}`AwfIX+1WWcI5_k&K+tW~xYkqHU&n*-9agHBNi44C@KoEYiJM#<$ z13S#taLz0($&g+K`2$m#MoMwPV=q7Q5CGK$?zp9UsS?_bMSnRn3>ml3%IOa6V?!`M ziOjg*gih+d1s4UUpM#+++(!{P8**XoqlX!!0Q&>{N<8@-{Cu}q!RfRx!<}cp-h+oJ%*W6& zk<0$t_SeW~&%Os046Wc*i}gvq%Xs>Sr{c#OA>&h8i@QSD9CdbV!y491wF&SrWYbzX zHDsNiqCS_sCYDEk#$)5M8G$Olrg2p^pOA&EiaQajfL}P*F9lqZSqF&T z>%kqvRq>72*$zwHQ81qJAG-SXV~55nINWckUkz&IdqTy1cKZvl)&m%5Rsmj@PO-uk zOFcrGth1zC{C&%6Fjb_!rbaR%GB2MX8||8MJ2CPXx5dzdq)~{~B(B+GpQFvKzI5V9 zFh7~Dh!=ry2#;=A-kZ7Z)}ZOD%t&nl&JgMyASXH~?`^`fkmpK~_bwv_&eh}Tb}RT1 zg|*Vl4|evQK5wLyN41bpE9;WeB{2bk!^diBwCbi{2Xr#(bVo zVTD~9?8>hGrEEuQa0$!z+3$uo4>Qyk?d_k!)%7`mU2;Z-IwR87*fJInF zjT67iuyVPcpwn(wYnH~Xf2B7j5bzMB_N-+3%tFS7`L`}&0+|aEHe4Hx>`<)c=f<=K zVl0T&;Hm)XG_s0c2voEI8_0f^jK?p-e+=7lI&qMEy`(I6HQ5g0e>lf^cpRYkDEr>z zDOW3`2cO>#CGwMuk0f|w*}C%#w-m_VsD*Vy#&5ZB_Uyr%XGPd#0bhX9h#Z4+2W6|FCtH=+@7Y~J(G~vv8PN9l?eQ}fqCJij_zT_nNzch9KTN*cw z)onhIz477X!Ac@>G6oUG4a8>@A!b8rB;?Fy!H7hN7SSy|f@N-^JMSQgQ6wLGwrm(| zZ*OaB8(1n*+1yOemwhUPz1$9XXK_1}m{|2-ir=KE2G!bny=J&mg%~1y8%8Z$;UXIS zJCZl(SkNN2u^@RDCv9)8K9?@BOiW0as5GG&AB3m>At5$4_KG=r0@;<9*#+DLYX}vw zn%=y}3jP^C;Qq3JTpD52NU%dYuV0VYMw`hX<$?!GQVBdC|I}gTzLj13OP}|R$S>#w zCnGIwqf380qjLhLxwjY0L>XA3vovY#4*|8Q*k4z-wY4q5O|*~9jzx zmF{F~SPM&E{Nc*hq!ucqM-g_~B{Jf6n(NF_q*1>w_sEb(_rb*C%+bisOo8AO)uNn| z+CMoucDe>bE3r~TL)lyIbf&e(wq|+Pj)M_fUh6$E-lso)APtO+jGhQ(fEt_pByTAh zUq&8Wra8^k?L9DSe`Cyo*-?ezxx~A+PJWW?Dak82r!Mae!mpNaW|F(2;(H%u7fF8^ zth3$ZI_u^oD2!!`U(HLrw5*l?9l>i>XFS1DG%*Q)2RBcLd856mF&IorZUJ%nWG+06FK!oblV1y5y8Ze2KgfxfS11XlH+IBND z3ql_KHM^wNekw-@$f!tQhSaM)Ea@k6@vD9phs)ikh`uRZ25Dz44A1B#hSIWgfri^n`3^jjSsg63;$zQNqE5{0Q7LcT%esIy-l@q?upnD%Gp4n zHu8??)WG_!iLXXoLXm`>T%He~HuckBybl@mB1U)lbH!~h{D(&b_t^p3lM0}Ap> zIJo4m)V{Cl>EXe$Poy~BPw!-Pi&RL1R6rOb{P8Eni{Y79R3F?vf0HFyyxkts28aC` z*V!@43yF~RXC;T%Mlke0UznS_eC+<-JXV(B)E>pZv$Lb{aV4E##}J65<4H+SN%FE@ zHN^!tRN#~G&V`%}7K!}jOh`re+}jI2TQV?Jo3pe<>fHAtZF zKG+*i7aH!Q9UULJd&MkglR zXB_Gm>i8LK3CJpzVbF!A=8wM-QUyHr6cH2S=T9o+=+U-A)oaO~RP8+{54MWY<-N96 zuO>8CQpz> z)4fBLn2UHn>6R)7?f!(bX8-)!JnboHKV$~C7cj#h%a`XK{i^-dC8Z?0z2K5mu; z>tCZKy;)fhlVg;@B2t9$B?YA?n3rR4wz1okIhEN{@@4JC;fd%gDlS zNPcx#3Y}*GI8U%`HtPWnHzr>pg1P40rD!4y4yxTqeWTpNLcaWn$gLwfLWensPCB{N zcVjsFGT+Ftacvq7N>(*fTrv|J2*ZzlbA2GesPt&cweJcRQS@j$(f-5=VGf(N zOSTLAr9=GuW#x|ieC=qT3fd1QZ_RD_&^dtR18rJQd?o)?31M=VnP5Z!g+PMuU8b=F~!wdQVh=GTQHB4F2HBmC;Ez@$c50jkqrpL=+QG z!&i)gf@{~DUoQkBcOqg1ZxrD&0)1B%!A%l8TmX<7B1j;gHrg)t=yFDo6?FLf4g1{+ zAzNlc=Co6Ki-0D}FK>Cxc1Oesspi?3UwK8r9`hxP^S~dHC8J$#-@e^WPQ>ImjW+8X z9fg229Ej~A*xt5fDzi#2egA%o+fvR$5=bfhzGWrrD67=hO77V^ItIKh1+z!){ly}` z4&~~p$^|juRW*<%|HN(deUou8X{cc4>m-`BkgLpkukR0jwHugAiC2YHMOSBwPl{u@ zu-Tx}_(C!{thsdR@pW<4;vNb=ABS^9-3DJp92b5h}Zlufm|$>#24Z zcUWvi%r{n0%pMg8;Q8k38!Ut{8t%IodGzkR{@P=*U^Y-pMpd1Z)aPX6@>)BvV+u3g z>fM7vX@e*m>jsByRKdMre6_C$8RtMm>7MgESd2USR7O(d7b{pL)HmIiXBA}|v(v;; z^|UrF1i8%inf#ZvmkV_4AR;_G96*S)uNq!;&UcW^kte2?6fGY#Brr+DW4B$a-M9e@ zyj5p!r%_=3TyPOkQgCqjXGs97C^sq-9kOH*>+-h--JAR%9&Crhq^BBmG$Y!xDJ4aL zL?t1EIIhNN+^k&mVU{v?86SnZikaRo5O$!ZNObve^ap?wYe#2q>omJ<3}gM4Zbs5s z@is0AMzGo^Hf3NcX^{4(&2PP*pbfv}ve7;?#Ay_Z%nq{>&DN|Ut$N=4dN1SL zFYM?8H4YA4(qApWpM7z<*Wmleb^0320S})^n1=(9xLnB!?|sTmIQ%wB}o#++gD)1rF`@vB!JPEJ)um2R@Irl%ug*65C}uj6)_a-%j)IapiVx5k%8 zn~w4IL0bW~Z`*O+QCR>Xev7B!fQ_soJF){KevXP-8_ z6MvRqY`4aXPyi593?18ohgN9zbOq7yDM^wEuim8e?dQE_o_E!c%OC~lpUSGLgS3V` zrC_~LVeWeB#5`qIvJtVqo}@fkfs0AyHyobiB8flcCvirwKR&%D3)YKv+wuosJ=hD9 zndV5rm{(^cG!`cxzrXCkp)$0wc~E)n|FidI3YUp<2`fp;_w^GATZ=Y_l=I;19bV8N zL6PAlAhn2p--C?nwMlIP%UMEV+~vY(O7N8BMz@!4D+cCIh5q>NQy(mlC%8^Y2^S#7 zijZ{@eJ%3?#si1-y)>&^tgJ;!aqB%tH|oDHL1?=}rQGsvAOBCyR=kgtmir zR{*z9Vf;_tGVl( z4NCCyALG94tcI`n^;KC!_f~<-i@o|)Ig9fPuzoB_6F>!?cPRzA;WX6(-E;Ya1t68*}LjU36AwUcg6;a$PL)+FUn$WBsI8(Mw zdzeu8NSBJxbjn9x_yU*Az+&EXnGLjd_C;5n|Fg6*@2;~+{pT|$wCwI|?T-7>sVRLA z_e|ePPf(umQaCFj#W)@}1E*7-4E$;SmEnCzMHwr_3<~hgx!@H(hkZi-K1c=K@QE4B z2XM<%2AKjWs=!@bi2Jhn0Az1RsL#w7l?U(1=y~auLHlyCj&o5Sy^?oXe8%3hBwKQX z0RXJ>LcGDjI|+Eb0PuEzF;q|^H0>D}mcQ!rG9A#f2nj{@&h2uw5_8SxY)I8Nj6_p% zQ#}+DYbVce^T5c)7&ZVva%l3SQYy0{9f>4;X%@s-mUhBDm)^c@K^v2PNU%sPNR4xH zWT;A|_QE6tm zJ0f3gQUzX~POFc4032BD!3(^&3R6;2QvL{Ln=KlEB9W`z6&cOB2-|6*jtC5U_;(kG zBR8{DIYFgb*5tvIaVPF9h>=Odv|3q%21QoqmU2WzO^qqC(??j?Aoe>~qmpsx*hcBe zJ$baXSm#&k&o0DNOagtzWO`j}a7xZ^04;}bjOFZ!Mby1hU#_L_wO$W2ZSndgV>m4F zan7?1%h-b8w%(c)vi2e|Hr3G3pgwC$i0z()yUWF8L?qByh*H_phPi<|=|=9xHJ*Gp zy71V+WIqBbOoyTgE03(+0HE?5Lx@Or5*ZIs12BucXo*!h;$hR~%(xuffc&X0#@;Im z!lQO*zs(M^KLMbiJ;+!JT-VSZ1>=CbL->!~AGP+ccNFQAQUQtzq_yAPoG9Y>MXqKs zi=wt|Erh3N0zfn_AIzydXlA?VuCyl_$cm>f5@Uis9Q;Ol3bkS#EeUhM# zXpR$3teIn>{fXq1fmBzrs=|AgF!I!szl7B7ZYrxfH-d%nW z_l4x-q5>GR%Z3OdI#;r86YL{=RQlspt$j$1*~GT*lUNBzhkxTvlT5}J5O<4M;wT(e zJxmia#MD6IK(Bh%GKZD?5HlhktY^XX7!0*!giY@A$|?Ph+VEa{G8%aPduf>B)(Fp1XQTeI5YE z2%(NoEgCArkczp*v%cWY%vKP0B(c0k&0~xA%#jQJfws1r9#nEmO`kuLS1UOe85e+@fy`J^L?G|1xpnIp3;*&_@e_o=w1~d z=J;MjJp>}A+UHF9tMpL91KX|5)N}*KG54nHqnj>Mzf0_@ctjllcwa;zNQG9k18kp7 z)G7u(Ie-5+otD2>hCmTT9B6Gl0f+yxrhwMHlt&jl&7=Zx1LNh_D(z{pwocat!(&#( zr##%<=b13KNq(B;gs76jkMQO?KTNyb`Czxj1uB1J_bx9)#7b_z0o$P-M~!%om+v*u z{t^HT>O-yMzUEIaEJ!WUguq(C4K>&D{o0p+Tmf*S%x+@Cq%z#gGL2$Y7{t*fkcNg+QC* zQI$7CDrPj$`u_`*yy5246g3KhL&oNS>|+ElG?BriZHU$G=2+nL+i?>huLnf zch2o`p;@{(B&8L80W&Y2s!`088a#p4!jZKgEAh+O!2JY<`DRc4d4mVF%-nl0l!&`W z1@Qn^(T~d3S&xO0w7X3%t9^B={R!Pd=0>};K3a3eQ*ji7@704TlmEOSrdNz~$Rt&R z4zCv|#t2c(m6s(b^pV!lNcJ{`u31<|G__crUj_t!>{2hjeiOhM=@%gRS6;wGPq1lEz0kE!X zs|;E=j@_`s&=sq7IJgi6u@GmQ*Qf%bSQSI(BbSw)!Hjcm(M#GM&yMXAvZYg|K843g z$`9AD-icj7LKry0;#F;nT^&+R@Nw=);M)xDlrm0F-+W3af2A*3R8J6q(60c1@XRw! z34H$i^;e>Ej;U$b-X4-vglCa>peH`KLvY)Aj*>t*3#&6hN?zetmCz`hb+jQ2Bm18xDWP_m$i}dG)VyunXc5Y`}a6NbRqrq4bNT zQ1?*r^t_EM62f#OtNrZGz80d@`TCjpgg<@uU8|V95X{t6gkXOS9XSn}ec)vW+Z0{T z?M|{&`hJ(!fW+i>D2c{LeB#sw@&}qNWN>MvI=x8zNFMj_xE6wl3x%O8XQ2#9yV#dz)|=B(xsjrC zIr+%Hk+=i!ieYo$6|^i`DMt3m(hC#7{grXQXAAlEFqm?2@yUp|ubY%IM*RVzgWH*0 zUgOUexi^NVFPvtZvF)IWkGE`InW{n~OH_nW!qt*j_-1q^hrLWqu|j~QEq-Gpm$W|9 zC<341K|vMym`uo!274!7DL^K}rJf+Rm?s`(?0q;J=qPy2DBNEv7m)z@uzH|m3;@T_ z0Y*ETsUH|J{a?Pry1{z<1tRod{loi# zQo=WL6xRdQCIby2#Cu#*9yf(YtYT!H!5+4jmOmA>#}jU1%I7E}_c%9vxYV^-Ezdq1 zR#;f+d&fpf?=|gO$Gc5EqgepJ(uYdl z!;xW35ctu;8}}zmCjNg7#-C&HBx?&~-vYN6h45SrG&eOtSiQghos9d$ zHXk|(5mHf6AmSHQ;Ro?gfB3Z^6e^BDxj3+9L^td!mcAp;N@2(Mqn5o}-C8 z$t@`g!hg{39Lj~-grb_Ege)>ZbUYtJYX*?cWu;%GJRcU%0_%fS-k(*>$fMtA&>U*Q zrwDo@d9}tGn&cjA0zx!{DUYhEhdTG6JQdaD<-{%zol6UbzFCS|vMi3y&tC!hy*gQ| z7W0tWH2|MC3A)U8Xo-PqODW?E*Q<+mcFl&I729hwm~i616&u?_3$F8Ks9O&;!hc+k zj*bq-3W8g-G>>4ae{+43vzq&eh9X(w13mbWPtnBpWSz6@-j`^lYbc%Hk5{SGE9cGK zSjo8$B!gx<-!;Jc{>}_H93LwEZ-V@@R_|CU)L4^u5(WN@Uv0_ESRzAwV&9Eb7|YB! zm@o`111iwllP8s9U@E(D=wBHfKx(XrZ&dcRopC9ukA4FPo-*y{l6Dm-+y;Cq?C%oo zrfF8oPd=E1@C+>7At5mt2RzeIzls}_iZ>inY0}IJ_)T3-k6w@wsoaYNK*TpSA8@&M z&Gu$AQ^a0Vq)(QW%$2ovlT^{cq^SMxmN*)c@yt`rhXD}$&&^u+(H(1PVf3`V@7;)z z@K$)uE9dD1RNAJN3Z2CD?&6*tq)R;hx=IbMXUR8Y}EKUiHUS!U4rkwYX%k*H7 zn=CQn`}5eioo9&t3eDZ>E68!+Z#$me}V`+LpeC6O=BLyJxcU4|b3 zC$>D<5S1Lvs@#fF9>@$e?v04vF{9!K$$tjKom#|S*MEeG-+LRjbZbC3RI{+}uQ&N! z%-c!i#0#Fm+2VZfVlm{WTnrlL8yyO+C1S#jGzlBUw)UlGG4{(zd?XdZW)+(0aGee8 zVI`eHG;Y4^KilcH`!x03r43-b?vzd%FilgR7gU1aY;Q&A38@5#K8C#0 z!UzOQoM>vr66h>mPz5A2ce{Fdoh`tR#L1B=e5qyGA)mL(*^t^pn?cNL48wgnj5J)DCz6 zo8L$PkbcF!CVY`O2;x<_m!C1`G06zFB6$c!t&@M>`4OO4C~7;nRlEU#-h1sOCpY0* zNg_xx@0_TB!{VKsaoVXNA}Q%N2wXdekzme8!1JSRAbULuXIDrEFGSksOyq&APX3a+ znCiVS{!v}7V>n8rN7ThhlsEbDX7v_BA?aTV9qZ4u-=&EX`NS^g`bH|6<4J9o8j;E^ z)zXNJb5ej|l*t`wUK4uGeTS-v_o)z}FKI!7;i zp_;_RHK09#Yi64NOq*=0NVT+HX@TcYyf8uVTGKu^_wkdsmRF)NXF}Wyg0Sro>(5S$ z)fTrw5n6V*gjc@0gy`oM%rl<=ylK}ypOUbJ>MO0DJt6^+ifnFe;kJ>7K|d%gb`2HU z#~M-(r*C+l?J0^%Y60qXmWc8ES_~ZY`=;UG@Xa zsWDdo@z~N)lW>j8qL}FYHsc*UG>{>Akfk6AhaY+3B`Y7ebNOc^mFM|yEWJ|;w5P$5 z*V!Svu(mBY13(e@h5CkA3M9^i*kMDb;;I(_(>7|jBq<<(E^KgiW!A$votDoE6Lb?z zQl4J`xPd0V@>eO$ubmiNy3nNcMf86tlO_epKY`FSY9+W=D7)E0Gc zDCrc4is9~}EYeEt%%p5^H$+d-h)vrJ5wG~j*vVY91=0tQ2Z`*ez(YL@?7NAn`L0~X z8gX30?dei$2B?y4eQGj2_!`oL8T+e0)gc1`rsH7tvkj^A$FCy)2T=(~Pr$McE}8QZ zYyfq`2@wfX6#%1Z0l*g%p&mU(_~FANEC7;s|Blx8j76I==K<;&Z6gsF-S*B-^Zhcn z#HVL?MH9pTST4z*ti#b5My7wJ-FqIiMmUo6EaRL97JSLXq~Su}H^1q9@8~yO( zsX!sfWh=X$5xk`ob8cJK-0a~~1`6tWK=u+A1vi(|j)0KX_5l1D4s$B9V2iuJD1 z)d&=#){AJgUG0nei;yw)(OiY3>EiL5qK>QA*VhZzq9vxx7Q2WU+leiP%==xaXe?xn zpT4JddG#dzo|I1s5pC&rGRyR>NE~U_S`6CN|7GwyaU4>fhUPfI?6?b~TqvkKU3;Ri zW#|LrE+MPIyVs;bN&j><9X=o(Xav;XzC)6_2V?a96Hed2%gd-R~k+@RuY}Asl$`?+rv9fiMw)cb!h2Z%C>`;IKxGNt2d5H##1i+t=`bD3ed`<{&<>Ne{ z3-4~}9gcsx|G;>k8G**f&)1-V>;LpB{cn@z!rvxMPh~6Ff7-odA?bTSV_4?i1KEAk uf93#eXZ#yTMUw*8t^c>sS5JNf;$A8~$o=S2{0it=#ZghzgjdQVgZ~E;4pB4! literal 0 HcmV?d00001 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/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..9c272f0 --- /dev/null +++ b/output/src/api/index.ts @@ -0,0 +1,24 @@ +import axios from 'axios'; + +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/page.tsx b/output/src/app/admin/page.tsx new file mode 100644 index 0000000..2a736a1 --- /dev/null +++ b/output/src/app/admin/page.tsx @@ -0,0 +1,126 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import { ChangeEvent, FormEvent, useState } from 'react'; + +import { createNewGame } from '@/api/admin'; +import Input from '@/components/common/Input/Input'; +import useDate from '@/hooks/useDate'; +import useValidate from '@/hooks/useValidate'; + +export default function Admin() { + const router = useRouter(); + + const { month, day } = useDate(new Date().toString()); + + const [gameData, setGameData] = useState({ + name: '삼건물대회', + sportsName: '축구', + firstTeam: 0, + secondTeam: 0, + date: '', + time: '', + }); + + const { isError: isDateError } = useValidate( + gameData.date, + dateValue => new Date(dateValue) < new Date(`2023-${month}-${day}`), + ); + const { isError: isTimeError } = useValidate(gameData.time, timeValue => { + const [hour] = (timeValue as string).split(':').map(Number); + + return hour < 8 || hour > 18; + }); + const { isError: isTeamError } = useValidate( + gameData.secondTeam, + teamValue => teamValue === gameData.firstTeam, + ); + + const handleInput = ( + e: ChangeEvent, + ) => { + const { name, value } = e.target; + + setGameData(prev => ({ ...prev, [name]: value })); + }; + + const submitHandler = (e: FormEvent) => { + e.preventDefault(); + + if (isDateError || isTeamError || isTimeError) return; + + createNewGame({ + name: gameData.name, + sportsName: gameData.sportsName, + firstTeam: Number(gameData.firstTeam), + secondTeam: Number(gameData.secondTeam), + startTime: new Date(`${gameData.date}T${gameData.time}:00`), + }).then(() => router.push('/')); + }; + + return ( +
+ +

팀 선택

+ {isTeamError && ( +
팀을 다시 선택해주세요!
+ )} + + + + + +
+ ); +} 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}
+