d3ro-voice/apps/web/.next/static/chunks/app/login/page-b71b3729d311a739.js
yunchan8804 61a96b3e9b feat(V2-6/V2-7/V2-8): Mobile MVP + Teams + Billing 스캐폴딩
V2-6 — Mobile (Expo) MVP
- apps/mobile/ 신규, npm workspace에서 제외 (Expo deps 부담 회피)
- Expo SDK 51 + Expo Router + AsyncStorage Supabase 클라이언트
- 화면: index/login/(tabs)/{meetings,record,profile}
- expo-av로 녹음 → Edge Function stt-proxy 호출
- expo-web-browser + expo-linking으로 OAuth 콜백 처리
- README에 setup/EAS build 가이드
- 루트 package.json workspaces를 명시 나열로 변경 (apps/mobile 제외)

V2-7 — 팀 기능 (Web)
- apps/web/src/app/teams/page.tsx — 가입한 팀 카드 그리드
- apps/web/src/app/teams/[id]/page.tsx — 멤버 + 공유 회의
- components/teams/create-team-form.tsx — 팀 생성 + owner 자동 team_members
- components/teams/invite-member-form.tsx — user_id 직접 초대 (V2-7b에서 invite flow)
- Sidebar에 Teams/Billing 메뉴 + 아이콘
- ko.json에 nav.teams/nav.billing 키 추가
- V2-2 teams/team_members RLS 활용

V2-8 — 결제 스캐폴딩
- apps/web/src/app/billing/page.tsx — Free/Pro/Team 가격표 + 현재 구독
- components/billing/checkout-button.tsx — Edge Function 호출 후 redirect
- server/supabase/functions/stripe-checkout/index.ts:
  - JWT 인증 -> 기존 customer 조회/생성 -> Checkout Session 생성
  - subscriptions 테이블에 customer_id upsert
- server/supabase/functions/stripe-webhook/index.ts:
  - checkout.session.completed -> subscriptions tier=pro/team active
  - customer.subscription.updated/created -> status/period 업데이트
  - customer.subscription.deleted -> tier=free, status=canceled
  - signature 검증은 placeholder (V2-8b에서 정식)
- server/supabase/config.toml에 두 함수 등록 (webhook verify_jwt=false)

검증:
- desktop typecheck OK (회귀 없음)
- web typecheck OK
- web next build OK (11 라우트)
- mobile은 별도 install 필요 (workspace 제외)

memory/project_status.md 갱신 — V2 마스터 플랜 전 페이즈 로컬 완료
2026-04-09 16:39:54 +09:00

1 line
No EOL
6.5 KiB
JavaScript

