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) => (