From 063b1a20cba614b61407340b5ee597abbad2ab8a Mon Sep 17 00:00:00 2001 From: sirpiglr <49359077-sirpiglr@users.noreply.replit.com> Date: Sat, 13 Dec 2025 05:12:02 +0000 Subject: [PATCH] Add an event calendar tab for upcoming community events Introduces a new 'EventCalendarTab' component to client/pages/Activity.tsx, displaying upcoming AeThex events with RSVP functionality and local storage persistence for RSVP'd events. Imports new icons from 'lucide-react' to support event type visualization. Replit-Commit-Author: Agent Replit-Commit-Session-Id: 9203795e-937a-4306-b81d-b4d5c78c240e Replit-Commit-Checkpoint-Type: intermediate_checkpoint Replit-Commit-Event-Id: ddfcfc66-1505-407e-bee9-93bcd8ae8286 Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/7c94b7a0-29c7-4f2e-94ef-44b2153872b7/9203795e-937a-4306-b81d-b4d5c78c240e/139vJay Replit-Helium-Checkpoint-Created: true --- client/pages/Activity.tsx | 715 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 715 insertions(+) diff --git a/client/pages/Activity.tsx b/client/pages/Activity.tsx index ea221bf6..172d6699 100644 --- a/client/pages/Activity.tsx +++ b/client/pages/Activity.tsx @@ -34,6 +34,14 @@ import { ThumbsUp, Layers, Eye, + UserPlus, + Crosshair, + Crown, + Dice6, + Video, + Mic, + MapPin, + FileText, } from "lucide-react"; import { motion, AnimatePresence } from "framer-motion"; @@ -967,6 +975,705 @@ function PollsTab({ userId, username }: { userId?: string; username?: string }) ); } +interface AethexEvent { + id: string; + title: string; + description: string; + type: 'stream' | 'workshop' | 'tournament' | 'ama' | 'meetup'; + startTime: number; + endTime: number; + host: string; + hostAvatar?: string; + realm: ArmType; + attendees: number; + maxAttendees?: number; +} + +function EventCalendarTab({ userId, openExternalLink }: { userId?: string; openExternalLink: (url: string) => Promise }) { + const [rsvpEvents, setRsvpEvents] = useState>(() => { + try { + const saved = localStorage.getItem('aethex_event_rsvps'); + return new Set(saved ? JSON.parse(saved) : []); + } catch { + return new Set(); + } + }); + + useEffect(() => { + localStorage.setItem('aethex_event_rsvps', JSON.stringify(Array.from(rsvpEvents))); + }, [rsvpEvents]); + + const now = Date.now(); + const hour = 60 * 60 * 1000; + const day = 24 * hour; + + const events: AethexEvent[] = [ + { id: 'e1', title: 'GameForge Weekly Showcase', description: 'Present your latest game projects', type: 'stream', startTime: now + 2 * hour, endTime: now + 4 * hour, host: 'AeThex Team', realm: 'gameforge', attendees: 45, maxAttendees: 100 }, + { id: 'e2', title: 'Unity Workshop: VFX Basics', description: 'Learn to create stunning visual effects', type: 'workshop', startTime: now + 1 * day, endTime: now + 1 * day + 2 * hour, host: 'PixelMaster', realm: 'gameforge', attendees: 28, maxAttendees: 50 }, + { id: 'e3', title: 'Labs Demo Day', description: 'R&D team demos experimental features', type: 'stream', startTime: now + 2 * day, endTime: now + 2 * day + 3 * hour, host: 'Labs Team', realm: 'labs', attendees: 62 }, + { id: 'e4', title: 'Community AMA', description: 'Ask us anything about AeThex platform', type: 'ama', startTime: now + 3 * day, endTime: now + 3 * day + hour, host: 'Founders', realm: 'foundation', attendees: 89 }, + { id: 'e5', title: 'Pixel Art Tournament', description: 'Create the best pixel art in 2 hours', type: 'tournament', startTime: now + 4 * day, endTime: now + 4 * day + 2 * hour, host: 'ArtistX', realm: 'nexus', attendees: 34, maxAttendees: 64 }, + ]; + + const toggleRsvp = (eventId: string) => { + if (!userId) return; + setRsvpEvents(prev => { + const next = new Set(prev); + if (next.has(eventId)) { + next.delete(eventId); + } else { + next.add(eventId); + } + return next; + }); + }; + + const formatEventTime = (startTime: number) => { + const diff = startTime - now; + if (diff < hour) return 'Starting soon'; + if (diff < day) return `In ${Math.floor(diff / hour)}h`; + return `In ${Math.floor(diff / day)}d`; + }; + + const getEventIcon = (type: AethexEvent['type']) => { + switch (type) { + case 'stream': return Video; + case 'workshop': return BookOpen; + case 'tournament': return Trophy; + case 'ama': return Mic; + case 'meetup': return MapPin; + default: return Calendar; + } + }; + + return ( + +
+
+
+ +
+
+

