first commit

This commit is contained in:
kirukib 2025-12-21 18:05:16 +03:00
commit e232c50e52
24 changed files with 2062 additions and 0 deletions

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
node_modules
dist
.storybook-static
.DS_Store
*.log
*.cache
.vscode
.idea

View File

@ -0,0 +1,239 @@
import React, { useState, useEffect } from "react";
import { SketchPicker, ColorResult } from "react-color";
import { BrandingConfig, defaultBrandingConfig, popularFonts } from "../src/config/branding.config";
interface CustomizationPanelProps {
active?: boolean;
}
export const CustomizationPanel: React.FC<CustomizationPanelProps> = ({ active }) => {
const [config, setConfig] = useState<BrandingConfig>(defaultBrandingConfig);
const [showPrimaryPicker, setShowPrimaryPicker] = useState(false);
const [showSecondaryPicker, setShowSecondaryPicker] = useState(false);
const [showBackgroundPicker, setShowBackgroundPicker] = useState(false);
useEffect(() => {
// Send initial config to preview
window.postMessage(
{
type: "CUSTOMIZATION_UPDATE",
config: config,
},
"*"
);
}, [config]);
const handleColorChange = (colorType: "primary" | "secondary" | "background") => (
color: ColorResult
) => {
const newConfig = {
...config,
colors: {
...config.colors,
[colorType]: color.hex,
},
};
setConfig(newConfig);
window.postMessage(
{
type: "CUSTOMIZATION_UPDATE",
config: newConfig,
},
"*"
);
};
const handleFontChange = (font: string) => {
const newConfig = {
...config,
font: {
family: font,
},
};
setConfig(newConfig);
window.postMessage(
{
type: "CUSTOMIZATION_UPDATE",
config: newConfig,
},
"*"
);
};
if (!active) return null;
return (
<div style={{ padding: "20px", fontFamily: "Arial, sans-serif" }}>
<h2 style={{ marginTop: 0, marginBottom: "20px" }}>Customize Branding</h2>
{/* Font Selector */}
<div style={{ marginBottom: "24px" }}>
<label
style={{
display: "block",
marginBottom: "8px",
fontWeight: "bold",
fontSize: "14px",
}}
>
Font Family
</label>
<select
value={config.font.family}
onChange={(e) => handleFontChange(e.target.value)}
style={{
width: "100%",
padding: "8px",
fontSize: "14px",
border: "1px solid #ddd",
borderRadius: "4px",
}}
>
{popularFonts.map((font) => (
<option key={font} value={font}>
{font.split(",")[0]}
</option>
))}
</select>
</div>
{/* Primary Color Picker */}
<div style={{ marginBottom: "24px" }}>
<label
style={{
display: "block",
marginBottom: "8px",
fontWeight: "bold",
fontSize: "14px",
}}
>
Primary Color
</label>
<div style={{ position: "relative" }}>
<div
onClick={() => {
setShowPrimaryPicker(!showPrimaryPicker);
setShowSecondaryPicker(false);
setShowBackgroundPicker(false);
}}
style={{
width: "100%",
height: "40px",
backgroundColor: config.colors.primary,
border: "1px solid #ddd",
borderRadius: "4px",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#fff",
fontWeight: "bold",
}}
>
{config.colors.primary}
</div>
{showPrimaryPicker && (
<div style={{ position: "absolute", zIndex: 1000, marginTop: "4px" }}>
<SketchPicker
color={config.colors.primary}
onChange={handleColorChange("primary")}
/>
</div>
)}
</div>
</div>
{/* Secondary Color Picker */}
<div style={{ marginBottom: "24px" }}>
<label
style={{
display: "block",
marginBottom: "8px",
fontWeight: "bold",
fontSize: "14px",
}}
>
Secondary Color
</label>
<div style={{ position: "relative" }}>
<div
onClick={() => {
setShowSecondaryPicker(!showSecondaryPicker);
setShowPrimaryPicker(false);
setShowBackgroundPicker(false);
}}
style={{
width: "100%",
height: "40px",
backgroundColor: config.colors.secondary,
border: "1px solid #ddd",
borderRadius: "4px",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: "#fff",
fontWeight: "bold",
}}
>
{config.colors.secondary}
</div>
{showSecondaryPicker && (
<div style={{ position: "absolute", zIndex: 1000, marginTop: "4px" }}>
<SketchPicker
color={config.colors.secondary}
onChange={handleColorChange("secondary")}
/>
</div>
)}
</div>
</div>
{/* Background Color Picker */}
<div style={{ marginBottom: "24px" }}>
<label
style={{
display: "block",
marginBottom: "8px",
fontWeight: "bold",
fontSize: "14px",
}}
>
Background Color
</label>
<div style={{ position: "relative" }}>
<div
onClick={() => {
setShowBackgroundPicker(!showBackgroundPicker);
setShowPrimaryPicker(false);
setShowSecondaryPicker(false);
}}
style={{
width: "100%",
height: "40px",
backgroundColor: config.colors.background,
border: "1px solid #ddd",
borderRadius: "4px",
cursor: "pointer",
display: "flex",
alignItems: "center",
justifyContent: "center",
color: config.colors.background === "#F5F5F5" ? "#333" : "#fff",
fontWeight: "bold",
}}
>
{config.colors.background}
</div>
{showBackgroundPicker && (
<div style={{ position: "absolute", zIndex: 1000, marginTop: "4px" }}>
<SketchPicker
color={config.colors.background}
onChange={handleColorChange("background")}
/>
</div>
)}
</div>
</div>
</div>
);
};

19
.storybook/main.ts Normal file
View File

@ -0,0 +1,19 @@
import type { StorybookConfig } from "@storybook/react-webpack5";
const config: StorybookConfig = {
stories: ["../stories/**/*.stories.@(js|jsx|ts|tsx|mdx)"],
addons: [
"@storybook/addon-links",
"@storybook/addon-essentials",
"@storybook/addon-interactions",
],
framework: {
name: "@storybook/react-webpack5",
options: {},
},
docs: {
autodocs: "tag",
},
};
export default config;

