From 81f24c65fbf1c3dde6ef64882efe1611617967f8 Mon Sep 17 00:00:00 2001 From: six-standard Date: Tue, 24 Dec 2024 16:08:54 +0900 Subject: [PATCH 01/51] =?UTF-8?q?feature:=20=EA=B7=B8=EB=9E=98=ED=94=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth-required/main/Section/Graph.tsx | 104 ++++++++++++++++++ .../main/{Section.tsx => Section/index.tsx} | 20 ++-- 2 files changed, 113 insertions(+), 11 deletions(-) create mode 100644 src/components/auth-required/main/Section/Graph.tsx rename src/components/auth-required/main/{Section.tsx => Section/index.tsx} (77%) diff --git a/src/components/auth-required/main/Section/Graph.tsx b/src/components/auth-required/main/Section/Graph.tsx new file mode 100644 index 0000000..c17870a --- /dev/null +++ b/src/components/auth-required/main/Section/Graph.tsx @@ -0,0 +1,104 @@ +'use client'; + +import { Line } from 'react-chartjs-2'; + +import { + Chart as ChartJS, + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +} from 'chart.js'; +import { COLORS, SCREENS } from '@/constants'; +import { Button, Dropdown, Input } from '@/components'; +import { useResponsive } from '@/hooks'; + +ChartJS.register( + CategoryScale, + LinearScale, + PointElement, + LineElement, + Title, + Tooltip, + Legend, +); + +const data = { + labels: [ + '2024-12-11', + '2024-12-12', + '2024-12-13', + '2024-12-14', + '2024-12-15', + '2024-12-16', + ], + datasets: [ + { + label: 'Views', + data: [1032, 941, 513, 752, 469, 310], + fill: true, + backgroundColor: COLORS.text.main, + borderColor: COLORS.primary.main, + }, + ], +}; + +export const Graph = () => { + const width = useResponsive(); + const isMBI = width < SCREENS.MBI; + + return ( +
+
+
+ + ~ + + +
+ + +
+ +
+ ); +}; diff --git a/src/components/auth-required/main/Section.tsx b/src/components/auth-required/main/Section/index.tsx similarity index 77% rename from src/components/auth-required/main/Section.tsx rename to src/components/auth-required/main/Section/index.tsx index c8b461f..5d3dfe0 100644 --- a/src/components/auth-required/main/Section.tsx +++ b/src/components/auth-required/main/Section/index.tsx @@ -3,25 +3,24 @@ import { useState } from 'react'; import { Icon } from '@/components'; import { parseNumber } from '@/utils/numberUtil'; +import { COLORS } from '@/constants'; +import { Graph } from './Graph'; interface IProp { views: number; - before_views: number; date: string; title: string; - total_views: number; likes: number; id: string; } export const Section = (p: IProp) => { const [open, setOpen] = useState(false); - const doesViewIncreased = p.views - p.before_views > 0; return ( -
+
setOpen((prev) => !prev)} > {
{p.date.split('T')[0]}
- - {parseNumber(p.total_views)} + + {parseNumber(p.views * 100)} - + {parseNumber(p.views)} - / {parseNumber(p.likes)} @@ -61,6 +58,7 @@ export const Section = (p: IProp) => { className="MBI:hidden absolute bottom-3" />
+ {open && }
); }; From 809745d4a3f9b69e22947bc422af5d8d23ebb5e9 Mon Sep 17 00:00:00 2001 From: six-standard Date: Tue, 24 Dec 2024 16:09:38 +0900 Subject: [PATCH 02/51] =?UTF-8?q?modify:=20500=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=ED=95=B4=EA=B2=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__test__/login.test.tsx | 2 +- src/components/auth-required/main/Summary/BarContent.tsx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/__test__/login.test.tsx b/src/__test__/login.test.tsx index 1bc2334..d17a0ab 100644 --- a/src/__test__/login.test.tsx +++ b/src/__test__/login.test.tsx @@ -3,7 +3,7 @@ import { act, screen } from '@testing-library/react'; import { ToastContainer } from 'react-toastify'; import { useRouter } from 'next/navigation'; import fetchMock from 'jest-fetch-mock'; -import { renderWithQueryClient } from '@/utils'; +import { renderWithQueryClient } from '@/utils/componentUtil'; import { TimeoutError } from '@/errors'; import { Login } from '@/app'; diff --git a/src/components/auth-required/main/Summary/BarContent.tsx b/src/components/auth-required/main/Summary/BarContent.tsx index 13ed03c..4d0679d 100644 --- a/src/components/auth-required/main/Summary/BarContent.tsx +++ b/src/components/auth-required/main/Summary/BarContent.tsx @@ -1,4 +1,4 @@ -import { parseNumber } from '@/utils'; +import { parseNumber } from '@/utils/numberUtil'; interface IProp { title: string; From 672c13d7e14cbeec71b5d2fc032756555b68d125 Mon Sep 17 00:00:00 2001 From: six-standard Date: Tue, 24 Dec 2024 16:11:07 +0900 Subject: [PATCH 03/51] =?UTF-8?q?feature:=20=EC=97=85=EB=8D=B0=EC=9D=B4?= =?UTF-8?q?=ED=8A=B8=20=EC=8B=9C=EA=B0=84=20=ED=91=9C=EC=8B=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(auth-required)/main/page.tsx | 34 +++++++++++++++++++-------- src/app/globals.css | 16 +++++++++++++ src/components/common/Dropdown.tsx | 16 +++++++++++++ 3 files changed, 56 insertions(+), 10 deletions(-) create mode 100644 src/components/common/Dropdown.tsx diff --git a/src/app/(auth-required)/main/page.tsx b/src/app/(auth-required)/main/page.tsx index 25a7106..cb74acd 100644 --- a/src/app/(auth-required)/main/page.tsx +++ b/src/app/(auth-required)/main/page.tsx @@ -1,5 +1,5 @@ import type { Metadata } from 'next'; -import { Section, Summary } from '@/components'; +import { Button, Dropdown, Section, Summary } from '@/components'; export const metadata: Metadata = { title: '대시보드', @@ -58,15 +58,29 @@ const datas = [ export default function Page() { return (
- -
- {datas?.map((i) =>
)} + +
+
+ + 마지막 업데이트 : 2024-12-20, 20:13:34 + +
+
+ + + 마지막 업데이트 : 2024-12-20, 20:13:34 + +
+
+ + +
+
+
+ +
+ {datas?.map((i) =>
)} +
); diff --git a/src/app/globals.css b/src/app/globals.css index 65511e8..8be98c0 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -8,5 +8,21 @@ input, textarea { + color-scheme: dark; outline: none; } + +select { + appearance: none; + outline: none; +} + +*::-webkit-scrollbar { + width: 5px; + background-color: transparent; +} + +*::-webkit-scrollbar-thumb { + background-color: #4d4d4d; + border-radius: 4px; +} diff --git a/src/components/common/Dropdown.tsx b/src/components/common/Dropdown.tsx new file mode 100644 index 0000000..55ff079 --- /dev/null +++ b/src/components/common/Dropdown.tsx @@ -0,0 +1,16 @@ +interface IProp { + options: string[]; +} + +export const Dropdown = ({ options }: IProp) => { + return ( +
+ +
+
+ ); +}; From fd7abc5dc2764ed3f104fed88861f8f903f4f092 Mon Sep 17 00:00:00 2001 From: six-standard Date: Tue, 24 Dec 2024 16:12:44 +0900 Subject: [PATCH 04/51] =?UTF-8?q?refactor:=20=EB=84=A4=EC=9D=B4=EB=B0=8D?= =?UTF-8?q?=20=EA=B7=9C=EC=B9=99=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../auth-required/main/Summary/index.tsx | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/src/components/auth-required/main/Summary/index.tsx b/src/components/auth-required/main/Summary/index.tsx index 0c4a402..58988d0 100644 --- a/src/components/auth-required/main/Summary/index.tsx +++ b/src/components/auth-required/main/Summary/index.tsx @@ -6,20 +6,12 @@ import { SidebarContent } from './SidebarContent'; import { BarContent } from './BarContent'; interface IProp { - total_views: number; views: number; - total_likes: number; likes: number; - total_posts: number; + posts: number; } -export const Summary = ({ - total_views, - total_likes, - total_posts, - views, - likes, -}: IProp) => { +export const Summary = ({ posts, views, likes }: IProp) => { const [open, setOpen] = useState(false); return ( @@ -27,16 +19,16 @@ export const Summary = ({
- +
)}
From 395598eda5886ec9bff19b16cf7c1bb2f59e8008 Mon Sep 17 00:00:00 2001 From: six-standard Date: Tue, 24 Dec 2024 16:12:54 +0900 Subject: [PATCH 05/51] =?UTF-8?q?refactor:=20=EB=94=94=EC=9E=90=EC=9D=B8?= =?UTF-8?q?=20=EB=A7=9E=EC=B6=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/components/common/Button.tsx | 4 ++-- src/components/common/Input.tsx | 4 ++-- src/components/common/index.ts | 1 + 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/src/components/common/Button.tsx b/src/components/common/Button.tsx index c3cb0dc..5c9ab34 100644 --- a/src/components/common/Button.tsx +++ b/src/components/common/Button.tsx @@ -13,12 +13,12 @@ interface IProp const FORMS = { LARGE: 'h-[55px] rounded-sm', - SMALL: 'pl-[20px] pr-[20px] w-fit h-8 rounded-[4px]', + SMALL: 'pl-[20px] pr-[20px] w-[fit-content_!important] h-8 rounded-[4px]', }; export const Button = ({ form = 'SMALL', size, children, ...rest }: IProp) => ( diff --git a/src/components/common/Input.tsx b/src/components/common/Input.tsx index 101144d..81812ac 100644 --- a/src/components/common/Input.tsx +++ b/src/components/common/Input.tsx @@ -4,7 +4,7 @@ import { forwardRef, InputHTMLAttributes, } from 'react'; -import { SIZES, sizeType } from '@/constants'; +import { SIZES, SizeType } from '@/constants'; interface IProp extends Omit< @@ -12,7 +12,7 @@ interface IProp 'size' > { form?: keyof typeof FORMS; - size: sizeType; + size: SizeType; } const FORMS = { diff --git a/src/constants/colors.ts b/src/constants/colors.constant.ts similarity index 85% rename from src/constants/colors.ts rename to src/constants/colors.constant.ts index 750af6d..5e13edb 100644 --- a/src/constants/colors.ts +++ b/src/constants/colors.constant.ts @@ -24,4 +24,4 @@ export const COLORS = { }, }; -export type colorType = Record; +export type ColorType = Record; diff --git a/src/constants/index.ts b/src/constants/index.ts index 3f3dcec..04c5282 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,3 +1,3 @@ -export * from './sizes'; -export * from './colors'; -export * from './screens'; +export * from './sizes.constant'; +export * from './colors.constant'; +export * from './screens.constant'; diff --git a/src/constants/screens.ts b/src/constants/screens.constant.ts similarity index 100% rename from src/constants/screens.ts rename to src/constants/screens.constant.ts diff --git a/src/constants/sizes.ts b/src/constants/sizes.constant.ts similarity index 68% rename from src/constants/sizes.ts rename to src/constants/sizes.constant.ts index 91902d7..0048c85 100644 --- a/src/constants/sizes.ts +++ b/src/constants/sizes.constant.ts @@ -4,4 +4,4 @@ export const SIZES = { SMALL: 'w-[100px]', }; -export type sizeType = keyof typeof SIZES; +export type SizeType = keyof typeof SIZES; diff --git a/src/errors/fetchErrors.ts b/src/errors/fetch.error.ts similarity index 89% rename from src/errors/fetchErrors.ts rename to src/errors/fetch.error.ts index 41e2212..48d42b7 100644 --- a/src/errors/fetchErrors.ts +++ b/src/errors/fetch.error.ts @@ -1,4 +1,4 @@ -import { CustomError } from './instance'; +import { CustomError } from './instance.error'; export class TimeoutError extends CustomError { constructor() { diff --git a/src/errors/index.ts b/src/errors/index.ts index 042c55c..b299e30 100644 --- a/src/errors/index.ts +++ b/src/errors/index.ts @@ -1 +1 @@ -export * from './fetchErrors'; +export * from './fetch.error'; diff --git a/src/errors/instance.ts b/src/errors/instance.error.ts similarity index 100% rename from src/errors/instance.ts rename to src/errors/instance.error.ts diff --git a/src/types/apis/index.ts b/src/types/apis/index.ts new file mode 100644 index 0000000..4df5300 --- /dev/null +++ b/src/types/apis/index.ts @@ -0,0 +1 @@ +export type * from './login.type'; diff --git a/src/types/apis/login.type.ts b/src/types/apis/login.type.ts new file mode 100644 index 0000000..5d9e24a --- /dev/null +++ b/src/types/apis/login.type.ts @@ -0,0 +1,4 @@ +export interface LoginVo { + accessToken: string; + refreshToken: string; +} diff --git a/src/types/index.ts b/src/types/index.ts new file mode 100644 index 0000000..dc946c8 --- /dev/null +++ b/src/types/index.ts @@ -0,0 +1 @@ +export type * from './apis'; diff --git a/src/utils/eventTracker.tsx b/src/utils/eventTracker.tsx index 2db1f40..7c637ba 100644 --- a/src/utils/eventTracker.tsx +++ b/src/utils/eventTracker.tsx @@ -1,7 +1,7 @@ 'use client'; import { useEffect, useRef } from 'react'; -import { instance } from '@/api'; +import { instance } from '@/apis'; type VisitDataType = { loadDate?: string; From 558614d14a5860fb98bb3d057fe8ed2d46a3d2e6 Mon Sep 17 00:00:00 2001 From: six-standard Date: Wed, 25 Dec 2024 00:14:24 +0900 Subject: [PATCH 09/51] =?UTF-8?q?refactor:=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20=ED=8C=8C=EC=9D=BC=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- public/favicon.jpg | Bin 18520 -> 0 bytes src/app/(login)/index.ts | 1 - src/app/index.ts | 1 - 3 files changed, 2 deletions(-) delete mode 100644 public/favicon.jpg delete mode 100644 src/app/(login)/index.ts delete mode 100644 src/app/index.ts diff --git a/public/favicon.jpg b/public/favicon.jpg deleted file mode 100644 index c50b028052401064127b16863a1a5b08b314f760..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18520 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⪙Hh2xZuv@z^+TMBD-Gw5MmlE}Cmj6N! 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 DU?gFB diff --git a/src/app/(login)/index.ts b/src/app/(login)/index.ts deleted file mode 100644 index 8d7561b..0000000 --- a/src/app/(login)/index.ts +++ /dev/null @@ -1 +0,0 @@ -export { default as Login } from './page'; diff --git a/src/app/index.ts b/src/app/index.ts deleted file mode 100644 index c632f97..0000000 --- a/src/app/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from './(login)'; From 3fb2282aa56fbdca05b53e43fd30b924c891ff57 Mon Sep 17 00:00:00 2001 From: six-standard Date: Wed, 25 Dec 2024 00:15:20 +0900 Subject: [PATCH 10/51] =?UTF-8?q?refactor:=20=EA=B2=BD=EB=A1=9C=20?= =?UTF-8?q?=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/__test__/login.test.tsx | 2 +- src/apis/index.ts | 77 ----------------------------------- src/apis/instance.request.ts | 78 ++++++++++++++++++++++++++++++++++++ src/apis/login.request.ts | 2 +- src/app/layout.tsx | 17 +++----- 5 files changed, 85 insertions(+), 91 deletions(-) create mode 100644 src/apis/instance.request.ts diff --git a/src/__test__/login.test.tsx b/src/__test__/login.test.tsx index d17a0ab..d2a2b6f 100644 --- a/src/__test__/login.test.tsx +++ b/src/__test__/login.test.tsx @@ -5,7 +5,7 @@ import { useRouter } from 'next/navigation'; import fetchMock from 'jest-fetch-mock'; import { renderWithQueryClient } from '@/utils/componentUtil'; import { TimeoutError } from '@/errors'; -import { Login } from '@/app'; +import { default as Login } from '@/app/(login)/page'; jest.mock('next/navigation', () => ({ useRouter: jest.fn(), diff --git a/src/apis/index.ts b/src/apis/index.ts index 80b8840..b69dd27 100644 --- a/src/apis/index.ts +++ b/src/apis/index.ts @@ -1,78 +1 @@ -import returnFetch, { FetchArgs } from 'return-fetch'; -import * as sentry from '@sentry/nextjs'; -import { ServerNotRespondingError } from '@/errors'; export * from './login.request'; - -const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; -const ABORT_MS = Number(process.env.NEXT_PUBLIC_ABORT_MS); - -if (Number.isNaN(ABORT_MS)) { - throw new Error('ABORT_MS가 ENV에서 설정되지 않았습니다'); -} - -if (!BASE_URL) { - throw new Error('BASE_URL이 ENV에서 설정되지 않았습니다.'); -} - -type ErrorObject = Record; - -const abortPolyfill = (ms: number) => { - const controller = new AbortController(); - setTimeout(() => controller.abort(), ms); - return controller.signal; -}; - -const fetch = returnFetch({ - baseUrl: BASE_URL, - headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, - interceptors: { - response: async (response) => { - if (!response.ok) { - throw response; - } - return { - ...response, - body: response?.text ? JSON.parse(await response?.text()) : {}, - }; - }, - }, -}); - -export const instance = async ( - input: URL | RequestInfo, - init?: Omit, 'body'> & { body: object }, - error?: ErrorObject, -) => { - try { - const data = await fetch('/api' + input, { - ...init, - body: init?.body ? JSON.stringify(init.body) : undefined, - signal: AbortSignal.timeout - ? AbortSignal.timeout(ABORT_MS) - : abortPolyfill(ABORT_MS), - }); - - return data as Awaited>; - } catch (err: any) { - const context = err as Response; - sentry.setContext('Request', { - path: context.url, - status: context.status, - }); - if ((err as Error).name === 'TimeoutError') { - sentry.captureException(new ServerNotRespondingError()); - throw new ServerNotRespondingError(); - } else { - if (!error?.[`${(err as Response).status}`]) { - const serverError = new Error( - `서버에서 예기치 않은 오류가 발생했습니다. (${err.name})`, - ); - sentry.captureException(serverError); - throw serverError; - } - - sentry.captureException(error[`${(err as Response).status}`]); - throw error[`${(err as Response).status}`]; - } - } -}; diff --git a/src/apis/instance.request.ts b/src/apis/instance.request.ts new file mode 100644 index 0000000..80b8840 --- /dev/null +++ b/src/apis/instance.request.ts @@ -0,0 +1,78 @@ +import returnFetch, { FetchArgs } from 'return-fetch'; +import * as sentry from '@sentry/nextjs'; +import { ServerNotRespondingError } from '@/errors'; +export * from './login.request'; + +const BASE_URL = process.env.NEXT_PUBLIC_BASE_URL; +const ABORT_MS = Number(process.env.NEXT_PUBLIC_ABORT_MS); + +if (Number.isNaN(ABORT_MS)) { + throw new Error('ABORT_MS가 ENV에서 설정되지 않았습니다'); +} + +if (!BASE_URL) { + throw new Error('BASE_URL이 ENV에서 설정되지 않았습니다.'); +} + +type ErrorObject = Record; + +const abortPolyfill = (ms: number) => { + const controller = new AbortController(); + setTimeout(() => controller.abort(), ms); + return controller.signal; +}; + +const fetch = returnFetch({ + baseUrl: BASE_URL, + headers: { Accept: 'application/json', 'Content-Type': 'application/json' }, + interceptors: { + response: async (response) => { + if (!response.ok) { + throw response; + } + return { + ...response, + body: response?.text ? JSON.parse(await response?.text()) : {}, + }; + }, + }, +}); + +export const instance = async ( + input: URL | RequestInfo, + init?: Omit, 'body'> & { body: object }, + error?: ErrorObject, +) => { + try { + const data = await fetch('/api' + input, { + ...init, + body: init?.body ? JSON.stringify(init.body) : undefined, + signal: AbortSignal.timeout + ? AbortSignal.timeout(ABORT_MS) + : abortPolyfill(ABORT_MS), + }); + + return data as Awaited>; + } catch (err: any) { + const context = err as Response; + sentry.setContext('Request', { + path: context.url, + status: context.status, + }); + if ((err as Error).name === 'TimeoutError') { + sentry.captureException(new ServerNotRespondingError()); + throw new ServerNotRespondingError(); + } else { + if (!error?.[`${(err as Response).status}`]) { + const serverError = new Error( + `서버에서 예기치 않은 오류가 발생했습니다. (${err.name})`, + ); + sentry.captureException(serverError); + throw serverError; + } + + sentry.captureException(error[`${(err as Response).status}`]); + throw error[`${(err as Response).status}`]; + } + } +}; diff --git a/src/apis/login.request.ts b/src/apis/login.request.ts index a70ad80..f037c0f 100644 --- a/src/apis/login.request.ts +++ b/src/apis/login.request.ts @@ -1,6 +1,6 @@ import { NotFoundError } from '@/errors'; import { LoginVo } from '@/types'; -import { instance } from '.'; +import { instance } from './instance.request'; export const login = async (body: LoginVo) => await instance( diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 49ef275..4394070 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,30 +1,23 @@ -import 'react-toastify/dist/ReactToastify.css'; -import './globals.css'; - import { Noto_Sans_KR } from 'next/font/google'; +import 'react-toastify/dist/ReactToastify.css'; import { ToastContainer } from 'react-toastify'; import * as sentry from '@sentry/nextjs'; import type { Metadata } from 'next'; import { ReactNode } from 'react'; +import './globals.css'; import { QueryProvider } from '@/components'; export const metadata: Metadata = { title: 'Velog Dashboard', description: 'Velog 통계를 확인할 수 있는 Velog Dashboard', - icons: { - icon: '/favicon.png', - }, + icons: { icon: '/favicon.png' }, }; -const NotoSansKr = Noto_Sans_KR({ - subsets: ['latin'], -}); +const NotoSansKr = Noto_Sans_KR({ subsets: ['latin'] }); export default function RootLayout({ children, -}: Readonly<{ - children: ReactNode; -}>) { +}: Readonly<{ children: ReactNode }>) { return ( From 1a4e287a47012a814f98b68534f3c4bc02de9807 Mon Sep 17 00:00:00 2001 From: six-standard Date: Wed, 25 Dec 2024 00:15:31 +0900 Subject: [PATCH 11/51] =?UTF-8?q?refactor:=20=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=A3=BC=EC=84=9D=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/global-error.tsx | 4 ---- 1 file changed, 4 deletions(-) diff --git a/src/app/global-error.tsx b/src/app/global-error.tsx index 453ec99..4b6ac69 100644 --- a/src/app/global-error.tsx +++ b/src/app/global-error.tsx @@ -16,10 +16,6 @@ export default function GlobalError({ return ( - {/* `NextError` is the default Next.js error page component. Its type - definition requires a `statusCode` prop. However, since the App Router - does not expose status codes for errors, we simply pass 0 to render a - generic error message. */} From 69f199a602114767213f9c64ff0eed445df9e14b Mon Sep 17 00:00:00 2001 From: six-standard Date: Wed, 25 Dec 2024 00:16:19 +0900 Subject: [PATCH 12/51] =?UTF-8?q?feature:=20=EB=A1=9C=EA=B7=B8=EC=9D=B8=20?= =?UTF-8?q?=ED=8E=98=EC=9D=B4=EC=A7=80=20=EB=B0=98=EC=9D=91=ED=98=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/app/(login)/Content.tsx | 13 ++++++++----- src/constants/sizes.constant.ts | 1 + 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/src/app/(login)/Content.tsx b/src/app/(login)/Content.tsx index f3fbb49..55faca8 100644 --- a/src/app/(login)/Content.tsx +++ b/src/app/(login)/Content.tsx @@ -23,16 +23,16 @@ export const Content = () => { }); return ( -
+
mutate(data))} - className="w-fit h-[480px] flex overflow-hidden bg-bg-sub rounded-[4px] shadow-[0_4px_16px_0_rgba(0,0,0,.04)]" + className="w-fit h-[480px] flex overflow-hidden bg-bg-sub rounded-[4px] shadow-[0_4px_16px_0_rgba(0,0,0,.04)] max-MBI:bg-bg-main max-MBI:h-fit max-MBI:w-full" > -
+
로고 이미지
-
-

+
+

Velog Dashboard

{ size="LARGE" type="password" placeholder="Access Token을 입력하세요" + className="max-MBI:w-[100%_!important]" {...register('accessToken', { required: true })} /> { size="LARGE" type="password" placeholder="Refresh Token을 입력하세요" + className="max-MBI:w-[100%_!important]" {...register('refreshToken', { required: true })} /> - + 마지막 업데이트 : 2024-12-20, 20:13:34
diff --git a/src/app/(login)/Content.tsx b/src/app/(login)/Content.tsx index 55faca8..24e6c57 100644 --- a/src/app/(login)/Content.tsx +++ b/src/app/(login)/Content.tsx @@ -26,13 +26,13 @@ export const Content = () => {
mutate(data))} - className="w-fit h-[480px] flex overflow-hidden bg-bg-sub rounded-[4px] shadow-[0_4px_16px_0_rgba(0,0,0,.04)] max-MBI:bg-bg-main max-MBI:h-fit max-MBI:w-full" + className="w-fit h-[480px] flex overflow-hidden bg-BG-SUB rounded-[4px] shadow-[0_4px_16px_0_rgba(0,0,0,.04)] max-MBI:bg-BG-MAIN max-MBI:h-fit max-MBI:w-full" > -
+
로고 이미지
-

+

Velog Dashboard

) { return ( - + diff --git a/src/components/auth-required/Header.tsx b/src/components/auth-required/Header.tsx index 1238537..53cd5a6 100644 --- a/src/components/auth-required/Header.tsx +++ b/src/components/auth-required/Header.tsx @@ -6,9 +6,9 @@ import { usePathname } from 'next/navigation'; import { COLORS, SCREENS } from '@/constants'; import { useResponsive } from '@/hooks'; -import { Icon, nameType } from '@/components'; +import { Icon, NameType } from '@/components'; -const layouts: Array<{ icon: nameType; title: string; path: string }> = [ +const layouts: Array<{ icon: NameType; title: string; path: string }> = [ { icon: 'Analytics', title: '내 통계', path: '/main' }, { icon: 'LeaderBoards', title: '리더보드', path: '/leaderboards' }, { icon: 'Compare', title: '통계 비교', path: '/compare' }, @@ -22,7 +22,7 @@ export const Header = () => { const width = useResponsive(); const path = usePathname(); const textStyle = (currentPath: string) => - `${currentPath === path ? 'text-text-main' : 'text-text-alt'} text-[20px] shrink-0 transition-all duration-300 max-TBL:text-[18px] max-MBI:hidden `; + `${currentPath === path ? 'text-TEXT-MAIN' : 'text-TEXT-ALT'} text-[20px] shrink-0 transition-all duration-300 max-TBL:text-[18px] max-MBI:hidden `; return (