/** ========= REACT & REACT ROUTER ========= */
import { useEffect, useRef } from 'react';
import { useNavigate } from 'react-router-dom';
/** ========= MUI ========= */
import Box from '@mui/material/Box';
import Button from '@mui/material/Button';
import Card from '@mui/material/Card';
import CardContent from '@mui/material/CardContent';
import CircularProgress from '@mui/material/CircularProgress';
import Divider from '@mui/material/Divider';
import Stack from '@mui/material/Stack';
import Typography from '@mui/material/Typography';
import { useTheme } from '@mui/material';
/** ========= ICONS ========= */
import ArrowForwardIcon from '@mui/icons-material/ArrowForward';
import DashboardIcon from '@mui/icons-material/Dashboard';
import HandshakeIcon from '@mui/icons-material/Handshake';
import LoginIcon from '@mui/icons-material/Login';
import LogoutIcon from '@mui/icons-material/Logout';
import PostAddIcon from '@mui/icons-material/PostAdd';
/** ========= BACKEND ========= */
import { GoogleAuthProvider, signInWithPopup, signOut } from 'firebase/auth';
import { useAuth } from '../contexts/useAuth';
import { useFeedback } from '../contexts/useFeedback';
import { auth } from '../data/Firebase';
import { createUserIfMissing } from '../data/FirestoreMethods';
/** Only want to define once, we don't need to re-define on re-render */
const provider = new GoogleAuthProvider();
export default function HomePage() {
const { setFeedback } = useFeedback();
const { user, loading } = useAuth();
const navigate = useNavigate();
const theme = useTheme();
const canvasRef = useRef(null);
const isDark = theme.palette.mode === 'dark';
const handleSignIn = async () => {
try {
const result = await signInWithPopup(auth, provider);
await createUserIfMissing(result.user);
} catch (e) {
console.error(e);
setFeedback('Sign in failed', 'error');
}
};
const handleSignOut = async () => {
try {
await signOut(auth);
} catch (e) {
console.error(e);
setFeedback('Sign out failed', 'error');
}
};
useEffect(() => {
const canvas = canvasRef.current;
const ctx = canvas.getContext('2d');
let width = (canvas.width = window.innerWidth);
let height = (canvas.height = window.innerHeight);
let animationFrameId;
const nodes = Array.from({ length: 80 }, () => ({
x: Math.random() * width,
y: Math.random() * height,
vx: (Math.random() - 0.5) * 0.4,
vy: (Math.random() - 0.5) * 0.4,
}));
function draw() {
ctx.clearRect(0, 0, width, height);
const bg = isDark
? ctx.createLinearGradient(0, 0, width, height)
: ctx.createLinearGradient(0, 0, width, height);
if (isDark) {
bg.addColorStop(0, '#0d1117');
bg.addColorStop(0.5, '#17223b');
bg.addColorStop(1, '#263859');
} else {
bg.addColorStop(0, '#f0f4ff');
bg.addColorStop(1, '#e8f6ff');
}
ctx.fillStyle = bg;
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < nodes.length; i++) {
const n = nodes[i];
n.x += n.vx;
n.y += n.vy;
if (n.x < 0 || n.x > width) n.vx *= -1;
if (n.y < 0 || n.y > height) n.vy *= -1;
ctx.beginPath();
ctx.arc(n.x, n.y, 2, 0, Math.PI * 2);
ctx.fillStyle = isDark ? 'rgba(0,188,212,0.9)' : 'rgba(70,130,180,0.6)';
ctx.fill();
}
for (let i = 0; i < nodes.length; i++) {
for (let j = i + 1; j < nodes.length; j++) {
const dx = nodes[i].x - nodes[j].x;
const dy = nodes[i].y - nodes[j].y;
const dist = Math.sqrt(dx * dx + dy * dy);
if (dist < 120) {
ctx.beginPath();
ctx.strokeStyle = isDark
? `rgba(0,188,212,${1 - dist / 120})`
: `rgba(70,130,180,${0.6 - dist / 200})`;
ctx.lineWidth = 0.5;
ctx.moveTo(nodes[i].x, nodes[i].y);
ctx.lineTo(nodes[j].x, nodes[j].y);
ctx.stroke();
}
}
}
animationFrameId = requestAnimationFrame(draw);
}
draw();
const handleResize = () => {
width = canvas.width = window.innerWidth;
height = canvas.height = window.innerHeight;
};
window.addEventListener('resize', handleResize);
return () => {
window.removeEventListener('resize', handleResize);
cancelAnimationFrame(animationFrameId);
};
}, [isDark]);
return (
<Box
sx={{
position: 'relative',
minHeight: '100vh',
width: '100%',
display: 'flex',
flexDirection: 'column',
alignItems: 'center',
justifyContent: 'center',
color: isDark ? 'white' : 'black',
textAlign: 'center',
overflow: 'hidden',
}}
>
{/* Fullscreen Canvas */}
<canvas
ref={canvasRef}
style={{
position: 'fixed',
top: 0,
left: 0,
width: '100vw',
height: '100vh',
zIndex: -1,
pointerEvents: 'none',
}}
/>
{/* Hero and Content */}
{loading ? (
<CircularProgress color="inherit" sx={{ zIndex: 1 }} />
) : (
<>
<Stack
spacing={2}
alignItems="center"
textAlign="center"
mb={6}
sx={{ position: 'relative', zIndex: 1 }}
>
<Typography
fontWeight={800}
sx={{
fontSize: { xs: '2.8rem', sm: '4rem', md: '5rem' },
letterSpacing: '1px',
background: isDark
? 'linear-gradient(90deg, #00bcd4, #26c6da, #80deea)'
: 'linear-gradient(90deg, #1e88e5, #64b5f6)',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
textShadow: isDark
? '0 0 25px rgba(0,188,212,0.3)'
: '0 0 10px rgba(100,149,237,0.2)',
}}
>
Swapify
</Typography>
<Typography
variant="subtitle1"
sx={{
fontSize: '1.1rem',
color: isDark ? 'rgba(255,255,255,0.85)' : 'rgba(0,0,0,0.7)',
}}
>
Trade skills. Grow together.
</Typography>
{!user ? (
<Button
onClick={handleSignIn}
variant="contained"
startIcon={<LoginIcon />}
sx={{
mt: 2,
px: 3,
py: 1.2,
fontSize: '1rem',
backgroundColor: isDark ? '#00bcd4' : '#1e88e5',
color: 'white',
textTransform: 'none',
borderRadius: '10px',
boxShadow: isDark
? '0 0 20px rgba(0,188,212,0.4)'
: '0 3px 10px rgba(100,149,237,0.3)',
'&:hover': {
backgroundColor: isDark ? '#00acc1' : '#1976d2',
boxShadow: isDark
? '0 0 30px rgba(0,188,212,0.6)'
: '0 3px 10px rgba(100,149,237,0.5)',
},
}}
>
Sign in with Google
</Button>
) : (
<>
<Typography
variant="body1"
sx={{
color: isDark ? 'rgba(255,255,255,0.8)' : 'rgba(0,0,0,0.7)',
}}
>
Hello, {user.displayName || user.email}
</Typography>
<Stack direction="row" spacing={2} justifyContent="center">
<Button
onClick={() => navigate('/viewTrades')}
variant="contained"
color="success"
startIcon={<DashboardIcon />}
>
Go to Dashboard
</Button>
<Button
onClick={handleSignOut}
variant="outlined"
color="inherit"
startIcon={<LogoutIcon />}
>
Sign Out
</Button>
</Stack>
<Button
variant="contained"
color="secondary"
startIcon={<PostAddIcon />}
onClick={() => navigate('/createTrade')}
sx={{ mt: 2 }}
>
Create a New Trade
</Button>
</>
)}
</Stack>
<Divider
sx={{
width: '100%',
maxWidth: '800px',
borderColor: isDark ? 'rgba(255,255,255,0.2)' : 'rgba(0,0,0,0.1)',
mb: 4,
zIndex: 1,
}}
/>
<Typography
variant="h5"
fontWeight="bold"
gutterBottom
sx={{ zIndex: 1 }}
>
How It Works
</Typography>
<Stack
direction={{ xs: 'column', md: 'row' }}
spacing={3}
alignItems="center"
justifyContent="center"
sx={{
width: '100%',
maxWidth: '900px',
px: 2,
position: 'relative',
zIndex: 1,
}}
>
{[
{
icon: <PostAddIcon fontSize="large" sx={{ mb: 1 }} />,
title: 'Post a Skill',
desc: 'List something you can offer — coding, design, tutoring, anything.',
},
{
icon: <ArrowForwardIcon fontSize="large" sx={{ mb: 1 }} />,
title: 'Find a Match',
desc: 'Explore and match with users looking to trade compatible skills.',
},
{
icon: <HandshakeIcon fontSize="large" sx={{ mb: 1 }} />,
title: 'Make the Trade',
desc: 'Connect securely and exchange skills with clarity and confidence.',
},
].map((card, i) => (
<Card
key={i}
sx={{
backgroundColor: isDark ? '#1f1f1f' : '#ffffff',
color: isDark ? 'white' : 'black',
minWidth: 250,
boxShadow: isDark
? '0 0 15px rgba(0,188,212,0.2)'
: '0 3px 10px rgba(100,149,237,0.2)',
borderRadius: 3,
transition: 'transform 0.3s ease, box-shadow 0.3s ease',
'&:hover': {
transform: 'translateY(-5px)',
boxShadow: isDark
? '0 0 25px rgba(0,188,212,0.4)'
: '0 5px 20px rgba(100,149,237,0.3)',
},
}}
>
<CardContent>
{card.icon}
<Typography variant="h6" fontWeight="bold">
{card.title}
</Typography>
<Typography
variant="body2"
sx={{
color: isDark
? 'rgba(255,255,255,0.8)'
: 'rgba(0,0,0,0.7)',
}}
>
{card.desc}
</Typography>
</CardContent>
</Card>
))}
</Stack>
<Typography
variant="caption"
sx={{
mt: 6,
opacity: 0.6,
zIndex: 1,
color: isDark ? 'rgba(255,255,255,0.6)' : 'rgba(0,0,0,0.5)',
}}
>
Built by students at Wilfrid Laurier University to connect and grow.
</Typography>
</>
)}
</Box>
);
}