feat(dev): /test 라우트 — URL prefill 픽스처로 분석 플로우 빠르게 검증

- TestPrefillPage: 자주 쓰는 병원 URL 묶음을 버튼 한 번에 채널 입력으로 prefill
- /dev/* 와 동일하게 DevOnly 가드 적용 (로컬에서만 접근)
main
Mina Choi 2026-05-20 11:51:27 +09:00
parent 47fed51efc
commit a805afa39d
3 changed files with 214 additions and 1 deletions

View File

@ -0,0 +1,144 @@
/**
* /test + E2E· URL .
*
* 7 : homepage, youtube, instagram, facebook, naverPlace, naverBlog, gangnamUnni
* . naverPlace ( ID ).
*/
export interface ChannelUrls {
homepage?: string
youtube?: string
instagram?: string
facebook?: string
naverPlace?: string
naverBlog?: string
gangnamUnni?: string
}
export interface ClinicFixture {
label: string
urls: ChannelUrls
}
export const CLINICS: ClinicFixture[] = [
{
label: '뷰성형외과',
urls: {
homepage: 'viewclinic.com',
youtube: 'youtube.com/channel/UCQqqH3Klj2HQSHNNSVug-CQ',
instagram: 'instagram.com/viewplastic',
facebook: 'facebook.com/viewps1',
naverPlace: 'https://naver.me/x9BxGXkK',
naverBlog: 'blog.naver.com/viewclinicps',
gangnamUnni: 'gangnamunni.com/hospitals/189',
},
},
{
label: '바노바기 성형외과',
urls: {
homepage: 'banobagi.com',
youtube: 'youtube.com/c/banobagips',
instagram: 'instagram.com/banobagi_ps',
facebook: 'facebook.com/BanobagiPlasticSurgery',
naverPlace: 'https://naver.me/xxY2yLr5',
naverBlog: 'blog.naver.com/banobagiprs',
gangnamUnni: 'gangnamunni.com/hospitals/23',
},
},
{
label: 'ID 성형외과',
urls: {
homepage: 'idhospital.com',
youtube: 'youtube.com/user/IDhospital',
instagram: 'instagram.com/idhospital',
facebook: 'facebook.com/idhospital0050',
naverPlace: 'https://naver.me/GtURpCEn',
naverBlog: '',
gangnamUnni: 'gangnamunni.com/hospitals/257',
},
},
{
label: 'JK 성형외과',
urls: {
homepage: 'jkplastic.com',
youtube: 'youtube.com/channel/UC5F8dEt32hdp3cTeFyls4qg',
instagram: 'instagram.com/jkplasticsurgery_kr',
facebook: 'facebook.com/jkmedicalgroup',
naverPlace: 'https://naver.me/x67y6cAc',
naverBlog: 'blog.naver.com/jkstory1',
gangnamUnni: 'gangnamunni.com/hospitals/858',
},
},
{
label: '그랜드 성형외과',
urls: {
homepage: 'grandsurgery.com',
youtube: 'youtube.com/channel/UCU2o_aHqsNFuqwtdzVM3xbQ',
instagram: 'instagram.com/grand_korea',
facebook: 'facebook.com/grandps.korea',
naverPlace: 'https://naver.me/Fw7MYKWK',
naverBlog: 'blog.naver.com/grandprs',
gangnamUnni: 'gangnamunni.com/hospitals/62',
},
},
{
label: 'BK 성형외과',
urls: {
homepage: 'bkhospital.com',
youtube: 'youtube.com/channel/UChJONft3hemy5DGbXUveTFg',
instagram: 'instagram.com/bkhospital_korea',
facebook: '',
naverPlace: 'https://naver.me/517CTH3W',
naverBlog: '',
gangnamUnni: '',
},
},
{
label: '톡스앤필',
urls: {
homepage: 'toxnfill.com',
youtube: 'youtube.com/channel/UCFpFZkm7mclD-z_-j7FTUag',
instagram: 'instagram.com/toxnfill_official',
facebook: 'facebook.com/toxnfill.official',
naverPlace: 'https://naver.me/FvEmJIHA',
naverBlog: 'blog.naver.com/toxnfill',
gangnamUnni: 'gangnamunni.com/hospitals/3702',
},
},
{
label: '더 압구정 성형외과',
urls: {
homepage: 'theclinic.co.kr',
youtube: 'youtube.com/user/theplasticsurgery1',
instagram: 'instagram.com/the_plasticsurgery',
facebook: 'facebook.com/THEPS16445998',
naverPlace: '',
naverBlog: 'blog.naver.com/with_theps',
gangnamUnni: 'gangnamunni.com/hospitals/30',
},
},
{
label: '라마르 성형외과',
urls: {
homepage: 'lamarps.com',
youtube: '',
instagram: '',
facebook: '',
naverPlace: '',
naverBlog: '',
gangnamUnni: '',
},
},
{
label: '오라클 성형외과',
urls: {
homepage: 'oracleclinic.com',
youtube: 'youtube.com/@oracle_medical_group',
instagram: 'instagram.com/oraclemedicalgroup',
facebook: 'facebook.com/oracleclinickr',
naverPlace: 'https://naver.me/GhbU3VtK',
naverBlog: '',
gangnamUnni: 'gangnamunni.com/hospitals/125',
},
},
]

