Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
bf6f61c
feat: introduce learn v2 behind a feature-flag
GuiBibeau Jun 16, 2025
2eba1fb
feat: review a bit of styling
GuiBibeau Jun 16, 2025
bec6eb5
feat: alighn with dev page which will have new design
GuiBibeau Jun 16, 2025
ce19045
feat: add blog posts
GuiBibeau Jun 27, 2025
5ffd95c
feat: fix the headers not using the proper tag to auto close menus
GuiBibeau Jun 27, 2025
f0d518d
feat: translations
GuiBibeau Jun 27, 2025
ae1ceba
feat: nuke old learn page badaboum with new stuff
GuiBibeau Jun 27, 2025
7407f94
feat: review a bit ux
GuiBibeau Jun 27, 2025
1de2636
feat: clean up
GuiBibeau Jun 27, 2025
bef8961
feat: clean up
GuiBibeau Jun 27, 2025
5b414fd
Merge remote-tracking branch 'upstream/main' into setup-feature-flag-…
GuiBibeau Jun 29, 2025
8a48b69
feat: remove hero to reuse existing component, clean up JS ot TS
GuiBibeau Jun 30, 2025
90c0e0e
feat: add illustration comments for future updates
GuiBibeau Jun 30, 2025
514c10f
fix: revert some fixes to menus and merge conflicts resolved
GuiBibeau Jun 30, 2025
7c25c12
feat: fix last link
GuiBibeau Jun 30, 2025
f20d874
feat: improve flow of each posts
GuiBibeau Jul 8, 2025
f5397dd
feat: finish v2, hero tbd
GuiBibeau Jul 8, 2025
840ab24
feat: WCAG improvements
GuiBibeau Jul 8, 2025
47b9685
feat: translations and refactor
GuiBibeau Jul 9, 2025
f7fef4b
feat: card name
GuiBibeau Jul 9, 2025
db8862b
feat: move components into learn folder
GuiBibeau Jul 9, 2025
87a2394
feat: a few design review implementations
GuiBibeau Jul 9, 2025
dc05f3b
feat: implement different colour for link
GuiBibeau Jul 9, 2025
421c80e
feat: remove a bit of graphic suggestions
GuiBibeau Jul 9, 2025
9b19a8c
feat: bring back components from other pages
GuiBibeau Jul 9, 2025
ebd83ef
feat: review content from John's review.
GuiBibeau Jul 10, 2025
88bd416
feat: new hero from dev section
GuiBibeau Jul 10, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,8 @@ INKEEP_API_KEY=
NEXT_PUBLIC_POSTHOG_KEY=
# Sentry
NEXT_PUBLIC_SENTRY_DSN=
SENTRY_AUTH_TOKEN=
SENTRY_AUTH_TOKEN=

