diff --git a/README_ACHIEVEMENTS.md b/README_ACHIEVEMENTS.md new file mode 100644 index 0000000..91ef1e5 --- /dev/null +++ b/README_ACHIEVEMENTS.md @@ -0,0 +1,25 @@ +# Achievements & Tournaments + +## Achievements + +Unlock achievements by completing challenges and milestones. + +### Categories +- **Milestone**: Major accomplishments +- **Streak**: Consecutive day bonuses +- **Time**: Speed-based achievements +- **Skill**: Accuracy and knowledge + +## Tournaments + +Compete in weekly and special tournaments for bigger prizes. + +### Status Types +- **Upcoming**: Registration open +- **Active**: Tournament in progress +- **Completed**: Results finalized + +### Features +- Prize pools +- Participant tracking +- Multiple tournament types diff --git a/frontend/src/app/achievements/layout.tsx b/frontend/src/app/achievements/layout.tsx new file mode 100644 index 0000000..3dff01d --- /dev/null +++ b/frontend/src/app/achievements/layout.tsx @@ -0,0 +1,4 @@ +import { ReactNode } from 'react'; +export default function AchievementsLayout({ children }: { children: ReactNode }) { + return <>{children}; +} diff --git a/frontend/src/app/tournaments/layout.tsx b/frontend/src/app/tournaments/layout.tsx new file mode 100644 index 0000000..9bc095b --- /dev/null +++ b/frontend/src/app/tournaments/layout.tsx @@ -0,0 +1,4 @@ +import { ReactNode } from 'react'; +export default function TournamentsLayout({ children }: { children: ReactNode }) { + return <>{children}; +} diff --git a/frontend/src/app/tournaments/page.tsx b/frontend/src/app/tournaments/page.tsx new file mode 100644 index 0000000..6f9fc0e --- /dev/null +++ b/frontend/src/app/tournaments/page.tsx @@ -0,0 +1,26 @@ +'use client'; + +import React from 'react'; +import { TournamentCard } from '@/components/tournaments'; +import { useTournaments } from '@/hooks/useTournaments'; + +export default function TournamentsPage() { + const { tournaments, isLoading } = useTournaments(); + + if (isLoading) { + return
Loading...
; + } + + return ( +
+
+

Tournaments

+
+ {tournaments.map((t) => ( + + ))} +
+
+
+ ); +} diff --git a/frontend/src/components/achievements/AchievementBadge.tsx b/frontend/src/components/achievements/AchievementBadge.tsx new file mode 100644 index 0000000..0199565 --- /dev/null +++ b/frontend/src/components/achievements/AchievementBadge.tsx @@ -0,0 +1,37 @@ +import React from 'react'; + +interface AchievementBadgeProps { + icon: string; + name: string; + description: string; + isUnlocked: boolean; + progress?: number; + target?: number; +} + +export const AchievementBadge: React.FC = ({ + icon, + name, + description, + isUnlocked, + progress, + target, +}) => { + const progressPercent = progress && target ? (progress / target) * 100 : 0; + + return ( +
+
{isUnlocked ? icon : '🔒'}
+

{name}

+

{description}

+ {!isUnlocked && progress !== undefined && target && ( +
+
+
+
+

{progress}/{target}

+
+ )} +
+ ); +}; diff --git a/frontend/src/components/achievements/index.ts b/frontend/src/components/achievements/index.ts new file mode 100644 index 0000000..5e546fa --- /dev/null +++ b/frontend/src/components/achievements/index.ts @@ -0,0 +1 @@ +export { AchievementBadge } from './AchievementBadge'; diff --git a/frontend/src/components/rewards/RewardProgress.tsx b/frontend/src/components/rewards/RewardProgress.tsx new file mode 100644 index 0000000..72073db --- /dev/null +++ b/frontend/src/components/rewards/RewardProgress.tsx @@ -0,0 +1,32 @@ +import React from 'react'; +import { REWARD_TIERS } from '@/constants/rewards'; + +interface RewardProgressProps { + currentPoints: number; +} + +export const RewardProgress: React.FC = ({ currentPoints }) => { + const currentTier = REWARD_TIERS.slice().reverse().find(t => currentPoints >= t.minPoints) || REWARD_TIERS[0]; + const nextTier = REWARD_TIERS.find(t => t.minPoints > currentPoints); + const progress = nextTier + ? ((currentPoints - currentTier.minPoints) / (nextTier.minPoints - currentTier.minPoints)) * 100 + : 100; + + return ( +
+

Your Rewards

+
+

{currentPoints} pts

+

{currentTier.name} Tier

+
+ {nextTier && ( +
+
+
+
+

{nextTier.minPoints - currentPoints} points to {nextTier.name}

+
+ )} +
+ ); +}; diff --git a/frontend/src/components/rewards/index.ts b/frontend/src/components/rewards/index.ts new file mode 100644 index 0000000..67fed1d --- /dev/null +++ b/frontend/src/components/rewards/index.ts @@ -0,0 +1 @@ +export { RewardProgress } from './RewardProgress'; diff --git a/frontend/src/components/tournaments/TournamentCard.tsx b/frontend/src/components/tournaments/TournamentCard.tsx new file mode 100644 index 0000000..1ae35ef --- /dev/null +++ b/frontend/src/components/tournaments/TournamentCard.tsx @@ -0,0 +1,37 @@ +import React from 'react'; +import { Tournament } from '@/types/tournament'; +import { formatTournamentStatus } from '@/constants/tournament'; + +interface TournamentCardProps { + tournament: Tournament; + onJoin?: (id: string) => void; +} + +export const TournamentCard: React.FC = ({ tournament, onJoin }) => { + return ( +
+
+

{tournament.name}

+ + {tournament.status} + +
+

{tournament.description}

+
+
+

Prize Pool

+

{tournament.prizePool}

+
+
+

Participants

+

{tournament.participants}/{tournament.maxParticipants}

+
+
+ {tournament.status === 'upcoming' && onJoin && ( + + )} +
+ ); +}; diff --git a/frontend/src/components/tournaments/index.ts b/frontend/src/components/tournaments/index.ts new file mode 100644 index 0000000..9baff9e --- /dev/null +++ b/frontend/src/components/tournaments/index.ts @@ -0,0 +1 @@ +export { TournamentCard } from './TournamentCard'; diff --git a/frontend/src/constants/rewards.ts b/frontend/src/constants/rewards.ts new file mode 100644 index 0000000..f57c94a --- /dev/null +++ b/frontend/src/constants/rewards.ts @@ -0,0 +1,13 @@ +export interface RewardTier { + tier: number; + name: string; + minPoints: number; + rewards: string[]; +} + +export const REWARD_TIERS: RewardTier[] = [ + { tier: 1, name: 'Bronze', minPoints: 0, rewards: ['Bronze Badge', '5% bonus'] }, + { tier: 2, name: 'Silver', minPoints: 5000, rewards: ['Silver Badge', '10% bonus', 'Early access'] }, + { tier: 3, name: 'Gold', minPoints: 15000, rewards: ['Gold Badge', '15% bonus', 'Exclusive events'] }, + { tier: 4, name: 'Platinum', minPoints: 50000, rewards: ['Platinum Badge', '20% bonus', 'VIP support'] }, +]; diff --git a/frontend/src/constants/tournament.ts b/frontend/src/constants/tournament.ts new file mode 100644 index 0000000..4e77056 --- /dev/null +++ b/frontend/src/constants/tournament.ts @@ -0,0 +1,16 @@ +import { Tournament } from '@/types/tournament'; + +export const TOURNAMENT_CONFIG = { + MIN_PARTICIPANTS: 10, + MAX_PARTICIPANTS: 1000, + DURATION_DAYS: 7, +}; + +export const formatTournamentStatus = (status: string) => { + const colors = { + upcoming: 'bg-blue-100 text-blue-700', + active: 'bg-green-100 text-green-700', + completed: 'bg-gray-100 text-gray-700', + }; + return colors[status as keyof typeof colors] || ''; +}; diff --git a/frontend/src/hooks/useAchievements.ts b/frontend/src/hooks/useAchievements.ts new file mode 100644 index 0000000..603a6ad --- /dev/null +++ b/frontend/src/hooks/useAchievements.ts @@ -0,0 +1,23 @@ +import { useState, useEffect } from 'react'; +import { Achievement, AchievementState } from '@/types/achievement'; + +export const useAchievements = () => { + const [achievements, setAchievements] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchAchievements = async () => { + await new Promise((r) => setTimeout(r, 300)); + setAchievements([ + { id: '1', title: 'First Win', description: 'Win your first game', icon: '🎯', category: 'milestone', unlockedAt: '2025-01-15', progress: 1, target: 1, isUnlocked: true }, + { id: '2', title: 'Streak Master', description: 'Get a 7-day streak', icon: '🔥', category: 'streak', progress: 5, target: 7, isUnlocked: false }, + { id: '3', title: 'Speed Demon', description: 'Answer 50 questions in under 10s', icon: '⚡', category: 'time', progress: 35, target: 50, isUnlocked: false }, + { id: '4', title: 'Trivia Expert', description: 'Answer 500 questions correctly', icon: '🧠', category: 'skill', progress: 420, target: 500, isUnlocked: false }, + ]); + setIsLoading(false); + }; + fetchAchievements(); + }, []); + + return { achievements, isLoading }; +}; diff --git a/frontend/src/hooks/useTournaments.ts b/frontend/src/hooks/useTournaments.ts new file mode 100644 index 0000000..109a05d --- /dev/null +++ b/frontend/src/hooks/useTournaments.ts @@ -0,0 +1,21 @@ +import { useState, useEffect } from 'react'; +import { Tournament } from '@/types/tournament'; + +export const useTournaments = () => { + const [tournaments, setTournaments] = useState([]); + const [isLoading, setIsLoading] = useState(true); + + useEffect(() => { + const fetchTournaments = async () => { + await new Promise((r) => setTimeout(r, 300)); + setTournaments([ + { id: '1', name: 'Weekly Championship', description: 'Compete for the top spot', startDate: new Date(), endDate: new Date(Date.now() + 7 * 86400000), prizePool: 10000, participants: 150, maxParticipants: 500, status: 'active' }, + { id: '2', name: 'Weekend Warrior', description: 'Special weekend tournament', startDate: new Date(Date.now() + 3 * 86400000), endDate: new Date(Date.now() + 5 * 86400000), prizePool: 5000, participants: 0, maxParticipants: 200, status: 'upcoming' }, + ]); + setIsLoading(false); + }; + fetchTournaments(); + }, []); + + return { tournaments, isLoading }; +}; diff --git a/frontend/src/types/tournament.ts b/frontend/src/types/tournament.ts new file mode 100644 index 0000000..a4ad99b --- /dev/null +++ b/frontend/src/types/tournament.ts @@ -0,0 +1,11 @@ +export interface Tournament { + id: string; + name: string; + description: string; + startDate: Date; + endDate: Date; + prizePool: number; + participants: number; + maxParticipants: number; + status: 'upcoming' | 'active' | 'completed'; +}