Add travel section
BIN
public/trips/asc2024/IMG_2631.png
Normal file
|
After Width: | Height: | Size: 4.6 MiB |
BIN
public/trips/asc2024/IMG_2641.png
Normal file
|
After Width: | Height: | Size: 5.1 MiB |
BIN
public/trips/asc2024/IMG_7987.png
Normal file
|
After Width: | Height: | Size: 5.6 MiB |
BIN
public/trips/engr290/P1013747.png
Normal file
|
After Width: | Height: | Size: 3.4 MiB |
BIN
public/trips/engr290/insta290.jpg
Normal file
|
After Width: | Height: | Size: 106 KiB |
BIN
public/trips/imola2024/IMG_2050.png
Normal file
|
After Width: | Height: | Size: 42 MiB |
BIN
public/trips/imola2024/IMG_2066.png
Normal file
|
After Width: | Height: | Size: 4.8 MiB |
BIN
public/trips/imola2024/IMG_2093.png
Normal file
|
After Width: | Height: | Size: 15 MiB |
BIN
public/trips/roman2024/IMG_3946.png
Normal file
|
After Width: | Height: | Size: 11 MiB |
BIN
public/trips/roman2024/IMG_3951.png
Normal file
|
After Width: | Height: | Size: 29 MiB |
BIN
public/trips/roman2024/IMG_3978.png
Normal file
|
After Width: | Height: | Size: 30 MiB |
BIN
public/trips/sca2024/bean.png
Normal file
|
After Width: | Height: | Size: 7.1 MiB |
BIN
public/trips/sca2024/group.jpeg
Normal file
|
After Width: | Height: | Size: 3.2 MiB |
BIN
public/trips/sca2024/plane.png
Normal file
|
After Width: | Height: | Size: 3.9 MiB |
BIN
public/trips/silverstone/P1025274.png
Normal file
|
After Width: | Height: | Size: 20 MiB |
BIN
public/trips/silverstone/_1035764.png
Normal file
|
After Width: | Height: | Size: 26 MiB |
BIN
public/trips/silverstone/_1035852.png
Normal file
|
After Width: | Height: | Size: 25 MiB |
@@ -1,14 +0,0 @@
|
|||||||
import { NextResponse } from 'next/server';
|
|
||||||
import { promises as fs } from 'fs';
|
|
||||||
import path from 'path';
|
|
||||||
|
|
||||||
export async function GET() {
|
|
||||||
try {
|
|
||||||
const cvPath = path.join(process.cwd(), 'cv.tex');
|
|
||||||
const cvContent = await fs.readFile(cvPath, 'utf8');
|
|
||||||
|
|
||||||
return NextResponse.json({ content: cvContent });
|
|
||||||
} catch (error) {
|
|
||||||
return NextResponse.json({ error: 'Failed to load CV' }, { status: 500 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -46,7 +46,7 @@ export default function ArticlesPage() {
|
|||||||
<div className="space-y-12">
|
<div className="space-y-12">
|
||||||
<section className="space-y-6">
|
<section className="space-y-6">
|
||||||
<div>
|
<div>
|
||||||
<h1 className="text-2xl font-bold">In the Media 🗞️</h1>
|
<h1 className="text-2xl font-bold">In the Media 📰</h1>
|
||||||
<p className="text-lg text-muted-foreground mt-2">
|
<p className="text-lg text-muted-foreground mt-2">
|
||||||
I have been lucky enough to have a few wonderful articles written about me and my work. Go check them out!
|
I have been lucky enough to have a few wonderful articles written about me and my work. Go check them out!
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
115
src/app/travel/page.tsx
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import { useEffect, useState } from "react";
|
||||||
|
import { Card, CardHeader, CardTitle, CardContent, CardDescription } from "~/components/ui/card";
|
||||||
|
import { CardSkeleton } from "~/components/ui/skeletons";
|
||||||
|
import Image from "next/image";
|
||||||
|
import { Badge } from "~/components/ui/badge";
|
||||||
|
|
||||||
|
const tripsData = [
|
||||||
|
{
|
||||||
|
title: "AIChE Annual Student Conference 2024",
|
||||||
|
description: "With the funding of Bucknell's chemical engineering department, and an amazing team, I was able to attend the 2024 AIChE Annual Student Conference and compete in the national Chem-E-Car competition.",
|
||||||
|
images: ["/trips/asc2024/IMG_2641.png", "/trips/asc2024/IMG_2631.png", "/trips/asc2024/IMG_7987.png"],
|
||||||
|
tags: ["Chem-E-Car", "AIChE", "Conference", "Competition"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "IEEE RO-MAN 2024",
|
||||||
|
description: "I got to attend the IEEE RO-MAN 2024 conference in Pasadena, California. It was a great opportunity to present my work on my project HRIStudio, and to network with other researchers and industry professionals.",
|
||||||
|
images: ["/trips/roman2024/IMG_3951.png", "/trips/roman2024/IMG_3978.png", "/trips/roman2024/IMG_3946.png"],
|
||||||
|
tags: ["RO-MAN", "IEEE", "Conference", "Presentation"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "ENGR 290: Following da Vinci's Footsteps",
|
||||||
|
description: "During the summer of 2024, I went on a study abroad program with about thirty of my peers. We explored Italy and France, following the footsteps of Leonardo da Vinci- evaluating the world through his lenses.",
|
||||||
|
images: ["/trips/engr290/insta290.jpg", "/trips/engr290/P1013747.png"],
|
||||||
|
tags: ["Italy", "France", "Study Abroad", "Engineering"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "SCA Specialty Coffee Expo 2024",
|
||||||
|
description: "As a member of the executive board of the Bucknell Coffee Society, I was able to attend the Specialty Coffee Association's Specialty Coffee Expo in early 2024, traveling to Chicago, IL.",
|
||||||
|
images: ["/trips/sca2024/group.jpeg", "/trips/sca2024/bean.png", "/trips/sca2024/plane.png"],
|
||||||
|
tags: ["Coffee Society", "Chicago", "SCA", "Coffee"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Formula 1 Gran Premio dell'Emilia Romagna 2024",
|
||||||
|
description: "While studying abroad with Bucknell Engineering, we were lucky enough to be within a few hours of the Imola Grand Prix! A group of students went to see the race, and it was an amazing experience.",
|
||||||
|
images: ["/trips/imola2024/IMG_2093.png", "/trips/imola2024/IMG_2050.png", "/trips/imola2024/IMG_2066.png"],
|
||||||
|
tags: ["Racing", "Formula One", "Italy"]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Formula 1 British Grand Prix 2024",
|
||||||
|
description: "As a semi-recent Formula One fan, I was very excited to have the opportunity to attend the British Grand Prix weekend in 2024. I was able to see every event- marking one of my favorite weekends, probably ever.",
|
||||||
|
images: ["/trips/silverstone/_1035852.png", "/trips/silverstone/P1025274.png", "/trips/silverstone/_1035764.png"],
|
||||||
|
tags: ["Racing", "Formula One", "Great Britain", "Silverstone"]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
export default function TripsPage() {
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const timer = setTimeout(() => {
|
||||||
|
setLoading(false);
|
||||||
|
}, 1000);
|
||||||
|
return () => clearTimeout(timer);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<section className="prose prose-zinc dark:prose-invert max-w-none">
|
||||||
|
<h1 className="text-2xl font-bold">My Trips & Events 🌍</h1>
|
||||||
|
<p className="text-lg text-muted-foreground mt-2">
|
||||||
|
A collection of memorable trips and events I've attended.
|
||||||
|
</p>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<div className="grid grid-cols-1 gap-6 md:grid-cols-2">
|
||||||
|
{loading ? (
|
||||||
|
<>
|
||||||
|
<CardSkeleton />
|
||||||
|
<CardSkeleton />
|
||||||
|
<CardSkeleton />
|
||||||
|
<CardSkeleton />
|
||||||
|
<CardSkeleton />
|
||||||
|
<CardSkeleton />
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
tripsData.map((trip, index) => (
|
||||||
|
<Card key={index} className="rounded-lg overflow-hidden">
|
||||||
|
<CardHeader className="p-0">
|
||||||
|
<div className="flex flex-col">
|
||||||
|
<div className="flex overflow-x-auto space-x-0">
|
||||||
|
{trip.images.map((image, imgIndex) => (
|
||||||
|
<div key={imgIndex} className="flex-shrink-0">
|
||||||
|
<Image
|
||||||
|
src={image}
|
||||||
|
alt={trip.title}
|
||||||
|
width={250}
|
||||||
|
height={200}
|
||||||
|
className="object-cover min-h-[200px] max-h-[200px]"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<div className="flex flex-col justify-items-start content-between">
|
||||||
|
<CardTitle className="mt-6 mb-2">{trip.title}</CardTitle>
|
||||||
|
<CardDescription className="">{trip.description}</CardDescription>
|
||||||
|
{/* Show badges for tags */}
|
||||||
|
<div className="flex flex-wrap gap-2 mt-4">
|
||||||
|
{trip.tags.map((tag, tagIndex) => (
|
||||||
|
<Badge key={tagIndex} variant="secondary">{tag}</Badge>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
))
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</div >
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
'use client';
|
'use client';
|
||||||
|
|
||||||
import { BookOpenText, FileText, FolderGit2, Home, Menu, Moon, Newspaper, Sun, SunMoon, X } from 'lucide-react';
|
import { BookOpenText, FileText, FolderGit2, Home, Menu, Moon, Newspaper, Plane, Sun, SunMoon, X } from 'lucide-react';
|
||||||
import { useTheme } from 'next-themes';
|
import { useTheme } from 'next-themes';
|
||||||
import Link from 'next/link';
|
import Link from 'next/link';
|
||||||
import { usePathname } from 'next/navigation';
|
import { usePathname } from 'next/navigation';
|
||||||
@@ -12,6 +12,7 @@ const navItems = [
|
|||||||
{ href: '/articles', label: 'Articles', icon: Newspaper },
|
{ href: '/articles', label: 'Articles', icon: Newspaper },
|
||||||
{ href: '/projects', label: 'Projects', icon: FolderGit2 },
|
{ href: '/projects', label: 'Projects', icon: FolderGit2 },
|
||||||
{ href: '/publications', label: 'Publications', icon: BookOpenText },
|
{ href: '/publications', label: 'Publications', icon: BookOpenText },
|
||||||
|
{ href: '/travel', label: 'Travel', icon: Plane },
|
||||||
{ href: '/cv', label: 'CV', icon: FileText },
|
{ href: '/cv', label: 'CV', icon: FileText },
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -26,6 +27,12 @@ export function Navigation() {
|
|||||||
setMounted(true);
|
setMounted(true);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
// Update the document title based on the current pathname
|
||||||
|
useEffect(() => {
|
||||||
|
const currentItem = navItems.find(item => item.href === pathname);
|
||||||
|
document.title = currentItem ? `${currentItem.label} - Sean O'Connor` : 'Sean O\'Connor'; // Default title
|
||||||
|
}, [pathname]);
|
||||||
|
|
||||||
// Determine the icon to show based on the theme
|
// Determine the icon to show based on the theme
|
||||||
const themeIcon = theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />;
|
const themeIcon = theme === 'dark' ? <Sun size={20} /> : <Moon size={20} />;
|
||||||
const defaultThemeIcon = <SunMoon size={20} />; // Default icon for server-side rendering
|
const defaultThemeIcon = <SunMoon size={20} />; // Default icon for server-side rendering
|
||||||
|
|||||||