From acd058e9a9ce25d6720a5754beed9e1200c2a64c Mon Sep 17 00:00:00 2001 From: Andrea Vargas Date: Thu, 2 Oct 2025 20:03:19 -0600 Subject: [PATCH 01/11] Unviersity page layout --- app/(home)/university/layout.tsx | 32 +++ app/(home)/university/page.tsx | 358 +++++++++++++++++++++++++++++++ app/layout.config.tsx | 10 + 3 files changed, 400 insertions(+) create mode 100644 app/(home)/university/layout.tsx create mode 100644 app/(home)/university/page.tsx diff --git a/app/(home)/university/layout.tsx b/app/(home)/university/layout.tsx new file mode 100644 index 00000000000..f067cdfee29 --- /dev/null +++ b/app/(home)/university/layout.tsx @@ -0,0 +1,32 @@ +import type { Metadata } from 'next'; +import { createMetadata } from '@/utils/metadata'; + +export const metadata: Metadata = createMetadata({ + title: 'University', + description: 'Discover opportunities for students and educators to explore blockchain technology, access educational resources, and join our community of builders on Avalanche.', + openGraph: { + url: '/university', + images: { + url: '/api/og/university', + width: 1200, + height: 630, + alt: 'Avalanche University Program', + }, + }, + twitter: { + images: { + url: '/api/og/university', + width: 1200, + height: 630, + alt: 'Avalanche University Program', + }, + }, +}); + +export default function UniversityLayout({ + children, +}: { + children: React.ReactNode; +}) { + return <>{children}; +} diff --git a/app/(home)/university/page.tsx b/app/(home)/university/page.tsx new file mode 100644 index 00000000000..793a89e82d6 --- /dev/null +++ b/app/(home)/university/page.tsx @@ -0,0 +1,358 @@ +"use client"; + +import Image from "next/image"; +import { Button } from "@/components/ui/button"; +import { + ArrowRight, + BookOpen, + GraduationCap, + Users, + Mail, + Calendar, + MessageSquare, + UserPlus, + Mic, + DollarSign, + ExternalLink, + Award, + Globe, + Building, +} from "lucide-react"; +import Link from "next/link"; +import { HeroBackground } from "@/components/landing/hero"; + +interface ProgramCardProps { + title: string; + description: string; + icon: React.ReactNode; + href?: string; + external?: boolean; +} + +function ProgramCard({ title, description, icon, href, external = false }: ProgramCardProps) { + const cardContent = ( +
+
+
+ {icon} +
+ {external ? ( + + ) : ( + + )} +
+

{title}

+

{description}

+
+ ); + + if (href) { + if (external) { + return ( + + {cardContent} + + ); + } else { + return ( + + {cardContent} + + ); + } + } + + return cardContent; +} + +interface ActionCardProps { + title: string; + description: string; + icon: React.ReactNode; + href?: string; + external?: boolean; +} + +function ActionCard({ title, description, icon, href, external = false }: ActionCardProps) { + const cardContent = ( +
+
+
+ {icon} +
+ {external ? ( + + ) : ( + + )} +
+

{title}

+

{description}

+
+ ); + + if (href) { + if (external) { + return ( + + {cardContent} + + ); + } else { + return ( + + {cardContent} + + ); + } + } + + return cardContent; +} + +export default function Page() { + return ( + <> + +
+ {/* Hero Section */} +
+
+ Avalanche Logo + Avalanche Logo +
+

+ University Program +

+

+ Discover opportunities for students and educators to explore blockchain technology, + access educational resources, and join our community of builders on Avalanche. +

+
+ + {/* Photo Slideshow Section */} +
+
+
+
+
+ + + +
+

+ Photo slideshow - Add your university photos here +

+
+
+
+

+ Learn, connect, build and innovate with Avalanche. +

+
+ + {/* University Statistics */} +
+
+
+
500+
+
Universities Worldwide
+
+
+
50,000+
+
Students Engaged
+
+
+
100+
+
Faculty Members
+
+
+
+ + {/* LEARN Section */} +
+
+

LEARN

+

+ Free learning programs to feed your curiosity and advance your career. +

+
+
+ } + href="/academy" + /> + } + href="/codebase-entrepreneur-academy" + /> + } + href="https://4h8ew.share.hsforms.com/22moDWT9uT1mWJcIrdTPnZA" + external + /> +
+
+ + {/* CONNECT Section */} +
+
+