27
.storybook/preview.ts Normal file
View File

@ -0,0 +1,27 @@
import type { Preview } from "@storybook/react";
import { CustomizationDecorator } from "../stories/CustomizationDecorator";
import React from "react";
const preview: Preview = {
parameters: {
actions: { argTypesRegex: "^on[A-Z].*" },
controls: {
matchers: {
color: /(background|color)$/i,
date: /Date$/i,
},
},
layout: "fullscreen",
},
decorators: [
(Story) => (
<CustomizationDecorator>
<div style={{ padding: "20px", backgroundColor: "#f5f5f5", minHeight: "100vh" }}>
<Story />
</div>
</CustomizationDecorator>
),
],
};
export default preview;

32
package.json Normal file
View File

@ -0,0 +1,32 @@
{
"name": "fortune-sys-emails",
"version": "1.0.0",
"description": "React Email templates for iGaming system with live customization",
"private": true,
"scripts": {
"dev": "storybook dev -p 6006",
"build": "storybook build",
"preview": "storybook build && npx serve storybook-static"
},
"dependencies": {
"@react-email/components": "^0.0.20",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-color": "^2.19.3"
},
"devDependencies": {
"@storybook/addon-essentials": "^7.6.17",
"@storybook/addon-interactions": "^7.6.17",
"@storybook/addon-links": "^7.6.17",
"@storybook/blocks": "^7.6.17",
"@storybook/react": "^7.6.17",
"@storybook/react-webpack5": "^7.6.17",
"@storybook/test": "^7.6.17",
"@types/node": "^20.11.5",
"@types/react": "^18.2.48",
"@types/react-color": "^3.0.12",
"@types/react-dom": "^18.2.18",
"storybook": "^7.6.17",
"typescript": "^5.3.3"
}
}

32
src/components/Button.tsx Normal file
View File

@ -0,0 +1,32 @@
import { Link } from "@react-email/components";
import { useBrandingConfig } from "../hooks/useBrandingConfig";
interface ButtonProps {
href: string;
children: React.ReactNode;
variant?: "primary" | "secondary";
}
export const Button = ({ href, children, variant = "primary" }: ButtonProps) => {
const config = useBrandingConfig();
const backgroundColor = variant === "primary" ? config.colors.primary : config.colors.secondary;
return (
<Link
href={href}
style={{
display: "inline-block",
padding: "12px 24px",
backgroundColor: backgroundColor,
color: "#FFFFFF",
textDecoration: "none",
borderRadius: "4px",
fontWeight: "bold",
fontFamily: config.font.family,
textAlign: "center" as const,
}}
>
{children}
</Link>
);
};

View File

@ -0,0 +1,98 @@
import {
Html,
Head,
Body,
Container,
Section,
Text,
Heading,
} from "@react-email/components";
import { useBrandingConfig } from "../hooks/useBrandingConfig";
import { Logo } from "./Logo";
interface EmailLayoutProps {
children: React.ReactNode;
title?: string;
}
export const EmailLayout = ({ children, title }: EmailLayoutProps) => {
const config = useBrandingConfig();
return (
<Html>
<Head />
<Body
style={{
fontFamily: config.font.family,
backgroundColor: config.colors.background,
margin: 0,
padding: 0,
}}
>
<Container style={{ maxWidth: "600px", margin: "0 auto", backgroundColor: "#FFFFFF" }}>
{/* Header */}
<Section
style={{
padding: "20px",
backgroundColor: config.colors.primary,
textAlign: "center" as const,
}}
>
<Logo />
</Section>
{/* Main Content */}
<Section
style={{
padding: "40px 20px",
backgroundColor: "#FFFFFF",
}}
>
{title && (
<Heading
style={{
color: config.colors.text,
fontFamily: config.font.family,
fontSize: "24px",
marginTop: 0,
marginBottom: "20px",
}}
>
{title}
</Heading>
)}
<div style={{ color: config.colors.text, fontFamily: config.font.family }}>
{children}
</div>
</Section>
{/* Footer */}
<Section
style={{
padding: "20px",
backgroundColor: config.colors.background,
textAlign: "center" as const,
fontSize: "12px",
color: config.colors.text,
fontFamily: config.font.family,
}}
>
<Text style={{ margin: "4px 0", color: config.colors.text }}>
© {new Date().getFullYear()} {config.companyName}
</Text>
{config.contact?.email && (
<Text style={{ margin: "4px 0", color: config.colors.text }}>
Contact: {config.contact.email}
</Text>
)}
{config.contact?.address && (
<Text style={{ margin: "4px 0", color: config.colors.text }}>
{config.contact.address}
</Text>
)}
</Section>
</Container>
</Body>
</Html>
);
};

25
src/components/Logo.tsx Normal file
View File

@ -0,0 +1,25 @@
import { Img } from "@react-email/components";
import { useBrandingConfig } from "../hooks/useBrandingConfig";
interface LogoProps {
width?: number;
height?: number;
alt?: string;
}
export const Logo = ({ width = 200, height = 60, alt }: LogoProps) => {
const config = useBrandingConfig();
return (
<Img
src={config.logoUrl}
width={width}
height={height}
alt={alt || config.companyName}
style={{
display: "block",
margin: "0 auto",
}}
/>
);
};

View File

@ -0,0 +1,47 @@
export interface BrandingConfig {
companyName: string;
logoUrl: string;
colors: {
primary: string;
secondary: string;
background: string;
text: string;
};
font: {
family: string;
};
contact?: {
email?: string;
address?: string;
};
}
export const defaultBrandingConfig: BrandingConfig = {
companyName: "Fortune Gaming",
logoUrl: "https://via.placeholder.com/200x60/0066CC/FFFFFF?text=Logo",
colors: {
primary: "#0066CC",
secondary: "#00CC66",
background: "#F5F5F5",
text: "#333333",
},
font: {
family: "Arial, sans-serif",
},
contact: {
email: "support@fortunegaming.com",
},
};
export const popularFonts = [
"Arial, sans-serif",
"Helvetica, sans-serif",
"Times New Roman, serif",
"Georgia, serif",
"Verdana, sans-serif",
"Roboto, sans-serif",
"Open Sans, sans-serif",
"Lato, sans-serif",
"Montserrat, sans-serif",
"Poppins, sans-serif",
];

