diff --git a/src/web/src/App.tsx b/src/web/src/App.tsx index 1fc6a189..c7689415 100644 --- a/src/web/src/App.tsx +++ b/src/web/src/App.tsx @@ -36,6 +36,7 @@ import { ScrollToTop, GuestPage, PrivatePolicy, + ProfileSearch, TOS, ManagePosts, EditPosts, @@ -214,6 +215,7 @@ function App() { > }> }> + }> {role === 'admin' && Verified && ( }> }> diff --git a/src/web/src/Components.ts b/src/web/src/Components.ts index 48268ecc..de68e5db 100644 --- a/src/web/src/Components.ts +++ b/src/web/src/Components.ts @@ -84,5 +84,7 @@ export { default as FollowingSideNav } from './Components/global/sidebar/Followi export { default as PostPageSkeleton } from './Components/post/PostPageSkeleton'; export { default as LeaderboardPosition } from './Components/profile/LeaderboardPosition'; export { default as VerificationMessage } from './Components/profile/VerificationMessage'; +export { default as FiltersPopup } from './Components/auth/FiltersPopup'; +export { default as ProfileSearch } from './Pages/user/ProfileSearch'; export { default as MobileComments } from './Components/post/MobileComments'; export { default as ZoomableImage } from './Components/post/ZoomableImage'; diff --git a/src/web/src/Components/auth/FiltersPopup.tsx b/src/web/src/Components/auth/FiltersPopup.tsx new file mode 100644 index 00000000..1edaa5b1 --- /dev/null +++ b/src/web/src/Components/auth/FiltersPopup.tsx @@ -0,0 +1,198 @@ +import React, { useState } from 'react'; + +interface FiltersPopupProps { + onClose: () => void; + selectedGame: string; + onSubmit: (filters: Filters) => void; +} + +interface Filters { + [key: string]: string[]; +} + +type FilterCategories = + | 'mapName' + | 'teamSide' + | 'grenadeType' + | 'jumpThrow' + | 'valorantAgent'; + +const initialFilters: Filters = { + teamSide: [], + mapName: [], + grenadeType: [], + jumpThrow: [], + valorantAgent: [], +}; + +const FiltersPopup: React.FC = ({ + onClose, + selectedGame, + onSubmit, +}) => { + const [filters, setFilters] = useState(initialFilters); + + // Add value in FilterCategory only if it is not yet toggled + const toggleCheckbox = (category: FilterCategories, value: string) => { + setFilters((prevFilters) => { + const currVal = prevFilters[category]; + if (currVal.includes(value)) { + return { + ...prevFilters, + [category]: currVal.filter((v) => v !== value), + }; + } else { + return { + ...prevFilters, + [category]: [...currVal, value], + }; + } + }); + }; + + // Format each checkbox - e.g. hover effects + const renderCheckboxes = ( + title: string, + category: FilterCategories, + options: string[], + ) => ( +
+

{title}

