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
68 changes: 41 additions & 27 deletions front-end/components/FlipBook.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import Social from "./Social";
import SearchAndCompare from "./SearchandCompare";
import MatchSelector from "./MatchSelector";
import MatchTimeline from "./MatchTimeline";
import { useTimelineContext } from "@/context/TimelineContext";
import ChatInput from "./ChatInput";
import ChatOutput from "./ChatOutput.jsx";
import matchesData from "../data/matches.json";
import AncientRunicPage from "./AncientRunicPage.jsx";
import MapFragmentPage from "./MapFragmentPage";

Expand All @@ -21,31 +21,32 @@ const FlipBook = () => {
const [onFirstPage, setOnFirstPage] = useState(true);
const [onLastPage, setOnLastPage] = useState(false);
const [isTurning, setIsTurning] = useState(false);
const { timelineResult } = useTimelineContext();

// Match Timeline state + Match Selector
const [matches, setMatches] = useState([]);
const [selectedMatchId, setSelectedMatchId] = useState(null);

// Player stats state
const [player1Stats, setPlayer1Stats] = useState({
Games: "150",
WinRate: "60%",
KDA: "100",
CPM: "85",
gold15: "70",
GPM: "60",
DPM: "90"
});
"avg_kda": 0,
"avg_cs_per_min": 0,
"avg_kill_participation": 0,
"avg_dpm": 0,
"avg_gpm": 0,
"avg_solo_kills": 0,
"avg_vision_score": 0,
"avg_cc_time": 0});

const [player2Stats, setPlayer2Stats] = useState({
Games: "",
WinRate: "",
KDA: "",
CPM: "",
gold15: "",
GPM: "",
DPM: ""
});
"avg_kda": 0,
"avg_cs_per_min": 0,
"avg_kill_participation": 0,
"avg_dpm": 0,
"avg_gpm": 0,
"avg_solo_kills": 0,
"avg_vision_score": 0,
"avg_cc_time": 0});

// Chat state
const [chatMessages, setChatMessages] = useState([]);
Expand All @@ -60,16 +61,19 @@ const FlipBook = () => {
const pageRefs = useRef([]);
const isInitialized = useRef(false);

// Load matches data on component mount
useEffect(() => {
if (matchesData && matchesData.timeline_data) {
setMatches(matchesData.timeline_data);
// Set first match as selected by default
if (matchesData.timeline_data.length > 0) {
setSelectedMatchId(matchesData.timeline_data[0].match_id);
}
if (!timelineResult) return;

if (timelineResult.timeline_data) {

setMatches(timelineResult.timeline_data);
setSelectedMatchId(timelineResult.timeline_data[0]?.match_id || null);
} else {
console.warn("No timeline data found in search result:", timelineResult);
}
}, []);
setPlayer1Stats(timelineResult.stats)
}, [timelineResult]);


// Get the currently selected match
const selectedMatch = useMemo(() =>
Expand Down Expand Up @@ -136,7 +140,9 @@ const FlipBook = () => {
}, []);

