-
Notifications
You must be signed in to change notification settings - Fork 0
홈페이지 리팩토링 #254
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
홈페이지 리팩토링 #254
Changes from all commits
17aa588
32432d3
0514900
fecd256
bfcca8c
2eac85b
2c5e6b0
be3e82b
aa966b6
5f3405d
9fe28da
e24be11
bd6aeff
7164565
b7b73b5
0e300e6
90dbe26
bf4d7dc
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| import { test as setup } from "@playwright/test" | ||
|
|
||
| setup("attach browser logs", async ({ page }) => { | ||
| page.on("console", (msg) => { | ||
| console.log(`[browser:${msg.type()}] ${msg.text()}`) | ||
| }) | ||
|
|
||
| page.on("pageerror", (err) => { | ||
| console.log(`[pageerror] ${err.message}\n${err.stack ?? ""}`) | ||
| }) | ||
|
|
||
| page.on("requestfailed", (req) => { | ||
| console.log( | ||
| `[requestfailed] ${req.method()} ${req.url()} -> ${req.failure()?.errorText ?? "unknown"}`, | ||
| ) | ||
| }) | ||
|
|
||
| page.on("response", (res) => { | ||
| if (res.status() >= 400) { | ||
| console.log( | ||
| `[response:${res.status()}] ${res.request().method()} ${res.url()}`, | ||
| ) | ||
| } | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,29 @@ | ||
| import { Suspense } from "react" | ||
|
|
||
| import { getDefaultGame } from "@/entities/game/api/getDefaultGame" | ||
|
|
||
| import { GameSectionClient } from "./GameSectionClient" | ||
| import { GameSectionHeader } from "./GameSectionHeader" | ||
| import { GameSectionSkeleton } from "./GameSectionSkeleton" | ||
|
|
||
| interface GameSectionProps { | ||
| className?: string | ||
| } | ||
|
|
||
| export const GameSection = async ({ className = "" }: GameSectionProps) => { | ||
| const response = await getDefaultGame({ cache: "force-cache" }) | ||
| const games = response.data.games | ||
|
|
||
| return ( | ||
| <section | ||
| className={`flex w-full flex-col items-center justify-center self-stretch p-0 ${className}`} | ||
| > | ||
| <div className="flex min-w-[952px] flex-col gap-28"> | ||
| <GameSectionHeader /> | ||
| <Suspense fallback={<GameSectionSkeleton />}> | ||
| <GameSectionClient games={games} /> | ||
| </Suspense> | ||
| </div> | ||
| </section> | ||
| ) | ||
| } | ||
|
Comment on lines
+1
to
+29
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일명 규칙 위반 코딩 가이드라인에 따르면 모든 파일명은 camelCase를 사용해야 하지만, 이 파일은 PascalCase( 코딩 가이드라인 기준 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,45 @@ | ||
| "use client" | ||
|
|
||
| import type { GameListItem } from "@/entities/game" | ||
| import { useGamePreview } from "@/entities/game/hooks/useGamePreview" | ||
| import * as GameCard from "@/entities/game/ui/GameCard/gameCard" | ||
|
|
||
| interface GameSectionClientProps { | ||
| games: GameListItem[] | ||
| } | ||
|
|
||
| export const GameSectionClient = ({ games }: GameSectionClientProps) => { | ||
| const { openPreview } = useGamePreview() | ||
|
|
||
| return ( | ||
| <div | ||
| className="flex items-center justify-between" | ||
| data-testid="game-section-cards" | ||
| > | ||
| {games?.map((game) => ( | ||
| <GameCard.Root | ||
| key={game.gameId} | ||
| className="w-[178px]" | ||
| title={game.gameTitle} | ||
| onClick={() => openPreview(game)} | ||
| aria-label="게임 카드" | ||
| tabIndex={0} | ||
| > | ||
| <GameCard.Image | ||
| src={game.gameThumbnailUrl ?? "/checker.svg"} | ||
| alt={game.gameTitle} | ||
| sizes="178px" | ||
| placeholder="blur" | ||
| blurDataURL="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNzI3IiBoZWlnaHQ9IjQ1OSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iMTAwJSIgaGVpZ2h0PSIxMDAlIiBmaWxsPSIjZTVlN2ViIi8+PC9zdmc+" | ||
| > | ||
| <GameCard.Badge>{game.questionCount}문제</GameCard.Badge> | ||
| {game.isShared && ( | ||
| <GameCard.Badge variant="bottom-left">공유</GameCard.Badge> | ||
| )} | ||
| </GameCard.Image> | ||
| <GameCard.Description>{game.gameTitle}</GameCard.Description> | ||
| </GameCard.Root> | ||
| ))} | ||
| </div> | ||
| ) | ||
| } | ||
|
Comment on lines
+1
to
+45
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일명 규칙 위반 코딩 가이드라인에 따르면 모든 파일명은 camelCase를 사용해야 하지만, 이 파일은 PascalCase( 코딩 가이드라인 기준 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| import { PrimaryBoxButton } from "@ject-5-fe/design/components/button" | ||
| import Link from "next/link" | ||
|
|
||
| export const GameSectionHeader = () => { | ||
| return ( | ||
| <div className="flex w-full items-center justify-between"> | ||
| <h2 className="typography-heading-lg-semibold text-text-interactive-secondary"> | ||
| 어떤 게임으로 시작해 볼까요? | ||
| </h2> | ||
| <PrimaryBoxButton size="md" _style="outline" asChild> | ||
| <Link href="/games">게임 더 보기</Link> | ||
| </PrimaryBoxButton> | ||
| </div> | ||
| ) | ||
| } | ||
|
Comment on lines
+1
to
+15
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일명 규칙 위반 코딩 가이드라인에 따르면 모든 파일명은 camelCase를 사용해야 하지만, 이 파일은 PascalCase( 컴포넌트 구현은 적절합니다. 코딩 가이드라인 기준 🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| import { GameCardSkeleton } from "@/entities/game/ui/GameCard/GameCardSkeleton" | ||
|
|
||
| export const GameSectionSkeleton = () => { | ||
| return ( | ||
| <div className="flex items-center justify-between"> | ||
| {Array.from({ length: 4 }).map((_, index) => ( | ||
| <GameCardSkeleton key={index} /> | ||
| ))} | ||
| </div> | ||
| ) | ||
| } | ||
|
Comment on lines
+1
to
+11
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일 이름 규칙 위반 코딩 가이드라인에 따르면 모든 파일 이름은 camelCase를 사용해야 하지만, 이 파일은 PascalCase를 사용하고 있습니다.
마찬가지로 디렉토리 이름도 기준 코딩 가이드라인코딩 가이드라인 명시 사항:
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,30 +1,15 @@ | ||
| "use client" | ||
|
|
||
| import { ErrorBoundary } from "react-error-boundary" | ||
|
|
||
| import { useAuthStore } from "@/entities/auth" | ||
| import { GameSection } from "@/widgets/GameSection" | ||
| import { HeroSection } from "@/widgets/HeroSection" | ||
| import { GameSection } from "@/app/_components/GameSection/GameSection" | ||
| import { HeroSection } from "@/app/_components/HeroSection" | ||
| import { HomeNavigation } from "@/widgets/HomeNavigation" | ||
|
|
||
| import ErrorPage from "./error" | ||
|
|
||
| function HomeContent() { | ||
| const { isAuthenticated } = useAuthStore() | ||
|
|
||
| export default function Home() { | ||
| return ( | ||
| <main className="flex min-h-screen w-full flex-col items-start gap-112 bg-background-primary"> | ||
| <HomeNavigation isLoggedIn={isAuthenticated} /> | ||
| <HomeNavigation /> | ||
| <HeroSection /> | ||
| <GameSection /> | ||
| </main> | ||
| ) | ||
| } | ||
|
|
||
| export default function Home() { | ||
| return ( | ||
| <ErrorBoundary FallbackComponent={ErrorPage}> | ||
| <HomeContent /> | ||
| </ErrorBoundary> | ||
| ) | ||
| } | ||
| export const dynamic = "force-dynamic" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Check page.tsx and GameSection for dynamic data fetching requirements
echo "=== Checking page.tsx content ==="
if [ -f "service/app/src/app/page.tsx" ]; then
cat -n "service/app/src/app/page.tsx"
else
echo "File not found"
fi
echo ""
echo "=== Checking GameSection directory ==="
find service/app/src/app/_components -name "*GameSection*" -type f 2>/dev/null | head -20
echo ""
echo "=== Checking GameSection implementation ==="
find service/app/src/app/_components -path "*GameSection*" -type f -name "*.tsx" -o -name "*.ts" 2>/dev/null | head -5Repository: JECT-Study/JECT-5-FE Length of output: 1268 🏁 Script executed: #!/bin/bash
# Check GameSection components for dynamic data patterns
echo "=== GameSection.tsx ==="
cat -n "service/app/src/app/_components/GameSection/GameSection.tsx"
echo ""
echo "=== GameSectionClient.tsx ==="
wc -l "service/app/src/app/_components/GameSection/GameSectionClient.tsx"
head -60 "service/app/src/app/_components/GameSection/GameSectionClient.tsx"Repository: JECT-Study/JECT-5-FE Length of output: 2760
페이지에 제안:
🤖 Prompt for AI Agents |
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| export const GameCardSkeleton = () => { | ||
| return ( | ||
| <div className="flex w-[178px] flex-col items-start gap-[14px]"> | ||
| <div className="size-[178px] animate-pulse rounded-[10px] bg-gray-200" /> | ||
| <div className="h-[46px] w-[178px] animate-pulse rounded bg-gray-200" /> | ||
| </div> | ||
| ) | ||
| } | ||
|
Comment on lines
+1
to
+8
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 파일명 규칙 위반 코딩 가이드라인에 따르면 모든 파일명은 camelCase를 사용해야 하지만, 이 파일은 PascalCase( 컴포넌트 구현 자체는 적절합니다. 스켈레톤 UI가 명확하게 정의되어 있고 Tailwind 유틸리티가 올바르게 사용되었습니다. 코딩 가이드라인 기준 🤖 Prompt for AI Agents |
||
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Suspense 경계가 효과가 없습니다
Line 14에서
getDefaultGame을 호출하고 await한 후, 이미 해결된 데이터를 Line 24에서GameSectionClient에 props로 전달하고 있습니다.GameSectionClient는 클라이언트 컴포넌트이며 이미 사용 가능한 데이터를 렌더링하기만 하므로 suspend되지 않습니다.Suspense는 비동기 작업이 진행 중일 때만 fallback을 표시합니다. 현재 구조에서는 데이터가 이미 fetching 완료된 후이므로
GameSectionSkeleton이 표시되지 않습니다.다음 중 하나를 선택하세요:
🔎 제안된 수정 (옵션 1: 데이터 fetching 분리)
별도의 서버 컴포넌트를 생성:
그런 다음 GameSection에서 사용:
🤖 Prompt for AI Agents