+ {options.map((option) => ( + + ))} +
+ ); + + const pressedSubmit = () => { + onSubmit(filters); + onClose(); + }; + + return ( +
+
+
+

Filters

+ +
+
+ {selectedGame === 'Valorant' && ( + <> + {renderCheckboxes('Side', 'teamSide', [ + 'Attacker', + 'Defender', + ])} + {renderCheckboxes('Map', 'mapName', [ + 'abyss', + 'ascent', + 'bind', + 'breeze', + 'fracture', + 'haven', + 'icebox', + 'lotus', + 'pearl', + 'split', + 'sunset', + ])} + {renderCheckboxes('Agent', 'valorantAgent', [ + 'Brimstone', + 'Phoenix', + 'Sage', + 'Sova', + 'Viper', + 'Cypher', + 'Reyna', + 'Killjoy', + 'Breach', + 'Omen', + 'Jett', + 'Raze', + 'Skye', + 'Yoru', + 'Astra', + 'KAY/O', + 'Chamber', + 'Neon', + 'Fade', + 'Harbor', + 'Gekko', + 'Deadlock', + 'Iso', + 'Clove', + 'Vyse', + ])} + + )} + {selectedGame === 'CS2' && ( + <> + {renderCheckboxes('Side', 'teamSide', ['T', 'CT'])} + {renderCheckboxes('Map', 'mapName', [ + 'mirage', + 'inferno', + 'nuke', + 'overpass', + 'vertigo', + 'ancient', + 'anubis', + 'dust2', + ])} + {renderCheckboxes('Grenade', 'grenadeType', [ + 'he', + 'smoke', + 'flashbangs', + 'decoy', + 'molotov', + 'incendiary', + ])} + {renderCheckboxes('Jumpthrow?', 'jumpThrow', [ + 'YES', + 'NO', + ])} + + )} +
+
+ +
+
+
+ ); +}; + +export default FiltersPopup; diff --git a/src/web/src/Pages/user/ProfilePage.tsx b/src/web/src/Pages/user/ProfilePage.tsx index a96a4c57..275cc2e3 100644 --- a/src/web/src/Pages/user/ProfilePage.tsx +++ b/src/web/src/Pages/user/ProfilePage.tsx @@ -10,6 +10,7 @@ import { UnapprovedPostsPopup, LeaderboardPosition, VerificationMessage, + FiltersPopup, } from '../../Components'; import { getUserByUsername } from '../../util/getUser'; import { follow } from '../../util/followStatus'; @@ -54,12 +55,18 @@ const ProfilePage = () => { }); const [loading, setLoading] = useState(true); const [showFollowerPopup, setShowFollowerPopup] = useState(false); + const [showFiltersPopup, setShowFiltersPopup] = useState(false); const [showFollowingPopup, setShowFollowingPopup] = useState(false); const [followingCount, setFollowingCount] = useState(0); const [following, setFollowing] = useState>(); const [followerCount, setFollowerCount] = useState(0); const [followers, setFollowers] = useState>(); const [posts, setPosts] = useState([[]]); + // Will be modified when filters change + const [ValorantGlobalPosts, setValorantGlobalPosts] = useState( + [], + ); + const [CSGlobalPosts, setCSGlobalPosts] = useState([]); const [open, setOpen] = useState(false); const [selectedTab, setSelectedTab] = useState('Posts'); const Auth = useContext(AuthContext); @@ -79,7 +86,7 @@ const ProfilePage = () => { // Gets called twice during dev mode // So there should be 2 error messages - // If you search for an non exisitant user + // If you search for an non-existent user useEffect(() => { // Fetch Users if (!Auth && !id) { @@ -133,6 +140,8 @@ const ProfilePage = () => { .slice(2 * numGames) .map((response) => response.data); + setCSGlobalPosts(allPosts[0]); + setValorantGlobalPosts(allPosts[1]); setPosts(allPosts); setUnapprovedPosts(unapprovedPosts.flat()); setSavedPosts(savedPosts.flat()); @@ -225,6 +234,71 @@ const ProfilePage = () => { } }; + const handleFiltersSubmit = (filters: any) => { + // Go through all posts, then check each filter for each post + // As we parse, we only add posts that match the filters to a subset + if (selectedGame === 'CS2') { + var csFilteredSubset = []; + for ( + let post_index = 0; + post_index < CSGlobalPosts.length; + post_index++ + ) { + if (filters.teamSide.includes(CSGlobalPosts[post_index].teamSide)) { + csFilteredSubset.push(CSGlobalPosts[post_index]); + } else if ( + filters.mapName.includes(CSGlobalPosts[post_index].mapName) + ) { + csFilteredSubset.push(CSGlobalPosts[post_index]); + } else if ( + filters.grenadeType.includes( + CSGlobalPosts[post_index].grenadeType, + ) + ) { + csFilteredSubset.push(CSGlobalPosts[post_index]); + } else if ( + (filters.jumpThrow.includes('YES') && + CSGlobalPosts[post_index].jumpThrow) || + (filters.jumpThrow.includes('NO') && + !CSGlobalPosts[post_index].jumpThrow) + ) { + csFilteredSubset.push(CSGlobalPosts[post_index]); + } + // Update state of posts with filtered subset + const updatedPosts = [csFilteredSubset, ValorantGlobalPosts]; + setPosts(updatedPosts); + } + } else if (selectedGame === 'Valorant') { + var valorantFilteredSubset = []; + for ( + let post_index = 0; + post_index < ValorantGlobalPosts.length; + post_index++ + ) { + if ( + filters.teamSide.includes( + ValorantGlobalPosts[post_index].teamSide, + ) + ) { + valorantFilteredSubset.push(ValorantGlobalPosts[post_index]); + } else if ( + filters.mapName.includes(ValorantGlobalPosts[post_index].mapName) + ) { + valorantFilteredSubset.push(ValorantGlobalPosts[post_index]); + } else if ( + filters.valorantAgent.includes( + ValorantGlobalPosts[post_index].valorantAgent, + ) + ) { + valorantFilteredSubset.push(ValorantGlobalPosts[post_index]); + } + } + // Update state of posts with filtered subset + const updatedPosts = [CSGlobalPosts, valorantFilteredSubset]; + setPosts(updatedPosts); + } + }; + if (loading) return ; return ( @@ -311,6 +385,18 @@ const ProfilePage = () => { {Auth?.username === user.username && ( <>
+ {/* ))} +
{GAMES.map((game, index) => { if (game === selectedGame) { @@ -603,6 +713,14 @@ const ProfilePage = () => { onClose={() => setUnapprovedPostsPopup(false)} /> )} + + {showFiltersPopup && ( + setShowFiltersPopup(false)} + selectedGame={selectedGame} + onSubmit={handleFiltersSubmit} + /> + )} ); }; diff --git a/src/web/src/Pages/user/ProfileSearch.tsx b/src/web/src/Pages/user/ProfileSearch.tsx new file mode 100644 index 00000000..845cc5d3 --- /dev/null +++ b/src/web/src/Pages/user/ProfileSearch.tsx @@ -0,0 +1,249 @@ +import React from 'react'; + +const ProfileSearch: React.FC = () => { + return ( +
+ {/* Profile banner */} +
+ Profile Banner +
+ + {/* Nav bar */} +
+
+ + Overview + + + Matches + + + Performance + + + Agents + + Maps + + Weapons + + + Customs + + + Crosshairs + + + Lineups + +
+
+ +
+ {/* PFP and username */} +
+ Player +
+

