Compare commits
No commits in common. "1463a69dca545c52d009bccab855a2007daf6dcc" and "1e15173fd470d1aadda77b24bee29708f4330373" have entirely different histories.
1463a69dca
...
1e15173fd4
@ -146,8 +146,7 @@
|
|||||||
|
|
||||||
**소유자 조치 대기:**
|
**소유자 조치 대기:**
|
||||||
1. **Gemini API 유료 키 교체**(시연 블로커) — 무료 티어 쿼터 0(429)으로 S7 조감·ReRoom 실렌더 불가. 텍스트 AI(Claude)는 정상.
|
1. **Gemini API 유료 키 교체**(시연 블로커) — 무료 티어 쿼터 0(429)으로 S7 조감·ReRoom 실렌더 불가. 텍스트 AI(Claude)는 정상.
|
||||||
|
2. **EDM 운영 실발송 개방 여부** — 현재 이중 잠금(요청 live=true + 서버 EDM_TEST_MODE=false 둘 다 필요). 테스트 발송은 검증 완료.
|
||||||
**EDM 운영 실발송 개방 완료(2026-07-14 소유자 지시):** 서버 `EDM_TEST_MODE=false` 전환 + EDM 화면에 운영 실발송 버튼 신설 — 기존 버튼은 '테스트 발송'(secondary, live=false 안전 기본)으로 유지, '운영 실발송'(primary)은 **이중 확인**(대상 인원 경고 confirm → 'LIVE' 타이핑) 후에만 live=true 호출. i18n 4개국어. 참고: 현 데모 수신자 5명은 `@kintex.test` 시드 주소라 실발송해도 반송됨 — 실제 캠페인 전 수신자 데이터 정비 필요.
|
|
||||||
|
|
||||||
~~완료 처리(2026-07-13 배포 확인): Tabulator/KxDataTable(1.3), 브레드크럼·404(4.4), 모바일 역할랜딩(4.3), 메시지 관리 화면(6.6), 반응형 스윕(1.11), Nifty 카드·그리드·폰트(1.1·1.2·1.9), 참가업체 관리(6.2), 상세 Nifty blog(1.4), 설정 커스터마이저(1.12), 히어로 5장 회전(3.13), 카드 3:4(3.12), 모바일 다국어(9.4)~~
|
~~완료 처리(2026-07-13 배포 확인): Tabulator/KxDataTable(1.3), 브레드크럼·404(4.4), 모바일 역할랜딩(4.3), 메시지 관리 화면(6.6), 반응형 스윕(1.11), Nifty 카드·그리드·폰트(1.1·1.2·1.9), 참가업체 관리(6.2), 상세 Nifty blog(1.4), 설정 커스터마이저(1.12), 히어로 5장 회전(3.13), 카드 3:4(3.12), 모바일 다국어(9.4)~~
|
||||||
~~완료 처리(2026-07-14 배포 확인): ReRoomAI image-to-image(13.2), Nifty 잔여 컴포넌트 버튼/드롭다운/List Group(1.5), 경계선 회색 정렬(1.10), 좌측메뉴 검색+아코디언, 그리드 짤림, 세션 재수화~~
|
~~완료 처리(2026-07-14 배포 확인): ReRoomAI image-to-image(13.2), Nifty 잔여 컴포넌트 버튼/드롭다운/List Group(1.5), 경계선 회색 정렬(1.10), 좌측메뉴 검색+아코디언, 그리드 짤림, 세션 재수화~~
|
||||||
|
|||||||
@ -2132,11 +2132,7 @@
|
|||||||
"sendFail": "Failed to send.",
|
"sendFail": "Failed to send.",
|
||||||
"confirm": "Send the {{name}} campaign?",
|
"confirm": "Send the {{name}} campaign?",
|
||||||
"sending": "Sending…",
|
"sending": "Sending…",
|
||||||
"sendBtn": "Send",
|
"sendBtn": "Send"
|
||||||
"sendTestBtn": "Test send",
|
|
||||||
"sendLiveBtn": "Live send",
|
|
||||||
"confirmLive": "This will send the '{{name}}' campaign to {{audience}} real recipients. This cannot be undone. Continue?",
|
|
||||||
"confirmLiveType": "Type LIVE to confirm the live send."
|
|
||||||
},
|
},
|
||||||
"program": {
|
"program": {
|
||||||
"title": "Program Management",
|
"title": "Program Management",
|
||||||
|
|||||||
@ -2132,11 +2132,7 @@
|
|||||||
"sendFail": "送信に失敗しました。",
|
"sendFail": "送信に失敗しました。",
|
||||||
"confirm": "{{name}} キャンペーンを送信しますか?",
|
"confirm": "{{name}} キャンペーンを送信しますか?",
|
||||||
"sending": "送信中…",
|
"sending": "送信中…",
|
||||||
"sendBtn": "送信",
|
"sendBtn": "送信"
|
||||||
"sendTestBtn": "テスト送信",
|
|
||||||
"sendLiveBtn": "本番送信",
|
|
||||||
"confirmLive": "'{{name}}' キャンペーンを実際の受信者 {{audience}} 名に本番送信します。取り消せません。続行しますか?",
|
|
||||||
"confirmLiveType": "本番送信を確定するには LIVE と入力してください。"
|
|
||||||
},
|
},
|
||||||
"program": {
|
"program": {
|
||||||
"title": "プログラム管理",
|
"title": "プログラム管理",
|
||||||
|
|||||||
@ -2132,11 +2132,7 @@
|
|||||||
"sendFail": "발송에 실패했습니다.",
|
"sendFail": "발송에 실패했습니다.",
|
||||||
"confirm": "{{name}} 캠페인을 발송하시겠습니까?",
|
"confirm": "{{name}} 캠페인을 발송하시겠습니까?",
|
||||||
"sending": "발송 중…",
|
"sending": "발송 중…",
|
||||||
"sendBtn": "발송",
|
"sendBtn": "발송"
|
||||||
"sendTestBtn": "테스트 발송",
|
|
||||||
"sendLiveBtn": "운영 실발송",
|
|
||||||
"confirmLive": "'{{name}}' 캠페인을 실제 수신자 {{audience}}명에게 운영 발송합니다. 되돌릴 수 없습니다. 계속하시겠습니까?",
|
|
||||||
"confirmLiveType": "운영 발송을 확정하려면 LIVE 를 입력하세요."
|
|
||||||
},
|
},
|
||||||
"program": {
|
"program": {
|
||||||
"title": "프로그램 관리",
|
"title": "프로그램 관리",
|
||||||
|
|||||||
@ -2132,11 +2132,7 @@
|
|||||||
"sendFail": "发送失败。",
|
"sendFail": "发送失败。",
|
||||||
"confirm": "确定发送 {{name}} 活动吗?",
|
"confirm": "确定发送 {{name}} 活动吗?",
|
||||||
"sending": "发送中…",
|
"sending": "发送中…",
|
||||||
"sendBtn": "发送",
|
"sendBtn": "发送"
|
||||||
"sendTestBtn": "测试发送",
|
|
||||||
"sendLiveBtn": "正式发送",
|
|
||||||
"confirmLive": "将向 {{audience}} 名真实收件人正式发送'{{name}}'活动,此操作不可撤销。是否继续?",
|
|
||||||
"confirmLiveType": "输入 LIVE 以确认正式发送。"
|
|
||||||
},
|
},
|
||||||
"program": {
|
"program": {
|
||||||
"title": "程序管理",
|
"title": "程序管理",
|
||||||
|
|||||||
@ -52,9 +52,8 @@ export function EdmCampaignPage() {
|
|||||||
|
|
||||||
const [sendMsg, setSendMsg] = useState<string | null>(null);
|
const [sendMsg, setSendMsg] = useState<string | null>(null);
|
||||||
const sendMut = useMutation({
|
const sendMut = useMutation({
|
||||||
// 안전 기본: 테스트 모드(live=false). 운영 실발송은 ①UI 이중 확인(live=true) ②서버 EDM_TEST_MODE=false 둘 다 필요.
|
// 안전 기본: 테스트 모드(live 미지정). 운영 실발송은 서버 설정(EDM_TEST_MODE=false)에서만 개방.
|
||||||
mutationFn: (vars: { id: string; live: boolean }) =>
|
mutationFn: (id: string) => marketingApi.sendCampaign(eventId as string, id),
|
||||||
marketingApi.sendCampaign(eventId as string, vars.id, vars.live),
|
|
||||||
onSuccess: (res) => {
|
onSuccess: (res) => {
|
||||||
const n = res.sentCount.toLocaleString();
|
const n = res.sentCount.toLocaleString();
|
||||||
let base: string;
|
let base: string;
|
||||||
@ -89,23 +88,10 @@ export function EdmCampaignPage() {
|
|||||||
const ok = window.confirm(t('marketing.confirm', { name: c.name }));
|
const ok = window.confirm(t('marketing.confirm', { name: c.name }));
|
||||||
if (ok) {
|
if (ok) {
|
||||||
setSendMsg(null);
|
setSendMsg(null);
|
||||||
sendMut.mutate({ id: c.id, live: false });
|
sendMut.mutate(c.id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** 운영 실발송 — 이중 확인(경고 confirm + 'LIVE' 타이핑). 서버 EDM_TEST_MODE=false 일 때만 실제 발송된다. */
|
|
||||||
function confirmSendLive(c: CampaignDto) {
|
|
||||||
if (sendMut.isPending) return;
|
|
||||||
const ok = window.confirm(
|
|
||||||
t('marketing.confirmLive', { name: c.name, audience: c.audience.toLocaleString() }),
|
|
||||||
);
|
|
||||||
if (!ok) return;
|
|
||||||
const typed = window.prompt(t('marketing.confirmLiveType'));
|
|
||||||
if (typed !== 'LIVE') return;
|
|
||||||
setSendMsg(null);
|
|
||||||
sendMut.mutate({ id: c.id, live: true });
|
|
||||||
}
|
|
||||||
|
|
||||||
const audience = useMemo(
|
const audience = useMemo(
|
||||||
() => SEGMENTS.filter((s) => segments[s.id]).reduce((sum, s) => sum + s.count, 0),
|
() => SEGMENTS.filter((s) => segments[s.id]).reduce((sum, s) => sum + s.count, 0),
|
||||||
[segments],
|
[segments],
|
||||||
@ -218,8 +204,7 @@ export function EdmCampaignPage() {
|
|||||||
key={c.id}
|
key={c.id}
|
||||||
campaign={c}
|
campaign={c}
|
||||||
onSend={() => confirmSend(c)}
|
onSend={() => confirmSend(c)}
|
||||||
onSendLive={() => confirmSendLive(c)}
|
sending={sendMut.isPending && sendMut.variables === c.id}
|
||||||
sending={sendMut.isPending && sendMut.variables?.id === c.id}
|
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
{rows.length === 0 && (
|
{rows.length === 0 && (
|
||||||
@ -348,12 +333,10 @@ export function EdmCampaignPage() {
|
|||||||
function CampaignRow({
|
function CampaignRow({
|
||||||
campaign: c,
|
campaign: c,
|
||||||
onSend,
|
onSend,
|
||||||
onSendLive,
|
|
||||||
sending,
|
sending,
|
||||||
}: {
|
}: {
|
||||||
campaign: CampaignDto;
|
campaign: CampaignDto;
|
||||||
onSend?: () => void;
|
onSend?: () => void;
|
||||||
onSendLive?: () => void;
|
|
||||||
sending?: boolean;
|
sending?: boolean;
|
||||||
}) {
|
}) {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -388,13 +371,8 @@ function CampaignRow({
|
|||||||
<strong>{c.clickRate ?? '-'}</strong>
|
<strong>{c.clickRate ?? '-'}</strong>
|
||||||
</div>
|
</div>
|
||||||
{canSend && onSend && (
|
{canSend && onSend && (
|
||||||
<Button variant="secondary" onClick={onSend} disabled={sending}>
|
<Button variant="primary" onClick={onSend} disabled={sending}>
|
||||||
{sending ? t('marketing.sending') : t('marketing.sendTestBtn')}
|
{sending ? t('marketing.sending') : t('marketing.sendBtn')}
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{canSend && onSendLive && (
|
|
||||||
<Button variant="primary" onClick={onSendLive} disabled={sending}>
|
|
||||||
{t('marketing.sendLiveBtn')}
|
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user