d3ro-voice/apps/web/.next/static/chunks/949-25a59c4f9171c905.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
4.8 KiB
JavaScript

"use strict";(self.webpackChunk_N_E=self.webpackChunk_N_E||[]).push([[949],{4620:(e,t,o)=>{o.d(t,{A:()=>l});let l=function(e){return"string"==typeof e}},6678:(e,t,o)=>{o.d(t,{A:()=>n});var l=o(4620);let n=function(e,t,o){return void 0===e||(0,l.A)(e)?t:{...t,ownerState:{...t.ownerState,...o}}}},6924:(e,t,o)=>{o.d(t,{A:()=>l});let l=function(e,t,o){return"function"==typeof e?e(t,o):e}},7814:(e,t,o)=>{o.d(t,{A:()=>l});let l=function(e,t=[]){if(void 0===e)return{};let o={};return Object.keys(e).filter(o=>o.match(/^on[A-Z]/)&&"function"==typeof e[o]&&!t.includes(o)).forEach(t=>{o[t]=e[t]}),o}},7972:(e,t,o)=>{o.d(t,{A:()=>a});var l=o(3786),n=o(6678),r=o(6924),i=o(9826);function a(e,t){let{className:o,elementType:a,ownerState:s,externalForwardedProps:c,internalForwardedProps:u,shouldForwardComponentProp:f=!1,...d}=t,{component:p,slots:m={[e]:void 0},slotProps:v={[e]:void 0},...y}=c,h=m[e]||a,S=(0,r.A)(v[e],s),{props:{component:A,...g},internalRef:z}=(0,i.A)({className:o,...d,externalForwardedProps:"root"===e?y:void 0,externalSlotProps:S}),N=(0,l.A)(z,null==S?void 0:S.ref,t.ref),b="root"===e?A||p:A,k=(0,n.A)(h,{..."root"===e&&!p&&!m[e]&&u,..."root"!==e&&!m[e]&&u,...g,...b&&!f&&{as:b},...b&&f&&{component:b},ref:N},s);return[h,k]}},8703:(e,t,o)=>{o.d(t,{A:()=>y});var l=o(7620),n=o(2902),r=o(7138),i=o(9407),a=o(8613),s=o(9468),c=o(5120),u=o(8020),f=o(3664);function d(e){return(0,f.Ay)("MuiSvgIcon",e)}(0,u.A)("MuiSvgIcon",["root","colorPrimary","colorSecondary","colorAction","colorError","colorDisabled","fontSizeInherit","fontSizeSmall","fontSizeMedium","fontSizeLarge"]);var p=o(4568);let m=(0,a.default)("svg",{name:"MuiSvgIcon",slot:"Root",overridesResolver:(e,t)=>{let{ownerState:o}=e;return[t.root,"inherit"!==o.color&&t["color".concat((0,i.A)(o.color))],t["fontSize".concat((0,i.A)(o.fontSize))]]}})((0,s.A)(e=>{var t,o,l,n,r,i,a,s,c,u,f,d,p,m,v,y,h,S;let{theme:A}=e;return{userSelect:"none",width:"1em",height:"1em",display:"inline-block",flexShrink:0,transition:null==(n=A.transitions)||null==(l=n.create)?void 0:l.call(n,"fill",{duration:null==(o=(null!=(v=A.vars)?v:A).transitions)||null==(t=o.duration)?void 0:t.shorter}),variants:[{props:e=>!e.hasSvgAsChild,style:{fill:"currentColor"}},{props:{fontSize:"inherit"},style:{fontSize:"inherit"}},{props:{fontSize:"small"},style:{fontSize:(null==(i=A.typography)||null==(r=i.pxToRem)?void 0:r.call(i,20))||"1.25rem"}},{props:{fontSize:"medium"},style:{fontSize:(null==(s=A.typography)||null==(a=s.pxToRem)?void 0:a.call(s,24))||"1.5rem"}},{props:{fontSize:"large"},style:{fontSize:(null==(u=A.typography)||null==(c=u.pxToRem)?void 0:c.call(u,35))||"2.1875rem"}},...Object.entries((null!=(y=A.vars)?y:A).palette).filter(e=>{let[,t]=e;return t&&t.main}).map(e=>{var t,o,l;let[n]=e;return{props:{color:n},style:{color:null==(o=(null!=(l=A.vars)?l:A).palette)||null==(t=o[n])?void 0:t.main}}}),{props:{color:"action"},style:{color:null==(d=(null!=(h=A.vars)?h:A).palette)||null==(f=d.action)?void 0:f.active}},{props:{color:"disabled"},style:{color:null==(m=(null!=(S=A.vars)?S:A).palette)||null==(p=m.action)?void 0:p.disabled}},{props:{color:"inherit"},style:{color:void 0}}]}})),v=l.forwardRef(function(e,t){let o=(0,c.b)({props:e,name:"MuiSvgIcon"}),{children:a,className:s,color:u="inherit",component:f="svg",fontSize:v="medium",htmlColor:y,inheritViewBox:h=!1,titleAccess:S,viewBox:A="0 0 24 24",...g}=o,z=l.isValidElement(a)&&"svg"===a.type,N={...o,color:u,component:f,fontSize:v,instanceFontSize:e.fontSize,inheritViewBox:h,viewBox:A,hasSvgAsChild:z},b={};h||(b.viewBox=A);let k=(e=>{let{color:t,fontSize:o,classes:l}=e,n={root:["root","inherit"!==t&&"color".concat((0,i.A)(t)),"fontSize".concat((0,i.A)(o))]};return(0,r.A)(n,d,l)})(N);return(0,p.jsxs)(m,{as:f,className:(0,n.A)(k.root,s),focusable:"false",color:y,"aria-hidden":!S||void 0,role:S?"img":void 0,ref:t,...b,...g,...z&&a.props,ownerState:N,children:[z?a.props.children:a,S?(0,p.jsx)("title",{children:S}):null]})});function y(e,t){function o(t,o){return(0,p.jsx)(v,{"data-testid":void 0,ref:o,...t,children:e})}return o.muiName=v.muiName,l.memo(l.forwardRef(o))}v.muiName="SvgIcon"},9826:(e,t,o)=>{o.d(t,{A:()=>i});var l=o(2902),n=o(7814);let r=function(e){if(void 0===e)return{};let t={};return Object.keys(e).filter(t=>!(t.match(/^on[A-Z]/)&&"function"==typeof e[t])).forEach(o=>{t[o]=e[o]}),t},i=function(e){let{getSlotProps:t,additionalProps:o,externalSlotProps:i,externalForwardedProps:a,className:s}=e;if(!t){let e=(0,l.A)(o?.className,s,a?.className,i?.className),t={...o?.style,...a?.style,...i?.style},n={...o,...a,...i};return e.length>0&&(n.className=e),Object.keys(t).length>0&&(n.style=t),{props:n,internalRef:void 0}}let c=(0,n.A)({...a,...i}),u=r(i),f=r(a),d=t(c),p=(0,l.A)(d?.className,o?.className,s,a?.className,i?.className),m={...d?.style,...o?.style,...a?.style,...i?.style},v={...d,...o,...f,...u};return p.length>0&&(v.className=p),Object.keys(m).length>0&&(v.style=m),{props:v,internalRef:d.ref}}}}]);