Major: Added Authentication Functionality, interfaces with backend;
Added new styles to HomePage & VideoPage; Added AuthContext to persist logged_in status;
This commit is contained in:
49
frontend/src/components/Auth/AuthModal.tsx
Normal file
49
frontend/src/components/Auth/AuthModal.tsx
Normal file
@@ -0,0 +1,49 @@
|
||||
import React, { useState } from "react";
|
||||
import { ToggleButton } from "../Layout/Button";
|
||||
import { LogIn as LogInIcon, User as UserIcon } from "lucide-react";
|
||||
import LoginForm from "./LoginForm";
|
||||
import RegisterForm from "./RegisterForm";
|
||||
|
||||
interface AuthModalProps {
|
||||
onClose: () => void;
|
||||
}
|
||||
|
||||
const AuthModal: React.FC<AuthModalProps> = ({ onClose }) => {
|
||||
const [selectedTab, setSelectedTab] = useState<string>("Login");
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="blurring-layer fixed z-10 inset-0 w-screen h-screen backdrop-blur-sm group-has-[input:focus]:backdrop-blur-[5px]"></div>
|
||||
|
||||
<div className="modal-container fixed inset-0 bg-black/30 has-[input:focus]:bg-black/40 flex flex-col items-center justify-around z-50 h-[70vh] m-auto min-w-[40vw] w-fit py-[50px] rounded-[2rem] transition-all">
|
||||
<div className="login-methods w-full flex flex-row items-center justify-evenly">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-[1rem] right-[2rem] text-[2rem] text-white hover:text-red-500 font-black hover:text-[2.5rem] transition-all"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<ToggleButton
|
||||
toggled={selectedTab === "Login"}
|
||||
extraClasses="flex flex-col items-center px-8"
|
||||
onClick={() => setSelectedTab("Login")}
|
||||
>
|
||||
<LogInIcon className="h-[40px] w-[40px] mr-1" />
|
||||
Login
|
||||
</ToggleButton>
|
||||
<ToggleButton
|
||||
toggled={selectedTab === "Register"}
|
||||
extraClasses="flex flex-col items-center px-8"
|
||||
onClick={() => setSelectedTab("Register")}
|
||||
>
|
||||
<UserIcon className="h-[40px] w-[40px] mr-1" />
|
||||
Register
|
||||
</ToggleButton>
|
||||
</div>
|
||||
{selectedTab === "Login" ? <LoginForm /> : <RegisterForm />}
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default AuthModal;
|
||||
@@ -1 +1,127 @@
|
||||
// login.html
|
||||
import React, { useState } from "react";
|
||||
import Input from "../Layout/Input";
|
||||
import Button from "../Layout/Button";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
|
||||
interface LoginFormData {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
username?: string;
|
||||
password?: string;
|
||||
general?: string; // For general authentication errors
|
||||
}
|
||||
|
||||
const LoginForm: React.FC = () => {
|
||||
const { setIsLoggedIn } = useAuth();
|
||||
|
||||
const [formData, setFormData] = useState<LoginFormData>({
|
||||
username: "",
|
||||
password: "",
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
|
||||
// Check for empty fields
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (!formData[key as keyof LoginFormData]) {
|
||||
newErrors[key as keyof FormErrors] = "This field is required";
|
||||
}
|
||||
});
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (validateForm()) {
|
||||
try {
|
||||
const response = await fetch("/api/login", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(data.message || "Login failed");
|
||||
}
|
||||
|
||||
if (data.logged_in) {
|
||||
//TODO: Handle successful login (e.g., redirect to home page)
|
||||
console.log("Login successful");
|
||||
setIsLoggedIn(true);
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Handle authentication errors
|
||||
if (data.errors) {
|
||||
setErrors({
|
||||
general: "Invalid username or password",
|
||||
});
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Login error:", error);
|
||||
setErrors({
|
||||
general: "An error occurred during login. Please try again.",
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="login-form h-[100%] flex flex-col h-full justify-evenly items-center"
|
||||
>
|
||||
{errors.general && (
|
||||
<p className="text-red-500 text-sm text-center">{errors.general}</p>
|
||||
)}
|
||||
|
||||
{errors.username && (
|
||||
<p className="text-red-500 mt-3 text-sm">{errors.username}</p>
|
||||
)}
|
||||
<Input
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
value={formData.username}
|
||||
onChange={handleInputChange}
|
||||
extraClasses={`${errors.username ? "border-red-500" : ""}`}
|
||||
/>
|
||||
|
||||
{errors.password && (
|
||||
<p className="text-red-500 mt-3 text-sm">{errors.password}</p>
|
||||
)}
|
||||
<Input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
extraClasses={`${errors.password ? "border-red-500" : ""}`}
|
||||
/>
|
||||
|
||||
<Button type="submit">Login</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default LoginForm;
|
||||
|
||||
155
frontend/src/components/Auth/RegisterForm.tsx
Normal file
155
frontend/src/components/Auth/RegisterForm.tsx
Normal file
@@ -0,0 +1,155 @@
|
||||
import React, { useState } from "react";
|
||||
import Input from "../Layout/Input";
|
||||
import Button from "../Layout/Button";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
|
||||
interface RegisterFormData {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
confirmPassword: string;
|
||||
}
|
||||
|
||||
interface FormErrors {
|
||||
username?: string;
|
||||
email?: string;
|
||||
password?: string;
|
||||
confirmPassword?: string;
|
||||
}
|
||||
|
||||
const RegisterForm: React.FC = () => {
|
||||
const { setIsLoggedIn } = useAuth();
|
||||
|
||||
const [formData, setFormData] = useState<RegisterFormData>({
|
||||
username: "",
|
||||
email: "",
|
||||
password: "",
|
||||
confirmPassword: "",
|
||||
});
|
||||
const [errors, setErrors] = useState<FormErrors>({});
|
||||
|
||||
const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
|
||||
const { name, value } = e.target;
|
||||
setFormData((prev) => ({
|
||||
...prev,
|
||||
[name]: value,
|
||||
}));
|
||||
};
|
||||
|
||||
const validateForm = (): boolean => {
|
||||
const newErrors: FormErrors = {};
|
||||
|
||||
// Check for empty fields
|
||||
Object.keys(formData).forEach((key) => {
|
||||
if (!formData[key as keyof RegisterFormData]) {
|
||||
newErrors[key as keyof FormErrors] = "This field is required";
|
||||
}
|
||||
});
|
||||
|
||||
// Check password match
|
||||
if (formData.password !== formData.confirmPassword) {
|
||||
newErrors.confirmPassword = "Passwords do not match";
|
||||
}
|
||||
|
||||
setErrors(newErrors);
|
||||
return Object.keys(newErrors).length === 0;
|
||||
};
|
||||
|
||||
const handleSubmit = async (e: React.FormEvent) => {
|
||||
e.preventDefault();
|
||||
|
||||
if (validateForm()) {
|
||||
try {
|
||||
const response = await fetch("/api/signup", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
credentials: "include",
|
||||
body: JSON.stringify(formData),
|
||||
});
|
||||
console.log(`sending data: ${JSON.stringify(formData)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(
|
||||
data.message || `Registration failed. ${response.body}`
|
||||
);
|
||||
}
|
||||
|
||||
if (data.account_created) {
|
||||
//TODO Handle successful registration (e.g., redirect or show success message)
|
||||
console.log("Registration Successful! Account created successfully");
|
||||
setIsLoggedIn(true);
|
||||
window.location.reload();
|
||||
} else {
|
||||
// Handle validation errors from server
|
||||
const serverErrors: FormErrors = {};
|
||||
if (data.errors) {
|
||||
Object.entries(data.errors).forEach(([field, message]) => {
|
||||
serverErrors[field as keyof FormErrors] = message as string;
|
||||
});
|
||||
setErrors(serverErrors);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Registration error:", error);
|
||||
//TODO Show user-friendly error message via Alert component maybe
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={handleSubmit}
|
||||
className="register-form h-[100%] flex flex-col h-full justify-evenly items-center"
|
||||
>
|
||||
{errors.username && (
|
||||
<p className="text-red-500 mt-3 text-sm">{errors.username}</p>
|
||||
)}
|
||||
<Input
|
||||
name="username"
|
||||
placeholder="Username"
|
||||
value={formData.username}
|
||||
onChange={handleInputChange}
|
||||
extraClasses={`${errors.username ? "border-red-500" : ""}`}
|
||||
/>
|
||||
{errors.email && (
|
||||
<p className="text-red-500 mt-3 text-sm">{errors.email}</p>
|
||||
)}
|
||||
<Input
|
||||
name="email"
|
||||
type="email"
|
||||
placeholder="Email"
|
||||
value={formData.email}
|
||||
onChange={handleInputChange}
|
||||
extraClasses={`${errors.email ? "border-red-500" : ""}`}
|
||||
/>
|
||||
{errors.password && (
|
||||
<p className="text-red-500 mt-3 text-sm">{errors.password}</p>
|
||||
)}
|
||||
<Input
|
||||
name="password"
|
||||
type="password"
|
||||
placeholder="Password"
|
||||
value={formData.password}
|
||||
onChange={handleInputChange}
|
||||
extraClasses={`${errors.password ? "border-red-500" : ""}`}
|
||||
/>
|
||||
{errors.confirmPassword && (
|
||||
<p className="text-red-500 mt-3 text-sm">{errors.confirmPassword}</p>
|
||||
)}
|
||||
<Input
|
||||
name="confirmPassword"
|
||||
type="password"
|
||||
placeholder="Confirm Password"
|
||||
value={formData.confirmPassword}
|
||||
onChange={handleInputChange}
|
||||
extraClasses={`${errors.confirmPassword ? "border-red-500" : ""}`}
|
||||
/>
|
||||
<Button type="submit">Register</Button>
|
||||
</form>
|
||||
);
|
||||
};
|
||||
|
||||
export default RegisterForm;
|
||||
@@ -11,7 +11,6 @@ const API_URL = import.meta.env.VITE_API_URL;
|
||||
// Initialize Stripe once
|
||||
const stripePromise = loadStripe(import.meta.env.VITE_STRIPE_PUBLISHABLE_KEY);
|
||||
|
||||
|
||||
export const Return: React.FC = () => {
|
||||
const [status, setStatus] = useState<string | null>(null);
|
||||
const [customerEmail, setCustomerEmail] = useState("");
|
||||
@@ -69,18 +68,18 @@ const CheckoutForm: React.FC<CheckoutFormProps> = ({ onClose }) => {
|
||||
return (
|
||||
<>
|
||||
<div className="blurring-layer fixed z-10 inset-0 w-screen h-screen backdrop-blur-sm"></div>
|
||||
<div className="fixed inset-0 bg-black/30 flex items-center justify-center z-50 h-[70vh] m-auto w-fit py-[50px] px-[100px] rounded-[2rem]">
|
||||
<div className="modal-container fixed inset-0 bg-black/30 flex items-center justify-center z-50 h-[70vh] m-auto w-fit py-[50px] px-[100px] rounded-[2rem]">
|
||||
<button
|
||||
onClick={onClose}
|
||||
className="absolute top-[1rem] right-[3rem] text-[2rem] text-red-600 font-black hover:text-[2.5rem] transition-all"
|
||||
className="absolute top-[1rem] right-[3rem] text-[2rem] text-white hover:text-red-500 font-black hover:text-[2.5rem] transition-all"
|
||||
>
|
||||
✕
|
||||
</button>
|
||||
<div className="bg-white p-6 rounded-lg w-full max-w-2xl relative h-full rounded-[2rem]" style={{ width: "clamp(300px, 60vw, 800px)" }}>
|
||||
<div
|
||||
id="checkout"
|
||||
className="h-full overflow-auto min-w-[30vw]"
|
||||
>
|
||||
<div
|
||||
className="bg-white p-6 rounded-lg w-full max-w-2xl relative h-full rounded-[2rem]"
|
||||
style={{ width: "clamp(300px, 60vw, 800px)" }}
|
||||
>
|
||||
<div id="checkout" className="h-full overflow-auto min-w-[30vw]">
|
||||
<EmbeddedCheckoutProvider stripe={stripePromise} options={options}>
|
||||
<EmbeddedCheckout />
|
||||
</EmbeddedCheckoutProvider>
|
||||
@@ -91,4 +90,4 @@ const CheckoutForm: React.FC<CheckoutFormProps> = ({ onClose }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export default CheckoutForm;
|
||||
export default CheckoutForm;
|
||||
|
||||
@@ -1,18 +1,50 @@
|
||||
import React from "react";
|
||||
|
||||
interface ButtonProps {
|
||||
title?: string;
|
||||
type?: "button" | "submit" | "reset";
|
||||
extraClasses?: string;
|
||||
children?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
}
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({
|
||||
title = "Submit",
|
||||
type = "button",
|
||||
children = "Submit",
|
||||
extraClasses = "",
|
||||
onClick,
|
||||
}) => {
|
||||
return (
|
||||
<button
|
||||
type={type}
|
||||
className={`${extraClasses} p-2 text-[1.5rem] text-white hover:text-purple-600 bg-black/30 hover:bg-black/80 rounded-md border border-gray-300 hover:border-purple-500 hover:border-b-4 hover:border-l-4 active:border-b-2 active:border-l-2 transition-all`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
interface ToggleButtonProps extends ButtonProps {
|
||||
toggled?: boolean;
|
||||
}
|
||||
|
||||
export const ToggleButton: React.FC<ToggleButtonProps> = ({
|
||||
children = "Toggle",
|
||||
extraClasses = "",
|
||||
onClick,
|
||||
toggled = false,
|
||||
}) => {
|
||||
toggled
|
||||
? (extraClasses += " cursor-default bg-purple-600")
|
||||
: (extraClasses +=
|
||||
" cursor-pointer hover:text-purple-600 hover:bg-black/80 hover:border-purple-500 hover:border-b-4 hover:border-l-4");
|
||||
return (
|
||||
<div>
|
||||
<button className="underline bg-blue-600/30 p-4 rounded-[5rem] transition-all hover:text-[3.2rem]" onClick={onClick}>
|
||||
{title}
|
||||
<button
|
||||
className={`${extraClasses} p-2 text-[1.5rem] text-white bg-black/30 rounded-[1rem] border border-gray-300 transition-all`}
|
||||
onClick={onClick}
|
||||
>
|
||||
{children}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
|
||||
29
frontend/src/components/Layout/Input.tsx
Normal file
29
frontend/src/components/Layout/Input.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from "react";
|
||||
|
||||
interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||
extraClasses?: string;
|
||||
}
|
||||
|
||||
const Input: React.FC<InputProps> = ({
|
||||
name,
|
||||
type = "text",
|
||||
placeholder = "",
|
||||
value = "",
|
||||
extraClasses = "",
|
||||
onChange = () => {},
|
||||
...props // all other HTML input props
|
||||
}) => {
|
||||
return (
|
||||
<input
|
||||
name={name}
|
||||
type={type}
|
||||
placeholder={placeholder}
|
||||
value={value}
|
||||
onChange={onChange}
|
||||
{...props}
|
||||
className={`${extraClasses} p-2 rounded-[1rem] w-[20vw] focus:w-[120%] bg-black/40 border border-gray-300 focus:border-purple-500 focus:outline-purple-500 text-center text-white text-xl transition-all`}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
||||
export default Input;
|
||||
@@ -17,20 +17,20 @@ interface ListRowProps {
|
||||
title: string;
|
||||
description: string;
|
||||
streams: StreamItem[];
|
||||
onStreamClick?: (streamId: string) => void;
|
||||
onStreamClick?: (streamerId: string) => void;
|
||||
}
|
||||
|
||||
// Individual stream entry component
|
||||
const ListEntry: React.FC<ListEntryProps> = ({ stream, onClick }) => {
|
||||
return (
|
||||
<div
|
||||
className="flex flex-col bg-gray-800 rounded-lg overflow-hidden cursor-pointer hover:bg-gray-700 transition-colors"
|
||||
className="flex flex-col bg-gray-800 rounded-lg overflow-hidden cursor-pointer hover:bg-gray-700 border border-gray-100 hover:border-purple-500 hover:border-b-4 hover:border-l-4 transition-all"
|
||||
onClick={onClick}
|
||||
>
|
||||
<div className="relative w-full pt-[56.25%]">
|
||||
{stream.thumbnail ? (
|
||||
<img
|
||||
src={`images/`+stream.thumbnail}
|
||||
src={`images/` + stream.thumbnail}
|
||||
alt={stream.title}
|
||||
className="absolute top-0 left-0 w-full h-full object-cover"
|
||||
/>
|
||||
@@ -55,7 +55,7 @@ const ListRow: React.FC<ListRowProps> = ({
|
||||
onStreamClick,
|
||||
}) => {
|
||||
return (
|
||||
<div className="flex flex-col space-y-4 py-6">
|
||||
<div className="flex flex-col space-y-4 py-6 mx-4">
|
||||
<div className="space-y-1">
|
||||
<h2 className="text-2xl font-bold">{title}</h2>
|
||||
<p className="text-gray-400">{description}</p>
|
||||
@@ -65,7 +65,7 @@ const ListRow: React.FC<ListRowProps> = ({
|
||||
<ListEntry
|
||||
key={stream.id}
|
||||
stream={stream}
|
||||
onClick={() => onStreamClick?.(stream.id)}
|
||||
onClick={() => onStreamClick?.(stream.streamer)}
|
||||
/>
|
||||
))}
|
||||
</div>
|
||||
|
||||
@@ -1,18 +1,25 @@
|
||||
import React from "react";
|
||||
import { Link } from "react-router-dom";
|
||||
|
||||
const Logo: React.FC = () => {
|
||||
const gradient =
|
||||
"bg-gradient-to-br from-yellow-400 via-red-500 to-indigo-500 text-transparent bg-clip-text group-hover:mx-1 transition-all";
|
||||
return (
|
||||
<div className="text-center text-[12vh] font-bold text-orange-500">
|
||||
<h6 className="text-sm">Go on, have a...</h6>
|
||||
<div className="flex justify-center items-center space-x-6">
|
||||
<span>G</span>
|
||||
<span>A</span>
|
||||
<span>N</span>
|
||||
<span>D</span>
|
||||
<span>E</span>
|
||||
<span>R</span>
|
||||
<Link to="/" className="cursor-pointer">
|
||||
<div className="logo group py-3 text-center text-[12vh] font-bold hover:scale-110 transition-all">
|
||||
<h6 className="text-sm bg-gradient-to-br from-blue-400 via-green-500 to-indigo-500 font-black text-transparent bg-clip-text">
|
||||
Go on, have a...
|
||||
</h6>
|
||||
<div className="flex w-fit min-w-[30vw] justify-evenly leading-none transition-all">
|
||||
<span className={gradient}>G</span>
|
||||
<span className={gradient}>A</span>
|
||||
<span className={gradient}>N</span>
|
||||
<span className={gradient}>D</span>
|
||||
<span className={gradient}>E</span>
|
||||
<span className={gradient}>R</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,56 +1,90 @@
|
||||
import React from "react";
|
||||
import React, { useState, useEffect } from "react";
|
||||
import Logo from "./Logo";
|
||||
import Button from "./Button";
|
||||
import { Link } from "react-router-dom";
|
||||
import { Search, User, LogIn } from "lucide-react";
|
||||
import Sidebar from "./Sidebar";
|
||||
import { Sidebar as SidebarIcon } from "lucide-react";
|
||||
import {
|
||||
Search as SearchIcon,
|
||||
LogIn as LogInIcon,
|
||||
LogOut as LogOutIcon,
|
||||
Settings as SettingsIcon,
|
||||
} from "lucide-react";
|
||||
import Input from "./Input";
|
||||
import AuthModal from "../Auth/AuthModal";
|
||||
import { useAuth } from "../../context/AuthContext";
|
||||
|
||||
interface NavbarProps {
|
||||
logged_in: boolean;
|
||||
}
|
||||
const Navbar: React.FC = () => {
|
||||
const [showAuthModal, setShowAuthModal] = useState(false);
|
||||
const { isLoggedIn } = useAuth();
|
||||
|
||||
useEffect(() => {
|
||||
if (showAuthModal) {
|
||||
document.body.style.overflow = "hidden";
|
||||
} else {
|
||||
document.body.style.overflow = "unset";
|
||||
}
|
||||
return () => {
|
||||
document.body.style.overflow = "unset";
|
||||
};
|
||||
}, [showAuthModal]);
|
||||
|
||||
const handleLogout = () => {
|
||||
console.log("Logging out...");
|
||||
fetch("/api/logout")
|
||||
.then((response) => response.json())
|
||||
.then((data) => {
|
||||
console.log(data);
|
||||
window.location.reload();
|
||||
});
|
||||
};
|
||||
|
||||
const Navbar: React.FC<NavbarProps> = ({ logged_in }) => {
|
||||
return (
|
||||
<div className="flex flex-col justify-around items-center h-[45vh]">
|
||||
<Logo />
|
||||
<div className="nav-buttons flex items-center space-x-4">
|
||||
{logged_in ? (
|
||||
<div>
|
||||
<Link
|
||||
to="/logout"
|
||||
className="flex items-center text-gray-700 hover:text-purple-600"
|
||||
>
|
||||
<Button title="" />
|
||||
</Link>
|
||||
</div>
|
||||
<Button
|
||||
extraClasses="absolute top-[20px] left-[20px] text-[1rem] flex items-center flex-nowrap"
|
||||
onClick={() => (isLoggedIn ? handleLogout() : setShowAuthModal(true))}
|
||||
>
|
||||
{isLoggedIn ? (
|
||||
<>
|
||||
<LogOutIcon className="h-15 w-15 mr-1" />
|
||||
Logout
|
||||
</>
|
||||
) : (
|
||||
<div>
|
||||
<Link
|
||||
to="/login"
|
||||
className="flex items-center text-gray-700 hover:text-purple-600"
|
||||
>
|
||||
<LogIn className="h-5 w-5 mr-1" />
|
||||
Login
|
||||
</Link>
|
||||
<Link
|
||||
to="/signup"
|
||||
className="flex items-center text-gray-700 hover:text-purple-600"
|
||||
>
|
||||
<User className="h-5 w-5 mr-1" />
|
||||
Sign Up
|
||||
</Link>
|
||||
</div>
|
||||
<>
|
||||
<LogInIcon className="h-15 w-15 mr-1" />
|
||||
Login / Register
|
||||
</>
|
||||
)}
|
||||
<Button title="Quick Settings" />
|
||||
</div>
|
||||
</Button>
|
||||
|
||||
<div className="search-bar relative">
|
||||
<input
|
||||
{isLoggedIn && (
|
||||
<>
|
||||
<Button extraClasses="absolute top-[75px] left-[20px]">
|
||||
<SidebarIcon className="h-15 w-15 mr-1" />
|
||||
</Button>
|
||||
<Sidebar />
|
||||
</>
|
||||
)}
|
||||
|
||||
<Button
|
||||
extraClasses="absolute top-[20px] right-[20px] text-[1rem] flex items-center flex-nowrap"
|
||||
onClick={() => console.log("Settings")}
|
||||
>
|
||||
<SettingsIcon className="h-15 w-15 mr-1" />
|
||||
Quick Settings
|
||||
</Button>
|
||||
|
||||
<div className="search-bar flex items-center">
|
||||
<Input
|
||||
type="text"
|
||||
placeholder="Search..."
|
||||
className="w-64 px-4 py-2 rounded-lg border border-gray-300 focus:outline-none focus:border-purple-500"
|
||||
extraClasses="pr-[30px] focus:outline-none focus:border-purple-500 focus:w-[30vw]"
|
||||
/>
|
||||
<Search className="absolute right-3 top-2.5 h-5 w-5 text-gray-400" />
|
||||
<SearchIcon className="-translate-x-[28px] top-1/2 h-6 w-6 text-white" />
|
||||
</div>
|
||||
|
||||
{showAuthModal && <AuthModal onClose={() => setShowAuthModal(false)} />}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
7
frontend/src/components/Layout/Sidebar.tsx
Normal file
7
frontend/src/components/Layout/Sidebar.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import React from "react";
|
||||
|
||||
const Sidebar: React.FC = () => {
|
||||
return <div></div>;
|
||||
};
|
||||
|
||||
export default Sidebar;
|
||||
Reference in New Issue
Block a user