View File

@ -0,0 +1,179 @@
import { Section, Text, Heading, Hr } from "@react-email/components";
import { EmailLayout } from "../../components/EmailLayout";
import { Button } from "../../components/Button";
import { useBrandingConfig } from "../../hooks/useBrandingConfig";
import { formatCurrency, formatPercentage } from "../../utils/emailHelpers";
interface DepositBonusEmailProps {
playerName?: string;
bonusPercentage?: number;
maxBonus?: number;
minimumDeposit?: number;
bonusCode?: string;
depositLink?: string;
expirationDate?: Date | string;
}
export const DepositBonusEmail = ({
playerName = "Valued Player",
bonusPercentage = 100,
maxBonus = 500,
minimumDeposit = 20,
bonusCode = "BONUS100",
depositLink = "https://example.com/deposit",
expirationDate = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
}: DepositBonusEmailProps) => {
const config = useBrandingConfig();
return (
<EmailLayout title="💰 Exclusive Deposit Bonus Just For You!">
<Section>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "20px" }}>
Hello {playerName},
</Text>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "20px" }}>
We have an exclusive deposit bonus offer waiting just for you! Boost your account
with this limited-time promotion.
</Text>
<Section
style={{
backgroundColor: config.colors.primary + "15",
padding: "25px",
borderRadius: "8px",
margin: "20px 0",
border: `3px solid ${config.colors.primary}`,
textAlign: "center" as const,
}}
>
<Heading
style={{
fontSize: "36px",
color: config.colors.primary,
marginTop: 0,
marginBottom: "10px",
fontWeight: "bold",
}}
>
{formatPercentage(bonusPercentage)} BONUS
</Heading>
<Text
style={{
fontSize: "18px",
color: config.colors.text,
margin: "5px 0",
fontWeight: "bold",
}}
>
Up to {formatCurrency(maxBonus)}
</Text>
<Text style={{ fontSize: "14px", color: "#666666", margin: "10px 0 0 0" }}>
Minimum deposit: {formatCurrency(minimumDeposit)}
</Text>
</Section>
<Hr style={{ borderColor: config.colors.primary, margin: "30px 0" }} />
<Section>
<Heading
style={{
fontSize: "20px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "15px",
}}
>
🎟 Bonus Code
</Heading>
<Section
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
border: `2px dashed ${config.colors.primary}`,
}}
>
<Text
style={{
fontSize: "28px",
fontWeight: "bold",
color: config.colors.primary,
letterSpacing: "3px",
margin: 0,
fontFamily: "monospace",
}}
>
{bonusCode}
</Text>
</Section>
</Section>
<Section style={{ margin: "30px 0" }}>
<Heading
style={{
fontSize: "18px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "10px",
}}
>
How to Claim
</Heading>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
1. Make a deposit of at least {formatCurrency(minimumDeposit)}
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
2. Enter bonus code: <strong>{bonusCode}</strong>
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
3. Receive your {formatPercentage(bonusPercentage)} bonus instantly (up to{" "}
{formatCurrency(maxBonus)})
</Text>
</Section>
<Section style={{ textAlign: "center" as const, margin: "30px 0" }}>
<Button href={depositLink}>Deposit Now & Claim Bonus</Button>
</Section>
<Section
style={{
backgroundColor: "#fff3cd",
padding: "15px",
borderRadius: "4px",
border: "1px solid #ffc107",
margin: "20px 0",
}}
>
<Text
style={{
fontSize: "12px",
lineHeight: "20px",
color: "#856404",
margin: 0,
fontWeight: "bold",
}}
>
Limited Time Offer
</Text>
<Text style={{ fontSize: "12px", lineHeight: "20px", color: "#856404", margin: "5px 0 0 0" }}>
This bonus expires on {new Date(expirationDate).toLocaleDateString()}. Terms and
conditions apply. Please gamble responsibly.
</Text>
</Section>
<Text style={{ fontSize: "14px", lineHeight: "22px", marginTop: "20px" }}>
Don't miss out on this amazing opportunity to boost your account!
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px" }}>
Best regards,
<br />
The {config.companyName} Team
</Text>
</Section>
</EmailLayout>
);
};

View File

@ -0,0 +1,104 @@
import { Section, Text, Heading, Hr } from "@react-email/components";
import { EmailLayout } from "../../components/EmailLayout";
import { Button } from "../../components/Button";
import { useBrandingConfig } from "../../hooks/useBrandingConfig";
import { formatCurrency, formatDate } from "../../utils/emailHelpers";
interface RaffleEmailProps {
raffleName?: string;
prizeAmount?: number;
entryDeadline?: Date | string;
drawDate?: Date | string;
entryLink?: string;
participantName?: string;
}
export const RaffleEmail = ({
raffleName = "Mega Jackpot Raffle",
prizeAmount = 10000,
entryDeadline = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
drawDate = new Date(Date.now() + 10 * 24 * 60 * 60 * 1000),
entryLink = "https://example.com/enter-raffle",
participantName = "Valued Member",
}: RaffleEmailProps) => {
const config = useBrandingConfig();
return (
<EmailLayout title={`🎉 ${raffleName} - Enter Now!`}>
<Section>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "20px" }}>
Dear {participantName},
</Text>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "20px" }}>
We're excited to announce our latest raffle event! This is your chance to win big.
</Text>
<Section
style={{
backgroundColor: config.colors.background,
padding: "20px",
borderRadius: "8px",
margin: "20px 0",
border: `2px solid ${config.colors.primary}`,
}}
>
<Heading
style={{
fontSize: "28px",
color: config.colors.primary,
marginTop: 0,
marginBottom: "10px",
textAlign: "center" as const,
}}
>
Grand Prize: {formatCurrency(prizeAmount)}
</Heading>
</Section>
<Hr style={{ borderColor: config.colors.primary, margin: "30px 0" }} />
<Section>
<Heading
style={{
fontSize: "20px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "10px",
}}
>
📋 Raffle Details
</Heading>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
<strong>Entry Deadline:</strong> {formatDate(entryDeadline)}
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
<strong>Draw Date:</strong> {formatDate(drawDate)}
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
<strong>Prize Pool:</strong> {formatCurrency(prizeAmount)}
</Text>
</Section>
<Section style={{ textAlign: "center" as const, margin: "30px 0" }}>
<Button href={entryLink}>Enter Raffle Now</Button>
</Section>
<Text style={{ fontSize: "14px", lineHeight: "22px", color: "#666666" }}>
Don't miss this incredible opportunity! Enter now for your chance to win the grand prize.
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", marginTop: "30px" }}>
Good luck!
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px" }}>
Best regards,
<br />
The {config.companyName} Team
</Text>
</Section>
</EmailLayout>
);
};