CONNECT

+

+ Students and educators — step into blockchain and join the Avalanche builder community. +

+
+ + {/* Stay in the loop */} +
+

Stay in the loop

+

+ Subscribe to our newsletter and be the first to know about upcoming university events, + internship opportunities, and more. +

+ } + href="https://4h8ew.share.hsforms.com/10iRrhSW3Q9Od8rcOda5O2A4h8ew" + external + /> +
+ + {/* Attend events */} +
+

Attend our Avalanche events

+

+ Check out our Team1 and Avalanche global events and attend an event near you. +

+ } + href="/events" + /> +
+ + {/* Join communities */} +
+

Join our communities on telegram

+

+ Make new friends and join the conversation on our telegram groups for university students and educators. +

+
+ } + href="http://t.me/avalancheacademy" + external + /> + } + href="http://t.me/avalancheacademy/4960" + external + /> +
+
+ + {/* Student Club Launchpad */} +
+

Student Club Launchpad

+

+ Want more Avalanche on your campus? Get access to resources for your club, + from guest speakers to teaching materials and funding for your event. +

+
+ } + href="/students" + /> + } + href="http://t.me/avalancheacademy/8726" + external + /> + } + href="http://t.me/avalancheacademy/8726" + external + /> +
+
+
+ + {/* CTA Section */} +
+
+

+ Ready to Start Your Blockchain Journey? +

+

+ Join thousands of students and educators already building the future of blockchain + technology with Avalanche. Start learning today and become part of our global community. +

