- {/* Header */}
-
-
- Player Profile
-
-
- Your trivia game statistics and achievements
-
-
+
+
+
- {/* Profile Header */}
-
-
-
👤
- {!isEditing ? (
- <>
-
{username}
-
- >
- ) : (
-
-
setNewUsername(e.target.value)}
- placeholder="New username"
- className="px-4 py-2 rounded-lg text-gray-900 w-full max-w-xs"
- maxLength={20}
- />
-
-
-
-
-
- )}
-
-
-
-
Rank
-
- {rank > 0n ? `#${rank.toString()}` : 'Unranked'}
-
-
-
-
Total Score
-
{totalScore.toString()}
-
-
-
+
- {/* Stats Grid */}
- {!username ? (
-
- ) : (
-
-
-
- {balance}
- Current wallet balance
-
-
-
-
- {pendingRewards}
- Ready to claim
-
-
-
-
-
- {accuracy}%
-
- {correctAnswers.toString()}/{totalQuestions.toString()} correct
-
-
-
-
-
- {achievements.filter(a => a.isUnlocked).length}
-
- {achievements.filter(a => a.isUnlocked).length}/{achievements.length} unlocked
-
-
-
-
- )}
-
- {/* Detailed Stats */}
- Game Statistics
-
-
-
Games Played
-
{gamesPlayed.toString()}
-
-
-
Best Score
-
{bestScore.toString()}
-
-
-
Avg Score
-
{avgScore}
-
-
-
Total Questions
-
{totalQuestions.toString()}
-
-
-
-
- {/* Account Info */}
-
- Account Information
-
-
- Wallet Address
-
- {address?.slice(0, 6)}...{address?.slice(-4)}
-
-
-
- Username
- {username}
-
-
- Leaderboard Rank
-
- {rank > 0n ? `#${rank.toString()}` : 'Unranked'}
-
-
-
-
-
- {/* Quick Actions */}
-
-
-
-
+
diff --git a/frontend/src/components/profile/AchievementBadge.tsx b/frontend/src/components/profile/AchievementBadge.tsx
new file mode 100644
index 0000000..746b64d
--- /dev/null
+++ b/frontend/src/components/profile/AchievementBadge.tsx
@@ -0,0 +1,21 @@
+import { motion } from 'framer-motion';
+
+interface AchievementBadgeProps {
+ title: string;
+ icon: string;
+ unlocked: boolean;
+}
+
+export default function AchievementBadge({ title, icon, unlocked }: AchievementBadgeProps) {
+ return (
+
+ {icon}
+ {title}
+
+ );
+}
diff --git a/frontend/src/components/profile/EmptyState.tsx b/frontend/src/components/profile/EmptyState.tsx
new file mode 100644
index 0000000..d207e4a
--- /dev/null
+++ b/frontend/src/components/profile/EmptyState.tsx
@@ -0,0 +1,17 @@
+interface EmptyStateProps {
+ icon: string;
+ title: string;
+ description: string;
+ action?: React.ReactNode;
+}
+
+export default function EmptyState({ icon, title, description, action }: EmptyStateProps) {
+ return (
+
+
{icon}
+
{title}
+
{description}
+ {action}
+
+ );
+}
diff --git a/frontend/src/components/profile/GameHistoryList.tsx b/frontend/src/components/profile/GameHistoryList.tsx
new file mode 100644
index 0000000..f328f4a
--- /dev/null
+++ b/frontend/src/components/profile/GameHistoryList.tsx
@@ -0,0 +1,85 @@
+"use client";
+
+import { GameHistory } from '@/types/profile';
+import { motion } from 'framer-motion';
+import { formatDistanceToNow } from '@/utils/dateUtils';
+
+interface GameHistoryListProps {
+ history: GameHistory[];
+}
+
+export default function GameHistoryList({ history }: GameHistoryListProps) {
+ if (history.length === 0) {
+ return (
+
+
🎮
+
+ No Games Yet
+
+
+ Start playing to build your game history!
+
+
+ );
+ }
+
+ return (
+
+
+ Game History
+
+
+
+ {history.map((game, index) => (
+
+ ))}
+
+
+ );
+}
+
+interface GameHistoryItemProps {
+ game: GameHistory;
+ index: number;
+}
+
+function GameHistoryItem({ game, index }: GameHistoryItemProps) {
+ const percentage = (game.score / game.totalQuestions) * 100;
+
+ return (
+
+
+
+
+ Game #{game.gameId}
+
+
+ {formatDistanceToNow(game.timestamp)} ago
+
+
+
+
+ {game.score}/{game.totalQuestions}
+
+
+ {percentage.toFixed(0)}%
+
+
+
+
+
+
+ {game.difficulty}
+
+
+ +{game.rewardEarned} USDC
+
+
+
+ );
+}
diff --git a/frontend/src/components/profile/ProfileHeader.tsx b/frontend/src/components/profile/ProfileHeader.tsx
new file mode 100644
index 0000000..7dd07fb
--- /dev/null
+++ b/frontend/src/components/profile/ProfileHeader.tsx
@@ -0,0 +1,51 @@
+"use client";
+
+import { motion } from 'framer-motion';
+
+interface ProfileHeaderProps {
+ address: string;
+ username?: string;
+ joinedDate: number;
+}
+
+export default function ProfileHeader({ address, username, joinedDate }: ProfileHeaderProps) {
+ const formattedAddress = `${address.slice(0, 6)}...${address.slice(-4)}`;
+ const memberSince = new Date(joinedDate).toLocaleDateString('en-US', {
+ month: 'long',
+ year: 'numeric',
+ });
+
+ return (
+
+
+
+
+ 👤
+
+
+
+ {username || formattedAddress}
+
+
+ {address}
+
+
+ Member since {memberSince}
+
+
+
+
+
+
+
+ );
+}
diff --git a/frontend/src/components/profile/ProfileSkeleton.tsx b/frontend/src/components/profile/ProfileSkeleton.tsx
new file mode 100644
index 0000000..b0d6cc7
--- /dev/null
+++ b/frontend/src/components/profile/ProfileSkeleton.tsx
@@ -0,0 +1,9 @@
+export default function ProfileSkeleton() {
+ return (
+
+ );
+}
diff --git a/frontend/src/components/profile/StatsCard.tsx b/frontend/src/components/profile/StatsCard.tsx
new file mode 100644
index 0000000..94f0829
--- /dev/null
+++ b/frontend/src/components/profile/StatsCard.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+import { motion } from 'framer-motion';
+import { UserStats } from '@/types/profile';
+
+interface StatsCardProps {
+ stats: UserStats;
+}
+
+export default function StatsCard({ stats }: StatsCardProps) {
+ return (
+
+
+ Game Statistics
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
+
+interface StatItemProps {
+ label: string;
+ value: string | number;
+ icon: string;
+ color?: string;
+}
+
+function StatItem({ label, value, icon, color = 'text-gray-900' }: StatItemProps) {
+ return (
+
+ {icon}
+
+ {value}
+
+
+ {label}
+
+
+ );
+}
diff --git a/frontend/src/components/profile/index.ts b/frontend/src/components/profile/index.ts
new file mode 100644
index 0000000..a684266
--- /dev/null
+++ b/frontend/src/components/profile/index.ts
@@ -0,0 +1,3 @@
+export { default as ProfileHeader } from './ProfileHeader';
+export { default as StatsCard } from './StatsCard';
+export { default as GameHistoryList } from './GameHistoryList';
diff --git a/frontend/src/hooks/useGameHistory.ts b/frontend/src/hooks/useGameHistory.ts
new file mode 100644
index 0000000..278e1c3
--- /dev/null
+++ b/frontend/src/hooks/useGameHistory.ts
@@ -0,0 +1,24 @@
+import { useState, useEffect } from 'react';
+import { useAccount } from 'wagmi';
+import { GameHistory } from '@/types/profile';
+
+export function useGameHistory() {
+ const { address } = useAccount();
+ const [history, setHistory] = useState
([]);
+ const [isLoading, setIsLoading] = useState(false);
+
+ useEffect(() => {
+ if (address) {
+ fetchHistory();
+ }
+ }, [address]);
+
+ const fetchHistory = async () => {
+ setIsLoading(true);
+ // TODO: Fetch from contract
+ setHistory([]);
+ setIsLoading(false);
+ };
+
+ return { history, isLoading, refetch: fetchHistory };
+}
diff --git a/frontend/src/hooks/useUserProfile.ts b/frontend/src/hooks/useUserProfile.ts
new file mode 100644
index 0000000..0417257
--- /dev/null
+++ b/frontend/src/hooks/useUserProfile.ts
@@ -0,0 +1,63 @@
+import { useAccount } from 'wagmi';
+import { useState, useEffect } from 'react';
+import { UserProfile, UserStats } from '@/types/profile';
+
+/**
+ * Hook to fetch and manage user profile data
+ */
+export function useUserProfile() {
+ const { address } = useAccount();
+ const [profile, setProfile] = useState(null);
+ const [isLoading, setIsLoading] = useState(false);
+ const [error, setError] = useState(null);
+
+ useEffect(() => {
+ if (!address) {
+ setProfile(null);
+ return;
+ }
+
+ fetchProfile();
+ }, [address]);
+
+ const fetchProfile = async () => {
+ if (!address) return;
+
+ setIsLoading(true);
+ setError(null);
+
+ try {
+ // TODO: Implement actual profile fetching from contract/API
+ const mockProfile: UserProfile = {
+ address,
+ stats: {
+ totalGames: 0,
+ totalWins: 0,
+ totalLosses: 0,
+ winRate: 0,
+ totalRewards: '0',
+ averageScore: 0,
+ },
+ createdAt: Date.now(),
+ lastActive: Date.now(),
+ };
+
+ setProfile(mockProfile);
+ } catch (err) {
+ setError(err as Error);
+ } finally {
+ setIsLoading(false);
+ }
+ };
+
+ const refreshProfile = () => {
+ fetchProfile();
+ };
+
+ return {
+ profile,
+ isLoading,
+ error,
+ refreshProfile,
+ };
+}
diff --git a/frontend/src/types/profile.ts b/frontend/src/types/profile.ts
new file mode 100644
index 0000000..410d961
--- /dev/null
+++ b/frontend/src/types/profile.ts
@@ -0,0 +1,31 @@
+/**
+ * User Profile Types
+ *
+ * Type definitions for user profile and statistics
+ */
+
+export interface UserStats {
+ totalGames: number;
+ totalWins: number;
+ totalLosses: number;
+ winRate: number;
+ totalRewards: string;
+ averageScore: number;
+}
+
+export interface UserProfile {
+ address: string;
+ username?: string;
+ stats: UserStats;
+ createdAt: number;
+ lastActive: number;
+}
+
+export interface GameHistory {
+ gameId: string;
+ timestamp: number;
+ score: number;
+ totalQuestions: number;
+ rewardEarned: string;
+ difficulty: 'easy' | 'medium' | 'hard';
+}
diff --git a/frontend/src/utils/dateUtils.ts b/frontend/src/utils/dateUtils.ts
new file mode 100644
index 0000000..e8a0d9d
--- /dev/null
+++ b/frontend/src/utils/dateUtils.ts
@@ -0,0 +1,36 @@
+/**
+ * Date utility functions
+ */
+
+export function formatDistanceToNow(timestamp: number): string {
+ const now = Date.now();
+ const diff = now - timestamp;
+
+ const seconds = Math.floor(diff / 1000);
+ const minutes = Math.floor(seconds / 60);
+ const hours = Math.floor(minutes / 60);
+ const days = Math.floor(hours / 24);
+
+ if (days > 0) return `${days} day${days > 1 ? 's' : ''}`;
+ if (hours > 0) return `${hours} hour${hours > 1 ? 's' : ''}`;
+ if (minutes > 0) return `${minutes} minute${minutes > 1 ? 's' : ''}`;
+ return `${seconds} second${seconds !== 1 ? 's' : ''}`;
+}
+
+export function formatDate(timestamp: number): string {
+ return new Date(timestamp).toLocaleDateString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ });
+}
+
+export function formatDateTime(timestamp: number): string {
+ return new Date(timestamp).toLocaleString('en-US', {
+ year: 'numeric',
+ month: 'long',
+ day: 'numeric',
+ hour: '2-digit',
+ minute: '2-digit',
+ });
+}
diff --git a/frontend/src/utils/profileUtils.ts b/frontend/src/utils/profileUtils.ts
new file mode 100644
index 0000000..f58335a
--- /dev/null
+++ b/frontend/src/utils/profileUtils.ts
@@ -0,0 +1,11 @@
+import { UserStats } from '@/types/profile';
+
+export function calculateWinRate(wins: number, total: number): number {
+ if (total === 0) return 0;
+ return (wins / total) * 100;
+}
+
+export function calculateAverageScore(scores: number[]): number {
+ if (scores.length === 0) return 0;
+ return scores.reduce((a, b) => a + b, 0) / scores.length;
+}