View File

@ -0,0 +1,160 @@
import { Section, Text, Heading, Hr } from "@react-email/components";
import { EmailLayout } from "../../components/EmailLayout";
import { Button } from "../../components/Button";
import { useBrandingConfig } from "../../hooks/useBrandingConfig";
import { formatCurrency } from "../../utils/emailHelpers";
interface ReferralBonusEmailProps {
referrerName?: string;
referralBonus?: number;
referredBonus?: number;
referralCode?: string;
referralLink?: string;
expirationDate?: Date | string;
}
export const ReferralBonusEmail = ({
referrerName = "Valued Member",
referralBonus = 50,
referredBonus = 25,
referralCode = "REF123456",
referralLink = "https://example.com/refer",
expirationDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
}: ReferralBonusEmailProps) => {
const config = useBrandingConfig();
return (
<EmailLayout title="🎁 Refer Your Friends & Earn Bonus!">
<Section>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "20px" }}>
Hi {referrerName},
</Text>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "20px" }}>
You've been selected to participate in our exclusive referral program! Refer your
friends and earn amazing bonuses.
</Text>
<Section
style={{
backgroundColor: config.colors.secondary + "20",
padding: "20px",
borderRadius: "8px",
margin: "20px 0",
border: `2px solid ${config.colors.secondary}`,
}}
>
<Heading
style={{
fontSize: "24px",
color: config.colors.secondary,
marginTop: 0,
marginBottom: "15px",
textAlign: "center" as const,
}}
>
Earn {formatCurrency(referralBonus)} for each referral!
</Heading>
<Text
style={{
fontSize: "16px",
textAlign: "center" as const,
color: config.colors.text,
margin: 0,
}}
>
Your friends get {formatCurrency(referredBonus)} too!
</Text>
</Section>
<Hr style={{ borderColor: config.colors.secondary, margin: "30px 0" }} />
<Section>
<Heading
style={{
fontSize: "20px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "15px",
}}
>
🔑 Your Referral Code
</Heading>
<Section
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
border: `1px solid ${config.colors.primary}`,
}}
>
<Text
style={{
fontSize: "32px",
fontWeight: "bold",
color: config.colors.primary,
letterSpacing: "4px",
margin: 0,
fontFamily: "monospace",
}}
>
{referralCode}
</Text>
</Section>
</Section>
<Section style={{ margin: "30px 0" }}>
<Heading
style={{
fontSize: "18px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "10px",
}}
>
📋 How It Works
</Heading>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
1. Share your referral link or code with friends
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
2. They sign up using your code
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", margin: "8px 0" }}>
3. You both receive bonuses when they make their first deposit!
</Text>
</Section>
<Section style={{ textAlign: "center" as const, margin: "30px 0" }}>
<Button href={referralLink} variant="secondary">
Get Your Referral Link
</Button>
</Section>
<Text
style={{
fontSize: "12px",
lineHeight: "20px",
color: "#666666",
fontStyle: "italic",
}}
>
This offer expires on {new Date(expirationDate).toLocaleDateString()}. Terms and
conditions apply.
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", marginTop: "30px" }}>
Start referring today and watch your bonuses grow!
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px" }}>
Best regards,
<br />
The {config.companyName} Team
</Text>
</Section>
</EmailLayout>
);
};

View File

