File: /home/xedaptot/ai.naniguide.com/database/seeders/Demo/DemoConfig.php
<?php
namespace Database\Seeders\Demo;
/**
* DemoConfig — single source of truth for the demo seeder system.
*
* Anything that defines WHO the demo accounts are, HOW MUCH data they get,
* and HOW the test bots / marketing video recorder log in lives in this file.
*
* Edit this file to tune the demo dataset; do not hardcode accounts elsewhere.
*
* Consumed by:
* - database/seeders/Demo/* (sub-seeders)
* - docs/DEMO_ACCOUNTS.json (regenerated by DemoSeeder on every run)
* - docs/e2e/_helpers.cjs (reads DEMO_ACCOUNTS.json)
* - docs/marketing/scripts/* (reads DEMO_ACCOUNTS.json)
*/
class DemoConfig
{
/** Deterministic faker seed → reproducible dataset across runs. */
public const FAKER_SEED = 20260408;
/** Password for the two primary accounts (admin Michael + customer Emma). */
public const PRIMARY_PASSWORD = '69dcf4f4c938d@';
/** Cached customers array — ensures passwords are consistent across calls. */
private static ?array $cachedCustomers = null;
/** Generate a random password for non-primary demo users. */
public static function randomPassword(): string
{
return bin2hex(random_bytes(8));
}
// ─── ADMINS ─────────────────────────────────────────────────────────────
// Single admin account — no customer account attached.
public static function admins(): array
{
return [
[
'key' => 'super',
'email' => '[email protected]',
'password' => self::PRIMARY_PASSWORD,
'first_name' => 'Michael',
'last_name' => 'Chen',
'timezone' => 'UTC',
'group_id' => 1, // Administrator
'avatar' => '[email protected]',
'purpose' => 'Admin only — no customer account',
],
];
}
// ─── CUSTOMERS ──────────────────────────────────────────────────────────
// Primary customer (Emma) gets all sample data (lists, subscribers, campaigns).
// Other customers are just accounts with avatars — no sample data.
// `created_months_ago` spreads registrations across 10 months for the
// Customer Growth chart (cumulative, last month = tallest bar).
// Plan keys are distributed across all plans for the Plan Distribution pie.
public static function customers(): array
{
if (self::$cachedCustomers !== null) {
return self::$cachedCustomers;
}
return self::$cachedCustomers = [
// ── Primary — all sample data goes here ──────────────────────────
[
'key' => 'primary',
'company' => 'Awesome Solutions',
'email' => '[email protected]',
'password' => self::PRIMARY_PASSWORD,
'first_name' => 'Emma',
'last_name' => 'Tanaka',
'plan_key' => 'growth',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 10,
'lists' => 8,
'subscribers_total' => rand(70000, 72000),
'campaigns' => 10,
'automations' => 3,
'forms' => 3,
'funnels' => 2,
'websites' => 2,
'purpose' => 'Primary demo customer with all sample data.',
// Additional team members on Awesome Solutions account.
// Demonstrates multi-user / team-seat feature: same customer
// workspace, separate User logins each with own avatar.
'team_users' => [
[
'email' => '[email protected]',
'password' => self::PRIMARY_PASSWORD,
'first_name' => 'Nina',
'last_name' => 'Park',
'avatar' => '[email protected]',
],
[
'email' => '[email protected]',
'password' => self::PRIMARY_PASSWORD,
'first_name' => 'Jamal',
'last_name' => 'Carter',
'avatar' => '[email protected]',
],
[
'email' => '[email protected]',
'password' => self::PRIMARY_PASSWORD,
'first_name' => 'Kenji',
'last_name' => 'Nakamura',
'avatar' => '[email protected]',
],
[
'email' => '[email protected]',
'password' => self::PRIMARY_PASSWORD,
'first_name' => 'Alex',
'last_name' => 'Romano',
'avatar' => '[email protected]',
],
],
],
// ── Other customers — accounts only, no sample data ──────────────
// Spread across last 10 months: 1,1,1,1,1,1,2,2,2,2 (increasing)
[
'key' => 'sakura',
'company' => 'Synthetica AI',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Sakura',
'last_name' => 'Hayashi',
'plan_key' => 'growth',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 9,
],
[
'key' => 'mei',
'company' => 'DataPulse',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Mei Lin',
'last_name' => 'Wang',
'plan_key' => 'pro',
'sub_status' => 'active',
'timezone' => 'Asia/Shanghai',
'avatar' => '[email protected]',
'created_months_ago' => 8,
],
[
'key' => 'yuki',
'company' => 'ClearFlow Marketing',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Yuki',
'last_name' => 'Sato',
'plan_key' => 'free',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 7,
],
[
'key' => 'arjun',
'company' => 'NeuralStack',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Arjun',
'last_name' => 'Patel',
'plan_key' => 'enterprise',
'sub_status' => 'active',
'timezone' => 'Asia/Kolkata',
'avatar' => '[email protected]',
'created_months_ago' => 7,
],
[
'key' => 'daniel',
'company' => 'AdForge',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Daniel',
'last_name' => 'Weber',
'plan_key' => 'growth',
'sub_status' => 'active',
'timezone' => 'Europe/Berlin',
'avatar' => '[email protected]',
'created_months_ago' => 6,
],
[
'key' => 'ryan',
'company' => 'CloudReach AI',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Ryan',
'last_name' => 'Mitchell',
'plan_key' => 'pro',
'sub_status' => 'active',
'timezone' => 'America/New_York',
'avatar' => '[email protected]',
'created_months_ago' => 5,
],
[
'key' => 'sofia',
'company' => 'BrightSpark',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Sofia',
'last_name' => 'Larsson',
'plan_key' => 'starter',
'sub_status' => 'active',
'timezone' => 'Europe/Stockholm',
'avatar' => '[email protected]',
'created_months_ago' => 4,
],
[
'key' => 'hana',
'company' => 'PixelCraft AI',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Hana',
'last_name' => 'Kim',
'plan_key' => 'enterprise',
'sub_status' => 'active',
'timezone' => 'Asia/Seoul',
'avatar' => '[email protected]',
'created_months_ago' => 4,
],
[
'key' => 'marcus',
'company' => 'FlowStack',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Marcus',
'last_name' => 'Johnson',
'plan_key' => 'free',
'sub_status' => 'active',
'timezone' => 'America/Chicago',
'avatar' => '[email protected]',
'created_months_ago' => 3,
],
[
'key' => 'thomas',
'company' => 'InsightLoop AI',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Thomas',
'last_name' => 'Berg',
'plan_key' => 'growth',
'sub_status' => 'active',
'timezone' => 'Europe/Zurich',
'avatar' => '[email protected]',
'created_months_ago' => 3,
],
[
'key' => 'james',
'company' => 'StratosAI',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'James',
'last_name' => 'Crawford',
'plan_key' => 'pro',
'sub_status' => 'active',
'timezone' => 'Europe/London',
'avatar' => '[email protected]',
'created_months_ago' => 2,
],
[
'key' => 'aiko',
'company' => 'Zenith Marketing',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Aiko',
'last_name' => 'Nakamura',
'plan_key' => 'starter',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 2,
],
[
'key' => 'mika',
'company' => 'Lumina Digital',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Mika',
'last_name' => 'Fujimoto',
'plan_key' => 'starter',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 1,
],
[
'key' => 'olivia',
'company' => 'Brooks Media',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Olivia',
'last_name' => 'Brooks',
'plan_key' => 'free',
'sub_status' => 'active',
'timezone' => 'Europe/London',
'avatar' => '[email protected]',
'created_months_ago' => 8,
],
[
'key' => 'ava',
'company' => 'Whitman & Co',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Ava',
'last_name' => 'Whitman',
'plan_key' => 'starter',
'sub_status' => 'active',
'timezone' => 'America/Los_Angeles',
'avatar' => '[email protected]',
'created_months_ago' => 6,
],
[
'key' => 'hiroshi',
'company' => 'Sato Strategies',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Hiroshi',
'last_name' => 'Sato',
'plan_key' => 'enterprise',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 5,
],
[
'key' => 'lila',
'company' => 'NorthBeam Labs',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Lila',
'last_name' => 'Chen',
'plan_key' => 'pro',
'sub_status' => 'active',
'timezone' => 'Asia/Singapore',
'avatar' => '[email protected]',
'created_months_ago' => 1,
],
[
'key' => 'riko',
'company' => 'NightOwl Mail',
'email' => '[email protected]',
'password' => self::randomPassword(),
'first_name' => 'Riko',
'last_name' => 'Watanabe',
'plan_key' => 'growth',
'sub_status' => 'active',
'timezone' => 'Asia/Tokyo',
'avatar' => '[email protected]',
'created_months_ago' => 1,
],
];
}
// ─── PLANS ──────────────────────────────────────────────────────────────
// 4-concept canonical structure: each plan returns base attrs (name/price/...)
// plus 4 typed maps (credits/quotas/entitlements/rate_limit) consumed by
// DatabaseInit::insertPlans which calls PlansService::setCredits/setQuotas/
// setEntitlements/setRateLimit directly. Null values = unlimited; missing
// entitlement keys default to false (not granted). Source of truth = enum
// cases in app/Services/Plans/{Credits,Quotas,Entitlements,RateLimits}/.
public static function plans(): array
{
// All 5 demo plans grant the full sending-server suite + SMTP. Factor
// out the repeated map.
$allSendingProviders = [
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_AMAZON->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_SENDGRID->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_MAILGUN->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_ELASTICEMAIL->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_SPARKPOST->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_BLASTENGINE->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_SMTP->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_SENDMAIL->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::ALLOW_SENDING_GMAIL_OAUTH->value => true,
];
return [
'free' => [
'name' => 'Free Forever',
'price' => 0.00,
'description' => 'Get started with email marketing — perfect for personal projects.',
'trial_amount' => 0,
'no_payment_required_when_free' => 0,
'credits' => [
\App\Services\Plans\Credits\CreditKey::SEND_EMAIL->value => 2000,
],
'quotas' => [
\App\Services\Plans\Quotas\QuotaKey::MAX_LISTS->value => 3,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS->value => 500,
\App\Services\Plans\Quotas\QuotaKey::MAX_CONTACTS->value => 500,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST->value => 500,
\App\Services\Plans\Quotas\QuotaKey::MAX_SEGMENTS_PER_LIST->value => 3,
\App\Services\Plans\Quotas\QuotaKey::MAX_CAMPAIGNS->value => 10,
\App\Services\Plans\Quotas\QuotaKey::MAX_AUTOMATIONS->value => 2,
\App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_SERVERS->value => 0,
\App\Services\Plans\Quotas\QuotaKey::MAX_USERS->value => 1,
\App\Services\Plans\Quotas\QuotaKey::MAX_UPLOAD_SIZE_TOTAL->value => 100 * 1024 * 1024, // 100 MB
],
'entitlements' => $allSendingProviders + [
\App\Services\Plans\Entitlements\EntitlementKey::HAS_API_ACCESS->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_LIST_IMPORT->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_LIST_EXPORT->value => true,
],
'rate_limit' => ['amount' => 100, 'time_amount' => 1, 'time_unit' => 'hour'],
],
'starter' => [
'name' => 'Starter',
'price' => 19.00,
'description' => 'For small businesses sending occasional newsletters.',
'credits' => [
\App\Services\Plans\Credits\CreditKey::SEND_EMAIL->value => 20000,
],
'quotas' => [
\App\Services\Plans\Quotas\QuotaKey::MAX_LISTS->value => 10,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS->value => 5000,
\App\Services\Plans\Quotas\QuotaKey::MAX_CONTACTS->value => 5000,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST->value => null, // unlimited
\App\Services\Plans\Quotas\QuotaKey::MAX_SEGMENTS_PER_LIST->value => 10,
\App\Services\Plans\Quotas\QuotaKey::MAX_CAMPAIGNS->value => 50,
\App\Services\Plans\Quotas\QuotaKey::MAX_AUTOMATIONS->value => 5,
\App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_SERVERS->value => 2,
\App\Services\Plans\Quotas\QuotaKey::MAX_USERS->value => 2,
\App\Services\Plans\Quotas\QuotaKey::MAX_UPLOAD_SIZE_TOTAL->value => 1024 * 1024 * 1024, // 1 GB
],
'entitlements' => $allSendingProviders + [
\App\Services\Plans\Entitlements\EntitlementKey::HAS_API_ACCESS->value => true,
],
'rate_limit' => ['amount' => 1000, 'time_amount' => 1, 'time_unit' => 'hour'],
],
'growth' => [
'name' => 'Growth',
'price' => 49.00,
'description' => 'For growing teams running regular campaigns and automations.',
'trial_amount' => 0, // No trial — paid immediately. Used by Video #9 to record the Stripe checkout flow.
'credits' => [
\App\Services\Plans\Credits\CreditKey::SEND_EMAIL->value => 100000,
],
'quotas' => [
\App\Services\Plans\Quotas\QuotaKey::MAX_LISTS->value => 50,
// Subscriber quota set above DemoSeeder's primary-customer
// seed size (70k under Emma) so opt-in / API / import
// scenarios in e2e-subscribers-opt-in-essentials.cjs don't
// hit a real-but-irrelevant quota gate.
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS->value => 100000,
\App\Services\Plans\Quotas\QuotaKey::MAX_CONTACTS->value => 100000,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SEGMENTS_PER_LIST->value => 20,
\App\Services\Plans\Quotas\QuotaKey::MAX_CAMPAIGNS->value => 200,
\App\Services\Plans\Quotas\QuotaKey::MAX_AUTOMATIONS->value => 20,
\App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_SERVERS->value => 5,
\App\Services\Plans\Quotas\QuotaKey::MAX_USERS->value => 5,
\App\Services\Plans\Quotas\QuotaKey::MAX_UPLOAD_SIZE_TOTAL->value => 10 * 1024 * 1024 * 1024, // 10 GB
],
'entitlements' => $allSendingProviders + [
\App\Services\Plans\Entitlements\EntitlementKey::HAS_API_ACCESS->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_OWN_SENDING_SERVER->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_OWN_EMAIL_VERIFICATION_SERVER->value => true,
],
'rate_limit' => ['amount' => 5000, 'time_amount' => 1, 'time_unit' => 'hour'],
],
'pro' => [
'name' => 'Pro',
'price' => 99.00,
'description' => 'Advanced features for high-volume senders and agencies.',
'trial_amount' => 0, // No trial — committed tier, requires payment method on signup.
'credits' => [
\App\Services\Plans\Credits\CreditKey::SEND_EMAIL->value => 500000,
],
'quotas' => [
\App\Services\Plans\Quotas\QuotaKey::MAX_LISTS->value => 200,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS->value => 100000,
\App\Services\Plans\Quotas\QuotaKey::MAX_CONTACTS->value => 100000,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SEGMENTS_PER_LIST->value => 50,
\App\Services\Plans\Quotas\QuotaKey::MAX_CAMPAIGNS->value => 1000,
\App\Services\Plans\Quotas\QuotaKey::MAX_AUTOMATIONS->value => 100,
\App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_SERVERS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_USERS->value => 20,
\App\Services\Plans\Quotas\QuotaKey::MAX_UPLOAD_SIZE_TOTAL->value => 100 * 1024 * 1024 * 1024, // 100 GB
],
'entitlements' => $allSendingProviders + [
\App\Services\Plans\Entitlements\EntitlementKey::HAS_API_ACCESS->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_OWN_SENDING_SERVER->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_OWN_EMAIL_VERIFICATION_SERVER->value => true,
],
'rate_limit' => ['amount' => 20000, 'time_amount' => 1, 'time_unit' => 'hour'],
],
'enterprise' => [
'name' => 'Enterprise',
'price' => 299.00,
'description' => 'Unlimited everything for large organizations and resellers.',
'trial_amount' => 0, // No trial — enterprise tier, payment method required.
'credits' => [
\App\Services\Plans\Credits\CreditKey::SEND_EMAIL->value => null, // unlimited
],
'quotas' => [
\App\Services\Plans\Quotas\QuotaKey::MAX_LISTS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_CONTACTS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SUBSCRIBERS_PER_LIST->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SEGMENTS_PER_LIST->value => 100,
\App\Services\Plans\Quotas\QuotaKey::MAX_CAMPAIGNS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_AUTOMATIONS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_SERVERS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_SENDING_DOMAINS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_EMAIL_VERIFICATION_SERVERS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_USERS->value => null,
\App\Services\Plans\Quotas\QuotaKey::MAX_UPLOAD_SIZE_TOTAL->value => null,
],
'entitlements' => $allSendingProviders + [
\App\Services\Plans\Entitlements\EntitlementKey::HAS_API_ACCESS->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_LIST_IMPORT->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_LIST_EXPORT->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_OWN_SENDING_SERVER->value => true,
\App\Services\Plans\Entitlements\EntitlementKey::HAS_OWN_EMAIL_VERIFICATION_SERVER->value => true,
],
'rate_limit' => null, // null = no throttle (unlimited send rate)
],
];
}
// ─── DATA VOLUME (subscribers + tracking logs) ──────────────────────────
/** Tracking logs per "sent" campaign — sampled fraction of recipients. */
public const TRACKING_OPEN_RATE_MIN = 0.18;
public const TRACKING_OPEN_RATE_MAX = 0.55;
public const TRACKING_CLICK_RATE_MIN = 0.04;
public const TRACKING_CLICK_RATE_MAX = 0.18;
public const TRACKING_BOUNCE_RATE_MIN = 0.01;
public const TRACKING_BOUNCE_RATE_MAX = 0.05;
/** Cap how many tracking_logs rows we generate per campaign (perf). */
public const TRACKING_LOG_HARD_CAP = 4000;
// ─── OUTPUT MANIFEST ────────────────────────────────────────────────────
/** Where DemoSeeder writes the consumable account manifest. */
public static function manifestPath(): string
{
return base_path('docs/demo/DEMO_ACCOUNTS.json');
}
/**
* Find an admin entry by key.
*/
public static function findAdmin(string $key): ?array
{
foreach (self::admins() as $a) {
if ($a['key'] === $key) return $a;
}
return null;
}
/**
* Absolute path to an avatar file in the seeders/Demo/avatars/ directory.
*/
public static function avatarPath(string $filename): string
{
return __DIR__ . '/avatars/' . $filename;
}
}