fractioned

+
+
+ + {/* Profile body */} +
+
+
+

Current Rating

+
+ Radiant Icon +

+ Radiant +

+
+
+
+

+ Peak Rating +

+
+ Radiant Icon +

+ Radiant +

+
+
+
+
+

E9: A3 Competitive Overview

+
+
+

+ Wins +

+

+ 111 +

+
+ +
+

+ KAST +

+

+ 75.4% +

+
+ +
+

+ DDΔ/Round +

+

29

+
+ +
+

+ Kills +

+

+ 2,836 +

+
+ +
+

+ Deaths +

+

+ 2,271 +

+
+
+ +
+
+

+ Assists +

+

+ 982 +

+
+ +
+

+ ACS +

+

+ 229.2 +

+
+ +
+

+ KAD Ratio +

+

+ 1.68 +

+
+ +
+

+ Kills/Round +

+

+ 0.8 +

+
+ +
+

+ First Bloods +

+

+ 290 +

+
+
+ +
+
+

+ Flawless Rounds +

+

+ 168 +

+
+ +
+

+ Aces +

+

5

+
+
+
+
+ +
+
+

Accuracy

+

+ Head +

+

+ Body +

+

+ Legs +

+
+
+

Top Agents

+

+ Agent +

+
+
+ +
+

Match History

+
+ Ascent + Kills: 25 | K/D: 1.5 + Win +
+
+ Breeze + Kills: 18 | K/D: 1.0 + Loss +
+
+ Bind + Kills: 20 | K/D: 1.2 + Win +
+
+
+
+ ); +}; + +export default ProfileSearch;