@ -0,0 +1,393 @@
import { Section, Text, Heading, Hr, Row, Column } from "@react-email/components";
import { EmailLayout } from "../../components/EmailLayout";
import { useBrandingConfig } from "../../hooks/useBrandingConfig";
import { formatCurrency, formatDate, formatPercentage } from "../../utils/emailHelpers";
interface MonthlyReportEmailProps {
reportMonth?: Date | string;
totalDeposits?: number;
totalWithdrawals?: number;
activeUsers?: number;
newUsers?: number;
totalRevenue?: number;
averageDeposit?: number;
retentionRate?: number;
topGames?: Array<{ name: string; players: number; revenue: number }>;
growthStats?: {
depositGrowth: number;
userGrowth: number;
revenueGrowth: number;
};
}
export const MonthlyReportEmail = ({
reportMonth = new Date(),
totalDeposits = 520000,
totalWithdrawals = 340000,
activeUsers = 4850,
newUsers = 720,
totalRevenue = 180000,
averageDeposit = 125,
retentionRate = 68.5,
topGames = [
{ name: "Slot Adventure", players: 1850, revenue: 65000 },
{ name: "Blackjack Pro", players: 1320, revenue: 48000 },
{ name: "Roulette Master", players: 1150, revenue: 42000 },
{ name: "Poker Championship", players: 980, revenue: 25000 },
],
growthStats = {
depositGrowth: 12.5,
userGrowth: 8.3,
revenueGrowth: 15.2,
},
}: MonthlyReportEmailProps) => {
const config = useBrandingConfig();
const netRevenue = totalDeposits - totalWithdrawals;
const monthDate = typeof reportMonth === "string" ? new Date(reportMonth) : reportMonth;
const monthName = monthDate.toLocaleDateString("en-US", { month: "long", year: "numeric" });
return (
<EmailLayout title="📊 Monthly Activity Report">
<Section>
<Text style={{ fontSize: "18px", lineHeight: "24px", marginBottom: "10px" }}>
Comprehensive Monthly Summary Report
</Text>
<Text
style={{
fontSize: "16px",
color: config.colors.primary,
marginBottom: "30px",
fontWeight: "bold",
}}
>
{monthName}
</Text>
{/* Executive Summary */}
<Section
style={{
backgroundColor: config.colors.primary + "10",
padding: "20px",
borderRadius: "8px",
marginBottom: "30px",
border: `2px solid ${config.colors.primary}`,
}}
>
<Heading
style={{
fontSize: "24px",
color: config.colors.primary,
marginTop: 0,
marginBottom: "15px",
}}
>
📈 Executive Summary
</Heading>
<Row>
<Column style={{ padding: "10px" }}>
<Text style={{ fontSize: "12px", color: "#666666", margin: "0 0 5px 0" }}>
TOTAL DEPOSITS
</Text>
<Text
style={{
fontSize: "28px",
fontWeight: "bold",
color: config.colors.primary,
margin: 0,
}}
>
{formatCurrency(totalDeposits)}
</Text>
<Text style={{ fontSize: "11px", color: "#28a745", margin: "5px 0 0 0" }}>
{formatPercentage(growthStats.depositGrowth)} vs last month
</Text>
</Column>
<Column style={{ padding: "10px" }}>
<Text style={{ fontSize: "12px", color: "#666666", margin: "0 0 5px 0" }}>
NET REVENUE
</Text>
<Text
style={{
fontSize: "28px",
fontWeight: "bold",
color: "#333",
margin: 0,
}}
>
{formatCurrency(netRevenue)}
</Text>
<Text style={{ fontSize: "11px", color: "#28a745", margin: "5px 0 0 0" }}>
{formatPercentage(growthStats.revenueGrowth)} vs last month
</Text>
</Column>
</Row>
</Section>
<Hr style={{ borderColor: config.colors.primary, margin: "30px 0" }} />
{/* Financial Overview */}
<Section style={{ marginBottom: "25px" }}>
<Heading
style={{
fontSize: "22px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "20px",
}}
>
💰 Financial Overview
</Heading>
<Row>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
}}
>
<Text style={{ fontSize: "12px", color: "#666666", margin: "0 0 5px 0" }}>
Total Revenue
</Text>
<Text
style={{
fontSize: "22px",
fontWeight: "bold",
color: config.colors.text,
margin: 0,
}}
>
{formatCurrency(totalRevenue)}
</Text>
</Section>
</Column>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
}}
>
<Text style={{ fontSize: "12px", color: "#666666", margin: "0 0 5px 0" }}>
Total Withdrawals
</Text>
<Text
style={{
fontSize: "22px",
fontWeight: "bold",
color: config.colors.text,
margin: 0,
}}
>
{formatCurrency(totalWithdrawals)}
</Text>
</Section>
</Column>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
}}
>
<Text style={{ fontSize: "12px", color: "#666666", margin: "0 0 5px 0" }}>
Avg. Deposit
</Text>
<Text
style={{
fontSize: "22px",
fontWeight: "bold",
color: config.colors.text,
margin: 0,
}}
>
{formatCurrency(averageDeposit)}
</Text>
</Section>
</Column>
</Row>
</Section>
{/* User Analytics */}
<Section style={{ marginBottom: "25px" }}>
<Heading
style={{
fontSize: "22px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "20px",
}}
>
👥 User Analytics
</Heading>
<Row>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: config.colors.primary + "15",
padding: "20px",
borderRadius: "4px",
textAlign: "center" as const,
border: `2px solid ${config.colors.primary}`,
}}
>
<Text style={{ fontSize: "14px", color: "#666666", margin: "0 0 10px 0" }}>
Active Users
</Text>
<Text
style={{
fontSize: "32px",
fontWeight: "bold",
color: config.colors.primary,
margin: 0,
}}
>
{activeUsers.toLocaleString()}
</Text>
<Text style={{ fontSize: "11px", color: "#28a745", margin: "5px 0 0 0" }}>
{formatPercentage(growthStats.userGrowth)} growth
</Text>
</Section>
</Column>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: config.colors.secondary + "15",
padding: "20px",
borderRadius: "4px",
textAlign: "center" as const,
border: `2px solid ${config.colors.secondary}`,
}}
>
<Text style={{ fontSize: "14px", color: "#666666", margin: "0 0 10px 0" }}>
New Users
</Text>
<Text
style={{
fontSize: "32px",
fontWeight: "bold",
color: config.colors.secondary,
margin: 0,
}}
>
{newUsers.toLocaleString()}
</Text>
</Section>
</Column>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: config.colors.background,
padding: "20px",
borderRadius: "4px",
textAlign: "center" as const,
border: "2px solid #333",
}}
>
<Text style={{ fontSize: "14px", color: "#666666", margin: "0 0 10px 0" }}>
Retention Rate
</Text>
<Text
style={{
fontSize: "32px",
fontWeight: "bold",
color: "#333",
margin: 0,
}}
>
{formatPercentage(retentionRate)}
</Text>
</Section>
</Column>
</Row>
</Section>
{/* Top Performing Games */}
<Section>
<Heading
style={{
fontSize: "22px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "20px",
}}
>
🎮 Top Performing Games
</Heading>
{topGames.map((game, index) => (
<Section
key={index}
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
marginBottom: "10px",
borderLeft: `4px solid ${config.colors.primary}`,
}}
>
<Row>
<Column style={{ width: "40%" }}>
<Text style={{ fontSize: "16px", fontWeight: "bold", margin: "0 0 5px 0" }}>
#{index + 1} {game.name}
</Text>
</Column>
<Column style={{ width: "30%", textAlign: "center" as const }}>
<Text style={{ fontSize: "13px", color: "#666666", margin: "0 0 3px 0" }}>
Players
</Text>
<Text style={{ fontSize: "14px", fontWeight: "bold", margin: 0 }}>
{game.players.toLocaleString()}
</Text>
</Column>
<Column style={{ width: "30%", textAlign: "right" as const }}>
<Text style={{ fontSize: "13px", color: "#666666", margin: "0 0 3px 0" }}>
Revenue
</Text>
<Text
style={{
fontSize: "14px",
fontWeight: "bold",
color: config.colors.primary,
margin: 0,
}}
>
{formatCurrency(game.revenue)}
</Text>
</Column>
</Row>
</Section>
))}
</Section>
<Hr style={{ borderColor: config.colors.primary, margin: "30px 0" }} />
<Text
style={{
fontSize: "12px",
color: "#666666",
marginTop: "20px",
fontStyle: "italic",
}}
>
This is an automated monthly report. For detailed analytics and insights, please log
into the admin dashboard.
</Text>
<Text style={{ fontSize: "14px", lineHeight: "22px", marginTop: "30px" }}>
Best regards,
<br />
The {config.companyName} Analytics Team
</Text>
</Section>
</EmailLayout>
);
};

