Finders โ ํ๋ฆ์นด๋ฉ๋ผ ํ์์ ํ์ยทํ์/์ธํ ์ฃผ๋ฌธยท์ฌ์ง ํผ๋ ์๋น์ค. React SPA๋ฅผ Capacitor๋ก ๊ฐ์ผ ํ์ด๋ธ๋ฆฌ๋ ์ฑ (Web + Android + iOS). ๋ชจ๋ฐ์ผ ์ ์ฉ UI (max-width 480px).
์ด ๋ฌธ์๋ ์ฝ๋๋ง ์ฝ์ด์๋ ์ ์ ์๋ ๊ท์นยท๊ด์ตยทํจ์ ๋ง ๋ด๋๋ค. ์ฝ๋์์ ํ์ ๊ฐ๋ฅํ ์ ๋ณด๋ ์ ์ง ์๋๋ค.
์ด ๋ ํฌ์ .claude/skills/tdd-clean-arch/์ ํฌํจ๋์ด ์๋ค. ์ฝ๋ ๋ณ๊ฒฝ ๋๊ตฌ(Edit/Write)๋ฅผ ์ฐ๊ธฐ ์ ์ Skill ๋๊ตฌ๋ก ๋จผ์ ํธ์ถํ๋ค.
- ๋์ ์์
(์์ธ ์์): ์ปดํฌ๋ํธยทํ
ยท์ ํธยทํ์ด์งยท์คํ ์ด์ ์์ฑ/์์ /๋ฆฌํฉํ ๋ง, API ํจ์(
apis/)ยทQuery ํ (hooks/)ยทZustand ์คํ ์ด(store/) ์์ , ํผยท๋ฐธ๋ฆฌ๋ฐ์ด์ ยท๋ผ์ฐํ ยท๋ ์ด์์ ๋ณ๊ฒฝ. ์ฌ์ฉ์๊ฐ ์คํฌ์ ์ธ๊ธํ์ง ์์๋ ํด๋น๋๋ฉด ์๋ ๋ก๋. - ๋จ์ ์ง๋ฌธ ๋ต๋ณยทํ์ผ ํ์ยท์ค๋ช ๋ง ํ๋ ๊ฒฝ์ฐ๋ ์คํฌ ์์ด ์งํ ๊ฐ๋ฅ.
- ์คํฌ ์ฌ์ฉ ๋ถ๊ฐ ํ๊ฒฝ์ด๋ฉด ์ฌ์ฉ์์๊ฒ ์๋ฆฌ๊ณ ์งํ ์ฌ๋ถ ํ์ธ.
- ํต์ฌ ์์น (์์ธ๋ ์คํฌ ๋ด๋ถ): Karpathy ํ๋ ์์น(์๊ฒ ์์, ์์ฒญ ๋ฒ์๋ง, ๊ธฐ์กด ์คํ์ผ ์ ์ง, ๋ถํ์ํ ์ถ์ํ ๊ธ์ง) ยท feature ํด๋ โ Domain/Application/Infrastructure/Presentation ๋ ์ด์ด ๋งคํ ยท TDD๋ vitest๊ฐ ์ค์น๋ ๊ฒฝ์ฐ์๋ง ์ ์ฉ(์คํฌ์ด ์๋ ํ์ โ vitest ์ค์น๋์ด ์์. ํ ์คํธ๋ ๋์ ํ์ผ ์์ co-locate).
- ์๋ ์๋ ๋ฌธ๊ตฌ(์นดํผ)๋ฅผ ์ถ๊ฐํ์ง ์๋๋ค. ์๋ฌ ๋ฉ์์งยท์๋ด ๋ฌธ๊ตฌยท๋ผ๋ฒจ์ ์ง์ด๋ด์ง ๋ง ๊ฒ.
- Figma๊ฐ ์ฃผ์ด์ง๊ฑฐ๋ ์ฌ์ฉ์๊ฐ ์ง์ ์๊ตฌํ ๊ฒฝ์ฐ๊ฐ ์๋๋ฉด ๊ธฐ์กด ๋ ์ด์์ยท์คํ์ผ์ ๊ทธ๋๋ก ์ ์งํ๋ค.
- ๋ชจ์ ์์ ์ ์ ์ ์ธํ(๋ชจ์ยท์ยทํฌ๊ธฐยท๋ฌธ๊ตฌ)์ ๊ฑด๋๋ฆฌ์ง ๋ง๊ณ ๋ชจ์ ๋ง ์ถ๊ฐํ๋ค.
- ์ค์ผ๋ ํค ๋ฑ ์ ์๊ฐ ์์๊ฐ ๋ถ๊ฐํผํ๋ฉด ์์๋ก ๋์์ธํ์ง ๋ง๊ณ ๋จผ์ ์ฌ์ฉ์/๋์์ด๋์๊ฒ ํ์ธํ๋ค.
- Claude๋ git add/commit/push๋ฅผ ์ง์ ํ์ง ์๋๋ค. ๊ตฌํ + ๊ฒ์ฆ(
pnpm lint,pnpm build)๊น์ง ํ๊ณ ๋ฉ์ถ๋ค. ์ปค๋ฐ์ ๊ฐ๋ฐ์๊ฐ ์ง์ ํ๋ค. - ์ปค๋ฐ์ ๋ ผ๋ฆฌ/ํ๋ฉด ๋จ์๋ก ์๊ฒ ์ชผ๊ฐ ๋ค โ ์๋ก ๋ค๋ฅธ ํ๋ฉดยท๊ธฐ๋ฅ์ ํ ์ปค๋ฐ์ ๋ฌถ์ง ์๋๋ค. ์ปค๋ฐ ๋ฉ์์ง๋ฅผ ์ ์ํ ๋๋ ์ด ๋จ์ ๊ธฐ์ค.
- PR ์ ๋ฆฌ๋ทฐ์์ ๋ฐ๊ฒฌํ ๋ฌธ์ ๋ ์ค์ ๋ก ์ฌํยท๊ฒ์ฆ๋ ๊ฒ๋ง ์์ ํ๋ค. ์ถ์ธก์ฑ ๋ฐ๊ฒฌ์ผ๋ก ์ฝ๋๋ฅผ ๋ฐ๊พธ์ง ์๋๋ค.
ํจํค์ง ๋งค๋์ ๋ pnpm ๊ณ ์ (npm/yarn ๊ธ์ง). pre-commit ํ (Husky + lint-staged)์ด eslint --fix + prettier๋ฅผ ์๋ ์คํํ๋ค.
pnpm dev # ๊ฐ๋ฐ ์๋ฒ
pnpm build # tsc -b && vite build
pnpm build:dev # --mode development ๋น๋ (QA/dev ์๋ฒ์ฉ)
pnpm lint # ESLint (--max-warnings 0)
pnpm lint:fix / format / format:check
pnpm test # vitest watch (test:run = 1ํ ์คํ, test:coverage)
pnpm cap:sync # build ํ dist/๋ฅผ android+ios ๋ค์ดํฐ๋ธ ํ๋ก์ ํธ์ ๋ณต์ฌ
pnpm cap:sync:android / cap:sync:ios # ๊ฐ :dev ๋ณํ์ build:dev ๊ธฐ๋ฐ
pnpm cap:open:android / cap:open:ios # Android Studio / Xcode ์ด๊ธฐ- ์น ์ฝ๋ ์์ ์
cap:sync๋ฅผ ํด์ผ ๋ค์ดํฐ๋ธ ์ฑ์ ๋ฐ์๋๋ค (webDir: dist๋ณต์ฌ ๋ฐฉ์). - pnpm์
node-linker=hoisted๊ณ ์ (.npmrc) โ isolated ๋ ์ด์์์ผ๋ก ์ค์น๋ ์ํ์์cap sync๋ฅผ ๋๋ฆฌ๋ฉด iOSPodfile.lock์.pnpmํด์ ๊ฒฝ๋ก๊ฐ ๋ฐํ ๋ค๋ฅธ ๋จธ์ ์์ ๊นจ์ง๋ค(#321). Podfile.lock์ ๊ฒฝ๋ก๋ ํญ์../../node_modules/{pkg}ํํ์ฌ์ผ ์ ์.
React 19 ยท Vite 7 ยท TypeScript strict ยท Tailwind CSS 4 ยท React Router 7 ยท TanStack Query 5 ยท Zustand 5 ยท Axios ยท Capacitor 8 ยท PortOne V2 browser SDK ยท vite-plugin-svgr
src/ ์๋ feature-based ํด๋๋ง. ํ feature๊ฐ ๋ชจ๋ ํด๋๋ฅผ ๊ฐ์ง๋ ์๋๋ค (์: member/file/my๋ apis+hooks๋ง ์กด์ฌ).
apis/{feature}/โ Axios ํธ์ถ ํจ์ (*.api.ts)hooks/{feature}/โ Query/Mutation ํ .hooks/common/โ ๊ณต์ฉ ํ (useInfiniteScroll, useDebounceValue, useGeolocation, useCarousel ๋ฑ โ ์๋ก ๋ง๋ค๊ธฐ ์ ํ์ธ)components/{feature}/,components/common/โ ๊ณต์ฉ ์ปดํฌ๋ํธ๋ barrel(index.ts) ๋จผ์ ํ์ธ (Header, BottomSheet, DialogBox, CTA_Button, TabBar, ToastMessage, EmptyView, chips ๋ฑ)pages/{feature}/,types/{feature}/,constants/{feature}/store/โ Zustand ์คํ ์ด (use{Name}.store.ts)utils/โ ๊ณต์ฉ ํฌํผ (platform.ts, tokenStorage.ts, dateFormat.ts, timeAgo.ts ๋ฑ)lib/โ axiosInstance, setUpInterceptors(ํ ํฐ ๋ฆฌํ๋ ์),billing/(๋ค์ดํฐ๋ธ IAP ๋ธ๋ฆฟ์ง),payment/(PortOne)layouts/โ ์๋ Layout ์น์ ์ฐธ๊ณrouter/Router.tsxโ ๋ชจ๋ ๋ผ์ฐํธ๊ฐ ์ด ํ ํ์ผ์ ์์android/,ios/โ Capacitor ๋ค์ดํฐ๋ธ ํ๋ก์ ํธ
feature ๋๋ฉ์ธ: auth, mainPage, photoLab(ํ์์), photoFeed(์ฌ์ง์๋ค), photoManage(ํ์๊ด๋ฆฌ/์ธํ), photoRestoration, mypage, credit, payment, developmentHistory, filmCameraGuide, member, file, my. (pages/demoDay๋ ์์ ์ด๋ฒคํธ ํ์ด์ง)
| ๋์ | ๊ท์น |
|---|---|
| ์ปดํฌ๋ํธ ํ์ผ | PascalCase (Header.tsx) |
| ํ ํ์ผ | camelCase (useRecentSearches.ts) |
| API ํ์ผ | {name}.api.ts |
| ์คํ ์ด ํ์ผ | use{Name}.store.ts |
| SVG ํ์ผ | kebab-case (arrow-left.svg) |
| SVG export | PascalCase + Icon ์ ๋ฏธ์ฌ (ArrowLeftIcon) |
| ํ์ /์ธํฐํ์ด์ค | PascalCase |
| ์์ | UPPER_SNAKE_CASE |
- ํฌ๊ธฐ ๋จ์๋ rem (
px / 16 = rem). ๋์์ธ ์์์ px ๊ฐ์ ๊ทธ๋๋ก ์ ์ง ๋ง ๊ฒ. - Path Alias
@/โsrc/. - ์ปค์คํ
์ปฌ๋ฌ๋
src/index.css์@theme๋ธ๋ก์๋ง ์ ์:orange-100~900+orange-450,neutral-0~1000+neutral-{750,850,875}. ์์ hex ์ง์ ์ฌ์ฉ ๊ธ์ง. - ๊ธ๋ก๋ฒ ๊ฐ์
:rootCSS ๋ณ์:--tabbar-height,--fab-gap. - prettier-plugin-tailwindcss๊ฐ ํด๋์ค ์์ ์๋ ์ ๋ ฌ.
- ํฐํธ: ๊ธฐ๋ณธ Pretendard Variable, ํฌ์ธํธ์ฉ
.font-ydestreet.
- ์ค์ ์คํฌ๋กค๋ฌ๋ ํญ์
#root๋ค. RootLayout์ดmin-h-dvh(๋ถํ์ ๋์ด)๋ผ ํ์ด์ง์h-full์ด ํด์๋์ง ์๋๋ค โ ํ์ด์ง ์์flex-1 overflow-y-auto๋ด๋ถ ์คํฌ๋กค ์ปจํ ์ด๋๋ฅผ ๋ง๋ค์ด๋ ์ค๋ฒํ๋ก๊ฐ ์๊ธฐ์ง ์๋ ์ฃฝ์ ์ปจํ ์ด๋๊ฐ ๋๋ค. ์ง์ง ๋ด๋ถ ์คํฌ๋กค์ ๋ ์ด์์ ๋์ด ์ฒด์ธ ๊ฐํธ์ด ํ์ํ ๋ณ๋ ์์ . 100dvh๋์100%์ฌ์ฉ (PWA โ ์ฃผ์์ฐฝ ์ถค ๋ฐฉ์ง, body ์คํฌ๋กค ์ ์).- ๊ณ ์ ํค๋๋
#root์คํฌ๋กค ๊ธฐ์ค sticky ํจํด:<div className="sticky top-0 z-20 -mx-4 bg-neutral-900 px-4">(-mx-4/px-4๋ RootLayout์ px-4 ํจ๋ฉ ์๋ก ๋ฐฐ๊ฒฝ ํ์ฅ.PhotoLabDetailPage์ฐธ๊ณ ). overscroll-behavior: none์ body์ ์ด๋ฏธ ์๋ค. ๊ฐ๋ณ ์์์overscroll-y-none์ ์ถ๊ฐํ์ง ๋ง ๊ฒ โ ์ค๋ฒํ๋ก ์๋ ์ปจํ ์ด๋์ ๋ถ์ผ๋ฉด ํ ์คํฌ๋กค์ด ์์ ํ ์ฃฝ๋๋ค.- (Claude ๊ฒ์ฆ ํ) ๋ธ๋ผ์ฐ์ ์๋ํ(CDP)์ ํฉ์ฑ ์คํฌ๋กค์ ๊ฐ๋ก ์บ๋ฌ์ (snap-x) ์์์ ์ด๋ฒคํธ๊ฐ ์๋ฉธํ๋ค โ ์บ๋ฌ์ ๊ด๋ จ ์คํฌ๋กค ๋์์ CDP๋ก ํ์ ํ์ง ๋ง๊ณ ์ฌ์ฉ์ ์ค๊ธฐ๊ธฐ ํ์ธ์ ์์ฒญํ ๊ฒ.
import { ArrowLeftIcon } from "@/assets/icon";
<ArrowLeftIcon className="h-6 w-6 text-orange-500" />;- SVGO๊ฐ
fill/stroke๋ฅผcurrentColor๋ก ๋ณํ โtext-*๋ก ์์ ์ ์ด.viewBox์ ์ง,width/height์ ๊ฑฐ โ Tailwind ํด๋์ค๋ก ์ฌ์ด์ฆ ์ ์ด. - ์ถ๊ฐ ์:
src/assets/icon/์ kebab-case๋ก ์ ์ฅ โindex.ts์export { default as XxxIcon } from "./xxx.svg?react";์ถ๊ฐ. - Figma export SVG๋ ์์ ํ์ง ๋ง๊ณ ๊ทธ๋๋ก ์ ์ฅ.
์๋ฒ ์๋ต์ ApiResponse<T> ๋ํผ(success/message/data, @/types/common/apiResponse). API ํจ์์์ !body.success๋ฉด throw:
// apis/{feature}/{name}.api.ts
export async function getResource(params: Params): Promise<ApiResponse<Data>> {
const res = await axiosInstance.get<ApiResponse<Data>>("/endpoint", {
params,
});
if (!res.data.success) throw new Error(res.data.message);
return res.data;
}- ๋ฌดํ์คํฌ๋กค:
useInfiniteQuery+pagination.hasNext(hooks/photoLab/usePhotoLabList์ฐธ๊ณ ). - queryKey ์ปจ๋ฒค์
:
["{domain}", "{action}", ...params]. ๊ธฐ์กด ํค ์ผ๋ถ๋ ์ด ๊ท์น์ ์ ๋ฐ๋ฅด๋๋ฐ, ๋ฌด๊ดํ ์์ ์์ ๊ณ ์น๋ ค ๋ค์ง ๋ง ๊ฒ. - ๋ฐฐ์ด ์ฟผ๋ฆฌ ํ๋ผ๋ฏธํฐ๋ ์ฝค๋ง join ํ ์ ์ก (
apis/photoLab/photoLab.api.ts์ฐธ๊ณ ). - ์๋ฒ ID๋ TSID โ JSON์์ string์ด๋ค (photoLabId, regionId, productId ๋ฑ). number๋ก ํ์ฑยท์ ์ธํ์ง ๋ง ๊ฒ. ์ผ๋ถ ๋ ๊ฑฐ์ ID๋ number๋ก ๋จ์ ์์ง๋ง ์ ์ฝ๋๋ string.
- ์ฆ๊ฒจ์ฐพ๊ธฐ๋ฅ ๋๊ด์ ์
๋ฐ์ดํธ ์ปจ๋ฒค์
: onMutate์์ ๋ถ๋ฆฌ์ธ(isFavorite)๋ง ํ๋ฆฝ, ์นด์ดํธ(favoriteCount)๋ ยฑ1 ์๋ ๊ณ์ฐํ์ง ์๋๋ค โ onSettled์ invalidate๋ก ์๋ฒ ์ ์ฐ (
hooks/photoLab/useFavoriteToggle์ฐธ๊ณ ).
- accessToken ์ ์ฅ์๋ ํ๋ซํผ๋ณ๋ก ๋ค๋ฅด๋ค: ์น=localStorage, ๋ค์ดํฐ๋ธ=SecureStoragePlugin. ๋ฐ๋์
utils/tokenStorage.ts๋ฅผ ๊ฒฝ์ ํ๊ณ localStorage๋ฅผ ์ง์ ๋ง์ง์ง ๋ง ๊ฒ. - refreshToken์ httpOnly ์ฟ ํค (ํด๋ผ์ด์ธํธ ์ ๊ทผ ๋ถ๊ฐ). 401 โ
/auth/reissueโ ์์์ฒญ ์ฌ์๋ + ์ค๋ณต refresh ๋ฐฉ์ง ํ๊ฐlib/setUpInterceptors.ts์ ๊ตฌํ๋์ด ์๋ค. ์ธ์ฆ ๊ด๋ จ ์์ ์ ์ ์ด ํ์ผ์ ๋จผ์ ์ฝ์ ๊ฒ. signupToken: ๊ฐ์ ๋ฏธ์๋ฃ ์ฌ์ฉ์์ฉ ์์ ํ ํฐ. accessToken ์์ ๋ ๋์ ์ฒจ๋ถ๋๋ฉฐ, 401์ด์ด๋ refresh ๋์์ด ์๋๋ค.- ์์
๋ก๊ทธ์ธ: ์นด์นด์ค(์น=redirect ๋ฐฉ์, ๋ค์ดํฐ๋ธ=
capacitor3-kakao-loginํ๋ฌ๊ทธ์ธ), ์ ํ(@capacitor-community/apple-sign-in). ๊ด๋ จ ์ ํธ์utils/auth/.
- ํ๋ซํผ ํ๋ณ์
utils/platform.tsํฌํผ ์ฌ์ฉ (isAndroidApp/isIosApp/getPaymentProvider),utils/auth/envUtils.ts์isNativeApp. - ๊ฒฐ์ ๋ ํ๋์ PaymentPage์์ 3๋ถ๊ธฐ: android=
GOOGLE_PLAY(์ธ์ฑ๊ฒฐ์ ), ios=APPLE_IAP(StoreKit2), web=PORTONE(์น๊ฒฐ์ ). ํฌ๋ ๋ง ์ํ ๋ชฉ๋ก์ provider ํ๋ผ๋ฏธํฐ๋ก ์๋ฒ ์กฐํ. - ๋ค์ดํฐ๋ธ IAP: ์ปค์คํ
ํ๋ฌ๊ทธ์ธ
FindersBillingPlugin(android/ios ๋ค์ดํฐ๋ธ ์ฝ๋) โlib/billing/finders-billing.ts๋ธ๋ฆฟ์ง. ์์์ฆ ๊ฒ์ฆ์ ์๋ฒ(/payments/{google|apple}/verify). Google์ ์๋ฒ๊ฐ consume, Apple์ ํด๋ผ์ด์ธํธ๊ฐ finishPurchase โ ์ด ๊ณ์ฝ์ ๋ฐ๊พธ๋ฉด ๊ฒฐ์ ๊ฐ ์ ์ค๋๋ค. - PortOne:
lib/payment/portone.ts.paymentId๋ ๋ฐฑ์๋๊ฐ ์์ฑ(์ฌ์ ๋ฑ๋ก โ requestPayment โ complete). ๋ชจ๋ฐ์ผ ์น redirect ๋ณต๊ท ์ sessionStorage๋ก ๋ณต์(pendingPortonePayment.ts). - ์ํ ID ๋ถ๋ณ์: DB์ externalProductId === ์คํ ์ด ์ฝ์(Play Console/App Store Connect) product ID === ํด๋ผ๊ฐ ์กฐํํ๋ SKU. ํ๋๋ผ๋ ์ด๊ธ๋๋ฉด ํด๋น ์คํ ์ด ๊ฒฐ์ ๊ฐ ์ ๊ฑด ์คํจํ๋ค.
- verify์
PAYMENT_410(์ค๋ณต ๊ฒฐ์ )์ ์ฑ๊ณต์ผ๋ก ์ทจ๊ธํ๋ค โ ์ด๋ฏธ ์ง๊ธ๋ ๊ตฌ๋งค์ ์ฌ์ ์ก ์ผ์ด์ค์ด๋ฏ๋ก ์ ์ ํ๋ก์ฐ(finish ๋ฑ)๋ฅผ ๊ณ์ ์งํ. - ๋ฏธ๋ฐ์ ๊ตฌ๋งค ์๊ฐ๋ณต๊ตฌ:
useReconcileGooglePurchases/useReconcileApplePurchases๊ฐ ๋ฏธ์๋ฃ ๊ตฌ๋งค๋ฅผ ์ฌ๊ฒ์ฆํ๋ค. ๊ฒฐ์ ์ฝ๋ ์์ ์ ์ด ๊ฒฝ๋ก๋ฅผ ๊นจ๋จ๋ฆฌ์ง ๋ง ๊ฒ. - iOS ์ฑ์์ PortOne(์น๊ฒฐ์ ) UI๊ฐ ๋ ธ์ถ๋๋ฉด ์ฑ์คํ ์ด ๋ฆฌ์ ์ฌ์ (๊ฐ์ด๋๋ผ์ธ 3.1.1) โ PaymentPage ํ๋ซํผ ๋ถ๊ธฐ ์์ ์ ์ฃผ์.
- ๊ฒฐ์ E2E๋ ๋ก์ปฌ ๋ถ๊ฐ: Google=๋ด๋ถํ ์คํธ ํธ๋ ์๋ช ๋น๋ ํ์, Apple=์ค๊ธฐ๊ธฐ sandbox/TestFlight (Xcode ๋ก์ปฌ .storekit ํธ๋์ญ์ ์ ์๋ฒ ๊ฒ์ฆ์ด ์ ๋จ).
- ์๋ฒ ์ํ๋ TanStack Query.
- ํด๋ผ์ด์ธํธ ์ ์ญ ์ํ๋ง Zustand:
useAuth,useLoginModal,useNewPostState,usePhotoLabFilter,usePrintOrder,useAddressId,usePaymentOrder. - ํ์ด์ง ๋ก์ปฌ ์ํ๋
useState/useReducer. ๊ตณ์ด store๋ก ๋์ด์ฌ๋ฆฌ์ง ๋ง ๊ฒ.
- ๊ธฐ๋ณธ ํ์ด์ง โ
RootLayout(safe-area, max-width 480px ๋ชจ๋ฐ์ผ ์ปจํ ์ด๋). - 5ํญ ๋ฉ์ธ ๋ค๋น ํ์ด์ง โ
FooterLayout(TabBar. Router์์<Route element={<FooterLayout />}>๊ทธ๋ฃน). - ๋ง์ดํ์ด์ง ํ๋ก์ฐ โ
MyPageLayout, ์ธํ ํ๋ก์ฐ โPhotoManageLayout. - MyPage/PhotoManage ํ์ ๋ผ์ฐํธ๋ route
handle({ title, isTab, showBack, hideHeader })๋ก ํค๋๋ฅผ ์ ์ดํ๋ค โ ์ ํ์ ํ์ด์ง ์ถ๊ฐ ์ Router.tsx์ ๊ธฐ์กด handle ํจํด์ ๋ฐ๋ฅผ ๊ฒ.
| Field | Format |
|---|---|
distanceKm |
{value}km |
avgWorkTimeMinutes |
{value}๋ถ |
workCount |
{value}๊ฑด |
API_URL ยท KAKAO_REDIRECT_URI ยท KAKAO_JS_KEY ยท KAKAO_REST_API_KEY ยท KAKAO_NATIVE_APP_KEY ยท APPLE_CLIENT_ID ยท APPLE_REDIRECT_URI ยท PORTONE_STORE_ID ยท PORTONE_CHANNEL_KEY
- ID ์ฒด๊ณ๊ฐ ํท๊ฐ๋ฆฐ๋ค: Capacitor appIdยทiOS ๋ฒ๋คIDยทAndroid namespace๋
com.finders.app, Android ์คํ ์ด์ฉ applicationId๋งcom.finders(build.gradle์์ ์๋์ ์ผ๋ก ๋ถ๋ฆฌ โcom.finders.app์ Play์์ ํ ๊ณ์ ์ด ์ ์ ). - ์ฑ WebView์ ์ถ์ฒ(origin)๋ ์น๊ณผ ๋ค๋ฅด๋ค: Android=
https://localhost, iOS=capacitor://localhost(๊ณ ์ โ https๋ก ๋ฐ๊ฟ ์ ์์). ๋ฐฑ์๋ CORS๊ฐ ์ด ๋ ์ถ์ฒ๋ฅผallowCredentials(true)์ ํจ๊ป ํ์ฉํด์ผ ํ๋ค. "์ฑ์์๋ง API๊ฐ 403 CORS"๋ฉด ์ญ์คํ๊ตฌ ์ด ๋ฌธ์ . - ๊ฐ์ ์ด์ ๋ก ์ธ๋ถ ๋ฆฌ์์ค(์นด์นด์ค ์ง๋ SDK ๋ฑ)๋ ์๋ URL(
//...)์ด ๊นจ์ง๋ค โ ์ ๋ URL(https://) ๋ช ์. - ์์น์ ๋ณด ๋ฑ ๊ธฐ๊ธฐ ๊ถํ์ ์น ์ฝ๋๋ง์ผ๋ก ์ ๋๊ณ
android/ยทios/์ ๋งค๋ํ์คํธ/plist ๊ถํ ์ ์ธ์ด ํ์ํ๋ค. - ์นด์นด์ค OAuth ๋ฆฌ๋ค์ด๋ ํธ(
kakao{key}://oauth)๋AuthCodeHandlerActivity์ intent-filter๊ฐ ๋ฐ๋๋ค (AndroidManifest์ ๋ฐ์๋จ โ MainActivity๋ก ์ฎ๊ธฐ๋ฉด ๋ค์ดํฐ๋ธ ์นด์นด์ค ๋ก๊ทธ์ธ์ด ๊นจ์ง๋ค). - release ๋น๋๋ JS
console.log๊ฐ logcat์ ์ ์ฐํ๋ค (๋ค์ดํฐ๋ธLog.*๋ง ๋์ด). JS ๋ ๋ฒจ ๋๋ฒ๊น ์ debug ๋น๋๋ก. - Android ์๋ช
release ๋น๋:
pnpm buildโnpx cap sync androidโandroid/์์gradlew assembleRelease(Play Store์ฉ AAB๋bundleRelease). Capacitor 8์ JDK 21 ํ์ โinvalid source release: 21์๋ฌ๋ ๋ก์ปฌ์ ๋ฎ์ JDK(JAVA_HOME ๋๋ ์ฌ์ฉ์ ์ ์ญ gradle.properties)๊ฐ ์กํ ๊ฒ์ด๋-Dorg.gradle.java.home=<JDK21 ๊ฒฝ๋ก>๋ก ์ค๋ฒ๋ผ์ด๋. ์๋ช ๋น๋ฐ์ ๋ณด๋android/keystore.properties(gitignore) โ ์ ๋ ์ปค๋ฐ ๊ธ์ง. - User-Agent์
FindersApp์ด append๋์ด ์์ (์๋ฒ/์ธ๋ถ ์๋น์ค์์ ์ฑ ํ๋ณ์ฉ).