UPDATE: Dynamically change profile picture on UserPage, without requiring page reload

This commit is contained in:
Chris-1010
2025-03-03 12:14:16 +00:00
parent 45b62a06fc
commit 3012e8ebd1
6 changed files with 112 additions and 116 deletions

View File

@@ -23,6 +23,7 @@ function App() {
const [userId, setUserId] = useState<number | null>(null); const [userId, setUserId] = useState<number | null>(null);
const [isLive, setIsLive] = useState(false); const [isLive, setIsLive] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);
const [profilePicture, setProfilePicture] = useState<string | null>(null);
useEffect(() => { useEffect(() => {
fetch("/api/user/login_status") fetch("/api/user/login_status")
@@ -53,10 +54,12 @@ function App() {
username, username,
userId, userId,
isLive, isLive,
profilePicture,
setIsLoggedIn, setIsLoggedIn,
setUsername, setUsername,
setUserId, setUserId,
setIsLive, setIsLive,
setProfilePicture,
}} }}
> >
<SidebarProvider> <SidebarProvider>

View File

@@ -23,7 +23,7 @@ interface SideBarProps {
const Sidebar: React.FC<SideBarProps> = ({ extraClasses = "" }) => { const Sidebar: React.FC<SideBarProps> = ({ extraClasses = "" }) => {
const { showSideBar, setShowSideBar } = useSidebar(); const { showSideBar, setShowSideBar } = useSidebar();
const navigate = useNavigate(); const navigate = useNavigate();
const { username, isLoggedIn } = useAuth(); const { username, isLoggedIn, profilePicture } = useAuth();
const [followedStreamers, setFollowedStreamers] = useState<Streamer[]>([]); const [followedStreamers, setFollowedStreamers] = useState<Streamer[]>([]);
const [followedCategories, setFollowedCategories] = useState<Category[]>([]); const [followedCategories, setFollowedCategories] = useState<Category[]>([]);
const [justToggled, setJustToggled] = useState(false); const [justToggled, setJustToggled] = useState(false);
@@ -92,13 +92,13 @@ const Sidebar: React.FC<SideBarProps> = ({ extraClasses = "" }) => {
{/* Profile Info */} {/* Profile Info */}
<div className="flex flex-row items-center border-b-4 border-[var(--profile-border)] justify-evenly bg-[var(--sideBar-profile-bg)] py-[1em]"> <div className="flex flex-row items-center border-b-4 border-[var(--profile-border)] justify-evenly bg-[var(--sideBar-profile-bg)] py-[1em]">
<img <img
src={`/user/${username}/profile_picture`} src={profilePicture || `/user/${username}/profile_picture`}
onError={(e) => { onError={(e) => {
e.currentTarget.src = "/images/pfps/default.png"; e.currentTarget.src = "/images/pfps/default.png";
e.currentTarget.onerror = null; e.currentTarget.onerror = null;
}} }}
alt="profile picture" alt="profile picture"
className="w-[3em] h-[3em] rounded-full border-[0.15em] border-purple-500 cursor-pointer" className="w-[3em] h-[3em] object-cover rounded-full border-[0.15em] border-purple-500 cursor-pointer"
onClick={() => navigate(`/user/${username}`)} onClick={() => navigate(`/user/${username}`)}
/> />
<div className="text-center flex flex-col items-center justify-center"> <div className="text-center flex flex-col items-center justify-center">

View File

@@ -5,10 +5,12 @@ interface AuthContextType {
username: string | null; username: string | null;
userId: number | null; userId: number | null;
isLive: boolean; isLive: boolean;
profilePicture: string | null;
setIsLoggedIn: (value: boolean) => void; setIsLoggedIn: (value: boolean) => void;
setUsername: (value: string | null) => void; setUsername: (value: string | null) => void;
setUserId: (value: number | null) => void; setUserId: (value: number | null) => void;
setIsLive: (value: boolean) => void; setIsLive: (value: boolean) => void;
setProfilePicture: (value: string | null) => void;
} }
export const AuthContext = createContext<AuthContextType | undefined>(undefined); export const AuthContext = createContext<AuthContextType | undefined>(undefined);

View File

@@ -78,8 +78,7 @@ export function useFetchContent<T>(
} }
const rawData = await response.json(); const rawData = await response.json();
let processedData = processor(Array.isArray(rawData) ? rawData : (rawData ? [rawData] : [])); let processedData = processor(Array.isArray(rawData) ? rawData : rawData ? [rawData] : []);
console.log("processedData", processedData);
setData(processedData); setData(processedData);
setError(null); setError(null);
} catch (err) { } catch (err) {

View File

@@ -4,28 +4,18 @@ import Button from "../components/Input/Button";
import ChromeDinoGame from "react-chrome-dino"; import ChromeDinoGame from "react-chrome-dino";
const NotFoundPage: React.FC = () => { const NotFoundPage: React.FC = () => {
const [stars, setStars] = useState< const [stars, setStars] = useState<{ x: number; y: number; xChange: number; yChange: number }[]>([]);
{ x: number; y: number; xChange: number; yChange: number }[]
>([]);
const starSize = 30; const starSize = 30;
const [score, setScore] = useState(0); const [score, setScore] = useState(0);
const [isGameOver, setIsGameOver] = useState(false); const [isGameOver, setIsGameOver] = useState(false);
useEffect(() => { useEffect(() => {
console.log("Game over state:", isGameOver);
const loop = setInterval(() => { const loop = setInterval(() => {
if (Math.random() < 0.1) { if (Math.random() < 0.1) {
const newStar = { const newStar = {
x: x: score > 20000 ? window.innerWidth + starSize : Math.random() * (window.innerWidth - starSize),
score > 20000 y: score > 20000 ? Math.random() * (window.innerHeight - starSize) : -starSize,
? window.innerWidth + starSize
: Math.random() * (window.innerWidth - starSize),
y:
score > 20000
? Math.random() * (window.innerHeight - starSize)
: -starSize,
xChange: score * 0.001, xChange: score * 0.001,
yChange: 5, yChange: 5,
}; };
@@ -34,10 +24,7 @@ const NotFoundPage: React.FC = () => {
setStars((prev) => { setStars((prev) => {
const newStars = prev.filter((star) => { const newStars = prev.filter((star) => {
if ( if (star.y > window.innerHeight - starSize && star.y < window.innerHeight) {
star.y > window.innerHeight - starSize &&
star.y < window.innerHeight
) {
return false; return false;
} }
if (star.y > window.innerHeight) return false; if (star.y > window.innerHeight) return false;
@@ -78,20 +65,12 @@ const NotFoundPage: React.FC = () => {
return ( return (
<div <div
className={`h-screen w-screen ${ className={`h-screen w-screen ${
score > 25000 score > 25000 ? "bg-black" : score > 10000 ? "bg-[#0f0024]" : "bg-slate-900"
? "bg-black"
: score > 10000
? "bg-[#0f0024]"
: "bg-slate-900"
} text-white overflow-hidden relative transition-colors duration-[5s]`} } text-white overflow-hidden relative transition-colors duration-[5s]`}
> >
<div> <div>
{stars.map((star, index) => ( {stars.map((star, index) => (
<div <div key={index} className="absolute w-5 h-5 text-yellow-300" style={{ left: `${star.x}px`, top: `${star.y}px` }}>
key={index}
className="absolute w-5 h-5 text-yellow-300"
style={{ left: `${star.x}px`, top: `${star.y}px` }}
>
</div> </div>
))} ))}
@@ -105,10 +84,7 @@ const NotFoundPage: React.FC = () => {
<h1 className="text-6xl font-bold mb-4">404</h1> <h1 className="text-6xl font-bold mb-4">404</h1>
<p className="text-2xl mb-8">Page Not Found</p> <p className="text-2xl mb-8">Page Not Found</p>
<ChromeDinoGame /> <ChromeDinoGame />
<Button <Button extraClasses="z-[100]" onClick={() => (window.location.href = "/")}>
extraClasses="z-[100]"
onClick={() => (window.location.href = "/")}
>
Go Home Go Home
</Button> </Button>
</div> </div>

View File

@@ -1,4 +1,4 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback, useRef } from "react";
import AuthModal from "../components/Auth/AuthModal"; import AuthModal from "../components/Auth/AuthModal";
import { useAuthModal } from "../hooks/useAuthModal"; import { useAuthModal } from "../hooks/useAuthModal";
import { useAuth } from "../context/AuthContext"; import { useAuth } from "../context/AuthContext";
@@ -27,7 +27,8 @@ const UserPage: React.FC = () => {
const [profileData, setProfileData] = useState<UserProfileData>(); const [profileData, setProfileData] = useState<UserProfileData>();
const { isFollowing, checkFollowStatus, followUser, unfollowUser } = useFollow(); const { isFollowing, checkFollowStatus, followUser, unfollowUser } = useFollow();
const { showAuthModal, setShowAuthModal } = useAuthModal(); const { showAuthModal, setShowAuthModal } = useAuthModal();
const { username: loggedInUsername } = useAuth(); const { username: loggedInUsername, profilePicture, setProfilePicture } = useAuth();
const initialProfilePicture = useRef(profilePicture);
const { username } = useParams(); const { username } = useParams();
const { vods } = useVods(`/api/vods/${username}`); const { vods } = useVods(`/api/vods/${username}`);
const navigate = useNavigate(); const navigate = useNavigate();
@@ -70,7 +71,8 @@ const UserPage: React.FC = () => {
if (response.ok) { if (response.ok) {
console.log("Success"); console.log("Success");
window.location.reload(); console.log(URL.createObjectURL(img))
setProfilePicture(URL.createObjectURL(img));
} }
} catch (error) { } catch (error) {
console.log("Failure"); console.log("Failure");
@@ -78,6 +80,21 @@ const UserPage: React.FC = () => {
} }
}; };
const handleNavigation = (path: string) => {
if (profilePicture === initialProfilePicture.current) {
// Variable hasn't changed - use React Router navigation
navigate(path);
} else {
// Variable has changed - use full page reload
window.location.href = path;
}
};
// Store initial profile picture to know if it changes later
useEffect(() => {
initialProfilePicture.current = profilePicture;
}, []);
// Check if the current user is the currently logged-in user // Check if the current user is the currently logged-in user
useEffect(() => { useEffect(() => {
if (username === loggedInUsername) setUserPageVariant("personal"); if (username === loggedInUsername) setUserPageVariant("personal");
@@ -138,9 +155,8 @@ const UserPage: React.FC = () => {
"" ""
)} )}
<img <img
src={`/user/${profileData.username}/profile_picture`} src={userPageVariant === "personal" && profilePicture ? profilePicture : `/user/${profileData.username}/profile_picture`}
onError={(e) => { onError={(e) => {
console.log("no error")
e.currentTarget.src = "/images/pfps/default.png"; e.currentTarget.src = "/images/pfps/default.png";
e.currentTarget.onerror = null; e.currentTarget.onerror = null;
}} }}
@@ -216,7 +232,7 @@ const UserPage: React.FC = () => {
viewers={currentStream.viewers || 0} viewers={currentStream.viewers || 0}
thumbnail={currentStream.thumbnail} thumbnail={currentStream.thumbnail}
onItemClick={() => { onItemClick={() => {
navigate(`/${profileData.username}`); handleNavigation(`/${profileData.username}`);
}} }}
/> />
</div> </div>
@@ -253,7 +269,7 @@ const UserPage: React.FC = () => {
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "var(--follow-shadow)")} onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "var(--follow-shadow)")}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")}
> >
<button className="text-[var(--follow-text)] whitespace-pre-wrap" onClick={() => navigate(`/user/${username}/following`)}> <button className="text-[var(--follow-text)] whitespace-pre-wrap" onClick={() => handleNavigation(`/user/${username}/following`)}>
Following Following
</button> </button>
</div> </div>
@@ -273,7 +289,7 @@ const UserPage: React.FC = () => {
onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "var(--follow-shadow)")} onMouseEnter={(e) => (e.currentTarget.style.boxShadow = "var(--follow-shadow)")}
onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")} onMouseLeave={(e) => (e.currentTarget.style.boxShadow = "none")}
> >
<button onClick={() => navigate(`/user/${username}/followedCategories`)}>Categories</button> <button onClick={() => handleNavigation(`/user/${username}/followedCategories`)}>Categories</button>
</div> </div>
</div> </div>
</div> </div>