+
+ + + + + + +
+
+
+
+ + ); +} diff --git a/app/layout.config.tsx b/app/layout.config.tsx index ec73ec2a48a..16b2892f7dc 100644 --- a/app/layout.config.tsx +++ b/app/layout.config.tsx @@ -28,6 +28,9 @@ import { ArrowLeftRight, Shield, Triangle, + GraduationCap, + BookOpen, + Users, } from 'lucide-react'; import Image from 'next/image'; import { UserButtonWrapper } from '@/components/login/user-button/UserButtonWrapper'; @@ -415,6 +418,12 @@ export const grantsMenu: LinkItemType = { ], }; +export const universityMenu: LinkItemType = { + type: 'main', + text: 'University', + url: '/university', +}; + export const eventsMenu: LinkItemType = { type: 'menu', text: 'Events', @@ -516,6 +525,7 @@ export const baseOptions: BaseLayoutProps = { consoleMenu, eventsMenu, grantsMenu, + universityMenu, integrationsMenu, userMenu, blogMenu, From df3f84ed99b19b765cbfc98986930d7c0ab0d29f Mon Sep 17 00:00:00 2001 From: Andrea Vargas Date: Thu, 2 Oct 2025 20:39:54 -0600 Subject: [PATCH 02/11] University slideshow connection with blob --- app/(home)/university/page.tsx | 26 +-- app/api/university/slideshow/route.ts | 42 ++++ components/university/UniversitySlideshow.tsx | 212 ++++++++++++++++++ server/services/blob.ts | 14 +- 4 files changed, 269 insertions(+), 25 deletions(-) create mode 100644 app/api/university/slideshow/route.ts create mode 100644 components/university/UniversitySlideshow.tsx diff --git a/app/(home)/university/page.tsx b/app/(home)/university/page.tsx index 793a89e82d6..4b92f1d0ef5 100644 --- a/app/(home)/university/page.tsx +++ b/app/(home)/university/page.tsx @@ -20,6 +20,7 @@ import { } from "lucide-react"; import Link from "next/link"; import { HeroBackground } from "@/components/landing/hero"; +import UniversitySlideshow from "@/components/university/UniversitySlideshow"; interface ProgramCardProps { title: string; @@ -155,30 +156,7 @@ export default function Page() { {/* Photo Slideshow Section */}
-
-
-
-
- - - -
-

- Photo slideshow - Add your university photos here -

-
-
-
+

Learn, connect, build and innovate with Avalanche.

diff --git a/app/api/university/slideshow/route.ts b/app/api/university/slideshow/route.ts new file mode 100644 index 00000000000..6d7faa7a573 --- /dev/null +++ b/app/api/university/slideshow/route.ts @@ -0,0 +1,42 @@ +import { NextResponse } from 'next/server'; +import { blobService } from '@/server/services/blob'; + +// GET - Fetch all slideshow photos from University-Slideshow folder +export async function GET() { + try { + // List all blobs in the University-Slideshow directory + const blobs = await blobService.listFiles('University-Slideshow/'); + + // Filter out empty entries and sort blobs by filename to ensure proper order (numbered images) + const filteredBlobs = blobs.filter(blob => { + const filename = blob.pathname.split('/').pop() || ''; + return filename && filename.length > 0 && blob.size > 0; + }); + + const sortedBlobs = filteredBlobs.sort((a, b) => { + const aName = a.pathname.split('/').pop() || ''; + const bName = b.pathname.split('/').pop() || ''; + + // Extract numbers from filenames for sorting + const aNum = parseInt(aName.match(/\d+/)?.[0] || '0'); + const bNum = parseInt(bName.match(/\d+/)?.[0] || '0'); + + return aNum - bNum; + }); + + return NextResponse.json({ + images: sortedBlobs.map(blob => ({ + url: blob.url, + filename: blob.pathname.split('/').pop(), + size: blob.size, + uploadedAt: blob.uploadedAt + })) + }); + } catch (error) { + console.error('Error fetching university slideshow photos:', error); + return NextResponse.json( + { error: 'Failed to fetch slideshow photos' }, + { status: 500 } + ); + } +} diff --git a/components/university/UniversitySlideshow.tsx b/components/university/UniversitySlideshow.tsx new file mode 100644 index 00000000000..7a32d0a7132 --- /dev/null +++ b/components/university/UniversitySlideshow.tsx @@ -0,0 +1,212 @@ +"use client"; + +import { useState, useEffect } from 'react'; +import Image from 'next/image'; +import { ChevronLeft, ChevronRight } from 'lucide-react'; +import { Button } from '@/components/ui/button'; + +interface SlideshowImage { + url: string; + filename: string; + size: number; + uploadedAt: string; +} + +interface UniversitySlideshowProps { + className?: string; +} + +export default function UniversitySlideshow({ className = "" }: UniversitySlideshowProps) { + const [images, setImages] = useState([]); + const [currentIndex, setCurrentIndex] = useState(0); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const fetchImages = async () => { + try { + const response = await fetch('/api/university/slideshow'); + if (!response.ok) { + throw new Error('Failed to fetch slideshow images'); + } + const data = await response.json(); + setImages(data.images || []); + } catch (err) { + console.error('Error fetching slideshow images:', err); + setError(err instanceof Error ? err.message : 'Failed to load images'); + } finally { + setLoading(false); + } + }; + + fetchImages(); + }, []); + + const nextImage = () => { + setCurrentIndex((prev) => (prev + 1) % images.length); + }; + + const prevImage = () => { + setCurrentIndex((prev) => (prev - 1 + images.length) % images.length); + }; + + const goToImage = (index: number) => { + setCurrentIndex(index); + }; + + // Auto-advance slideshow every 5 seconds + useEffect(() => { + if (images.length <= 1) return; + + const interval = setInterval(nextImage, 5000); + return () => clearInterval(interval); + }, [images.length]); + + if (loading) { + return ( +
+
+
+
+ + + +
+

Loading slideshow...

+
+
+
+ ); + } + + if (error) { + return ( +
+
+
+
+ + + +
+

Error loading slideshow

+

{error}

+
+
+
+ ); + } + + if (images.length === 0) { + return ( +
+
+
+
+ + + +
+

+ No slideshow images found +

+
+
+
+ ); + } + + return ( +
+
+ {/* Main Image */} +
+ {`University + + {/* Navigation Arrows */} + {images.length > 1 && ( + <> + + + + )} +
+ + {/* Dots Indicator */} + {images.length > 1 && ( +
+ {images.map((_, index) => ( +
+ )} + + {/* Image Counter */} + {images.length > 1 && ( +
+ {currentIndex + 1} / {images.length} +
+ )} +
+
+ ); +} diff --git a/server/services/blob.ts b/server/services/blob.ts index e0facc64a71..2e8ceede03c 100644 --- a/server/services/blob.ts +++ b/server/services/blob.ts @@ -1,5 +1,5 @@ -import { put, del, head, type HeadBlobResult } from '@vercel/blob'; +import { put, del, head, list, type HeadBlobResult } from '@vercel/blob'; export class BlobService { private token: string; @@ -48,6 +48,18 @@ export class BlobService { throw new Error(`Failed to fetch file: ${error instanceof Error ? error.message : "Unknown error"}`); } } + + async listFiles(prefix: string): Promise { + try { + const { blobs } = await list({ + prefix, + token: this.token, + }); + return blobs; + } catch (error) { + throw new Error(`Failed to list files: ${error instanceof Error ? error.message : "Unknown error"}`); + } + } } export const blobService = new BlobService(); \ No newline at end of file From afed9aa9721050752c9f81cd37f195b195d5b374 Mon Sep 17 00:00:00 2001 From: Andrea Vargas Date: Mon, 13 Oct 2025 10:20:12 -0600 Subject: [PATCH 03/11] clener layout --- app/(home)/university/page.tsx | 51 +++++++------------ components/university/UniversitySlideshow.tsx | 32 ++++++------ 2 files changed, 33 insertions(+), 50 deletions(-) diff --git a/app/(home)/university/page.tsx b/app/(home)/university/page.tsx index 4b92f1d0ef5..f4bbd34b762 100644 --- a/app/(home)/university/page.tsx +++ b/app/(home)/university/page.tsx @@ -157,28 +157,12 @@ export default function Page() { {/* Photo Slideshow Section */}
-

+

Learn, connect, build and innovate with Avalanche.

- {/* University Statistics */} -
-
-
-
500+
-
Universities Worldwide
-
-
-
50,000+
-
Students Engaged
-
-
-
100+
-
Faculty Members
-
-
-
+ {/* LEARN Section */}
@@ -202,7 +186,7 @@ export default function Page() { href="/codebase-entrepreneur-academy" /> } href="https://4h8ew.share.hsforms.com/22moDWT9uT1mWJcIrdTPnZA" @@ -221,15 +205,11 @@ export default function Page() { {/* Stay in the loop */} -
-

Stay in the loop

-

- Subscribe to our newsletter and be the first to know about upcoming university events, - internship opportunities, and more. -

+
} href="https://4h8ew.share.hsforms.com/10iRrhSW3Q9Od8rcOda5O2A4h8ew" external @@ -238,13 +218,9 @@ export default function Page() { {/* Attend events */}
-

Attend our Avalanche events

-

- Check out our Team1 and Avalanche global events and attend an event near you. -

} href="/events" /> @@ -256,7 +232,7 @@ export default function Page() {

Make new friends and join the conversation on our telegram groups for university students and educators.

-
+
} + href="http://t.me/avalancheacademy/4960" + external + /> + } href="http://t.me/avalancheacademy/4960" external diff --git a/components/university/UniversitySlideshow.tsx b/components/university/UniversitySlideshow.tsx index 7a32d0a7132..3daa4d5cd9a 100644 --- a/components/university/UniversitySlideshow.tsx +++ b/components/university/UniversitySlideshow.tsx @@ -64,8 +64,8 @@ export default function UniversitySlideshow({ className = "" }: UniversitySlides if (loading) { return ( -
-
+
+
-
+
+
-
+
+
-
+
+
{/* Main Image */}
- + )} @@ -188,10 +188,10 @@ export default function UniversitySlideshow({ className = "" }: UniversitySlides {images.map((_, index) => (