View File

@ -0,0 +1,322 @@
import { Section, Text, Heading, Hr, Row, Column } from "@react-email/components";
import { EmailLayout } from "../../components/EmailLayout";
import { useBrandingConfig } from "../../hooks/useBrandingConfig";
import { formatCurrency, formatDate } from "../../utils/emailHelpers";
interface WeeklyReportEmailProps {
reportPeriod?: { start: Date | string; end: Date | string };
totalDeposits?: number;
totalWithdrawals?: number;
activeUsers?: number;
newUsers?: number;
totalRevenue?: number;
topGames?: Array<{ name: string; players: number }>;
}
export const WeeklyReportEmail = ({
reportPeriod = {
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
end: new Date(),
},
totalDeposits = 125000,
totalWithdrawals = 85000,
activeUsers = 1250,
newUsers = 180,
totalRevenue = 40000,
topGames = [
{ name: "Slot Adventure", players: 450 },
{ name: "Blackjack Pro", players: 320 },
{ name: "Roulette Master", players: 280 },
],
}: WeeklyReportEmailProps) => {
const config = useBrandingConfig();
const netRevenue = totalDeposits - totalWithdrawals;
return (
<EmailLayout title="📊 Weekly Activity Report">
<Section>
<Text style={{ fontSize: "16px", lineHeight: "24px", marginBottom: "10px" }}>
Weekly Summary Report
</Text>
<Text
style={{
fontSize: "14px",
color: "#666666",
marginBottom: "30px",
fontWeight: "bold",
}}
>
Period: {formatDate(reportPeriod.start)} - {formatDate(reportPeriod.end)}
</Text>
{/* Key Metrics */}
<Section
style={{
backgroundColor: config.colors.background,
padding: "20px",
borderRadius: "8px",
marginBottom: "20px",
}}
>
<Heading
style={{
fontSize: "22px",
color: config.colors.primary,
marginTop: 0,
marginBottom: "20px",
}}
>
📈 Key Metrics
</Heading>
<Row>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: "#FFFFFF",
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
border: `2px solid ${config.colors.primary}`,
}}
>
<Text
style={{
fontSize: "12px",
color: "#666666",
margin: "0 0 5px 0",
textTransform: "uppercase",
}}
>
Total Deposits
</Text>
<Text
style={{
fontSize: "24px",
fontWeight: "bold",
color: config.colors.primary,
margin: 0,
}}
>
{formatCurrency(totalDeposits)}
</Text>
</Section>
</Column>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: "#FFFFFF",
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
border: `2px solid ${config.colors.secondary}`,
}}
>
<Text
style={{
fontSize: "12px",
color: "#666666",
margin: "0 0 5px 0",
textTransform: "uppercase",
}}
>
Total Withdrawals
</Text>
<Text
style={{
fontSize: "24px",
fontWeight: "bold",
color: config.colors.secondary,
margin: 0,
}}
>
{formatCurrency(totalWithdrawals)}
</Text>
</Section>
</Column>
</Row>
<Row style={{ marginTop: "10px" }}>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: "#FFFFFF",
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
border: "2px solid #333",
}}
>
<Text
style={{
fontSize: "12px",
color: "#666666",
margin: "0 0 5px 0",
textTransform: "uppercase",
}}
>
Net Revenue
</Text>
<Text
style={{
fontSize: "24px",
fontWeight: "bold",
color: "#333",
margin: 0,
}}
>
{formatCurrency(netRevenue)}
</Text>
</Section>
</Column>
<Column style={{ padding: "10px" }}>
<Section
style={{
backgroundColor: "#FFFFFF",
padding: "15px",
borderRadius: "4px",
textAlign: "center" as const,
border: "2px solid #666",
}}
>
<Text
style={{
fontSize: "12px",
color: "#666666",
margin: "0 0 5px 0",
textTransform: "uppercase",
}}
>
Total Revenue
</Text>
<Text
style={{
fontSize: "24px",
fontWeight: "bold",
color: "#666",
margin: 0,
}}
>
{formatCurrency(totalRevenue)}
</Text>
</Section>
</Column>
</Row>
</Section>
<Hr style={{ borderColor: config.colors.primary, margin: "30px 0" }} />
{/* User Statistics */}
<Section style={{ marginBottom: "20px" }}>
<Heading
style={{
fontSize: "20px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "15px",
}}
>
👥 User Statistics
</Heading>
<Row>
<Column
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
marginRight: "10px",
}}
>
<Text style={{ fontSize: "14px", color: "#666666", margin: "0 0 5px 0" }}>
Active Users
</Text>
<Text
style={{
fontSize: "28px",
fontWeight: "bold",
color: config.colors.primary,
margin: 0,
}}
>
{activeUsers.toLocaleString()}
</Text>
</Column>
<Column
style={{
backgroundColor: config.colors.background,
padding: "15px",
borderRadius: "4px",
}}
>
<Text style={{ fontSize: "14px", color: "#666666", margin: "0 0 5px 0" }}>
New Users
</Text>
<Text
style={{
fontSize: "28px",
fontWeight: "bold",
color: config.colors.secondary,
margin: 0,
}}
>
{newUsers.toLocaleString()}
</Text>
</Column>
</Row>
</Section>
{/* Top Games */}
<Section>
<Heading
style={{
fontSize: "20px",
color: config.colors.text,
marginTop: "20px",
marginBottom: "15px",
}}
>
🎮 Top Games This Week
</Heading>
{topGames.map((game, index) => (
<Section
key={index}
style={{
backgroundColor: config.colors.background,
padding: "12px 15px",
borderRadius: "4px",
marginBottom: "8px",
}}
>
<Row>
<Column style={{ width: "60%" }}>
<Text style={{ fontSize: "14px", fontWeight: "bold", margin: 0 }}>
{index + 1}. {game.name}
</Text>
</Column>
<Column style={{ textAlign: "right" as const }}>
<Text style={{ fontSize: "14px", color: "#666666", margin: 0 }}>
{game.players.toLocaleString()} players
</Text>
</Column>
</Row>
</Section>
))}
</Section>
<Text
style={{
fontSize: "12px",
color: "#666666",
marginTop: "30px",
fontStyle: "italic",
}}
>
This is an automated weekly report. For detailed analytics, please log into the admin
dashboard.
</Text>
</Section>
</EmailLayout>
);
};

