فهرست منبع

fix(frontend): 전수 감사 확정결함 11건 (계약·프록시·라우트·용어)

계약(라벨맵):
- LOCKUP_REASON_LABEL 창작 라벨 → SEIBro 원본 DUTY_SAFEDP_RACD 전체 코드표(58종) — /market/lockup 사유 오표기 해소
- FOREIGN_SECN_TPCD_LABEL 72~78 오표기 교정(외화DR/WRTS/RGTS/채권/CD/CP/수익증권)
- NATION_LABEL E1: 유럽(유로존)→유로클리어(ICSD)

프록시(DELETE 부재 → 기능 불능 해소):
- feed/note/store [...path] route.ts 에 DELETE 핸들러 추가 — 피드 글·댓글/쪽지/보관함 삭제 복구

라우트/링크:
- 포럼 태그 링크 /tag/{slug}(404) → /latest?tag={slug}
- approval 문의 링크 /support/qna(404) → /board/qna
- login returnUrl 소비 — 로그인 후 원위치(체크아웃 등) 복귀
- 종목 공시 탭 Pager perPage 30→20 (백엔드 기본과 일치, 뒤 페이지 도달 가능)

용어:
- checkout 'POINT 잔액(예치금)' → '캐시 잔액'
- cart/store 결제안내 '캐시·토큰 차감' → '캐시 잔액(토큰 사용 불가)' (백엔드 캐시 전용 계약 일치)