(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[520],{850:(e,t,n)=>{"use strict";n.d(t,{default:()=>s});var l=n(8530),r=n(1580);function s(e){let{props:t,name:n,defaultTheme:s,themeId:a}=e,o=(0,r.default)(s);return a&&(o=o[a]||o),(0,l.A)({theme:o,name:n,props:t})}},1009:(e,t,n)=>{"use strict";n.r(t),n.d(t,{default:()=>b});var l=n(4568),r=n(7541),s=n(7620),a=n(1284),o=n(5754),i=n(6032),u=n(2622),c=n(8703);let d=(0,c.A)((0,l.jsx)("path",{d:"M12.545,10.239v3.821h5.445c-0.712,2.315-2.647,3.972-5.445,3.972c-3.332,0-6.033-2.701-6.033-6.032s2.701-6.032,6.033-6.032c1.498,0,2.866,0.549,3.921,1.453l2.814-2.814C17.503,2.988,15.139,2,12.545,2C7.021,2,2.543,6.477,2.543,12s4.478,10,10.002,10c8.396,0,10.249-7.85,9.426-11.748L12.545,10.239z"}),"Google"),f=(0,c.A)((0,l.jsx)("path",{d:"M12 1.27a11 11 0 00-3.48 21.46c.55.09.73-.28.73-.55v-1.84c-3.03.64-3.67-1.46-3.67-1.46-.55-1.29-1.28-1.65-1.28-1.65-.92-.65.1-.65.1-.65 1.1 0 1.73 1.1 1.73 1.1.92 1.65 2.57 1.2 3.21.92a2 2 0 01.64-1.47c-2.47-.27-5.04-1.19-5.04-5.5 0-1.1.46-2.1 1.2-2.84a3.76 3.76 0 010-2.93s.91-.28 3.11 1.1c1.8-.49 3.7-.49 5.5 0 2.1-1.38 3.02-1.1 3.02-1.1a3.76 3.76 0 010 2.93c.83.74 1.2 1.74 1.2 2.94 0 4.21-2.57 5.13-5.04 5.4.45.37.82.92.82 2.02v3.03c0 .27.1.64.73.55A11 11 0 0012 1.27"}),"GitHub");var h=n(1441),p=n(3791),v=n(7740),g=n(1037),x=n(1598);function b(){var e,t,n,c;let b=(0,r.useRouter)(),{user:m,loading:A}=(0,x.A)(),{t:k}=(0,v.s9)(),[j,y]=(0,s.useState)(null),[S,C]=(0,s.useState)(!1),w=(0,g.$)();async function M(e){if(!w)return void y("Supabase 환경변수가 설정되지 않았습니다. apps/web/env.example.txt를 참고하세요.");y(null),C(!0);try{let t=(0,g.c)(),n="".concat(window.location.origin,"/auth/callback"),{error:l}=await t.auth.signInWithOAuth({provider:e,options:{redirectTo:n}});l&&(y(l.message),C(!1))}catch(e){y(e instanceof Error?e.message:"Unknown error"),C(!1)}}return(0,s.useEffect)(()=>{!A&&m&&b.replace("/dashboard")},[A,m,b]),(0,l.jsx)(a.default,{sx:{minHeight:"100vh",display:"flex",alignItems:"center",justifyContent:"center",bgcolor:p.sM.bg.app,p:4},children:(0,l.jsx)(h.Uu,{sx:{maxWidth:420,width:"100%",p:4},children:(0,l.jsxs)(o.default,{spacing:3,children:[(0,l.jsxs)(a.default,{sx:{textAlign:"center"},children:[(0,l.jsx)(h.Rk,{variant:"title",children:"D3RO VOICE"}),(0,l.jsx)(a.default,{sx:{mt:1,...(0,p.kR)("label"),color:p.sM.text.secondary},children:null!=(e=k("login.subtitle"))?e:"AI 음성 어시스턴트"})]}),!w&&(0,l.jsx)(i.A,{severity:"warning",variant:"outlined",children:"Supabase 환경변수가 설정되지 않았습니다. 로그인은 env 설정 후 가능합니다."}),j&&(0,l.jsx)(i.A,{severity:"error",variant:"outlined",children:j}),(0,l.jsxs)(o.default,{spacing:2,children:[(0,l.jsx)(u.A,{fullWidth:!0,variant:"contained",size:"large",startIcon:(0,l.jsx)(d,{}),disabled:S||!w,onClick:()=>void M("google"),children:null!=(t=k("login.google"))?t:"Google로 계속하기"}),(0,l.jsx)(u.A,{fullWidth:!0,variant:"outlined",size:"large",startIcon:(0,l.jsx)(f,{}),disabled:S||!w,onClick:()=>void M("github"),children:null!=(n=k("login.github"))?n:"GitHub로 계속하기"})]}),(0,l.jsx)(a.default,{sx:{textAlign:"center",color:p.sM.text.label,fontSize:12},children:null!=(c=k("login.terms"))?c:"계속 진행하면 이용약관 및 개인정보 처리방침에 동의합니다."})]})})})}},1441:(e,t,n)=>{"use strict";n.d(t,{Rk:()=>r.PhosphorText,Uu:()=>l.MetalCard}),n(4070),n(9006),n(5281),n(6652);var l=n(9502),r=n(215);n(2517),n(1459),n(984)},1598:(e,t,n)=>{"use strict";n.d(t,{A:()=>i,AuthProvider:()=>o});var l=n(4568),r=n(7620),s=n(1037);let a=(0,r.createContext)({session:null,user:null,loading:!0});function o(e){var t;let{children:n}=e,[o,i]=(0,r.useState)(null),[u,c]=(0,r.useState)(!0);return(0,r.useEffect)(()=>{let e=(0,s.c)();e.auth.getSession().then(e=>{let{data:t}=e;i(t.session)}).finally(()=>{c(!1)});let{data:{subscription:t}}=e.auth.onAuthStateChange((e,t)=>{i(t)});return()=>{t.unsubscribe()}},[]),(0,l.jsx)(a.Provider,{value:{session:o,user:null!=(t=null==o?void 0:o.user)?t:null,loading:u},children:n})}function i(){return(0,r.useContext)(a)}},3292:(e,t,n)=>{"use strict";n.d(t,{default:()=>m});var l=n(7620),r=n(2902),s=n(2028),a=n(3664),o=n(7138),i=n(4294),u=n(850),c=n(9166),d=n(7537),f=n(2874),h=n(2237),p=n(4568);let v=(0,d.A)(),g=(0,i.A)("div",{name:"MuiStack",slot:"Root"});function x(e){return(0,u.default)({props:e,name:"MuiStack",defaultTheme:v})}let b=e=>{let{ownerState:t,theme:n}=e,l={display:"flex",flexDirection:"column",...(0,f.NI)({theme:n},(0,f.kW)({values:t.direction,breakpoints:n.breakpoints.values}),e=>({flexDirection:e}))};if(t.spacing){let e=(0,h.LX)(n),r=Object.keys(n.breakpoints.values).reduce((e,n)=>(("object"==typeof t.spacing&&null!=t.spacing[n]||"object"==typeof t.direction&&null!=t.direction[n])&&(e[n]=!0),e),{}),a=(0,f.kW)({values:t.direction,base:r}),o=(0,f.kW)({values:t.spacing,base:r});"object"==typeof a&&Object.keys(a).forEach((e,t,n)=>{if(!a[e]){let l=t>0?a[n[t-1]]:"column";a[e]=l}}),l=(0,s.A)(l,(0,f.NI)({theme:n},o,(n,l)=>t.useFlexGap?{gap:(0,h._W)(e,n)}:{"& > :not(style):not(style)":{margin:0},"& > :not(style) ~ :not(style)":{["margin".concat({row:"Left","row-reverse":"Right",column:"Top","column-reverse":"Bottom"}[l?a[l]:t.direction])]:(0,h._W)(e,n)}}))}return(0,f.iZ)(n.breakpoints,l)};function m(){let e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},{createStyledComponent:t=g,useThemeProps:n=x,componentName:s="MuiStack"}=e,i=t(b);return l.forwardRef(function(e,t){let u=n(e),{component:d="div",direction:f="column",spacing:h=0,divider:v,children:g,className:x,useFlexGap:b=!1,...m}=(0,c.A)(u),A=(0,o.A)({root:["root"]},e=>(0,a.Ay)(s,e),{});return(0,p.jsx)(i,{as:d,ownerState:{direction:f,spacing:h,useFlexGap:b},ref:t,className:(0,r.A)(A.root,x),...m,children:v?function(e,t){let n=l.Children.toArray(e).filter(Boolean);return n.reduce((e,r,s)=>(e.push(r),s<n.length-1&&e.push(l.cloneElement(t,{key:"separator-".concat(s)})),e),[])}(g,v):g})})}},4294:(e,t,n)=>{"use strict";n.d(t,{A:()=>l});let l=(0,n(8318).Ay)()},5754:(e,t,n)=>{"use strict";n.d(t,{default:()=>a});var l=n(3292),r=n(8613),s=n(5120);let a=(0,l.default)({createStyledComponent:(0,r.default)("div",{name:"MuiStack",slot:"Root"}),useThemeProps:e=>(0,s.b)({props:e,name:"MuiStack"})})},8119:(e,t,n)=>{Promise.resolve().then(n.bind(n,1009))},8530:(e,t,n)=>{"use strict";n.d(t,{A:()=>r});var l=n(3450);function r(e){let{theme:t,name:n,props:r}=e;return t&&t.components&&t.components[n]&&t.components[n].defaultProps?(0,l.A)(t.components[n].defaultProps,r):r}}},e=>{e.O(0,[385,77,988,189,949,32,791,803,910,587,18,358],()=>e(e.s=8119)),_N_E=e.O()}]);