// Create page structure once and store it in a ref
const pageStructure = useMemo(() => [
const pageStructure = useMemo(() =>

[
{ cover: "book_cover.jpg", frontCover: true, id: 0 },
{ front: <AncientRunicPage
variant="power" // "default", "power", "mystical", "elements"
Expand Down Expand Up @@ -399,7 +405,15 @@ const FlipBook = () => {

return newPage;
}, [player1Stats, player2Stats, matches, selectedMatchId, selectedMatch, handleMatchSelect, handlePlayer2Found, chatMessages, isLoadingChat, handleSendMessage]);

/*
if(!timelineResult){
return (
<div className="w-full text-center text-3xl">
LOADING...
</div>
)
}
*/
return (
<div className="book-frame">
<div className="page-wrapper slideUp-animation">
Expand Down
88 changes: 60 additions & 28 deletions front-end/components/SearchandCompare.js
Original file line number Diff line number Diff line change
@@ -1,44 +1,73 @@
import React, { useState } from "react";
import { useFriendContext } from "@/context/FriendContext";

const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
const [searchQuery, setSearchQuery] = useState("");
const [player2Stats, setPlayer2Stats] = useState({
Games: "",
WinRate: "",
KDA: "",
CPM: "",
gold15: "",
GPM: "",
DPM: ""
});
"avg_kda": 0,
"avg_cs_per_min": 0,
"avg_kill_participation": 0,
"avg_dpm": 0,
"avg_gpm": 0,
"avg_solo_kills": 0,
"avg_vision_score": 0,
"avg_cc_time": 0});
const [hasSearched, setHasSearched] = useState(false); // Track if user has searched

const stats = ["Games", "WinRate", "KDA", "CPM", "gold15", "GPM", "DPM"];
const { setFriendResult } = useFriendContext();
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",
"avg_cs_per_min": "CS per min",
"avg_kill_participation": "kill participation",
"avg_dpm": "damage per minute",
"avg_gpm": "gold per minute",
"avg_solo_kills": "solo kills",
"avg_vision_score": "vision score",
"avg_cc_time": "crowd control time"
}

const handleSearch = async () => {
if (!searchQuery.trim()) return;

// TODO: Replace with actual API call
const mockPlayerData = {
Games: "95",
WinRate: "55%",
KDA: "80",
CPM: "75",
gold15: "65",
GPM: "85",
DPM: "70"
const endpoint = "https://v4ft9564pb.execute-api.us-west-2.amazonaws.com/player/process";

const body = {
game_name: "ShadowLeaf",
tagline: "8005",
num_games: 1
};

setPlayer2Stats(mockPlayerData);
setHasSearched(true);
onPlayer2Found(mockPlayerData); // Pass stats back to parent

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

const res= await
fetch(endpoint, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
})

if (!res.ok) {
throw new Error(`API request failed: ${res.status}`);
}

const timelineData = await res.json();
setFriendResult(timelineData)
setPlayer2Stats(timelineData.stats);
onPlayer2Found(timelineData.stats);
setHasSearched(true);
} catch (err) {
console.error("Error fetching API data:", err);
setFriendResult({ error: "Failed to load timeline." });
}
};

const getStatColor = (stat1, stat2) => {
if (!hasSearched) return "text-gray-300"; // Gray if no search yet

const val1 = parseFloat(stat1);
const val2 = parseFloat(stat2);
const val1 = stat1;
const val2 = stat2;

if (!stat2 || stat2 === "") return "text-gray-300";
if (isNaN(val1) || isNaN(val2)) return "text-gray-300";
Expand All @@ -61,6 +90,10 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
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..."
/>
<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"/>
<button
onClick={handleSearch}
className="magical-button"
Expand All @@ -72,15 +105,14 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
<span className="particle"></span>
<span className="particle"></span>
</div>
</button>
</button>
</div>
{hasSearched && player2Stats.Games && (
<p className="text-green-400 text-center text-sm mt-2">
Player found: {searchQuery}
</p>
)}
</div>

{/* Stats Display - Identical to Social */}
<div className="bg-black bg-opacity-70 rounded-lg p-8 w-full max-w-md">
<h3 className="text-xl font-semibold text-white mb-6 text-center">
Expand All @@ -91,10 +123,10 @@ const SearchAndCompare = ({ player1Stats, onPlayer2Found }) => {
{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])}`}>
<label className={"text-sm font-medium capitalize"}>
{stat}
{displayStats[stat]}
</label>
<div className="font-semibold">
{hasSearched ? (player2Stats[stat] || "0") : (player2Stats[stat] || "0")}
{hasSearched ? (player2Stats[stat].toFixed(2) || "0") : (player2Stats[stat] || "0")}
</div>
</div>
))}
Expand Down
21 changes: 15 additions & 6 deletions front-end/components/Social.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ const Social = ({ input1 = {}, input2 = {} }) => {
const stats2 = input2;

const getStatColor = (stat1, stat2) => {
const val1 = parseFloat(stat1);
const val2 = parseFloat(stat2);
const val1 = stat1;
const val2 = stat2;

if (!stat2 || stat2 === "") return "text-gray-300";
if (isNaN(val1) || isNaN(val2)) return "text-gray-300";
Expand All @@ -16,8 +16,17 @@ const Social = ({ input1 = {}, input2 = {} }) => {
return "text-gray-300";
};

const stats = ["Games", "WinRate", "KDA", "CPM", "gold15", "GPM", "DPM"];

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",
"avg_cs_per_min": "CS per min",
"avg_kill_participation": "kill participation",
"avg_dpm": "damage per minute",
"avg_gpm": "gold per minute",
"avg_solo_kills": "solo kills",
"avg_vision_score": "vision score",
"avg_cc_time": "crowd control time"
}
return (
<div className="flex flex-col items-center justify-center p-8">
<h2 className="section-title">Stat Comparison</h2>
Expand All @@ -34,10 +43,10 @@ const Social = ({ input1 = {}, input2 = {} }) => {
{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(stats1[stat], stats2[stat])}`}>
<label className="text-sm font-medium capitalize">
{stat}
{displayStats[stat]}
</label>
<div className="font-semibold">
{stats1[stat] || "0"}
{stats1[stat].toFixed(2) || "0"}
</div>
</div>
))}
Expand Down
Loading