Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e4a16c3
new config
navillanueva Nov 10, 2025
cbf39ec
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 11, 2025
1b47db7
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 11, 2025
5e8f029
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 13, 2025
2e445cd
new layout 2
navillanueva Nov 13, 2025
9907e8d
new layout 3
navillanueva Nov 13, 2025
a36bacc
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 14, 2025
3609364
merge master
navillanueva Nov 14, 2025
88a7895
merge master
navillanueva Nov 14, 2025
c34cb89
reorg
navillanueva Nov 17, 2025
d64c838
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 17, 2025
a63e499
unified /academy endpoint for all three + added new banner in navbar
navillanueva Nov 17, 2025
eb5b5c4
resolve merge conflicts
navillanueva Nov 17, 2025
48b525c
working wrapper
navillanueva Nov 17, 2025
55c522c
working text switch
navillanueva Nov 17, 2025
4a0f135
last nits
navillanueva Nov 17, 2025
9137ccf
broken link
navillanueva Nov 18, 2025
4868e0e
fixing comments sarp
navillanueva Nov 18, 2025
5a194f6
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 18, 2025
0d6a60e
nits
navillanueva Nov 18, 2025
1570815
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 19, 2025
c23e78a
resolved merge conflicts
navillanueva Nov 19, 2025
2691c97
Merge branch 'master' into new-academy-layout
owenwahlgren Nov 19, 2025
a0264fa
resolved build errors
navillanueva Nov 19, 2025
158ba56
Merge remote-tracking branch 'origin/master' into new-academy-layout
navillanueva Nov 20, 2025
880b480
bubble navigation button takes you directly to the learning tree
navillanueva Nov 20, 2025
6e94d9e
scroll feature
navillanueva Nov 20, 2025
d908665
hover effect
navillanueva Nov 20, 2025
7a90f69
hover effect improvement
navillanueva Nov 20, 2025
74dfc09
nits
navillanueva Nov 20, 2025
0efd27b
nits
navillanueva Nov 20, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
11 changes: 0 additions & 11 deletions app/(home)/academy/avalanche-developer.config.ts

This file was deleted.

12 changes: 12 additions & 0 deletions app/(home)/academy/avalanche-l1/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import type { AcademyLandingPageConfig } from '@/components/academy/shared/academy-types';

export const avalancheDeveloperAcademyLandingPageConfig: AcademyLandingPageConfig = {
id: 'academy-avalanche-l1',
name: 'Avalanche L1 Academy',
heroTitle: 'Avalanche',
heroAccent: 'L1',
heroAccentWords: ['L1 Developer', 'Blockchain', 'Entrepreneur'],
heroDescription: 'Learn through our Avalanche L1, Blockchain and Entrepreneur Academies. Use the navigation buttons below to switch between academies and discover each learning tree with courses tailored to your journey.',
pathType: 'avalanche',
showBlogs: true,
};
151 changes: 151 additions & 0 deletions app/(home)/academy/avalanche-l1/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
import type { Metadata } from "next";
import Link from "next/link";
import { BookOpen, ArrowRight, ExternalLink } from "lucide-react";
import { createMetadata } from "@/utils/metadata";
import { blog } from "@/lib/source";
import { AcademyLayout } from "@/components/academy/shared/academy-layout";
import { avalancheDeveloperAcademyLandingPageConfig } from "./config";
import { entrepreneurAcademyLandingPageConfig } from "../entrepreneur/config";
import type { AcademyPathType } from "@/components/academy/shared/academy-types";
import { Suspense } from "react";

export const metadata: Metadata = createMetadata({
title: "Avalanche L1 Academy",
description:
"Learn Avalanche L1 development with courses designed for builders launching custom blockchains",
openGraph: {
url: "/academy/avalanche-l1",
images: {
url: "/api/og/academy",
width: 1200,
height: 630,
alt: "Avalanche L1 Academy",
},
},
twitter: {
images: {
url: "/api/og/academy",
width: 1200,
height: 630,
alt: "Avalanche L1 Academy",
},
},
});

type PageProps = {
searchParams?: {
path?: string;
};
};

const isPathType = (value: string | undefined): value is AcademyPathType => {
return value === "avalanche" || value === "blockchain" || value === "entrepreneur";
};

