Skip to content

Commit 9e16c2e

Browse files
Add files via upload
1 parent f73a549 commit 9e16c2e

35 files changed

+6390
-0
lines changed

src/App.css

Lines changed: 439 additions & 0 deletions
Large diffs are not rendered by default.

src/App.js

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,120 @@
1+
import './App.css';
2+
import React, { useState, useEffect } from 'react';
3+
import { BrowserRouter as Router, Routes, Route, useLocation } from 'react-router-dom';
4+
import Home from './components/Home';
5+
import Services from './components/Services';
6+
import About from './components/About';
7+
import Contact from './components/Contact';
8+
import Blog from './components/Blog';
9+
import Auth from './components/Auth';
10+
import User from './components/User';
11+
import Admin from './components/Admin';
12+
import techbloggerLogo from '../src/techblogger.png';
13+
import Navbar from './Navbar';
14+
15+
function AnimatedMain({ isLoggedIn, username, onLogout }) {
16+
const location = useLocation();
17+
const [animate, setAnimate] = useState(true);
18+
19+
useEffect(() => {
20+
setAnimate(false);
21+
const timeout = setTimeout(() => setAnimate(true), 10);
22+
return () => clearTimeout(timeout);
23+
}, [location.pathname]);
24+
25+
return (
26+
<main className={animate ? 'route-fade-in' : ''}>
27+
<header className="header-container">
28+
<div className="logo-container">
29+
<a href="/"><img src={techbloggerLogo} alt="Tech Blogger Logo" className="logo-img" /></a>
30+
</div>
31+
<Navbar isLoggedIn={isLoggedIn} username={username} onLogout={onLogout} />
32+
</header>
33+
<Routes>
34+
<Route path="/" element={<Home />} />
35+
<Route path="/services" element={<Services />} />
36+
<Route path="/about" element={<About />} />
37+
<Route path="/contact" element={<Contact />} />
38+
<Route path="/blog" element={<Blog />} />
39+
<Route path="/auth" element={<Auth />} />
40+
<Route path="/user" element={<User />} />
41+
<Route path="/admin" element={<Admin />} />
42+
</Routes>
43+
</main>
44+
);
45+
}
46+
47+
function ScrollToTop() {
48+
useEffect(() => {
49+
window.scrollTo({ top: 0, behavior: 'smooth' });
50+
}, []);
51+
return null;
52+
}
53+
54+
function App() {
55+
const [showScrollBtn, setShowScrollBtn] = useState(false);
56+
const [scrollBtnVisible, setScrollBtnVisible] = useState(false);
57+
const [isLoggedIn, setIsLoggedIn] = useState(!!localStorage.getItem('username'));
58+
const [username, setUsername] = useState(localStorage.getItem('username') || '');
59+
const fadeDuration = 400; // ms, must match CSS
60+
61+
useEffect(() => {
62+
let timeout;
63+
const handleScroll = () => {
64+
if (window.scrollY > 80) {
65+
setShowScrollBtn(true);
66+
setScrollBtnVisible(true);
67+
if (timeout) clearTimeout(timeout);
68+
} else {
69+
setShowScrollBtn(false);
70+
timeout = setTimeout(() => setScrollBtnVisible(false), fadeDuration);
71+
}
72+
};
73+
window.addEventListener('scroll', handleScroll, { passive: true });
74+
// Check on mount in case already scrolled
75+
handleScroll();
76+
return () => {
77+
window.removeEventListener('scroll', handleScroll);
78+
clearTimeout(timeout);
79+
};
80+
}, []);
81+
82+
// Listen for login/logout changes (e.g., from Auth.js)
83+
useEffect(() => {
84+
const syncAuth = () => {
85+
const storedUsername = localStorage.getItem('username');
86+
setIsLoggedIn(!!storedUsername);
87+
setUsername(storedUsername || '');
88+
};
89+
window.addEventListener('storage', syncAuth);
90+
return () => window.removeEventListener('storage', syncAuth);
91+
}, []);
92+
93+
const handleLogout = () => {
94+
localStorage.removeItem('username');
95+
setIsLoggedIn(false);
96+
setUsername('');
97+
window.location.href = '/auth';
98+
};
99+
100+
return (
101+
<Router>
102+
<AnimatedMain isLoggedIn={isLoggedIn} username={username} onLogout={handleLogout} />
103+
<ScrollToTop />
104+
{scrollBtnVisible && (
105+
<button
106+
className={`scroll-to-top-btn ${showScrollBtn ? 'fade-in' : 'fade-out'}`}
107+
onClick={() => window.scrollTo({ top: 0, behavior: 'smooth' })}
108+
aria-label="Scroll to top"
109+
>
110+
&#8593;
111+
</button>
112+
)}
113+
<footer>
114+
<p>&copy; 2025 Tech Blogger</p>
115+
</footer>
116+
</Router>
117+
);
118+
}
119+
120+
export default App;

src/App.test.js

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import { render, screen } from '@testing-library/react';
2+
import App from './App';
3+
4+
test('renders learn react link', () => {
5+
render(<App />);
6+
const linkElement = screen.getByText(/learn react/i);
7+
expect(linkElement).toBeInTheDocument();
8+
});