Upcoming Events

+

{events.length} events this week

+
+
+
+ + {events.map((event, index) => { + const config = ARM_CONFIG[event.realm]; + const EventIcon = getEventIcon(event.type); + const hasRsvp = rsvpEvents.has(event.id); + const isFull = event.maxAttendees && event.attendees >= event.maxAttendees; + + return ( + +
+
+ +
+ +
+
+ {event.title} + + {event.type} + +
+

{event.description}

+ +
+ + {formatEventTime(event.startTime)} + + + {event.attendees}{event.maxAttendees ? `/${event.maxAttendees}` : ''} + + by {event.host} +
+
+ + toggleRsvp(event.id)} + disabled={!userId || (isFull && !hasRsvp)} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors shrink-0 ${ + hasRsvp + ? 'bg-green-500/20 text-green-400 border border-green-500/30' + : isFull + ? 'bg-[#3f4147] text-[#4e5058] cursor-not-allowed' + : 'bg-blue-500 hover:bg-blue-600 text-white' + }`} + > + {hasRsvp ? 'Going' : isFull ? 'Full' : 'RSVP'} + +
+
+ ); + })} + + +
+ ); +} + +interface TeamListing { + id: string; + projectTitle: string; + description: string; + creator: string; + creatorAvatar?: string; + realm: ArmType; + rolesNeeded: string[]; + teamSize: number; + maxTeam: number; + createdAt: number; +} + +function TeamFinderTab({ userId, openExternalLink }: { userId?: string; openExternalLink: (url: string) => Promise }) { + const [appliedTeams, setAppliedTeams] = useState>(() => { + try { + const saved = localStorage.getItem('aethex_team_applications'); + return new Set(saved ? JSON.parse(saved) : []); + } catch { + return new Set(); + } + }); + const [showCreateForm, setShowCreateForm] = useState(false); + + useEffect(() => { + localStorage.setItem('aethex_team_applications', JSON.stringify(Array.from(appliedTeams))); + }, [appliedTeams]); + + const listings: TeamListing[] = [ + { id: 't1', projectTitle: 'Neon Runners', description: 'Looking for 2D animator and sound designer for cyberpunk runner game', creator: 'GameDev_Pro', realm: 'gameforge', rolesNeeded: ['2D Animator', 'Sound Designer'], teamSize: 3, maxTeam: 5, createdAt: Date.now() - 2 * 60 * 60 * 1000 }, + { id: 't2', projectTitle: 'EcoSim', description: 'Need backend dev for environmental simulation game', creator: 'GreenCoder', realm: 'gameforge', rolesNeeded: ['Backend Dev'], teamSize: 2, maxTeam: 4, createdAt: Date.now() - 5 * 60 * 60 * 1000 }, + { id: 't3', projectTitle: 'AI Art Tool', description: 'Building an AI-powered pixel art generator, need ML engineer', creator: 'PixelMaster', realm: 'labs', rolesNeeded: ['ML Engineer', 'UI Designer'], teamSize: 1, maxTeam: 3, createdAt: Date.now() - 12 * 60 * 60 * 1000 }, + { id: 't4', projectTitle: 'SoundBoard Pro', description: 'Audio production tool needs React developer', creator: 'BeatMaker', realm: 'nexus', rolesNeeded: ['React Dev', 'Audio Engineer'], teamSize: 2, maxTeam: 4, createdAt: Date.now() - 1 * 24 * 60 * 60 * 1000 }, + ]; + + const applyToTeam = (teamId: string) => { + if (!userId || appliedTeams.has(teamId)) return; + setAppliedTeams(prev => new Set(prev).add(teamId)); + }; + + const formatTime = (timestamp: number) => { + const diff = Date.now() - timestamp; + const hours = Math.floor(diff / (60 * 60 * 1000)); + if (hours < 1) return 'Just now'; + if (hours < 24) return `${hours}h ago`; + return `${Math.floor(hours / 24)}d ago`; + }; + + return ( + +
+
+
+
+ +
+
+

Team Finder

+

{listings.length} teams looking for members

+
+
+ +
+
+ + {listings.map((listing, index) => { + const config = ARM_CONFIG[listing.realm]; + const hasApplied = appliedTeams.has(listing.id); + const isFull = listing.teamSize >= listing.maxTeam; + + return ( + +
+
+
+ {listing.projectTitle} + +
+

{listing.description}

+
+ {formatTime(listing.createdAt)} +
+ +
+ {listing.rolesNeeded.map(role => ( + + {role} + + ))} +
+ +
+
+ + {listing.teamSize}/{listing.maxTeam} members + + by {listing.creator} +
+ + applyToTeam(listing.id)} + disabled={!userId || hasApplied || isFull} + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + className={`px-3 py-1.5 rounded-lg text-xs font-medium transition-colors ${ + hasApplied + ? 'bg-green-500/20 text-green-400' + : isFull + ? 'bg-[#3f4147] text-[#4e5058] cursor-not-allowed' + : 'bg-purple-500 hover:bg-purple-600 text-white' + }`} + > + {hasApplied ? 'Applied' : isFull ? 'Full' : 'Apply'} + +
+
+ ); + })} + + +
+ ); +} + +interface SpotlightCreator { + id: string; + username: string; + displayName: string; + avatar?: string; + bio: string; + realm: ArmType; + followers: number; + projects: number; + votes: number; + featured: boolean; +} + +function CreatorSpotlightTab({ userId, openExternalLink }: { userId?: string; openExternalLink: (url: string) => Promise }) { + const [votedCreators, setVotedCreators] = useState>(() => { + try { + const saved = localStorage.getItem('aethex_spotlight_votes'); + return new Set(saved ? JSON.parse(saved) : []); + } catch { + return new Set(); + } + }); + const [creators, setCreators] = useState([ + { id: 'c1', username: 'PixelMaster', displayName: 'Pixel Master', bio: 'Creating stunning pixel art games and tools', realm: 'gameforge', followers: 2340, projects: 12, votes: 892, featured: true }, + { id: 'c2', username: 'CodeNinja', displayName: 'Code Ninja', bio: 'Full-stack developer building innovative web apps', realm: 'labs', followers: 1890, projects: 8, votes: 654, featured: false }, + { id: 'c3', username: 'SoundWizard', displayName: 'Sound Wizard', bio: 'Composer and audio engineer for games', realm: 'nexus', followers: 1560, projects: 15, votes: 543, featured: false }, + { id: 'c4', username: 'ArtistX', displayName: 'Artist X', bio: 'Digital artist and UI designer', realm: 'gameforge', followers: 3210, projects: 22, votes: 1203, featured: false }, + ]); + + useEffect(() => { + localStorage.setItem('aethex_spotlight_votes', JSON.stringify(Array.from(votedCreators))); + }, [votedCreators]); + + const voteCreator = (creatorId: string) => { + if (!userId || votedCreators.has(creatorId)) return; + setVotedCreators(prev => new Set(prev).add(creatorId)); + setCreators(prev => prev.map(c => + c.id === creatorId ? { ...c, votes: c.votes + 1 } : c + )); + }; + + const featuredCreator = creators.find(c => c.featured); + const otherCreators = creators.filter(c => !c.featured).sort((a, b) => b.votes - a.votes); + + return ( + + {featuredCreator && ( + +
+ + Featured Creator of the Week +
+ +
+
+ {featuredCreator.displayName[0]} +
+ +
+ +

{featuredCreator.bio}

+ +
+ {featuredCreator.followers.toLocaleString()} followers + {featuredCreator.projects} projects + + {featuredCreator.votes} votes + +
+
+
+
+ )} + +
+ Vote for Next Week's Spotlight + Resets Sunday +
+ + {otherCreators.map((creator, index) => { + const config = ARM_CONFIG[creator.realm]; + const hasVoted = votedCreators.has(creator.id); + + return ( + + #{index + 1} + +
+ {creator.displayName[0]} +
+ +
+
+ + +
+

{creator.bio}

+
+ +
+ + {creator.votes} + + voteCreator(creator.id)} + disabled={!userId || hasVoted} + whileHover={!hasVoted && userId ? { scale: 1.1 } : {}} + whileTap={!hasVoted && userId ? { scale: 0.9 } : {}} + className={`p-2 rounded-lg transition-colors ${ + hasVoted + ? 'bg-amber-500/20 text-amber-400' + : 'bg-[#1e1f22] text-[#949ba4] hover:text-amber-400 hover:bg-[#3f4147]' + }`} + > + + +
+
+ ); + })} +
+ ); +} + +interface MiniGameScore { + game: string; + score: number; + playedAt: number; +} + +function MiniGamesTab({ userId }: { userId?: string }) { + const [triviaIndex, setTriviaIndex] = useState(0); + const [triviaAnswer, setTriviaAnswer] = useState(null); + const [triviaCorrect, setTriviaCorrect] = useState(null); + const [triviaStreak, setTriviaStreak] = useState(() => { + try { + return parseInt(localStorage.getItem('aethex_trivia_streak') || '0'); + } catch { + return 0; + } + }); + const [typingRace, setTypingRace] = useState<{ text: string; startTime: number; input: string } | null>(null); + const [typingWPM, setTypingWPM] = useState(null); + const inputRef = useRef(null); + + const triviaQuestions = [ + { question: "How many realms are there in AeThex?", options: ["4", "5", "6", "7"], correct: 2 }, + { question: "What is the GameForge realm focused on?", options: ["Business", "Game Development", "Research", "Education"], correct: 1 }, + { question: "What does XP stand for?", options: ["Extra Points", "Experience Points", "Exchange Points", "Expansion Points"], correct: 1 }, + { question: "Which realm handles R&D projects?", options: ["Nexus", "Labs", "Corp", "Foundation"], correct: 1 }, + { question: "What's the color associated with the Labs realm?", options: ["Green", "Blue", "Yellow", "Purple"], correct: 2 }, + ]; + + const typingTexts = [ + "Build the future with AeThex platform", + "Create amazing games in GameForge", + "Explore experimental features in Labs", + "Join the creator community today", + "Level up your skills and earn XP", + ]; + + const handleTriviaAnswer = (answerIndex: number) => { + if (triviaAnswer !== null) return; + setTriviaAnswer(answerIndex); + const isCorrect = answerIndex === triviaQuestions[triviaIndex].correct; + setTriviaCorrect(isCorrect); + + if (isCorrect) { + const newStreak = triviaStreak + 1; + setTriviaStreak(newStreak); + localStorage.setItem('aethex_trivia_streak', String(newStreak)); + } else { + setTriviaStreak(0); + localStorage.setItem('aethex_trivia_streak', '0'); + } + }; + + const nextTrivia = () => { + setTriviaIndex((prev) => (prev + 1) % triviaQuestions.length); + setTriviaAnswer(null); + setTriviaCorrect(null); + }; + + const startTypingRace = () => { + const text = typingTexts[Math.floor(Math.random() * typingTexts.length)]; + setTypingRace({ text, startTime: Date.now(), input: '' }); + setTypingWPM(null); + setTimeout(() => inputRef.current?.focus(), 100); + }; + + const handleTypingInput = (e: React.ChangeEvent) => { + if (!typingRace) return; + const input = e.target.value; + setTypingRace({ ...typingRace, input }); + + if (input === typingRace.text) { + const timeTaken = (Date.now() - typingRace.startTime) / 1000 / 60; + const words = typingRace.text.split(' ').length; + const wpm = Math.round(words / timeTaken); + setTypingWPM(wpm); + } + }; + + const currentQuestion = triviaQuestions[triviaIndex]; + + return ( + +
+
+
+ +
+
+

Mini-Games

+

Play games, earn bragging rights

+
+
+
+ + {/* AeThex Trivia */} + +
+ + AeThex Trivia + + {triviaStreak > 0 && ( + + {triviaStreak} streak + + )} +
+ +

{currentQuestion.question}

+ +
+ {currentQuestion.options.map((option, index) => { + const isSelected = triviaAnswer === index; + const isCorrect = index === currentQuestion.correct; + const showResult = triviaAnswer !== null; + + return ( + handleTriviaAnswer(index)} + disabled={triviaAnswer !== null} + whileHover={triviaAnswer === null ? { scale: 1.02 } : {}} + whileTap={triviaAnswer === null ? { scale: 0.98 } : {}} + className={`w-full p-3 rounded-lg text-sm text-left transition-all ${ + showResult + ? isCorrect + ? 'bg-green-500/20 border border-green-500/50 text-green-300' + : isSelected + ? 'bg-red-500/20 border border-red-500/50 text-red-300' + : 'bg-[#1e1f22] border border-transparent text-[#949ba4]' + : 'bg-[#1e1f22] hover:bg-[#2b2d31] border border-transparent text-white' + }`} + > + {option} + + ); + })} +
+ + {triviaAnswer !== null && ( + + + {triviaCorrect ? '🎉 Correct!' : '❌ Wrong!'} + + + + )} +
+ + {/* Typing Race */} + +
+ + Typing Race + + {typingWPM !== null && ( + {typingWPM} WPM + )} +
+ + {!typingRace ? ( + + ) : typingWPM !== null ? ( +
+

🏆 {typingWPM} WPM

+

Great typing!

+ +
+ ) : ( +
+

+ {typingRace.text.split('').map((char, i) => { + const inputChar = typingRace.input[i]; + const isCorrect = inputChar === char; + const isTyped = i < typingRace.input.length; + + return ( + + {char} + + ); + })} +

+ +
+ )} +
+ +

+ More mini-games coming soon! +

+
+ ); +} + interface Challenge { id: string; title: string; @@ -1815,6 +2522,10 @@ export default function Activity() { { id: "polls", label: "Polls", icon: BarChart3 }, { id: "challenges", label: "Challenges", icon: Trophy }, { id: "projects", label: "Projects", icon: Layers }, + { id: "events", label: "Events", icon: Calendar }, + { id: "teams", label: "Teams", icon: UserPlus }, + { id: "spotlight", label: "Spotlight", icon: Crown }, + { id: "games", label: "Games", icon: Dice6 }, { id: "realms", label: "Realms", icon: Sparkles }, { id: "quests", label: "Quests", icon: Target }, { id: "top", label: "Top", icon: TrendingUp }, @@ -1917,6 +2628,10 @@ export default function Activity() { {activeTab === "polls" && } {activeTab === "challenges" && } {activeTab === "projects" && } + {activeTab === "events" && } + {activeTab === "teams" && } + {activeTab === "spotlight" && } + {activeTab === "games" && } {activeTab === "realms" && } {activeTab === "quests" && } {activeTab === "top" && }