diff --git a/package.json b/package.json index cb90ba1c..241ae4a7 100644 --- a/package.json +++ b/package.json @@ -28,5 +28,8 @@ "license": "ISC", "resolutions": { "@types/react": "^18.2.0" + }, + "devDependencies": { + "@types/gtag.js": "^0.0.17" } } diff --git a/packages/common/src/PageContainer/LoginPage/index.tsx b/packages/common/src/PageContainer/LoginPage/index.tsx index 77505925..a8f9339d 100644 --- a/packages/common/src/PageContainer/LoginPage/index.tsx +++ b/packages/common/src/PageContainer/LoginPage/index.tsx @@ -16,7 +16,7 @@ const LoginPage = () => { const { mutate, isSuccess } = usePostLoginCode(); const handleLogin = () => { - window.location.href = `https://gauth.co.kr/login?client_id=e6e8ac7857c94ca7a24db504d33369078ab562d7a29a4c9db353204ae8080be9&redirect_uri=${window.location.href}`; + window.location.href = `${process.env.NEXT_PUBLIC_GAUTH_URL}${window.location.href}`; }; useEffect(() => { @@ -37,7 +37,7 @@ const LoginPage = () => { Stack Knowledge - + ); }; diff --git a/packages/common/src/PageContainer/MainPage/style.ts b/packages/common/src/PageContainer/MainPage/style.ts index 43bff4a9..0cc4436c 100644 --- a/packages/common/src/PageContainer/MainPage/style.ts +++ b/packages/common/src/PageContainer/MainPage/style.ts @@ -13,4 +13,12 @@ export const MissionAlertText = styled.p` font-weight: 400; margin-top: 1rem; width: 80rem; + + @media (max-width: 1240px) { + width: calc(100vw - 12.5rem); + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: calc(100vw - 3rem); + } `; diff --git a/packages/common/src/assets/Login/BottomIcon.tsx b/packages/common/src/assets/Login/BottomIcon.tsx index 4ea8aa0a..167bea41 100644 --- a/packages/common/src/assets/Login/BottomIcon.tsx +++ b/packages/common/src/assets/Login/BottomIcon.tsx @@ -1,8 +1,8 @@ const BottomIcon = () => ( diff --git a/packages/common/src/assets/Login/TopIcon.tsx b/packages/common/src/assets/Login/TopIcon.tsx index 9cf04d83..73ee1184 100644 --- a/packages/common/src/assets/Login/TopIcon.tsx +++ b/packages/common/src/assets/Login/TopIcon.tsx @@ -1,8 +1,8 @@ const TopIcon = () => ( diff --git a/packages/common/src/components/Banner/Banner1/style.ts b/packages/common/src/components/Banner/Banner1/style.ts index 733b0e79..6850a084 100644 --- a/packages/common/src/components/Banner/Banner1/style.ts +++ b/packages/common/src/components/Banner/Banner1/style.ts @@ -6,18 +6,35 @@ export const BannerWrapper = styled.div` height: 18.75rem; border-radius: 1.25rem; display: flex; + + @media (max-width: 1240px) { + width: calc(100vw - 12.5rem); + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: calc(100vw - 3rem); + } `; export const BannerTitleContainer = styled.div` width: 40rem; height: 18.75rem; padding: 4.375rem 0 0 3.75rem; + + @media (max-width: 1240px) { + width: 80vw; + } `; export const BannerTitle = styled.span` color: ${({ theme }) => theme.color.white}; ${({ theme }) => theme.typo.h1}; font-weight: 700; + word-break: keep-all; + + @media (max-width: 1240px) { + ${({ theme }) => theme.typo.h2}; + } `; export const SunIconWrapper = styled.div` @@ -26,16 +43,43 @@ export const SunIconWrapper = styled.div` right: 1.25rem; width: 18.1875rem; height: 12rem; + + @media (max-width: 1240px) { + width: 20vw; + height: 13vw; + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: 30vw; + height: 18vw; + } `; export const LetgoIconContainer = styled.div` - width: 40rem; height: 18.75rem; - padding: 1.5rem 3.75rem 1rem 11.25rem; + padding: 2.5rem 3.75rem 1rem 11.25rem; + + @media (max-width: 1240px) { + padding: 3.8rem 0 1rem 0; + } + + @media ${({ theme }) => theme.breakPoint[600]} { + padding-top: 5rem; + } `; export const LetgoIconWrapper = styled.div` position: relative; width: 25rem; height: 16.25rem; + + @media (max-width: 1240px) { + width: 22vw; + height: 16vw; + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: 27vw; + height: 18vw; + } `; diff --git a/packages/common/src/components/Banner/Banner2/style.ts b/packages/common/src/components/Banner/Banner2/style.ts index afc0d937..7f8e20f1 100644 --- a/packages/common/src/components/Banner/Banner2/style.ts +++ b/packages/common/src/components/Banner/Banner2/style.ts @@ -12,6 +12,14 @@ export const BannerWrapper = styled.div` height: 18.75rem; border-radius: 1.25rem; display: flex; + + @media (max-width: 1240px) { + width: calc(100vw - 12.5rem); + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: calc(100vw - 3rem); + } `; export const BannerTitleContainer = styled.div` @@ -21,18 +29,32 @@ export const BannerTitleContainer = styled.div` display: flex; flex-direction: column; gap: 1.25rem; + + @media (max-width: 1240px) { + width: 80vw; + } `; export const BannerTitle = styled.span` color: ${({ theme }) => theme.color.white}; ${({ theme }) => theme.typo.h1}; font-weight: 700; + word-break: keep-all; + + @media (max-width: 1240px) { + ${({ theme }) => theme.typo.h2}; + } `; export const DocumentIconWrapper = styled.div` width: 18.75rem; height: 15.625rem; position: relative; + + @media (max-width: 1240px) { + width: 25vw; + height: 18vw; + } `; export const DocumentContainer = styled.div` @@ -41,4 +63,8 @@ export const DocumentContainer = styled.div` justify-content: flex-end; width: 100%; padding-right: 6.25rem; + + @media (max-width: 1240px) { + padding-right: 2vw; + } `; diff --git a/packages/common/src/components/Banner/Banner3/style.ts b/packages/common/src/components/Banner/Banner3/style.ts index d21f7673..6909e99f 100644 --- a/packages/common/src/components/Banner/Banner3/style.ts +++ b/packages/common/src/components/Banner/Banner3/style.ts @@ -6,6 +6,14 @@ export const BannerWrapper = styled.div` height: 18.75rem; border-radius: 1.25rem; display: flex; + + @media (max-width: 1240px) { + width: calc(100vw - 12.5rem); + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: calc(100vw - 3rem); + } `; export const BannerTitleContainer = styled.div` @@ -15,18 +23,32 @@ export const BannerTitleContainer = styled.div` display: flex; flex-direction: column; gap: 1.25rem; + + @media (max-width: 1240px) { + width: 50vw; + } `; export const BannerTitle = styled.span` color: ${({ theme }) => theme.color.white}; ${({ theme }) => theme.typo.h1}; font-weight: 700; + word-break: keep-all; + + @media (max-width: 1240px) { + ${({ theme }) => theme.typo.h2}; + } `; export const GiftIconWrapper = styled.div` width: 18.75rem; height: 18.75rem; position: relative; + + @media (max-width: 1240px) { + width: 25vw; + height: 25vw; + } `; export const GiftContainer = styled.div` @@ -35,4 +57,8 @@ export const GiftContainer = styled.div` justify-content: flex-end; width: 100%; padding-right: 6.25rem; + + @media (max-width: 1240px) { + padding-right: 2vw; + } `; diff --git a/packages/common/src/components/Banner/style.ts b/packages/common/src/components/Banner/style.ts index 06936a6a..c426145f 100644 --- a/packages/common/src/components/Banner/style.ts +++ b/packages/common/src/components/Banner/style.ts @@ -7,18 +7,36 @@ export const BannerWrapper = styled.div` display: flex; position: relative; overflow: hidden; + + @media (max-width: 1240px) { + width: calc(100vw - 12.5rem); + } + + @media ${({ theme }) => theme.breakPoint[600]} { + width: calc(100vw - 3rem); + } `; export const BannerContainer = styled.div<{ currentBanner: number; bannerCount: number; }>` + width: 80rem; display: flex; transition: transform 0.5s ease-in-out; - transform: translateX( - ${({ currentBanner, bannerCount }) => - -currentBanner * ((bannerCount * 80) / bannerCount)}rem - ); + transform: translateX(${({ currentBanner }) => -currentBanner * 80}rem); + + @media (max-width: 1240px) { + transform: translateX( + calc(${({ currentBanner }) => -currentBanner} * (100vw - 12.5rem)) + ); + } + + @media ${({ theme }) => theme.breakPoint[600]} { + transform: translateX( + calc(${({ currentBanner }) => -currentBanner} * (100vw - 3rem)) + ); + } `; export const BannerItem = styled.div` @@ -26,7 +44,8 @@ export const BannerItem = styled.div` `; export const DotWrapper = styled.div` - width: 80rem; + max-width: 80rem; + width: 100%; height: 18.75rem; position: absolute; display: flex; diff --git a/packages/common/src/components/Header/index.tsx b/packages/common/src/components/Header/index.tsx index 71630381..82d862d6 100644 --- a/packages/common/src/components/Header/index.tsx +++ b/packages/common/src/components/Header/index.tsx @@ -37,7 +37,13 @@ const Header: React.FC = ({ role }) => { Stack Knowledge - + + + @@ -72,7 +78,12 @@ const Header: React.FC = ({ role }) => { )} - + + 상점 diff --git a/packages/common/src/components/MainContents/style.ts b/packages/common/src/components/MainContents/style.ts index af201254..21be7d41 100644 --- a/packages/common/src/components/MainContents/style.ts +++ b/packages/common/src/components/MainContents/style.ts @@ -24,6 +24,7 @@ export const Contents = styled.div` padding: 2.375rem 2.5rem; gap: 3rem; overflow-x: scroll; + overflow-y: hidden; .medalWrapper:nth-of-type(1n) .medal { background-color: #ffd79b; diff --git a/packages/tsconfig/base.json b/packages/tsconfig/base.json index 0a237341..1a6e377e 100644 --- a/packages/tsconfig/base.json +++ b/packages/tsconfig/base.json @@ -5,7 +5,6 @@ "lib": ["dom", "dom.iterable", "esnext"], "allowJs": true, "skipLibCheck": true, - "strict": true, "noEmit": true, "esModuleInterop": true, "module": "esnext", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5eb4fdb2..26e33dd5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -38,6 +38,10 @@ importers: react-toastify: specifier: ^9.1.3 version: 9.1.3(react-dom@18.2.0)(react@18.2.0) + devDependencies: + '@types/gtag.js': + specifier: ^0.0.17 + version: 0.0.17 packages/api: dependencies: @@ -4242,6 +4246,10 @@ packages: '@types/node': 20.4.7 dev: true + /@types/gtag.js@0.0.17: + resolution: {integrity: sha512-OsSO6Yyuygr3DeeTovOY0ppLiAVHoXg4CSanO9/vzDQCV+e8pkZNE+HR2dVxGJfUbtRe2IqzC97PYuHSNUepJw==} + dev: true + /@types/html-minifier-terser@6.1.0: resolution: {integrity: sha512-oh/6byDPnL1zeNXFrDXFLyZjkr1MsBG667IM792caf1L2UPOOMf65NFzjUH/ltyfwjAGfs1rsX1eftK0jC/KIg==} dev: true diff --git a/projects/admin/next.config.js b/projects/admin/next.config.js index f8bd7b31..e00614b0 100644 --- a/projects/admin/next.config.js +++ b/projects/admin/next.config.js @@ -1,6 +1,5 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, swcMinify: true, transpilePackages: ['common', 'api'], diff --git a/projects/admin/src/PageContainer/CreatePage/index.tsx b/projects/admin/src/PageContainer/CreatePage/index.tsx index 1a7897af..b7a80738 100644 --- a/projects/admin/src/PageContainer/CreatePage/index.tsx +++ b/projects/admin/src/PageContainer/CreatePage/index.tsx @@ -16,15 +16,20 @@ import { toast } from 'react-toastify'; const CreatePage = () => { const [titleValue, setTitleValue] = useState(''); const [detailValue, setDetailValue] = useState(''); - const [time, setTime] = useState(0); + const [minute, setMinute] = useState(0); + const [second, setSecond] = useState(0); const { push } = useRouter(); const { mutate, isSuccess } = usePostMission(); const handleSubmit = () => { - if (titleValue && detailValue && time) - mutate({ title: titleValue, content: detailValue, timeLimit: time }); + if (titleValue && detailValue && minute + second !== 0) + mutate({ + title: titleValue, + content: detailValue, + timeLimit: minute * 60 + second, + }); else toast.error('작성되지 않은 빈칸이 있습니다.'); }; @@ -37,7 +42,12 @@ const CreatePage = () => {
- + [제목] diff --git a/projects/admin/src/PageContainer/ScoringPage/index.tsx b/projects/admin/src/PageContainer/ScoringPage/index.tsx index 7d49e8da..96d2dd2e 100644 --- a/projects/admin/src/PageContainer/ScoringPage/index.tsx +++ b/projects/admin/src/PageContainer/ScoringPage/index.tsx @@ -1,14 +1,23 @@ 'use client'; +import { useGetScoringList } from 'api/admin'; import * as S from './style'; import { MissionCarousel } from 'admin/components'; -const ScoringPage = () => ( - - 채점하기 - - -); +const ScoringPage = () => { + const { data } = useGetScoringList(); + + return ( + + 채점하기 + {data?.length > 0 ? ( + + ) : ( +

채점할 문제가 없습니다...

+ )} +
+ ); +}; export default ScoringPage; diff --git a/projects/admin/src/components/Timer/index.tsx b/projects/admin/src/components/Timer/index.tsx index 7a945919..1ccdbe44 100644 --- a/projects/admin/src/components/Timer/index.tsx +++ b/projects/admin/src/components/Timer/index.tsx @@ -1,39 +1,41 @@ 'use client'; import * as S from './style'; -import { Dispatch, SetStateAction, useState } from 'react'; +import { Dispatch, SetStateAction } from 'react'; interface TimerProps { - time: number; - setTime: Dispatch>; + minute: number; + setMinute: Dispatch>; + second: number; + setSecond: Dispatch>; } -const Timer: React.FC = ({ time, setTime }) => { - const [minute, setMinute] = useState(0); - const [second, setSecond] = useState(0); - - const onMinuteChange = (e: React.ChangeEvent) => { - const newMinute = parseInt(e.target.value) * 60; - setMinute(newMinute); - setTime(newMinute + second); - }; - - const onSecondChange = (e: React.ChangeEvent) => { - const newSecond = parseInt(e.target.value); - setSecond(newSecond); - setTime(minute + newSecond); - }; - - return ( - - - - : - - - * 문제는 12:30 ~ 19:30분까지 풀 수 있습니다. - - ); -}; +const Timer: React.FC = ({ + minute, + setMinute, + second, + setSecond, +}) => ( + + + + e.target.value ? setMinute(parseInt(e.target.value)) : setMinute(0) + } + /> + : + + e.target.value ? setSecond(parseInt(e.target.value)) : setSecond(0) + } + /> + + * 문제는 12:30 ~ 19:30분까지 풀 수 있습니다. + +); export default Timer; diff --git a/projects/admin/tsconfig.json b/projects/admin/tsconfig.json index e70cd06d..ddad5803 100644 --- a/projects/admin/tsconfig.json +++ b/projects/admin/tsconfig.json @@ -3,11 +3,25 @@ "compilerOptions": { "jsx": "preserve", "paths": { - "common/*": ["../../packages/common/src/*"], - "admin/*": ["./src/*"], - "api/*": ["../../packages/api/*"] - } + "common/*": [ + "../../packages/common/src/*" + ], + "admin/*": [ + "./src/*" + ], + "api/*": [ + "../../packages/api/*" + ] + }, + "strict": false }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], - "exclude": ["node_modules"] + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts" + ], + "exclude": [ + "node_modules" + ] } diff --git a/projects/client/next.config.js b/projects/client/next.config.js index f8bd7b31..e00614b0 100644 --- a/projects/client/next.config.js +++ b/projects/client/next.config.js @@ -1,6 +1,5 @@ /** @type {import('next').NextConfig} */ const nextConfig = { - reactStrictMode: true, swcMinify: true, transpilePackages: ['common', 'api'], diff --git a/projects/client/src/PageContainer/MissionDetailPage/index.tsx b/projects/client/src/PageContainer/MissionDetailPage/index.tsx index 6884aa66..3a9a33bf 100644 --- a/projects/client/src/PageContainer/MissionDetailPage/index.tsx +++ b/projects/client/src/PageContainer/MissionDetailPage/index.tsx @@ -38,6 +38,8 @@ const MissionDetailPage: React.FC = ({ missionId }) => { }; useEffect(() => { + toast.error('문제를 푸는 동안에는 다른 페이지로 이동할 수 없습니다.'); + const handleBeforeUnload = () => window.addEventListener('beforeunload', preventClose); handleBeforeUnload(); @@ -57,7 +59,8 @@ const MissionDetailPage: React.FC = ({ missionId }) => { const submitSolution = () => { mutate({ - solvation: inputValue, + solvation: + inputValue.length > 0 ? inputValue : '시간초과로 제출된 사용자입니다.', }); }; diff --git a/projects/client/src/app/layout.tsx b/projects/client/src/app/layout.tsx index da5d4524..c9490799 100644 --- a/projects/client/src/app/layout.tsx +++ b/projects/client/src/app/layout.tsx @@ -5,6 +5,10 @@ import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { GlobalStyle } from 'common'; import { Header } from 'common'; +import Script from 'next/script'; + +import * as gtag from 'client/libs/gtag'; + import Providers from './providers'; export const metadata: Metadata = { @@ -35,7 +39,7 @@ export const metadata: Metadata = { openGraph: { title: 'StackKnowledge', description: '학습 장려 게임화 플랫폼입니다.', - url: 'https://stack-knowledge-client.vercel.app/', + url: 'https://stackknowledge.vercel.app/', siteName: 'StackKnowledge', images: [ { @@ -58,6 +62,27 @@ export default function RootLayout({ }) { return ( + + {/* Global Site Tag (gtag.js) - Google Analytics */} +