World Vibe Web
The distributed app store for vibe-coded projects. Browse, discover, and install open-source apps from across GitHub.
지역 농가와 소비자를 직접 연결하는 신선식품 커머스 · 미래에이아이랩 제작 레퍼런스 데모
Translation pending
CommerceTypeScript · JavaScriptGitHub @sanghohoho0813-labOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
No updates detected yet. GitHub releases will appear here.
Owners of public repositories under a personal GitHub account can sign in to verify ownership and manage the introduction themselves. No administrator approval is needed.
Sign in, generate a verification file, and commit it to this repository’s default branch within 30 minutes. Files on other branches or forks do not count. Then check the file below to manage the project without administrator approval. Keep the file to retain your access.
If needed, you will go to sign-in. After signing in, return here and press your chosen verification button again.
Run /nomorevibe verify in your project folder to verify ownership and manage the introduction yourself.
Verify in one minuteAsk about other verification options
Loading the full README. The saved excerpt is shown below.
로컬맘 (LocalMat) — 로컬 식품 커머스 MVP 미래에이아이랩(MIRAE AI LAB) 이 제작한 커머스 레퍼런스 데모입니다. 지역 농가와 소비자를 직접 연결하는 신선식품 커머스 반응형 웹앱입니다. 포트폴리오·정부지원사업 IR 시연을 목표로, 탐색 → 상품 상세 → 장바구니 → 주문 → 주문 완료까지 실제 커머스처럼 끊김 없이 동작합니다. 레퍼런스임을 한눈에 알 수 있도록 세 곳에 제작사를 노출합니다. • 최상단 리본 — 모든 페이지 상단에 "미래에이아이랩이 제작한 커머스 레퍼런스 데모" • 홈 하단 크레딧 밴드 — 브랜드 다크 컬러 섹션 + 로고 락업 + 제작 역량 3가지 • 푸터 — 로고 락업 + 저작권 표기 데모 사용자도 미래에이아이랩 김팀장으로 설정되어 마이페이지·주문서·주문내역에 반영됩니다. 브랜드 에셋은 public/brand/에 있습니다 (logo.png 전체 락업, mark.png 심볼). 원본 로고를 교체하려면: 배경을 자동으로 투명 처리하고 심볼만 잘라낸 정사각 이미지를 함께 생성합니다.Read the full README ↗
미래에이아이랩(MIRAE AI LAB) 이 제작한 커머스 레퍼런스 데모입니다.
지역 농가와 소비자를 직접 연결하는 신선식품 D2C 커머스 반응형 웹앱입니다. 탐색 → 상품 상세 → 장바구니(또는 바로 구매) → 주문서 → 데모 결제 → 주문 완료 → 주문 내역·재구매·리뷰까지 실제 서비스처럼 끊김 없이 동작합니다. 실제 PG·로그인·배송 연동은 없습니다(데모 범위).
신선식품을 사는 사람이 실제로 묻는 것 — 언제 수확했나, 언제 도착하나, 누가 키웠나, 얼마나 되나 — 에 카드·상세·장바구니·주문 완료 어디서든 같은 답을 줍니다. 제철·출고 마감·도착일은 접속한 날짜(한국 시간)로 계산합니다.
npm install
npm run dev # http://localhost:3000
npm run check # 타입 검사 + ESLint + 단위 테스트
npm run build && npm run test:e2e # 프로덕션 빌드로 E2E(모바일·PC) + 접근성 검사
| 단계 | 도구 | 무엇을 막나 |
|---|---|---|
| 정적 검사 | tsc --strict, ESLint(next/core-web-vitals + next/typescript) | 타입 오류, 훅 규칙 위반, 접근성 기본 규칙 |
| 단위 테스트 | Vitest — src/lib/*.test.ts (71건) | 금액·배송비·쿠폰 계산, 출고 마감·주말·도착일, 제철 판정, 추천 근거, 저장값 검증, 검색 매칭·자동완성 |
| E2E | Playwright — e2e/ (모바일 iPhone 13 · PC 1280, 프로덕션 빌드) | 구매 전 과정, 바로 구매, 재고 한도, 품절(시즌 준비) 상품, 지난 주문 다시 담기, PC 고정 구매 상자, 검색 자동완성·최근 검색어, 필터 칩, 장바구니 고른 상품만 주문·선택 삭제, 모바일 옵션 시트, 사진 크게 보기, 주문 상태 모아 보기, 뒤로 가기·필터 유지, 리뷰, 404 상태 코드, 콘솔 오류 0 |
| 반응형 | e2e/layout.spec.ts — 320 ~ 1280px 7개 폭 × 12개 화면 | 가로 넘침·모바일 줌아웃(긴 상품명·품절 상품이 섞인 장바구니 포함) |
| 접근성 | axe-core — WCAG 2.1 AA, 주요 12개 화면 | 명암비, 랜드마크, 제목 순서, 이름 없는 버튼 등 |
| 회복성 | e2e/resilience.spec.ts | 깨진/오래된 localStorage, 다른 탭과의 장바구니 동기화 |
워크플로: .github/workflows/ci.yml (lint → unit → build → typecheck → e2e, 실패 시 리포트 업로드)
src/
├─ app/ # Next.js 15 App Router (화면)
│ ├─ products/(list)/ # 목록 — 로딩 화면을 목록에만 두려고 route group으로 분리(아래 '결정 기록')
│ ├─ products/[slug]/ # 상세 — ISR(1시간) + schema.org Product JSON-LD
│ ├─ cart · checkout · order-complete · orders · mypage · search · farms
│ ├─ error.tsx · not-found.tsx · loading.tsx
│ └─ robots.ts · sitemap.ts · manifest.ts · apple-icon.png
├─ components/ # 화면 조각 (ProductCard, DeliveryEta, FarmGroupHeader, OrderTimeline, home/* …)
└─ lib/
├─ pricing.ts # 금액 계산의 단일 출처 — 순수 함수
├─ kst.ts # 한국 시간 달력 계산 — 서버·기기 시간대와 무관
├─ delivery.ts # 출고 마감·배송 온도별 출고 요일·도착일 — 순수 함수
├─ season.ts # 제철 판정 — 순수 함수
├─ recommend.ts # 근거 있는 추천(함께 먹기 좋은·같은 농가·지금 제철·무료배송 채우기) — 순수 함수
├─ persisted.ts # 저장값 검증·정리 — 순수 함수
├─ search.ts · orders.ts · catalog.ts
├─ store.ts # Zustand persist 저장소 (위 모듈을 조합)
├─ useHydrated.ts · useNow.ts · useScrollSpy.ts · useReorder.ts · useTabs.ts
└─ data/ # 샘플 데이터 (상품 26 · 농가 8 · 리뷰 62 · 주문 5)
e2e/ # Playwright 시나리오
supabase/schema.sql # 실제 백엔드 전환용 스키마
원칙: 화면은 계산하지 않습니다. 금액·쿠폰·검색·저장값 판단은 src/lib의 순수 함수가 하고,
화면과 저장소는 그 결과만 씁니다. 그래서 핵심 규칙은 브라우저 없이 단위 테스트로 검증됩니다.
금액 계산을 한 곳에 — 장바구니·주문서가 각자 배송비·쿠폰을 계산하던 것을 pricing.ts로 모았습니다.
쿠폰 할인은 상품 금액을 넘지 않고, 조건이 깨진 쿠폰을 골라 둔 채 금액이 바뀌면 자동으로 0원 처리됩니다.
저장값은 믿지 않는다 — localStorage는 이전 버전이 남긴 값일 수도, 사용자가 고친 값일 수도 있습니다.
모든 persist 저장소는 version + 통과형 migrate + merge 단계 검증을 거칩니다.
판매 종료 상품·없는 옵션·재고 초과 수량·깨진 JSON은 화면에 올라오기 전에 걸러집니다.
(migrate가 없으면 zustand는 버전이 다른 저장값을 통째로 버려 업데이트 직후 장바구니가 비어 버립니다.)
탭 간 동기화 — 다른 탭에서 장바구니·찜이 바뀌면 storage 이벤트로 다시 읽어 헤더 배지가 어긋나지 않습니다.
하이드레이션 — 저장값에 기대는 화면은 useHydrated()(useSyncExternalStore)로 서버 HTML과 맞춥니다.
useState + useEffect 방식과 달리 화면을 옮길 때마다 빈 화면이 깜빡이지 않습니다.
바로 구매는 장바구니와 분리 — 상세의 '바로 구매'는 그 상품만 담긴 주문서(/checkout?mode=now, 탭 단위 sessionStorage)로 갑니다.
장바구니에 담아 둔 다른 상품은 건드리지 않고, 결제 후에는 주문서를 히스토리에서 대체해 뒤로 가기로 이중 주문할 수 없습니다.
정확한 404 — 목록의 loading.tsx가 상세까지 감싸면 Next가 로딩 화면과 함께 200 헤더를 먼저 보내,
없는 상품 주소가 200으로 응답됩니다. 목록을 (list) route group으로 옮겨 상세는 정확히 404를 돌려줍니다.
명암비는 토큰에서 — 주황 CTA와 회색 보조 글자가 WCAG AA(4.5:1)에 못 미쳐, 개별 요소가 아니라
색 토큰 자체(tangerine-600, bark-400/500)를 기준에 맞췄습니다. 500 이하 주황은 아이콘·게이지 같은 '그림'에만 씁니다.
HTML 규칙 — 상품 카드는 링크 안에 버튼을 넣지 않고(무효한 HTML, 포커스가 꼬임) 상품명 링크를 카드 전체로 늘리는 stretched-link 방식입니다. 탭 UI는 WAI-ARIA 탭 패턴(←/→·Home/End, roving tabindex)을 따릅니다.
신선식품 데이터 — 상품마다 수확(harvest·harvestDetail), 포장, 배송 온도(temp), 실제 양(amount),
출하 월(season), 재구매율, 옵션별 차이(options[].note)를 둡니다. 카드에는 짧은 수확 문구 하나, 상세에는 전체를 보여 줍니다.
제철이 지난 상품은 지우지 않고 stock: 0 + restockNote("12월 첫 수확 예정")로 남겨, 찾아온 사람에게 언제 다시 살 수 있는지 알려 줍니다.
품절 상품은 목록·검색·농가 페이지에서 뒤로 가고, 장바구니에 남아 있어도 주문서로는 넘어가지 않습니다.
도착일은 규칙으로 계산 — 한국 시간 오후 2시 출고 마감, 냉장·냉동은 월목·상온은 월금 출고(주말 배송 사고 방지),
주문 후 수확·도정 상품은 준비일(leadDays)을 더합니다. 도착은 출고 다음 날입니다. 장바구니·주문서는 여러 농가 중 가장 늦은 도착일을,
농가 묶음 머리에는 그 묶음의 도착일을 보여 줍니다. 주문에는 출고일·도착 예정일을 저장해 주문 완료·내역의 진행 단계가 같은 날짜를 씁니다.
시간에 따라 바뀌는 화면 — 정적 HTML에 빌드한 날의 '내일 도착'이 굳지 않도록, 남은 시간·도착일은 클라이언트의 useNow()
(분 단위, 서버 렌더에서는 비움)로 그립니다. 홈의 '이번 달 제철'·'이번 주 농가'처럼 서버에서 정하는 것은 ISR(1시간)로 날짜를 따라갑니다.
추천에는 이유를 — "함께 사면 좋은 상품"은 직접 고른 짝(감귤↔감귤청, 삼겹살↔배추 등) → 같은 농가(한 상자로 함께 출발) → 지금 제철인 같은 분류 → 인기 순으로 채우고, 카드에 그 이유를 한 줄로 적습니다. 장바구니는 무료배송까지 모자란 금액을 채우는 상품을 맨 앞에 둡니다.
데모임을 숨기지 않는다 — 배송 API가 없으므로 주문 진행 단계는 실제로 바뀌지 않습니다. 진행 화면에 그 사실과 날짜 계산 근거(오후 2시 마감·주말 미출고)를 함께 적었습니다.
디자인 방향 — 하이브리드 — 고르는 화면(홈·상세·농가)은 사진이 이끄는 비주얼 중심, 사는 화면(장바구니·주문서·주문 내역)은
기능 중심으로 나눴습니다. 바탕은 노란 크림 대신 따뜻한 중립 오프화이트(cream-100 #F6F5F1), 제목은 ExtraBold 대신 Bold와 좁은 자간으로
정리했습니다. 모바일 홈 머리는 흰 면 하나에 배송지(우리집)와 검색을 묶고, 상세 사진은 화면 끝까지 채운 뒤 정보 시트가 그 위로 올라옵니다.
PC 상세는 위쪽 구매 영역이 화면에서 사라지면 오른쪽에 고정 구매 상자(옵션·수량·도착일·담기)를 띄웁니다 — 같은 버튼이 동시에 두 번 보이지 않게
IntersectionObserver로 전환합니다. 농가 묶음 머리는 농가 얼굴 + 두 줄(이름 / 포장 온도·도착)로 바꿔 좁은 화면에서도 넘치지 않습니다.
찾기·고르기 UX — 검색창은 WAI-ARIA combobox 자동완성(이름이 맞는 상품·농가, ↑↓·Enter·Esc)이며, 상품 데이터는 검색창을 처음
쓸 때 import()로 불러와 모든 화면의 첫 로딩에 싣지 않습니다. 검색어는 최근 검색어(localmat-search-history, 저장값 검증·탭 동기화)로 남습니다.
목록의 개수·필터·정렬 줄은 모바일·태블릿에서 헤더 아래에 붙어 있고, 적용한 필터는 하나씩 풀 수 있는 칩으로 보입니다.
담기·찜 알림에는 상품 사진과 옵션이 함께 뜨고, 목록의 담기 버튼은 담은 직후 잠깐 체크로 바뀝니다(useQuickAdd).
빈 장바구니는 최근 본 상품·많이 찾는 상품을 이어 보여 줘 막다른 화면이 되지 않습니다. 리뷰 쓰기는 모바일에서 아래 시트로 열리고 자주 쓰는 말을 눌러 이어 쓸 수 있습니다.
고르고 확인하는 UX — 장바구니는 상품마다 고르기(기본 전체 선택)·전체 선택·선택 삭제(되돌리기)를 지원하고, 금액·쿠폰·도착일·주문은
고른 상품 기준입니다. 해제한 항목 키만 unchecked로 저장해(저장값 검증 포함) 새로 담은 상품이 몰래 빠지는 일이 없습니다.
장바구니·주문서·주문 완료에는 구매 단계 표시가 있고, 주문 완료·상세는 큰 도착일과 단계 막대로 된 배송 요약 카드로 시작합니다.
모바일 상세에서 옵션 영역을 지나 내려가면 구매 바 위에 '선택 5kg · 2개 · 변경'이 나타나 아래 시트에서 바로 바꿀 수 있고,
상품 사진은 눌러 크게(핀치 확대) 볼 수 있습니다. 주문 내역은 상태별(전체·진행 중·배송 완료)로 모아 보고, 홈에는 지난번에 산 상품을 바로 다시 담는 줄이 있습니다.
공용 뒤로·앞으로 버튼 — public/mirae-history-nav.js는 여러 데모가 함께 쓰는 외부 스크립트라 수정하지 않습니다.
최대 z-index로 떠 있어서, 모달·필터 시트는 네이티브 <dialog>.showModal()(브라우저 최상위 레이어)로 띄워 가려지지 않게 했습니다.
| 경로 | 화면 |
|---|---|
/ | 홈 — (모바일) 배송지·검색 머리, 오늘 주문 시 도착일, 사진 카테고리, 최근 본 상품, 이번 달 제철, 다시 찾는 상품(재구매율 순), 이번 주 농가, 첫 구매 혜택 |
/products | 목록 — 모바일 카테고리 칩, 붙어 다니는 필터·정렬 줄, 적용 필터 칩, 가격/지역 필터(시트), 6종 정렬. 조건은 주소에 남아 뒤로 와도 유지 |
/products/[slug] | 상세 — (모바일) 꽉 찬 사진 + 정보 시트, (PC) 스크롤 시 오른쪽 고정 구매 상자, 수확·포장·도착·보관 요약, 옵션별 양 차이, 구역 바로가기(상품정보·생산자·배송·후기), 같은 농가 상품, 근거 있는 추천 |
/cart | 장바구니 — 상품 고르기·전체 선택·선택 삭제, 농가별 묶음·도착일, 옵션 변경, 품절 상품 분리, 무료배송 진행 바, 쿠폰 미리 보기, 무료배송 채우기 추천 |
/checkout | 주문서 — 1 배송 정보 · 2 주문 상품(농가별) · 3 할인(최대 할인 자동) · 4 결제 수단 |
/order-complete/[id] | 주문 완료(방금 결제) / 주문 상세(지난 주문) — 진행 단계, 농가별 상품, 같은 상품 다시 담기 |
/orders | 주문 내역 — 상태별 모아 보기, 출고·도착 예정, 진행 단계, 농가 바로가기, 다시 담기, 리뷰 쓰기(별점·10자 이상 검증) |
/search | 검색 — 자동완성, 최근 검색어·많이 찾는 검색어 순위·카테고리, 띄어 쓴/붙여 쓴 단어 모두 매칭, 이름 일치 우선 정렬 |
/mypage | 찜(되돌리기) · 최근 본 상품 · 쿠폰 · 배송지 |
/farms, /farms/[slug] | 농가 스토리 — 판매 상품 바로가기 |
레퍼런스임을 한눈에 알 수 있도록 최상단 리본, 모든 화면 하단의 브릿지 CTA(src/components/mirae/MiraeAILabSampleCTA.tsx,
링크·문구는 src/lib/mirae.ts), 푸터에 제작사를 노출합니다. 데모 사용자는 미래에이아이랩 김팀장입니다.
브랜드 로고 교체: node scripts/prepare-brand.mjs <원본로고파일> (배경 투명화 + 심볼 추출)
| 접속 기기 | 버튼 | 동작 |
|---|---|---|
| PC | 스마트폰에서 보기 | 현재 페이지를 기기 목업 iframe(375/390/430px)으로 띄워 실제 브레이크포인트로 확인 |
| 스마트폰 | PC 버전으로 보기 | viewport meta 폭을 1280px로 바꿔 데스크톱 레이아웃으로 렌더링 |
viewport meta는 Next 기본 태그 하나만 조작합니다(중복되면 브라우저마다 적용 폭이 달라짐). 선택은 localStorage에 저장되고 첫 페인트 전에 적용됩니다.
상품 26종 · 농가 8곳의 사진은 public/images/{products,farms}/{slug}.webp이며, 파일만 바꾸면 교체됩니다.
node scripts/optimize-images.mjs <원본디렉터리>로 WebP 변환(현재 34장 79.2MB → 4.2MB).
이미지가 없으면 카테고리별 플레이스홀더로 대체되고, 비율을 강제해 레이아웃이 흔들리지 않습니다.
Vercel에 연결하면 추가 설정 없이 배포됩니다. 공유 미리보기·사이트맵의 절대 주소는
NEXT_PUBLIC_SITE_URL(없으면 Vercel 프로덕션 주소)로 정해집니다.