From 41a246ad7bb7c0e90207470287ffe842e7211738 Mon Sep 17 00:00:00 2001 From: jisangs Date: Sat, 4 Jul 2026 15:32:19 +0900 Subject: [PATCH] =?UTF-8?q?=EC=A0=84=EC=B2=B4=20=EC=BD=94=EB=93=9C,=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EA=B2=80=ED=86=A0=20-=20P1=20=EC=BD=94?= =?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95=20=EC=A7=84=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- AGENTS.md | 5 ++ .../[maestroId]/UpgradeRequestsSection.tsx | 10 ++- app/(admin)/maestros/[maestroId]/page.tsx | 1 + docs/plan/improvement-plan.md | 12 ++- lib/extension-requests.ts | 82 ++++++++++++------- lib/upgrade-requests.ts | 41 +++++++++- 6 files changed, 110 insertions(+), 41 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 1ae069d..cae1705 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -20,6 +20,9 @@ Admin panel for the chocomae service (mouse-typing), extracted into a standalone - Upgrade approval sets `AvailableActivateDateTime = NOW() + 1 year`. **Do not change `PlayerCount`** on upgrade. - When approving an extension or upgrade, close all other `Status = 1` requests for the same `MaestroID` by setting them to `Status = 2`, then set the approved request to `Status = 3`. - **Docker containers must set `TZ: Asia/Seoul`** so that date calculations (`setHours(0,0,0,0)`, `formatDate`) match the KST-based existing data in the shared MariaDB. +- **Extension requests** (`POST /api/maestros/[id]/extension-requests`) require `ActivateStatus = 2` (ACTIVE). Trial (1) and cancelled (100) accounts are rejected with 400. Approval (`PATCH /api/extension-requests/[id]`) re-checks `ActivateStatus` and throws 409 if not ACTIVE (transaction rolls back the atomic claim). +- **Upgrade requests**: TRIAL accounts (`ActivateStatus = 1`) may request the **same tier** (trial-to-paid conversion, `requestedAccountType >= AccountType`). ACTIVE accounts must use a strictly higher tier (`>`). At approval time, this direction is re-verified against the maestro's **current** `AccountType` — if it became invalid (e.g., admin manually raised the tier), the approval throws 409 and rolls back. +- Admin-initiated extension/upgrade registrations do **not** send an email (unlike self-registration flows which send payment-instruction emails). This is intentional. ## Authentication @@ -62,6 +65,8 @@ Always record a log entry after a successful DB change. Use these `Type` values: | `update_maestro_account_type` | AccountType changed (admin, new) | `{prev} -> {new}` (numeric) | | `update_maestro_available_date` | AvailableActivateDateTime changed (admin, new) | `{YYYY-MM-DD} -> {YYYY-MM-DD}` | | `update_maestro_allow_enter_code` | AllowEditEnterCode changed (admin, new) | `{prev} -> {new}` (0 or 1) | +| `request_extension_maestro` | Admin-initiated extension request created | `{maestroName}({accountTypeLabel})` | +| `request_upgrade_maestro` | Admin-initiated upgrade request created | `{registeredAccountTypeLabel} -> {requestedAccountTypeLabel}` | `Remark` column is `char(100)` — keep values under 100 characters. diff --git a/app/(admin)/maestros/[maestroId]/UpgradeRequestsSection.tsx b/app/(admin)/maestros/[maestroId]/UpgradeRequestsSection.tsx index 435f5b0..6c37e91 100644 --- a/app/(admin)/maestros/[maestroId]/UpgradeRequestsSection.tsx +++ b/app/(admin)/maestros/[maestroId]/UpgradeRequestsSection.tsx @@ -5,13 +5,14 @@ import { useRouter } from "next/navigation"; import { ArrowUp } from "lucide-react"; import { Button } from "@/components/ui/button"; -import { ACCOUNT_TYPES } from "@/lib/constants"; +import { ACCOUNT_TYPES, ACTIVATE_STATUSES } from "@/lib/constants"; import { formatDateTime, getAccountTypeLabel, getRequestStatusLabel } from "@/lib/utils"; import type { MaestroDetail } from "@/lib/maestros"; type UpgradeRequestsSectionProps = { maestroID: number; accountType: number; + activateStatus: number; totalCount: number; upgradeRequests: MaestroDetail["upgradeRequests"]; }; @@ -30,6 +31,7 @@ const selectClass = export function UpgradeRequestsSection({ maestroID, accountType, + activateStatus, totalCount, upgradeRequests, }: UpgradeRequestsSectionProps) { @@ -39,8 +41,10 @@ export function UpgradeRequestsSection({ const [selectedAccountType, setSelectedAccountType] = useState(null); const [errorMessage, setErrorMessage] = useState(""); + const isTrial = activateStatus === ACTIVATE_STATUSES.TRIAL; const isUpgradeEnabled = - selectedAccountType !== null && selectedAccountType > accountType; + selectedAccountType !== null && + (isTrial ? selectedAccountType >= accountType : selectedAccountType > accountType); const isDisabled = isCreating || isPending; async function handleUpgrade() { @@ -107,7 +111,7 @@ export function UpgradeRequestsSection({ {upgradeOptions.map((opt) => (