UPDATE: User page allows for profile image upload if on their own profile
This commit is contained in:
24
frontend/src/hooks/useSameUser.ts
Normal file
24
frontend/src/hooks/useSameUser.ts
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect, useState } from "react"
|
||||||
|
|
||||||
|
export function useSameUser({ username }: { username: string | undefined }) {
|
||||||
|
const [isSame, setIsSame] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchStatus = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/user/same/${username}`);
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error("Failed to validate user");
|
||||||
|
}
|
||||||
|
const data = await response.json();
|
||||||
|
setIsSame(data.same);
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Error:", error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchStatus();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return isSame;
|
||||||
|
}
|
||||||
@@ -11,6 +11,7 @@ import LoadingScreen from "../components/Layout/LoadingScreen";
|
|||||||
import { StreamListItem } from "../components/Layout/ListItem";
|
import { StreamListItem } from "../components/Layout/ListItem";
|
||||||
import { CameraIcon } from "lucide-react";
|
import { CameraIcon } from "lucide-react";
|
||||||
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
|
import { getCategoryThumbnail } from "../utils/thumbnailUtils";
|
||||||
|
import { useSameUser } from "../hooks/useSameUser";
|
||||||
|
|
||||||
interface UserProfileData {
|
interface UserProfileData {
|
||||||
id: number;
|
id: number;
|
||||||
@@ -36,16 +37,29 @@ const UserPage: React.FC = () => {
|
|||||||
const { showAuthModal, setShowAuthModal } = useAuthModal();
|
const { showAuthModal, setShowAuthModal } = useAuthModal();
|
||||||
const { username: loggedInUsername } = useAuth();
|
const { username: loggedInUsername } = useAuth();
|
||||||
const { username } = useParams();
|
const { username } = useParams();
|
||||||
const [isUser, setIsUser] = useState(true);
|
const isUser = useSameUser({username});
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const bgColors = {
|
// Saves uploaded image as profile picture for the user
|
||||||
personal: "",
|
const saveUploadedImage = async (event) => {
|
||||||
streamer:
|
const img = event.target.files[0];
|
||||||
"bg-gradient-radial from-[rgba(255, 0, 241, 0.5)] via-[rgba(4, 0, 255, 0.5)] to-[rgba(255, 0, 0, 0.5)]", // offline streamer
|
if (img) {
|
||||||
user: "bg-gradient-radial from-[rgba(255, 0, 241, 0.5)] via-[rgba(4, 0, 255, 0.5)] to-[rgba(255, 0, 241, 0.5)]",
|
const formData = new FormData();
|
||||||
admin:
|
formData.append('image', img);
|
||||||
"bg-gradient-to-r from-[rgba(255,100,100,0.5)] via-transparent to-[rgba(100,100,255,0.5)]",
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/user/profile_picture/upload', {
|
||||||
|
method: 'POST',
|
||||||
|
body: formData,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (response.ok) {
|
||||||
|
console.log("Success");
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log("Failure");
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -108,11 +122,7 @@ const UserPage: React.FC = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<DynamicPageContent
|
<DynamicPageContent
|
||||||
className={`min-h-screen ${/*
|
className={`min-h-screen text-white flex flex-col`}
|
||||||
profileData.isLive
|
|
||||||
? "bg-gradient-radial from-[#1a6600] via-[#66ff66] to-[#003900]"
|
|
||||||
: bgColors[userPageVariant]
|
|
||||||
*/ ""} text-white flex flex-col`}
|
|
||||||
>
|
>
|
||||||
<div className="flex justify-evenly justify-self-center items-center h-full px-4 py-8 max-w-[80vw] w-full">
|
<div className="flex justify-evenly justify-self-center items-center h-full px-4 py-8 max-w-[80vw] w-full">
|
||||||
<div className="grid grid-cols-4 grid-rows-[0.1fr_4fr] w-full gap-8">
|
<div className="grid grid-cols-4 grid-rows-[0.1fr_4fr] w-full gap-8">
|
||||||
@@ -138,18 +148,25 @@ const UserPage: React.FC = () => {
|
|||||||
{/* Profile Picture */}
|
{/* Profile Picture */}
|
||||||
<div
|
<div
|
||||||
className="relative -top-[40px] sm:-top-[90px] w-[16vw] h-[16vw] sm:w-[20vw] sm:h-[20vw] max-w-[10em] max-h-[10em]
|
className="relative -top-[40px] sm:-top-[90px] w-[16vw] h-[16vw] sm:w-[20vw] sm:h-[20vw] max-w-[10em] max-h-[10em]
|
||||||
rounded-full overflow-hidden flex-shrink-0 border-4 border-[var(--user-pfp-border)] inset-0 z-20"
|
rounded-full flex-shrink-0 border-4 border-[var(--user-pfp-border)] inset-0 z-20"
|
||||||
style={{ boxShadow: "var(--user-pfp-border-shadow)" }}
|
style={{ boxShadow: "var(--user-pfp-border-shadow)" }}
|
||||||
>
|
>
|
||||||
<label
|
<label
|
||||||
className={`relative ${isUser ? "cursor-pointer group" : ""}`}
|
className={`relative ${isUser ? "cursor-pointer group" : ""} overflow-visible`}
|
||||||
>
|
>
|
||||||
|
{/* If user is live then displays a live div */}
|
||||||
|
{Boolean(profileData.isLive) && (
|
||||||
|
<div className="absolute -bottom-2 left-1/2 transform -translate-x-1/2 bg-red-600 text-white text-sm font-bold py-1 sm:px-5 px-4 z-30 flex items-center justify-center rounded-tr-xl rounded-bl-xl rounded-tl-xl rounded-br-xl">
|
||||||
|
LIVE
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<img
|
<img
|
||||||
src="/images/monkey.png"
|
src="/images/monkey.png"
|
||||||
alt={`${profileData.username}'s profile`}
|
alt={`${profileData.username}'s profile`}
|
||||||
className="sm:w-full h-full object-cover rounded-full"
|
className="sm:w-full h-full object-cover rounded-full relative z-0"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
{/* If current user is the profile user then allow profile picture swap */}
|
||||||
{isUser && (
|
{isUser && (
|
||||||
<>
|
<>
|
||||||
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-200 rounded-full"></div>
|
<div className="absolute inset-0 bg-black/20 opacity-0 group-hover:opacity-100 transition-opacity duration-200 rounded-full"></div>
|
||||||
@@ -159,7 +176,11 @@ const UserPage: React.FC = () => {
|
|||||||
className="text-white bg-black/50 p-1 rounded-full"
|
className="text-white bg-black/50 p-1 rounded-full"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<input type="file" className="hidden" />
|
<input
|
||||||
|
type="file"
|
||||||
|
className="hidden"
|
||||||
|
onChange={saveUploadedImage}
|
||||||
|
accept="image/*" />
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</label>
|
</label>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
from flask import Blueprint, jsonify, session
|
from flask import Blueprint, jsonify, session, request
|
||||||
from utils.user_utils import *
|
from utils.user_utils import *
|
||||||
from utils.auth import *
|
from utils.auth import *
|
||||||
from utils.utils import get_category_id
|
from utils.utils import get_category_id
|
||||||
@@ -6,6 +6,9 @@ from blueprints.middleware import login_required
|
|||||||
from utils.email import send_email, forgot_password_body, newsletter_conf
|
from utils.email import send_email, forgot_password_body, newsletter_conf
|
||||||
import redis
|
import redis
|
||||||
|
|
||||||
|
from io import BytesIO
|
||||||
|
from PIL import Image
|
||||||
|
|
||||||
redis_url = "redis://redis:6379/1"
|
redis_url = "redis://redis:6379/1"
|
||||||
r = redis.from_url(redis_url, decode_responses=True)
|
r = redis.from_url(redis_url, decode_responses=True)
|
||||||
|
|
||||||
@@ -33,6 +36,31 @@ def user_stream_key(username: str):
|
|||||||
data = db.fetchone("SELECT stream_key FROM users WHERE user_id = ?", (user_id,))
|
data = db.fetchone("SELECT stream_key FROM users WHERE user_id = ?", (user_id,))
|
||||||
return jsonify({"stream_key": data["stream_key"]})
|
return jsonify({"stream_key": data["stream_key"]})
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@user_bp.route('/user/profile_picture/upload', methods=['POST'])
|
||||||
|
def user_profile_picture_save():
|
||||||
|
"""
|
||||||
|
Saves user profile picture
|
||||||
|
"""
|
||||||
|
user_id = session.get("user_id")
|
||||||
|
image = request.files['image']
|
||||||
|
ext = image.filename.split('.')[-1]
|
||||||
|
|
||||||
|
image.save(f"/web_server/stream_data/{user_id}.{ext}")
|
||||||
|
|
||||||
|
return "Success", 200
|
||||||
|
|
||||||
|
@login_required
|
||||||
|
@user_bp.route('/user/same/<string:username>')
|
||||||
|
def user_is_same(username):
|
||||||
|
"""
|
||||||
|
Returns if given user is current user
|
||||||
|
"""
|
||||||
|
current_username = session.get("username")
|
||||||
|
if username == current_username:
|
||||||
|
return jsonify({"same": True})
|
||||||
|
return jsonify({"same": False})
|
||||||
|
|
||||||
## Subscription Routes
|
## Subscription Routes
|
||||||
@login_required
|
@login_required
|
||||||
@user_bp.route('/user/subscription/<string:streamer_name>')
|
@user_bp.route('/user/subscription/<string:streamer_name>')
|
||||||
@@ -146,7 +174,7 @@ def user_login_status():
|
|||||||
'username': username,
|
'username': username,
|
||||||
'user_id': user_id})
|
'user_id': user_id})
|
||||||
|
|
||||||
@user_bp.route('/user/forgot_password/<string:email>', methods=['GET','POST'])
|
@user_bp.route('/user/forgot_password/<string:email>', methods=['POST'])
|
||||||
def user_forgot_password(email):
|
def user_forgot_password(email):
|
||||||
"""
|
"""
|
||||||
Initializes the function to handle password reset
|
Initializes the function to handle password reset
|
||||||
|
|||||||
@@ -28,4 +28,5 @@ flask-oauthlib==0.9.6
|
|||||||
celery==5.2.3
|
celery==5.2.3
|
||||||
redis==5.2.1
|
redis==5.2.1
|
||||||
python-dateutil
|
python-dateutil
|
||||||
Authlib==1.4.1
|
Authlib==1.4.1
|
||||||
|
Pillow==11.1.0
|
||||||
Reference in New Issue
Block a user