diff --git a/.eslintrc.json b/.eslintrc.json index 5131d7f3..3b470e2d 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -29,9 +29,11 @@ "no-unused-vars": "off", "import/no-duplicates": "error", "unused-imports/no-unused-imports": "error", - "@typescript-eslint/no-explicit-any": "warn", "@typescript-eslint/no-unused-vars": "warn", "simple-import-sort/exports": "warn", + "react/display-name": "off", + "@next/next/no-img-element": "off", + "@typescript-eslint/no-explicit-any": "warn", "simple-import-sort/imports": [ "warn", { diff --git a/.gitignore b/.gitignore index 69a2cbc6..a3062fe6 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ # dependencies .env +.env.production /node_modules /.pnp .pnp.js diff --git a/generate-runtime-config.mjs b/generate-runtime-config.mjs new file mode 100755 index 00000000..a1ec225b --- /dev/null +++ b/generate-runtime-config.mjs @@ -0,0 +1,22 @@ +import fs from 'fs'; +import path from 'path'; + +// 환경 변수를 읽어들임 +const runtime = process.env.NODE_ENV === 'production' ? 'edge' : 'nodejs'; + +const configContent = `export const runtime = '${runtime}';\n`; + +const configDir = path.join(process.cwd(), 'src/config'); +const configPath = path.join(configDir, 'runtime.js'); + +// 디렉토리가 존재하지 않으면 생성 +if (!fs.existsSync(configDir)) { + fs.mkdirSync(configDir, { recursive: true }); +} + +// runtime.js 파일 생성 +fs.writeFileSync(configPath, configContent, 'utf8'); + +console.log(`Runtime configuration written to ${configPath}`); + + diff --git a/package.json b/package.json index 3027ecbb..afea9990 100644 --- a/package.json +++ b/package.json @@ -4,6 +4,7 @@ "private": true, "scripts": { "dev": "next dev", + "prebuild": "node generate-runtime-config.mjs", "build": "next build", "start": "next start", "lint": "next lint", diff --git a/src/app/layout.tsx b/src/app/layout.tsx index c2e54ee8..2f2cda2d 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -10,12 +10,15 @@ import { cn } from "@shared/utils/cn"; import "./globals.css"; import MSWProviders from "@mocks/MSWProviders"; +import { Toaster } from "@shared/components/ui/toaster"; export const metadata: Metadata = { title: "FEW", description: "매일 아침마다 경제 아티클과 문제를 보내드려요!", }; +// export const runtime = 'edge'; // TBD: 개발환경과 분리 + const pretendard = localFont({ src: [ { @@ -64,6 +67,7 @@ export default function RootLayout({ > {children} + diff --git a/src/app/workbook/[id]/page.tsx b/src/app/workbook/[id]/page.tsx index 35193112..5b6dcf95 100644 --- a/src/app/workbook/[id]/page.tsx +++ b/src/app/workbook/[id]/page.tsx @@ -1,4 +1,5 @@ "use client"; + import Image from "next/image"; import { usePathname } from "next/navigation"; @@ -11,6 +12,7 @@ import { useToast } from "@shared/components/ui/use-toast"; import CurriculumSection from "@workbook/components/CurriculumSection"; import OverviewSection from "@workbook/components/OverviewSection"; import TitleSection from "@workbook/components/TitleSection"; +import WorkbookSkeleton from "@workbook/components/WorkbookSkeleton"; import { getWorkbookQueryOptions } from "@workbook/remotes/getWorkbookQueryOptions"; import { getWorkbookId } from "@workbook/utils"; @@ -51,7 +53,7 @@ export default function WorkbookPage() { setIsClient(true); }, []); - if (isLoading) return
Loading...
; + if (isLoading) return ; if (isError) return
Error loading workbook
; return ( @@ -66,7 +68,7 @@ export default function WorkbookPage() { width={0} height={0} sizes="100vw" - style={{ width: "100%", height: "auto" }} + style={{ width: "100%", height: "338px" }} /> { +export function createQueryProviderWrapper () { const queryClient = new QueryClient(); return ({ children }: { children: ReactNode }) => ( diff --git a/src/common/components/ContentSkeleton/index.tsx b/src/common/components/ContentSkeleton/index.tsx new file mode 100644 index 00000000..cfd166c3 --- /dev/null +++ b/src/common/components/ContentSkeleton/index.tsx @@ -0,0 +1,10 @@ +import { Skeleton } from "@shared/components/ui/skeleton"; + +export default function ContentSkeleton ({ + className, + ...props + }: React.HTMLAttributes) { + return ( + + ) +} \ No newline at end of file diff --git a/src/config/runtime.js b/src/config/runtime.js new file mode 100644 index 00000000..203c5dbb --- /dev/null +++ b/src/config/runtime.js @@ -0,0 +1 @@ +export const runtime = 'nodejs'; diff --git a/src/mocks/handlers.ts b/src/mocks/handlers.ts index ae62acb5..141827ba 100644 --- a/src/mocks/handlers.ts +++ b/src/mocks/handlers.ts @@ -1,8 +1,7 @@ import { http, HttpResponse } from "msw"; import { apiRoutes } from "@shared/constants/apiRoutes"; - -import { getWorkbookId } from "@workbook/utils"; +import { _3_SECOND, delay } from "@shared/utils/delay"; import response from "./response"; @@ -31,19 +30,24 @@ export const submitAnswerHandler = http.post( return HttpResponse.json(response[apiRoutes.submitAnswer]); }, ); -export const workbookHandler = http.get(apiRoutes.workbook, ({ request, params }) => { +export const workbookHandler = http.get(apiRoutes.workbook, async ({ request, params }) => { const workbookId = params - if (!workbookId) { - return new HttpResponse(null, { status: 404 }); - } + if (!workbookId) { + return new HttpResponse(null, { status: 404 }); + } - console.log( - HttpResponse.json(response[apiRoutes.workbook]), - apiRoutes.workbook, - ); - return HttpResponse.json(response[apiRoutes.workbook]); -}); + console.log( + HttpResponse.json(response[apiRoutes.workbook]), + apiRoutes.workbook, + ); + + // 딜레이 적용 + await delay(_3_SECOND); + + return HttpResponse.json(response[apiRoutes.workbook]); + }, +); export const handlers = [ tagsHandler, diff --git a/src/problem/components/TagList/TagList.stories.tsx b/src/problem/components/TagList/TagList.stories.tsx index 2e35041c..ea2b6c98 100644 --- a/src/problem/components/TagList/TagList.stories.tsx +++ b/src/problem/components/TagList/TagList.stories.tsx @@ -1,5 +1,5 @@ import TagList from "."; -import { tagsHandler } from "@mocks/worker"; +import { tagsHandler } from "@mocks/handlers"; import { Meta, StoryObj } from "@storybook/react"; const meta = { diff --git a/src/shared/components/ui/skeleton.tsx b/src/shared/components/ui/skeleton.tsx new file mode 100644 index 00000000..2f6eeacb --- /dev/null +++ b/src/shared/components/ui/skeleton.tsx @@ -0,0 +1,15 @@ +import { cn } from "@shared/utils/cn" + +function Skeleton({ + className, + ...props +}: React.HTMLAttributes) { + return ( +
+ ) +} + +export { Skeleton } diff --git a/src/shared/utils/delay.ts b/src/shared/utils/delay.ts new file mode 100644 index 00000000..3bad933c --- /dev/null +++ b/src/shared/utils/delay.ts @@ -0,0 +1,5 @@ +export const _1_SECOND = 1_000; +export const _3_SECOND = 3_000; + + +export const delay = (second = _3_SECOND) => new Promise((resolve) => setTimeout(resolve, second)); diff --git a/src/workbook/components/CurriculumSection/index.tsx b/src/workbook/components/CurriculumSection/index.tsx index ff7f7913..7635731b 100644 --- a/src/workbook/components/CurriculumSection/index.tsx +++ b/src/workbook/components/CurriculumSection/index.tsx @@ -1,4 +1,5 @@ import { ICurriculumItem } from "@workbook/types"; + import CurriculumItem from "../CurriculumItem"; interface CurriculumSectionProps { diff --git a/src/workbook/components/TitleSection/index.tsx b/src/workbook/components/TitleSection/index.tsx index 73650c17..b00eea7c 100644 --- a/src/workbook/components/TitleSection/index.tsx +++ b/src/workbook/components/TitleSection/index.tsx @@ -1,7 +1,8 @@ +import React from "react"; + import { Writer } from "@workbook/types"; import ShareIcon from "public/assets/icon36/share_36.svg"; -import React from "react"; interface TitleSectionProps { category: string; diff --git a/src/workbook/components/WorkbookSkeleton/index.tsx b/src/workbook/components/WorkbookSkeleton/index.tsx new file mode 100644 index 00000000..d7932411 --- /dev/null +++ b/src/workbook/components/WorkbookSkeleton/index.tsx @@ -0,0 +1,29 @@ +import { Skeleton } from "@shared/components/ui/skeleton"; + +import ContentSkeleton from "@common/components/ContentSkeleton"; + +export default function WorkbookSkeleton() { + const skeletonItems = new Array(6).fill(null); + + return ( +
+ + + {/* Content Skeleton */} +
+ + + {skeletonItems.map((_, index) => ( + + ))} +
+
+ ); +} diff --git a/src/workbook/remotes/getWorkbookQueryOptions.ts b/src/workbook/remotes/getWorkbookQueryOptions.ts index 9803ac4d..7a8e715d 100644 --- a/src/workbook/remotes/getWorkbookQueryOptions.ts +++ b/src/workbook/remotes/getWorkbookQueryOptions.ts @@ -1,4 +1,4 @@ -import { UseQueryOptions, useQuery } from "@tanstack/react-query"; +import { useQuery,UseQueryOptions } from "@tanstack/react-query"; import { ApiResponse, axiosRequest } from "@api/api-config";