Skip to content
devhongsPublic

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

๐Ÿ“˜ BX-CF Frontend Foundation (Monorepo)

๋ณธ ํ”„๋กœ์ ํŠธ๋Š” ์ฑ„๋„ ํŒŒ์šด๋ฐ์ด์…˜(Channel Foundation, CF) ์˜ ํ”„๋ก ํŠธ์—”๋“œ ๋ ˆํฌ์ง€ํ† ๋ฆฌ์ž…๋‹ˆ๋‹ค. ํŠน์ • ์„œ๋น„์Šค๊ฐ€ ์•„๋‹ˆ๋ผ, ์›น ํ”„๋กœ์ ํŠธ๋ฅผ ์‹œ์ž‘ํ•  ๋•Œ ๊ฐ€์ ธ๊ฐ€ ์“ฐ๋Š” React ๊ธฐ๋ฐ˜ ํ”„๋ก ํŠธ์—”๋“œ ํŒŒ์šด๋ฐ์ด์…˜(์Šคํƒ€ํ„ฐํ‚ท) ์„ ์ง€ํ–ฅํ•ฉ๋‹ˆ๋‹ค.

๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์€ ์ตœ์†Œํ™”ํ•˜๊ณ , ๋‹ค์Œ ์˜์—ญ์˜ ๋ฒ ์ŠคํŠธ ํ”„๋ž™ํ‹ฐ์Šค๋ฅผ ์ œ๊ณตํ•˜๋Š” ๊ฒƒ์ด ๋ชฉ์ ์ž…๋‹ˆ๋‹ค.

  • ๋ผ์šฐํŒ…, ์„œ๋ฒ„ ํ†ต์‹ (HTTP/์ธ์ฆ), ์ „์—ญ ์ƒํƒœ ๋“ฑ ๊ณตํ†ต ๊ธฐ๋ฐ˜ ๊ตฌ์„ฑ
  • ์„œ๋ฒ„ OpenAPI ์ŠคํŽ™ โ†’ ํ”„๋ก ํŠธ ์ŠคํŽ™(ํƒ€์ž…ยท์ฟผ๋ฆฌยทAPI ํด๋ผ์ด์–ธํŠธ) ์ž๋™ํ™”
  • ๋ชจ๋…ธ๋ ˆํฌ ํด๋” ๊ตฌ์กฐ์™€ Feature-Sliced Design (FSD) ์„ค๊ณ„ ๊ทœ๊ฒฉ

๊ธฐ์ˆ  ์Šคํƒ์€ ์ดˆ๊ณ ์† ๋นŒ๋“œ ์„ฑ๋Šฅ๊ณผ ๊ทน๋Œ€ํ™”๋œ DX(Developer Experience)๋ฅผ ์ง€ํ–ฅํ•˜๋Š” React 19.2 + TypeScript 6 + Vite 8 ๊ธฐ๋ฐ˜์ž…๋‹ˆ๋‹ค.

pnpm Workspaces + Turborepo ๊ธฐ๋ฐ˜์˜ ๋ชจ๋…ธ๋ ˆํฌ๋กœ ๊ตฌ์„ฑ๋˜๋ฉฐ, Feature-Sliced Design (FSD) ์„ค๊ณ„ ๊ทœ๊ฒฉ๊ณผ ๋‹จ์ผ ๊ณต์œ  ํŒจํ‚ค์ง€(@bx/shared) ์•„ํ‚คํ…์ฒ˜๋ฅผ ๋”ฐ๋ฆ…๋‹ˆ๋‹ค. PCยท๋ชจ๋ฐ”์ผยท๊ด€๋ฆฌ์ž ์›น์€ ๊ฐ๊ฐ ๋…๋ฆฝ๋œ FSD ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์ด๋ฉฐ, ๋„๋ฉ”์ธ ๋กœ์งยทUIยทHTTP ํ†ต์‹ ยท์ธ์ฆ์„ @bx/shared์—์„œ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์š” ๋„๊ตฌ ๋ฒ„์ „
React 19.2.x
TypeScript 6.0.x
Vite 8.0.x
TanStack Query 5.100.x
TanStack Router 1.130.x
SVGR vite-plugin-svgr 5.2.x
pnpm 11.1.3

๐Ÿš€ ์‹œ์ž‘ํ•˜๊ธฐ (Quick Start)

1. ํŒจํ‚ค์ง€ ์˜์กด์„ฑ ์„ค์น˜

pnpm install

2. ๊ฐœ๋ฐœ ์„œ๋ฒ„ ์‹คํ–‰

