尚未偵測到更新。GitHub 發布版本後會顯示在這裡。
個人 GitHub 帳號下公開儲存庫的擁有者可以登入並驗證所有權,自行管理介紹,無需管理員核准。
登入並產生驗證檔案,在 30 分鐘內將其提交至此儲存庫的預設分支。其他分支或分叉儲存庫中的檔案無效。然後在下方檢查檔案,即可無需管理員核准直接管理專案。請保留檔案以維持管理權限。
如需登入,將前往登入頁面。登入後請回到此處,再次點擊所選的驗證按鈕。
在專案資料夾中執行 /nomorevibe verify,即可驗證所有權並自行管理介紹。
一分鐘完成驗證正在載入完整 README。 以下為已儲存的摘錄。
친구들과 여행 날짜·취향·숙소를 함께 조율하고 하나의 일정으로 합의하는 협업 여행 플래너 여행을 준비할 때 단체 채팅방에서 각자의 가능한 날짜를 다시 묻고, 취향을 정리하고, 숙소 링크를 주고받으며 의견을 모으는 과정을 하나의 흐름으로 만들었습니다. 가입 절차 없이 초대 링크 + 닉네임으로 참여할 수 있고, 여러 사용자가 동시에 응답하거나 투표하면 Supabase Realtime을 통해 같은 여행 화면에 반영됩니다. 여러 명이 함께 여행을 준비할 때 생기는 핵심 문제를 세 가지로 정의했습니다. • 각자 가능한 날짜가 달라 공통 일정이 한눈에 보이지 않는 문제 • 여행 스타일과 우선순위를 말로만 공유해 취향 차이를 파악하기 어려운 문제 • 숙소 후보와 투표 결과가 여러 채팅에 흩어져 결정 과정이 길어지는 문제 TRIPTUNE은 이를 응답 → 그룹 합의 → 숙소 후보 → 투표 → 최종 확정의 단계형 UX로 정리했습니다. 주최자가 여행 이름과 후보 기간을 설정하면 초대 링크가 생성됩니다. 참여자는 별도 회원가입 없이 닉네임만 입력해 같은 여행에 참여합니다. 각 날짜를 가능 / 미정 / 불가로 표시하고 여행 취향을 입력합니다.檢視完整 README ↗
친구들과 여행 날짜·취향·숙소를 함께 조율하고 하나의 일정으로 합의하는 협업 여행 플래너
여행을 준비할 때 단체 채팅방에서 각자의 가능한 날짜를 다시 묻고, 취향을 정리하고, 숙소 링크를 주고받으며 의견을 모으는 과정을 하나의 흐름으로 만들었습니다.
가입 절차 없이 초대 링크 + 닉네임으로 참여할 수 있고, 여러 사용자가 동시에 응답하거나 투표하면 Supabase Realtime을 통해 같은 여행 화면에 반영됩니다.
여러 명이 함께 여행을 준비할 때 생기는 핵심 문제를 세 가지로 정의했습니다.
TRIPTUNE은 이를 응답 → 그룹 합의 → 숙소 후보 → 투표 → 최종 확정의 단계형 UX로 정리했습니다.
주최자가 여행 이름과 후보 기간을 설정하면 초대 링크가 생성됩니다.
참여자는 별도 회원가입 없이 닉네임만 입력해 같은 여행에 참여합니다.
각 날짜를 가능 / 미정 / 불가로 표시하고 여행 취향을 입력합니다.
응답을 저장하면 불필요하게 홈으로 돌아가지 않고 바로 그룹 합의 화면으로 이동해, 자신의 응답이 전체 결과에 어떻게 반영됐는지 확인할 수 있도록 구성했습니다.
참여자의 응답을 집계해 다음 정보를 한 화면에서 비교할 수 있습니다.
자유 입력 의견은 자동 점수에 섞지 않고 별도로 보여줘, 정량화하기 어려운 요구도 놓치지 않도록 했습니다.
합의된 일정 이후 숙소 후보를 등록하고 투표합니다.
전체 숙소 예약뿐 아니라 여러 객실을 조합하는 경우도 입력할 수 있으며, 객실 수·총 수용 인원·총액·1인당 비용을 계산해 보여줍니다.
참여 인원보다 수용 인원이 부족한 예약안은 투표 후보로 진행되지 않도록 막았습니다.
숙소까지 확정되면 여행이 완료됩니다.
하지만 실제 여행 계획은 확정 후에도 바뀔 수 있기 때문에, 주최자는 전체 데이터를 초기화하지 않고
중 필요한 단계만 선택해 재개할 수 있습니다.
단기적인 여행 조율 서비스에 별도 회원가입은 진입 장벽이 크다고 판단해 Supabase Anonymous Auth를 사용했습니다.
초대 링크를 받은 사용자는 닉네임 입력만으로 바로 참여할 수 있습니다.
모든 참여자가 같은 화면을 사용하지만 행동 권한은 다르게 설계했습니다.
UI에서 버튼을 숨기는 것에 그치지 않고 서버 RPC와 RLS에서도 권한을 확인하도록 구성했습니다.
여행 계획은 한 번에 끝나는 흐름이 아니기 때문에 이전 단계의 정보를 최대한 유지하도록 설계했습니다.
예를 들어 날짜를 다시 조율하는 동안 숙소 단계는 잠기지만 기존 숙소 후보 자체는 삭제하지 않습니다.
Realtime 이벤트로 다른 사용자의 응답과 투표를 즉시 반영합니다.
구독 오류나 이벤트 유실로 화면이 오래 멈추는 경우를 줄이기 위해 구독 상태를 감지해 재연결하고, 화면이 활성화된 동안 주기적으로 서버 상태와 다시 맞추는 보정 로직도 추가했습니다.
날짜 집계, 연속 일정 후보, 취향 평균, 충돌 판정, 투표 집계처럼 화면과 독립적으로 계산할 수 있는 로직은 컴포넌트 내부에 두지 않고 순수 함수로 분리했습니다.
UI
↓
trip domain logic
↓
Supabase
이 구조로 계산 로직을 단위 테스트하기 쉬워졌고, UI 변경과 비즈니스 규칙 변경의 영향을 분리했습니다.
초기에는 탭이나 화면을 이동할 때 동일한 여행 데이터를 반복해서 조회해 체감 지연이 있었습니다.
이를 줄이기 위해
을 적용했습니다.
데이터 조회 실패를 단순히 ‘여행이 없음’으로 처리하면 사용자가 실제 원인을 알 수 없었습니다.
여행이 존재하지 않는 상태와 일시적인 서버 오류를 분리해, 실패 시 다시 시도할 수 있는 오류 화면을 보여주도록 수정했습니다.
숙소 링크에서 OG 메타데이터를 읽어 후보 정보를 보여주는 API를 구현했습니다.
외부 URL을 서버에서 요청하는 기능인 만큼 내부 주소 접근을 막는 SSRF 방어 로직을 함께 적용했습니다.
익명 로그인은 ‘여행 참여’ 시점에 생성되는데, 초대 정보를 가져오는 RPC가 인증된 사용자에게만 허용되어 있었습니다.
초대 토큰으로 필요한 정보만 반환하는 함수의 권한을 조정해 세션이 없는 최초 방문자도 초대 화면을 볼 수 있도록 수정했습니다.
구독 상태를 확인하지 않아 채널 오류나 timeout이 발생해도 UI가 그대로 멈춰 있었습니다.
구독 상태를 감지해 백오프로 재연결하고, 재연결 후 놓친 데이터를 다시 조회하도록 변경했습니다.
최종 확정을 ‘끝 상태’로만 처리하면 실제 여행 계획 변경에 대응하기 어려웠습니다.
기존 데이터를 보존하면서 필요한 단계만 되돌릴 수 있도록 조율 재개 기능을 설계하고, 서버 함수에서 권한과 현재 상태를 함께 검증하도록 구현했습니다.
| 영역 | 사용 기술 |
|---|---|
| Framework | Next.js 16 · App Router |
| Language | TypeScript |
| UI | React 19 · Tailwind CSS v4 · Lucide React |
| Form | React Hook Form · Zod |
| Backend / DB | Supabase · Postgres |
| Auth | Supabase Anonymous Auth |
| Realtime | Supabase Realtime |
| Security | Row Level Security · RPC |
| Test | Vitest · Playwright |
| CI | GitHub Actions |
| Deploy | Vercel |
테스트 자체가 프로젝트의 목적은 아니지만, 여러 사용자의 상태가 연결되는 서비스라 회귀 오류를 막기 위해 핵심 흐름을 자동화했습니다.
테스트를 실제 환경에서 돌리는 과정에서 초대 RPC 권한, Realtime 갱신, 오류 상태 처리 문제를 발견하고 제품 코드까지 수정했습니다.
| 명령어 | 설명 |
|---|---|
npm run test:unit | Vitest 단위 테스트 |
npm run test:e2e | Playwright E2E |
npm run test:e2e:ui | Playwright UI 모드 |
npm run test:all | 타입 → 린트 → 단위 테스트 → 빌드 → E2E |
npm run typecheck | TypeScript 타입 검사 |
src/
├─ app/
│ ├─ page.tsx # 여행 생성
│ ├─ join/[inviteToken]/ # 초대 링크 참여
│ ├─ trip/[tripId]/
│ │ ├─ (tabs)/ # 홈 · 합의 · 투표
│ │ └─ respond/ # 날짜 · 취향 입력
│ └─ api/link-preview/ # 숙소 링크 메타데이터
│
├─ components/
│ ├─ ui/ # Button · Modal · BottomSheet 등
│ └─ trip/ # 여행 도메인 UI
│
└─ lib/
├─ trip/ # 합의 · 숙소 · 포맷 계산 로직
├─ validation/ # 입력 검증
└─ supabase/ # browser / server client · DB 타입
supabase/
├─ migrations/ # DB 스키마 · RPC · RLS
└─ tests/ # 서버 함수 SQL 테스트
e2e/
├─ scenario-*.spec.ts # 핵심 사용자 흐름
└─ support/ # 테스트 액터 · 환경 · 정리 유틸
npm install
.env.example을 참고해 .env.local을 생성합니다.
NEXT_PUBLIC_SUPABASE_URL=your_supabase_url
NEXT_PUBLIC_SUPABASE_ANON_KEY=your_supabase_anon_key
supabase/migrations/의 SQL을 번호 순서대로 적용하고 Anonymous Sign-Ins를 활성화합니다.
npm run dev
http://localhost:3000에서 확인할 수 있습니다.
Vercel에 배포하고 Production / Preview 환경에 아래 값을 설정합니다.
NEXT_PUBLIC_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY
데이터베이스 스키마와 접근 권한은 Supabase의 migration / RLS / RPC에서 관리하며, 앱 코드에는 service role key를 사용하지 않습니다.