first commit

This commit is contained in:
s.esmaeili
2026-09-10 01:58:29 +03:30
commit 077da7eacd
201 changed files with 25072 additions and 0 deletions
+14
View File
@@ -0,0 +1,14 @@
{
"name": "@lucky-wheel/shared-types",
"version": "1.0.0",
"private": true,
"main": "./dist/index.js",
"types": "./dist/index.d.ts",
"scripts": {
"build": "tsc",
"watch": "tsc -w"
},
"devDependencies": {
"typescript": "^5.4.5"
}
}
+44
View File
@@ -0,0 +1,44 @@
/**
* تبدیل تمامی ارقام انگلیسی (0-9) و عربی (٠-٩) به ارقام استاندارد فارسی (۰-۹)
*/
const FARSI_DIGITS = ['۰', '۱', '۲', '۳', '۴', '۵', '۶', '۷', '۸', '۹'];
export function toFarsiDigits(val: number | string | null | undefined): string {
if (val === null || val === undefined) return '';
return String(val)
.replace(/[0-9]/g, (d) => FARSI_DIGITS[+d])
.replace(/[٠-٩]/g, (d) => FARSI_DIGITS[d.charCodeAt(0) - 1632]);
}
/**
* قالب‌بندی اعداد با جداکننده هزارگان و ارقام فارسی
*/
export function formatFarsiNumber(val: number | string | null | undefined): string {
if (val === null || val === undefined || val === '') return '';
const num = typeof val === 'string' ? parseFloat(toEnglishDigits(val)) : val;
if (isNaN(num)) return toFarsiDigits(val);
const formatted = new Intl.NumberFormat('fa-IR').format(num);
return toFarsiDigits(formatted);
}
/**
* تبدیل ارقام فارسی (۰-۹) و عربی (٠-٩) به ارقام استاندارد انگلیسی (0-9)
*/
export function toEnglishDigits(val: number | string | null | undefined): string {
if (val === null || val === undefined) return '';
return String(val)
.replace(/[۰-۹]/g, (d) => String(d.charCodeAt(0) - 1776))
.replace(/[٠-٩]/g, (d) => String(d.charCodeAt(0) - 1632));
}
/**
* پارس مطمئن مقادیر عددی از ورودی‌ها (پشتیبانی از هر دو فرمت فارسی و انگلیسی)
*/
export function parseFarsiNumber(val: number | string | null | undefined, fallback: number = 0): number {
if (val === null || val === undefined || val === '') return fallback;
if (typeof val === 'number') return isNaN(val) ? fallback : val;
const enStr = toEnglishDigits(val).replace(/[^0-9.-]/g, '');
const parsed = Number(enStr);
return isNaN(parsed) ? fallback : parsed;
}
+202
View File
@@ -0,0 +1,202 @@
// ==========================================
// Shared Enums (Synchronized with Prisma Schema)
// ==========================================
export enum AdminStatus {
ACTIVE = 'ACTIVE',
DISABLED = 'DISABLED',
}
export enum AdminRole {
SUPER_ADMIN = 'SUPER_ADMIN',
OPERATOR = 'OPERATOR',
SUPPORT = 'SUPPORT',
}
export enum UserStatus {
ACTIVE = 'ACTIVE',
BLOCKED = 'BLOCKED',
}
export enum CampaignStatus {
DRAFT = 'DRAFT',
ACTIVE = 'ACTIVE',
PAUSED = 'PAUSED',
FINISHED = 'FINISHED',
}
export enum PrizeType {
PHYSICAL = 'PHYSICAL',
DISCOUNT_CODE = 'DISCOUNT_CODE',
CREDIT = 'CREDIT',
CASH = 'CASH',
NO_PRIZE = 'NO_PRIZE',
OTHER = 'OTHER',
}
export enum StockType {
LIMITED = 'LIMITED',
UNLIMITED = 'UNLIMITED',
}
export enum PrizeStatus {
ACTIVE = 'ACTIVE',
INACTIVE = 'INACTIVE',
}
export enum SpinStatus {
SUCCESS = 'SUCCESS',
FAILED = 'FAILED',
DUPLICATE = 'DUPLICATE',
}
export enum ChanceTxnType {
SIGNUP_BONUS = 'SIGNUP_BONUS',
ADMIN_ADJUSTMENT = 'ADMIN_ADJUSTMENT',
SPIN_CONSUME = 'SPIN_CONSUME',
CAMPAIGN_BONUS = 'CAMPAIGN_BONUS',
REFUND = 'REFUND',
}
export enum UserPrizeStatus {
NEW = 'NEW',
CONTACTED = 'CONTACTED',
DELIVERED = 'DELIVERED',
ACTIVE_CODE = 'ACTIVE_CODE',
USED = 'USED',
EXPIRED = 'EXPIRED',
}
export enum SmsType {
OTP = 'OTP',
WINNING = 'WINNING',
NO_PRIZE = 'NO_PRIZE',
PRIZE_REMINDER = 'PRIZE_REMINDER',
}
export enum SmsStatus {
PENDING = 'PENDING',
SENT = 'SENT',
FAILED = 'FAILED',
}
export enum WheelDesign {
CLASSIC_CASINO = 'CLASSIC_CASINO',
NEON_CYBER = 'NEON_CYBER',
MINIMAL_FLAT = 'MINIMAL_FLAT',
LUXURY_GOLD = 'LUXURY_GOLD',
PLAYFUL_CARTOON = 'PLAYFUL_CARTOON',
}
// ==========================================
// Client & Wheel Interfaces
// ==========================================
export interface WheelSegment {
id: string;
title: string;
image?: string | null;
color: string;
sortOrder: number;
}
export interface WheelDesignProps {
segments: WheelSegment[];
targetSegmentIndex: number | null;
spinning: boolean;
}
export interface PublicCampaignWheelDto {
id: string;
title: string;
slug: string;
description: string | null;
logo: string | null;
banner: string | null;
wheelDesign: WheelDesign;
settings: Record<string, any> | null;
rules: string | null;
ctaText: string | null;
startAt: string;
endAt: string;
segments: WheelSegment[];
}
export interface SpinRequestDto {
idempotencyKey: string;
}
export interface PrizeResultDto {
id: string;
title: string;
segment: number;
code: string | null;
type: PrizeType;
image: string | null;
}
export interface SpinResponseDto {
success: boolean;
spinId: string;
prize: PrizeResultDto | null;
remainingChances: number;
}
export interface UserChancesDto {
totalChances: number;
usedChances: number;
remainingChances: number;
}
export interface UserPrizeItemDto {
id: string;
prizeTitle: string;
prizeImage: string | null;
prizeType: PrizeType;
code: string | null;
status: UserPrizeStatus;
wonAt: string;
expiresAt: string | null;
campaignTitle: string;
}
// ==========================================
// Admin & Reporting Interfaces
// ==========================================
export interface PaginationMeta {
page: number;
pageSize: number;
total: number;
totalPages: number;
}
export interface PaginatedResponse<T> {
data: T[];
meta: PaginationMeta;
}
export interface DashboardStatsDto {
totalUsers: number;
todayUsers: number;
totalSpins: number;
todaySpins: number;
totalWinners: number;
totalDistributedPrizes: number;
consumedChances: number;
activePrizesCount: number;
}
export interface SpinsByDayDataPoint {
date: string;
spins: number;
winners: number;
}
export interface PrizeDistributionDataPoint {
prizeTitle: string;
count: number;
percentage: number;
}
export * from './farsi-number';
+8
View File
@@ -0,0 +1,8 @@
{
"extends": "../../tsconfig.base.json",
"compilerOptions": {
"outDir": "./dist",
"rootDir": "./src"
},
"include": ["src/**/*"]
}