Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 11 additions & 9 deletions front-end/components/FlipBook.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import MatchSelector from "./MatchSelector";
import MatchTimeline from "./MatchTimeline";
import { useTimelineContext } from "@/context/TimelineContext";
import { useRealTimelineContext } from "@/context/RealTimelineContext";
import { usePercentileContext } from "@/context/PercentileContext";
import ChatInput from "./ChatInput";
import ChatOutput from "./ChatOutput.jsx";
import AncientRunicPage from "./AncientRunicPage.jsx";
Expand All @@ -24,6 +25,7 @@ const FlipBook = () => {
const [isTurning, setIsTurning] = useState(false);
const { timelineResult } = useTimelineContext();
const { realTimelineResult, setRealTimelineResult} = useRealTimelineContext();
const { percentileResult } = usePercentileContext();

// Match Timeline state + Match Selector
const [matches, setMatches] = useState([]);
Expand Down Expand Up @@ -111,7 +113,7 @@ const FlipBook = () => {
const mostPlayedList = timelineResult.most_played_champions
const championsArray = Object.entries(mostPlayedList).map(([name, games]) => ({ name, games }));
setMostPlayed(championsArray);

if (!realTimelineResult) return;

if (realTimelineResult.timeline_data) {
Expand Down Expand Up @@ -194,7 +196,7 @@ const FlipBook = () => {

// Create page structure once and store it in a ref
const pageStructure = useMemo(() => {
if (!mostPlayed || !timelineResult || !player1Stats) return [];
if (!mostPlayed || !timelineResult || !player1Stats || !percentileResult ) return [];

return [
{ cover: "book_cover.jpg", frontCover: true, id: 0 },
Expand All @@ -221,7 +223,7 @@ const FlipBook = () => {
back: <SummaryBack data={{
username: timelineResult.player_id,
region: timelineResult.playstyle.archetype,
profile:["Late-Game", "Scaling", "Empire-Building"],
profile: timelineResult.playstyle?.profile ? timelineResult.playstyle.profile.split(",").map(s => s.trim()): [],
statistics: { goldpm: player1Stats.avg_gpm.toFixed(2), winRate: player1Stats.win_rate.toFixed(2), averageKDA: player1Stats.avg_kda.toFixed(2), cspm: player1Stats.avg_cs_per_min.toFixed(2)},
mostPlayed: mostPlayed,
playerStats: player1Stats
Expand All @@ -230,9 +232,9 @@ const FlipBook = () => {
},
{
front: <SummaryFront data={{
roles: { top: 2, jg: 19, mid: 10, adc: 8, sup: 5 },
strengths:["Azir", "Sivir", "Cassiopeia"],
weaknesses:["Nasus", "Taliyah"]
overall: percentileResult.overall_performance,
strengths: percentileResult.ranked_stats.top_5,
weaknesses: percentileResult.ranked_stats.bottom_5
}}/>,
back: <MapFragmentPage
region="Ionia" // Text displayed in center
Expand Down Expand Up @@ -337,7 +339,7 @@ const FlipBook = () => {
id: 11
},
{ cover: "green-cover.jpg", id: 12 },
]}, [timelineResult, mostPlayed, player1Stats]);
]}, [timelineResult, mostPlayed, player1Stats, percentileResult]);

// Initialize pages only once
useEffect(() => {
Expand Down Expand Up @@ -387,7 +389,7 @@ const FlipBook = () => {
setTimeout(() => {
pageRefs.current[i]?.flip();
resolve();
}, 800);
}, 500);
});
}
} else {
Expand All @@ -396,7 +398,7 @@ const FlipBook = () => {
setTimeout(() => {
pageRefs.current[i]?.flip();
resolve();
}, 800);
}, 500);
});
}
}
Expand Down
58 changes: 43 additions & 15 deletions front-end/components/SearchandCompare.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
import React, { useState } from "react";
import { useFriendContext } from "@/context/FriendContext";
import { useTimelineContext } from "@/context/TimelineContext";