npm build 0에러. 보류: 아이템 소모품 탭 지위아이템 혼입(#4 — 백엔드 kind 필터 필요).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
KIM-JINO5 2 هفته پیش
والد
کامیت
ec4b13c7fa

+ 1 - 1
app/(auth)/approval/page.tsx

@@ -209,7 +209,7 @@ export default function Approval()
 
 
 						<dl>
 						<dl>
 							<dt>도움이 필요하신가요?</dt>
 							<dt>도움이 필요하신가요?</dt>
-							<dd>인증번호를 받을 수 없거나 오류가 발생한 경우 <Link href="/support/qna">운영자에게 문의</Link>하세요.</dd>
+							<dd>인증번호를 받을 수 없거나 오류가 발생한 경우 <Link href="/board/qna">운영자에게 문의</Link>하세요.</dd>
 						</dl>
 						</dl>
 					</form>
 					</form>
 				</fieldset>
 				</fieldset>

+ 1 - 1
app/(main)/(forum)/post/[id]/view.tsx

@@ -307,7 +307,7 @@ export default function View({ _board, _post }: Props)
 						{/* 태그 표시 */}
 						{/* 태그 표시 */}
 						{_post.tagList.map((row, i) => (
 						{_post.tagList.map((row, i) => (
 							<span key={i}>
 							<span key={i}>
-								<Link href={`/tag/${row.slug}`}>#{row.slug}</Link>
+								<Link href={`/latest?tag=${encodeURIComponent(row.slug)}`}>#{row.slug}</Link>
 							</span>
 							</span>
 						))}
 						))}
 					</article>
 					</article>

+ 1 - 1
app/(main)/cart/page.tsx

@@ -260,7 +260,7 @@ export default function CartPage()
 							/>
 							/>
 
 
 							<p className='text-xs text-neutral-500 mt-2'>
 							<p className='text-xs text-neutral-500 mt-2'>
-								* 결제 시 캐시·토큰 잔액에서 차감됩니다.
+								* 결제 시 캐시 잔액에서 차감됩니다. (토큰 사용 불가)
 							</p>
 							</p>
 						</div>
 						</div>
 					</div>
 					</div>

+ 1 - 1
app/(main)/checkout/page.tsx

@@ -175,7 +175,7 @@ export default function CheckoutPage()
 
 
 				{/* 결제수단 / 후원 채널 */}
 				{/* 결제수단 / 후원 채널 */}
 				<div className='px-4 sm:px-6 py-4 text-xs text-neutral-600 dark:text-neutral-400 space-y-1'>
 				<div className='px-4 sm:px-6 py-4 text-xs text-neutral-600 dark:text-neutral-400 space-y-1'>
-					<div>결제수단: <span className='font-semibold'>POINT 잔액 (예치금)</span></div>
+					<div>결제수단: <span className='font-semibold'>캐시 잔액</span></div>
 					{channel ? (
 					{channel ? (
 						<div>
 						<div>
 							후원 채널: <span className='font-semibold'>{channel.name}</span>
 							후원 채널: <span className='font-semibold'>{channel.name}</span>

+ 1 - 1
app/(main)/stock/[code]/page.tsx

@@ -178,7 +178,7 @@ export default async function StockDetailPage({ params, searchParams }: Props)
 							<Pager
 							<Pager
 								total={disclosures.data.total}
 								total={disclosures.data.total}
 								page={page}
 								page={page}
-								perPage={HISTORY_PER_PAGE}
+								perPage={20}
 								basePath={`/stock/${detail.code}`}
 								basePath={`/stock/${detail.code}`}
 								query={{ tab: 'disclosure' }}
 								query={{ tab: 'disclosure' }}
 							/>
 							/>

+ 1 - 1
app/(main)/store/[id]/page.tsx

@@ -434,7 +434,7 @@ export default function StoreDetailPage({ params }: { params: Promise<{ id: stri
 					</div>
 					</div>
 
 
 					<p className='text-xs text-neutral-500'>
 					<p className='text-xs text-neutral-500'>
-						* 결제 시 캐시·토큰 잔액에서 차감됩니다.
+						* 결제 시 캐시 잔액에서 차감됩니다. (토큰 사용 불가)
 					</p>
 					</p>
 				</div>
 				</div>
 			</div>
 			</div>

+ 7 - 0
app/api/feed/[[...path]]/route.ts

@@ -44,4 +44,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
 	const endpoint = buildEndpoint(path);
 	const endpoint = buildEndpoint(path);
 	const res = await forwardWithBody(request, endpoint, 'POST');
 	const res = await forwardWithBody(request, endpoint, 'POST');
 	return NextResponse.json(res);
 	return NextResponse.json(res);
+}
+
+export async function DELETE(_: NextRequest, { params }: { params: Promise<{ path?: string[] }> }) {
+	const { path } = await params;
+	const endpoint = buildEndpoint(path);
+	const res: ResultDto = await fetchJson(endpoint, { method: 'DELETE' });
+	return NextResponse.json(res);
 }
 }

+ 7 - 0
app/api/note/[...path]/route.ts

@@ -15,4 +15,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
 	const endpoint = `/api/note/${path.join('/')}`;
 	const endpoint = `/api/note/${path.join('/')}`;
 	const res: ResultDto = await fetchJson(endpoint, { method: 'POST', body: (await request.text()) || '{}' });
 	const res: ResultDto = await fetchJson(endpoint, { method: 'POST', body: (await request.text()) || '{}' });
 	return NextResponse.json(res);
 	return NextResponse.json(res);
+}
+
+export async function DELETE(_: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+	const { path } = await params;
+	const endpoint = `/api/note/${path.join('/')}`;
+	const res: ResultDto = await fetchJson(endpoint, { method: 'DELETE' });
+	return NextResponse.json(res);
 }
 }

+ 7 - 0
app/api/store/[...path]/route.ts

@@ -29,3 +29,10 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
 	const res: ResultDto = await fetchJson(endpoint, { method: 'POST' });
 	const res: ResultDto = await fetchJson(endpoint, { method: 'POST' });
 	return NextResponse.json(res);
 	return NextResponse.json(res);
 }
 }
+
+export async function DELETE(_: NextRequest, { params }: { params: Promise<{ path: string[] }> }) {
+	const { path } = await params;
+	const endpoint = `/api/store/${path.join('/')}`;
+	const res: ResultDto = await fetchJson(endpoint, { method: 'DELETE' });
+	return NextResponse.json(res);
+}

+ 2 - 1
hooks/useAuth.ts

@@ -33,7 +33,8 @@ export default function useAuth()
 			localStorage.setItem("member", JSON.stringify(res.data));
 			localStorage.setItem("member", JSON.stringify(res.data));
 			document.cookie = `member=${encodeURIComponent(JSON.stringify(res.data))}; path=/; max-age=${60 * 60 * 24 * 30}`;
 			document.cookie = `member=${encodeURIComponent(JSON.stringify(res.data))}; path=/; max-age=${60 * 60 * 24 * 30}`;
 
 
-			location.replace('/');
+			const returnUrl = new URLSearchParams(location.search).get('returnUrl');
+			location.replace(returnUrl && returnUrl.startsWith('/') && !returnUrl.startsWith('//') ? returnUrl : '/');
 		}).catch(err => {
 		}).catch(err => {
 			console.error('로그인 처리 중 오류:', err);
 			console.error('로그인 처리 중 오류:', err);
 		});
 		});

+ 22 - 20
types/seibro.ts

@@ -35,18 +35,20 @@ export const LOCKUP_OCCR_LABEL: Record<string, string> = {
 	'2': '반환'
 	'2': '반환'
 };
 };
 
 
