feat(WEB): Phase 2-3 V2 마이그레이션 완료 및 ServerErrorPage 적용

Phase 2 완료 (4개):
- 노무관리, 단가관리(건설), 입금, 출금

Phase 3 라우팅 구조 변경 완료 (22개):
- 거래처(영업), 팝업관리, 공정관리, 게시판관리, 대손추심, Q&A
- 현장관리, 실행내역, 견적관리, 견적(테스트)
- 입찰관리, 이슈관리, 현장설명회, 견적서(건설)
- 협력업체, 시공관리, 기성관리, 품목관리(건설)
- 회계 도메인: 거래처, 매출, 세금계산서, 매입

신규 컴포넌트:
- ErrorCard: 에러 페이지 UI 통일
- ServerErrorPage: V2 페이지 에러 처리 필수
- V2 Client 컴포넌트 및 Config 파일들

총 47개 상세 페이지 중 28개 완료, 19개 제외/불필요

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
유병철
2026-01-19 17:31:28 +09:00
parent 1a6cde2d36
commit 1d7b028693
109 changed files with 6811 additions and 2562 deletions

View File

@@ -7,7 +7,7 @@
* - 헤더 자동 설정
*/
import { cookies } from 'next/headers';
import { cookies, headers } from 'next/headers';
import { redirect } from 'next/navigation';
import { isNextRedirectError } from '@/lib/utils/redirect-error';
import { createErrorResponse, type ApiErrorResponse } from './errors';
@@ -76,10 +76,24 @@ async function setNewTokenCookies(tokens: {
/**
* API 헤더 생성 (Server Side)
*
* 🆕 미들웨어에서 전달한 새 토큰 우선 사용
* - 미들웨어 pre-refresh 성공 시 request headers에 'x-refreshed-access-token' 설정
* - Set-Cookie는 응답 헤더에만 설정되어 같은 요청 내 cookies()로 읽을 수 없음
* - 따라서 request headers를 먼저 확인
*/
export async function getServerApiHeaders(token?: string): Promise<HeadersInit> {
// 🆕 미들웨어에서 전달한 새 토큰 먼저 확인
const headerStore = await headers();
const refreshedAccessToken = headerStore.get('x-refreshed-access-token');
const cookieStore = await cookies();
const accessToken = token || cookieStore.get('access_token')?.value;
const accessToken = token || refreshedAccessToken || cookieStore.get('access_token')?.value;
// 디버깅: 어떤 토큰을 사용하는지 로그
if (refreshedAccessToken) {
console.log('🔵 [getServerApiHeaders] Using refreshed token from middleware headers');
}
return {
'Accept': 'application/json',
@@ -112,14 +126,18 @@ export async function serverFetch(
}
): Promise<{ response: Response | null; error: ApiErrorResponse | null }> {
try {
// 🆕 미들웨어에서 전달한 새 refresh_token 먼저 확인
const headerStore = await headers();
const refreshedRefreshToken = headerStore.get('x-refreshed-refresh-token');
const cookieStore = await cookies();
const refreshToken = cookieStore.get('refresh_token')?.value;
const refreshToken = refreshedRefreshToken || cookieStore.get('refresh_token')?.value;
const baseHeaders = await getServerApiHeaders() as Record<string, string>;
// FormData일 경우 Content-Type을 제외 (브라우저가 자동 설정)
const isFormData = options?.body instanceof FormData;
const headers: HeadersInit = isFormData
const requestHeaders: HeadersInit = isFormData
? {
Accept: baseHeaders.Accept,
Authorization: baseHeaders.Authorization,
@@ -130,7 +148,7 @@ export async function serverFetch(
let response = await fetch(url, {
...options,
headers: {
...headers,
...requestHeaders,
...options?.headers,
},
cache: options?.cache || 'no-store',
@@ -151,7 +169,7 @@ export async function serverFetch(
// 새 토큰으로 원래 요청 재시도
const newBaseHeaders = await getServerApiHeaders(refreshResult.accessToken) as Record<string, string>;
// FormData일 경우 Content-Type을 제외 (브라우저가 자동 설정)
const newHeaders: HeadersInit = isFormData
const newRequestHeaders: HeadersInit = isFormData
? {
Accept: newBaseHeaders.Accept,
Authorization: newBaseHeaders.Authorization,
@@ -161,7 +179,7 @@ export async function serverFetch(
response = await fetch(url, {
...options,
headers: {
...newHeaders,
...newRequestHeaders,
...options?.headers,
},
cache: options?.cache || 'no-store',