UPDATE: Dynamically change profile picture on UserPage, without requiring page reload
This commit is contained in:
@@ -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>
|
||||||
|
|||||||
@@ -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">
|
||||||
|
|||||||
@@ -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);
|
||||||
|
|||||||
@@ -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) {
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user