-// 보호예수 사유코드 (DUTY_SAFEDP_RACD) — 주요 사유 (미매칭 시 원문 코드 노출)
+// 보호예수 사유코드 (DUTY_SAFEDP_RACD) — SEIBro 원본 전체 코드표 (미매칭 시 원문 코드 노출)
 export const LOCKUP_REASON_LABEL: Record<string, string> = {
 export const LOCKUP_REASON_LABEL: Record<string, string> = {
-	'01': '유상증자',
-	'02': '무상증자',
-	'03': '기업공개(IPO)',
-	'04': '주식분할',
-	'05': '합병',
-	'06': '주식교환',
-	'07': '전환사채',
-	'08': '신주인수권',
-	'09': '스톡옵션',
-	'99': '기타'
+	'00': '해당없음', '01': '일반', '02': '모집(전매제한)', '03': '최대주주(상장)', '04': '최대주주(코스닥)',
+	'05': '투자회사(상장)', '06': '초과유상증자', '07': '지정취소', '08': '질권담보', '09': '등록주선인',
+	'10': '외국인양수(변동제한)', '11': '벤처금융', '12': '기관투자가', '13': '합병(상장)', '14': '합병(코스닥)',
+	'15': '부동산(상장)', '16': '부동산(코스닥)', '17': '법원M&A', '18': '주식교환(코스닥)', '19': '벤처금융(주식교환)',
+	'20': '벤처금융(합병)', '21': '기관투자가(합병)', '22': '기관투자가(주식교환)', '23': '제3자배정최대주주(코스닥)', '24': '선박(상장)',
+	'25': '채권기관협의회', '26': '주식교환(상장)', '27': '영업자산양도&제3자배정(상장)', '28': '영업자산양도&제3자배정(코스닥)', '29': '제3자배정유상증자(상장)',
+	'30': '제3자배정유상증자(코스닥)', '31': '최대주주(제3자배정)', '32': 'SPAC(상장)', '33': 'SPAC(코스닥)', '34': 'SPAC 합병(상장)',
+	'35': 'SPAC 합병(코스닥)', '36': '명목회사(상장)', '37': '명목회사(코스닥)', '38': '자발적보호예수', '39': '우회상장',
+	'40': '상장주선인(국내기업)', '41': '전매제한', '42': '기타(특정금전신탁편입 등)', '43': '최대주주(기술성장기업)', '44': '기관투자가(코넥스)',
+	'45': '크라우드펀딩', '46': '크라우드펀딩(최대주주)', '47': '주관회사(금투협)', '48': '상장주선인(외국기업)', '49': '모집(전매제한)-크라우드펀딩전문투자자',
+	'50': '상장주선인(50%미만)', '51': '상장주선인(50%이상)', '52': '최대주주변경(법인 또는 조합)', '53': '상장적격성 실질심사', '54': '기타 보호예수 필요 주주',
+	'55': '주식매수선택권', '90': '기타의무예수', '99': '기타'
 };
 };
 
 
 // 주총 종류코드 (GMET_KACD) — 001정기 002임시 003종류 009기타
 // 주총 종류코드 (GMET_KACD) — 001정기 002임시 003종류 009기타
@@ -63,16 +65,16 @@ export const ELS_SECN_TPCD_LABEL: Record<string, string> = {
 	'43': 'DLS'
 	'43': 'DLS'
 };
 };
 
 
-// 외화증권 종목구분코드 (SECN_TPCD) — 71외화주식~78 (합산은 null)
+// 외화증권 종목구분코드 (SECN_TPCD) — SEIBro 스펙 코드표 (docs/SEIBro/외화증권.md)
 export const FOREIGN_SECN_TPCD_LABEL: Record<string, string> = {
 export const FOREIGN_SECN_TPCD_LABEL: Record<string, string> = {
 	'71': '외화주식',
 	'71': '외화주식',
-	'72': '외화채권',
-	'73': '외화수익증권',
-	'74': '외화ETF',
-	'75': '외화DR',
-	'76': '외화ETN',
-	'77': '외화워런트',
-	'78': '기타 외화증권'
+	'72': '외화DR',
+	'73': '외화WRTS',
+	'74': '외화RGTS',
+	'75': '외화채권',
+	'76': '외화CD',
+	'77': '외화CP',
+	'78': '외화수익증권'
 };
 };
 
 
 // 국가코드 (NATION_CD) — 주요국 (미매칭 시 원문 코드 노출)
 // 국가코드 (NATION_CD) — 주요국 (미매칭 시 원문 코드 노출)
@@ -87,7 +89,7 @@ export const NATION_LABEL: Record<string, string> = {
 	VN: '베트남',
 	VN: '베트남',
 	SG: '싱가포르',
 	SG: '싱가포르',
 	CH: '스위스',
 	CH: '스위스',
-	E1: '유럽(유로존)'
+	E1: '유로클리어(ICSD)'
 };
 };
 
 
 // 채권 옵션구분코드 (OPTION_TPCD) — 9401CALL 9402PUT 9403CALL+PUT 9404NOTE 0000해당없음
 // 채권 옵션구분코드 (OPTION_TPCD) — 9401CALL 9402PUT 9403CALL+PUT 9404NOTE 0000해당없음