Files
slot/next-app/scripts/screenshot.mjs
T
chpark 30e9b7a8ee Build out the user-facing site to match production scope
After the initial scaffold the site was missing most of the production
surface (회원가입/마이페이지/메가메뉴 카테고리/관리자/게임/포인트존/...).
This commit closes that gap and verifies every route end-to-end with
playwright screenshots (27/27 pass).

## Theme system
- Replaced eyoom theme with a higher-fidelity reproduction of the
  production eb4_maga_005 layout: top utility bar (북마크/회원가입/구매내역
  /추가메뉴), brand row with 로그인/내글반응/쪽지 icon stack with badges,
  purple gradient mega-menu carrying 10 categories with submenus, dark
  mode toggle, sticky LOGIN box + 텔레그램CS box + 태그 클라우드 + 회원
  랭킹 + 방문자 stats in the sidebar, and a footer with 이용약관/개인정보
  처리방침/이메일무단수집거부.
- Added IndexHome slot to the theme contract; basic/amina/youngcart got
  matching implementations so theme switching keeps working.
- Layout now consistently provides the right-rail sidebar on public pages
  and hides it on /login, /register, /mypage and /admin (admin uses its
  own left rail).

## Pages added
- Auth: /register (with API-backed insert into the new members table),
  /auth/recover (id+password recovery shells).
- /mypage dashboard with 12 sub-routes.
- /memo, /bookmarks, /new, /tags, /tag/[tag], /profile/[nick].
- 10 mega-menu landing pages: /guarantee, /guarantee/apply, /mukti,
  /complaint, /inspection, /fakesite, /event, /lottery_ticket,
  /gift_coupons, /gift_exchanges, /notice; /games (bacara, fortunes,
  fivetreasures, slot, roulette, ranking); /wallet (+ exchange,
  point-exchange, slotbuff); /tv; /guide (+ community/pointgame/mukti/tv);
  /help/qa, /help/faq.
- Static pages: /page/provision, /page/privacy, /page/noemail,
  /page/aboutus, /page/manual, /page/attendance.
- Per-board: /[slug]/write, /[slug]/[wrId]/edit, /[slug]/search.

## Post interactions (wired against legacy g5_* tables)
- POST /api/posts/create        — insert into inspection2.g5_write_<slug>
- POST /api/posts/[id]/comment  — insert is_comment row + bump wr_comment
- POST /api/posts/[id]/good|bad — bump wr_good/wr_nogood + g5_board_good
- POST /api/posts/[id]/scrap    — insert g5_scrap
- POST /api/posts/[id]/report   — write into writing_activity
- POST /api/posts/[id]/delete   — owner+admin gate, soft-delete row
- POST /api/ui/dark-mode        — flip slot_dark cookie

## Admin
- /admin layout with left nav (10 sections) gated by member level >= 10.
- /admin                — dashboard with live counts pulled from PG
- /admin/members        — searchable member list with status badges
- /admin/boards         — board roster with post/comment counts
- /admin/betting        — bacara/swiun/game-point counters + recent feed
- /admin/stats          — 14-day visit chart + top boards + level histogram
- /admin/themes         — 4-theme picker (already existed, now polished)
- /admin/{menu,permissions,points,games} — stubs for M5

## Infra fixes
- Postgres pool hoisted onto globalThis so HMR doesn't leak connections
  ("sorry, too many clients already" 500s).
- Removed broken Next.js redirects() entry that prevented dev from booting.

## Verification
- scripts/screenshot.mjs: pre-logs-in as admin/test1234, then captures 27
  pages + 4 theme variants of /. All 200, all rendered. PNGs committed
  under next-app/screenshots/ for review.
2026-04-27 20:20:49 +09:00

77 lines
3.4 KiB
JavaScript

import { chromium } from 'playwright';
import { mkdir } from 'node:fs/promises';
import { resolve } from 'node:path';
const OUT = resolve(process.cwd(), 'screenshots');
await mkdir(OUT, { recursive: true });
const PAGES = [
{ url: '/', name: '01-home' },
{ url: '/free', name: '02-board-free' },
{ url: '/free?page=2', name: '03-board-free-p2' },
{ url: '/login', name: '04-login' },
{ url: '/register', name: '05-register' },
{ url: '/auth/recover', name: '06-recover' },
{ url: '/help/qa', name: '07-qa' },
{ url: '/help/faq', name: '08-faq' },
{ url: '/wallet', name: '09-wallet' },
{ url: '/page/attendance', name: '10-attendance' },
{ url: '/games/bacara', name: '11-game-bacara' },
{ url: '/games/ranking', name: '12-rankings' },
{ url: '/new', name: '13-new-posts' },
{ url: '/tags', name: '14-tags' },
{ url: '/notice', name: '15-notice-board' },
{ url: '/page/provision', name: '16-terms' },
{ url: '/page/privacy', name: '17-privacy' },
{ url: '/admin/themes', name: '18-admin-themes' },
{ url: '/admin', name: '19-admin-dashboard', authed: true },
{ url: '/admin/members', name: '20-admin-members', authed: true },
{ url: '/admin/boards', name: '21-admin-boards', authed: true },
{ url: '/admin/betting', name: '22-admin-betting', authed: true },
{ url: '/admin/stats', name: '23-admin-stats', authed: true },
{ url: '/free/1024', name: '24-post-view' },
{ url: '/mypage', name: '25-mypage', authed: true },
{ url: '/games/roulette', name: '26-roulette' },
{ url: '/lottery_ticket', name: '27-lottery' },
];
const browser = await chromium.launch();
const context = await browser.newContext({ viewport: { width: 1440, height: 900 }, locale: 'ko-KR' });
const page = await context.newPage();
// Pre-login as admin so authed routes can be visited
await page.goto('http://localhost:3000/login');
await page.fill('input[name="loginId"]', 'admin');
await page.fill('input[name="password"]', 'test1234');
await Promise.all([page.waitForURL((u) => u.pathname === '/'), page.click('button[type="submit"]')]).catch(() => {});
let okCount = 0, failCount = 0;
for (const p of PAGES) {
const url = `http://localhost:3000${p.url}`;
try {
const resp = await page.goto(url, { waitUntil: 'networkidle', timeout: 20_000 });
const status = resp ? resp.status() : 0;
await page.screenshot({ path: `${OUT}/${p.name}.png`, fullPage: false });
const title = await page.title();
console.log(`${status === 200 ? '✓' : '✗'} ${status} ${p.url} ${title.slice(0, 50)}`);
if (status === 200) okCount++; else failCount++;
} catch (e) {
console.log(`✗ ERR ${p.url} ${e.message?.slice(0, 80)}`);
failCount++;
}
}
// Also: theme switch screenshots
const THEMES = ['basic', 'eyoom', 'amina', 'youngcart'];
for (const t of THEMES) {
// set the cookie to override
await context.addCookies([{ name: 'slot_theme_pref', value: t, url: 'http://localhost:3000' }]);
await page.goto('http://localhost:3000/', { waitUntil: 'networkidle' });
await page.screenshot({ path: `${OUT}/theme-${t}-home.png`, fullPage: false });
console.log(`✓ theme=${t} home captured`);
}
await browser.close();
console.log(`\nDone. ${okCount} ok, ${failCount} fail. → ${OUT}`);
process.exit(failCount > 0 ? 1 : 0);