feat: 이베이 부품 AI 리스팅 툴 — 실제 크롤링·AI·가격 모듈 구현
[핵심 모듈 (lib/ebay-tools/)] - types.ts: 검색 요청/결과/크롤링/가격 공통 타입 정의 - crawler.ts: RockAuto HTTP 크롤러 + eBay 검색 (cheerio, UA 로테이션) - ai-analyzer.ts: Claude API Tool Use로 크롤링 결과 구조화 (lazy 클라이언트, 런타임 검증) - pricing.ts: 환율 API 연동 + HS Code 관세 + VAT + 소액면세 계산 [검색 API] - Mock 데이터 → 실제 크롤링+AI+가격 파이프라인으로 교체 - AI 실패 시 fallback 결과 생성 - 입력값 50자 제한 + 허용 문자 검증 [프론트엔드] - 중복 타입 제거 → lib/ebay-tools/types import - 가격 탭에 VAT, 총 수입비용, 면세 여부, 면책 문구 추가 [DB] - 004_ebay_search_history.sql: 검색 이력 테이블 + RLS (anon 전체 권한 제거) [Evaluator 반영] - anon RLS 보안 취약점 수정 - AI 응답 런타임 필드 검증 추가 - Anthropic 클라이언트 lazy 초기화 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,50 +1,11 @@
|
||||
'use client';
|
||||
|
||||
import { useState, useCallback } from 'react';
|
||||
import type { SearchResult as FullSearchResult, FitmentEntry, PriceSource } from '@/lib/ebay-tools/types';
|
||||
|
||||
/* ── Types ──────────────────────────────────────────────────── */
|
||||
interface FitmentEntry {
|
||||
year: string;
|
||||
make: string;
|
||||
model: string;
|
||||
engine: string;
|
||||
confidence: 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
interface PricingSource {
|
||||
site: string;
|
||||
price: number;
|
||||
currency: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
interface SearchResult {
|
||||
basicInfo: {
|
||||
partNumber: string;
|
||||
partName: string;
|
||||
brand: string;
|
||||
oemNumbers: string[];
|
||||
category: string;
|
||||
};
|
||||
listing: {
|
||||
title: string;
|
||||
category: string;
|
||||
itemSpecifics: Record<string, string>;
|
||||
};
|
||||
fitment: FitmentEntry[];
|
||||
pricing: {
|
||||
sources: PricingSource[];
|
||||
exchangeRate: { rate: number; source: string; date: string };
|
||||
customs: { hsCode: string; dutyRate: string; estimatedDuty: number };
|
||||
};
|
||||
rawData: Record<string, unknown>;
|
||||
meta: {
|
||||
searchedAt: string;
|
||||
sourcesChecked: string[];
|
||||
processingTime: string;
|
||||
aiModel: string;
|
||||
};
|
||||
}
|
||||
/* ── Types (페이지 전용) ──────────────────────────────────── */
|
||||
// SearchResult['data']에 해당하는 타입 (API 응답의 data 필드)
|
||||
type PageSearchResult = FullSearchResult['data'];
|
||||
|
||||
interface HistoryItem {
|
||||
partNumber: string;
|
||||
@@ -96,7 +57,7 @@ export default function EbayPartsPage() {
|
||||
const [partName, setPartName] = useState('');
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [result, setResult] = useState<SearchResult | null>(null);
|
||||
const [result, setResult] = useState<PageSearchResult | null>(null);
|
||||
const [activeTab, setActiveTab] = useState<TabId>('basic');
|
||||
const [history, setHistory] = useState<HistoryItem[]>([]);
|
||||
const [copied, setCopied] = useState(false);
|
||||
@@ -360,7 +321,12 @@ export default function EbayPartsPage() {
|
||||
</p>
|
||||
</div>
|
||||
<div className="bg-slate-50 rounded-lg p-4 border border-slate-100">
|
||||
<p className="text-xs text-slate-500 font-medium mb-2">관세 참고</p>
|
||||
<p className="text-xs text-slate-500 font-medium mb-2">관세/부가세 참고</p>
|
||||
{pricing.customs.isExempt && (
|
||||
<p className="text-sm text-emerald-700 font-semibold mb-2">
|
||||
$150 이하 소액면세 대상
|
||||
</p>
|
||||
)}
|
||||
<p className="text-sm text-slate-900">
|
||||
HS Code: <span className="font-mono font-bold">{pricing.customs.hsCode}</span>
|
||||
</p>
|
||||
@@ -370,6 +336,13 @@ export default function EbayPartsPage() {
|
||||
<p className="text-sm text-slate-900">
|
||||
예상 관세: <span className="font-bold text-blue-700">{pricing.customs.estimatedDuty.toLocaleString()}원</span>
|
||||
</p>
|
||||
<p className="text-sm text-slate-900">
|
||||
부가세 (VAT 10%): <span className="font-bold text-blue-700">{pricing.customs.vat.toLocaleString()}원</span>
|
||||
</p>
|
||||
<p className="text-sm text-slate-900 mt-1">
|
||||
총 수입 비용: <span className="font-bold text-blue-800 text-base">{pricing.customs.totalImportCost.toLocaleString()}원</span>
|
||||
</p>
|
||||
<p className="text-xs text-slate-400 mt-2">{pricing.customs.disclaimer}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user