View File

@ -0,0 +1,17 @@
import { useContext, createContext, ReactNode } from "react";
import { BrandingConfig, defaultBrandingConfig } from "../config/branding.config";
interface BrandingContextValue {
config: BrandingConfig;
updateConfig: (updates: Partial<BrandingConfig>) => void;
}
export const BrandingContext = createContext<BrandingContextValue>({
config: defaultBrandingConfig,
updateConfig: () => {},
});
export const useBrandingConfig = (): BrandingConfig => {
const { config } = useContext(BrandingContext);
return config;
};

19
src/utils/emailHelpers.ts Normal file
View File

@ -0,0 +1,19 @@
export const formatCurrency = (amount: number, currency: string = "USD"): string => {
return new Intl.NumberFormat("en-US", {
style: "currency",
currency: currency,
}).format(amount);
};
export const formatDate = (date: Date | string): string => {
const dateObj = typeof date === "string" ? new Date(date) : date;
return new Intl.DateTimeFormat("en-US", {
year: "numeric",
month: "long",
day: "numeric",
}).format(dateObj);
};
export const formatPercentage = (value: number): string => {
return `${value.toFixed(2)}%`;
};

View File

@ -0,0 +1,84 @@
import React, { useState, useCallback, useEffect } from "react";
import { BrandingContext } from "../src/hooks/useBrandingConfig";
import { BrandingConfig, defaultBrandingConfig } from "../src/config/branding.config";
interface CustomizationDecoratorProps {
children: React.ReactNode;
}
const CUSTOMIZATION_STORAGE_KEY = "email-branding-config";
export const CustomizationDecorator = ({ children }: CustomizationDecoratorProps) => {
const [config, setConfig] = useState<BrandingConfig>(() => {
// Load from localStorage if available
if (typeof window !== "undefined") {
const saved = localStorage.getItem(CUSTOMIZATION_STORAGE_KEY);
if (saved) {
try {
return { ...defaultBrandingConfig, ...JSON.parse(saved) };
} catch (e) {
return defaultBrandingConfig;
}
}
}
return defaultBrandingConfig;
});
const updateConfig = useCallback((updates: Partial<BrandingConfig>) => {
setConfig((prev) => {
const newConfig = {
...prev,
...updates,
colors: {
...prev.colors,
...(updates.colors || {}),
},
font: {
...prev.font,
...(updates.font || {}),
},
};
// Save to localStorage
if (typeof window !== "undefined") {
localStorage.setItem(CUSTOMIZATION_STORAGE_KEY, JSON.stringify(newConfig));
}
return newConfig;
});
}, []);
// Listen for customization updates from panel
useEffect(() => {
const handleStorageChange = (e: StorageEvent) => {
if (e.key === CUSTOMIZATION_STORAGE_KEY && e.newValue) {
try {
const newConfig = { ...defaultBrandingConfig, ...JSON.parse(e.newValue) };
setConfig(newConfig);
} catch (e) {
// Ignore parse errors
}
}
};
const handleMessage = (event: MessageEvent) => {
if (event.data?.type === "CUSTOMIZATION_UPDATE") {
updateConfig(event.data.config);
}
};
window.addEventListener("storage", handleStorageChange);
window.addEventListener("message", handleMessage);
return () => {
window.removeEventListener("storage", handleStorageChange);
window.removeEventListener("message", handleMessage);
};
}, [updateConfig]);
return (
<BrandingContext.Provider value={{ config, updateConfig }}>
{children}
</BrandingContext.Provider>
);
};

View File

@ -0,0 +1,37 @@
import type { Meta, StoryObj } from "@storybook/react";
import { DepositBonusEmail } from "../src/emails/promotional/DepositBonusEmail";
const meta: Meta<typeof DepositBonusEmail> = {
title: "Emails/Promotional/Deposit Bonus Email",
component: DepositBonusEmail,
parameters: {
layout: "fullscreen",
},
};
export default meta;
type Story = StoryObj<typeof DepositBonusEmail>;
export const Default: Story = {
args: {
playerName: "John Doe",
bonusPercentage: 100,
maxBonus: 500,
minimumDeposit: 20,
bonusCode: "BONUS100",
depositLink: "https://example.com/deposit",
expirationDate: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
},
};
export const HighValueBonus: Story = {
args: {
playerName: "VIP Player",
bonusPercentage: 200,
maxBonus: 2000,
minimumDeposit: 100,
bonusCode: "VIP200",
depositLink: "https://example.com/deposit",
expirationDate: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
},
};

View File

