Phase 3. 인증 및 공통 레이아웃 작업
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
export const ACCOUNT_TYPES = {
|
||||
BASIC_20: 1,
|
||||
STANDARD_50: 2,
|
||||
PRO_100: 3,
|
||||
SCHOOL_500: 4,
|
||||
SCHOOL_1000: 5,
|
||||
STUDENT_TRIAL: 100,
|
||||
MAESTRO_TRIAL: 101,
|
||||
} as const;
|
||||
|
||||
export const ACCOUNT_TYPE_LABELS = {
|
||||
[ACCOUNT_TYPES.BASIC_20]: "20명 (1만원)",
|
||||
[ACCOUNT_TYPES.STANDARD_50]: "50명 (2만원)",
|
||||
[ACCOUNT_TYPES.PRO_100]: "100명 (3만원)",
|
||||
[ACCOUNT_TYPES.SCHOOL_500]: "500명 (4만원)",
|
||||
[ACCOUNT_TYPES.SCHOOL_1000]: "1,000명 (5만원)",
|
||||
[ACCOUNT_TYPES.STUDENT_TRIAL]: "학생체험",
|
||||
[ACCOUNT_TYPES.MAESTRO_TRIAL]: "마에체험",
|
||||
} as const;
|
||||
|
||||
export const TRIAL_ACCOUNT_TYPE_LABELS = {
|
||||
[ACCOUNT_TYPES.BASIC_20]: "20명 (0원)",
|
||||
[ACCOUNT_TYPES.STANDARD_50]: "50명 (0원)",
|
||||
[ACCOUNT_TYPES.PRO_100]: "100명 (0원)",
|
||||
[ACCOUNT_TYPES.SCHOOL_500]: "500명 (0원)",
|
||||
[ACCOUNT_TYPES.SCHOOL_1000]: "1,000명 (0원)",
|
||||
} as const;
|
||||
|
||||
export const ACCOUNT_TYPE_PLAYER_COUNTS = {
|
||||
[ACCOUNT_TYPES.BASIC_20]: 20,
|
||||
[ACCOUNT_TYPES.STANDARD_50]: 50,
|
||||
[ACCOUNT_TYPES.PRO_100]: 100,
|
||||
[ACCOUNT_TYPES.SCHOOL_500]: 500,
|
||||
[ACCOUNT_TYPES.SCHOOL_1000]: 1000,
|
||||
} as const;
|
||||
|
||||
export const ACCOUNT_TYPE_PRICES = {
|
||||
[ACCOUNT_TYPES.BASIC_20]: 10000,
|
||||
[ACCOUNT_TYPES.STANDARD_50]: 20000,
|
||||
[ACCOUNT_TYPES.PRO_100]: 30000,
|
||||
[ACCOUNT_TYPES.SCHOOL_500]: 40000,
|
||||
[ACCOUNT_TYPES.SCHOOL_1000]: 50000,
|
||||
} as const;
|
||||
|
||||
export const ACTIVATE_STATUSES = {
|
||||
PENDING: 0,
|
||||
TRIAL: 1,
|
||||
ACTIVE: 2,
|
||||
CANCELED: 100,
|
||||
} as const;
|
||||
|
||||
export const ACTIVATE_STATUS_LABELS = {
|
||||
[ACTIVATE_STATUSES.PENDING]: "미승인",
|
||||
[ACTIVATE_STATUSES.TRIAL]: "체험",
|
||||
[ACTIVATE_STATUSES.ACTIVE]: "활성화",
|
||||
[ACTIVATE_STATUSES.CANCELED]: "등록 취소",
|
||||
} as const;
|
||||
|
||||
export const REQUEST_STATUSES = {
|
||||
REQUESTED: 1,
|
||||
CLOSED: 2,
|
||||
APPLIED: 3,
|
||||
} as const;
|
||||
|
||||
export const REQUEST_STATUS_LABELS = {
|
||||
[REQUEST_STATUSES.REQUESTED]: "요청",
|
||||
[REQUEST_STATUSES.CLOSED]: "취소",
|
||||
[REQUEST_STATUSES.APPLIED]: "적용 완료",
|
||||
} as const;
|
||||
|
||||
export type AccountType = (typeof ACCOUNT_TYPES)[keyof typeof ACCOUNT_TYPES];
|
||||
export type PaidAccountType =
|
||||
| typeof ACCOUNT_TYPES.BASIC_20
|
||||
| typeof ACCOUNT_TYPES.STANDARD_50
|
||||
| typeof ACCOUNT_TYPES.PRO_100
|
||||
| typeof ACCOUNT_TYPES.SCHOOL_500
|
||||
| typeof ACCOUNT_TYPES.SCHOOL_1000;
|
||||
export type ActivateStatus =
|
||||
(typeof ACTIVATE_STATUSES)[keyof typeof ACTIVATE_STATUSES];
|
||||
export type RequestStatus =
|
||||
(typeof REQUEST_STATUSES)[keyof typeof REQUEST_STATUSES];
|
||||
@@ -0,0 +1,25 @@
|
||||
import { PrismaMariaDb } from "@prisma/adapter-mariadb";
|
||||
|
||||
import { PrismaClient } from "@/lib/generated/prisma/client";
|
||||
|
||||
const globalForPrisma = globalThis as unknown as {
|
||||
prisma?: PrismaClient;
|
||||
};
|
||||
|
||||
function createPrismaClient() {
|
||||
const databaseUrl = process.env.DATABASE_URL;
|
||||
|
||||
if (!databaseUrl) {
|
||||
throw new Error("DATABASE_URL is required to initialize Prisma Client.");
|
||||
}
|
||||
|
||||
const adapter = new PrismaMariaDb(databaseUrl);
|
||||
|
||||
return new PrismaClient({ adapter });
|
||||
}
|
||||
|
||||
export const db = globalForPrisma.prisma ?? createPrismaClient();
|
||||
|
||||
if (process.env.NODE_ENV !== "production") {
|
||||
globalForPrisma.prisma = db;
|
||||
}
|
||||
+113
@@ -1,6 +1,119 @@
|
||||
import { clsx, type ClassValue } from "clsx";
|
||||
import { twMerge } from "tailwind-merge";
|
||||
|
||||
import {
|
||||
ACCOUNT_TYPE_LABELS,
|
||||
ACCOUNT_TYPE_PLAYER_COUNTS,
|
||||
ACCOUNT_TYPE_PRICES,
|
||||
ACTIVATE_STATUS_LABELS,
|
||||
REQUEST_STATUS_LABELS,
|
||||
TRIAL_ACCOUNT_TYPE_LABELS,
|
||||
type AccountType,
|
||||
type ActivateStatus,
|
||||
type PaidAccountType,
|
||||
type RequestStatus,
|
||||
} from "@/lib/constants";
|
||||
|
||||
export function cn(...inputs: ClassValue[]) {
|
||||
return twMerge(clsx(inputs));
|
||||
}
|
||||
|
||||
export function getAccountTypeLabel(accountType: number): string {
|
||||
return (
|
||||
ACCOUNT_TYPE_LABELS[accountType as AccountType] ?? `${accountType}? (N/A)`
|
||||
);
|
||||
}
|
||||
|
||||
export function getTrialAccountTypeLabel(accountType: number): string {
|
||||
return (
|
||||
TRIAL_ACCOUNT_TYPE_LABELS[accountType as PaidAccountType] ??
|
||||
`${accountType}? (N/A)`
|
||||
);
|
||||
}
|
||||
|
||||
export function getAccountTypePlayerCount(accountType: number): number {
|
||||
return ACCOUNT_TYPE_PLAYER_COUNTS[accountType as PaidAccountType] ?? 0;
|
||||
}
|
||||
|
||||
export function getAccountTypePrice(accountType: number): number {
|
||||
return ACCOUNT_TYPE_PRICES[accountType as PaidAccountType] ?? 0;
|
||||
}
|
||||
|
||||
export function getActivateStatusLabel(activateStatus: number): string {
|
||||
return (
|
||||
ACTIVATE_STATUS_LABELS[activateStatus as ActivateStatus] ??
|
||||
`${activateStatus}? (N/A)`
|
||||
);
|
||||
}
|
||||
|
||||
export function getRequestStatusLabel(status: number): string {
|
||||
return REQUEST_STATUS_LABELS[status as RequestStatus] ?? `${status}? (N/A)`;
|
||||
}
|
||||
|
||||
export function calculateUpgradePrice(
|
||||
requestedAccountType: number,
|
||||
registeredAccountType: number
|
||||
): number {
|
||||
return (
|
||||
getAccountTypePrice(requestedAccountType) -
|
||||
getAccountTypePrice(registeredAccountType)
|
||||
);
|
||||
}
|
||||
|
||||
export function formatDate(value: Date | string | null | undefined): string {
|
||||
const date = toDate(value);
|
||||
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, "0");
|
||||
const day = String(date.getDate()).padStart(2, "0");
|
||||
|
||||
return `${year}-${month}-${day}`;
|
||||
}
|
||||
|
||||
export function formatDateTime(
|
||||
value: Date | string | null | undefined
|
||||
): string {
|
||||
const date = toDate(value);
|
||||
|
||||
if (!date) {
|
||||
return "";
|
||||
}
|
||||
|
||||
const hours = String(date.getHours()).padStart(2, "0");
|
||||
const minutes = String(date.getMinutes()).padStart(2, "0");
|
||||
const seconds = String(date.getSeconds()).padStart(2, "0");
|
||||
|
||||
return `${formatDate(date)} ${hours}:${minutes}:${seconds}`;
|
||||
}
|
||||
|
||||
export function calculateExtendedAvailableDate(
|
||||
availableDate: Date,
|
||||
now = new Date()
|
||||
): Date {
|
||||
const baseDate =
|
||||
availableDate.getTime() < now.getTime() ? now : availableDate;
|
||||
const extendedDate = new Date(baseDate);
|
||||
|
||||
extendedDate.setFullYear(extendedDate.getFullYear() + 1);
|
||||
extendedDate.setHours(0, 0, 0, 0);
|
||||
|
||||
return extendedDate;
|
||||
}
|
||||
|
||||
function toDate(value: Date | string | null | undefined): Date | null {
|
||||
if (!value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const date = value instanceof Date ? value : new Date(value);
|
||||
|
||||
if (Number.isNaN(date.getTime())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return date;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user