-
Notifications
You must be signed in to change notification settings - Fork 0
관리자 페이지 신고탭 추가 #270
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
Merged
Merged
관리자 페이지 신고탭 추가 #270
Changes from all commits
Commits
Show all changes
37 commits
Select commit
Hold shift + click to select a range
7a8f5cb
chore(assets): remove unused files
kimnamheeee f9b8724
chore: remove unused file
kimnamheeee 38fc9a3
feat(admin): add navigation ui
kimnamheeee eff0943
feat(admin): add auth guard
kimnamheeee 2ef3e4c
feat(admin): add link / separate className
kimnamheeee f8ad64f
feat(admin): use /admin as alias
kimnamheeee 72ffc7f
feat(admin/users): add block, unblock confirm dialog
kimnamheeee ce65ea9
feat(msw): add auth handler
kimnamheeee a7f87e5
fix(figma-plugin): apply sanitation for figma.mixed
kimnamheeee b773d40
feat(admin): add admin report handling
kimnamheeee 0cfea3f
feat(admin): add basic routes
kimnamheeee ec9b7f8
Revert "fix(figma-plugin): apply sanitation for figma.mixed"
kimnamheeee d6bfd7d
refactor(gamepreview): update gamepreview component using composition
kimnamheeee 899ff75
feat(components): add pagination
kimnamheeee 5dc73d2
style(pagination): update selected style
kimnamheeee 985b716
feat(admin): add reported games table
kimnamheeee 273521a
feat(admin): add table ui to reports page
kimnamheeee 75aad17
feat(msw): add report detail handler
kimnamheeee 6422647
fix(msw): change endpoint
kimnamheeee 65cdcd9
feat(admin): open preview on row click
kimnamheeee ab4b5e3
feat(msw): add report delete handler
kimnamheeee 0721e20
feat(msw): add user block, delete game api
kimnamheeee 575f22b
feat(admin): match api call on admin game preview
kimnamheeee 234b7eb
fix(admin): fix report id type to number, fix enum type for report
kimnamheeee d1a0b8a
fix(admin): move directory
kimnamheeee 4354cb9
feat(reports): add a11y tags
kimnamheeee 02d8496
test(reports): add e2e tests for reports page
kimnamheeee c582a16
refactor(msw): change admin handler to be fixture based
kimnamheeee f60de70
feat(reportdetail): add block state and report status
kimnamheeee 89ea011
feat(reports): use react query on reportDetail api
kimnamheeee a4568f4
merge: resolve conflict with dev branch
kimnamheeee 61dc5dd
fix: remove unused export
kimnamheeee 4039928
test(reports): update test logic
kimnamheeee 00f2b8f
fix(session): update type
kimnamheeee 4e9b7c1
fix: fix typo
kimnamheeee e5ea0a6
refactor(report): use async / await
kimnamheeee 318b593
test(reports): update test selector
kimnamheeee File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,65 @@ | ||
| import { SecondaryPlainBoxButton } from "@ject-5-fe/design/components/button" | ||
| import { Navigation } from "@ject-5-fe/design/components/navigation" | ||
| import { cn } from "@ject-5-fe/design/utils/cn" | ||
| import Link from "next/link" | ||
| import { usePathname } from "next/navigation" | ||
|
|
||
| import { HomeButton } from "@/widgets/components/homeButton" | ||
|
|
||
| export const AdminNavigation = () => { | ||
| const pathname = usePathname() | ||
|
|
||
| const isActive = (href: string) => | ||
| pathname === href || pathname.startsWith(`${href}/`) | ||
|
|
||
| const getAriaCurrent = (active: boolean) => (active ? "page" : undefined) | ||
|
|
||
| const navItemClass = (active: boolean) => | ||
| cn( | ||
| "typography-heading-md-semibold", | ||
| active | ||
| ? "text-text-interactive-secondary" | ||
| : "text-text-interactive-tertiary", | ||
| ) | ||
| return ( | ||
| <Navigation | ||
| leftContent={ | ||
| <div className="flex shrink-0 items-center gap-12"> | ||
| <HomeButton /> | ||
| <span className="typography-heading-md-semibold text-text-interactive-secondary-hovered"> | ||
| 관리자페이지 | ||
| </span> | ||
| </div> | ||
| } | ||
| rightContent={ | ||
| <div | ||
| className="flex shrink-0 items-center gap-52" | ||
| role="group" | ||
| aria-label="관리자 섹션 탭" | ||
| > | ||
| <SecondaryPlainBoxButton | ||
| asChild | ||
| className={navItemClass(isActive("/admin/reports"))} | ||
| aria-current={getAriaCurrent(isActive("/admin/reports"))} | ||
| > | ||
| <Link href="/admin/reports">신고접수</Link> | ||
| </SecondaryPlainBoxButton> | ||
| <SecondaryPlainBoxButton | ||
| asChild | ||
| className={navItemClass(isActive("/admin/games"))} | ||
| aria-current={getAriaCurrent(isActive("/admin/games"))} | ||
| > | ||
| <Link href="/admin/games">게임관리</Link> | ||
| </SecondaryPlainBoxButton> | ||
| <SecondaryPlainBoxButton | ||
| asChild | ||
| className={navItemClass(isActive("/admin/users"))} | ||
| aria-current={getAriaCurrent(isActive("/admin/users"))} | ||
| > | ||
| <Link href="/admin/users">회원관리</Link> | ||
| </SecondaryPlainBoxButton> | ||
| </div> | ||
| } | ||
| /> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,3 @@ | ||
| export default function AdminGamesPage() { | ||
| return <div>AdminPage-Games</div> | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| "use client" | ||
|
|
||
| import { useRouter } from "next/navigation" | ||
| import { useEffect, useState } from "react" | ||
|
|
||
| import { validateSession } from "@/entities/auth/api/validateSession" | ||
|
|
||
| import { AdminNavigation } from "./_components/adminNavigation" | ||
|
|
||
| export default function AdminLayout({ | ||
| children, | ||
| }: { | ||
| children: React.ReactNode | ||
| }) { | ||
| const router = useRouter() | ||
| const [checked, setChecked] = useState(false) | ||
|
|
||
| useEffect(() => { | ||
| validateSession() | ||
| .then((user) => { | ||
| if (!user || user.data?.role !== "ADMIN") { | ||
| router.replace("/") | ||
| return | ||
| } | ||
| setChecked(true) | ||
| }) | ||
| .catch(() => { | ||
| router.replace("/") | ||
| }) | ||
| }, [router]) | ||
|
|
||
| if (!checked) { | ||
| return null | ||
| } | ||
|
|
||
| return ( | ||
| <div> | ||
| <AdminNavigation /> | ||
| {children} | ||
| </div> | ||
| ) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,5 @@ | ||
| import { redirect } from "next/navigation" | ||
|
|
||
| export default function AdminPage() { | ||
| redirect("/admin/reports") | ||
| } |
196 changes: 196 additions & 0 deletions
196
service/app/src/app/admin/reports/__tests__/reports.spec.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,196 @@ | ||
| import { expect, test } from "@playwright/test" | ||
|
|
||
| import { ReportsPOM } from "./reportsPOM" | ||
|
|
||
| test.describe("신고접수 페이지", () => { | ||
| test.describe("인증 및 권한", () => { | ||
| test("비로그인 사용자는 홈으로 리다이렉트 되어야 한다", async ({ | ||
| page, | ||
| }) => { | ||
| await page.goto("/admin/reports") | ||
| await page.waitForURL("/") | ||
| }) | ||
|
|
||
| // test("로그인 사용자라도 ADMIN이 아닌 경우 홈으로 리다이렉트 되어야 한다", async ({ | ||
| // page, | ||
| // }) => { | ||
| // // TODO: USER role로 로그인 상태 설정 필요 | ||
| // await page.goto("/admin/reports") | ||
| // await page.waitForURL("/") | ||
| // }) | ||
| }) | ||
|
|
||
| test.describe("초기 UI 요소", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("초기 UI 요소가 잘 표시되어야 한다", async () => { | ||
| await pom.checkNavigationHighlight() | ||
| await pom.checkReportedTable() | ||
| }) | ||
| }) | ||
|
|
||
| test.describe("네비게이션", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("게임 관리 클릭시 게임 관리 페이지로 이동한다", async ({ page }) => { | ||
| await pom.clickGamesTab() | ||
| await page.waitForURL("/admin/games") | ||
| }) | ||
|
|
||
| test("회원 관리 클릭시 회원 관리 페이지로 이동한다", async ({ page }) => { | ||
| await pom.clickUsersTab() | ||
| await page.waitForURL("/admin/users") | ||
| }) | ||
| }) | ||
|
|
||
| test.describe("기본 리스트 렌더링", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("한 화면에 표시된 row 수가 7개 이하이다", async () => { | ||
| await pom.checkRowCount() | ||
| }) | ||
|
|
||
| test("각 row에 컬럼이 존재한다 (번호, 게임명, 제작자, 신고자, 신고일자, 처리여부)", async () => { | ||
| await pom.checkRowColumns() | ||
| }) | ||
| }) | ||
|
|
||
| test.describe("신고 상세 팝업: 공통", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("신고 항목을 클릭하면 신고 내용 확인 팝업이 표시된다", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
| await pom.checkReportDetailPopup() | ||
| }) | ||
|
|
||
| test("게임 정보가 표시된다 (게임명, 전체 문제 수, 신고 사유)", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
| await pom.checkGameInfo() | ||
| }) | ||
|
|
||
| test("팝업 닫기 버튼 클릭시 팝업이 닫힌다", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
| await pom.checkReportDetailPopup() | ||
| await pom.clickCloseButton() | ||
| await expect(pom.reportDetail).not.toBeVisible() | ||
| }) | ||
| }) | ||
|
|
||
| test.describe("신고 상세 팝업: 삭제되지 않은 게임", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("문제 목록이 캐러셀로 노출된다", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
| await pom.checkCarousel() | ||
| }) | ||
|
|
||
| test("삭제되지 않은 게임에서 게임 삭제 클릭시 팝업이 닫히고 상태가 삭제 완료로 업데이트된다", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
|
|
||
| const row = pom.reportedGameRows.first() | ||
|
|
||
| await pom.clickDeleteGame() | ||
| await expect(pom.reportDetail).not.toBeVisible() | ||
|
|
||
| await expect(row).toHaveAttribute("aria-label", /처리 여부 삭제 완료/) | ||
| }) | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| }) | ||
|
|
||
| test.describe("신고 상세 팝업: 삭제된 게임", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
| test("삭제된 게임의 경우 삭제 처리된 게임입니다 문구가 표시되고 캐러셀이 노출되지 않는다", async () => { | ||
| await pom.clickReportedGameRow(1) | ||
|
|
||
| await pom.checkDeletedGameMessage() | ||
| await expect(pom.gameQuestionList).not.toBeVisible() | ||
| }) | ||
|
|
||
| test("삭제된 게임 클릭시 신고 무시 버튼과 게임 삭제 버튼이 비활성화된다", async () => { | ||
| await pom.clickReportedGameRow(1) | ||
|
|
||
| await pom.checkButtonDisabled(pom.reportIgnoreButton) | ||
| await pom.checkButtonDisabled(pom.gameDeleteButton) | ||
| }) | ||
| }) | ||
|
|
||
| test.describe("신고 무시", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("신고 무시 클릭시 팝업이 닫히고 상태가 신고 무시로 업데이트된다", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
| const row = pom.reportedGameRows.first() | ||
|
|
||
| await pom.clickIgnoreReport() | ||
| await expect(pom.reportDetail).not.toBeVisible() | ||
|
|
||
| await expect(row).toHaveAttribute("aria-label", /처리 여부 신고 무시/) | ||
| }) | ||
| }) | ||
|
|
||
| test.describe("사용자 차단", () => { | ||
| test.use({ storageState: "playwright/.auth/user.json" }) | ||
|
|
||
| let pom: ReportsPOM | ||
|
|
||
| test.beforeEach(async ({ page }) => { | ||
| pom = new ReportsPOM(page) | ||
| await pom.navigateToReports() | ||
| }) | ||
|
|
||
| test("사용자 차단 클릭시 사용자 차단 버튼이 비활성화된다", async () => { | ||
| await pom.clickReportedGameRow(0) | ||
| await pom.clickBlockCreator() | ||
| await expect(pom.reportDetail).toBeVisible() | ||
| await pom.checkButtonDisabled(pom.blockCreatorButton) | ||
| }) | ||
| }) | ||
| }) | ||
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
주석 처리된 권한 테스트는 추적이 어렵습니다.
해당 케이스는 주석 블록 대신 이슈로 등록하거나 테스트 형태로 관리해 주세요. 필요하시면 구현 지원 가능합니다.
🤖 Prompt for AI Agents