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