# Learn V2 Development
# Set LOCAL_LEARN_V2=true to enable the learn-v2 feature locally
LOCAL_LEARN_V2=false
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ templates/*.html
public/sitemap*

coverage
.env.local
.env*.local
.source

# yalc
Expand Down
4 changes: 4 additions & 0 deletions next.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -178,6 +178,10 @@ const nextConfig = {
protocol: "https",
hostname: "assets.getriver.io",
},
{
protocol: "https",
hostname: "placehold.co",
},
],
},

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@
"@sindresorhus/slugify": "^2.2.1",
"@solana-foundation/solana-lib": "^2.40.3",
"@typeform/embed": "^5.1.0",
"@vercel/edge-config": "^1.4.0",
"ai": "^4.1.50",
"bootstrap": "^5.3.3",
"cheerio": "^1.0.0",
Expand Down
23 changes: 22 additions & 1 deletion public/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -996,7 +996,28 @@
"learn": {
"explore": "Explore {{category}} Projects on Solana",
"prev": "Previous use case",
"next": "Next use case"
"next": "Next use case",
"hero": {
"title": "Start Your Solana Journey",
"subtitle": "Learn how to use Solana without any coding knowledge",
"nav-aria-label": "Quick links",
"start-learning": "Start Learning",
"start-learning-aria-label": "Skip to tutorials section",
"build": "Build on Solana",
"build-aria-label": "Go to developer documentation"
},
"featured": {
"title": "Get Started",
"aria-label": "Featured learning paths",
"learn-more": "Learn more"
},
"tutorials": {
"title": "Complete Learning Path",
"subtitle": "Master the essentials of Web3 with these beginner-friendly tutorials—no coding required.",
"aria-label": "Complete learning path",
"tutorial-aria-label": "Tutorial {{id}}: {{title}} - {{description}}",
"step": "Step {{id}}"
}
},
"breakpoint": {
"header": {
Expand Down
270 changes: 270 additions & 0 deletions src/app/[locale]/learn-v2/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,270 @@
import React from "react";
import Image from "next/image";
import Link from "next/link";
import { serverTranslation } from "@/i18n/translation";

const featuredPosts = [
{
id: 1,
title: "What is a Crypto Wallet?",
description:
"Learn what a wallet is, why you need one, and how it works as your gateway to Web3.",
icon: "https://placehold.co/40x40/9945FF/FFFFFF?text=W",
},
{
id: 2,
title: "Getting Your First SOL",
description:
"Discover how to acquire SOL tokens to start exploring the Solana ecosystem.",
icon: "https://placehold.co/40x40/14F195/000000?text=S",
},
{
id: 3,
title: "Your First Solana Transaction",
description:
"Step-by-step guide to sending and receiving tokens on Solana safely.",
icon: "https://placehold.co/40x40/FF623A/FFFFFF?text=T",
},
];

const tutorials = [
{
id: 1,
title: "Setting Up Your Phantom Wallet",
description:
"Create your first wallet and secure it properly with recovery phrases.",
image: "https://placehold.co/400x400/9945FF/FFFFFF?text=Wallet",
},
{
id: 2,
title: "Understanding NFTs",
description:
"What are NFTs, how to buy them, and how to spot legitimate projects.",
image: "https://placehold.co/400x400/14F195/000000?text=NFT",
},
{
id: 3,
title: "Staying Safe in Web3",
description:
"Essential security tips to protect your assets from scams and hacks.",
image: "https://placehold.co/400x400/FF623A/FFFFFF?text=Security",
},
{
id: 4,
title: "Introduction to Stablecoins",
description:
"Learn about USDC and other stablecoins for safer crypto transactions.",
image: "https://placehold.co/400x400/80ECFF/000000?text=USDC",
},
{
id: 5,
title: "Making Your First Payment",
description:
"How to pay for goods and services using Solana Pay and crypto.",
image: "https://placehold.co/400x400/9945FF/FFFFFF?text=Pay",
},
{
id: 6,
title: "Exploring DeFi Basics",
description:
"Simple introduction to earning yield and swapping tokens safely.",
image: "https://placehold.co/400x400/14F195/000000?text=DeFi",
},
{
id: 7,
title: "Using Jupiter for Token Swaps",
description:
"How to exchange one token for another using Jupiter aggregator.",
image: "https://placehold.co/400x400/FF623A/FFFFFF?text=Swap",
},
{
id: 8,
title: "Understanding Transaction Fees",
description:
"Learn about Solana's low fees and how to manage transaction costs.",
image: "https://placehold.co/400x400/80ECFF/000000?text=Fees",
},
{
id: 9,
title: "Connecting to dApps Safely",
description:
"How to connect your wallet to applications and verify legitimacy.",
image: "https://placehold.co/400x400/9945FF/FFFFFF?text=dApps",
},
{
id: 10,
title: "Your Web3 Journey Continues",
description: "Next steps and resources to deepen your Solana knowledge.",
image: "https://placehold.co/400x400/14F195/000000?text=Next",
},
];

import { redirect } from "next/navigation";
import { get } from "@vercel/edge-config";

type Props = { params: Promise<{ locale: string }> };

export default async function LearnV2Page(props: Props) {
const { locale } = await props.params;

const learnV2Enabled =
process.env.LOCAL_LEARN_V2 === "true" || (await get("learn-v2"));

if (!learnV2Enabled) {
redirect("/learn");
}

const { t } = await serverTranslation(locale, ["common"]);

return (
<>
<section
className="relative min-h-[480px] flex items-center justify-center overflow-hidden mb-4"
aria-label="Introduction"
>
<div className="absolute inset-0 z-0">
<Image
src="https://placehold.co/1920x600/9945FF/FFFFFF?text=Learn+Solana"
alt=""
role="presentation"
fill
priority
style={{ objectFit: "cover" }}
/>
<div
className="absolute inset-0 bg-gradient-to-b from-black/10 to-black/40"
aria-hidden="true"
/>
</div>
<div className="container relative z-10">
<div className="text-center py-5 px-4 md:px-8">
<h1 className="h1 mb-4 text-white">{t("learn.hero.title")}</h1>
<p className="text-xl text-white/90 mb-4">
{t("learn.hero.subtitle")}
</p>
<nav
aria-label={t("learn.hero.nav-aria-label")}
className="flex gap-4 justify-center"
>
<Link
href="#tutorials"
className="text-white hover:text-white/80 underline transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-purple-600 rounded px-1"
aria-label={t("learn.hero.start-learning-aria-label")}
>
{t("learn.hero.start-learning")}
</Link>
<span className="text-white/60" aria-hidden="true">
</span>
<Link
href="/docs"
className="text-white hover:text-white/80 underline transition-colors duration-200 focus:outline-none focus:ring-2 focus:ring-white focus:ring-offset-2 focus:ring-offset-purple-600 rounded px-1"
aria-label={t("learn.hero.build-aria-label")}
>
{t("learn.hero.build")}
</Link>
</nav>
</div>
</div>
</section>

<section
id="featured"
className="py-8 md:py-12"
aria-label={t("learn.featured.aria-label")}
>
<div className="container">
<h2 className="h3 mb-4">{t("learn.featured.title")}</h2>
<div className="row g-4" role="list">
{featuredPosts.map((post) => (
<div
key={post.id}
className="col-12 col-md-6 col-lg-4"
role="listitem"
>
<Link
href={`/learn/${post.id}`}
className="no-underline d-block h-100 focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-offset-2 rounded-2xl"
aria-label={`${post.title}: ${post.description}`}
>
<article className="h-full bg-white rounded-2xl shadow-sm overflow-hidden transition-all duration-300 hover:shadow-lg hover:-translate-y-1 relative">
<div className="p-6 d-flex flex-column h-100">
<div className="flex-grow-1">
<h3 className="text-xl font-bold mb-3 text-gray-900">
{post.title}
</h3>
<p className="text-gray-600 text-sm">
{post.description}
</p>
</div>
<div className="text-right mt-4">
<span
className="text-sm text-purple-600 hover:text-purple-700"
aria-hidden="true"
>
{t("learn.featured.learn-more")} →
</span>
</div>
</div>
</article>
</Link>
</div>
))}
</div>
</div>
</section>

<section
id="tutorials"
className="pt-8 md:pt-12 pb-20 md:pb-24 bg-light"
aria-label={t("learn.tutorials.aria-label")}
>
<div className="container">
<h2 className="h3 mb-4 text-dark">{t("learn.tutorials.title")}</h2>
<p className="text-muted mb-5 col-lg-8">
{t("learn.tutorials.subtitle")}
</p>
<ol className="row g-4 list-unstyled" role="list">
{tutorials.map((tutorial) => (
<li
key={tutorial.id}
className="col-12 col-sm-6 col-md-4 col-lg-3"
role="listitem"
>
<Link
href={`/learn/tutorial/${tutorial.id}`}
className="no-underline focus:outline-none focus:ring-2 focus:ring-purple-600 focus:ring-offset-2 rounded-2xl d-block h-100"
aria-label={t("learn.tutorials.tutorial-aria-label", {
id: tutorial.id,
title: tutorial.title,
description: tutorial.description,
})}
>
<article className="h-full bg-white rounded-2xl shadow-sm overflow-visible transition-all duration-300 hover:shadow-lg hover:-translate-y-1 relative group d-flex flex-column">
<div
className="absolute -top-2 -left-2 w-10 h-10 bg-purple-600 text-white rounded-full flex items-center justify-center font-bold text-sm z-10 shadow-md transition-transform duration-300 group-hover:scale-110"
aria-hidden="true"
>
{tutorial.id}
</div>
<div className="p-6 flex-grow-1 d-flex flex-column">
<h3 className="text-lg font-bold text-gray-900 mb-2">
<span className="sr-only">
{t("learn.tutorials.step", { id: tutorial.id })}:{" "}
</span>
{tutorial.title}
</h3>
<p className="text-sm text-gray-600 flex-grow-1">
{tutorial.description}
</p>
</div>
</article>
</Link>
</li>
))}
</ol>
</div>
</section>
</>
);
}
1 change: 1 addition & 0 deletions src/pages/[locale]/[...slug].js
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,7 @@ function useAppRouterNavigation(page) {
new RegExp(`^/(?:[^/]{2}/)?developers(/)?$`),
new RegExp(`^/(?:[^/]{2}/)?developers/cookbook(/.*)?$`),
new RegExp(`^/(?:[^/]{2}/)?developers/guides(/.*)?$`),
new RegExp(`^/(?:[^/]{2}/)?learn-v2(/.*)?$`),
];
return regexes.some((regex) => regex.test(pathname));
}
12 changes: 12 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4620,6 +4620,18 @@
resolved "https://registry.yarnpkg.com/@unrs/resolver-binding-win32-x64-msvc/-/resolver-binding-win32-x64-msvc-1.3.3.tgz#eee226e5b4c4d91c862248afd24452c8698ed542"
integrity sha512-GraLbYqOJcmW1qY3osB+2YIiD62nVf2/bVLHZmrb4t/YSUwE03l7TwcDJl08T/Tm3SVhepX8RQkpzWbag/Sb4w==

"@vercel/[email protected]":
version "0.1.0"
resolved "https://registry.yarnpkg.com/@vercel/edge-config-fs/-/edge-config-fs-0.1.0.tgz#cda8327f611418c1d1cbb28c6bed02d782be928b"
integrity sha512-NRIBwfcS0bUoUbRWlNGetqjvLSwgYH/BqKqDN7vK1g32p7dN96k0712COgaz6VFizAm9b0g6IG6hR6+hc0KCPg==

"@vercel/edge-config@^1.4.0":
version "1.4.0"
resolved "https://registry.yarnpkg.com/@vercel/edge-config/-/edge-config-1.4.0.tgz#b6290037c25f0f730f46bc86c81fdd110929e9bb"
integrity sha512-69Wg5gw9DzwnyUmnjToSeLRm1nm8mCPgN0kflX8EHRHyqvzH80wPem5A8rI2LXPb2Y9tJNoqN3vXPcQhS2Wh5g==
dependencies:
"@vercel/edge-config-fs" "0.1.0"

abort-controller@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392"
Expand Down