Skip to content

Commit df73a36

Browse files
committed
Add logged-in state to landing CTAs and header
1 parent 59a30b4 commit df73a36

5 files changed

Lines changed: 279 additions & 33 deletions

File tree

components/landing/FinalCTA/FinalCTA.jsx

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,14 @@ import { gsap } from 'gsap';
33
import { ScrollTrigger } from 'gsap/ScrollTrigger';
44
import { useGSAP } from '@gsap/react';
55
import { useRetroSound } from '../hooks/useRetroSound';
6+
import { useAuth } from '@/hooks/use-auth';
67
import './FinalCTA.css';
78

89
gsap.registerPlugin(ScrollTrigger);
910

1011
export default function FinalCTA() {
1112
const sectionRef = useRef(null);
13+
const { isAuthenticated } = useAuth();
1214
const { playHover, playClick } = useRetroSound();
1315

1416
useGSAP(() => {
@@ -42,13 +44,23 @@ export default function FinalCTA() {
4244
</p>
4345
<div className="final-cta__actions">
4446
<a
45-
href="/register"
46-
className="btn btn--primary"
47+
href={isAuthenticated ? '/dashboard' : '/register'}
48+
className={`btn ${isAuthenticated ? 'btn--secondary' : 'btn--primary'}`}
4749
id="cta-register-now"
4850
onMouseEnter={playHover}
4951
onClick={playClick}
5052
>
51-
Register Now
53+
{isAuthenticated ? (
54+
<>
55+
Access Granted
56+
<svg className="btn__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
57+
<line x1="5" y1="12" x2="19" y2="12" />
58+
<polyline points="12 5 19 12 12 19" />
59+
</svg>
60+
</>
61+
) : (
62+
'Register Now'
63+
)}
5264
</a>
5365

5466
</div>

components/landing/Header/Header.css

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,122 @@
179179
transform: translateY(-1px);
180180
}
181181

182+
/* --- Authed user controls (avatar tile + logout) --- */
183+
.pbctf-landing .header__user {
184+
display: none;
185+
align-items: center;
186+
gap: var(--space-3);
187+
}
188+
189+
.pbctf-landing .header__user-tile {
190+
display: inline-flex;
191+
align-items: center;
192+
gap: var(--space-2);
193+
height: 36px;
194+
padding: 0 var(--space-3) 0 4px;
195+
border-radius: var(--radius-sm);
196+
background: rgba(255, 255, 255, 0.03);
197+
border: 1px solid rgba(255, 255, 255, 0.06);
198+
text-decoration: none;
199+
transition: background var(--duration-normal) var(--ease-out),
200+
border-color var(--duration-normal) var(--ease-out),
201+
box-shadow var(--duration-normal) var(--ease-out);
202+
}
203+
204+
.pbctf-landing .header__user-tile:hover {
205+
background: rgba(0, 255, 136, 0.08);
206+
border-color: rgba(0, 255, 136, 0.4);
207+
box-shadow: 0 0 15px rgba(0, 255, 136, 0.15);
208+
}
209+
210+
.pbctf-landing .header__user-avatar {
211+
width: 28px;
212+
height: 28px;
213+
border-radius: 4px;
214+
object-fit: cover;
215+
flex-shrink: 0;
216+
}
217+
218+
.pbctf-landing .header__user-avatar--initials {
219+
display: inline-flex;
220+
align-items: center;
221+
justify-content: center;
222+
background: var(--primary);
223+
color: #050505;
224+
font-family: var(--font-mono);
225+
font-weight: 700;
226+
font-size: 11px;
227+
}
228+
229+
.pbctf-landing .header__user-name {
230+
font-family: var(--font-heading);
231+
font-weight: 600;
232+
font-size: 13px;
233+
color: var(--text);
234+
line-height: 1;
235+
max-width: 120px;
236+
overflow: hidden;
237+
text-overflow: ellipsis;
238+
white-space: nowrap;
239+
}
240+
241+
.pbctf-landing .header__logout {
242+
display: inline-flex;
243+
align-items: center;
244+
gap: var(--space-2);
245+
height: 36px;
246+
padding: 0 var(--space-3);
247+
border-radius: var(--radius-sm);
248+
background: rgba(255, 255, 255, 0.03);
249+
border: 1px solid rgba(255, 255, 255, 0.06);
250+
color: var(--muted);
251+
cursor: pointer;
252+
transition: color var(--duration-normal) var(--ease-out),
253+
background var(--duration-normal) var(--ease-out),
254+
border-color var(--duration-normal) var(--ease-out),
255+
box-shadow var(--duration-normal) var(--ease-out);
256+
}
257+
258+
.pbctf-landing .header__logout svg {
259+
width: 16px;
260+
height: 16px;
261+
}
262+
263+
.pbctf-landing .header__logout:hover {
264+
color: var(--primary);
265+
background: rgba(0, 255, 136, 0.08);
266+
border-color: rgba(0, 255, 136, 0.4);
267+
box-shadow: 0 0 15px rgba(0, 255, 136, 0.15);
268+
}
269+
270+
.pbctf-landing .header__logout-text {
271+
font-family: var(--font-mono);
272+
font-weight: 600;
273+
font-size: 11px;
274+
text-transform: uppercase;
275+
letter-spacing: 0.05em;
276+
}
277+
278+
/* --- Mobile overlay logout --- */
279+
.pbctf-landing .header__overlay-logout {
280+
margin-top: var(--space-3);
281+
align-self: flex-start;
282+
background: none;
283+
border: none;
284+
cursor: pointer;
285+
font-family: var(--font-mono);
286+
font-weight: 600;
287+
font-size: var(--text-sm);
288+
text-transform: uppercase;
289+
letter-spacing: 0.05em;
290+
color: var(--muted);
291+
transition: color 0.3s ease;
292+
}
293+
294+
.pbctf-landing .header__overlay-logout:hover {
295+
color: var(--primary);
296+
}
297+
182298
/* --- Custom Tech Hamburger --- */
183299
.pbctf-landing .header__hamburger {
184300
display: flex;
@@ -326,6 +442,11 @@
326442
flex-shrink: 0;
327443
}
328444

445+
.pbctf-landing .header__user {
446+
display: flex;
447+
flex-shrink: 0;
448+
}
449+
329450
.pbctf-landing .header__hamburger {
330451
display: none;
331452
}

components/landing/Header/Header.jsx

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useEffect } from 'react';
22
import { motion, AnimatePresence } from 'framer-motion';
33
import { useRetroSound } from '../hooks/useRetroSound';
4+
import { useAuth } from '@/hooks/use-auth';
45
import './Header.css';
56

67
const NAV_LINKS = [
@@ -12,6 +13,7 @@ const NAV_LINKS = [
1213
];
1314

1415
export default function Header() {
16+
const { isAuthenticated, user, logout } = useAuth();
1517
const [scrolled, setScrolled] = useState(false);
1618
const [menuOpen, setMenuOpen] = useState(false);
1719
const {
@@ -24,6 +26,21 @@ export default function Header() {
2426
muted,
2527
} = useRetroSound();
2628

29+
const firstName = (user?.name || '').trim().split(/\s+/)[0] || '';
30+
const initials =
31+
(user?.name || '')
32+
.split(/\s+/)
33+
.filter(Boolean)
34+
.slice(0, 2)
35+
.map((s) => s[0]?.toUpperCase())
36+
.join('') || 'U';
37+
const avatarSrc = user?.profile_picture || '';
38+
39+
const handleLogout = () => {
40+
playClick();
41+
logout();
42+
};
43+
2744
useEffect(() => {
2845
const handleScroll = () => {
2946
setScrolled(window.scrollY > 50);
@@ -107,16 +124,52 @@ export default function Header() {
107124
)}
108125
</button>
109126

110-
{/* Desktop CTA */}
111-
<a
112-
href="/register"
113-
className="btn btn--primary header__cta"
114-
id="header-cta"
115-
onMouseEnter={playHover}
116-
onClick={playClick}
117-
>
118-
Register Now
119-
</a>
127+
{/* Desktop CTA (logged out) / user controls (logged in) */}
128+
{isAuthenticated ? (
129+
<div className="header__user">
130+
<a
131+
href="/dashboard/profile"
132+
className="header__user-tile"
133+
id="header-profile"
134+
onMouseEnter={playHover}
135+
onClick={playClick}
136+
aria-label="Open profile"
137+
>
138+
{avatarSrc ? (
139+
// eslint-disable-next-line @next/next/no-img-element
140+
<img className="header__user-avatar" src={avatarSrc} alt="" />
141+
) : (
142+
<span className="header__user-avatar header__user-avatar--initials">{initials}</span>
143+
)}
144+
<span className="header__user-name">{firstName}</span>
145+
</a>
146+
<button
147+
type="button"
148+
className="header__logout"
149+
id="header-logout"
150+
onMouseEnter={playHover}
151+
onClick={handleLogout}
152+
aria-label="Log out"
153+
>
154+
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.6" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
155+
<path d="M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4" />
156+
<polyline points="16 17 21 12 16 7" />
157+
<line x1="21" y1="12" x2="9" y2="12" />
158+
</svg>
159+
<span className="header__logout-text">Logout</span>
160+
</button>
161+
</div>
162+
) : (
163+
<a
164+
href="/register"
165+
className="btn btn--primary header__cta"
166+
id="header-cta"
167+
onMouseEnter={playHover}
168+
onClick={playClick}
169+
>
170+
Register Now
171+
</a>
172+
)}
120173

121174
{/* Mobile Hamburger */}
122175
<button
@@ -161,15 +214,27 @@ export default function Header() {
161214
</motion.a>
162215
))}
163216
<motion.a
164-
href="/register"
217+
href={isAuthenticated ? '/dashboard' : '/register'}
165218
className="btn btn--primary header__overlay-cta"
166219
onClick={() => { playClick(); closeMenu(); }}
167220
initial={{ opacity: 0, y: 20 }}
168221
animate={{ opacity: 1, y: 0 }}
169222
transition={{ delay: 0.1 + NAV_LINKS.length * 0.05, duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
170223
>
171-
Register Now
224+
{isAuthenticated ? 'Access Granted' : 'Register Now'}
172225
</motion.a>
226+
{isAuthenticated && (
227+
<motion.button
228+
type="button"
229+
className="header__overlay-logout"
230+
onClick={() => { handleLogout(); closeMenu(); }}
231+
initial={{ opacity: 0, y: 20 }}
232+
animate={{ opacity: 1, y: 0 }}
233+
transition={{ delay: 0.1 + (NAV_LINKS.length + 1) * 0.05, duration: 0.4, ease: [0.16, 1, 0.3, 1] }}
234+
>
235+
Logout
236+
</motion.button>
237+
)}
173238
</div>
174239
</motion.nav>
175240
)}

components/landing/Hero/Hero.jsx

Lines changed: 38 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { useState, useEffect } from 'react';
22
import { motion } from 'framer-motion';
33
import { useRetroSound } from '../hooks/useRetroSound';
4+
import { useAuth } from '@/hooks/use-auth';
45
import './Hero.css';
56

67
const CHARS = '!<>-_\\\\/[]{}—=+*^?#________';
@@ -61,6 +62,7 @@ const stagger = (i) => ({
6162
});
6263

6364
export default function Hero() {
65+
const { isAuthenticated } = useAuth();
6466
const { playHover, playClick } = useRetroSound();
6567

6668
return (
@@ -127,24 +129,42 @@ export default function Hero() {
127129

128130
<div className="hero__actions-wrapper">
129131
<motion.div className="hero__actions" {...stagger(15)}>
130-
<a
131-
href="/register"
132-
className="btn btn--primary"
133-
id="hero-register-cta"
134-
onMouseEnter={playHover}
135-
onClick={playClick}
136-
>
137-
Register Now
138-
</a>
139-
<a
140-
href="/login"
141-
className="btn btn--secondary"
142-
id="hero-login-cta"
143-
onMouseEnter={playHover}
144-
onClick={playClick}
145-
>
146-
Login
147-
</a>
132+
{isAuthenticated ? (
133+
<a
134+
href="/dashboard"
135+
className="btn btn--secondary"
136+
id="hero-dashboard-cta"
137+
onMouseEnter={playHover}
138+
onClick={playClick}
139+
>
140+
Access Granted
141+
<svg className="btn__arrow" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true">
142+
<line x1="5" y1="12" x2="19" y2="12" />
143+
<polyline points="12 5 19 12 12 19" />
144+
</svg>
145+
</a>
146+
) : (
147+
<>
148+
<a
149+
href="/register"
150+
className="btn btn--primary"
151+
id="hero-register-cta"
152+
onMouseEnter={playHover}
153+
onClick={playClick}
154+
>
155+
Register Now
156+
</a>
157+
<a
158+
href="/login"
159+
className="btn btn--secondary"
160+
id="hero-login-cta"
161+
onMouseEnter={playHover}
162+
onClick={playClick}
163+
>
164+
Login
165+
</a>
166+
</>
167+
)}
148168
</motion.div>
149169
</div>
150170

0 commit comments

Comments
 (0)