diff --git a/apps/frontend/src/app.tsx b/apps/frontend/src/app.tsx index ce4e7fea..fbf70c86 100644 --- a/apps/frontend/src/app.tsx +++ b/apps/frontend/src/app.tsx @@ -7,26 +7,55 @@ import ApplicationsPage from '@features/applications/pages/ApplicationsPage'; import ApplicationDetailPage from '@features/applications/pages/ApplicationDetailPage'; import LoginContext from '@features/auth/components/LoginPage/LoginContext'; import ProtectedRoutes from '@features/auth/components/ProtectedRoutes'; -import LoginPage from '@features/auth/components/LoginPage'; import AdminRoutes from '@features/auth/components/AdminRoutes'; import HomePage from '@shared/pages/HomePage'; export const App: React.FC = () => { - const [token, setToken] = useState(''); + const [token, setToken] = useState(() => { + const storedToken = localStorage.getItem('token'); + return storedToken ? JSON.parse(storedToken) : ''; + }); + return ( - } /> + } /> } /> - }> + }> }> - } /> - } /> + + + + } + /> + + + + } + /> } + element={ + + + + } + /> + + + + } /> diff --git a/apps/frontend/src/features/applicant/components/ApplicantView/user.tsx b/apps/frontend/src/features/applicant/components/ApplicantView/user.tsx index 11e17abb..c9f7115d 100644 --- a/apps/frontend/src/features/applicant/components/ApplicantView/user.tsx +++ b/apps/frontend/src/features/applicant/components/ApplicantView/user.tsx @@ -125,8 +125,7 @@ export const ApplicantView = ({ user }: ApplicantViewProps) => { {!isLoading && selectedApplication && - String(selectedApplication.stage) === - ApplicationStage.PM_CHALLENGE && ( + String(selectedApplication.stage) === 'PM_CHALLENGE' && ( - - - Logging you in... - - + + ); } diff --git a/apps/frontend/src/features/auth/components/ProtectedRoutes/index.tsx b/apps/frontend/src/features/auth/components/ProtectedRoutes/index.tsx index 07e64b0d..9ab01015 100644 --- a/apps/frontend/src/features/auth/components/ProtectedRoutes/index.tsx +++ b/apps/frontend/src/features/auth/components/ProtectedRoutes/index.tsx @@ -5,7 +5,10 @@ import { Navigate, Outlet } from 'react-router-dom'; * if the user is authenticated (i.e if an access token exists). * If the user is not authenticated, it redirects to the login page. */ -function ProtectedRoutes({ token }: { token: string }) { +function ProtectedRoutes() { + const storedToken = localStorage.getItem('token'); + const token = storedToken ? JSON.parse(storedToken) : ''; + return token ? : ; } diff --git a/apps/frontend/src/shared/pages/HomePage.tsx b/apps/frontend/src/shared/pages/HomePage.tsx index 6eac0cfa..a680a9c5 100644 --- a/apps/frontend/src/shared/pages/HomePage.tsx +++ b/apps/frontend/src/shared/pages/HomePage.tsx @@ -1,29 +1,127 @@ -import { Box, Container, Stack } from '@mui/material'; -import { useAuth } from '@shared/hooks/useAuth'; -import { - Header, - WelcomeBanner, - RoleSelector, - DeadlineCountdown, -} from '@features/homepage/components'; - -/** - * HomePage - Application landing page - * - * Clean, presentational component that composes smaller, reusable components. - * Authentication logic is delegated to the useAuth hook. - * - * Responsibilities: - * - Display welcome banner - * - Show role selection options - * - Display application deadline countdown - * - Show login/logout based on authentication state - */ +import React, { useState, useEffect } from 'react'; +import { Box, Button, Container, Stack, Typography } from '@mui/material'; +import { useNavigate } from 'react-router-dom'; +import { CognitoJwtVerifier } from 'aws-jwt-verify'; +import apiClient from '@api/apiClient'; +import useLoginContext from '@features/auth/components/LoginPage/useLoginContext'; + +const verifier = CognitoJwtVerifier.create({ + userPoolId: import.meta.env.VITE_COGNITO_USER_POOL_ID as string, + tokenUse: 'access', + clientId: import.meta.env.VITE_COGNITO_CLIENT_ID as string, +}); + const HomePage = () => { - const { isAuthenticated, signOut } = useAuth(); + const navigate = useNavigate(); + const { setToken, token } = useLoginContext(); + const [isAuthenticated, setIsAuthenticated] = useState(false); + const [timeLeft, setTimeLeft] = useState({ + days: 0, + hours: 0, + minutes: 0, + seconds: 0, + }); + + // Hardcoded deadline: October 31, 2025 + const deadline = new Date('2025-10-31T23:59:59').getTime(); + + // Authentication logic - handle auth code and token verification + useEffect(() => { + const urlParams = new URLSearchParams(window.location.search); + const authCode = urlParams.get('code'); + + async function handleAuth() { + const localToken = localStorage.getItem('token'); + + if (localToken) { + try { + const token = JSON.parse(localToken); + await verifier.verify(token); + setToken(token); + setIsAuthenticated(true); + // Don't navigate away - let them stay on the home page + } catch (error) { + console.log('Error verifying token:', error); + localStorage.removeItem('token'); + setIsAuthenticated(false); + } + } else if (authCode) { + try { + const tokenResponse = await apiClient.getToken(authCode); + + // Store both tokens in localStorage for persistence + localStorage.setItem( + 'auth_tokens', + JSON.stringify({ + accessToken: tokenResponse.access_token, + refreshToken: tokenResponse.refresh_token, + }), + ); + + // Keep backward compatibility - store access token for existing code + localStorage.setItem( + 'token', + JSON.stringify(tokenResponse.access_token), + ); + setToken(tokenResponse.access_token); + setIsAuthenticated(true); + + // Redirect to dashboard after successful login from Cognito + navigate('/'); + } catch (error) { + console.error('Error fetching token:', error); + setIsAuthenticated(false); + } + } else { + // No token and no auth code - user is not authenticated + setIsAuthenticated(false); + } + } + handleAuth(); + }, [navigate, setToken]); + + // Countdown timer logic + useEffect(() => { + const timer = setInterval(() => { + const now = new Date().getTime(); + const distance = deadline - now; - // Application deadline - const deadline = new Date('2025-10-31T23:59:59'); + if (distance < 0) { + setTimeLeft({ days: 0, hours: 0, minutes: 0, seconds: 0 }); + clearInterval(timer); + } else { + const days = Math.floor(distance / (1000 * 60 * 60 * 24)); + const hours = Math.floor( + (distance % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60), + ); + const minutes = Math.floor((distance % (1000 * 60 * 60)) / (1000 * 60)); + const seconds = Math.floor((distance % (1000 * 60)) / 1000); + + setTimeLeft({ days, hours, minutes, seconds }); + } + }, 1000); + + return () => clearInterval(timer); + }, [deadline]); + + const formatTime = (value: number) => String(value).padStart(2, '0'); + + const handleLearnMore = (role: string) => { + const urls: { [key: string]: string } = { + designer: 'https://www.c4cneu.com/apply/Product-Designer', + developer: 'https://www.c4cneu.com/apply/Software-Developer', + pm: 'https://www.c4cneu.com/apply/Product-Manager', + }; + window.open(urls[role], '_blank'); + }; + + const handleSignOut = () => { + // Clear all authentication data + localStorage.removeItem('token'); + localStorage.removeItem('auth_tokens'); + setToken(''); + setIsAuthenticated(false); + }; return ( { flexDirection: 'column', }} > -
+ {/* Header */} + + + C4C Logo + + 2025 Application + + + {isAuthenticated ? ( + + ) : ( + + )} + + {/* Main Content */} - - - + {/* Welcome Banner */} + + + Welcome to the C4C Application page! + + + Apply as a Developer, Designer, or Project Manager here. + + + + {/* Role Buttons */} + + + {/* Designer Row */} + + + + + + {/* Software Developer Row */} + + + + + + {/* Product Manager Row */} + + + + + + + + {/* Deadline Section */} + + + Deadline: October 31, 2025 + + + {/* Countdown Timer */} + + + {formatTime(timeLeft.days)} days, {formatTime(timeLeft.hours)}{' '} + hours, {formatTime(timeLeft.minutes)} minutes, and{' '} + {formatTime(timeLeft.seconds)} seconds left! + + +