-
Notifications
You must be signed in to change notification settings - Fork 1
ToDo
출처:
dodo-frontend(Vite + React 19 + Tailwind v4 + React Query + react-router v7, FSD 구조) 대상:dodo-app(Expo SDK 54 + expo-router + RN 0.81.5) 이 문서 하나로 원본 레포 없이 RN 작업이 가능하도록 작성됨. 최종 조사: 2026-07-14
커밋 1개(119a8ec Expo 기반 프로젝트 생성). 스캐폴딩은 이미 상당 부분 완료됨.
- Expo SDK 54 / RN 0.81.5 / React 19.1 / New Architecture 켜짐 (
newArchEnabled: true) - expo-router v6 (
app/디렉토리,typedRoutes+reactCompiler실험 기능 켜짐) -
@react-navigation/bottom-tabs설치 완료 → §3 하단 탭바 바로 구현 가능 -
@tanstack/react-queryv5,axios,zustand설치 완료 -
딥링크 스킴
dodoapp이미 설정됨 (app.json) → OAuth 리다이렉트에 사용 (§5) -
expo-web-browser,expo-linking설치 완료 → OAuth 흐름에 필요 -
expo-font,expo-image,expo-splash-screen,expo-haptics -
react-native-reanimatedv4 → 웹 CSS 애니메이션 대체용 (§1) - 경로 별칭
@/*→./*(tsconfig). 웹의@/=src/와 매핑이 다름에 주의 - husky + lint-staged + prettier + eslint (웹과 동일한 워크플로)
-
이미 이식된 파일:
shared/api/axios.ts,shared/config/{env,apiConfig,index}.ts,shared/lib/react-query/queryClient.ts
-
app/는 Expo 기본 템플릿 그대로 —(tabs)/index.tsx,(tabs)/explore.tsx,modal.tsx,hello-wave.tsx,parallax-scroll-view.tsx등은 전부 삭제 대상 -
constants/theme.ts는 Expo 기본 색상 → §1 DoDo 토큰으로 교체 필요 -
app.json에ios.bundleIdentifier/android.package없음 → §6 네이버 지도 콘솔 등록의 전제. 이걸 먼저 정해야 함 (예:com.dodo.app) -
env.ts가EXPO_PUBLIC_API_BASE_URL하나만 검증 → §10의 나머지 변수 추가 필요 -
axios.ts인터셉터가 껍데기 상태 (request/response 둘 다 그냥 통과) → 토큰 주입 + 401 갱신 로직 필요 (§5) - 미설치:
@mj-studio/react-native-naver-map(§6),@stomp/stompjs(§8),expo-secure-store(§5),expo-auth-session(§5),expo-crypto(§5),expo-image-picker(§7),react-native-svg+ transformer(§7)
원본은 모바일 우선으로 설계된 앱이 아니다. 데스크톱 웹 레이아웃이다.
- 본문 컨테이너:
max-w-5xl(1024px) — 모바일 고정폭 컨테이너가 아님 - 헤더: 상단 가로 nav. 반응형 분기 없음, 햄버거 없음, 하단 탭바 없음
- 브레이크포인트 사용량:
sm:192회 /lg:34회 /xl:5회 /md:2회
Tailwind는 모바일 우선이므로 "핸드폰 버전" = sm:/lg: 접두사를 제거한 기본 클래스가 전부다.
별도로 디자인된 모바일 화면이 존재하지 않는다.
결론: 화면 내부 UI(카드/폼/리스트/차트)는 기본 클래스 기준으로 그대로 옮기되, 네비게이션은 하단 탭바로 재설계한다. (결정 완료)
원본 src/app/styles/index.css의 @theme 블록에서 추출. RN 테마 파일로 그대로 옮길 것.
export const colors = {
brand: '#e56c31',
brandForeground: '#fefefe',
secondary: '#f6b93b',
secondaryForeground: '#fefefe',
// 앱 전역 배경 (모든 레이아웃이 bg-neutral-50)
background: '#fafafa', // neutral-50
surface: '#ffffff',
border: '#e5e5e5', // neutral-200
textPrimary: '#171717', // neutral-900
textSecondary: '#404040', // neutral-700
// 지오펜스 상태 색
fenceInside: '#22c55e', // green-500
fenceOutside: '#ef4444', // red-500
fenceActiveLabel: '#16a34a',// green-600
fenceIdleLabel: '#6b7280', // gray-500
};
export const fonts = {
sans: 'Pretendard', // 본문
display: 'GmarketSans', // 제목 (GmarketSansTTFBold.ttf, weight 700)
};폰트 주의: 웹은 Pretendard를 jsDelivr CDN에서, GmarketSans를 /fonts/GmarketSansTTFBold.ttf에서 로드한다.
RN에서는 둘 다 번들에 포함해야 한다 (expo-font 또는 네이티브 asset 링크).
애니메이션: 웹은 CSS @keyframes 5종 사용 —
skeleton-shimmer(스켈레톤 반짝임 1.4s), enter-soft(진입 시 y+10px 페이드인 0.45s),
chart-line-draw(라인 차트 stroke 그리기 1.1s), chart-point-pop, chart-fade-up.
→ RN에서는 Reanimated로 재작성. 스켈레톤은 react-native-skeleton-placeholder 등으로 대체 가능.
원본 src/app/router.tsx 기준. 총 20개 화면.
| 웹 라우트 | 화면 | 비고 |
|---|---|---|
/auth |
로그인 | 소셜 로그인 (Google/Naver) |
/auth/callback/:provider |
OAuth 콜백 | RN에서는 불필요/재설계 (§5 참고) |
/auth/signup |
회원가입 | 닉네임/지역/프로필 설정 |
/auth/family/join |
가족 참여 | 초대코드 입력. 인증 필요 |
| 웹 라우트 | 화면 | 인증 | 탭 |
|---|---|---|---|
/ |
메인 홈 | 선택 | 홈 |
/walk |
산책 (지도) | — | 산책 |
/community |
커뮤니티 목록 | — | 커뮤니티 |
/community/my |
내 활동 | 필요 | 커뮤니티 |
/community/new |
글 작성 | 필요 | 커뮤니티 |
/community/:boardId |
글 상세 | 필요 | 커뮤니티 |
/community/:boardId/edit |
글 수정 | 필요 | 커뮤니티 |
/my |
마이도도 | 필요 | 마이 |
/my/notifications |
알림 설정 | 필요 | 마이 |
/my/withdrawal |
회원 탈퇴 | 필요 | 마이 |
/my/pets/new |
반려동물 등록 | 필요 | 마이 |
/my/pets/:petId |
반려동물 상세 | 필요 | 마이 |
/my/pets/:petId/edit |
반려동물 수정 | 필요 | 마이 |
/my/pets/:petId/notes |
특이사항 | 필요 | 마이 |
/my/pets/:petId/weight |
체중 기록 (차트) | 필요 | 마이 |
* |
404 | — | — |
메인 홈 특이사항: 비로그인 시 로고만 렌더(MainPage.tsx), 로그인 시 LoggedInHome
(반려동물 선택형 건강 레포트).
원본 헤더(src/widgets/header/ui/Header.tsx)는 로고 + 가로 링크 3개(gap-8) + 프로필 아바타 드롭다운.
375px에서 폭이 거의 꽉 차 찌그러진다. 하단 탭바로 대체한다.
Root
├─ AuthStack (미인증)
│ └─ Login → Signup → FamilyJoin
└─ MainTabs (인증)
├─ 홈 → HomeStack (Main)
├─ 산책 → WalkStack (Walk) ※ 전체화면 지도, 탭바 위에 오버레이
├─ 커뮤니티 → CommunityStack (List → Detail / New / Edit / MyActivity)
└─ 마이도도 → MyStack (MyDodo → Notifications / Withdrawal / Pet*)
-
프로필 드롭다운(
ProfileMenu.tsx) → "마이도도" 탭 상단 프로필 섹션으로 흡수 -
비로그인 상태: 웹은 헤더가
GuestNav(서비스 소개/시작하기)로 바뀜. RN은 AuthStack으로 분기하거나, 탭 진입 시 로그인 모달을 띄우는 방식 중 택1 -
산책 화면: 원본
AppLayout이/walk만isFullBleed로 헤더를 제거함 (AppLayout.tsx:13). RN에서도 전체화면 지도로 유지할 것
| 원본 경로 | 판정 | 비고 |
|---|---|---|
shared/api/axios.ts |
거의 그대로 | 인터셉터의 토큰 저장소만 교체 |
features/*/api/* |
그대로 | 순수 axios 호출 |
features/*/model/* (React Query 훅) |
거의 그대로 | RQ v5는 RN 동일 동작 |
features/*/model/types.ts |
그대로 | |
shared/lib/socket/stompClient.ts |
거의 그대로 | ✅ SockJS 미사용, 순수 WebSocket |
shared/lib/regions, lib/api/errorMessage
|
그대로 | |
features/*/ui, pages/*/ui (97개 tsx) |
전면 재작성 | Tailwind className → RN 스타일 |
shared/ui/* (Modal, Toast, Skeleton, Spinner) |
재작성 | RN 대응물로 |
shared/lib/auth/token.ts |
재작성 | §5 |
features/auth/lib/oauth.ts |
재작성 | §5 |
shared/lib/naver-map/* |
재작성 | §6 — 최대 리스크 |
shared/api/files.ts |
재작성 | §7 |
app/router.tsx, app/layouts/*
|
재작성 | §3 |
요약: 데이터/로직 레이어는 대부분 재사용 가능. UI 레이어 97개 파일이 실제 작업량.
-
토큰 저장:
accessToken은 localStorage,refreshToken은 sessionStorage (shared/lib/auth/token.ts). 사용처: localStorage 37회, sessionStorage 12회 - 저장 키:
accessToken,dodo.refreshToken,profileUrl,nickname,region,notificationEnabled,accessTokenExpiresAt,accessTokenTtlMs -
만료 처리:
ACCESS_TOKEN_REFRESH_BUFFER_MS = 60_000— 만료 60초 전 갱신 -
상태 전파:
window.dispatchEvent(new Event('dodo:auth-state-change'))— 커스텀 이벤트 -
OAuth:
window.location.href = buildSocialAuthUrl(provider)로 전체 페이지 리다이렉트. CSRF용state를 sessionStorage에 저장 후 콜백에서 검증
| 항목 | 대응 |
|---|---|
| 토큰 저장 |
expo-secure-store (리프레시 토큰은 반드시 여기) / AsyncStorage(비민감 정보) |
| sessionStorage 의미 | RN엔 세션 개념이 없음 → 앱 시작 시 초기화하는 in-memory 저장소로 흉내내거나, SecureStore로 승격 (보안 정책 재검토 필요) |
crypto.randomUUID() |
expo-crypto 로 교체 |
| OAuth 리다이렉트 |
expo-auth-session + expo-web-browser. 딥링크 스킴 필요 |
AuthCallbackPage |
RN에선 화면 불필요 — expo-auth-session 결과 핸들러로 흡수 |
dodo:auth-state-change 이벤트 |
Context/Zustand 등 상태 저장소로 교체 |
redirectToLogin() |
네비게이션 reset() 으로 교체 |
백엔드 협의 필요: VITE_OAUTH_REDIRECT_URI가 웹 URL 기준이다.
RN 딥링크 스킴(dodo://auth/callback/...)을 OAuth 콘솔(Google/Naver)과 백엔드 양쪽에 등록해야 한다.
이거 안 되면 로그인 자체가 막힌다 — 가장 먼저 확인할 것.
결론: WebView 우회나 지도 교체 불필요. 네이티브 SDK 래퍼로 1:1 대응된다.
shared/lib/naver-map/loadNaverMap.ts — <script src="https://oapi.map.naver.com/openapi/v3/maps.js?ncpKeyId=...">
를 document.head에 주입. RN에서는 불가능 → 이 파일은 폐기하고 라이브러리로 대체.
features/walk-fence/ui/WalkMap.tsx (196줄)가 하는 일:
- 지도 생성 + 클릭 리스너 (좌표 콜백)
- 기존 울타리 원(Circle) 다중 렌더
- 펫 이름 HTML 라벨 마커 (
petLabelContent— innerHTML 문자열) - 생성/수정 미리보기 원 (draftCenter + draftRadius)
-
실시간 위치 점 마커 — 울타리 안=
#22c55e/ 밖=#ef4444(liveMarkerContent) - 기본 중심: 경복궁
{ lat: 37.5796, lng: 126.977 }
| 웹 (naver.maps) | RN (@mj-studio/react-native-naver-map) |
|---|---|
naver.maps.Circle (울타리/미리보기 원) |
NaverMapCircleOverlay — radius(미터), color, outlineWidth, outlineColor
|
| 지도 클릭 리스너 | onTapMap?: (params: Coord & { x, y }) => void |
naver.maps.Marker + HTML 라벨 |
NaverMapMarkerOverlay — 커스텀 React 컴포넌트 마커 지원 |
| 지도 인스턴스 ref 조작 |
NaverMapViewRef — animateCameraTo, animateRegionTo, screenToCoordinate 등 |
| — |
onInitialized, onCameraChanged, onCameraIdle
|
-
radius가 웹과 동일하게 미터 단위 → 지오펜스 반경 로직 변환 없이 재사용 - HTML 문자열 마커 2종(
petLabelContent,liveMarkerContent)만 RN 컴포넌트로 재작성. 이 과정에서escapeHtml()XSS 방어 함수는 불필요해져 삭제 가능 - 그 외 오버레이도 제공: Polygon, Polyline, Path, MultiPath, ArrowheadPath, Ground
- 최신
2.9.0(2026-05-28 배포), 최근 커밋 2026-06-05 - GitHub 158 stars / 열린 이슈 2개 / MIT / 주간 다운로드 2,323
- peerDeps:
react: *,react-native: *(버전 제약 없음) -
Expo config plugin 지원 →
app.json설정만으로 iOS/Android 네이티브 파일 직접 수정 불필요
npx expo install @mj-studio/react-native-naver-map- 웹은
ncpKeyId파라미터 사용 = 네이버 신규 독립 Maps 상품 키 형식. 라이브러리도 2.4.x+ 부터 이 신규 상품을 지원하므로 방식이 일치한다. - 단, 네이버 클라우드 콘솔에 Android 패키지명 / iOS Bundle ID 등록 필요. 등록된 값과 앱의 실제 값이 일치해야 인증이 통과한다. → OAuth 딥링크 등록과 함께 처리할 것.
- https://github.com/mym0404/react-native-naver-map
- https://rnnavermap.mjstudio.net/ (공식 문서)
- https://www.npmjs.com/package/@mj-studio/react-native-naver-map
-
파일 업로드 (
shared/api/files.ts):File/FormData기반, multipart 필드명files(복수). RN에서는expo-image-picker→{ uri, name, type }형태로 FormData 구성.POST /files/upload→{ imageUrls: string[] }. 커뮤니티/프로필 공용 -
window.38회 /document.21회 사용 — 대부분 Modal/Toast/드롭다운의 이벤트 리스너(mousedown,Escape)와 리다이렉트. RN 대응물로 교체 -
SVG:
vite-plugin-svgr로?reactimport 사용 (Logo_light.svg?react). RN은react-native-svg+react-native-svg-transformer필요 -
경로 별칭:
@→src(vite.config.ts). RN은 babel module-resolver / tsconfig paths로 재설정
shared/lib/socket/stompClient.ts:
-
@stomp/stompjsClient,brokerURL: env.WS_URL(wss://). SockJS 미사용 → RN 호환 -
reconnectDelay: 5000,heartbeatIncoming/Outgoing: 10000 -
beforeConnect에서 매 연결 직전 최신 액세스 토큰을Authorization: Bearer ...헤더에 주입 (재연결 시 갱신 토큰 반영 목적)
features/walk-fence/model/useLiveLocation.ts:
- 구독 토픽:
/sub/fence/location/{petId} - 메시지:
{ payload: LiveLocationPayload }— 래핑된 payload만 꺼내 사용 -
petId변경 시 재연결, 언마운트 시deactivate()
주의: 위치는 기기(펫 트래커)가 서버로 보내고 앱은 구독만 한다. 앱 자체의 geolocation 사용처는 0건이다. RN에서 위치 권한이 필요하다고 지레 판단하지 말 것.
RN 추가 고려: 백그라운드 진입 시 WebSocket이 끊긴다. AppState 기반 재연결 처리 필요 (웹엔 없던 문제).
- Base:
VITE_API_BASE_URL, timeout 10초 (shared/config/apiConfig.ts) - 인증:
Authorization: Bearer {accessToken}
인증/유저
POST /auth/logout
GET /users/me
PATCH /users/me
PUT /users/me/profile
GET /users/nickname/check
PATCH /users/me/setting/notification
POST /users/me/withdrawal/email
DELETE /users/me
메인
GET /main
반려동물
GET /pets/list
POST /pets
GET /pets/{petId}
PATCH /pets/{petId}
DELETE /pets/{petId}
GET /pets/{petId}/significant
POST /pets/significant
PATCH /pets/significant/{noteId}
DELETE /pets/significant/{noteId}
GET /pet/{petId}/weight/history
POST /pet/{petId}/weight
PATCH /pet/{petId}/weight/{weightId}
DELETE /pet/{petId}/weight/{weightId}
⚠️ 체중 API만/pet/(단수), 나머지는/pets/(복수). 원본 그대로임 — 오타 아님.
가족
POST /pets/{petId}/invitation-code
GET /pets/family/applications
GET /pets/family/pending-users
POST /pets/family/approval
GET /pets/family/blocked-users
DELETE /pets/family/block
산책/지오펜스
GET /fence/boundaries
GET /fence/{fenceId}/boundary
GET /fence/{petId}/status
POST /fence/range
PATCH /fence/{fenceId}/range
PATCH /fence/{fenceId}/toggle
커뮤니티
GET /boards
POST /boards
GET /boards/me
GET /boards/{boardId}
PATCH /boards/{boardId}
DELETE /boards/{boardId}
POST /boards/temp-save
GET /boards/temp-save/{sessionKey}
GET /comments/{boardId}
GET /comments/me
POST /comments
PATCH /comments/{commentId}
DELETE /comments/{commentId}
POST /reactions/board
PATCH /reactions/board/{boardId}
DELETE /reactions/board/{boardId}
파일
POST /files/upload (multipart, 필드명: files)
웹(shared/config/env.ts)은 6개를 요구하며 누락 시 throw한다.
| 웹 | RN 대응 | 비고 |
|---|---|---|
VITE_API_BASE_URL |
EXPO_PUBLIC_API_BASE_URL |
✅ 이미 이식됨 |
VITE_WS_URL |
EXPO_PUBLIC_WS_URL |
추가 필요 |
VITE_GOOGLE_CLIENT_ID |
EXPO_PUBLIC_GOOGLE_CLIENT_ID |
iOS/Android용 클라이언트 ID 신규 발급 필요 |
VITE_NAVER_CLIENT_ID |
EXPO_PUBLIC_NAVER_CLIENT_ID |
동일하게 앱용 재발급 검토 |
VITE_OAUTH_REDIRECT_URI |
— |
딥링크로 교체. 스킴 dodoapp 이미 설정됨 → dodoapp://auth/callback/{provider} (§5) |
VITE_NAVER_MAP_CLIENT_ID |
app.json config plugin client
|
신규 Maps 상품 키 형식 동일. 콘솔에 앱 패키지명/Bundle ID 등록 필요 (§6) |
블로커 2건 모두 해소됨. OAuth 딥링크 등록 가능 확인(§5), 지도는 네이티브 SDK 래퍼로 결정(§6). 스캐폴딩도 대부분 완료 상태(§-1) — 아래 2번은 대폭 축소됨.
-
[선행/블로킹]
app.json에ios.bundleIdentifier/android.package확정 후, OAuth 딥링크(Google/Naver 콘솔 + 백엔드) + 네이버 지도(앱 패키지명/Bundle ID) 등록. → 앱 식별자가 정해져야 나머지가 다 굴러감. 나중에 바꾸면 콘솔 재등록 - 정리 & 토큰 — Expo 템플릿 잔재 삭제,
constants/theme.ts를 §1 DoDo 토큰으로 교체, Pretendard/GmarketSans 번들(expo-font) - 하단 탭 네비게이션 골격 (§3) —
(tabs)/_layout.tsx재작성. bottom-tabs는 이미 설치됨 - 데이터 레이어 —
axios.ts인터셉터 채우기 + 토큰 저장소(§5) +features/*/api,features/*/model이식 - 공용 UI 재작성 — Modal / Toast / Skeleton / Spinner
- 화면 이식 — 마이도도 → 커뮤니티 → 홈 → 산책 순 (산책이 가장 복잡)
- STOMP 연결 + AppState 재연결 (§8)
| 레포 | 별칭 | 실제 경로 |
|---|---|---|
dodo-frontend (웹) |
@/ |
src/ |
dodo-app (RN) |
@/ |
./ (루트) |
웹의 @/shared/api/axios → RN의 @/shared/api/axios 로 문자열은 동일하게 유지되지만
디스크 상 위치는 src/shared/... vs shared/... 로 다르다. 복붙 시 import 경로는 대체로 그대로 동작한다.
단, RN은 expo-router 기반이라 app/이 라우팅 전용이고 components/, hooks/, constants/가
루트에 있다. FSD(features/, widgets/, shared/)와 섞이므로 디렉토리 규칙을 먼저 합의할 것.
현재 shared/만 FSD식으로 만들어져 있다.
c:\Users\soo\dodo-frontend (branch: develop)
파일이 필요하면 /add-dir c:\Users\soo\dodo-frontend 로 붙일 수 있음.