diff --git a/.storybook/CustomizationPanel.tsx b/.storybook/CustomizationPanel.tsx index 0d4c020..421633e 100644 --- a/.storybook/CustomizationPanel.tsx +++ b/.storybook/CustomizationPanel.tsx @@ -13,14 +13,26 @@ export const CustomizationPanel: React.FC = ({ active } const [showBackgroundPicker, setShowBackgroundPicker] = useState(false); useEffect(() => { - // Send initial config to preview - window.postMessage( - { - type: "CUSTOMIZATION_UPDATE", - config: config, - }, - "*" - ); + // Save to localStorage for persistence and cross-tab sync + if (typeof window !== "undefined") { + localStorage.setItem("email-branding-config", JSON.stringify(config)); + + // Send message for immediate updates + window.postMessage( + { + type: "CUSTOMIZATION_UPDATE", + config: config, + }, + "*" + ); + + // Dispatch custom event for same-tab updates + window.dispatchEvent( + new CustomEvent("customization-update", { + detail: { config }, + }) + ); + } }, [config]); const handleColorChange = (colorType: "primary" | "secondary" | "background") => ( diff --git a/.storybook/manager.ts b/.storybook/manager.ts new file mode 100644 index 0000000..1ee6724 --- /dev/null +++ b/.storybook/manager.ts @@ -0,0 +1,5 @@ +import { addons } from "@storybook/manager-api"; + +addons.setConfig({ + panelPosition: "bottom", +}); diff --git a/.storybook/preview.ts b/.storybook/preview.ts index dab5491..538312e 100644 --- a/.storybook/preview.ts +++ b/.storybook/preview.ts @@ -2,6 +2,22 @@ import type { Preview } from "@storybook/react"; import { CustomizationDecorator } from "../stories/CustomizationDecorator"; import React from "react"; +// Load Google Fonts for better rendering in Storybook +const googleFonts = [ + "Roboto", + "Open+Sans", + "Lato", + "Montserrat", + "Poppins", +]; + +if (typeof document !== "undefined") { + const link = document.createElement("link"); + link.rel = "stylesheet"; + link.href = `https://fonts.googleapis.com/css2?${googleFonts.map(font => `family=${font}:wght@400;600;700`).join("&")}&display=swap`; + document.head.appendChild(link); +} + const preview: Preview = { parameters: { actions: { argTypesRegex: "^on[A-Z].*" }, diff --git a/README.md b/README.md new file mode 100644 index 0000000..e785235 --- /dev/null +++ b/README.md @@ -0,0 +1,198 @@ +# Fortune System Email Templates + +A collection of React Email templates for iGaming systems with live customization capabilities. Preview templates in Storybook and customize colors, fonts, and branding in real-time. + +## Features + +- 📧 **Email Templates**: 5 professional email templates for promotional and reporting purposes + - Promotional: Raffle, Referral Bonus, Deposit Bonus + - Reports: Weekly Activity Report, Monthly Activity Report +- 🎨 **Live Customization**: Real-time preview updates as you change colors and fonts +- đŸŽ¯ **Color Pickers**: Visual color pickers for primary, secondary, and background colors +- 🔤 **Font Selection**: Dropdown with 10 popular web-safe and Google Fonts +- âš™ī¸ **Configurable Branding**: Easy configuration through a centralized config file +- 📱 **Email Compatible**: Uses React Email components for maximum email client compatibility +- 🎭 **Storybook Preview**: Professional preview system for showcasing templates to clients + +## Project Structure + +``` +fortune-sys-emails/ +├── src/ +│ ├── emails/ +│ │ ├── promotional/ # Promotional email templates +│ │ └── reports/ # Report email templates +│ ├── components/ # Reusable email components +│ ├── config/ # Branding configuration +│ ├── hooks/ # React hooks +│ └── utils/ # Helper functions +├── .storybook/ # Storybook configuration +├── stories/ # Storybook stories +└── package.json +``` + +## Installation + +1. Install dependencies: + +```bash +npm install +``` + +2. Start Storybook: + +```bash +npm run dev +``` + +3. Open your browser to `http://localhost:6006` + +## Usage + +### Viewing Templates + +1. Start Storybook with `npm run dev` +2. Navigate to the "Emails" section in the Storybook sidebar +3. Browse through the available templates: + - **Promotional**: Raffle Email, Referral Bonus Email, Deposit Bonus Email + - **Reports**: Weekly Report Email, Monthly Report Email + +### Customizing Branding + +#### Method 1: Live Customization Panel + +1. Navigate to "Customization > Customization Panel" in Storybook +2. Use the controls to: + - Select a font from the dropdown (10 popular fonts available) + - Pick colors using the color pickers: + - Primary Color + - Secondary Color + - Background Color +3. Changes are applied instantly to all email templates +4. Customizations are saved in browser localStorage for persistence + +#### Method 2: Configuration File + +Edit `src/config/branding.config.ts` to set default branding: + +```typescript +export const defaultBrandingConfig: BrandingConfig = { + companyName: "Your Company Name", + logoUrl: "https://your-logo-url.com/logo.png", + colors: { + primary: "#0066CC", + secondary: "#00CC66", + background: "#F5F5F5", + text: "#333333", + }, + font: { + family: "Arial, sans-serif", + }, + contact: { + email: "support@yourcompany.com", + }, +}; +``` + +### Available Fonts + +The following fonts are available in the customization panel: + +- Arial +- Helvetica +- Times New Roman +- Georgia +- Verdana +- Roboto +- Open Sans +- Lato +- Montserrat +- Poppins + +## Email Templates + +### Promotional Templates + +#### Raffle Email +- **Purpose**: Promote raffle events and contests +- **Props**: `raffleName`, `prizeAmount`, `entryDeadline`, `drawDate`, `entryLink`, `participantName` + +#### Referral Bonus Email +- **Purpose**: Encourage user referrals with bonus offers +- **Props**: `referrerName`, `referralBonus`, `referredBonus`, `referralCode`, `referralLink`, `expirationDate` + +#### Deposit Bonus Email +- **Purpose**: Promote deposit bonuses and special offers +- **Props**: `playerName`, `bonusPercentage`, `maxBonus`, `minimumDeposit`, `bonusCode`, `depositLink`, `expirationDate` + +### Report Templates + +#### Weekly Report Email +- **Purpose**: Weekly activity summary for administrators +- **Props**: `reportPeriod`, `totalDeposits`, `totalWithdrawals`, `activeUsers`, `newUsers`, `totalRevenue`, `topGames` + +#### Monthly Report Email +- **Purpose**: Comprehensive monthly activity report +- **Props**: `reportMonth`, `totalDeposits`, `totalWithdrawals`, `activeUsers`, `newUsers`, `totalRevenue`, `averageDeposit`, `retentionRate`, `topGames`, `growthStats` + +## Customization Workflow + +1. **Open Storybook**: Run `npm run dev` +2. **Open Customization Panel**: Navigate to "Customization > Customization Panel" +3. **Customize**: Adjust colors and fonts using the controls +4. **Preview**: Navigate to any email template to see the changes +5. **Share**: Share the Storybook URL with clients for feedback +6. **Export**: Copy the customization values and update `branding.config.ts` for persistence + +## Development + +### Adding New Templates + +1. Create a new component in `src/emails/` (appropriate subfolder) +2. Use the `EmailLayout` component as a wrapper +3. Use `useBrandingConfig` hook to access branding configuration +4. Create a story file in `stories/` directory + +Example: + +```typescript +import { EmailLayout } from "../../components/EmailLayout"; +import { useBrandingConfig } from "../../hooks/useBrandingConfig"; + +export const MyNewEmail = () => { + const config = useBrandingConfig(); + return ( + + {/* Your email content */} + + ); +}; +``` + +### Building + +Build Storybook for static hosting: + +```bash +npm run build +``` + +The built files will be in the `storybook-static` directory. + +## Technologies Used + +- **React Email**: For email-compatible React components +- **Storybook**: For component preview and documentation +- **TypeScript**: For type safety +- **React Color**: For color picker functionality + +## Notes + +- All templates are responsive and tested for email client compatibility +- Customizations made in the panel are stored in browser localStorage +- The configuration file (`branding.config.ts`) sets the default values +- Google Fonts (Roboto, Open Sans, Lato, Montserrat, Poppins) need to be loaded in your email system or use web-safe fonts for maximum compatibility + +## License + +Private project - All rights reserved diff --git a/src/emails/admin/SportsbookTicketIssuesEmail.tsx b/src/emails/admin/SportsbookTicketIssuesEmail.tsx new file mode 100644 index 0000000..1b52545 --- /dev/null +++ b/src/emails/admin/SportsbookTicketIssuesEmail.tsx @@ -0,0 +1,343 @@ +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 SportsbookTicketIssuesEmailProps { + ticketId?: string; + issueType?: "void" | "cancellation" | "correction" | "settlement_error"; + ticketDetails?: { + ticketNumber: string; + betType: string; + event: string; + selection: string; + stake: number; + potentialPayout: number; + status: string; + placedDate: Date | string; + }; + reason?: string; + actionTaken?: string; + affectedUser?: { + username: string; + email: string; + accountId: string; + }; + resolutionNotes?: string; + administrator?: string; +} + +export const SportsbookTicketIssuesEmail = ({ + ticketId = "TKT-2024-001234", + issueType = "void", + ticketDetails = { + ticketNumber: "TKT-2024-001234", + betType: "Single", + event: "Manchester United vs Liverpool", + selection: "Manchester United Win", + stake: 100, + potentialPayout: 250, + status: "Void", + placedDate: new Date(), + }, + reason = "Event cancellation - Match postponed due to weather conditions", + actionTaken = "Ticket voided and stake refunded to user account", + affectedUser = { + username: "player123", + email: "player@example.com", + accountId: "ACC-12345", + }, + resolutionNotes = "Refund processed automatically. User notified via email.", + administrator = "Admin Team", +}: SportsbookTicketIssuesEmailProps) => { + const config = useBrandingConfig(); + + const getIssueTypeLabel = (type: string) => { + switch (type) { + case "void": + return "Ticket Voided"; + case "cancellation": + return "Ticket Cancelled"; + case "correction": + return "Ticket Correction"; + case "settlement_error": + return "Settlement Error"; + default: + return "Ticket Issue"; + } + }; + + const getSeverityColor = (type: string) => { + switch (type) { + case "settlement_error": + return "#dc3545"; // Red for errors + case "correction": + return "#ffc107"; // Yellow for corrections + default: + return config.colors.primary; + } + }; + + return ( + +
+
+ + {getIssueTypeLabel(issueType)} + + + Ticket ID: {ticketId} + + + Issue Type: {issueType.toUpperCase()} + +
+ +
+ + {/* Affected User Information */} +
+ + 👤 Affected User + +
+ + + + Username: {affectedUser.username} + + + Email: {affectedUser.email} + + + Account ID: {affectedUser.accountId} + + + +
+
+ + {/* Ticket Details */} +
+ + đŸŽĢ Ticket Details + +
+ + + + Ticket Number: + + + Bet Type: + + + Event: + + + Selection: + + + Placed Date: + + + + + {ticketDetails.ticketNumber} + + + {ticketDetails.betType} + + + {ticketDetails.event} + + + {ticketDetails.selection} + + + {formatDate(ticketDetails.placedDate)} + + + + +
+ + + + + Stake: + + + Potential Payout: + + + Status: + + + + + {formatCurrency(ticketDetails.stake)} + + + {formatCurrency(ticketDetails.potentialPayout)} + + + {ticketDetails.status} + + + +
+
+ + {/* Issue Information */} +
+ + 📋 Issue Information + +
+ + Reason: + + + {reason} + + + + Action Taken: + + + {actionTaken} + + + {resolutionNotes && ( + <> + + Resolution Notes: + + + {resolutionNotes} + + + )} +
+
+ +
+ + + This is an automated notification from the {config.companyName}{" "} + sportsbook system. +
+ Processed by: {administrator} +
+ Timestamp: {new Date().toLocaleString()} +
+
+
+ ); +}; diff --git a/src/emails/admin/SystemAlertEmail.tsx b/src/emails/admin/SystemAlertEmail.tsx new file mode 100644 index 0000000..47b4b63 --- /dev/null +++ b/src/emails/admin/SystemAlertEmail.tsx @@ -0,0 +1,300 @@ +import { Section, Text, Heading, Hr, Row, Column } from "@react-email/components"; +import { EmailLayout } from "../../components/EmailLayout"; +import { useBrandingConfig } from "../../hooks/useBrandingConfig"; +import { formatDate } from "../../utils/emailHelpers"; + +interface SystemAlertEmailProps { + alertId?: string; + alertType?: "critical" | "warning" | "info" | "maintenance"; + alertTitle?: string; + alertDescription?: string; + affectedSystems?: string[]; + severity?: "high" | "medium" | "low"; + detectedAt?: Date | string; + resolvedAt?: Date | string; + status?: "active" | "resolved" | "investigating"; + actionRequired?: string; + technicalDetails?: string; + incidentNumber?: string; +} + +export const SystemAlertEmail = ({ + alertId = "ALERT-2024-001", + alertType = "warning", + alertTitle = "High Transaction Volume Detected", + alertDescription = "Transaction processing system is experiencing higher than normal load. Response times may be slightly delayed.", + affectedSystems = ["Payment Gateway", "Transaction Processor", "User Accounts"], + severity = "medium", + detectedAt = new Date(), + resolvedAt, + status = "investigating", + actionRequired = "Monitor system performance and scale resources if needed.", + technicalDetails = "CPU usage: 85%, Memory usage: 72%, Response time: 1.2s (avg)", + incidentNumber = "INC-2024-1234", +}: SystemAlertEmailProps) => { + const config = useBrandingConfig(); + + const getAlertColor = (type: string) => { + switch (type) { + case "critical": + return "#dc3545"; // Red + case "warning": + return "#ffc107"; // Yellow + case "maintenance": + return "#17a2b8"; // Blue + case "info": + return config.colors.primary; + default: + return config.colors.primary; + } + }; + + const getSeverityColor = (severity: string) => { + switch (severity) { + case "high": + return "#dc3545"; + case "medium": + return "#ffc107"; + case "low": + return "#28a745"; + default: + return config.colors.primary; + } + }; + + const getStatusColor = (status: string) => { + switch (status) { + case "resolved": + return "#28a745"; + case "investigating": + return "#ffc107"; + case "active": + return "#dc3545"; + default: + return config.colors.primary; + } + }; + + return ( + +
+
+ + {alertTitle} + + + + + Alert ID: {alertId} + + {incidentNumber && ( + + Incident #: {incidentNumber} + + )} + + + + Type: {alertType.toUpperCase()} + + + Severity:{" "} + + {severity.toUpperCase()} + + + + Status:{" "} + + {status.toUpperCase()} + + + + +
+ +
+ + {/* Alert Description */} +
+ + 📋 Alert Description + +
+ + {alertDescription} + +
+
+ + {/* Affected Systems */} + {affectedSystems && affectedSystems.length > 0 && ( +
+ + 🔧 Affected Systems + +
+
    + {affectedSystems.map((system, index) => ( +
  • + {system} +
  • + ))} +
+
+
+ )} + + {/* Timeline */} +
+ + ⏰ Timeline + +
+ + Detected At: {formatDate(detectedAt)} ( + {new Date(detectedAt).toLocaleTimeString()}) + + {resolvedAt && ( + + Resolved At: {formatDate(resolvedAt)} ( + {new Date(resolvedAt).toLocaleTimeString()}) + + )} + {!resolvedAt && ( + + Status: Under Investigation + + )} +
+
+ + {/* Technical Details */} + {technicalDetails && ( +
+ + 🔍 Technical Details + +
+ + {technicalDetails} + +
+
+ )} + + {/* Action Required */} + {actionRequired && ( +
+ + ⚡ Action Required + +
+ {actionRequired} +
+
+ )} + +
+ + + This is an automated system alert from {config.companyName}. +
+ Timestamp: {new Date().toLocaleString()} +
+ Please monitor the system dashboard for updates. +
+
+
+ ); +}; diff --git a/src/emails/admin/UserSuspensionEmail.tsx b/src/emails/admin/UserSuspensionEmail.tsx new file mode 100644 index 0000000..2612829 --- /dev/null +++ b/src/emails/admin/UserSuspensionEmail.tsx @@ -0,0 +1,247 @@ +import { Section, Text, Heading, Hr } from "@react-email/components"; +import { EmailLayout } from "../../components/EmailLayout"; +import { useBrandingConfig } from "../../hooks/useBrandingConfig"; +import { formatDate } from "../../utils/emailHelpers"; + +interface UserSuspensionEmailProps { + userId?: string; + username?: string; + email?: string; + suspensionType?: "temporary" | "permanent" | "warning"; + suspensionReason?: string; + suspensionDuration?: number; // in days + suspensionStartDate?: Date | string; + suspensionEndDate?: Date | string; + violationDetails?: string[]; + administrator?: string; + appealInformation?: string; +} + +export const UserSuspensionEmail = ({ + userId = "ACC-12345", + username = "player123", + email = "player@example.com", + suspensionType = "temporary", + suspensionReason = "Violation of terms of service - Suspicious betting patterns detected", + suspensionDuration = 30, + suspensionStartDate = new Date(), + suspensionEndDate = new Date(Date.now() + 30 * 24 * 60 * 60 * 1000), + violationDetails = [ + "Multiple accounts detected", + "Violation of betting rules", + "Suspicious activity patterns", + ], + administrator = "Security Team", + appealInformation = "To appeal this decision, please contact support@example.com within 7 days.", +}: UserSuspensionEmailProps) => { + const config = useBrandingConfig(); + + const getSuspensionColor = (type: string) => { + switch (type) { + case "permanent": + return "#dc3545"; // Red + case "temporary": + return "#ffc107"; // Yellow + case "warning": + return "#17a2b8"; // Blue + default: + return config.colors.primary; + } + }; + + const getSuspensionLabel = (type: string) => { + switch (type) { + case "permanent": + return "Permanent Suspension"; + case "temporary": + return "Temporary Suspension"; + case "warning": + return "Account Warning"; + default: + return "Account Action"; + } + }; + + return ( + +
+
+ + {getSuspensionLabel(suspensionType)} + + + User: {username} + + + Account ID: {userId} + +
+ +
+ + {/* User Information */} +
+ + 👤 Account Information + +
+ + Username: {username} + + + Email: {email} + + + Account ID: {userId} + +
+
+ + {/* Suspension Details */} +
+ + âš ī¸ Suspension Details + +
+ + Reason: + + {suspensionReason} + + {suspensionType === "temporary" && ( + <> + + Suspension Period: + + + Start Date: {formatDate(suspensionStartDate)} + + + End Date: {formatDate(suspensionEndDate)} + + + Duration: {suspensionDuration} days + + + )} + + {suspensionType === "permanent" && ( + + This suspension is permanent. Your account access has been revoked indefinitely. + + )} +
+
+ + {/* Violation Details */} + {violationDetails && violationDetails.length > 0 && ( +
+ + 📋 Violation Details + +
+
    + {violationDetails.map((detail, index) => ( +
  • + {detail} +
  • + ))} +
+
+
+ )} + + {/* Appeal Information */} + {appealInformation && ( +
+ + 📞 Appeal Process + +
+ {appealInformation} +
+
+ )} + +
+ + + This is an automated notification from the {config.companyName} security system. +
+ Processed by: {administrator} +
+ Timestamp: {new Date().toLocaleString()} +
+
+
+ ); +}; diff --git a/src/emails/customer/AccountVerificationEmail.tsx b/src/emails/customer/AccountVerificationEmail.tsx new file mode 100644 index 0000000..32a1f6e --- /dev/null +++ b/src/emails/customer/AccountVerificationEmail.tsx @@ -0,0 +1,139 @@ +import { Section, Text, Heading, Hr } from "@react-email/components"; +import { EmailLayout } from "../../components/EmailLayout"; +import { Button } from "../../components/Button"; +import { useBrandingConfig } from "../../hooks/useBrandingConfig"; + +interface AccountVerificationEmailProps { + playerName?: string; + verificationLink?: string; + verificationCode?: string; + expirationTime?: number; // in hours + supportEmail?: string; +} + +export const AccountVerificationEmail = ({ + playerName = "John", + verificationLink = "https://example.com/verify?token=abc123", + verificationCode, + expirationTime = 24, + supportEmail, +}: AccountVerificationEmailProps) => { + const config = useBrandingConfig(); + + return ( + +
+ + Hi {playerName}, + + + + Thank you for signing up with {config.companyName}! To complete your registration and + start playing, please verify your email address. + + +
+ + Click the button below to verify your email address: + + +
+ + {verificationCode && ( +
+ + Or enter this verification code: + + + {verificationCode} + +
+ )} + +
+ +
+ + ⏰ Important: + + + This verification link will expire in {expirationTime} hours. Please verify your + account as soon as possible. + +
+ + + If the button doesn't work, copy and paste this link into your browser: + + + {verificationLink} + + + + If you didn't create an account with {config.companyName}, please ignore this email. + + + {supportEmail && ( + + Need help? Contact us at {supportEmail} + + )} +
+
+ ); +}; diff --git a/src/emails/customer/BetConfirmationEmail.tsx b/src/emails/customer/BetConfirmationEmail.tsx new file mode 100644 index 0000000..3c6c1ab --- /dev/null +++ b/src/emails/customer/BetConfirmationEmail.tsx @@ -0,0 +1,274 @@ +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 BetConfirmationEmailProps { + playerName?: string; + ticketNumber?: string; + betType?: string; + selections?: Array<{ + event: string; + market: string; + selection: string; + odds: number; + }>; + stake?: number; + potentialPayout?: number; + currency?: string; + placedDate?: Date | string; + status?: "pending" | "won" | "lost" | "void"; + accountLink?: string; +} + +export const BetConfirmationEmail = ({ + playerName = "John", + ticketNumber = "TKT-2024-001234", + betType = "Accumulator", + selections = [ + { + event: "Manchester United vs Liverpool", + market: "Match Result", + selection: "Manchester United Win", + odds: 2.5, + }, + { + event: "Barcelona vs Real Madrid", + market: "Total Goals", + selection: "Over 2.5 Goals", + odds: 1.8, + }, + ], + stake = 50, + potentialPayout = 225, + currency = "USD", + placedDate = new Date(), + status = "pending", + accountLink = "https://example.com/account", +}: BetConfirmationEmailProps) => { + const config = useBrandingConfig(); + + const getStatusColor = (status: string) => { + switch (status) { + case "won": + return config.colors.secondary; + case "lost": + return "#dc3545"; + case "void": + return "#6c757d"; + default: + return config.colors.primary; + } + }; + + const getStatusLabel = (status: string) => { + switch (status) { + case "won": + return "Won"; + case "lost": + return "Lost"; + case "void": + return "Void"; + default: + return "Pending"; + } + }; + + return ( + +
+ + Hi {playerName}, + + + + Your bet has been successfully placed! Good luck! + + +
+ + Ticket Number + + + {ticketNumber} + + + Status: {getStatusLabel(status)} + +
+ +
+ + {/* Bet Details */} +
+ + 📋 Bet Details + +
+ + + + Bet Type: + + + Stake: + + + Potential Payout: + + + Placed Date: + + + + {betType} + + {formatCurrency(stake, currency)} + + + {formatCurrency(potentialPayout, currency)} + + + {formatDate(placedDate)} + + + +
+
+ + {/* Selections */} +
+ + đŸŽ¯ Your Selections + + {selections.map((selection, index) => ( +
+ + Selection {index + 1} + + + Event: {selection.event} + + + Market: {selection.market} + + + Selection: {selection.selection} + + + Odds: {selection.odds.toFixed(2)} + +
+ ))} +
+ +
+ + Note: This bet is now active. You'll receive an email notification + once the bet is settled. + +
+ + + Good luck with your bet! We'll keep you updated on the outcome. + + + + Best regards, +
+ The {config.companyName} Team +
+
+
+ ); +}; diff --git a/src/emails/customer/DepositConfirmationEmail.tsx b/src/emails/customer/DepositConfirmationEmail.tsx new file mode 100644 index 0000000..ac12441 --- /dev/null +++ b/src/emails/customer/DepositConfirmationEmail.tsx @@ -0,0 +1,200 @@ +import { Section, Text, Heading, Hr, Row, Column } 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 DepositConfirmationEmailProps { + playerName?: string; + transactionId?: string; + amount?: number; + currency?: string; + paymentMethod?: string; + depositDate?: Date | string; + newBalance?: number; + bonusAmount?: number; + bonusCode?: string; + accountLink?: string; +} + +export const DepositConfirmationEmail = ({ + playerName = "John", + transactionId = "TXN-2024-001234", + amount = 100, + currency = "USD", + paymentMethod = "Credit Card", + depositDate = new Date(), + newBalance = 500, + bonusAmount, + bonusCode, + accountLink = "https://example.com/account", +}: DepositConfirmationEmailProps) => { + const config = useBrandingConfig(); + + return ( + +
+ + Hi {playerName}, + + + + Your deposit has been successfully processed and credited to your account! + + +
+ + {formatCurrency(amount, currency)} Deposited + +
+ +
+ + {/* Transaction Details */} +
+ + 📋 Transaction Details + +
+ + + + Transaction ID: + + + Amount: + + + Payment Method: + + + Date: + + + New Balance: + + + + + {transactionId} + + + {formatCurrency(amount, currency)} + + {paymentMethod} + + {formatDate(depositDate)} + + + {formatCurrency(newBalance, currency)} + + + +
+
+ + {/* Bonus Information */} + {bonusAmount && bonusAmount > 0 && ( +
+ + 🎁 Bonus Added! + + + +{formatCurrency(bonusAmount, currency)} + + {bonusCode && ( + + Applied bonus code: {bonusCode} + + )} +
+ )} + +
+ +
+ + + Your funds are now available in your account. Start playing your favorite games and + good luck! + + + + Best regards, +
+ The {config.companyName} Team +
+ + + If you did not make this deposit, please contact our support team immediately. + +
+
+ ); +}; diff --git a/src/emails/customer/PasswordResetEmail.tsx b/src/emails/customer/PasswordResetEmail.tsx new file mode 100644 index 0000000..b463534 --- /dev/null +++ b/src/emails/customer/PasswordResetEmail.tsx @@ -0,0 +1,151 @@ +import { Section, Text, Heading, Hr } from "@react-email/components"; +import { EmailLayout } from "../../components/EmailLayout"; +import { Button } from "../../components/Button"; +import { useBrandingConfig } from "../../hooks/useBrandingConfig"; + +interface PasswordResetEmailProps { + playerName?: string; + resetLink?: string; + resetCode?: string; + expirationTime?: number; // in minutes + ipAddress?: string; + supportEmail?: string; +} + +export const PasswordResetEmail = ({ + playerName = "John", + resetLink = "https://example.com/reset-password?token=abc123", + resetCode, + expirationTime = 30, + ipAddress, + supportEmail, +}: PasswordResetEmailProps) => { + const config = useBrandingConfig(); + + return ( + +
+ + Hi {playerName}, + + + + We received a request to reset your password for your {config.companyName} account. If + you made this request, click the button below to reset your password. + + +
+ +
+ + {resetCode && ( +
+ + Or enter this reset code: + + + {resetCode} + +
+ )} + +
+ +
+ + ⏰ Important: + + + This password reset link will expire in {expirationTime} minutes for security + purposes. + +
+ + + If the button doesn't work, copy and paste this link into your browser: + + + {resetLink} + + +
+ + 🔒 Security Notice: + + + If you didn't request a password reset, please ignore this email. Your password will + remain unchanged. + + {ipAddress && ( + + Request originated from: {ipAddress} + + )} +
+ + {supportEmail && ( + + If you have concerns about your account security, please contact us immediately at{" "} + {supportEmail} + + )} +
+
+ ); +}; diff --git a/src/emails/customer/WelcomeEmail.tsx b/src/emails/customer/WelcomeEmail.tsx new file mode 100644 index 0000000..a3efdde --- /dev/null +++ b/src/emails/customer/WelcomeEmail.tsx @@ -0,0 +1,150 @@ +import { Section, Text, Heading, Hr } from "@react-email/components"; +import { EmailLayout } from "../../components/EmailLayout"; +import { Button } from "../../components/Button"; +import { useBrandingConfig } from "../../hooks/useBrandingConfig"; + +interface WelcomeEmailProps { + playerName?: string; + username?: string; + welcomeBonus?: number; + bonusCode?: string; + depositLink?: string; + supportEmail?: string; +} + +export const WelcomeEmail = ({ + playerName = "John", + username = "player123", + welcomeBonus = 100, + bonusCode = "WELCOME100", + depositLink = "https://example.com/deposit", + supportEmail, +}: WelcomeEmailProps) => { + const config = useBrandingConfig(); + + return ( + +
+ + Hi {playerName}, + + + + Welcome to {config.companyName}! We're thrilled to have you join our gaming community. + Get ready for an exciting experience with top-notch games, amazing bonuses, and + fantastic rewards. + + + {/* Welcome Bonus Section */} + {welcomeBonus > 0 && ( +
+ + ${welcomeBonus} Welcome Bonus! + + {bonusCode && ( + + Use code: {bonusCode} + + )} + + Claim your welcome bonus on your first deposit! + +
+ )} + +
+ + {/* Getting Started */} +
+ + 🚀 Getting Started + + + 1. Verify your account - Check your email for verification link + + + 2. Make your first deposit - Choose from our secure payment methods + + + 3. Claim your bonus - Use your welcome bonus code when depositing + + + 4. Start playing - Explore our games and enjoy! + +
+ + {/* Account Info */} +
+ + Username: {username} + + + Account Status: Active + +
+ +
+ +
+ + + If you have any questions, our support team is available 24/7 to assist you. + + + + Best of luck and enjoy your gaming experience! + + + + Best regards, +
+ The {config.companyName} Team +
+ + {supportEmail && ( + + Need help? Contact us at {supportEmail} + + )} +
+
+ ); +}; diff --git a/src/emails/customer/WithdrawalConfirmationEmail.tsx b/src/emails/customer/WithdrawalConfirmationEmail.tsx new file mode 100644 index 0000000..19564ae --- /dev/null +++ b/src/emails/customer/WithdrawalConfirmationEmail.tsx @@ -0,0 +1,213 @@ +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 WithdrawalConfirmationEmailProps { + playerName?: string; + transactionId?: string; + amount?: number; + currency?: string; + withdrawalMethod?: string; + requestDate?: Date | string; + estimatedProcessingTime?: string; + processingFee?: number; + netAmount?: number; + newBalance?: number; + accountLink?: string; +} + +export const WithdrawalConfirmationEmail = ({ + playerName = "John", + transactionId = "WD-2024-001234", + amount = 500, + currency = "USD", + withdrawalMethod = "Bank Transfer", + requestDate = new Date(), + estimatedProcessingTime = "3-5 business days", + processingFee = 5, + netAmount, + newBalance = 200, + accountLink = "https://example.com/account", +}: WithdrawalConfirmationEmailProps) => { + const config = useBrandingConfig(); + const netWithdrawalAmount = netAmount || amount - processingFee; + + return ( + +
+ + Hi {playerName}, + + + + Your withdrawal request has been received and is being processed. We'll notify you once + the funds have been transferred to your account. + + +
+ + {formatCurrency(amount, currency)} Withdrawal Requested + + {processingFee > 0 && ( + + Processing Fee: {formatCurrency(processingFee, currency)} + + )} +
+ +
+ + {/* Transaction Details */} +
+ + 📋 Withdrawal Details + +
+ + + + Transaction ID: + + + Withdrawal Amount: + + {processingFee > 0 && ( + + Processing Fee: + + )} + + Net Amount: + + + Withdrawal Method: + + + Request Date: + + + Estimated Processing: + + + Remaining Balance: + + + + + {transactionId} + + + {formatCurrency(amount, currency)} + + {processingFee > 0 && ( + + {formatCurrency(processingFee, currency)} + + )} + + {formatCurrency(netWithdrawalAmount, currency)} + + {withdrawalMethod} + + {formatDate(requestDate)} + + + {estimatedProcessingTime} + + + {formatCurrency(newBalance, currency)} + + + +
+
+ +
+ + â„šī¸ Processing Information: + + + Your withdrawal is being processed and will be completed within{" "} + {estimatedProcessingTime}. You'll receive a notification email once the funds have + been transferred. + +
+ + + If you have any questions about your withdrawal, please don't hesitate to contact our + support team. + + + + Best regards, +
+ The {config.companyName} Team +
+ + + If you did not request this withdrawal, please contact our support team immediately to + secure your account. + +
+
+ ); +}; diff --git a/stories/CustomizationDecorator.tsx b/stories/CustomizationDecorator.tsx index 53079ca..2750315 100644 --- a/stories/CustomizationDecorator.tsx +++ b/stories/CustomizationDecorator.tsx @@ -61,18 +61,57 @@ export const CustomizationDecorator = ({ children }: CustomizationDecoratorProps } }; + const handleCustomEvent = (e: CustomEvent) => { + if (e.detail?.config) { + setConfig((prev) => ({ + ...prev, + ...e.detail.config, + colors: { + ...prev.colors, + ...(e.detail.config.colors || {}), + }, + font: { + ...prev.font, + ...(e.detail.config.font || {}), + }, + })); + } + }; + const handleMessage = (event: MessageEvent) => { if (event.data?.type === "CUSTOMIZATION_UPDATE") { updateConfig(event.data.config); } }; + // Poll localStorage for changes (for same-tab updates) + const pollInterval = setInterval(() => { + const saved = localStorage.getItem(CUSTOMIZATION_STORAGE_KEY); + if (saved) { + try { + const savedConfig = JSON.parse(saved); + setConfig((prev) => { + const prevStr = JSON.stringify(prev); + if (prevStr !== saved) { + return { ...defaultBrandingConfig, ...savedConfig }; + } + return prev; + }); + } catch (e) { + // Ignore parse errors + } + } + }, 500); + window.addEventListener("storage", handleStorageChange); window.addEventListener("message", handleMessage); + window.addEventListener("customization-update" as any, handleCustomEvent as EventListener); return () => { + clearInterval(pollInterval); window.removeEventListener("storage", handleStorageChange); window.removeEventListener("message", handleMessage); + window.removeEventListener("customization-update" as any, handleCustomEvent as EventListener); }; }, [updateConfig]); diff --git a/stories/CustomizationToolbar.stories.tsx b/stories/CustomizationToolbar.stories.tsx new file mode 100644 index 0000000..c310bce --- /dev/null +++ b/stories/CustomizationToolbar.stories.tsx @@ -0,0 +1,36 @@ +import type { Meta, StoryObj } from "@storybook/react"; +import { CustomizationPanel } from "../.storybook/CustomizationPanel"; +import React, { useState } from "react"; + +const meta: Meta = { + title: "Customization/Customization Panel", + component: CustomizationPanel, + parameters: { + layout: "padded", + }, +}; + +export default meta; +type Story = StoryObj; + +export const Panel: Story = { + render: () => { + const [active, setActive] = useState(true); + return ( +
+ + +
+ ); + }, +};