/**
* CoinRush User Profile & Settings - React App
* Complete premium profile/settings page with modern UI
*/
const { useState, useEffect, useRef } = React;
// ============================================
// UTILITIES
// ============================================
const fmt = (cents) => '$' + ((cents || 0) / 100).toLocaleString('en-US', {
minimumFractionDigits: 2,
maximumFractionDigits: 2
});
const colorFrom = (name) => {
if (!name) return '#5bffb2';
let hash = 0;
for (let i = 0; i < name.length; i++) {
hash = ((hash << 5) - hash) + name.charCodeAt(i);
}
const hue = Math.abs(hash % 360);
return `hsl(${hue}, 65%, 50%)`;
};
const formatDate = (dateStr) => {
if (!dateStr) return '—';
return new Date(dateStr).toLocaleDateString('en-US', {
year: 'numeric',
month: 'long',
day: 'numeric'
});
};
const formatTimeAgo = (dateStr) => {
if (!dateStr) return 'Never';
const diff = Date.now() - new Date(dateStr);
const mins = Math.floor(diff / 60000);
if (mins < 1) return 'Just now';
if (mins < 60) return `${mins}m ago`;
const hours = Math.floor(mins / 60);
if (hours < 24) return `${hours}h ago`;
const days = Math.floor(hours / 24);
return `${days}d ago`;
};
// Get the PNG border image path based on level
const getBorderImage = (level) => {
if (level >= 50) return '/static/img/borders/Challengerborder.png';
if (level >= 40) return '/static/img/borders/Rubyborder.png';
if (level >= 30) return '/static/img/borders/Diamondborder.png';
if (level >= 20) return '/static/img/borders/Goldborder.png';
if (level >= 10) return '/static/img/borders/Silverborder.png';
return '/static/img/borders/Bronzeborder.png';
};
// ============================================
// FLOATING ORBS COMPONENT
// ============================================
function FloatingOrbs() {
return (
<>
{[...Array(12)].map((_, i) => (
))}
>
);
}
// ============================================
// LOADING COMPONENT
// ============================================
function LoadingState() {
return (
);
}
// ============================================
// TOGGLE SWITCH COMPONENT
// ============================================
function Toggle({ checked, onChange, disabled }) {
return (
);
}
// ============================================
// TAB NAVIGATION
// ============================================
function TabNav({ activeTab, onTabChange }) {
const tabs = [
{ id: 'overview', label: 'Overview', icon: 'M3 12l2-2m0 0l7-7 7 7M5 10v10a1 1 0 001 1h3m10-11l2 2m-2-2v10a1 1 0 01-1 1h-3m-6 0a1 1 0 001-1v-4a1 1 0 011-1h2a1 1 0 011 1v4a1 1 0 001 1m-6 0h6' },
{ id: 'stats', label: 'Statistics', icon: 'M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z' },
{ id: 'referrals', label: 'Referrals', icon: 'M17 20h5v-2a3 3 0 00-5.356-1.857M17 20H7m10 0v-2c0-.656-.126-1.283-.356-1.857M7 20H2v-2a3 3 0 015.356-1.857M7 20v-2c0-.656.126-1.283.356-1.857m0 0a5.002 5.002 0 019.288 0M15 7a3 3 0 11-6 0 3 3 0 016 0zm6 3a2 2 0 11-4 0 2 2 0 014 0zM7 10a2 2 0 11-4 0 2 2 0 014 0z', special: true },
{ id: 'security', label: 'Security', icon: 'M12 15v2m-6 4h12a2 2 0 002-2v-6a2 2 0 00-2-2H6a2 2 0 00-2 2v6a2 2 0 002 2zm10-10V7a4 4 0 00-8 0v4h8z' },
{ id: 'settings', label: 'Settings', icon: 'M10.325 4.317c.426-1.756 2.924-1.756 3.35 0a1.724 1.724 0 002.573 1.066c1.543-.94 3.31.826 2.37 2.37a1.724 1.724 0 001.065 2.572c1.756.426 1.756 2.924 0 3.35a1.724 1.724 0 00-1.066 2.573c.94 1.543-.826 3.31-2.37 2.37a1.724 1.724 0 00-2.572 1.065c-.426 1.756-2.924 1.756-3.35 0a1.724 1.724 0 00-2.573-1.066c-1.543.94-3.31-.826-2.37-2.37a1.724 1.724 0 00-1.065-2.572c-1.756-.426-1.756-2.924 0-3.35a1.724 1.724 0 001.066-2.573c-.94-1.543.826-3.31 2.37-2.37.996.608 2.296.07 2.572-1.065z M15 12a3 3 0 11-6 0 3 3 0 016 0z' },
];
return (
{tabs.map(tab => (
))}
);
}
// ============================================
// PROFILE HERO SECTION
// ============================================
function ProfileHero({ user, stats, levelInfo, xpBoost }) {
const avatarColor = colorFrom(user?.username);
const avatarInitials = user?.username ? user.username.slice(0, 2).toUpperCase() : '??';
const level = user?.level || 1;
// Level progress calculation
const currentXP = levelInfo?.progress_in_level || 0;
const xpToNext = levelInfo?.next_level_wagered ? (levelInfo.next_level_wagered - (levelInfo.current_level_wagered || 0)) : 10000;
const progressPct = xpToNext > 0 ? Math.min(100, (currentXP / xpToNext) * 100) : 100;
// Account tier based on level
const getTier = (lvl) => {
if (lvl >= 50) return { name: 'Challenger', color: '#ff6b6b', glow: 'rgba(255, 107, 107, 0.3)' };
if (lvl >= 40) return { name: 'Ruby', color: '#e11d48', glow: 'rgba(225, 29, 72, 0.3)' };
if (lvl >= 30) return { name: 'Diamond', color: '#06b6d4', glow: 'rgba(6, 182, 212, 0.3)' };
if (lvl >= 20) return { name: 'Gold', color: '#fbbf24', glow: 'rgba(251, 191, 36, 0.3)' };
if (lvl >= 10) return { name: 'Silver', color: '#94a3b8', glow: 'rgba(148, 163, 184, 0.3)' };
return { name: 'Bronze', color: '#cd7f32', glow: 'rgba(205, 127, 50, 0.3)' };
};
const tier = getTier(level);
const borderImage = getBorderImage(level);
return (
{/* Avatar Section */}
{avatarInitials}
{user?.username || 'Player'}
{user?.email || ''}
{/* Tier Badge */}
{/* Level Progress */}
Level {level}
{fmt(currentXP)} / {fmt(xpToNext)}
{xpToNext - currentXP > 0 ? `${fmt(xpToNext - currentXP)} to Level ${level + 1}` : 'Max Level!'}
{/* XP Boost Indicator */}
{xpBoost > 0 && (
+{xpBoost}% XP Boost Active
)}
{/* Balance Display */}
Available Balance
{fmt(user?.balance || 0)}
);
}
// ============================================
// QUICK STATS CARDS
// ============================================
function QuickStats({ user, stats }) {
const overall = stats?.overall || {};
const cf = stats?.coinflip || {};
const bb = stats?.barbarian || {};
const totalBattles = (cf.played || 0) + (bb.played || 0);
const totalWins = (cf.wins || 0) + (bb.wins || 0);
const winRate = totalBattles > 0 ? Math.round((totalWins / totalBattles) * 100) : 0;
const netPL = overall.net || 0;
const statsData = [
{ label: 'Total Wagered', value: fmt(user?.total_wagered || overall.wagered || 0), icon: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z' },
{ label: 'Games Played', value: totalBattles, icon: 'M14.752 11.168l-3.197-2.132A1 1 0 0010 9.87v4.263a1 1 0 001.555.832l3.197-2.132a1 1 0 000-1.664z M21 12a9 9 0 11-18 0 9 9 0 0118 0z' },
{ label: 'Win Rate', value: `${winRate}%`, icon: 'M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z', highlight: winRate >= 50 },
{ label: 'Net P/L', value: `${netPL >= 0 ? '+' : ''}${fmt(Math.abs(netPL))}`, icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6', isPositive: netPL >= 0, isNegative: netPL < 0 },
];
return (
{statsData.map((stat, i) => (
{stat.value}
{stat.label}
))}
);
}
// ============================================
// RECENT ACTIVITY
// ============================================
function RecentActivity({ stats }) {
const activities = stats?.recent_activity || [];
if (activities.length === 0) {
return (
Recent Activity
No recent activity yet
Start playing to see your game history here!
);
}
return (
Recent Activity
{activities.slice(0, 5).map((act, i) => (
{act.type === 'win' ? (
) : (
)}
{act.description}
{formatTimeAgo(act.timestamp)}
{act.type === 'win' ? '+' : ''}{fmt(Math.abs(act.amount || 0))}
))}
);
}
// ============================================
// GAME STATISTICS TAB
// ============================================
function StatisticsTab({ stats }) {
const cf = stats?.coinflip || {};
const bb = stats?.barbarian || {};
const jp = stats?.jackpot || {};
const lottery = stats?.lottery || {};
const sliceDice = stats?.slice_dice || {};
const crash = stats?.crash || {};
const games = [
{ name: 'Coinflip', data: cf, color: '#fbbf24', icon: 'M12 8c-1.657 0-3 .895-3 2s1.343 2 3 2 3 .895 3 2-1.343 2-3 2m0-8c1.11 0 2.08.402 2.599 1M12 8V7m0 1v8m0 0v1m0-1c-1.11 0-2.08-.402-2.599-1M21 12a9 9 0 11-18 0 9 9 0 0118 0z' },
{ name: 'Knights Arena', data: bb, color: '#ef4444', icon: 'M9 12l2 2 4-4M7.835 4.697a3.42 3.42 0 001.946-.806 3.42 3.42 0 014.438 0 3.42 3.42 0 001.946.806 3.42 3.42 0 013.138 3.138 3.42 3.42 0 00.806 1.946 3.42 3.42 0 010 4.438 3.42 3.42 0 00-.806 1.946 3.42 3.42 0 01-3.138 3.138 3.42 3.42 0 00-1.946.806 3.42 3.42 0 01-4.438 0 3.42 3.42 0 00-1.946-.806 3.42 3.42 0 01-3.138-3.138 3.42 3.42 0 00-.806-1.946 3.42 3.42 0 010-4.438 3.42 3.42 0 00.806-1.946 3.42 3.42 0 013.138-3.138z' },
{ name: 'Jackpot', data: jp, color: '#22c55e', icon: 'M12 8v13m0-13V6a2 2 0 112 2h-2zm0 0V5.5A2.5 2.5 0 109.5 8H12zm-7 4h14M5 12a2 2 0 110-4h14a2 2 0 110 4M5 12v7a2 2 0 002 2h10a2 2 0 002-2v-7' },
{ name: 'Lottery', data: lottery, color: '#a855f7', icon: 'M15 5v2m0 4v2m0 4v2M5 5a2 2 0 00-2 2v3a2 2 0 110 4v3a2 2 0 002 2h14a2 2 0 002-2v-3a2 2 0 110-4V7a2 2 0 00-2-2H5z' },
{ name: "Slice n' Dice", data: sliceDice, color: '#ec4899', icon: 'M4 5a1 1 0 011-1h14a1 1 0 011 1v2a1 1 0 01-1 1H5a1 1 0 01-1-1V5zM4 13a1 1 0 011-1h6a1 1 0 011 1v6a1 1 0 01-1 1H5a1 1 0 01-1-1v-6zM16 13a1 1 0 011-1h2a1 1 0 011 1v6a1 1 0 01-1 1h-2a1 1 0 01-1-1v-6z' },
{ name: 'Crash', data: crash, color: '#06b6d4', icon: 'M13 7h8m0 0v8m0-8l-8 8-4-4-6 6' },
];
return (
{/* Game Cards */}
{games.map((game, i) => (
Games Played
{game.data.played || 0}
Wins
{game.data.wins || 0}
Losses
{game.data.losses || 0}
Win Rate
{game.data.win_rate || 0}%
Net P/L
= 0 ? 'positive' : 'negative'}>
{(game.data.net || 0) >= 0 ? '+' : ''}{fmt(game.data.net || 0)}
{(game.data.biggest_win || 0) > 0 && (
Biggest Win
{fmt(game.data.biggest_win)}
)}
))}
{/* Overall Stats */}
Overall Performance
{fmt(stats?.overall?.wagered || 0)}
Total Wagered
= 0 ? 'positive' : 'negative'}`}>
{(stats?.overall?.net || 0) >= 0 ? '+' : ''}{fmt(Math.abs(stats?.overall?.net || 0))}
Net Profit/Loss
= 0 ? 'positive' : 'negative'}`}>
{stats?.overall?.roi || 0}%
Return on Investment
);
}
// ============================================
// REFERRALS TAB (Affiliate Program) - Premium Glowing Design
// ============================================
function ReferralsTab({ referralData, onClaimBonus, onClaimVault, vaultClaimLoading, hasReferrer }) {
const [copyText, setCopyText] = useState('Copy');
const [claimCode, setClaimCode] = useState('');
const [claimMessage, setClaimMessage] = useState(null);
const [isClaimLoading, setIsClaimLoading] = useState(false);
const copyReferralCode = () => {
navigator.clipboard.writeText(referralData?.referral_code || '');
setCopyText('Copied!');
setTimeout(() => setCopyText('Copy'), 2000);
};
const handleClaimBonus = async () => {
if (!claimCode.trim()) {
setClaimMessage({ type: 'error', text: 'Please enter a referral code' });
return;
}
setIsClaimLoading(true);
setClaimMessage(null);
try {
const res = await fetch('/api/referral/claim-bonus', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
credentials: 'include',
body: JSON.stringify({ referral_code: claimCode.trim() })
});
const data = await res.json();
if (!res.ok) {
throw new Error(data.detail || 'Failed to claim bonus');
}
setClaimMessage({ type: 'success', text: data.message });
onClaimBonus();
} catch (error) {
setClaimMessage({ type: 'error', text: error.message });
} finally {
setIsClaimLoading(false);
}
};
const unclaimedAmount = referralData?.unclaimed_earnings || 0;
return (
{/* Floating Reward Particles - SVG coins */}
{[...Array(8)].map((_, i) => (
))}
{/* Vault Section - Clean & Glowy */}
{unclaimedAmount > 0 && (
REFERRAL VAULT
{fmt(unclaimedAmount)}
Ready to claim!
)}
{/* Hero Banner - Attention Grabbing */}
Earn Up To $2.50 Per Friend!
Share your code • Friends sign up • You earn 1% of their first $250 in winnings
{/* How It Works - Visual Steps */}
How It Works
1
Share Your Code
Copy your unique referral code below
2
Friends Sign Up
They register using your code
3
Earn Rewards!
Get 1% of their first $250 winnings
{/* Your Referral Code - Premium Glow Box */}
Your Referral Code
Click to copy!
{/* Referral Stats - Glowing Cards */}
{referralData?.total_referrals || 0}
Total Referrals
{fmt(referralData?.total_earnings || 0)}
Total Earned
{fmt(unclaimedAmount)}
Pending
{/* Claim a Code */}
{!hasReferrer && (
Have a Friend's Code?
{claimMessage && (
{claimMessage.text}
)}
setClaimCode(e.target.value.toUpperCase())}
/>
)}
);
}
// ============================================
// SECURITY TAB
// ============================================
function SecurityTab({ user }) {
const [twoFactorEnabled, setTwoFactorEnabled] = useState(user?.two_factor_enabled || false);
const [showPasswordModal, setShowPasswordModal] = useState(false);
return (
{/* Account Security Status */}
Security Status
{user?.email_verified ? (
) : (
)}
Email Verification
{user?.email_verified ? 'Verified' : 'Not verified'}
Two-Factor Authentication
{twoFactorEnabled ? 'Enabled' : 'Disabled'}
setTwoFactorEnabled(val)}
/>
{/* Password & Sessions */}
Password & Access
{/* Login History */}
Recent Logins
Current Session
Active now
This device
);
}
// ============================================
// SETTINGS TAB
// ============================================
function SettingsTab({ user, preferences, onUpdatePreferences }) {
const [soundEnabled, setSoundEnabled] = useState(preferences?.sound ?? true);
const [notificationsEnabled, setNotificationsEnabled] = useState(preferences?.notifications ?? true);
const [compactMode, setCompactMode] = useState(preferences?.compactMode ?? false);
const [showConfirmations, setShowConfirmations] = useState(preferences?.confirmations ?? true);
return (
{/* Display Preferences */}
Display
Compact Mode
Use a more condensed layout
{/* Sound & Notifications */}
Sound & Notifications
Sound Effects
Play sounds for wins, losses, and actions
Browser Notifications
Get notified about game results
{/* Gameplay */}
Gameplay
Bet Confirmations
Show confirmation dialog before placing bets
{/* Account Info */}
Account
Username
{user?.username}
Email
{user?.email}
Member Since
{formatDate(user?.created_at)}
Account ID
#{user?.id || '—'}
{/* Danger Zone */}
Danger Zone
These actions are irreversible. Please be certain.
);
}
// ============================================
// MAIN APP COMPONENT
// ============================================
function UserProfileApp() {
const [user, setUser] = useState(null);
const [stats, setStats] = useState(null);
const [referralData, setReferralData] = useState(null);
const [levelInfo, setLevelInfo] = useState(null);
const [xpBoost, setXpBoost] = useState(0);
const [isLoading, setIsLoading] = useState(true);
const [activeTab, setActiveTab] = useState('overview');
const [vaultClaimLoading, setVaultClaimLoading] = useState(false);
const [preferences, setPreferences] = useState({});
useEffect(() => {
loadData();
}, []);
const loadData = async () => {
try {
// Check authentication
const meRes = await fetch('/me', { credentials: 'include' });
if (!meRes.ok) {
alert('You need to be logged in to view your profile.');
window.location.href = '/';
return;
}
const meData = await meRes.json();
setUser(meData);
setLevelInfo(meData.level_info);
// Fetch extended profile stats
const profileRes = await fetch('/api/user/profile', { credentials: 'include' });
if (profileRes.ok) {
const profileData = await profileRes.json();
setStats(profileData.statistics);
setUser(prev => ({ ...prev, ...profileData.user }));
}
// Fetch daily reward status for XP boost
try {
const dailyRes = await fetch('/api/daily-reward/status', { credentials: 'include' });
if (dailyRes.ok) {
const dailyData = await dailyRes.json();
if (dailyData.active_bonuses?.xp_boost > 0) {
setXpBoost(dailyData.active_bonuses.xp_boost);
}
}
} catch {}
// Fetch referral data
await loadReferralData();
} catch (error) {
console.error('Failed to load profile:', error);
} finally {
setIsLoading(false);
}
};
const loadReferralData = async () => {
try {
const res = await fetch('/api/referral/stats', { credentials: 'include' });
if (res.ok) {
const data = await res.json();
setReferralData(data);
}
} catch (error) {
console.error('Error loading referral data:', error);
}
};
const claimVaultEarnings = async () => {
setVaultClaimLoading(true);
try {
const res = await fetch('/api/referral/claim-earnings', {
method: 'POST',
credentials: 'include',
headers: { 'Content-Type': 'application/json' }
});
const data = await res.json();
if (res.ok && data.success) {
setUser(prev => ({ ...prev, balance: data.new_balance * 100 }));
await loadReferralData();
}
} catch (error) {
console.error('Error claiming vault:', error);
} finally {
setVaultClaimLoading(false);
}
};
if (isLoading) {
return (
<>
>
);
}
return (
<>
{/* Profile Hero */}
{/* Tab Navigation */}
{/* Tab Content */}
{activeTab === 'overview' && (
<>
>
)}
{activeTab === 'stats' && (
)}
{activeTab === 'referrals' && (
)}
{activeTab === 'security' && (
)}
{activeTab === 'settings' && (
)}
{/* Back Button */}
>
);
}
// ============================================
// MOUNT APP
// ============================================
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render();