src/Navbar.css

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
.border-none {
2+
border: none;
3+
cursor: pointer;
4+
font-weight: bold;
5+
font-size: 16px;
6+
text-decoration: none;
7+
transition: background-color 0.3s ease;
8+
padding: 15px 30px;
9+
}
10+
11+
.dropdown-menu {
12+
position: absolute;
13+
right: 0;
14+
margin-top: 12px;
15+
width: 180px;
16+
background: rgba(255, 255, 255, 0.95);
17+
color: #232336;
18+
border-radius: 12px;
19+
box-shadow: 0 8px 32px 0 rgba(123, 0, 255, 0.13);
20+
border: 1px solid rgba(123, 0, 255, 0.13);
21+
z-index: 100;
22+
overflow: hidden;
23+
animation: fadeInUp 0.3s cubic-bezier(0.23, 1, 0.32, 1);
24+
transition: background 0.3s, box-shadow 0.3s;
25+
}
26+
27+
.dropdown-menu.dark {
28+
background: rgba(30, 25, 49, 0.98);
29+
color: #f1f8ff;
30+
border: 1px solid rgba(123, 0, 255, 0.18);
31+
}
32+
33+
.dropdown-menu button {
34+
width: 100%;
35+
background: none;
36+
border: none;
37+
color: inherit;
38+
padding: 12px 20px;
39+
text-align: left;
40+
font-size: 1rem;
41+
font-weight: 500;
42+
cursor: pointer;
43+
transition: background 0.2s, color 0.2s;
44+
border-radius: 0;
45+
outline: none;
46+
}
47+
48+
.dropdown-menu button:first-child {
49+
border-top-left-radius: 12px;
50+
border-top-right-radius: 12px;
51+
}
52+
53+
.dropdown-menu button:last-child {
54+
border-bottom-left-radius: 12px;
55+
border-bottom-right-radius: 12px;
56+
}
57+
58+
.dropdown-menu button:hover {
59+
background: linear-gradient(90deg, #f3e8ff 0%, #e0e7ff 100%);
60+
color: #7B00FF;
61+
}
62+
63+
.dropdown-menu .logout {
64+
color: #e11d48;
65+
}
66+
67+
.dropdown-menu .logout:hover {
68+
background: linear-gradient(90deg, #ffe4e6 0%, #f3e8ff 100%);
69+
color: #b91c1c;
70+
}
71+
72+
@media (max-width: 600px) {
73+
.dropdown-menu {
74+
right: 8px;
75+
width: 90vw;
76+
min-width: 0;
77+
}
78+
}

src/Navbar.js

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
import React, { useState, useRef, useEffect } from 'react';
2+
import { Link, useNavigate } from 'react-router-dom';
3+
import './App.css';
4+
import './Navbar.css';
5+
6+
function Navbar({ isLoggedIn, username, onLogout }) {
7+
const [dropdownOpen, setDropdownOpen] = useState(false);
8+
const dropdownRef = useRef(null);
9+
const buttonRef = useRef(null);
10+
const navigate = useNavigate();
11+
12+
// Close dropdown when clicking outside
13+
useEffect(() => {
14+
function handleClickOutside(event) {
15+
if (
16+
dropdownRef.current &&
17+
!dropdownRef.current.contains(event.target) &&
18+
!buttonRef.current.contains(event.target)
19+
) {
20+
setDropdownOpen(false);
21+
}
22+
}
23+
if (dropdownOpen) {
24+
document.addEventListener('mousedown', handleClickOutside);
25+
} else {
26+
document.removeEventListener('mousedown', handleClickOutside);
27+
}
28+
return () => document.removeEventListener('mousedown', handleClickOutside);
29+
}, [dropdownOpen]);
30+
31+
const handleDashboard = () => {
32+
setDropdownOpen(false);
33+
const isUser = localStorage.getItem('is_user');
34+
if (isUser === 'true') {
35+
navigate('/user');
36+
} else {
37+
navigate('/admin');
38+
}
39+
};
40+
41+
const handleLogout = () => {
42+
setDropdownOpen(false);
43+
onLogout && onLogout();
44+
};
45+
46+
47+
return (
48+
<nav className="navbar">
49+
{/* Left: Navigation Links */}
50+
<ul className="nav-menu">
51+
<li><Link to="/">Home</Link></li>
52+
<li><Link to="/services">Services</Link></li>
53+
<li><Link to="/about">About Us</Link></li>
54+
<li><Link to="/contact">Contact</Link></li>
55+
<li><Link to="/blog">Blog</Link></li>
56+
</ul>
57+
{/* Right: Auth/User Button */}
58+
<div className="relative ml-auto">
59+
{!isLoggedIn ? (
60+
<Link
61+
to="/auth"
62+
className="primary-button1"
63+
>
64+
Get Started
65+
</Link>
66+
) : (
67+
<>
68+
<button
69+
ref={buttonRef}
70+
onClick={() => setDropdownOpen((open) => !open)}
71+
className="primary-button1 border-none"
72+
>
73+
<span className="nav-btn-text">{username}</span>
74+
</button>
75+
{dropdownOpen && (
76+
<div
77+
ref={dropdownRef}
78+
className="dropdown-menu"
79+
>
80+
<button
81+
onClick={handleDashboard}
82+
className="w-full text-left px-4 py-2"
83+
>
84+
My Dashboard
85+
</button>
86+
<button
87+
onClick={handleLogout}
88+
className="w-full text-left px-4 py-2 logout"
89+
>
90+
Logout
91+
</button>
92+
</div>
93+
)}
94+
</>
95+
)}
96+
</div>
97+
</nav>
98+
);
99+
}
100+
101+
export default Navbar;

0 commit comments

Comments
 (0)