PageSourceSearch

https://www.freecanvas.ai.kr/assets/AdminPromotions-DnBEYkon.js

js freecanvas.ai.kr collected 2026-09-24 15:05:12 UTC 18,252 bytes, 1 lines download raw bytes

1import{r as d,j as e}from"./vendor-react-Hmgc0IJo.js";import{s as X}from"./index-CHgVLYjI.js";import{P as Y,a as Z,g as y,b as ee,u as te,c as se,t as ae}from"./promoCodeService-C9GimteN.js";import{A as le,a as ne}from"./AdminUserLookupModal-BQ61-__a.js";import{getAllSubscriptions as re}from"./subscriptionService-Bkle-r7y.js";import{getAllUserProfiles as ie}from"./userService-DFRgzeX4.js";import"./vendor-firebase-Dak2Hxsr.js";import"./firebase-FBroemQC.js";import"./firestoreService-BMfNrRSy.js";import"./detailHtml-B1LfqO6-.js";import"./storageService-BSSwqqbq.js";import"./brandPresetService-DgH8D9_A.js";import"./userNotifications-BFSmaFGp.js";const B={all:"전체 플랜",basic:"Basic",pro:"Pro",max:"Max",exclusive:"Exclusive"},de=l=>l?l==="크리에이티브크루"?"크리에이티브크루":l:null,oe=l=>l?new Intl.DateTimeFormat("ko-KR",{year:"numeric",month:"2-digit",day:"2-digit"}).format(l):"-",ce=l=>l?l==="basic"?"Basic":l==="pro"?"Pro":l==="max"?"Max":"Exclusive":"-",N=l=>!l||l.length===0?"전체 플랜":l.map(h=>B[h]).join(", "),v=l=>!l||l.length===0?"전체 카테고리":l.join(", "),ue=l=>l?l.status==="active"&&l.currentPeriodEnd>new Date:!1,$=()=>({code:"",name:"",description:"",type:"discount",status:"active",discountPercent:50,discountMonths:0,targetPlan:"all",targetPlans:[],targetCategories:[]}),ke=({onClose:l,adminEmail:h})=>{const[P,D]=d.useState([]),[w,z]=d.useState([]),[p,R]=d.useState(new Map),[O,k]=d.useState(!0),[M,u]=d.useState(null),[f,E]=d.useState(!1),[_,S]=d.useState(null),[U,F]=d.useState({}),[x,C]=d.useState(null),[a,i]=d.useState($()),[A,T]=d.useState(null),j=async()=>{try{k(!0),u(null);const[t,s,n]=await Promise.all([ee(),re(),ie()]);D(t),z(s.filter(o=>ue(o))),R(n)}catch(t){console.error("프로모션 코드 조회 실패:",t),u((t==null?void 0:t.message)||"프로모션 코드를 불러오지 못했습니다.")}finally{k(!1)}};d.useEffect(()=>{j()},[]);const G=d.useMemo(()=>{const t=new Map;return w.forEach(s=>{var b;const n=(b=s.promoCode)==null?void 0:b.trim().toLowerCase();if(!n)return;const o={subscription:s,profile:p.get(s.userId)},m=t.get(n)||[];m.push(o),t.set(n,m)}),t.forEach(s=>{s.sort((n,o)=>o.subscription.createdAt.getTime()-n.subscription.createdAt.getTime())}),t},[w,p]),L=()=>{C(null),i($()),u(null)},K=t=>{C(t),u(null),i({code:t.code,name:t.name,description:t.description||"",type:t.type,status:t.status,discountPercent:t.discountPercent||50,discountMonths:t.discountMonths||0,targetPlan:t.targetPlan||(t.type==="free"?"pro":"all"),targetPlans:y(t),targetCategories:t.targetCategories||[]})},W=t=>{i(s=>{const n=s.targetPlans.includes(t);return{...s,targetPlans:n?s.targetPlans.filter(o=>o!==t):[...s.targetPlans,t]}})},q=t=>{i(s=>{const n=s.targetCategories.includes(t);return{...s,targetCategories:n?s.targetCategories.filter(o=>o!==t):[...s.targetCategories,t]}})},H=async t=>{if(t.preventDefault(),!a.code.trim()){u("프로모션 코드를 입력해주세요.");return}if(!a.name.trim()){u("관리용 이름을 입력해주세요.");return}if(a.type==="free"&&a.targetPlans.length===0){u("무료 제공 코드는 지급할 플랜을 하나 이상 선택해주세요.");return}if(a.type==="discount"&&(a.discountPercent<1||a.discountPercent>100)){u("할인율은 1%부터 100% 사이로 입력해주세요.");return}if(a.type==="discount"&&(a.discountMonths<0||a.discountMonths>36||!Number.isInteger(a.discountMonths))){u("할인 적용 개월 수는 0(계속)부터 36 사이의 정수로 입력해주세요.");return}try{E(!0),u(null);const s={code:a.code,name:a.name,description:a.description,type:a.type,status:a.status,discountPercent:a.type==="discount"?a.discountPercent:void 0,discountMonths:a.type==="discount"&&a.discountMonths>=1?a.discountMonths:void 0,targetPlan:a.targetPlans.length===0?"all":a.targetPlans.length===1?a.targetPlans[0]:void 0,targetPlans:a.targetPlans,targetCategories:a.targetCategories};x?await te(x,s):await se({...s,createdByEmail:h}),L(),await j()}catch(s){console.error("프로모션 코드 저장 실패:",s),u((s==null?void 0:s.message)||"프로모션 코드 저장 중 오류가 발생했습니다.")}finally{E(!1)}},J=async t=>{try{S(t.id||t.normalizedCode),await ae(t),await j()}catch(s){console.error("프로모션 상태 변경 실패:",s),X((s==null?void 0:s.message)||"상태 변경 중 오류가 발생했습니다.")}finally{S(null)}};return e.jsxs("div",{className:"fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4",children:[e.jsxs("div",{className:"bg-white rounded-2xl shadow-xl max-w-6xl w-full max-h-[90vh] overflow-hidden flex flex-col",children:[e.jsxs("div",{className:"p-6 border-b border-slate-200 flex items-center justify-between",children:[e.jsxs("div",{children:[e.jsx("h2",{className:"text-2xl font-bold text-slate-900",children:"프로모션 코드 관리"}),e.jsx("p",{className:"text-sm text-slate-600 mt-1",children:"코드 생성과 기존 코드 수정, 플랜/카테고리 조건 설정, 활성 상태 관리를 한곳에서 처리합니다."})]}),e.jsx("button",{"aria-label":"닫기",onClick:l,className:"text-slate-400 hover:text-slate-600 transition-colors",children:e.jsx("svg",{className:"w-6 h-6",fill:"none",stroke:"currentColor",viewBox:"0 0 24 24",children:e.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",strokeWidth:2,d:"M6 18L18 6M6 6l12 12"})})})]}),e.jsxs("div",{className:"grid grid-cols-1 xl:grid-cols-[360px_1fr] gap-0 flex-1 min-h-0",children:[e.jsx("div",{className:"border-r border-slate-200 p-6 overflow-y-auto",children:e.jsxs("form",{onSubmit:H,className:"space-y-4",children:[e.jsxs("div",{className:"rounded-xl border border-slate-200 bg-slate-50 px-4 py-3",children:[e.jsx("p",{className:"text-sm font-semibold text-slate-900",children:x?`프로모션 수정: ${x.code}`:"새 프로모션 생성"}),e.jsx("p",{className:"text-xs text-slate-500 mt-1",children:x?x.id?"기존 저장된 프로모션 설정을 수정합니다.":"기존 기본 코드를 수정하면 DB 관리 대상으로 전환됩니다.":"코드 생성 후 플랜, 상태, 카테고리 조건을 바로 설정할 수 있습니다."})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"프로모션 코드"}),e.jsx("input",{value:a.code,onChange:t=>i(s=>({...s,code:t.target.value})),placeholder:"예: spring50",disabled:!!x,className:"w-full px-4 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-slate-100 disabled:text-slate-500"}),x&&e.jsx("p",{className:"text-xs text-slate-500 mt-2",children:"사용 중인 구독 연결을 유지하기 위해 기존 코드 값은 수정하지 않도록 고정했습니다."})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"관리용 이름"}),e.jsx("input",{value:a.name,onChange:t=>i(s=>({...s,name:t.target.value})),placeholder:"예: 봄맞이 50% 할인",className:"w-full px-4 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"설명"}),e.jsx("textarea",{value:a.description,onChange:t=>i(s=>({...s,description:t.target.value})),rows:3,placeholder:"내부 관리용 설명",className:"w-full px-4 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 resize-none"})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-3",children:[e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"유형"}
1),e.jsxs("select",{value:a.type,onChange:t=>i(s=>{const n=t.target.value;return{...s,type:n,targetPlan:n==="free"&&s.targetPlan==="all"?"pro":s.targetPlan,targetPlans:n==="free"&&s.targetPlans.length===0?["pro"]:s.targetPlans}}),className:"w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500",children:[e.jsx("option",{value:"discount",children:"할인 코드"}),e.jsx("option",{value:"free",children:"무료 제공 코드"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"상태"}),e.jsxs("select",{value:a.status,onChange:t=>i(s=>({...s,status:t.target.value})),className:"w-full px-3 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500",children:[e.jsx("option",{value:"active",children:"활성"}),e.jsx("option",{value:"inactive",children:"비활성"})]})]})]}),e.jsxs("div",{className:"grid grid-cols-1 gap-3",children:[e.jsxs("div",{className:"rounded-lg border border-slate-200 bg-slate-50 px-3 py-3",children:[e.jsx("p",{className:"text-sm font-medium text-slate-700",children:"대상 플랜"}),e.jsx("p",{className:"text-xs text-slate-500 mt-1",children:"선택한 플랜만 허용됩니다. 할인 코드는 아무것도 선택하지 않으면 전체 플랜에 적용됩니다. 무료 제공 코드는 하나 이상 선택해야 합니다."}),a.type==="discount"&&e.jsx("button",{type:"button",onClick:()=>i(t=>({...t,targetPlans:[]})),className:`mt-3 inline-flex rounded-full px-3 py-1 text-xs font-medium transition-colors ${a.targetPlans.length===0?"bg-slate-900 text-white":"bg-white text-slate-600 border border-slate-300 hover:bg-slate-100"}`,children:"전체 플랜"}),e.jsx("div",{className:"grid grid-cols-2 gap-2 mt-3",children:Y.map(t=>{const s=a.targetPlans.includes(t);return e.jsxs("label",{className:`flex items-center gap-2 rounded-lg border px-3 py-2 text-sm transition-colors ${s?"border-blue-500 bg-blue-50 text-blue-700":"border-slate-200 bg-white text-slate-700"}`,children:[e.jsx("input",{type:"checkbox",checked:s,onChange:()=>W(t),className:"h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500"}),e.jsx("span",{children:B[t]})]},t)})}),e.jsxs("p",{className:"text-xs text-slate-500 mt-2",children:["현재 설정: ",N(a.targetPlans)]})]}),e.jsxs("div",{className:"rounded-lg border border-slate-200 bg-slate-50 px-3 py-3",children:[e.jsx("p",{className:"text-sm font-medium text-slate-700",children:"카테고리 적용 방식"}),e.jsx("p",{className:"text-xs text-slate-500 mt-1",children:"선택한 카테고리만 허용됩니다. 아무것도 선택하지 않으면 전체 카테고리에 적용됩니다."}),e.jsx("button",{type:"button",onClick:()=>i(t=>({...t,targetCategories:[]})),className:`mt-3 inline-flex rounded-full px-3 py-1 text-xs font-medium transition-colors ${a.targetCategories.length===0?"bg-slate-900 text-white":"bg-white text-slate-600 border border-slate-300 hover:bg-slate-100"}`,children:"전체 카테고리"})]})]}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"대상 카테고리"}),e.jsx("div",{className:"grid grid-cols-2 gap-2 rounded-xl border border-slate-200 p-3",children:Z.map(t=>{const s=a.targetCategories.includes(t);return e.jsxs("label",{className:`flex items-center gap-2 rounded-lg border px-3 py-2 text-sm transition-colors ${s?"border-blue-500 bg-blue-50 text-blue-700":"border-slate-200 bg-white text-slate-700"}`,children:[e.jsx("input",{type:"checkbox",checked:s,onChange:()=>q(t),className:"h-4 w-4 rounded border-slate-300 text-blue-600 focus:ring-blue-500"}),e.jsx("span",{children:t})]},t)})}),e.jsxs("p",{className:"text-xs text-slate-500 mt-2",children:["현재 설정: ",v(a.targetCategories)]})]}),a.type==="discount"&&e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"할인율 (%)"}),e.jsx("input",{type:"number",min:1,max:100,value:a.discountPercent,onChange:t=>i(s=>({...s,discountPercent:Number(t.target.value)||0})),className:"w-full px-4 py-2 border border-slate-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"})]}),a.type==="discount"&&e.jsxs("div",{children:[e.jsx("label",{className:"block text-sm font-medium text-slate-700 mb-2",children:"할인 적용 개월 수"}),e.jsx("input",{type:"number",min:0,max:36,value:a.discountMonths,onChange:t=>i(s=>({...s,discountMonths:Math.max(0,Math.floor(Number(t.target.value)||0))}
1)),className:"w-full rounded-lg border border-slate-300 px-4 py-2 focus:outline-none focus:ring-2 focus:ring-slate-400"}),e.jsx("p",{className:"text-xs text-slate-500 mt-2",children:"0이면 갱신 내내 할인, 1이면 첫 결제만 할인, N이면 첫 N회 결제까지 할인 뒤 정가로 돌아갑니다. 이미 시작된 구독에는 소급되지 않습니다."})]}),M&&e.jsx("div",{className:"rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700",children:M}),e.jsxs("div",{className:"flex gap-2",children:[e.jsx("button",{type:"submit",disabled:f,className:"flex-1 px-4 py-3 bg-slate-900 text-white font-semibold rounded-lg hover:bg-slate-800 disabled:opacity-50",children:f?"저장 중...":x?"프로모션 저장":"프로모션 코드 생성"}),x&&e.jsx("button",{type:"button",onClick:L,disabled:f,className:"px-4 py-3 rounded-lg border border-slate-300 text-sm font-medium text-slate-700 hover:bg-slate-50 disabled:opacity-50",children:"취소"})]})]})}),e.jsx("div",{className:"p-6 overflow-y-auto",children:O?e.jsx("div",{className:"text-sm text-slate-500",children:"프로모션 코드를 불러오는 중입니다..."}):e.jsxs("div",{className:"space-y-4",children:[P.map(t=>{const s=t.id||t.normalizedCode,n=!t.id,o=_===s,m=G.get(t.normalizedCode)||[],b=!!U[s];return e.jsxs("div",{className:"rounded-xl border border-slate-200 p-4",children:[e.jsxs("div",{className:"flex flex-col lg:flex-row lg:items-center lg:justify-between gap-4",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex flex-wrap items-center gap-2 mb-2",children:[e.jsx("span",{className:"rounded-full bg-slate-900 px-3 py-1 text-xs font-semibold text-white",children:t.code}),e.jsx("span",{className:`rounded-full px-3 py-1 text-xs font-medium ${t.status==="active"?"bg-green-100 text-green-700":"bg-slate-200 text-slate-700"}`,children:t.status==="active"?"활성":"비활성"}),e.jsx("span",{className:"rounded-full bg-blue-50 px-3 py-1 text-xs font-medium text-blue-700",children:t.type==="discount"?`${t.discountPercent||0}% 할인${t.discountMonths?` · 첫 ${t.discountMonths}개월`:" · 계속"}`:"무료 제공"}),e.jsx("span",{className:"rounded-full bg-indigo-50 px-3 py-1 text-xs font-medium text-indigo-700",children:N(y(t))}),e.jsx("span",{className:"rounded-full bg-purple-50 px-3 py-1 text-xs font-medium text-purple-700",children:v(t.targetCategories)}),e.jsxs("span",{className:"rounded-full bg-emerald-50 px-3 py-1 text-xs font-medium text-emerald-700",children:["사용 중 ",m.length,"명"]}),n&&e.jsx("span",{className:"rounded-full bg-amber-100 px-3 py-1 text-xs font-medium text-amber-700",children:"기존 코드"})]}),e.jsx("h3",{className:"text-lg font-bold text-slate-900",children:t.name}),e.jsx("p",{className:"text-sm text-slate-600 mt-1",children:t.description||"설명 없음"}),e.jsxs("p",{className:"text-xs text-slate-500 mt-2",children:["대상 플랜: ",N(y(t))," · 대상 카테고리: ",v(t.targetCategories),t.createdByEmail?` · 생성자 ${t.createdByEmail}`:""]})]}),e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("button",{type:"button",onClick:()=>K(t),className:"px-4 py-2 rounded-lg border border-slate-300 text-sm font-medium text-slate-700 hover:bg-slate-50",children:"수정"}),e.jsx("button",{type:"button",onClick:()=>F(r=>({...r,[s]:!r[s]})),className:"px-4 py-2 rounded-lg border border-slate-300 text-sm font-medium text-slate-700 hover:bg-slate-50",children:b?"사용자 접기":`사용자 보기 (${m.length})`}),e.jsx("button",{type:"button",onClick:()=>void J(t),disabled:o,className:"px-4 py-2 rounded-lg border border-slate-300 text-sm font-medium text-slate-700 hover:bg-slate-50 disabled:opacity-50",children:o?"처리 중...":t.status==="active"?"비활성화":"활성화"})]})]}),b&&e.jsxs("div",{className:"mt-4 border-t border-slate-200 pt-4",children:[e.jsxs("div",{className:"flex items-center justify-between gap-3 mb-3",children:[e.jsx("h4",{className:"text-sm font-semibold text-slate-900",children:"현재 이 코드를 사용 중인 사용자"}),e.jsxs("span",{className:"text-xs text-slate-500",children:[m.length,"명"]})]}),m.length===0?e.jsx("div",{className:"rounded-lg border border-dashed border-slate-300 bg-slate-50 px-4 py-3 text-sm text-slate-500",children:"현재 활성 구독 중 이 코드를 사용 중인 사용자가 없습니다."}):e.jsx("div",{className:"space-y-3",children:m.map(({subscription:r,profile:c}
1)=>{const Q=de(r.category||(c==null?void 0:c.subscriptionCategory)||null),g=(c==null?void 0:c.email)||r.email,I=(c==null?void 0:c.displayName)||g||r.userId,V=c!=null&&c.displayName?g||r.userId:g&&g!==r.userId?`${g} · ${r.userId}`:r.userId;return e.jsx("div",{className:"rounded-lg border border-slate-200 bg-slate-50 px-4 py-3",children:e.jsxs("div",{className:"flex flex-col gap-3 lg:flex-row lg:items-center lg:justify-between",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-sm font-semibold text-slate-900",children:g?e.jsx(le,{email:g,userId:r.userId,onOpen:T,className:"font-semibold text-blue-600 underline-offset-2 hover:underline",children:I}):I}),e.jsx("p",{className:"text-xs text-slate-500 mt-1",children:V})]}),e.jsxs("div",{className:"flex flex-wrap gap-2 text-xs",children:[e.jsx("span",{className:"rounded-full bg-white px-3 py-1 font-medium text-slate-700 border border-slate-200",children:ce(r.plan)}),e.jsx("span",{className:"rounded-full bg-white px-3 py-1 font-medium text-slate-700 border border-slate-200",children:Q||"카테고리 없음"}),e.jsxs("span",{className:"rounded-full bg-white px-3 py-1 font-medium text-slate-700 border border-slate-200",children:["사용 종료 ",oe(r.currentPeriodEnd)]}),r.cancelAtPeriodEnd&&e.jsx("span",{className:"rounded-full bg-amber-100 px-3 py-1 font-medium text-amber-700",children:"해지 예정"})]})]})},r.id)})})]})]},s)}),P.length===0&&e.jsx("div",{className:"text-sm text-slate-500",children:"등록된 프로모션 코드가 없습니다."})]})})]})]}),A&&e.jsx(ne,{target:A,onClose:()=>T(null),userProfiles:p})]})};export{ke as default};

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.