전체 코드, 구조 검토 - P1-6 코드 수정 진행
This commit is contained in:
@@ -40,12 +40,12 @@ export const PATCH = withApiHandler<{ id: string }>(
|
|||||||
id: maestroExtensionID,
|
id: maestroExtensionID,
|
||||||
duration: Date.now() - t0,
|
duration: Date.now() - t0,
|
||||||
});
|
});
|
||||||
void sendExtensionDoneEmail(
|
const emailSent = await sendExtensionDoneEmail(
|
||||||
result.maestroName,
|
result.maestroName,
|
||||||
result.maestroEmail,
|
result.maestroEmail,
|
||||||
formatDate(result.availableActivateDateTime)
|
formatDate(result.availableActivateDateTime)
|
||||||
);
|
);
|
||||||
return NextResponse.json({ ok: true, availableActivateDateTime: result.availableActivateDateTime });
|
return NextResponse.json({ ok: true, availableActivateDateTime: result.availableActivateDateTime, emailSent });
|
||||||
}
|
}
|
||||||
|
|
||||||
await cancelExtensionRequest(maestroExtensionID);
|
await cancelExtensionRequest(maestroExtensionID);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export const PATCH = withApiHandler<{ id: string }>(
|
|||||||
id: maestroUpgradeID,
|
id: maestroUpgradeID,
|
||||||
duration: Date.now() - t0,
|
duration: Date.now() - t0,
|
||||||
});
|
});
|
||||||
void sendUpgradeDoneEmail(
|
const emailSent = await sendUpgradeDoneEmail(
|
||||||
result.maestroName,
|
result.maestroName,
|
||||||
result.maestroEmail,
|
result.maestroEmail,
|
||||||
result.registeredActivateStatus,
|
result.registeredActivateStatus,
|
||||||
@@ -48,7 +48,7 @@ export const PATCH = withApiHandler<{ id: string }>(
|
|||||||
result.requestedAccountType,
|
result.requestedAccountType,
|
||||||
formatDate(result.availableActivateDateTime)
|
formatDate(result.availableActivateDateTime)
|
||||||
);
|
);
|
||||||
return NextResponse.json({ ok: true, availableActivateDateTime: result.availableActivateDateTime });
|
return NextResponse.json({ ok: true, availableActivateDateTime: result.availableActivateDateTime, emailSent });
|
||||||
}
|
}
|
||||||
|
|
||||||
await rejectUpgradeRequest(maestroUpgradeID);
|
await rejectUpgradeRequest(maestroUpgradeID);
|
||||||
|
|||||||
@@ -108,19 +108,16 @@ function ExtensionRequestActions({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
const [emailWarning, setEmailWarning] = useState(false);
|
||||||
const isRequested = request.status === REQUEST_STATUSES.REQUESTED;
|
const isRequested = request.status === REQUEST_STATUSES.REQUESTED;
|
||||||
|
|
||||||
if (!isRequested) {
|
if (!isRequested && !emailWarning) {
|
||||||
return <span className="text-xs text-muted-foreground">처리 완료</span>;
|
return <span className="text-xs text-muted-foreground">처리 완료</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitAction(action: "approve" | "cancel") {
|
async function submitAction(action: "approve" | "cancel") {
|
||||||
const actionLabel = action === "approve" ? "승인" : "취소";
|
const actionLabel = action === "approve" ? "승인" : "취소";
|
||||||
const confirmed = window.confirm(
|
if (!window.confirm(`${request.maestroName} 연장 신청을 ${actionLabel}하시겠습니까?`)) {
|
||||||
`${request.maestroName} 연장 신청을 ${actionLabel}하시겠습니까?`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -136,14 +133,19 @@ function ExtensionRequestActions({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
const body = (await response.json().catch(() => null)) as {
|
||||||
const body = (await response.json().catch(() => null)) as {
|
message?: string;
|
||||||
message?: string;
|
emailSent?: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
throw new Error(body?.message ?? "연장 신청 처리에 실패했습니다.");
|
throw new Error(body?.message ?? "연장 신청 처리에 실패했습니다.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (action === "approve" && body?.emailSent === false) {
|
||||||
|
setEmailWarning(true);
|
||||||
|
}
|
||||||
|
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
router.refresh();
|
router.refresh();
|
||||||
});
|
});
|
||||||
@@ -158,27 +160,36 @@ function ExtensionRequestActions({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
{isRequested ? (
|
||||||
<Button
|
<div className="flex items-center gap-2">
|
||||||
disabled={isPending}
|
<Button
|
||||||
onClick={() => void submitAction("approve")}
|
disabled={isPending}
|
||||||
size="sm"
|
onClick={() => void submitAction("approve")}
|
||||||
type="button"
|
size="sm"
|
||||||
>
|
type="button"
|
||||||
<Check className="size-4" aria-hidden="true" />
|
>
|
||||||
승인
|
<Check className="size-4" aria-hidden="true" />
|
||||||
</Button>
|
승인
|
||||||
<Button
|
</Button>
|
||||||
disabled={isPending}
|
<Button
|
||||||
onClick={() => void submitAction("cancel")}
|
disabled={isPending}
|
||||||
size="sm"
|
onClick={() => void submitAction("cancel")}
|
||||||
type="button"
|
size="sm"
|
||||||
variant="outline"
|
type="button"
|
||||||
>
|
variant="outline"
|
||||||
<X className="size-4" aria-hidden="true" />
|
>
|
||||||
취소
|
<X className="size-4" aria-hidden="true" />
|
||||||
</Button>
|
취소
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">처리 완료</span>
|
||||||
|
)}
|
||||||
|
{emailWarning ? (
|
||||||
|
<p className="max-w-56 text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
이메일 발송 실패 — 수동 확인 필요
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
{errorMessage ? (
|
{errorMessage ? (
|
||||||
<p className="max-w-56 text-xs text-destructive">{errorMessage}</p>
|
<p className="max-w-56 text-xs text-destructive">{errorMessage}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -113,19 +113,16 @@ function UpgradeRequestActions({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const [isPending, startTransition] = useTransition();
|
const [isPending, startTransition] = useTransition();
|
||||||
const [errorMessage, setErrorMessage] = useState("");
|
const [errorMessage, setErrorMessage] = useState("");
|
||||||
|
const [emailWarning, setEmailWarning] = useState(false);
|
||||||
const isRequested = request.status === REQUEST_STATUSES.REQUESTED;
|
const isRequested = request.status === REQUEST_STATUSES.REQUESTED;
|
||||||
|
|
||||||
if (!isRequested) {
|
if (!isRequested && !emailWarning) {
|
||||||
return <span className="text-xs text-muted-foreground">처리 완료</span>;
|
return <span className="text-xs text-muted-foreground">처리 완료</span>;
|
||||||
}
|
}
|
||||||
|
|
||||||
async function submitAction(action: "approve" | "reject") {
|
async function submitAction(action: "approve" | "reject") {
|
||||||
const actionLabel = action === "approve" ? "승인" : "거절";
|
const actionLabel = action === "approve" ? "승인" : "거절";
|
||||||
const confirmed = window.confirm(
|
if (!window.confirm(`${request.maestroName} 업그레이드 신청을 ${actionLabel}하시겠습니까?`)) {
|
||||||
`${request.maestroName} 업그레이드 신청을 ${actionLabel}하시겠습니까?`
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!confirmed) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,14 +138,17 @@ function UpgradeRequestActions({
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!response.ok) {
|
const body = (await response.json().catch(() => null)) as {
|
||||||
const body = (await response.json().catch(() => null)) as {
|
message?: string;
|
||||||
message?: string;
|
emailSent?: boolean;
|
||||||
} | null;
|
} | null;
|
||||||
|
|
||||||
throw new Error(
|
if (!response.ok) {
|
||||||
body?.message ?? "업그레이드 신청 처리에 실패했습니다."
|
throw new Error(body?.message ?? "업그레이드 신청 처리에 실패했습니다.");
|
||||||
);
|
}
|
||||||
|
|
||||||
|
if (action === "approve" && body?.emailSent === false) {
|
||||||
|
setEmailWarning(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
startTransition(() => {
|
startTransition(() => {
|
||||||
@@ -165,27 +165,36 @@ function UpgradeRequestActions({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<div className="flex items-center gap-2">
|
{isRequested ? (
|
||||||
<Button
|
<div className="flex items-center gap-2">
|
||||||
disabled={isPending}
|
<Button
|
||||||
onClick={() => void submitAction("approve")}
|
disabled={isPending}
|
||||||
size="sm"
|
onClick={() => void submitAction("approve")}
|
||||||
type="button"
|
size="sm"
|
||||||
>
|
type="button"
|
||||||
<Check className="size-4" aria-hidden="true" />
|
>
|
||||||
승인
|
<Check className="size-4" aria-hidden="true" />
|
||||||
</Button>
|
승인
|
||||||
<Button
|
</Button>
|
||||||
disabled={isPending}
|
<Button
|
||||||
onClick={() => void submitAction("reject")}
|
disabled={isPending}
|
||||||
size="sm"
|
onClick={() => void submitAction("reject")}
|
||||||
type="button"
|
size="sm"
|
||||||
variant="outline"
|
type="button"
|
||||||
>
|
variant="outline"
|
||||||
<X className="size-4" aria-hidden="true" />
|
>
|
||||||
거절
|
<X className="size-4" aria-hidden="true" />
|
||||||
</Button>
|
거절
|
||||||
</div>
|
</Button>
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<span className="text-xs text-muted-foreground">처리 완료</span>
|
||||||
|
)}
|
||||||
|
{emailWarning ? (
|
||||||
|
<p className="max-w-56 text-xs text-amber-600 dark:text-amber-400">
|
||||||
|
이메일 발송 실패 — 수동 확인 필요
|
||||||
|
</p>
|
||||||
|
) : null}
|
||||||
{errorMessage ? (
|
{errorMessage ? (
|
||||||
<p className="max-w-56 text-xs text-destructive">{errorMessage}</p>
|
<p className="max-w-56 text-xs text-destructive">{errorMessage}</p>
|
||||||
) : null}
|
) : null}
|
||||||
|
|||||||
@@ -168,9 +168,10 @@
|
|||||||
- P1-5: `request_extension_maestro` / `request_upgrade_maestro` 로그 추가
|
- P1-5: `request_extension_maestro` / `request_upgrade_maestro` 로그 추가
|
||||||
- P1-7: 관리자 대행 등록 시 입금 메일 미발송 — 의도 확인, AGENTS.md에 명시
|
- P1-7: 관리자 대행 등록 시 입금 메일 미발송 — 의도 확인, AGENTS.md에 명시
|
||||||
- P2-10: 신청 시점 스냅샷 유지 — 변경 없음
|
- P2-10: 신청 시점 스냅샷 유지 — 변경 없음
|
||||||
3. **Step 3 (P1-6 이메일 + 검증)**
|
3. **Step 3 (P1-6 이메일 + 검증) ✅ 완료 (2026-07-04)**
|
||||||
- emailSent 응답 + UI 경고, stage에서 연장/업그레이드(체험→유료, 유료→유료) 메일 실측
|
- API route 2곳: `void sendXxxDoneEmail` → `await emailSent`, 응답에 `emailSent: boolean` 포함
|
||||||
(phase11 잔여 항목 겸)
|
- 테이블 UI 2곳: 응답 body를 단일 파싱으로 통일, 승인 성공 후 `emailSent === false`이면 인라인 경고 표시 (`router.refresh()` 후에도 client state 유지됨)
|
||||||
|
- stage 실측(체험→유료, 유료→유료 메일 검증)은 별도 수동 테스트 항목으로 남김
|
||||||
4. **Step 4 (P2 일괄)**
|
4. **Step 4 (P2 일괄)**
|
||||||
- 소규모 항목 묶음 처리 후 AGENTS.md / phase 문서 갱신
|
- 소규모 항목 묶음 처리 후 AGENTS.md / phase 문서 갱신
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user