Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/dashboard/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,7 @@ export default function DashboardLayout({
name: user.name,
email: user.email,
role: user.role,
profilePicture: user.profile_picture,
}
: undefined
}
Expand Down
2 changes: 2 additions & 0 deletions app/login/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { Button } from "@/components/registration/button";
import { StickyAlert } from "@/components/registration/sticky-alert";
import { RecaptchaNotice } from "@/components/registration/recaptcha-notice";
import { DotPattern } from "@/components/registration/dot-pattern";
import { AuthHeader } from "@/components/registration/auth-header";
import { Spinner } from "@/components/ui/spinner";
import { ArrowRight, LogIn } from "lucide-react";

Expand Down Expand Up @@ -51,6 +52,7 @@ export default function LoginPage() {
return (
<div className="min-h-screen w-full bg-void relative overflow-hidden">
<DotPattern />
<AuthHeader />

<main className="relative z-10 min-h-screen w-full flex flex-col items-center justify-center px-4 sm:px-6 py-10 sm:py-16">
<div className="w-full max-w-[460px] flex flex-col gap-6 anim-fade-up">
Expand Down
4 changes: 3 additions & 1 deletion app/register/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from "next/navigation";
import { useAuth } from "@/hooks/use-auth";
import { RegistrationContainer } from "@/components/registration/registration-container";
import { DotPattern } from "@/components/registration/dot-pattern";
import { AuthHeader } from "@/components/registration/auth-header";
import { Spinner } from "@/components/ui/spinner";

export default function RegisterPage() {
Expand All @@ -26,8 +27,9 @@ export default function RegisterPage() {
return (
<div className="min-h-screen w-full bg-void relative overflow-hidden">
<DotPattern />
<AuthHeader />

<main className="relative z-10 w-full min-h-screen flex flex-col items-center px-4 sm:px-6 md:px-8 py-10 sm:py-14">
<main className="relative z-10 w-full min-h-screen flex flex-col items-center px-4 sm:px-6 md:px-8 pt-24 pb-10 sm:pt-28 sm:pb-14">
<div className="w-full max-w-[1000px] flex flex-col gap-7 items-center anim-fade-up">
<RegistrationContainer />
</div>
Expand Down
18 changes: 15 additions & 3 deletions components/landing/FinalCTA/FinalCTA.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,14 @@ import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
import { useGSAP } from '@gsap/react';
import { useRetroSound } from '../hooks/useRetroSound';
import { useAuth } from '@/hooks/use-auth';
import './FinalCTA.css';

gsap.registerPlugin(ScrollTrigger);

export default function FinalCTA() {
const sectionRef = useRef(null);
const { isAuthenticated } = useAuth();
const { playHover, playClick } = useRetroSound();

useGSAP(() => {
Expand Down Expand Up @@ -42,13 +44,23 @@ export default function FinalCTA() {
</p>
<div className="final-cta__actions">
<a
href="/register"
className="btn btn--primary"
href={isAuthenticated ? '/dashboard' : '/register'}
className={`btn ${isAuthenticated ? 'btn--secondary' : 'btn--primary'}`}
id="cta-register-now"
onMouseEnter={playHover}
onClick={playClick}
>
Register Now
{isAuthenticated ? (
<>
Access Granted
<svg className="btn__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<line x1="5" y1="12" x2="19" y2="12" />
<polyline points="12 5 19 12 12 19" />
</svg>
</>
) : (
'Register Now'
)}
</a>

</div>
Expand Down
121 changes: 121 additions & 0 deletions components/landing/Header/Header.css
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,122 @@
transform: translateY(-1px);
}

/* --- Authed user controls (avatar tile + logout) --- */
.pbctf-landing .header__user {
display: none;
align-items: center;
gap: var(--space-3);
}

.pbctf-landing .header__user-tile {
display: inline-flex;
align-items: center;
gap: var(--space-2);
height: 36px;
padding: 0 var(--space-3) 0 4px;
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
text-decoration: none;
transition: background var(--duration-normal) var(--ease-out),
border-color var(--duration-normal) var(--ease-out),
box-shadow var(--duration-normal) var(--ease-out);
}

.pbctf-landing .header__user-tile:hover {
background: rgba(0, 255, 136, 0.08);
border-color: rgba(0, 255, 136, 0.4);
box-shadow: 0 0 15px rgba(0, 255, 136, 0.15);
}

.pbctf-landing .header__user-avatar {
width: 28px;
height: 28px;
border-radius: 4px;
object-fit: cover;
flex-shrink: 0;
}

.pbctf-landing .header__user-avatar--initials {
display: inline-flex;
align-items: center;
justify-content: center;
background: var(--primary);
color: #050505;
font-family: var(--font-mono);
font-weight: 700;
font-size: 11px;
}

.pbctf-landing .header__user-name {
font-family: var(--font-heading);
font-weight: 600;
font-size: 13px;
color: var(--text);
line-height: 1;
max-width: 120px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}

.pbctf-landing .header__logout {
display: inline-flex;
align-items: center;
gap: var(--space-2);
height: 36px;
padding: 0 var(--space-3);
border-radius: var(--radius-sm);
background: rgba(255, 255, 255, 0.03);
border: 1px solid rgba(255, 255, 255, 0.06);
color: var(--muted);
cursor: pointer;
transition: color var(--duration-normal) var(--ease-out),
background var(--duration-normal) var(--ease-out),
border-color var(--duration-normal) var(--ease-out),
box-shadow var(--duration-normal) var(--ease-out);
}

.pbctf-landing .header__logout svg {
width: 16px;
height: 16px;
}

.pbctf-landing .header__logout:hover {
color: var(--primary);
background: rgba(0, 255, 136, 0.08);
border-color: rgba(0, 255, 136, 0.4);
box-shadow: 0 0 15px rgba(0, 255, 136, 0.15);
}

.pbctf-landing .header__logout-text {
font-family: var(--font-mono);
font-weight: 600;
font-size: 11px;
text-transform: uppercase;
letter-spacing: 0.05em;
}

/* --- Mobile overlay logout --- */
.pbctf-landing .header__overlay-logout {
margin-top: var(--space-3);
align-self: flex-start;
background: none;
border: none;
cursor: pointer;
font-family: var(--font-mono);
font-weight: 600;
font-size: var(--text-sm);
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--muted);
transition: color 0.3s ease;
}

.pbctf-landing .header__overlay-logout:hover {
color: var(--primary);
}

/* --- Custom Tech Hamburger --- */
.pbctf-landing .header__hamburger {
display: flex;
Expand Down Expand Up @@ -326,6 +442,11 @@
flex-shrink: 0;
}

.pbctf-landing .header__user {
display: flex;
flex-shrink: 0;
}

.pbctf-landing .header__hamburger {
display: none;
}
Expand Down
89 changes: 77 additions & 12 deletions components/landing/Header/Header.jsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useState, useEffect } from 'react';
import { motion, AnimatePresence } from 'framer-motion';
import { useRetroSound } from '../hooks/useRetroSound';
import { useAuth } from '@/hooks/use-auth';
import './Header.css';

const NAV_LINKS = [
Expand All @@ -12,6 +13,7 @@ const NAV_LINKS = [
];

export default function Header() {
const { isAuthenticated, user, logout } = useAuth();
const [scrolled, setScrolled] = useState(false);
const [menuOpen, setMenuOpen] = useState(false);
const {
Expand All @@ -24,6 +26,21 @@ export default function Header() {
muted,
} = useRetroSound();

const firstName = (user?.name || '').trim().split(/\s+/)[0] || '';
const initials =
(user?.name || '')
.split(/\s+/)
.filter(Boolean)
.slice(0, 2)
.map((s) => s[0]?.toUpperCase())
.join('') || 'U';
const avatarSrc = user?.profile_picture || '';

const handleLogout = () => {
playClick();
logout();
};

useEffect(() => {
const handleScroll = () => {
setScrolled(window.scrollY > 50);
Expand Down Expand Up @@ -107,16 +124,52 @@ export default function Header() {
)}
</button>

{/* Desktop CTA */}
<a
href="/register"
className="btn btn--primary header__cta"
id="header-cta"
onMouseEnter={playHover}
onClick={playClick}
>
Register Now
</a>
{/* Desktop CTA (logged out) / user controls (logged in) */}
{isAuthenticated ? (
<div className="header__user">
<a
href="/dashboard/profile"
className="header__user-tile"
id="header-profile"
onMouseEnter={playHover}
onClick={playClick}
aria-label="Open profile"
>
{avatarSrc ? (
// eslint-disable-next-line @next/next/no-img-element
<img className="header__user-avatar" src={avatarSrc} alt="" />
) : (
<span className="header__user-avatar header__user-avatar--initials">{initials}</span>
)}
<span className="header__user-name">{firstName}</span>
</a>
<button
type="button"
className="header__logout"
id="header-logout"
onMouseEnter={playHover}
onClick={handleLogout}
aria-label="Log out"
>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
<polyline points="16 17 21 12 16 7" />
<line x1="21" y1="12" x2="9" y2="12" />
</svg>
<span className="header__logout-text">Logout</span>
</button>
</div>
) : (
<a
href="/register"
className="btn btn--primary header__cta"
id="header-cta"
onMouseEnter={playHover}
onClick={playClick}
>
Register Now
</a>
)}

{/* Mobile Hamburger */}
<button
Expand Down Expand Up @@ -161,15 +214,27 @@ export default function Header() {
</motion.a>
))}
<motion.a
href="/register"
href={isAuthenticated ? '/dashboard' : '/register'}
className="btn btn--primary header__overlay-cta"
onClick={() => { playClick(); closeMenu(); }}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 + NAV_LINKS.length * 0.05, duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
>
Register Now
{isAuthenticated ? 'Access Granted' : 'Register Now'}
</motion.a>
{isAuthenticated && (
<motion.button
type="button"
className="header__overlay-logout"
onClick={() => { handleLogout(); closeMenu(); }}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ delay: 0.1 + (NAV_LINKS.length + 1) * 0.05, duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
>
Logout
</motion.button>
)}
</div>
</motion.nav>
)}
Expand Down
Loading
Loading