export default function AvalancheAcademyPage({ searchParams }: PageProps): React.ReactElement {
// Get all guides server-side
const blogPages = [...blog.getPages()]
.sort(
(a, b) =>
new Date((b.data.date as string) ?? b.url).getTime() -
new Date((a.data.date as string) ?? a.url).getTime()
)
.slice(0, 9); // Limit to 9 guides

// Serialize blog data to pass to client component
const blogs = blogPages.map((page) => ({
url: page.url,
data: {
title: page.data.title || "Untitled",
description: page.data.description || "",
topics: (page.data.topics as string[]) || [],
date:
page.data.date instanceof Date
? page.data.date.toISOString()
: (page.data.date as string) || "",
},
file: {
name: page.url, // Use URL instead of file.name in v16
},
}));

const { features } = entrepreneurAcademyLandingPageConfig;

const entrepreneurBlogsFromConfig = (features?.highlights?.blogs ?? []).map((blogEntry) => ({
url: blogEntry.link,
data: {
title: blogEntry.title,
description: blogEntry.description,
topics: ['Entrepreneur'],
date: blogEntry.date || '',
},
file: {
name: blogEntry.id,
},
}));

const entrepreneurHighlights = features?.highlights ? (
<div className="mb-16">
<div className="flex items-center gap-3 mb-8">
<BookOpen className="h-6 w-6 text-red-600" />
<h2 className="text-2xl font-bold text-zinc-900 dark:text-white">
{features.highlights.title}
</h2>
</div>

<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
{features.highlights.blogs.map((blog) => (
<Link
key={blog.id}
href={blog.link}
target="_blank"
rel="noopener noreferrer"
className="group relative flex flex-col rounded-xl border border-zinc-200 dark:border-zinc-800 bg-white dark:bg-zinc-900 p-6 hover:shadow-lg transition-all duration-200 cursor-pointer"
>
<div className="flex items-start justify-between mb-4">
<h3 className="font-semibold text-lg text-zinc-900 dark:text-white group-hover:text-red-600 dark:group-hover:text-red-500 transition-colors pr-4">
{blog.title}
</h3>
<ExternalLink className="h-5 w-5 text-zinc-400 group-hover:text-red-600 transition-colors flex-shrink-0" />
</div>

{blog.date && (
<p className="text-sm text-zinc-500 dark:text-zinc-400 mb-3">
{blog.date}
</p>
)}

<p className="text-sm text-zinc-600 dark:text-zinc-400 flex-grow">
{blog.description}
</p>

<div className="mt-4 inline-flex items-center gap-1 text-sm font-medium text-red-600 group-hover:text-red-700 dark:text-red-500 dark:hover:text-red-400">
Read article
<ArrowRight className="h-4 w-4" />
</div>
</Link>
))}
</div>
</div>
) : null;

const initialPathType = isPathType(searchParams?.path) ? searchParams?.path : undefined;

return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center"><div className="text-zinc-600 dark:text-zinc-400">Loading...</div></div>}>
<AcademyLayout
config={avalancheDeveloperAcademyLandingPageConfig}
blogs={blogs}
blogsByPath={{
avalanche: blogs,
blockchain: blogs,
entrepreneur: entrepreneurBlogsFromConfig.length > 0 ? entrepreneurBlogsFromConfig : blogs,
}}
afterLearningPathByPath={{
entrepreneur: entrepreneurHighlights,
}}
initialPathType={initialPathType}
/>
</Suspense>
);
}
13 changes: 13 additions & 0 deletions app/(home)/academy/blockchain/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { AcademyLandingPageConfig } from '@/components/academy/shared/academy-types';

export const blockchainAcademyLandingPageConfig: AcademyLandingPageConfig = {
id: 'academy-blockchain',
name: 'Blockchain Academy',
heroTitle: 'Blockchain',
heroAccent: 'Developer',
heroAccentWords: ['Developer', 'Smart Contracts', 'Privacy'],
heroDescription: 'Start your blockchain development journey from the ground up. Master the fundamentals of blockchain technology, learn Solidity programming, and explore privacy-enhancing technologies to build secure and innovative decentralized applications.',
pathType: 'blockchain',
showBlogs: true,
};

72 changes: 72 additions & 0 deletions app/(home)/academy/blockchain/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
import type { Metadata } from "next";
import { createMetadata } from "@/utils/metadata";
import { blog } from "@/lib/source";
import { AcademyLayout } from "@/components/academy/shared/academy-layout";
import { blockchainAcademyLandingPageConfig } from "./config";
import { Suspense } from "react";

export const metadata: Metadata = createMetadata({
title: "Blockchain Academy",
description:
"Master blockchain fundamentals and smart contract development from the ground up",
openGraph: {
url: "/academy/blockchain",
images: {
url: "/api/og/academy",
width: 1200,
height: 630,
alt: "Blockchain Academy",
},
},
twitter: {
images: {
url: "/api/og/academy",
width: 1200,
height: 630,
alt: "Blockchain Academy",
},
},
});

export default function BlockchainAcademyPage(): React.ReactElement {
// Get all guides server-side
const blogPages = [...blog.getPages()]
.sort(
(a, b) =>
new Date((b.data.date as string) ?? b.url).getTime() -
new Date((a.data.date as string) ?? a.url).getTime()
)
.slice(0, 9); // Limit to 9 guides

// Serialize blog data to pass to client component
const blogs = blogPages.map((page) => ({
url: page.url,
data: {
title: page.data.title || "Untitled",
description: page.data.description || "",
topics: (page.data.topics as string[]) || [],
date:
page.data.date instanceof Date
? page.data.date.toISOString()
: (page.data.date as string) || "",
},
file: {
name: page.url, // Use URL instead of file.name in v16
},
}));

return (
<Suspense fallback={<div className="min-h-screen flex items-center justify-center"><div className="text-zinc-600 dark:text-zinc-400">Loading...</div></div>}>
<AcademyLayout
config={blockchainAcademyLandingPageConfig}
blogs={blogs}
blogsByPath={{
avalanche: blogs,
blockchain: blogs,
entrepreneur: blogs,
}}
/>
</Suspense>
);
}

Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import type { AcademyLandingPageConfig } from '@/components/academy/shared/academy-types';

export const codebaseEntrepreneurLandingPageConfig: AcademyLandingPageConfig = {
id: 'codebase-entrepreneur',
name: 'Codebase Entrepreneur Academy',
heroTitle: 'Codebase',
export const entrepreneurAcademyLandingPageConfig: AcademyLandingPageConfig = {
id: 'academy-entrepreneur',
name: 'Entrepreneur Academy',
heroTitle: 'Avalanche',
heroAccent: 'Entrepreneur',
heroAccentWords: ['Entrepreneur', 'Founder', 'Builder'],
heroDescription: 'Join the next generation of Web3 entrepreneurs. Learn how to build, launch, and scale your blockchain startup with guidance from industry experts and successful founders.',
pathType: 'entrepreneur',
showBlogs: false,
showBlogs: true,
features: {
codebaseBlogs: {
title: 'Codebase Blogs',
highlights: {
title: 'Entrepreneur Highlights',
blogs: [
{
id: 'suzaku-raises-1-5m',
Expand Down
Loading