@ -0,0 +1,62 @@
import type { Meta, StoryObj } from "@storybook/react";
import { MonthlyReportEmail } from "../src/emails/reports/MonthlyReportEmail";
const meta: Meta<typeof MonthlyReportEmail> = {
title: "Emails/Reports/Monthly Report Email",
component: MonthlyReportEmail,
parameters: {
layout: "fullscreen",
},
};
export default meta;
type Story = StoryObj<typeof MonthlyReportEmail>;
export const Default: Story = {
args: {
reportMonth: new Date(),
totalDeposits: 520000,
totalWithdrawals: 340000,
activeUsers: 4850,
newUsers: 720,
totalRevenue: 180000,
averageDeposit: 125,
retentionRate: 68.5,
topGames: [
{ name: "Slot Adventure", players: 1850, revenue: 65000 },
{ name: "Blackjack Pro", players: 1320, revenue: 48000 },
{ name: "Roulette Master", players: 1150, revenue: 42000 },
{ name: "Poker Championship", players: 980, revenue: 25000 },
],
growthStats: {
depositGrowth: 12.5,
userGrowth: 8.3,
revenueGrowth: 15.2,
},
},
};
export const HighPerformance: Story = {
args: {
reportMonth: new Date(),
totalDeposits: 1200000,
totalWithdrawals: 780000,
activeUsers: 11200,
newUsers: 1850,
totalRevenue: 420000,
averageDeposit: 180,
retentionRate: 75.2,
topGames: [
{ name: "Slot Adventure", players: 4200, revenue: 185000 },
{ name: "Blackjack Pro", players: 3100, revenue: 125000 },
{ name: "Roulette Master", players: 2800, revenue: 98000 },
{ name: "Poker Championship", players: 2100, revenue: 85000 },
{ name: "Live Casino", players: 1800, revenue: 72000 },
],
growthStats: {
depositGrowth: 25.8,
userGrowth: 18.5,
revenueGrowth: 32.1,
},
},
};

View File

@ -0,0 +1,35 @@
import type { Meta, StoryObj } from "@storybook/react";
import { RaffleEmail } from "../src/emails/promotional/RaffleEmail";
const meta: Meta<typeof RaffleEmail> = {
title: "Emails/Promotional/Raffle Email",
component: RaffleEmail,
parameters: {
layout: "fullscreen",
},
};
export default meta;
type Story = StoryObj<typeof RaffleEmail>;
export const Default: Story = {
args: {
raffleName: "Mega Jackpot Raffle",
prizeAmount: 10000,
entryDeadline: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000),
drawDate: new Date(Date.now() + 10 * 24 * 60 * 60 * 1000),
entryLink: "https://example.com/enter-raffle",
participantName: "John Doe",
},
};
export const HighValueRaffle: Story = {
args: {
raffleName: "Ultimate Million Dollar Raffle",
prizeAmount: 1000000,
entryDeadline: new Date(Date.now() + 14 * 24 * 60 * 60 * 1000),
drawDate: new Date(Date.now() + 21 * 24 * 60 * 60 * 1000),
entryLink: "https://example.com/enter-raffle",
participantName: "Valued Member",
},
};

View File

@ -0,0 +1,35 @@
import type { Meta, StoryObj } from "@storybook/react";
import { ReferralBonusEmail } from "../src/emails/promotional/ReferralBonusEmail";
const meta: Meta<typeof ReferralBonusEmail> = {
title: "Emails/Promotional/Referral Bonus Email",
component: ReferralBonusEmail,
parameters: {
layout: "fullscreen",
},
};
export default meta;
type Story = StoryObj<typeof ReferralBonusEmail>;
export const Default: Story = {
args: {
referrerName: "John Doe",
referralBonus: 50,
referredBonus: 25,
referralCode: "REF123456",
referralLink: "https://example.com/refer",
expirationDate: new Date(Date.now() + 30 * 24 * 60 * 60 * 1000),
},
};
export const PremiumReferral: Story = {
args: {
referrerName: "VIP Member",
referralBonus: 100,
referredBonus: 50,
referralCode: "VIPREF789",
referralLink: "https://example.com/refer",
expirationDate: new Date(Date.now() + 60 * 24 * 60 * 60 * 1000),
},
};

View File

@ -0,0 +1,52 @@
import type { Meta, StoryObj } from "@storybook/react";
import { WeeklyReportEmail } from "../src/emails/reports/WeeklyReportEmail";
const meta: Meta<typeof WeeklyReportEmail> = {
title: "Emails/Reports/Weekly Report Email",
component: WeeklyReportEmail,
parameters: {
layout: "fullscreen",
},
};
export default meta;
type Story = StoryObj<typeof WeeklyReportEmail>;
export const Default: Story = {
args: {
reportPeriod: {
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
end: new Date(),
},
totalDeposits: 125000,
totalWithdrawals: 85000,
activeUsers: 1250,
newUsers: 180,
totalRevenue: 40000,
topGames: [
{ name: "Slot Adventure", players: 450 },
{ name: "Blackjack Pro", players: 320 },
{ name: "Roulette Master", players: 280 },
],
},
};
export const HighActivity: Story = {
args: {
reportPeriod: {
start: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000),
end: new Date(),
},
totalDeposits: 350000,
totalWithdrawals: 220000,
activeUsers: 3200,
newUsers: 450,
totalRevenue: 130000,
topGames: [
{ name: "Slot Adventure", players: 1250 },
{ name: "Blackjack Pro", players: 980 },
{ name: "Roulette Master", players: 720 },
{ name: "Poker Championship", players: 560 },
],
},
};

25
tsconfig.json Normal file
View File

@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "ES2020",
"useDefineForClassFields": true,
"lib": ["ES2020", "DOM", "DOM.Iterable"],
"module": "ESNext",
"skipLibCheck": true,
"moduleResolution": "bundler",
"allowImportingTsExtensions": true,
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx",
"strict": true,
"noUnusedLocals": true,
"noUnusedParameters": true,
"noFallthroughCasesInSwitch": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["src", ".storybook", "stories"],
"references": [{ "path": "./tsconfig.node.json" }]
}

11
tsconfig.node.json Normal file
View File

@ -0,0 +1,11 @@
{
"compilerOptions": {
"composite": true,
"skipLibCheck": true,
"module": "ESNext",
"moduleResolution": "bundler",
"allowSyntheticDefaultImports": true,
"strict": true
},
"include": ["package.json"]
}