View File

@ -0,0 +1,67 @@
/**
* /test fixture dev .
*
* - CLINICS 1 form prefill
* - "다른 병원 랜덤"
* - DevOnly localhost
*/
import { useMemo, useState } from 'react'
import { useNavigate } from 'react-router'
import MultiChannelInput, { type AnalyzePayload } from '@/features/channels/components/MultiChannelInput'
import { CLINICS } from '../fixtures/mockUrls'
function pickRandomIndex(): number {
return Math.floor(Math.random() * CLINICS.length)
}
export default function TestPrefillPage() {
const navigate = useNavigate()
const [seed, setSeed] = useState(0)
const index = useMemo(() => pickRandomIndex(), [seed])
const clinic = CLINICS[index]
const handleAnalyze = (payload: AnalyzePayload) => {
navigate('/report/loading', {
state: {
url: payload.primaryUrl,
manualChannels: payload.manualChannels,
},
})
}
return (
<section className="min-h-screen pt-28 pb-12 px-6 bg-gradient-to-br from-indigo-50 via-purple-50 to-pink-50">
<div className="max-w-4xl mx-auto">
<div className="mb-8 flex items-center justify-between gap-4">
<div>
<div className="inline-flex items-center gap-2 px-3 py-1 rounded-full bg-amber-100 text-amber-800 text-xs font-medium mb-3">
DEV · /test
</div>
<h1 className="text-3xl font-bold text-primary-900">
</h1>
<p className="mt-2 text-sm text-slate-600">
: <span className="font-semibold text-primary-900">{clinic.label}</span>
{' '}<span className="text-slate-400">({index + 1} / {CLINICS.length})</span>
</p>
</div>
<button
type="button"
onClick={() => setSeed((s) => s + 1)}
className="px-4 py-2 rounded-lg bg-white border border-slate-200 text-sm font-medium text-slate-700 hover:bg-slate-50 shadow-sm"
>
</button>
</div>
{/* key={seed} 로 강제 remount — initialUrls는 mount 시점 1회만 반영되기 때문 */}
<MultiChannelInput
key={seed}
variant="hero"
initialUrls={clinic.urls}
onAnalyze={handleAnalyze}
/>
</div>
</section>
)
}

View File

@ -3,14 +3,16 @@ import DevOnly from './components/DevOnly'
const ComponentsPage = lazy(() => import('./pages/ComponentsPage'))
const ClinicsPage = lazy(() => import('./pages/ClinicsPage'))
const TestPrefillPage = lazy(() => import('./pages/TestPrefillPage'))
// `/dev/*` 는 DevOnly 가드를 거쳐 로컬호스트에서만 접근 가능.
// `/dev/*` 와 `/test` 는 DevOnly 가드를 거쳐 로컬호스트에서만 접근 가능.
export const devRoutes = [
{
element: <DevOnly />,
children: [
{ path: 'dev/components', element: <ComponentsPage /> },
{ path: 'dev/clinics', element: <ClinicsPage /> },
{ path: 'test', element: <TestPrefillPage /> },
],
},
]