const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
const [searchQuery, setSearchQuery] = useState("");
const [username, setUsername] = useState("");
const [tagline, setTagline] = useState("");
const [fetchingPlayer2, setFetchingPlayer2] = useState(false);
const [errorFetchingPlayer2, setErrorFetchingPlayer2] = useState(false);
const [player2Stats, setPlayer2Stats] = useState({
"avg_kda": 0,
"avg_cs_per_min": 0,
Expand All @@ -14,6 +19,7 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
"avg_cc_time": 0});
const [hasSearched, setHasSearched] = useState(false); // Track if user has searched
const { friendResult, setFriendResult } = useFriendContext();
const { timelineResult } = useTimelineContext();
const stats = ["avg_kda", "avg_cs_per_min", "avg_kill_participation", "avg_dpm", "avg_gpm", "avg_solo_kills", "avg_vision_score", "avg_cc_time"];
const displayStats = {
"avg_kda": "KDA",
Expand All @@ -27,18 +33,19 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
}

const handleSearch = async () => {
if (!searchQuery.trim()) return;
if (!username.trim() || !tagline.trim() || !timelineResult) return;

// TODO: Replace with actual API call
const endpoint = "https://v4ft9564pb.execute-api.us-west-2.amazonaws.com/player/process";
const endpoint = "https://v4ft9564pb.execute-api.us-west-2.amazonaws.com/player/compare";

const body = {
game_name: "ShadowLeaf",
tagline: "8005",
num_games: 1
game_name: username,
tagline: tagline,
num_games: timelineResult.matches_processed
};

try {
setFetchingPlayer2(true);
console.log("Fetching...");

const res= await
Expand All @@ -56,10 +63,14 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
setFriendResult(timelineData)
setPlayer2Stats(timelineData.stats);
onPlayer2Found(timelineData.stats);
setFetchingPlayer2(false);
setErrorFetchingPlayer2(false);
setHasSearched(true);
} catch (err) {
console.error("Error fetching API data:", err);
setFriendResult({ error: "Failed to load timeline." });
setErrorFetchingPlayer2(true)
setFetchingPlayer2(false);
}
};

Expand All @@ -72,8 +83,8 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
if (!stat2 || stat2 === "") return "text-gray-300";
if (isNaN(val1) || isNaN(val2)) return "text-gray-300";

if (val1 > val2) return "text-green-400";
if (val1 < val2) return "text-red-400";
if (val1 < val2) return "text-green-400";
if (val1 > val2) return "text-red-400";
return "text-gray-300";
};

Expand All @@ -84,16 +95,20 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
<div className="flex gap-2">
<input
type="text"
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
value={username}
onChange={(e) => setUsername(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSearch()}
className="flex-1 px-4 py-3 bg-gray-800 bg-opacity-70 border border-gray-700 rounded text-white focus:outline-none focus:border-gray-500"
placeholder="Find opponent..."
placeholder="Game name"
/>
<input
type="text"
placeholder="# ..."
className="w-24 px-4 py-3 bg-gray-800 bg-opacity-70 border border-gray-700 rounded text-white focus:outline-none focus:border-gray-500"/>
value={tagline}
onChange={(e) => setTagline(e.target.value)}
onKeyPress={(e) => e.key === "Enter" && handleSearch()}
className="w-24 px-4 py-3 bg-gray-800 bg-opacity-70 border border-gray-700 rounded text-white focus:outline-none focus:border-gray-500"
placeholder="#tag"
/>
<button
onClick={handleSearch}
className="magical-button"
Expand All @@ -114,12 +129,25 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
)}
</div>
{/* Stats Display - Identical to Social */}
<div className="bg-black bg-opacity-70 rounded-lg p-8 w-full max-w-md">
<div className="relative bg-black bg-opacity-70 rounded-lg p-8 w-full max-w-md">
{fetchingPlayer2 && (<div className="absolute inset-0 bg-black bg-opacity-70 rounded-lg p-8 h-full w-full max-w-md">
<div className="text-center">
<div className="relative w-16 h-16 mx-auto mb-4">
<div className="absolute inset-0 border-4 border-sky-200 border-t-sky-400 rounded-full animate-spin"></div>
</div>
<h2 className="text-xl font-semibold text-white">Loading...</h2>
<p className="text-amber-100">Please wait...</p>
</div>
</div>)}
{!fetchingPlayer2 && errorFetchingPlayer2 && (<div className="absolute inset-0 bg-black bg-opacity-70 rounded-lg p-8 h-full w-full max-w-md">
<div className="text-center">
<h2 className="text-xl font-semibold text-white">Error loading player, try again</h2>
</div>
</div>)}
<h3 className="text-xl font-semibold text-white mb-6 text-center">
{!friendResult && "Search for a player"}
{friendResult && `${friendResult.player_id}`}
</h3>