์—ฐ๊ฒฐํ•  ๋ฐฑ์—”๋“œ๋ฅผ ๋ช…๋ น์œผ๋กœ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด PC ์•ฑ์„ ๋กœ์ปฌ Nest(http://127.0.0.1:18081)์— ์—ฐ๊ฒฐํ•˜๋ ค๋ฉด:

pnpm dev:pc:nest

์›๊ฒฉ ๋ฐฑ์—”๋“œ๋Š” :remote, ๋กœ์ปฌ Nest๋Š” :nest, Mock์€ :mock์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

pnpm dev:pc:remote
pnpm dev:mobile:nest
pnpm dev:admin:mock

Mock ๋ชจ๋“œ๋Š” API ์„œ๋ฒ„๋ฅผ ๋ณ„๋„ ํ„ฐ๋ฏธ๋„์—์„œ ํ•จ๊ป˜ ์‹คํ–‰ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

pnpm dev:server
pnpm dev:pc:mock

๊ฐ€๋™ ํฌํŠธ


๐Ÿ“š Storybook Composition

apps/storybook ํ•œ workspace๊ฐ€ HubยทSharedยทPCยทAdminยทMobile์˜ ๋‹ค์„ฏ config profile์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ๊ฐ ๋Œ€์ƒ Storybook์€ ์ž๊ธฐ ์•ฑ์˜ ์ „์—ญ CSS์™€ source story๋งŒ ๋กœ๋“œํ•˜๊ณ , Hub๋Š” Composition์œผ๋กœ ๋„ค ์นดํƒˆ๋กœ๊ทธ๋ฅผ ํ•œ ์‚ฌ์ด๋“œ๋ฐ”์—์„œ ์—ฐ๊ฒฐํ•ฉ๋‹ˆ๋‹ค.

pnpm storybook          # Hub์™€ ๋„ค ๋Œ€์ƒ Storybook ๋™์‹œ ์‹คํ–‰
pnpm storybook:hub      # Hub๋งŒ ์‹คํ–‰
pnpm storybook:shared   # Shared๋งŒ ์‹คํ–‰
pnpm storybook:pc       # PC๋งŒ ์‹คํ–‰
pnpm storybook:admin    # Admin๋งŒ ์‹คํ–‰
pnpm storybook:mobile   # Mobile๋งŒ ์‹คํ–‰
pnpm build:storybook    # ๋‹ค์„ฏ Storybook ์ •์  ๋นŒ๋“œ
์นดํƒˆ๋กœ๊ทธ ๋กœ์ปฌ URL ์ดˆ๊ธฐ ์ „์‹œ ๋ฒ”์œ„
Hub http://localhost:6005 Composition ํƒ์ƒ‰
Shared http://localhost:6006 CSS token, ๊ณตํ†ต Button
PC http://localhost:6007 CSS token, PopoverPanel
Admin http://localhost:6008 CSS token, AdminFilterBar
Mobile http://localhost:6009 CSS token, AiSearchCard (390 ร— 844 viewport)

์žฌ์‚ฌ์šฉ ์ปดํฌ๋„ŒํŠธ story๋Š” packages/shared์˜ ์›๋ณธ ์ปดํฌ๋„ŒํŠธ ์˜†์—, ์•ฑ ์ „์šฉ story๋Š” ๊ฐ ์•ฑ์˜ ์›๋ณธ ์˜†์— ๋‘ก๋‹ˆ๋‹ค. apps/storybook/stories์—๋Š” token์ฒ˜๋Ÿผ ์—ฌ๋Ÿฌ ์นดํƒˆ๋กœ๊ทธ๊ฐ€ ํ•จ๊ป˜ ์“ฐ๋Š” ์ „์‹œ์šฉ story๋งŒ ๋‘ก๋‹ˆ๋‹ค.

ํ˜„์žฌ ๋‹จ๊ณ„๋Š” ์ €์žฅ์†Œ์˜ CSS ๋ณ€์ˆ˜๋ฅผ token source๋กœ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ๋Ÿ‰ POC์ž…๋‹ˆ๋‹ค. Figma ์—ฐ๋™, token JSON ์ƒ์„ฑ, MSW, router/auth provider, interaction test๋Š” ์‚ฌ์šฉ ์‚ฌ๋ก€๊ฐ€ ํ™•์ธ๋œ ๋’ค ํ™•์žฅํ•ฉ๋‹ˆ๋‹ค.


๐ŸŒ ๋ฐฑ์—”๋“œ ์—ฐ๊ฒฐ (Vite Proxy โ†” Mock โ†” Nest)

๊ฐœ๋ฐœ ์ค‘์—๋Š” ์†Œ์Šค๋ฅผ ์ˆ˜์ •ํ•˜์ง€ ์•Š๊ณ  Vite mode์™€ ์•ฑ๋ณ„ ๋ฌผ๋ฆฌ ํ™˜๊ฒฝ ํŒŒ์ผ๋กœ ์—ฐ๊ฒฐ ๋Œ€์ƒ์„ ์„ ํƒํ•ฉ๋‹ˆ๋‹ค. remote์™€ nest๋Š” ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ๊ฐ™์€ origin์˜ Vite dev server๋กœ ์š”์ฒญํ•˜๊ณ , Vite proxy๊ฐ€ ์„ ํƒํ•œ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋กœ ์ „๋‹ฌํ•ด CORS/ํฌ๋กœ์Šค๋„๋ฉ”์ธ ์ฟ ํ‚ค ๋ฌธ์ œ๋ฅผ ํ”ผํ•ฉ๋‹ˆ๋‹ค.

Mode ํ™˜๊ฒฝ ํŒŒ์ผ ์•ฑ API URL Vite proxy target ์‹คํ–‰ ์˜ˆ์‹œ
remote .env.remote /channel/backend/api/v1 http://192.168.110.217 pnpm dev:pc:remote
nest .env.nest /channel/backend/api/v1 http://127.0.0.1:18081 pnpm dev:pc:nest
mock .env.mock http://127.0.0.1:3333 ์‚ฌ์šฉํ•˜์ง€ ์•Š์Œ pnpm dev:server + pnpm dev:pc:mock
production .env.production /channel/backend/api/v1 Nginx same-origin pnpm build

ํ•ต์‹ฌ: Mock ์„œ๋ฒ„(mock/server.js)๋Š” Nest์™€ ๋™์ผํ•œ ๊ณ„์•ฝ(๊ณตํ†ต envelope + JWT ์ธ์ฆ)์„ ํ‰๋‚ด๋ƒ…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์•ฑ์€ ๋‹จ์ผ ์ฝ”๋“œํŒจ์Šค๋กœ ๋™์ž‘ํ•˜๋ฉฐ, mock์—์„œ ๊ฒ€์ฆํ•œ ์ธ์ฆยทํ†ต์‹  ๋กœ์ง์ด Nest ์—ฐ๋™ ์‹œ ๊ทธ๋Œ€๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. (pnpm dev:server๋กœ ๊ตฌ๋™, Node ๋‚ด์žฅ ๋ชจ๋“ˆ๋งŒ ์‚ฌ์šฉํ•ด ์˜์กด์„ฑ ์—†์Œ)

๊ฐ ์•ฑ์€ ์•„๋ž˜ ํŒŒ์ผ์„ ๋…๋ฆฝ์ ์œผ๋กœ ๊ฐ€์ง‘๋‹ˆ๋‹ค.

apps/pc-web/.env.remote
apps/pc-web/.env.nest
apps/pc-web/.env.mock
# apps/mobile-web, apps/admin-portal์—๋„ ๊ฐ™์€ ์„ธ ํŒŒ์ผ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ํ™˜๊ฒฝ ํŒŒ์ผ์€ ๊ฐ ์•ฑ ๋””๋ ‰ํ† ๋ฆฌ์— ์œ„์น˜ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ๋ฃจํŠธ .env๋Š” Vite ์•ฑ์ด ์ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ๊ฐœ์ธ๋ณ„ ์ฃผ์†Œ ๋ณ€๊ฒฝ์€ ์ถ”์  ํŒŒ์ผ์„ ์ˆ˜์ •ํ•˜์ง€ ๋ง๊ณ  ๊ฐ™์€ ์•ฑ์— .env.remote.local, .env.nest.local, .env.mock.local์„ ๋งŒ๋“ญ๋‹ˆ๋‹ค. ์ด ํŒŒ์ผ๋“ค์€ Git์—์„œ ์ œ์™ธ๋ฉ๋‹ˆ๋‹ค.
  • VITE_API_URL์„ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ๋‘๋ฉด ์•ฑ์˜ vite.config.ts proxy๋ฅผ ํƒ€๊ณ , ์ ˆ๋Œ€ URL๋กœ ๋‘๋ฉด ๋ธŒ๋ผ์šฐ์ €๊ฐ€ ํ•ด๋‹น ์„œ๋ฒ„๋กœ ์ง์ ‘ ์š”์ฒญํ•ฉ๋‹ˆ๋‹ค.
  • entity API wrapper์—๋Š” /system/menus/list ๊ฐ™์€ ์ƒ๋Œ€ API ๊ฒฝ๋กœ๋งŒ ๋‘๊ณ  ํ˜ธ์ŠคํŠธ ์ฃผ์†Œ๋ฅผ ๋„ฃ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • API_DOCS_URLS๋Š” pnpm gen:api๊ฐ€ OpenAPI ๋ฌธ์„œ๋ฅผ ๊ฐ€์ ธ์˜ฌ ์ฃผ์†Œ์ด๋ฉฐ, ์•ฑ์˜ ๋Ÿฐํƒ€์ž„ ๋ฐฑ์—”๋“œ ์„ ํƒ๊ณผ๋Š” ๋ณ„๊ฐœ์ž…๋‹ˆ๋‹ค.
  • Mock ์„œ๋ฒ„๋Š” db.json์„ ๊ทธ๋Œ€๋กœ ์„œ๋น™ํ•˜๋˜ ๋ชจ๋“  ์‘๋‹ต์„ envelope๋กœ ๊ฐ์‹ธ๊ณ , /auth/* ์—”๋“œํฌ์ธํŠธ๋Š” Nest ํ˜•ํƒœ์˜ ๊ฐ€์งœ ํ† ํฐ(๋จผ ๋ฏธ๋ž˜ ๋งŒ๋ฃŒ)์„ ๋ฐœ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค. (db.json ๋ณ€๊ฒฝ ์‹œ mock ์„œ๋ฒ„ ์žฌ์‹œ์ž‘ ํ•„์š”)

๊ณตํ†ต ์‘๋‹ต ๊ทœ๊ฒฉ (envelope)

์‹ค์„œ๋ฒ„ ์‘๋‹ต์€ ๊ณตํ†ต๋ถ€(success/code/msg)์™€ ๋ฐ์ดํ„ฐ๋ถ€(payload)๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. httpService๊ฐ€ payload๋ฅผ ์ž๋™ ์–ธ๋ž˜ํ•‘ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.

{
  "success": true,     // ์„ฑ๊ณต/์‹คํŒจ
  "code": "0",         // ์„ฑ๊ณต: "0", ์‹คํŒจ: ์Œ์ˆ˜ ๋ฌธ์ž์—ด
  "msg": "success",    // ๋ฉ”์‹œ์ง€
  "payload": { }       // ์‹ค์ œ ๋ฐ์ดํ„ฐ (any)
}

API ์—๋Ÿฌ ์ฝ”๋“œ (API_ERROR_CODE)

์ฝ”๋“œ ์ƒ์ˆ˜ ์„ค๋ช…
-1001 REQUIRED_VALUE_MISSING ํ•„์ˆ˜ ์ž…๋ ฅ๊ฐ’ ๋ˆ„๋ฝ
-1002 INVALID_TOKEN ์œ ํšจํ•˜์ง€ ์•Š์€ ํ† ํฐ โ†’ ๋กœ๊ทธ์•„์›ƒ
-1003 UNAUTHORIZED_CLIENT ์ธ์ฆ๋˜์ง€ ์•Š์€ ํด๋ผ์ด์–ธํŠธ โ†’ ๋กœ๊ทธ์•„์›ƒ
-1004 EXPIRED_TOKEN ํ† ํฐ ๋งŒ๋ฃŒ โ†’ ์ž๋™ ์žฌ๋ฐœ๊ธ‰(refresh)
-1005 ACCESS_DENIED ๋ฆฌ์†Œ์Šค ์ ‘๊ทผ ๊ถŒํ•œ ์—†์Œ (๋กœ๊ทธ์•„์›ƒ X)
-2003/-2004 JSON_*_PARSING JSON ์ง๋ ฌํ™”/์—ญ์ง๋ ฌํ™” ์˜ค๋ฅ˜
-4001/-4002 DB_*_ERROR DB ์กฐํšŒ/์ €์žฅ ์˜ค๋ฅ˜
-4003 ๋ฐฑ์—”๋“œ ์ •์˜ Database access error (ํ˜„์žฌ FE API_ERROR_CODE์—๋Š” ๋ฏธ๋“ฑ๋ก)
-9999 SERVER_ERROR ์„œ๋ฒ„ ๋‚ด๋ถ€ ์˜ค๋ฅ˜

๐Ÿ” ์ธ์ฆ (JWT)

Nest ๋ฐฑ์—”๋“œ ์—ฐ๋™ ์‹œ JWT ๊ธฐ๋ฐ˜ ์ธ์ฆ์ด ๋™์ž‘ํ•ฉ๋‹ˆ๋‹ค. ํ† ํฐ ๋ถ€์ฐฉยท๋งŒ๋ฃŒ ์‹œ ์ž๋™ ์žฌ๋ฐœ๊ธ‰ยท์ธ์ฆ ์‹คํŒจ ์ฒ˜๋ฆฌ๋Š” @bx/shared์— ์บก์Аํ™”๋˜์–ด ์žˆ์œผ๋ฉฐ, ์•ฑ์€ main.tsx์—์„œ ํ•œ ๋ฒˆ๋งŒ ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

// apps/[app]/src/main.tsx
httpService.init({
  baseURL: API_URL,
  timeout: API_CONFIG.TIMEOUT,
  auth: createHttpAuthConfig(),
});

๋กœ๊ทธ์ธ ํ๋ฆ„

  1. ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ sha256()๋กœ ํ•ด์‹ฑ โ†’ POST /auth/login ({ usrId, usrPwd })
  2. ์‘๋‹ต payload(์‚ฌ์šฉ์ž ์ •๋ณด + accessToken + accessTokenExpiresAt)๋ฅผ useAuthStore.setAuth()๋กœ ์ €์žฅ
  3. refreshToken ๊ฐ’์€ ์„œ๋ฒ„๊ฐ€ HttpOnly Cookie๋กœ ๊ด€๋ฆฌํ•˜๊ณ , FE๋Š” localStorage์— ์ €์žฅํ•˜์ง€ ์•Š์Œ
  4. accessToken ๋ฐ accessTokenExpiresAt์€ localStorage์— ๋ณด๊ด€(ํƒญ ๊ฐ„ ๊ณต์œ ยท์ƒˆ๋กœ๊ณ ์นจ ์œ ์ง€)

ํ† ํฐ ์ž๋™ ๊ด€๋ฆฌ (์ธํ„ฐ์…‰ํ„ฐ)

  • ์š”์ฒญ: ๋ชจ๋“  ์š”์ฒญ์— Authorization: Bearer <accessToken> ์ž๋™ ๋ถ€์ฐฉ
  • ์‘๋‹ต (-1004 ๋˜๋Š” HTTP 401): POST /auth/refresh-token์œผ๋กœ ์žฌ๋ฐœ๊ธ‰ ํ›„ ์›์š”์ฒญ ์ž๋™ ์žฌ์‹œ๋„
    • refreshToken์€ request body๊ฐ€ ์•„๋‹ˆ๋ผ HttpOnly Cookie๋กœ ์ „์†ก(withCredentials: true)
    • ๋™์‹œ ๋‹ค๋ฐœ ์š”์ฒญ์€ single-flight๋กœ refresh 1ํšŒ๋งŒ ํ˜ธ์ถœ
    • ์žฌ๋ฐœ๊ธ‰ ์‹คํŒจ โ†’ ๋กœ๊ทธ์•„์›ƒ + /login ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ
  • ์‘๋‹ต (-1002/-1003): ์žฌ๋ฐœ๊ธ‰ ๋ถˆ๊ฐ€ โ†’ ์ฆ‰์‹œ ๋กœ๊ทธ์•„์›ƒ
  • ์‘๋‹ต (-1005): ์ธ๊ฐ€(๊ถŒํ•œ) ์˜ค๋ฅ˜ โ†’ ๋กœ๊ทธ์•„์›ƒํ•˜์ง€ ์•Š๊ณ  ์—๋Ÿฌ ๊ทธ๋Œ€๋กœ ์ „๋‹ฌ

๋ผ์šฐํŠธ ๊ฐ€๋“œ

๋ณดํ˜ธ ๋ผ์šฐํŠธ((page)/_page)๋Š” beforeLoad์—์„œ requireAuth๋ฅผ ๋จผ์ € ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ensureValidAuthSession()์€ accessTokenExpiresAt์ด ์œ ํšจํ•˜๋ฉด ๋ฐ”๋กœ ํ†ต๊ณผํ•˜๊ณ , ๋งŒ๋ฃŒ๋๊ฑฐ๋‚˜ accessToken์ด ์—†์œผ๋ฉด refresh ์ฟ ํ‚ค๋กœ accessToken ์žฌ๋ฐœ๊ธ‰์„ ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. ์œ ํšจํ•œ ์„ธ์…˜๊ณผ ์‚ฌ์šฉ์ž ID๊ฐ€ ๋ชจ๋‘ ์žˆ์–ด์•ผ ๋ณดํ˜ธ ํ™”๋ฉด์— ์ง„์ž…ํ•ฉ๋‹ˆ๋‹ค.

์ธ์ฆ ํ†ต๊ณผ ํ›„ PCยทAdminยทMobile์€ ์•ฑ๊ณผ ์‚ฌ์šฉ์ž ID๋ฅผ ์กฐํ•ฉํ•œ ๋ฉ”๋‰ด ์บ์‹œ scope๋กœ ensureBaseInfoBootstrapped๋ฅผ ํ˜ธ์ถœํ•˜๊ณ , ๊ธฐ์ค€์ •๋ณด ์ค€๋น„๊ฐ€ ๋๋‚œ ๋’ค ํ™”๋ฉด์„ ๋กœ๋”ฉํ•ฉ๋‹ˆ๋‹ค.


๐Ÿงญ ๊ธฐ์ค€์ •๋ณด ๋ถ€ํŠธ์ŠคํŠธ๋žฉ

PCยทAdminยทMobile ๋ณดํ˜ธ ํ™”๋ฉด ์ตœ์ดˆ ์ง„์ž… ์‹œ ์ฝ”๋“œ/๋ฉ”๋‰ด ๊ธฐ์ค€์ •๋ณด๋ฅผ ์ค€๋น„ํ•ฉ๋‹ˆ๋‹ค. ์‹คํ–‰ ์ค‘ ์—๋Ÿฌ๊ฐ€ ๋‚˜๋„ ํ™”๋ฉด ์ง„์ž…์€ ๋ง‰์ง€ ์•Š๊ณ , ๊ฐ€๋Šฅํ•œ ๊ฒฝ์šฐ ๊ธฐ์กด localStorage ์บ์‹œ๋ฅผ ์žฌ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์ฒ˜๋ฆฌ ํ๋ฆ„

  1. POST /system/reference-data/versions/latest๋กœ ๊ธฐ์ค€์ •๋ณด ๋ฒ„์ „์„ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค. (refType/versionNo โ†’ type/version์œผ๋กœ ์ •๊ทœํ™”)
  2. ํƒ€์ž…๋ณ„ localStorage ์บ์‹œ๊ฐ€ serverVersion + schemaVersion ๊ธฐ์ค€์œผ๋กœ ์ตœ์‹ ์ธ์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.
  3. ์ตœ์‹  ์บ์‹œ๊ฐ€ ์žˆ์œผ๋ฉด API๋ฅผ ํ˜ธ์ถœํ•˜์ง€ ์•Š๊ณ  ์บ์‹œ๋ฅผ sessionStorage์— ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.
  4. ์ตœ์‹  ์บ์‹œ๊ฐ€ ์—†์œผ๋ฉด ํ•ด๋‹น ํƒ€์ž…๋งŒ ์žฌ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.
    • ๋ฉ”๋‰ด: POST /system/menus/list
    • ์ฝ”๋“œ: POST /system/common-codes/groups/list ํ›„ ๊ทธ๋ฃน๋ณ„ POST /system/common-codes/groups/{groupCd}/codes/list
  5. ์กฐํšŒ ๊ฒฐ๊ณผ๋ฅผ localStorage์— ์ €์žฅํ•˜๊ณ , ํ™”๋ฉด์—์„œ ์“ฐ๋Š” CONFIG.SESSION.CODE / CONFIG.SESSION.MENU_LIST์— ์ฃผ์ž…ํ•ฉ๋‹ˆ๋‹ค.

์บ์‹œ ์—ญํ• 

๋ ˆ์ด์–ด ์—ญํ• 
React Query ์•ฑ ์‹คํ–‰ ์„ธ์…˜์—์„œ ensureQueryData ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€ (baseInfoQueryKeys.bootstrap(menuCacheScope))
localStorage ์ƒˆ๋กœ๊ณ ์นจ/์žฌ์ ‘์† ํ›„์—๋„ ๊ธฐ์ค€์ •๋ณด ์žฌ์‚ฌ์šฉ (base-info:CODE, base-info:MENU:<app>:<usrId>)
sessionStorage ๊ธฐ์กด ํ™”๋ฉด ๋กœ์ง์ด ์ฐธ์กฐํ•˜๋Š” ๋Ÿฐํƒ€์ž„ ์„ธ์…˜ ๋ฐ์ดํ„ฐ (CONFIG.SESSION.CODE, CONFIG.SESSION.MENU_LIST)

๊ณตํ†ต์ฝ”๋“œ ์บ์‹œ๋Š” ๋ชจ๋“  ์•ฑ์ด ๋™์ผํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋Š” ์ „์ œ๋กœ base-info:CODE ํ•˜๋‚˜๋ฅผ ๊ณต์œ ํ•ฉ๋‹ˆ๋‹ค. ๋ฉ”๋‰ด๋Š” ์•ฑ๊ณผ ์‚ฌ์šฉ์ž๋ณ„ ๊ตฌ์„ฑ์ด ๋‹ค๋ฅผ ์ˆ˜ ์žˆ์œผ๋ฏ€๋กœ pc:<usrId>, admin:<usrId>, mobile:<usrId> scope๋กœ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

๋กœ์ปฌ ์ฝ”๋“œ fallback

๋ฐฐํฌ ์‹œ์ ์— ๊ณ ์ •๋˜๋Š” ์ฝ”๋“œ ๋ชฉ๋ก์€ packages/shared/src/shared/constants/local-codes.ts์˜ LOCAL_CODE_MAP์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค. ํ™”๋ฉด์€ ์ฝ”๋“œ ์ถœ์ฒ˜๋ฅผ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ  groupCd๋งŒ ์ „๋‹ฌํ•˜๋ฉฐ, $codeUtils์™€ Select๋Š” ๋‹ค์Œ ์šฐ์„ ์ˆœ์œ„๋กœ ์ฝ”๋“œ๋ฅผ ์กฐํšŒํ•ฉ๋‹ˆ๋‹ค.

  1. sessionStorage์— ์ ์žฌ๋œ ์„œ๋ฒ„ ์ฝ”๋“œ ๊ทธ๋ฃน
  2. ์„œ๋ฒ„์— ํ•ด๋‹น ๊ทธ๋ฃน์ด ์—†์œผ๋ฉด LOCAL_CODE_MAP
  3. ์„œ๋ฒ„์™€ ๋กœ์ปฌ์— ๋ชจ๋‘ ์—†์œผ๋ฉด ๋นˆ ๋ฐฐ์—ด

์„œ๋ฒ„ ๊ทธ๋ฃน์ด ๋นˆ ๋ฐฐ์—ด๋กœ ์กด์žฌํ•˜๋Š” ๊ฒฝ์šฐ์—๋Š” ์„œ๋ฒ„๊ฐ€ ์˜๋„์ ์œผ๋กœ ๋นˆ ๋ชฉ๋ก์„ ์ œ๊ณตํ•œ ๊ฒƒ์œผ๋กœ ๋ณด๊ณ  ๋กœ์ปฌ ์ฝ”๋“œ๋กœ ๋Œ€์ฒดํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋กœ์ปฌ ์ฝ”๋“œ๋Š” ๋ฒˆ๋“ค์— ํฌํ•จ๋˜๋Š” ์ •์  ๋ฐ์ดํ„ฐ์ด๋ฏ€๋กœ React Query๋‚˜ localStorage์— ๋ณ„๋„๋กœ ์ €์žฅํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

Select์™€ FormSelect๋Š” ๋ณ„๋„์˜ placeholder๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š๊ณ  ์ฒซ ๋ฒˆ์งธ ๋ณด์กฐ ํ•ญ๋ชฉ์„ emptyOption์œผ๋กœ ํ†ต์ผํ•ฉ๋‹ˆ๋‹ค.

emptyOption ๋ Œ๋”๋ง ์šฉ๋„
์ƒ๋žต ๋˜๋Š” ALL '' / ์ „์ฒด ์กฐํšŒ ์กฐ๊ฑด ๊ธฐ๋ณธ๊ฐ’
SELECT '' / ์„ ํƒ ์ž…๋ ฅ ํผ ์„ ํƒ ์•ˆ๋‚ด
NONE ๋ Œ๋”๋งํ•˜์ง€ ์•Š์Œ ์ฝ”๋“œ ๋ชฉ๋ก๋งŒ ๋…ธ์ถœ
{ value, label } ์ง€์ •ํ•œ ๊ฐ’๊ณผ ๋ฌธ๊ตฌ ์ „์ฒด ์œ ํ˜• ๋“ฑ ํ™”๋ฉด๋ณ„ ํ‘œํ˜„

schemaVersion์€ FE ๋‚ด๋ถ€ ์บ์‹œ ๊ตฌ์กฐ ๋ฒ„์ „์ž…๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฒ„์ „์ด ๊ทธ๋Œ€๋กœ์—ฌ๋„ FE ์ €์žฅ ๊ตฌ์กฐ๊ฐ€ ๋ฐ”๋€Œ๋ฉด schemaVersion์„ ์˜ฌ๋ ค ๊ฐ•์ œ๋กœ ์žฌ์กฐํšŒํ•˜๊ฒŒ ํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ CODE๋Š” ๊ทธ๋ฃน+children ๊ตฌ์กฐ๋ฅผ ๋ฐ˜์˜ํ•ด 2, MENU๋Š” 1์ž…๋‹ˆ๋‹ค.

๋ฒ„์ „ ํ™•์ธ ๋ฐ ๊ฐฑ์‹  ์‹œ์ 

staleTime๊ณผ gcTime์„ ๋ฌดํ•œ๋Œ€๋กœ ์„ค์ •ํ–ˆ์œผ๋ฏ€๋กœ ๋™์ผํ•œ QueryClient์™€ menuCacheScope๊ฐ€ ์œ ์ง€๋˜๋Š” ์•ฑ ์‹คํ–‰ ์ค‘์—๋Š” ๋ถ€ํŠธ์ŠคํŠธ๋žฉ์„ ํ•œ ๋ฒˆ๋งŒ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค. ์„œ๋ฒ„ ๋ฒ„์ „์ด ์•ฑ ์‹คํ–‰ ์ค‘ ๋ณ€๊ฒฝ๋˜๋”๋ผ๋„ ์ฆ‰์‹œ ๊ฐ์ง€ํ•˜์ง€ ์•Š์œผ๋ฉฐ, ๋ธŒ๋ผ์šฐ์ € ์ƒˆ๋กœ๊ณ ์นจ์ด๋‚˜ ์•ฑ ์žฌ์‹คํ–‰์œผ๋กœ ์ƒˆ QueryClient๊ฐ€ ์ƒ์„ฑ๋  ๋•Œ ๋ฒ„์ „์„ ๋‹ค์‹œ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

Admin์˜ ์ฝ”๋“œยท๋ฉ”๋‰ด ๋“ฑ๋ก/์ˆ˜์ •/์‚ญ์ œ๋Š” ํ•ด๋‹น ๊ด€๋ฆฌ ํ™”๋ฉด์˜ ๋ชฉ๋ก Query๋งŒ ๊ฐฑ์‹ ํ•ฉ๋‹ˆ๋‹ค. CONFIG.SESSION.CODE์™€ CONFIG.SESSION.MENU_LIST์— ์ ์žฌ๋œ ๊ธฐ์ค€์ •๋ณด๋Š” ๋‹ค์Œ ์ƒˆ๋กœ๊ณ ์นจ์—์„œ ์„œ๋ฒ„ ๋ฒ„์ „์„ ํ™•์ธํ•œ ๋’ค ๊ฐฑ์‹ ๋ฉ๋‹ˆ๋‹ค.

์‹คํŒจ ์‹œ ๋™์ž‘ (์˜๋„๋œ ๋™์ž‘)

๋ผ์šฐํŠธ ๊ฐ€๋“œ๊ฐ€ ํ˜ธ์ถœํ•˜๋Š” bootstrapBaseInfoSafe๋Š” ์ ˆ๋Œ€ throwํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์‹คํŒจ๋Š” ๋ฐ˜ํ™˜๊ฐ’์˜ failed ๋ฐฐ์—ด์— ๋‹ด๊ธฐ๊ณ , beforeLoad๋Š” ์ด ๋ฐฐ์—ด์„ ํ™•์ธํ•˜์ง€ ์•Š์€ ์ฑ„ ํ™”๋ฉด ์ง„์ž…์„ ํ—ˆ์šฉํ•ฉ๋‹ˆ๋‹ค. ๋ฒ„์ „ ๋˜๋Š” ๋ฐ์ดํ„ฐ ์กฐํšŒ๊ฐ€ ์‹คํŒจํ•˜๋ฉด ์‚ฌ์šฉ ๊ฐ€๋Šฅํ•œ ๊ธฐ์กด ์บ์‹œ๋ฅผ sessionStorage์— ๋ณต์›ํ•ฉ๋‹ˆ๋‹ค.

๋”ฐ๋ผ์„œ "์ฒซ ์ ‘์† + ๊ธฐ์ค€์ •๋ณด ์กฐํšŒ ์‹คํŒจ"(์žฌ์‚ฌ์šฉํ•  localStorage ์บ์‹œ๊ฐ€ ์•„์ง ์—†๋Š” ์ƒํƒœ)์—์„œ๋Š” CONFIG.SESSION.CODE๊ฐ€ ๋น„์–ด ์žˆ๋Š” ์ฑ„๋กœ ํ™”๋ฉด์ด ๋ Œ๋”๋ฉ๋‹ˆ๋‹ค. ์ด๋•Œ $codeUtils์™€ Select์˜ groupCd๋Š” ๋กœ์ปฌ ์ฝ”๋“œ ๊ทธ๋ฃน์ด ์žˆ์œผ๋ฉด ํ•ด๋‹น ๋ชฉ๋ก์„ ์‚ฌ์šฉํ•˜๊ณ , ๋กœ์ปฌ์—๋„ ์—†๋Š” ๊ทธ๋ฃน์ด๋ฉด ๋นˆ ๋ชฉ๋ก์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์„ธ์…˜์„ ๊ตฌ๋…ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ ์ดํ›„ ์„œ๋ฒ„ ์ฝ”๋“œ๊ฐ€ ์ฑ„์›Œ์ ธ๋„ ์ƒˆ๋กœ๊ณ ์นจ ์ „๊นŒ์ง€ ๋‹ค์‹œ ๋ Œ๋”๋ง๋˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

๊ธฐ์ค€์ •๋ณด๋ฅผ ๋ชป ๋ฐ›์•„๋„ ํ™”๋ฉด์€ ์—ด๋ฆฌ๋Š” ์ชฝ์„ ํƒํ•œ ๊ฒฐ๊ณผ์ž…๋‹ˆ๋‹ค. ์ฝ”๋“œ ๋ˆ„๋ฝ์„ ์˜ค๋ฅ˜๋กœ ๋‹ค๋ค„์•ผ ํ•˜๋Š” ์„œ๋น„์Šค๋ผ๋ฉด beforeLoad์—์„œ failed์— CODE๊ฐ€ ํฌํ•จ๋๋Š”์ง€ ํ™•์ธํ•ด ์—๋Ÿฌ ํ™”๋ฉด์œผ๋กœ ๋ณด๋‚ด๋„๋ก ๋ฐ”๊พธ๋ฉด ๋ฉ๋‹ˆ๋‹ค.


๐Ÿ“œ ์‹คํ–‰ ์Šคํฌ๋ฆฝํŠธ (Scripts)

๊ฐœ๋ฐœ ์„œ๋ฒ„

๋ช…๋ น ์„ค๋ช…
pnpm dev ์ „์ฒด ์•ฑ ๋ณ‘๋ ฌ ์‹คํ–‰ (Turborepo)
pnpm dev:pc / dev:mobile / dev:admin ๊ฐœ๋ณ„ ์•ฑ ๊ตฌ๋™
pnpm dev:<pc|mobile|admin>:remote ์„ ํƒ ์•ฑ โ†’ ์›๊ฒฉ ๋ฐฑ์—”๋“œ (192.168.110.217)
pnpm dev:<pc|mobile|admin>:nest ์„ ํƒ ์•ฑ โ†’ ๋กœ์ปฌ Nest (127.0.0.1:18081)
pnpm dev:<pc|mobile|admin>:mock ์„ ํƒ ์•ฑ โ†’ ๋กœ์ปฌ Mock (127.0.0.1:3333)
pnpm dev:server Mock API ์„œ๋ฒ„ (mock/server.js, 3333)
pnpm dev:all ์ „์ฒด ์•ฑ(๊ธฐ๋ณธ .env) + Mock API ์„œ๋ฒ„ ๋™์‹œ ๊ตฌ๋™
pnpm storybook Composition Hub์™€ SharedยทPCยทAdminยทMobile Storybook ๋™์‹œ ๊ตฌ๋™
pnpm storybook:<hub|shared|pc|admin|mobile> Storybook profile ๊ฐœ๋ณ„ ๊ตฌ๋™

๊ฒ€์ฆ / ๋นŒ๋“œ

๋ช…๋ น ์„ค๋ช…
pnpm check ์ „์ฒด ํƒ€์ž… ๊ฒ€์‚ฌ (turbo check โ†’ ๊ฐ ์•ฑ tsc --noEmit)
pnpm lint Biome ๋ฆฐํŠธ
pnpm format Biome ์ผ๊ด„ ํฌ๋งทํŒ…
pnpm check:api-paths httpService ๊ฒฝ๋กœ๊ฐ€ generated OpenAPI ๊ฒฝ๋กœ์™€ ๋งž๋Š”์ง€ ๊ฒ€์‚ฌ
pnpm check:css-tokens CSS Modules์™€ ์ปดํฌ๋„ŒํŠธ ์†Œ์Šค์˜ raw color ์‚ฌ์šฉ ์—ฌ๋ถ€ ๊ฒ€์‚ฌ
pnpm test ์ „์ฒด workspace ๋‹จ์œ„ ํ…Œ์ŠคํŠธ (Vitest, Turborepo ์บ์‹ฑ)
pnpm build ํ”„๋กœ๋•์…˜ ํ†ตํ•ฉ ๋นŒ๋“œ (Turborepo ์บ์‹ฑ)
pnpm build:storybook ๋‹ค์„ฏ Storybook ์ •์  ๋นŒ๋“œ

๋‹จ์œ„ ํ…Œ์ŠคํŠธ: pc-web, mobile-web, admin-portal, @bx/shared์— Vitest ์„ค์ •๊ณผ test ์Šคํฌ๋ฆฝํŠธ๊ฐ€ ์žˆ์œผ๋ฉฐ, ๋ฃจํŠธ pnpm test๊ฐ€ Turborepo๋ฅผ ํ†ตํ•ด ์ „์ฒด workspace ํ…Œ์ŠคํŠธ๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ…Œ์ŠคํŠธ ํŒŒ์ผ์ด ์•„์ง ์—†๋Š” workspace๋„ --passWithNoTests๋กœ ์ •์ƒ ์ข…๋ฃŒํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ GitHub Actions workflow์—๋Š” pnpm test ๋‹จ๊ณ„๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

E2E (Playwright)

๋ช…๋ น ์„ค๋ช…
pnpm test:e2e ์ „์ฒด E2E
pnpm test:e2e:pc / :mobile / :admin ๊ฐœ๋ณ„ ์•ฑ E2E

๊ธฐํƒ€

๋ช…๋ น ์„ค๋ช…
pnpm gen:api ์—ฌ๋Ÿฌ ๋ฐฑ์—”๋“œ OpenAPI ์ŠคํŽ™ โ†’ ์„œ๋น„์Šค๋ณ„ TS ํƒ€์ž… ์ƒ์„ฑ (scripts/gen-api.mjs โ†’ packages/shared/src/shared/api/*.schema.d.ts). ์ŠคํŽ™ ๋ชฉ๋ก์€ API_DOCS_URLS ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ๋ฎ์–ด์“ฐ๊ธฐ
pnpm gen:readme ๋กœ์ปฌ landing ๋ฏธ๋ฆฌ๋ณด๊ธฐ์šฉ README HTML ์ƒ์„ฑ (๋ฐฐํฌ ์‹œ CI๊ฐ€ ์ž๋™ ์‹คํ–‰)
pnpm wbs:pull -- --dry-run GitHub Projects์˜ ์‹ ๊ทœ ํ•ญ๋ชฉ์„ WBS ๋ฐ˜์˜ ์ „์— ๋ฏธ๋ฆฌ๋ณด๊ธฐ
pnpm wbs:pull GitHub Projects์˜ ์‹ ๊ทœ ํ•ญ๋ชฉ์„ docs/wbs.md์— ์ถ”๊ฐ€ (docs/wbs-pull.js)
pnpm wbs:sync docs/wbs.md โ†’ GitHub Projects ๋™๊ธฐํ™” (docs/wbs-sync.js)
pnpm wbs:force-sync WBS ๊ฐ•์ œ ์žฌ๋™๊ธฐํ™” (docs/wbs-force-sync.js)

๐Ÿšข ๋ฐฐํฌ (Deployment)

์šด์˜ ์„œ๋ฒ„๋Š” Nginx๋กœ ์„œ๋น™ํ•˜๋ฉฐ, ๊ฐ ์•ฑ์€ **๋ณ„๋„ context(ํ•˜์œ„ ๊ฒฝ๋กœ)**๋กœ ๋ถ„๋ฆฌ๋ฉ๋‹ˆ๋‹ค. ๋นŒ๋“œ ์‹œ Vite base๊ฐ€ ์ฃผ์ž…๋˜์–ด ์—์…‹ ๊ฒฝ๋กœ๊ฐ€ ํ•ด๋‹น context์— ๋งž์ถฐ์ง‘๋‹ˆ๋‹ค.

์•ฑ Nginx context ๋นŒ๋“œ base (vite.config)
pc-web /pc/ /pc/
mobile-web /mobile/ /mobile/
admin-portal /admin/ /admin/
์•ˆ๋‚ด ํŽ˜์ด์ง€(landing) / (๋ฃจํŠธ) ์—†์Œ โ€” ๋นŒ๋“œ ๋ถˆ์š” ์ •์  ํŒŒ์ผ

dev ์„œ๋ฒ„์—์„œ๋Š” base๊ฐ€ /๋กœ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค(๋นŒ๋“œ ์‹œ์—๋งŒ context ๊ฒฝ๋กœ ์ ์šฉ).

develop ๋ธŒ๋žœ์น˜์— push๋˜๋ฉด self-hosted ๋Ÿฌ๋„ˆ์—์„œ ์ž๋™์œผ๋กœ ๋‹ค์Œ์„ ์ˆ˜ํ–‰ํ•ฉ๋‹ˆ๋‹ค.

์ „์ฒด ์—ฐ๊ฒฐ ๊ตฌ์กฐ, job ์‹คํ–‰ ์ˆœ์„œ, ํ˜„์žฌ ์šด์˜์ƒ ์ฃผ์˜์ ์€ CI/CD ์‹คํ–‰ ํ๋ฆ„์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

ํ•ญ๋ชฉ ๊ฐ’
Runner self-hosted
Node.js 22
pnpm 11.1.3 (packageManager์™€ ๋™๊ธฐํ™”)
๋ฐฐํฌ ํŠธ๋ฆฌ๊ฑฐ develop ๋ธŒ๋žœ์น˜ push
API URL ์ฃผ์ž… VITE_API_URL=/channel/backend/api/v1
๋ฐฐํฌ ๊ฒฐ๊ณผ ์•Œ๋ฆผ Discord Webhook (DISCORD_WH GitHub Actions secret)
  1. pnpm install --frozen-lockfile
  2. pnpm gen:api๋กœ ๋ฐฑ์—”๋“œ OpenAPI ์ŠคํŽ™ ๊ธฐ์ค€ ํƒ€์ž… ์žฌ์ƒ์„ฑ
  3. git diff --exit-code -- packages/shared/src/shared/api๋กœ generated ํƒ€์ž… ์ปค๋ฐ‹ ๋ˆ„๋ฝ ์—ฌ๋ถ€ ํ™•์ธ
  4. pnpm check:api-paths๋กœ FE API ํ˜ธ์ถœ ๊ฒฝ๋กœ์™€ generated OpenAPI ๊ฒฝ๋กœ ์ •ํ•ฉ์„ฑ ํ™•์ธ
  5. pnpm build:debug(์ „์ฒด ์•ฑ ๋นŒ๋“œ, VITE_API_URL ์ฃผ์ž…) โ†’ pnpm check โ†’ pnpm lint
  6. pnpm gen:readme๋กœ landing/assets/fe.readme.html ์žฌ์ƒ์„ฑ
  7. ๊ฐ ์•ฑ dist/*์™€ landing/*๋ฅผ Nginx ์„œ๋น™ ํด๋”๋กœ ๋ณต์‚ฌ (๋ฐฐํฌ ์™„๋ฃŒ ํ›„ Discord ์•Œ๋ฆผ)

OpenAPI ์ŠคํŽ™ ์ •ํ•ฉ์„ฑ ์ฒดํฌ

CI๋Š” ๋นŒ๋“œ ์„ฑ๊ณต ์—ฌ๋ถ€๋ฟ ์•„๋‹ˆ๋ผ ๋ฐฑ์—”๋“œ ์ŠคํŽ™๊ณผ FE generated ํƒ€์ž…์˜ ๋™๊ธฐํ™” ์—ฌ๋ถ€๋„ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค.

  • pnpm gen:api๋Š” scripts/gen-api.mjs์˜ ๊ธฐ๋ณธ ๋‚ด๋ถ€๋ง Swagger URL ๋˜๋Š” API_DOCS_URLS ํ™˜๊ฒฝ๋ณ€์ˆ˜๋กœ ์ง€์ •ํ•œ URL์—์„œ ์ŠคํŽ™์„ ๋ฐ›์•„ packages/shared/src/shared/api/*.schema.d.ts๋ฅผ ์žฌ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.
  • ์žฌ์ƒ์„ฑ ํ›„ packages/shared/src/shared/api์— diff๊ฐ€ ์žˆ์œผ๋ฉด, ๋ฐฑ์—”๋“œ ์ŠคํŽ™ ๋ณ€๊ฒฝ์ด FE ํƒ€์ž… ํŒŒ์ผ์— ๋ฐ˜์˜๋˜์ง€ ์•Š์€ ์ƒํƒœ์ด๋ฏ€๋กœ CI๋ฅผ ์‹คํŒจ์‹œํ‚ต๋‹ˆ๋‹ค.
  • pnpm check:api-paths๋Š” packages/shared/src/entities์˜ httpService.get/post/... ํ˜ธ์ถœ ๊ฒฝ๋กœ๊ฐ€ generated OpenAPI ๊ฒฝ๋กœ์— ์กด์žฌํ•˜๋Š”์ง€ ํ™•์ธํ•ฉ๋‹ˆ๋‹ค. ์•„์ง ์ŠคํŽ™์— ์—†๋Š” legacy ๊ฒฝ๋กœ๋Š” scripts/check-api-paths.mjs์˜ allowlist์— ์‚ฌ์œ ์™€ ํ•จ๊ป˜ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ๊ฐœ๋ฐœ์ž ๋กœ์ปฌ์—์„œ๋„ Swagger URL์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜๋ฉด PR ์ „์— ๊ฐ™์€ ๊ฒ€์‚ฌ๋ฅผ ๋ฏธ๋ฆฌ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค: pnpm gen:api && git diff --exit-code -- packages/shared/src/shared/api.
  • self-hosted CI ๋Ÿฌ๋„ˆ๋„ Swagger URL์— ์ ‘๊ทผ ๊ฐ€๋Šฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ ‘๊ทผ์ด ์–ด๋ ต๋‹ค๋ฉด Swagger JSON snapshot์„ repo์— ์ €์žฅํ•˜๊ฑฐ๋‚˜, ๋ฐฑ์—”๋“œ ๋ฆด๋ฆฌ์ฆˆ ์‚ฐ์ถœ๋ฌผ๋กœ OpenAPI JSON์„ ์ œ๊ณต๋ฐ›๋Š” ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • gen-api ์‹คํ–‰ ์ค‘ missing schema patch ๊ฒฝ๊ณ ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ๋ฐฑ์—”๋“œ OpenAPI ๋ฌธ์„œ๊ฐ€ ๋ถˆ์™„์ „ํ•˜๋‹ค๋Š” ์‹ ํ˜ธ์ž…๋‹ˆ๋‹ค. ๋กœ์ปฌ ๊ฐœ๋ฐœ์—์„œ๋Š” warning์œผ๋กœ ๋ณผ ์ˆ˜ ์žˆ์ง€๋งŒ, ๋ฐฐํฌ์šฉ CI์—์„œ๋Š” ๋ฐฑ์—”๋“œ ์ŠคํŽ™ ๋ณด์™„ ๋˜๋Š” allowlist ์ •์ฑ…์„ ๋จผ์ € ๊ฒ€ํ† ํ•ฉ๋‹ˆ๋‹ค.
๋Œ€์ƒ ์„œ๋ฒ„ ๋ฐฐํฌ ๊ฒฝ๋กœ
pc-web /Users/channelunit/apps/bx-cf-fe/pc-web
mobile-web /Users/channelunit/apps/bx-cf-fe/mobile-web
admin-portal /Users/channelunit/apps/bx-cf-fe/admin-portal
landing /Users/channelunit/apps/bx-cf-fe/landing

์ฐธ๊ณ : ํ˜„์žฌ develop push ์‹œ ์„ธ ์•ฑ์ด ๋ชจ๋‘ ํ•จ๊ป˜ ๋นŒ๋“œยท๋ฐฐํฌ๋ฉ๋‹ˆ๋‹ค. ์šด์˜์šฉ ์•ฑ๋ณ„ ๋…๋ฆฝ ๋ฐฐํฌ๊ฐ€ ํ•„์š”ํ•˜๋ฉด ์›Œํฌํ”Œ๋กœ์šฐ๋ฅผ ๋ถ„๋ฆฌ(๋ธŒ๋žœ์น˜/ํƒœ๊ทธ/paths ํ•„ํ„ฐ ๋˜๋Š” turbo affected)ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ฃผ์˜: CI๋Š” --frozen-lockfile๋กœ ์˜์กด์„ฑ์„ ์„ค์น˜ํ•ฉ๋‹ˆ๋‹ค. package.json์˜ dependencies/devDependencies๋ฅผ ๋ณ€๊ฒฝํ–ˆ๋‹ค๋ฉด ๋ฐ˜๋“œ์‹œ pnpm-lock.yaml๋„ ํ•จ๊ป˜ ๊ฐฑ์‹ ํ•ด ์ปค๋ฐ‹ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. lockfile๋งŒ ๊ฐฑ์‹ ํ•˜๋ ค๋ฉด pnpm install --lockfile-only๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

์•ˆ๋‚ด ํŽ˜์ด์ง€ (landing)

landing/index.html์€ FEยทBE ์ž๋ฃŒ(์†Œ๊ฐœ PDFยทREADMEยทWBSยท์ €์žฅ์†Œ)๋ฅผ ๋งํฌ๋กœ ์•ˆ๋‚ดํ•˜๋Š” ๋‹จ์ผ ์ •์  ํŽ˜์ด์ง€์ž…๋‹ˆ๋‹ค. ์–ด๋–ค ์•ฑ์—๋„ ์†ํ•˜์ง€ ์•Š์œผ๋ฏ€๋กœ public/(์•ฑ ๊ณต์œ  publicDir)์ด ์•„๋‹Œ ๋ณ„๋„ landing/์— ๋‘๊ณ , Nginx ๋ฃจํŠธ context๋กœ ์„œ๋น™ํ•ฉ๋‹ˆ๋‹ค. CI/CD์—์„œ pnpm gen:readme ์‹คํ–‰ ํ›„ landing/* ์ „์ฒด๋ฅผ landing ๋ฐฐํฌ ๊ฒฝ๋กœ๋กœ ๋ณต์‚ฌํ•ฉ๋‹ˆ๋‹ค.

pnpm gen:readme๋กœ ์ƒ์„ฑํ•œ README HTML์€ ๋ฐ์Šคํฌํ†ฑ์—์„œ H2 ์ œ๋ชฉ๋งŒ ๋ชจ์€ ์šฐ์ธก ๊ณ ์ • ๋ชฉ์ฐจ๋ฅผ ์ œ๊ณตํ•˜๊ณ , ๋ฌธ์„œ ์Šคํฌ๋กค์— ๋”ฐ๋ผ ํ˜„์žฌ ํ•ญ๋ชฉ์„ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค. ๋ชฉ์ฐจ ๋งํฌ๋Š” URL hash์™€ ๋ถ€๋“œ๋Ÿฌ์šด ์Šคํฌ๋กค์„ ์‚ฌ์šฉํ•˜๋ฉฐ, 1100px ์ดํ•˜ ํ™”๋ฉด์—์„œ๋Š” ๋ชฉ์ฐจ๋ฅผ ์ˆจ๊ธฐ๊ณ  ๋ณธ๋ฌธ์„ ์ „์ฒด ํญ์œผ๋กœ ํ‘œ์‹œํ•ฉ๋‹ˆ๋‹ค.

README HTML์€ CI๊ฐ€ ๋ฐฐํฌ ์ „์— ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค. ๋กœ์ปฌ landing ๋ฏธ๋ฆฌ๋ณด๊ธฐ๊ฐ€ ํ•„์š”ํ•œ ๊ฒฝ์šฐ์—๋งŒ pnpm gen:readme๋ฅผ ์‹คํ–‰ํ•ฉ๋‹ˆ๋‹ค. ํ˜•์ œ BE ์ €์žฅ์†Œ๊ฐ€ ๋กœ์ปฌ์— ์žˆ์œผ๋ฉด be.readme.html๋„ ํ•จ๊ป˜ ๋ณ€๊ฒฝ๋  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.


๐Ÿ“‚ ๋””๋ ‰ํ† ๋ฆฌ ์•„ํ‚คํ…์ฒ˜

bx-cf-fe/
โ”œโ”€โ”€ package.json                 # ๋ฃจํŠธ ์Šคํฌ๋ฆฝํŠธ & ์›Œํฌ์ŠคํŽ˜์ด์Šค
โ”œโ”€โ”€ pnpm-workspace.yaml          # ์›Œํฌ์ŠคํŽ˜์ด์Šค ์ •์˜ (apps/*, packages/*)
โ”œโ”€โ”€ turbo.json                   # Turborepo ํŒŒ์ดํ”„๋ผ์ธ ์บ์‹ฑ
โ”œโ”€โ”€ biome.json                   # Biome ๋ฆฐํ„ฐ & ํฌ๋งทํ„ฐ (a11y ๊ทœ์น™ ์ œ์™ธ)
โ”œโ”€โ”€ tsconfig.json                # ๊ณตํ†ต TS ์„ค์ • (๊ฐ ์•ฑ์ด extends)
โ”œโ”€โ”€ db.json                      # Mock ๋ฐ์ดํ„ฐ
โ”œโ”€โ”€ mock/                        # Mock API ์„œ๋ฒ„ (Nest ๊ณ„์•ฝ ํ‰๋‚ด, ๋ฌด์˜์กด์„ฑ)
โ”‚   โ””โ”€โ”€ server.js
โ”œโ”€โ”€ public/                      # ๊ณต์šฉ ์ •์  ์ž์‚ฐ (์•ฑ ๊ฐ„ ๊ณต์œ  publicDir)
โ”œโ”€โ”€ landing/                     # ์•ˆ๋‚ด ํŽ˜์ด์ง€ (Nginx ๋ฃจํŠธ context, ์•ฑ ๋น„์ข…์†)
โ”‚   โ”œโ”€โ”€ index.html
โ”‚   โ””โ”€โ”€ assets/                  #   ์†Œ๊ฐœ PDFยทPPTX, gen:readme ์‚ฐ์ถœ๋ฌผ(fe/be.readme.html), backup/(์ด์ „ ํ…Œ๋งˆ ๋ฑ)
โ”œโ”€โ”€ scripts/
โ”‚   โ”œโ”€โ”€ gen-api.mjs              # OpenAPI โ†’ TS ํƒ€์ž… ์ƒ์„ฑ
โ”‚   โ”œโ”€โ”€ check-api-paths.mjs      # FE API ํ˜ธ์ถœ ๊ฒฝ๋กœ โ†” OpenAPI ๊ฒฝ๋กœ ์ •ํ•ฉ์„ฑ ์ฒดํฌ
โ”‚   โ”œโ”€โ”€ gen-readme-html.mjs      # README โ†’ landing HTML ์ƒ์„ฑ
โ”‚   โ””โ”€โ”€ presentations/           # ์ œ์•ˆ ๋ฑ ๋นŒ๋“œยท์ˆ˜์ • ์Šคํฌ๋ฆฝํŠธ (Biome ๊ฒ€์‚ฌ ์ œ์™ธ)
โ”œโ”€โ”€ .github/workflows/ci.yml     # develop push ์‹œ ๋นŒ๋“œยท๊ฒ€์ฆยทNginx ๋ฐฐํฌ
โ”œโ”€โ”€ docs/                        # ํ”„๋กœ์ ํŠธ ๋ฌธ์„œ & ๋„๊ตฌ
โ”‚   โ”œโ”€โ”€ order.md / todo.md / wbs.md
โ”‚   โ”œโ”€โ”€ form-components.md       # ํผ ์ปดํฌ๋„ŒํŠธ ์‚ฌ์šฉ ๊ฐ€์ด๋“œ
โ”‚   โ”œโ”€โ”€ ppt-intro.md             # ์ œ์•ˆ ๋ฑ ๋‚ด๋Ÿฌํ‹ฐ๋ธŒ ์›๊ณ 
โ”‚   โ”œโ”€โ”€ wbs-sync.js              # WBS โ†’ GitHub Projects ๋™๊ธฐํ™” ์Šคํฌ๋ฆฝํŠธ
โ”‚   โ”œโ”€โ”€ wbs-pull.js              # GitHub Projects โ†’ WBS ์‹ ๊ทœ ํ•ญ๋ชฉ ๊ฐ€์ ธ์˜ค๊ธฐ
โ”‚   โ”œโ”€โ”€ superpowers/             # ๊ธฐ๋Šฅ๋ณ„ ์ž‘์—… ๊ณ„ํš(plans)ยท์„ค๊ณ„(specs) ๊ธฐ๋ก
โ”‚   โ””โ”€โ”€ ppt-build/               # ์ œ์•ˆ PPT ๋นŒ๋“œยท๋ Œ๋” ์‚ฐ์ถœ๋ฌผ (Biome ๊ฒ€์‚ฌ ์ œ์™ธ)
โ”‚
โ”œโ”€โ”€ packages/
โ”‚   โ””โ”€โ”€ shared/                  # ๊ณต์œ  ํŒจํ‚ค์ง€ (@bx/shared)
โ”‚       โ””โ”€โ”€ src/
โ”‚           โ”œโ”€โ”€ entities/        # ๋„๋ฉ”์ธ: account, alarm, auth, base-info, common-code, menu, product, user
โ”‚           โ”‚   โ””โ”€โ”€ <entity>/    #   โ”œ api/    (HTTP ํ˜ธ์ถœ)
โ”‚           โ”‚                    #   โ”œ model/  (ํƒ€์ž…ยทhookยทqueriesยทstoreยทstorage)
โ”‚           โ”‚                    #   โ”” ui/     (๋„๋ฉ”์ธ ์ปดํฌ๋„ŒํŠธ)
โ”‚           โ”œโ”€โ”€ shared/          # ๊ณตํ†ต: ui, hooks, model, lib, types, constants, ajax
โ”‚           โ”‚   โ”œโ”€โ”€ ajax/        #   http.service (envelopeยท์ธํ„ฐ์…‰ํ„ฐยทJWT)
โ”‚           โ”‚   โ”œโ”€โ”€ constants/   #   api, error-codes, siteConfig, storage-keys
โ”‚           โ”‚   โ””โ”€โ”€ ui/          #   alert, dialog, drawer, modal, toast, button, input ...
โ”‚           โ””โ”€โ”€ index.ts         # ๋ฐฐ๋Ÿด (์™ธ๋ถ€๋กœ ์ผ๊ด„ Export)
โ”‚
โ””โ”€โ”€ apps/
    โ”œโ”€โ”€ pc-web/                  # PC ์›น (3000) โ€” FSD ์•ฑ
    โ”‚   โ””โ”€โ”€ src/
    โ”‚       โ”œโ”€โ”€ app/             #   ์ „์—ญ ํ”„๋กœ๋ฐ”์ด๋” (modal ๋“ฑ)
    โ”‚       โ”œโ”€โ”€ routes/          #   TanStack Router ํŒŒ์ผ ๊ธฐ๋ฐ˜ ๋ผ์šฐํŒ…
    โ”‚       โ”‚   โ”œโ”€โ”€ (auth)/      #     ๋กœ๊ทธ์ธ
    โ”‚       โ”‚   โ”œโ”€โ”€ (page)/      #     ๋ณดํ˜ธ ๋ผ์šฐํŠธ (requireAuth)
    โ”‚       โ”‚   โ””โ”€โ”€ (modal)/     #     ๋ชจ๋‹ฌ ๋ผ์šฐํŠธ
    โ”‚       โ”œโ”€โ”€ pages/           #   ํŽ˜์ด์ง€ ์ปดํฌ๋„ŒํŠธ
    โ”‚       โ”œโ”€โ”€ features/        #   ๊ธฐ๋Šฅ ๋‹จ์œ„ (auth, dashboard ...)
    โ”‚       โ”œโ”€โ”€ widgets/         #   ๋ ˆ์ด์•„์›ƒ ์œ„์ ฏ (sidebar ๋“ฑ)
    โ”‚       โ”œโ”€โ”€ queryClient.ts   #   ์•ฑ ๊ณต์šฉ TanStack QueryClient
    โ”‚       โ””โ”€โ”€ shared/          #   ์•ฑ ๋กœ์ปฌ ๊ณตํ†ต (guards ๋“ฑ)
    โ”‚
    โ”œโ”€โ”€ mobile-web/              # ๋ชจ๋ฐ”์ผ ์›น (3001) โ€” FSD ์•ฑ (๊ตฌ์กฐ ๋™์ผ, ํ’€์Šคํฌ๋ฆฐ ๋ชจ๋‹ฌ)
    โ”‚
    โ””โ”€โ”€ admin-portal/            # ๊ด€๋ฆฌ์ž ํฌํƒˆ (3002) โ€” ์šด์˜ ๊ด€๋ฆฌ ์•ฑ
        โ””โ”€โ”€ src/
            โ”œโ”€โ”€ routes/          #   ๋กœ๊ทธ์ธยท๋Œ€์‹œ๋ณด๋“œยท์ฝ”๋“œยท๋ฉ”๋‰ดยท์‚ฌ์šฉ์žยทํ”„๋กœํ•„ ๋ผ์šฐํŠธ
            โ”œโ”€โ”€ pages/           #   ๊ด€๋ฆฌ์ž ์—…๋ฌด ํ™”๋ฉด
            โ”œโ”€โ”€ features/        #   ์ฝ”๋“œยท๋ฉ”๋‰ดยท์‚ฌ์šฉ์ž ๋“ฑ๋ก/์ˆ˜์ • ๊ธฐ๋Šฅ
            โ”œโ”€โ”€ widgets/         #   ๊ด€๋ฆฌ์ž ๋ ˆ์ด์•„์›ƒยท์‚ฌ์ด๋“œ๋ฐ”
            โ””โ”€โ”€ shared/          #   ๊ด€๋ฆฌ์ž ์ „์šฉ ํผยทํ•„ํ„ฐยท๋“œ๋กœ์–ดยท์Šคํƒ€์ผ

๋ผ์šฐํŠธ ํŠธ๋ฆฌ(routeTree.gen.ts)๋Š” TanStack Router ํ”Œ๋Ÿฌ๊ทธ์ธ์ด dev/build ์‹œ ์ž๋™ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค(์ง์ ‘ ์ˆ˜์ • ๊ธˆ์ง€).


๐Ÿงฉ Feature-Sliced Design (FSD)

์ด ์ €์žฅ์†Œ๋Š” Feature-Sliced Design ๊ณต์‹ ํ™ˆํŽ˜์ด์ง€์˜ ๊ตฌ์กฐํ™” ์›์น™์„ ๊ธฐ๋ฐ˜์œผ๋กœ ํ•˜๋ฉฐ, ์ž์„ธํ•œ ๊ฐœ๋…์€ FSD ํ•œ๊ตญ์–ด Overview์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. FSD๋Š” ํ”„๋ก ํŠธ์—”๋“œ ์ฝ”๋“œ๋ฅผ ์ฑ…์ž„ ๋ฒ”์œ„์— ๋”ฐ๋ฅธ Layer, ๋น„์ฆˆ๋‹ˆ์Šค ๋„๋ฉ”์ธ์— ๋”ฐ๋ฅธ Slice, ๊ธฐ์ˆ  ์—ญํ• ์— ๋”ฐ๋ฅธ Segment๋กœ ๋‚˜๋ˆ„๋Š” ์•„ํ‚คํ…์ฒ˜ ๋ฐฉ๋ฒ•๋ก ์ž…๋‹ˆ๋‹ค.

BX-CF๋Š” ์ด๋ฅผ ๋ชจ๋…ธ๋ ˆํฌ์— ๋งž๊ฒŒ ์ ์šฉํ•ฉ๋‹ˆ๋‹ค.

FSD Layer ์ด ์ €์žฅ์†Œ์—์„œ์˜ ์—ญํ• 
app ์•ฑ ์ง„์ž…์ , ์ „์—ญ Provider์™€ ์„ค์ •
pages / routes ํ™”๋ฉด๊ณผ URL ์ง„์ž… ๋‹จ์œ„
widgets ์‚ฌ์ด๋“œ๋ฐ”ยท๋ ˆ์ด์•„์›ƒ์ฒ˜๋Ÿผ ๋…๋ฆฝ์ ์ธ ๋Œ€ํ˜• UI ๊ตฌ์„ฑ
features ๋กœ๊ทธ์ธ, ๋“ฑ๋กยท์ˆ˜์ •์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ž ํ–‰๋™ ์ค‘์‹ฌ์˜ ๊ธฐ๋Šฅ
entities account, auth, menu, product ๋“ฑ ๋„๋ฉ”์ธ๋ณ„ APIยทmodelยทUI
shared ํŠน์ • ๋„๋ฉ”์ธ์— ์ข…์†๋˜์ง€ ์•Š๋Š” UIยท์œ ํ‹ธ๋ฆฌํ‹ฐยทํ†ต์‹ ยท์ƒ์ˆ˜
  • ์•ฑ๋ณ„ ํ™”๋ฉดยท๊ธฐ๋Šฅยท์Šคํƒ€์ผ์€ apps/*/src์— ๋‘๊ณ , ์—ฌ๋Ÿฌ ์•ฑ์ด ๊ณต์œ ํ•˜๋Š” ๋„๋ฉ”์ธ๊ณผ ๊ธฐ๋ฐ˜ ๊ธฐ๋Šฅ์€ packages/shared/src์—์„œ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • Slice ๋‚ด๋ถ€๋Š” api, model, ui ๋“ฑ์˜ Segment๋กœ ์—ญํ• ์„ ๋ถ„๋ฆฌํ•ฉ๋‹ˆ๋‹ค.
  • ์™ธ๋ถ€ ๋ชจ๋“ˆ์€ Slice์˜ index.ts Public API๋ฅผ ํ†ตํ•ด ์ ‘๊ทผํ•˜๊ณ , ์ƒ์œ„ Layer์—์„œ ํ•˜์œ„ Layer ๋ฐฉํ–ฅ์œผ๋กœ ์˜์กดํ•˜๋„๋ก ๊ตฌ์„ฑํ•ฉ๋‹ˆ๋‹ค.

๐ŸŽจ ์ฝ”๋“œ ์Šคํƒ€์ผ ๋ฐ ๊ฐœ๋ฐœ ํ‘œ์ค€

1. ๋ฆฐํ„ฐยทํฌ๋งทํ„ฐ (Biome)

ESLint/Prettier ๋Œ€์‹  Rust ๊ธฐ๋ฐ˜ Biome์œผ๋กœ ํ’ˆ์งˆยท์Šคํƒ€์ผ์„ ๊ด€๋ฆฌํ•ฉ๋‹ˆ๋‹ค.

  • ์ ‘๊ทผ์„ฑ(a11y) ๊ทœ์น™ ์ œ์™ธ: ํ”„๋กœํ† ํƒ€์ดํ•‘/๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์†๋„๋ฅผ ์œ„ํ•ด biome.json์—์„œ a11y๋ฅผ ๋น„ํ™œ์„ฑํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค.
  • ํฌ๋งท ๊ทœ์น™: ์ธ๋ดํŠธ Space 2 ยท ๊ฐœํ–‰ LF ยท ์ž‘์€๋”ฐ์˜ดํ‘œ(') ยท ์„ธ๋ฏธ์ฝœ๋ก  ํ•ญ์ƒ.

2. Barrel(index.ts) ๊ทœ์น™

  • ์—”ํ‹ฐํ‹ฐยทํ›…ยทUI๋Š” ๋””๋ ‰ํ† ๋ฆฌ์˜ index.ts๋กœ ๋ฌถ์–ด ๋…ธ์ถœํ•ฉ๋‹ˆ๋‹ค.
  • ์ˆœํ™˜ ์ฐธ์กฐ ๊ธˆ์ง€: @bx/shared ๋‚ด๋ถ€ ๋ชจ๋“ˆ๋ผ๋ฆฌ๋Š” ๋ฐ˜๋“œ์‹œ ๋กœ์ปฌ ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ์ž„ํฌํŠธํ•ฉ๋‹ˆ๋‹ค. (ํŒจํ‚ค์ง€ ๋ช…์นญ @bx/shared๋กœ ์ž๊ธฐ ์ž์‹ ์„ ํ˜ธ์ถœํ•˜๋ฉด ์ˆœํ™˜ ์˜์กด์„ฑ ๋ฐœ์ƒ)

3. ๋„๋ฉ”์ธ ํŒŒ์ผ ๋ฐฐ์น˜ ์ปจ๋ฒค์…˜

์ƒˆ ํŒŒ์ผ/๊ธฐ๋Šฅ์„ ์ถ”๊ฐ€ํ•˜๊ธฐ ์ „์—๋Š” ๊ฐ™์€ ๋„๋ฉ”์ธ์˜ ๊ธฐ์กด ๊ตฌ์กฐ๋ฅผ ๋จผ์ € ํ™•์ธํ•˜๊ณ , entities ํ•˜์œ„์˜ ๊ธฐ์กด ํŒจํ„ด์„ ์šฐ์„ ํ•ฉ๋‹ˆ๋‹ค.

์ข…๋ฅ˜ ์œ„์น˜
API ํ˜ธ์ถœ api/*.api.ts
ํƒ€์ž… model/*.type.ts
TanStack Query model/*.queries.ts
storage/cache model/*.storage.ts
hook/store/bootstrap ๋“ฑ ๋ชจ๋ธ ๋กœ์ง model/*.hook.ts, model/*.store.ts, model/*.bootstrap.ts ๋“ฑ ๊ธฐ์กด ํŒจํ„ด์— ๋งž์ถค

TanStack Query key๋Š” xxxQueryKeys = { all, list, detail, ... } ๊ฐ์ฒด ํŒจํ„ด์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ๊ธฐ์กด ์ปจ๋ฒค์…˜๊ณผ ๋‹ค๋ฅด๊ฒŒ ๊ฐˆ ํ•„์š”๊ฐ€ ์žˆ์œผ๋ฉด ๊ตฌํ˜„ ์ „์— ์ด์œ ๋ฅผ ๋จผ์ € ์„ค๋ช…ํ•ฉ๋‹ˆ๋‹ค.

4. HTTP ํ†ต์‹  (httpService)

  • ๋ชจ๋“  API ํ˜ธ์ถœ์€ @bx/shared์˜ httpService.get/post/put/patch/delete๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.
  • baseURL์€ httpService.init()์—์„œ 1ํšŒ ์„ค์ •ํ•˜๋ฏ€๋กœ, ๊ฐ API ํ•จ์ˆ˜๋Š” ์ƒ๋Œ€ ๊ฒฝ๋กœ๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. (์˜ˆ: httpService.get('/product/list'))
  • execute()๊ฐ€ envelope์˜ payload๋ฅผ ์–ธ๋ž˜ํ•‘ํ•˜์—ฌ ๋ฐ˜ํ™˜ํ•˜๊ณ , success: false๋Š” ApiError๋กœ throwํ•ฉ๋‹ˆ๋‹ค.
  • envelope ์—…๋ฌด ์˜ค๋ฅ˜, HTTP ์˜ค๋ฅ˜, ๋„คํŠธ์›Œํฌ ๋‹จ์ ˆ, timeout, ์š”์ฒญ ์ทจ์†Œ๋Š” ๋ชจ๋‘ ApiError๋กœ ์ •๊ทœํ™”๋ฉ๋‹ˆ๋‹ค. ํ˜ธ์ถœ๋ถ€์—์„œ๋Š” code, status, kind, payload, url, message๋ฅผ ๋™์ผํ•œ ๋ฐฉ์‹์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

5. ๊ณตํ†ต UI์™€ ์•ฑ ์ „์šฉ UI

  • ์—ฌ๋Ÿฌ ์•ฑ์—์„œ ์žฌ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋ณธ UI๋Š” packages/shared/src/shared/ui์— ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  • ์•ฑ๋ณ„ ๋™์ž‘์ด๋‚˜ ์Šคํƒ€์ผ์ด ํ•„์š”ํ•˜๋ฉด shared ๊ธฐ๋ณธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์กฐํ•ฉํ•˜๊ฑฐ๋‚˜ ํ™•์žฅํ•˜๊ณ , wrapper์™€ ์Šคํƒ€์ผ์€ ํ•ด๋‹น ์•ฑ ๋‚ด๋ถ€์— ๋‘ก๋‹ˆ๋‹ค.
  • ์•ฑ์— ๋จผ์ € ๊ตฌํ˜„ํ•œ ๊ธฐ๋Šฅ์ด๋ผ๋„ ์žฌ์‚ฌ์šฉ ๋ฒ”์œ„๊ฐ€ ๋„“์–ด์ง€๋ฉด shared UI๋กœ ์Šน๊ฒฉํ•ฉ๋‹ˆ๋‹ค.

6. ์Šคํƒ€์ผ๋ง ์›์น™ (CSS Modules)

  • ์Šคํƒ€์ผ์€ CSS Modules(*.module.css)๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. Tailwind CSS๋Š” ์ œ๊ฑฐํ–ˆ์œผ๋ฉฐ, utility class ๊ธฐ๋ฐ˜ ์Šคํƒ€์ผ์„ ์ƒˆ๋กœ ์ถ”๊ฐ€ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.
  • ์ปดํฌ๋„ŒํŠธ๋Š” import styles from './Component.module.css' ํ˜•ํƒœ๋กœ ์Šคํƒ€์ผ์„ ๊ฐ€์ ธ์˜ค๊ณ , ์•ฑ๋ณ„ ์Šคํƒ€์ผ์€ ํ•ด๋‹น ์•ฑ ๋‚ด๋ถ€์— ๋‘ก๋‹ˆ๋‹ค.
  • ๊ณตํ†ต ์ปดํฌ๋„ŒํŠธ ์Šคํƒ€์ผ์€ packages/shared/src/shared/ui ๋˜๋Š” packages/shared/src/shared/form์˜ ์ปดํฌ๋„ŒํŠธ ์˜† CSS Module์— ๋‘ก๋‹ˆ๋‹ค.
  • ์ƒ‰์ƒ์€ ๊ฐ€๋Šฅํ•œ ํ•œ ์•ฑ/๊ณตํ†ต ํ…Œ๋งˆ ํ† ํฐ CSS variable์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. raw hex/rgb ์ƒ‰์ƒ์€ pnpm check:css-tokens๋กœ ๊ฒ€์‚ฌํ•ฉ๋‹ˆ๋‹ค.
  • ์ „์—ญ CSS๋Š” reset, theme token, ์•ฑ shell์ฒ˜๋Ÿผ ์ „์—ญ์ด์–ด์•ผ ํ•˜๋Š” ์ตœ์†Œ ๋ฒ”์œ„์—๋งŒ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

7. SVG ์ž์‚ฐ๊ณผ SVGR

์„ธ ์•ฑ(pc-web, mobile-web, admin-portal)์˜ Vite ์„ค์ •์—๋Š” vite-plugin-svgr๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.

  • SVG๋ฅผ React ์ปดํฌ๋„ŒํŠธ๋กœ ์‚ฌ์šฉํ•  ๋•Œ๋Š” import Icon from './icon.svg?react'์ฒ˜๋Ÿผ ?react ์ ‘๋ฏธ์‚ฌ๋ฅผ ๋ถ™์ž…๋‹ˆ๋‹ค.
  • ?react๊ฐ€ ์—†๋Š” SVG import๋Š” ๊ธฐ์กด Vite ๋™์ž‘๋Œ€๋กœ URL ๋ฌธ์ž์—ด๋กœ ์ทจ๊ธ‰ํ•ฉ๋‹ˆ๋‹ค.
  • ์•ฑ์—์„œ ๊ณต์œ ๋˜๋Š” ์ •์  ์ด๋ฏธ์ง€๋Š” public/assets/images/**์— ๋‘๊ณ , ์•ฑ ์ „์šฉ ์†Œ์Šค SVG๋Š” ๊ฐ ์•ฑ์˜ src/assets์— ๋‘ก๋‹ˆ๋‹ค.
  • ๋‹จ์ˆœ ์•„์ด์ฝ˜ ๋ฒ„ํŠผ์€ ๊ฐ€๋Šฅํ•˜๋ฉด SVG ํŒŒ์ผ์„ ์ง์ ‘ ์กฐ์ž‘ํ•˜๊ธฐ๋ณด๋‹ค ๊ธฐ์กด shared UI์™€ ์•„์ด์ฝ˜ ์ž์‚ฐ์„ ์กฐํ•ฉํ•ฉ๋‹ˆ๋‹ค.

8. ๊ณตํ†ต ์ƒ์ˆ˜ (@bx/shared/.../constants)

๋ชจ๋“ˆ ๋‚ด์šฉ
api.ts API_URL(env ์ฃผ์ž…), IS_MOCK_API, API_CONFIG(ํƒ€์ž„์•„์›ƒยท์žฌ์‹œ๋„)
error-codes.ts API_ERROR_CODE ๋ฐ ์ธ์ฆ ์ฝ”๋“œ ํŒ๋ณ„ ํ—ฌํผ
siteConfig.ts ์ „์—ญ CONFIG (๋ชจ๋“œยท์„ธ์…˜ยทํƒ€์ด๋จธยทํ…Œ๋งˆยทํ•œ๋„ ๋“ฑ)
storage-keys.ts STORAGE_KEYS (ํ† ํฐยท์‚ฌ์šฉ์žยท์บ์‹œ ํ‚ค)
index.ts APP_CONFIG, ROUTES, BANK_OPTIONS
  • ์•ฑ(apps/*)์—์„œ: @bx/shared ๋ฐฐ๋Ÿด๋กœ ์ผ๊ด„ ์ž„ํฌํŠธ. (์˜ˆ: import { API_URL, STORAGE_KEYS } from '@bx/shared';)
  • ํŒจํ‚ค์ง€ ๋‚ด๋ถ€์—์„œ: ์ƒ๋Œ€ ๊ฒฝ๋กœ๋กœ ๊ฐœ๋ณ„ ์ž„ํฌํŠธ. (์˜ˆ: import { API_URL } from './api';)

๐ŸชŸ ๋ชจ๋‹ฌ ์‹œ์Šคํ…œ

@bx/shared์˜ Zustand ์Šคํ† ์–ด(useModalStore/useModal)๋กœ ๋ชจ๋‹ฌ ์Šคํƒ์„ ๊ด€๋ฆฌํ•˜๊ณ , Radix Dialog ๊ธฐ๋ฐ˜ UI๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค.

  • PC: ํ™”๋ฉด ์ค‘์•™ ๋‹ค์ด์–ผ๋กœ๊ทธ (Dialog)
  • ๋ชจ๋ฐ”์ผ: ํ’€์Šคํฌ๋ฆฐ ๋‹ค์ด์–ผ๋กœ๊ทธ
  • ๋ชจ๋‹ฌ ํ™”๋ฉด์€ ๊ฐ ์•ฑ์˜ routes/(modal)/<name>/index.tsx์— ๋‘๊ณ , useModal().open({ path: '<name>' })์œผ๋กœ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

๐Ÿšจ ๊ณตํ†ต AlertยทConfirm ๋ฐ API ์—๋Ÿฌ ์ฒ˜๋ฆฌ

@bx/shared๋Š” Promise ๊ธฐ๋ฐ˜ openAlertยทopenConfirm๊ณผ Radix Dialog ๊ธฐ๋ฐ˜ <AlertHost />๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. PCยทMobileยทAdmin์€ main.tsx์—์„œ <AlertHost />๋ฅผ ๋ผ์šฐํ„ฐ์™€ ๊ฐ™์€ ๋ ˆ๋ฒจ์— ํ•œ ๋ฒˆ ๋งˆ์šดํŠธํ•ฉ๋‹ˆ๋‹ค.

<QueryClientProvider client={queryClient}>
  <RouterProvider router={router} />
  <Toaster />
  <AlertHost />
</QueryClientProvider>

์ง์ ‘ ํ˜ธ์ถœ

import { openAlert, openConfirm } from '@bx/shared';

await openAlert({
  title: '์•Œ๋ฆผ',
  message: '์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.',
  confirmText: 'ํ™•์ธ',
});

const confirmed = await openConfirm({
  title: '์‚ญ์ œ ํ™•์ธ',
  message: '์„ ํƒํ•œ ํ•ญ๋ชฉ์„ ์‚ญ์ œํ•˜์‹œ๊ฒ ์Šต๋‹ˆ๊นŒ?',
  confirmText: '์‚ญ์ œ',
  cancelText: '์œ ์ง€',
});

if (!confirmed) return;
  • openAlert๋Š” ํ™•์ธ ๋ฒ„ํŠผ๋งŒ ํ‘œ์‹œํ•˜๋ฉฐ, ๋‹ซํž ๋•Œ๊นŒ์ง€ ๊ธฐ๋‹ค๋ฆด ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • openConfirm์€ ๊ธฐ๋ณธ์ ์œผ๋กœ ํ™•์ธยท์ทจ์†Œ ๋ฒ„ํŠผ์„ ํ‘œ์‹œํ•˜๊ณ  Promise<boolean>์„ ๋ฐ˜ํ™˜ํ•ฉ๋‹ˆ๋‹ค.
  • Confirm์€ ํ™•์ธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅธ ๊ฒฝ์šฐ์—๋งŒ true์ด๋ฉฐ, ์ทจ์†ŒยทESCยท์˜ค๋ฒ„๋ ˆ์ด ํด๋ฆญ์€ false์ž…๋‹ˆ๋‹ค.
  • ์—ฌ๋Ÿฌ ์š”์ฒญ์€ queue์— ์Œ“์—ฌ ์ˆœ์„œ๋Œ€๋กœ ํ‘œ์‹œ๋ฉ๋‹ˆ๋‹ค.

API ์—๋Ÿฌ ์ค‘์•™ ์ฒ˜๋ฆฌ

createQueryClient()์˜ QueryCache์™€ MutationCache๊ฐ€ Query/Mutation ์‹คํŒจ๋ฅผ handleApiError()๋กœ ์ „๋‹ฌํ•ฉ๋‹ˆ๋‹ค. ๋ณ„๋„ ์ •์ฑ…์ด ์—†์œผ๋ฉด ์„œ๋ฒ„ ๋ฉ”์‹œ์ง€๋กœ ๊ณตํ†ต alert๋ฅผ ํ‘œ์‹œํ•˜๋ฉฐ, ๋™์‹œ์— ๊ฐ™์€ ๋ฉ”์‹œ์ง€๊ฐ€ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ฐœ์ƒํ•˜๋ฉด ํ•˜๋‚˜๋กœ ํ•ฉ์นฉ๋‹ˆ๋‹ค.

// ํ™”๋ฉด์—์„œ ์ง์ ‘ ์˜ค๋ฅ˜๋ฅผ ํ‘œ์‹œ
useQuery({
  ...queryOptions,
  meta: { error: { silent: true } },
});

// ๊ณตํ†ต alert ๋ฉ”์‹œ์ง€ ๋ณ€๊ฒฝ
useLogin({
  meta: {
    error: {
      message: '๋กœ๊ทธ์ธ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค. ์•„์ด๋”” ๋˜๋Š” ๋น„๋ฐ€๋ฒˆํ˜ธ๋ฅผ ํ™•์ธํ•ด์ฃผ์„ธ์š”.',
    },
  },
});

// alert๋ฅผ ๋‹ซ์€ ๋’ค ํ›„์ฒ˜๋ฆฌ
useMutation({
  ...mutationOptions,
  meta: { error: { onClose: () => navigate({ to: '/list' }) } },
});
meta.error ์˜ต์…˜ ์—ญํ• 
silent ๊ณตํ†ต alert๋ฅผ ํ‘œ์‹œํ•˜์ง€ ์•Š์Œ. boolean ๋˜๋Š” (error) => boolean ์‚ฌ์šฉ
ignoreCodes ์ง€์ •ํ•œ ์„œ๋ฒ„ ์—๋Ÿฌ ์ฝ”๋“œ๋งŒ ๊ณตํ†ต ์ฒ˜๋ฆฌ์—์„œ ์ œ์™ธ
message alert ๋ฉ”์‹œ์ง€ ๋ณ€๊ฒฝ. ๋ฌธ์ž์—ด ๋˜๋Š” (error) => string ์‚ฌ์šฉ
onClose ์‚ฌ์šฉ์ž๊ฐ€ ๊ณตํ†ต alert๋ฅผ ๋‹ซ์€ ๋’ค ์‹คํ–‰

์ธ์ฆ ์˜ค๋ฅ˜๋Š” HTTP ์ธํ„ฐ์…‰ํ„ฐ๊ฐ€ ์žฌ๋ฐœ๊ธ‰ยท๋กœ๊ทธ์•„์›ƒ์„ ์ฒ˜๋ฆฌํ•˜๊ณ , ์ทจ์†Œ๋œ ์š”์ฒญ๊ณผ TanStack Router redirect๋Š” ๊ณตํ†ต alert์—์„œ ์ œ์™ธํ•ฉ๋‹ˆ๋‹ค. ์—…๋ฌดยท์ธ์ฆ ์˜ค๋ฅ˜๋Š” ์žฌ์‹œ๋„ํ•˜์ง€ ์•Š์œผ๋ฉฐ networkยทtimeoutยทserver ์˜ค๋ฅ˜๋งŒ ํ•œ ๋ฒˆ ์žฌ์‹œ๋„ํ•ฉ๋‹ˆ๋‹ค. React Query๋ฅผ ๊ฑฐ์น˜์ง€ ์•Š๋Š” ์ง์ ‘ ํ˜ธ์ถœ์€ catch์—์„œ handleApiError(error, policy)๋ฅผ ํ˜ธ์ถœํ•ฉ๋‹ˆ๋‹ค.

Toast๋Š” ํ™•์ธ์ด ํ•„์š” ์—†๋Š” ์งง์€ ์„ฑ๊ณตยท์ƒํƒœ ์•Œ๋ฆผ์— ์‚ฌ์šฉํ•˜๊ณ , Alert๋Š” ์‚ฌ์šฉ์ž์˜ ํ™•์ธ์ด ํ•„์š”ํ•œ ์˜ค๋ฅ˜ยท์•ˆ๋‚ด, Confirm์€ ์‚ญ์ œ์ฒ˜๋Ÿผ ์‚ฌ์šฉ์ž ๊ฒฐ์ •์ด ํ•„์š”ํ•œ ์ž‘์—…์— ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ์‹ ๊ทœ ์ฝ”๋“œ์—์„œ๋Š” window.alertยทwindow.confirm ๋Œ€์‹  ๊ณตํ†ต API๋ฅผ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. ํ˜„์žฌ Admin ์‚ญ์ œ ํ™”๋ฉด์— ๋‚จ์•„ ์žˆ๋Š” native confirm์€ ์ˆœ์ฐจ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋Œ€์ƒ์ž…๋‹ˆ๋‹ค.


๐Ÿ”” ํ† ์ŠคํŠธ ์‹œ์Šคํ…œ

@bx/shared๊ฐ€ sonner๋ฅผ ์–‡๊ฒŒ ๊ฐ์‹ผ Toaster ์ปดํฌ๋„ŒํŠธ์™€ toast ํ•จ์ˆ˜๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ์„ธ ์•ฑ ๋ชจ๋‘ main.tsx์—์„œ <Toaster />๋ฅผ ๋ผ์šฐํ„ฐ์™€ ๊ฐ™์€ ๋ ˆ๋ฒจ์— ํ•œ ๋ฒˆ ๋งˆ์šดํŠธํ•˜๋ฉฐ, ํ˜ธ์ถœ์€ ์–ด๋””์„œ๋“  toast(...)๋กœ ํ•ฉ๋‹ˆ๋‹ค.

// apps/[app]/src/main.tsx
<QueryClientProvider client={queryClient}>
  <RouterProvider router={router} />
  <Toaster />
</QueryClientProvider>
import { toast } from '@bx/shared';

toast.success('์ €์žฅ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.');
toast.error('์ €์žฅ์— ์‹คํŒจํ–ˆ์Šต๋‹ˆ๋‹ค.');
  • ๊ธฐ๋ณธ๊ฐ’์€ position="bottom-center" ยท closeButton ํ™œ์„ฑ์ด๋ฉฐ, ํ•„์š” ์‹œ <Toaster />์— sonner props๋ฅผ ๊ทธ๋Œ€๋กœ ๋„˜๊ฒจ ๋ฎ์–ด์“ธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์•„์ด์ฝ˜์€ lucide-react ๊ธฐ๋ฐ˜์œผ๋กœ success/info/warning/error/loading์ด ๋ฏธ๋ฆฌ ์ง€์ •๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  • ์ƒ‰์ƒ์€ ์•ฑ๋ณ„ ํ…Œ๋งˆ ํ† ํฐ(--surface-elevated, --foreground, --border)์„ var()๋กœ ์ฐธ์กฐํ•˜๋ฏ€๋กœ, pc-web์˜ .darkยทadmin์˜ [data-admin-theme]ยทmobile-web์˜ light ๊ธฐ๋ณธ๊ฐ’์— ๋ž˜ํผ ์ˆ˜์ • ์—†์ด ๋”ฐ๋ผ๊ฐ‘๋‹ˆ๋‹ค.

๐Ÿ“˜ Git ์ปค๋ฐ‹ ์ปจ๋ฒค์…˜

ํ˜•์‹

Type(Scope): Subject

Type

Type ์„ค๋ช…
feat ์ƒˆ๋กœ์šด ๊ธฐ๋Šฅ ์ถ”๊ฐ€
fix ๋ฒ„๊ทธยท์—๋Ÿฌ ์ˆ˜์ •
docs ๋ฌธ์„œ ์ˆ˜์ • (README ๋“ฑ)
style ํฌ๋งทยท์Šคํƒ€์ผ (๊ธฐ๋Šฅ ์˜ํ–ฅ ์—†์Œ)
refactor ๊ตฌ์กฐ ๋ฆฌํŒฉํ„ฐ๋ง
build ๋นŒ๋“œ ์„ค์ • (Vite, pnpm ๋“ฑ)
chore ํŒจํ‚ค์ง€ ์—…๋ฐ์ดํŠธยท์žก๋ฌด

๊ธฐ์ˆ  ๋ฌธ์˜ ๋ฐ ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์„  ์ œ์•ˆ์€ ๊ฐœ๋ฐœ ๋ฆฌ๋“œ์—๊ฒŒ ์ „๋‹ฌํ•ด ์ฃผ์„ธ์š”.

About

No description, website, or topics provided.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages