first commit
This commit is contained in:
commit
e232c50e52
8
.gitignore
vendored
Normal file
8
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
node_modules
|
||||
dist
|
||||
.storybook-static
|
||||
.DS_Store
|
||||
*.log
|
||||
*.cache
|
||||
.vscode
|
||||
.idea
|
||||
239
.storybook/CustomizationPanel.tsx
Normal file
239
.storybook/CustomizationPanel.tsx
Normal 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
19
.storybook/main.ts
Normal 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
27
.storybook/preview.ts
Normal 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
32
package.json
Normal 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
32
src/components/Button.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
98
src/components/EmailLayout.tsx
Normal file
98
src/components/EmailLayout.tsx
Normal 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
25
src/components/Logo.tsx
Normal 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",
|
||||
}}
|
||||
/>
|
||||
);
|
||||
};
|
||||
47
src/config/branding.config.ts
Normal file
47
src/config/branding.config.ts
Normal 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",
|
||||
];
|
||||
179
src/emails/promotional/DepositBonusEmail.tsx
Normal file
179
src/emails/promotional/DepositBonusEmail.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
104
src/emails/promotional/RaffleEmail.tsx
Normal file
104
src/emails/promotional/RaffleEmail.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
160
src/emails/promotional/ReferralBonusEmail.tsx
Normal file
160
src/emails/promotional/ReferralBonusEmail.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
393
src/emails/reports/MonthlyReportEmail.tsx
Normal file
393
src/emails/reports/MonthlyReportEmail.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
322
src/emails/reports/WeeklyReportEmail.tsx
Normal file
322
src/emails/reports/WeeklyReportEmail.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
17
src/hooks/useBrandingConfig.ts
Normal file
17
src/hooks/useBrandingConfig.ts
Normal 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
19
src/utils/emailHelpers.ts
Normal 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)}%`;
|
||||
};
|
||||
84
stories/CustomizationDecorator.tsx
Normal file
84
stories/CustomizationDecorator.tsx
Normal 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>
|
||||
);
|
||||
};
|
||||
37
stories/DepositBonusEmail.stories.tsx
Normal file
37
stories/DepositBonusEmail.stories.tsx
Normal 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),
|
||||
},
|
||||
};
|
||||
62
stories/MonthlyReportEmail.stories.tsx
Normal file
62
stories/MonthlyReportEmail.stories.tsx
Normal 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,
|
||||
},
|
||||
},
|
||||
};
|
||||
35
stories/RaffleEmail.stories.tsx
Normal file
35
stories/RaffleEmail.stories.tsx
Normal 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",
|
||||
},
|
||||
};
|
||||
35
stories/ReferralBonusEmail.stories.tsx
Normal file
35
stories/ReferralBonusEmail.stories.tsx
Normal 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),
|
||||
},
|
||||
};
|
||||
52
stories/WeeklyReportEmail.stories.tsx
Normal file
52
stories/WeeklyReportEmail.stories.tsx
Normal 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
25
tsconfig.json
Normal 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
11
tsconfig.node.json
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
{
|
||||
"compilerOptions": {
|
||||
"composite": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "bundler",
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"strict": true
|
||||
},
|
||||
"include": ["package.json"]
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user