<div className="space-y-1">
{stats.map(stat => (
<div key={stat} className={`flex justify-between items-center px-3 py-2 bg-gray-800 border border-gray-700 rounded ${getStatColor(player1Stats[stat], player2Stats[stat])}`}>
Expand Down
54 changes: 51 additions & 3 deletions front-end/components/SummaryBack.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,55 @@ const SummaryBack = ({ data }) => {
"Late-Game": `Shines in the late stages of ${data.region} battle once resources are built.`,
"Scaling": "Grows stronger over time with continuous development.",
"Empire-Building": "Focuses on expansion and strategic dominance.",
};
"Patient": "Ready to capitalize on enemy mistakes.",
"Unpredictable": "No one knows this players next move will be, not even they know.",
"Whimsical": "Tilt free and good mental.",
"Mobile": "This summoner moves around the map quickly.",
"Creative": "Utilizes unique methods to put themselves ahead.",
"Relentless": "No one can hold this player back.",
"Undying": "Death is a foreign concept to this summoner.",
"Aggressive": "Takes control of the game by force.",
"Altruistic": "Looks out for teammates in need of support.",
"Opportunistic": "Constantly looking for plays to change the tide of the game.",
"Risky" : "Willing to lose it all for a massive payout.",
"Gold-Focused" : "Gold is the number one prospect in this player's agenda.",
"Jungle-Focused": "Focuses on gettting a lead through massive jungle monsters.",
"Elemental": "Grabs elemental buffs when needed.",
"Objective-Control": "Sets up pressure to secure powerful objectives.",
"Hidden": "Successful ganker",
"Balanced": "Can attack ruthlessly with little to no holes in their defense.",
"Harmonious": "In the flow state, they cannot be shaken.",
"Skillful": "Takes fights to outplay the enemy.",
"Adaptable": "Ready to overcome any situation, no matter how tough.",
"Honorable": "Willing to fight fairly in order to prove a point.",
"Protective": "Capable of protecting the less fortunate.",
"Teamfight": "Coordinates with their team to setup favorable fights.",
"Consistent": "Looks out for any possible positive outcomes that could bring about victory.",
"Dominant": "Bullies the opposing laner into submission.",
"Conquest": "Topples towers and inhibitors in order to crush enemy morale.",
"Powerful": "Enemies crumble to the mere presence of this summoner.",
"Efficient": "Makes the most out of every resource they have.",
"Innovative": "When one idea fails, they have ten more waiting to be unleashed.",
"Calculated": "Meticulous decision making goes into to each and every step of their plans.",
"Wealthy": "Money is of no object.",
"Draining": "Fuels their own life through the pain of their enemies.",
"Persistent": "Unyielding pressure that has no end.",
"Sustain": "Has an infinite amount of self-healing making it impossible for them to die.",
"Deaths-Dance": "Takes damage to recover that damage back.",
"Supportive": "Live and die for your allies.",
"Vision": "Able to see everywhere and anywhere across the map.",
"Celestial": "Benevolent aura.",
"Tanky": "You are an insurmountable wall that protects your team from enemy attacks.",
"Survival": "Able to survive the harsh cold as well as brutal attacks.",
"CC-Heavy": "Enemies find themselves unable to move in your presence.",
"Enduring": "No matter how many blows you take, no matter how much you want to capitulate, you endure.",
"Consuming": "Devours the enemy team's hope for victory.",
"Chaotic": "Ready to take any fights, whenever and wherever on the map.",
"Damage-Focused": "Eager to dish out tons of damage without a care in the world.",
"Experimental": "Eager to try new builds to see what syngeries can be brought about.",
"Damage-Over-Time": "Willing to push themselves to the limit to achieve their goals.",
"High-Risk": "High risk? High reward."
};



Expand All @@ -23,8 +71,8 @@ const SummaryBack = ({ data }) => {
setActiveTrait(null);
setActiveRef(null);
};
window.addEventListener("click", handleClickOutside);
return () => window.removeEventListener("click", handleClickOutside);
window.addEventListener("mousedown", handleClickOutside);
return () => window.removeEventListener("mousedown", handleClickOutside);
}, []);

const handleTraitClick = (trait, ref, e) => {
Expand Down
Loading