Update payments and tokenization pages with new components and layout adjustments

- Changed the payments page to use `index.page.tsx` instead of `index.md` for better component integration.
- Added `AdvantagesSection` and `ProjectCards` components to both payments and tokenization pages to enhance content presentation.
- Adjusted CSS styles for improved responsiveness and layout consistency across different screen sizes.
- Removed outdated security card implementation in tokenization page and replaced it with a more streamlined advantages section.
This commit is contained in:
akcodez
2025-08-25 11:54:17 -07:00
parent 2282eb86b6
commit e94a5a0269
14 changed files with 662 additions and 163 deletions

View File

@@ -1,5 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { useThemeHooks } from "@redocly/theme/core/hooks"; import { useThemeHooks } from "@redocly/theme/core/hooks";
import { AdvantagesSection } from "shared/components/advantages-section";
import { ProjectCards } from "shared/components/project-cards";
export const frontmatter = { export const frontmatter = {
seo: { seo: {
@@ -18,6 +20,90 @@ const PaymentsPage: React.FC = () => {
src: "https://www.youtube.com/embed/e2Iwsk37LMk?si=20-m6aQOWpaiQDW7", src: "https://www.youtube.com/embed/e2Iwsk37LMk?si=20-m6aQOWpaiQDW7",
}; };
const paymentAdvantages = [
{
id: "cross-border-stablecoin",
title: "Enable Cross-Border Stablecoin Payments",
contents: [
{
subtitle: "RLUSD and USDC support",
description: "",
},
{
subtitle: "Easily receive, store, convert, issue and send stablecoins",
description: "",
},
],
},
{
id: "reliable-infrastructure",
title: "Access Reliable Payments Infrastructure",
contents: [
{
subtitle: "100% uptime since 2012",
description: "",
},
{
subtitle: "Over $1.7T+ in value transferred",
description: "",
},
],
},
{
id: "efficient-payments",
title: "Move Money Efficiently",
contents: [
{
subtitle: "Transactions settle in 3-5 seconds",
description: "",
},
{
subtitle: "Fractions of a cent per transaction",
description: "",
},
],
},
];
const paymentProjects = [
{
id: "ripple-usd",
label: "Ripple USD",
url: "#",
description: "RLUSD, Ripple's enterprise-grade stablecoin, is live on XRPL and fully backed by USD deposits.",
},
{
id: "usdc",
label: "USDC",
url: "#",
description: "USDC, issued by Circle, is the world's largest regulated dollar stablecoin and now live on XRPL.",
},
{
id: "usdb",
label: "USDB",
url: "#",
description: "USDB, by Braza Group, is a USD-pegged stablecoin backed by U.S. and Brazilian bonds.",
},
{
id: "europ",
label: "EUROP",
url: "#",
description: "EUROP, issued by Schuman Financial, is the first MiCA-compliant euro stablecoin on XRPL.",
},
{
id: "xsgd",
label: "XSGD",
url: "#",
description: "XSGD, from StraitsX, is a Singapore Dollar-backed stablecoin regulated by MAS.",
},
{
id: "audd",
label: "AUDD",
url: "#",
description: "AUDD, an Australian dollar stablecoin, is live on XRPL and backed 1:1 with AUD.",
},
];
return ( return (
<main className="use-case-payments"> <main className="use-case-payments">
<section className="use-case-payments__hero"> <section className="use-case-payments__hero">
@@ -47,6 +133,20 @@ const PaymentsPage: React.FC = () => {
</p> </p>
</div> </div>
</section> </section>
<AdvantagesSection
title="Why Choose XRPL Payments Suite for Your Payment Rails?"
advantages={paymentAdvantages}
useLinks={false}
className="payments-advantages-spacing"
/>
<ProjectCards
title="Enterprise-Grade Stablecoins, Issued Natively on XRPL"
projects={paymentProjects}
showCarousel={false}
className="mt-12 px-0"
/>
</main> </main>
); );
}; };

View File

@@ -2,6 +2,8 @@ import React, { useRef, useState } from "react";
import { useThemeHooks } from '@redocly/theme/core/hooks'; import { useThemeHooks } from '@redocly/theme/core/hooks';
import { NavList } from "shared/components/nav-list"; import { NavList } from "shared/components/nav-list";
import { Link } from "@redocly/theme/components/Link/Link"; import { Link } from "@redocly/theme/components/Link/Link";
import { AdvantagesSection } from "shared/components/advantages-section";
import { ProjectCards } from "shared/components/project-cards";
export const frontmatter = { export const frontmatter = {
seo: { seo: {
@@ -34,18 +36,7 @@ const useCases = [
}, },
]; ];
const SecurityAdvantageCard = (securityAdvantageContents) => {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return securityAdvantageContents.map((content) => (
<div key={content.subtitle}>
<Link to={content.href}><h5 className="card-subhead">{translate(content.subtitle)}:</h5></Link>
<div className="card-text">
{translate(content.description)}
</div>
</div>
))
}
const securityAdvantages = [ const securityAdvantages = [
{ {
@@ -184,70 +175,7 @@ const articles = [
// TODO: Add more articles that aren't focused on price speculation // TODO: Add more articles that aren't focused on price speculation
]; ];
const FeaturedProjects = () => {
const [currentIndex, setCurrentIndex] = useState(0);
const handlePrev = () => {
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
};
const handleNext = () => {
if (currentIndex < projects.length - 3) {
setCurrentIndex(currentIndex + 1);
}
};
const ProjectCard = ({ project, index }) => (
<a
className={`col card float-up-on-hover ${
index % 2 === 0 ? "even" : "odd"
}`}
target="_blank"
href={project.url}
>
<div className="project-logo d-flex justify-content-center align-items-center mb-8">
<img className={project.id} alt={project.label} />
</div>
<div className="project-name h5 align-self-center">{project.label}</div>
</a>
);
return (
<div className="featured-projects">
<div className="project-cards-container card-grid card-grid-3xN">
<ProjectCard project={projects[currentIndex]} index={currentIndex} />
<ProjectCard
project={projects[currentIndex + 1]}
index={currentIndex + 1}
/>
<ProjectCard
project={projects[currentIndex + 2]}
index={currentIndex + 2}
/>
</div>
<div className="arrow-wrapper d-flex justify-content-center mt-16">
<button
className={`arrow-button prev ${
currentIndex > 0 ? "hover-color" : ""
}`}
onClick={handlePrev}
>
<img alt="left arrow" />
</button>
<button
className={`arrow-button next ${
currentIndex < projects.length - 3 ? "hover-color" : ""
}`}
onClick={handleNext}
>
<img alt="right arrow" />
</button>
</div>
</div>
);
};
export default function Tokenization() { export default function Tokenization() {
const modalRef = useRef(null); const modalRef = useRef(null);
@@ -303,29 +231,12 @@ export default function Tokenization() {
</div> </div>
</div> </div>
</section> </section>
<section className="container-new py-20"> <AdvantagesSection
<div className="d-flex flex-column-reverse"> title="Security advantages"
<p className="mb-16"> description="Check out the security features you can tap into right from the chain without the hassle of piecing together smart contracts."
{translate( advantages={securityAdvantages}
"Check out the security features you can tap into right from the chain without the hassle of piecing together smart contracts." useLinks
)} />
</p>
<h4 className="eyebrow mb-8">{translate("Security advantages")}</h4>
</div>
<div
className="security-card-grid nav card-grid"
id="security-features"
>
{securityAdvantages.map((advantage) => (
<div className="security-card" key={advantage.id}>
<div className="card-body p-6">
<h4 className="card-title h6">{translate(advantage.title)}</h4>
{SecurityAdvantageCard(advantage.contents)}
</div>
</div>
))}
</div>
</section>
<section className="container-new py-20"> <section className="container-new py-20">
<div className="d-flex flex-column-reverse"> <div className="d-flex flex-column-reverse">
@@ -343,16 +254,11 @@ export default function Tokenization() {
))} ))}
</div> </div>
</section> </section>
<section className="container-new py-20"> <ProjectCards
<div className="d-flex flex-column-reverse"> title="Featured real world projects"
<h4 className="eyebrow mb-16"> projects={projects}
{translate("Featured real world projects")} showCarousel={true}
</h4> />
</div>
<div className="project-cards">
<FeaturedProjects />
</div>
</section>
<section className="container-new py-20"> <section className="container-new py-20">
<div className="d-flex flex-column-reverse"> <div className="d-flex flex-column-reverse">

View File

@@ -0,0 +1,99 @@
import React from "react";
import { useThemeHooks } from '@redocly/theme/core/hooks';
import { Link } from "@redocly/theme/components/Link/Link";
interface AdvantageContent {
href?: string;
subtitle: string;
description?: string;
}
interface Advantage {
id: string;
title: string;
contents: AdvantageContent[];
}
interface AdvantagesSectionProps {
title: string;
description?: string;
advantages: Advantage[];
className?: string;
useLinks?: boolean; // New prop to control whether to use links or bullet points
}
const AdvantageCard = (advantageContents: AdvantageContent[], useLinks: boolean = true) => {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
if (useLinks) {
// Original tokenization style with links
return advantageContents.map((content) => (
<div key={content.subtitle}>
<Link to={content.href}><h5 className="card-subhead">{translate(content.subtitle)}:</h5></Link>
<div className="card-text">
{translate(content?.description || "")}
</div>
</div>
))
} else {
// Payments style with bullet points
return (
<ul className="advantages-list">
{advantageContents.map((content) => (
<li key={content.subtitle} className="advantage-item">
<strong>{translate(content.subtitle)}</strong>
{content.description && (
<span className="advantage-description">{translate(content.description)}</span>
)}
</li>
))}
</ul>
)
}
}
export const AdvantagesSection: React.FC<AdvantagesSectionProps> = ({
title,
description,
advantages,
className = "",
useLinks = true
}) => {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
// Dynamic grid class based on number of advantages
const getGridClass = () => {
const count = advantages.length;
if (count === 3) return "security-card-grid-3";
if (count === 4) return "security-card-grid-4";
return "security-card-grid"; // fallback to original
};
const headerSpacingClass = useLinks ? "mb-16" : "mb-8"; // 32px for bullet version, 64px for links
return (
<section className={`advantages-section container-new py-20 ${className}`}>
<div className="d-flex flex-column-reverse">
<p className={headerSpacingClass}>
{translate(description)}
</p>
<h4 className="eyebrow mb-8">{translate(title)}</h4>
</div>
<div
className={`${getGridClass()} nav card-grid`}
id="security-features"
>
{advantages.map((advantage) => (
<div className="security-card" key={advantage.id}>
<div className="card-body p-6">
<h4 className="card-title h6">{translate(advantage.title)}</h4>
{AdvantageCard(advantage.contents, useLinks)}
</div>
</div>
))}
</div>
</section>
);
};

View File

@@ -0,0 +1,148 @@
import React, { useState } from "react";
import { useThemeHooks } from '@redocly/theme/core/hooks';
interface Project {
id: string;
label: string;
url: string;
description?: string; // New optional field for payments page
}
interface ProjectCardsProps {
title: string;
projects: Project[];
showCarousel?: boolean; // true for tokenization (carousel), false for payments (grid)
className?: string;
}
const ProjectCard = ({ project, index, showCarousel = true }: {
project: Project;
index: number;
showCarousel?: boolean;
}) => {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<a
className={`col card float-up-on-hover ${
showCarousel
? (index % 2 === 0 ? "even" : "odd")
: `payments-project-card ${index % 2 === 0 ? "odd" : "even"}`
}`}
target="_blank"
href={project.url}
>
<div className="project-logo d-flex justify-content-center align-items-center">
<img className={project.id} alt={project.label} />
</div>
{!showCarousel && project.description && (
<div className="project-description">
{(() => {
const words = project.description.split(' ');
const firstWord = words[0];
const restOfText = words.slice(1).join(' ');
return (
<>
<strong className="first-word">{firstWord}</strong>
{restOfText && <span className="rest-text"> {restOfText}</span>}
</>
);
})()}
</div>
)}
</a>
);
};
const FeaturedProjectsCarousel = ({ projects }: { projects: Project[] }) => {
const [currentIndex, setCurrentIndex] = useState(0);
const handlePrev = () => {
if (currentIndex > 0) {
setCurrentIndex(currentIndex - 1);
}
};
const handleNext = () => {
if (currentIndex < projects.length - 3) {
setCurrentIndex(currentIndex + 1);
}
};
return (
<div className="featured-projects">
<div className="project-cards-container card-grid card-grid-3xN">
<ProjectCard project={projects[currentIndex]} index={currentIndex} />
<ProjectCard
project={projects[currentIndex + 1]}
index={currentIndex + 1}
/>
<ProjectCard
project={projects[currentIndex + 2]}
index={currentIndex + 2}
/>
</div>
<div className="arrow-wrapper d-flex justify-content-center mt-16">
<button
className={`arrow-button prev ${
currentIndex > 0 ? "hover-color" : ""
}`}
onClick={handlePrev}
>
<img alt="left arrow" />
</button>
<button
className={`arrow-button next ${
currentIndex < projects.length - 3 ? "hover-color" : ""
}`}
onClick={handleNext}
>
<img alt="right arrow" />
</button>
</div>
</div>
);
};
const ProjectsGrid = ({ projects }: { projects: Project[] }) => {
return (
<div className="payments-projects-grid card-grid">
{projects.map((project, index) => (
<ProjectCard
key={project.id}
project={project}
index={index}
showCarousel={false}
/>
))}
</div>
);
};
export const ProjectCards: React.FC<ProjectCardsProps> = ({
title,
projects,
showCarousel = true,
className = ""
}) => {
const { useTranslate } = useThemeHooks();
const { translate } = useTranslate();
return (
<section className={`container-new py-20 ${className}`}>
<div className="d-flex flex-column-reverse">
<h4 className="eyebrow mb-16">
{translate(title)}
</h4>
</div>
<div className="project-cards">
{showCarousel ? (
<FeaturedProjectsCarousel projects={projects} />
) : (
<ProjectsGrid projects={projects} />
)}
</div>
</section>
);
};

View File

@@ -16,7 +16,9 @@
- page: docs/use-cases/index.md - page: docs/use-cases/index.md
expanded: false expanded: false
items: items:
- page: docs/use-cases/payments/index.md - page: docs/use-cases/payments/index.page.tsx
label: Payments
labelTranslationKey: sidebar.docs.use-cases.payments
expanded: false expanded: false
items: items:
- page: docs/use-cases/payments/peer-to-peer-payments-uc.md - page: docs/use-cases/payments/peer-to-peer-payments-uc.md

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

View File

@@ -728,55 +728,7 @@ html.light {
font-weight: 400; font-weight: 400;
} }
/* Work-around for border gradient and radius */
.security-card {
position: relative;
border-radius: 0.5rem;
background-color: transparent;
white-space: normal;
box-sizing: border-box;
}
.security-card::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(90deg, #d91aff 26.41%, #1aa4ff 100.32%);
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask-composite: xor;
z-index: -1;
}
.security-card p {
margin-bottom: 0 !important;
}
.security-card .h6 {
@include media-breakpoint-down(sm) {
font-size: 1.25rem;
}
}
.security-card-grid {
gap: 1rem;
grid-template-columns: repeat(2, 1fr);
@media (min-width: 1200px) {
grid-template-columns: repeat(4, 1fr);
}
@media (max-width: 768px) {
grid-template-columns: repeat(1, 1fr);
}
}
.video-external-link .link-text { .video-external-link .link-text {
margin-left: 0.25rem; margin-left: 0.25rem;
@@ -994,7 +946,8 @@ body,
} }
.use-case-payments { .use-case-payments {
margin: 100px 120px; // Reduced margins to account for 284px sidebar
margin: 80px 100px;
&__hero { &__hero {
display: flex; display: flex;
@@ -1044,13 +997,39 @@ body,
} }
} }
// Responsive design // Responsive design accounting for 284px sidebar
@media (max-width: 1200px) {
margin: 80px 60px;
&__hero {
gap: 60px;
}
.video-content iframe {
min-height: 350px;
max-height: 450px;
}
}
@media (max-width: 1024px) {
margin: 60px 40px;
&__hero {
gap: 40px;
}
.video-content iframe {
min-height: 300px;
max-height: 400px;
}
}
@media (max-width: 768px) { @media (max-width: 768px) {
margin: 50px 20px; margin: 40px 20px;
&__hero { &__hero {
flex-direction: column; flex-direction: column;
gap: 40px; gap: 32px;
min-height: auto; min-height: auto;
} }
@@ -1061,6 +1040,271 @@ body,
.video-content iframe { .video-content iframe {
min-height: 250px; min-height: 250px;
max-height: 350px;
} }
} }
@media (max-width: 480px) {
margin: 20px 15px;
&__hero {
gap: 24px;
}
.video-content iframe {
min-height: 200px;
max-height: 280px;
}
.text-content {
.eyebrow {
font-size: 16px;
h2 {
font-size: 32px;
}
p {
font-size: 18px;
}
}
}
}
}
/* Shared styles for AdvantagesSection component - used across multiple pages */
.advantages-section {
/* Work-around for border gradient and radius */
.security-card {
position: relative;
border-radius: 0.5rem;
background-color: transparent;
white-space: normal;
box-sizing: border-box;
}
.security-card::before {
content: '';
position: absolute;
top: 0;
right: 0;
bottom: 0;
left: 0;
border-radius: inherit;
padding: 1px;
background: linear-gradient(90deg, #d91aff 26.41%, #1aa4ff 100.32%);
mask:
linear-gradient(#fff 0 0) content-box,
linear-gradient(#fff 0 0);
mask-composite: exclude;
-webkit-mask-composite: xor;
z-index: -1;
}
.security-card p {
margin-bottom: 0 !important;
}
.security-card .h6 {
@include media-breakpoint-down(sm) {
font-size: 1.25rem;
}
}
.security-card-grid,
.security-card-grid-3,
.security-card-grid-4 {
gap: 1rem;
grid-template-columns: repeat(2, 1fr);
@media (max-width: 768px) {
grid-template-columns: repeat(1, 1fr);
}
}
.security-card-grid {
@media (min-width: 1200px) {
grid-template-columns: repeat(4, 1fr);
}
}
.security-card-grid-3 {
gap: 2.5rem; /* 40px gap for 3-column layout */
@media (min-width: 1200px) {
grid-template-columns: repeat(3, 1fr);
}
}
.security-card-grid-4 {
@media (min-width: 1200px) {
grid-template-columns: repeat(4, 1fr);
}
}
/* Bullet point styles for payments page */
.advantages-list {
list-style: none;
padding: 0;
margin: 0;
}
.advantage-item {
position: relative;
padding-left: 20px;
margin-bottom: 16px;
&::before {
content: '';
position: absolute;
left: 0;
top: 0;
color: #7919FF;
font-weight: bold;
font-size: 16px;
}
strong {
display: block;
margin-bottom: 4px;
color: #E0E0E1;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 150% */
}
.advantage-description {
display: block;
color: #E0E0E1;
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px; /* 150% */
}
}
/* Card title typography for bullet version */
.security-card .card-title {
color: var(--Black-Black-0, #FFF);
font-size: 18px;
font-style: normal;
font-weight: 700;
line-height: 125%; /* 22.5px */
}
}
/* Specific spacing for payments page between hero and advantages sections */
.use-case-payments .payments-advantages-spacing {
padding-top: 180px;
padding-bottom: 20px; /* Keep standard bottom padding */
padding-right: 0px;
padding-left: 0px;
}
/* Project cards styles for payments page */
.use-case-payments .payments-projects-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 40px 40px; /* 40px horizontal, 48px vertical */
row-gap: 48px;
@media (min-width: 1200px) {
grid-template-columns: repeat(3, 1fr);
}
@media (max-width: 768px) {
grid-template-columns: repeat(1, 1fr);
gap: 32px;
}
}
.use-case-payments .payments-project-card {
min-height: 260px;
position: relative;
padding: 32px;
.project-description {
padding: 0 1rem;
text-align: left; /* Changed from center to left */
.first-word {
color: #FFF;
font-size: 16px;
font-style: normal;
font-weight: 700;
line-height: 24px; /* 150% */
}
.rest-text {
color: var(--XRPL-Primary-White, #FFF);
font-family: "Work Sans";
font-size: 16px;
font-style: normal;
font-weight: 400;
line-height: 24px;
}
}
/* Add the top border gradient for payments page */
&::before {
content: '';
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 0.25rem;
border-top-left-radius: 0.5rem;
border-top-right-radius: 0.5rem;
}
/* Stablecoin images */
.project-logo {
margin-Bottom: 32px;
img.ripple-usd {
content: url('../img/uses/payments/rlusd.png');
width: 180px;
height: 50px;
}
img.usdc {
content: url('../img/uses/payments/usdc.png');
width: 50px;
height: 50px;
}
img.usdb {
content: url('../img/uses/payments/usdb.png');
width: 126px;
height: 50px;
}
img.europ {
content: url('../img/uses/payments/eroup.png');
width: 147px;
height: 50px;
}
img.xsgd {
content: url('../img/uses/payments/XSGD.png');
width: 50px;
height: 50px;
}
img.audd {
content: url('../img/uses/payments/AUDD.png');
width: 50px;
height: 50px;
}
}
/* Alternating gradients for payments page */
&.odd::before {
background: linear-gradient(90deg, #D91AFF 26.41%, #1AA4FF 100.32%);
}
&.even::before {
background: linear-gradient(90deg, #4BB7FF -0.32%, #32E685 30.61%);
}
} }

View File

@@ -72,7 +72,7 @@
- group: Use Cases - group: Use Cases
groupTranslationKey: topnav.docs.use-cases groupTranslationKey: topnav.docs.use-cases
items: items:
- page: ./docs/use-cases/payments/index.md - page: ./docs/use-cases/payments/index.page.tsx
label: Payments label: Payments
labelTranslationKey: topnav.docs.payments labelTranslationKey: topnav.docs.payments
- page: ./docs/use-cases/tokenization/index.page.tsx - page: ./docs/use-cases/tokenization/index.page.tsx