Skip to content

Commit de8af0e

Browse files
authored
Adds Coretime guide and FAQ improvements (#40)
Implements a Coretime guide page with information on obtaining coretime in Paseo. Refactors the FAQ section to support linking to internal pages and external resources, enhancing user navigation and information access.
1 parent 8c4dac3 commit de8af0e

7 files changed

Lines changed: 242 additions & 44 deletions

File tree

src/app/coretime/page.tsx

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
import { FloatingNav } from "@/components/layouts/FloatingNav";
2+
import { Footer } from "@/components/layouts/Footer";
3+
import { CoretimeSection } from "@/components/sections/CoretimeSection";
4+
5+
export const metadata = {
6+
title: "How to Obtain Coretime | Paseo Testnet",
7+
description:
8+
"Learn how to obtain, assign, and manage coretime for your parachain on Paseo testnet.",
9+
};
10+
11+
export default function CoretimePage() {
12+
return (
13+
<div className="min-h-screen bg-background text-foreground">
14+
<FloatingNav />
15+
<CoretimeSection />
16+
<Footer />
17+
</div>
18+
);
19+
}
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
"use client";
2+
3+
import { InfoCard } from "@/components/shared/InfoCard";
4+
import { Section } from "@/components/shared/Section";
5+
import { SectionHeader } from "@/components/shared/SectionHeader";
6+
import { CORETIME_CTA, CORETIME_STEPS } from "@/constants/coretime";
7+
import { URLS } from "@/constants/urls";
8+
import { ExternalLink } from "lucide-react";
9+
10+
export function CoretimeSection() {
11+
return (
12+
<Section className="pt-32">
13+
<SectionHeader
14+
title="How to Obtain Coretime in Paseo"
15+
description="Obtaining coretime in Paseo follows the same process as production networks. All extrinsics are called on the Coretime chain as part of the broker pallet."
16+
/>
17+
18+
<div className="max-w-4xl mx-auto space-y-8">
19+
{CORETIME_STEPS.map((step) => (
20+
<InfoCard
21+
key={step.number}
22+
icon={step.icon}
23+
title={step.title}
24+
label={`Step ${step.number}`}
25+
description={step.content}
26+
note={step.note}
27+
layout="inline"
28+
className="hover:shadow-lg"
29+
/>
30+
))}
31+
32+
<InfoCard
33+
icon={CORETIME_CTA.icon}
34+
title={CORETIME_CTA.title}
35+
description={CORETIME_CTA.description}
36+
layout="inline"
37+
className="border-primary/20 bg-gradient-to-br from-primary/5 to-transparent hover:shadow-lg"
38+
footer={
39+
<a
40+
href={URLS.regionXHub}
41+
target="_blank"
42+
rel="noopener noreferrer"
43+
className="inline-flex items-center gap-2 text-primary hover:underline font-medium"
44+
>
45+
{CORETIME_CTA.linkLabel}
46+
<ExternalLink className="w-4 h-4" />
47+
</a>
48+
}
49+
/>
50+
</div>
51+
</Section>
52+
);
53+
}

src/components/sections/FAQSection.tsx

Lines changed: 31 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,38 +1,13 @@
11
"use client";
22

3+
import Link from "next/link";
34
import {
45
Accordion,
56
AccordionContent,
67
AccordionItem,
78
AccordionTrigger,
89
} from "@/components/ui/accordion";
9-
import { FAQ_CONTENT } from "@/constants/faq";
10-
11-
// Helper function to render text with clickable links
12-
const renderAnswerWithLinks = (text: string): React.ReactNode => {
13-
const urlRegex = /(https?:\/\/[^\s)]+)/g;
14-
const parts = text.split(urlRegex);
15-
16-
return parts.map((part, index) => {
17-
if (!part.match(urlRegex)) {
18-
return part;
19-
}
20-
21-
const key = `${index}-${part}`;
22-
23-
return (
24-
<a
25-
key={key}
26-
href={part}
27-
target="_blank"
28-
rel="noopener noreferrer"
29-
className="text-primary hover:text-primary/80 underline break-words"
30-
>
31-
{part}
32-
</a>
33-
);
34-
});
35-
};
10+
import { FAQ_CONTENT, type FAQLink } from "@/constants/faq";
3611

3712
export function FAQSection() {
3813
return (
@@ -52,14 +27,41 @@ export function FAQSection() {
5227
<AccordionItem
5328
key={item.question}
5429
value={`item-${index}`}
55-
className="bg-card border border-border rounded-2xl overflow-hidden hover:border-primary/30 transition-all duration-200"
30+
className="bg-card border border-border rounded-2xl overflow-hidden hover:border-primary/30 transition-all duration-200 last:border-b"
5631
>
5732
<AccordionTrigger className="px-6 py-5 text-left hover:bg-accent/5 transition-colors duration-200 hover:no-underline">
5833
<h3 className="text-lg font-medium pr-4">{item.question}</h3>
5934
</AccordionTrigger>
6035
<AccordionContent className="px-6 pb-5 border-t border-border">
6136
<p className="text-muted-foreground leading-relaxed pt-5">
62-
{renderAnswerWithLinks(item.answer)}
37+
{item.answer}{" "}
38+
{"links" in item &&
39+
item.links &&
40+
item.links.map((link: FAQLink, index: number) => {
41+
const isExternal = link.url.startsWith("http");
42+
const linkClasses =
43+
"text-primary hover:text-primary/80 underline";
44+
45+
return (
46+
<span key={link.url}>
47+
{isExternal ? (
48+
<a
49+
href={link.url}
50+
target="_blank"
51+
rel="noopener noreferrer"
52+
className={linkClasses}
53+
>
54+
{link.label ?? link.url}
55+
</a>
56+
) : (
57+
<Link href={link.url} className={linkClasses}>
58+
{link.label ?? link.url}
59+
</Link>
60+
)}
61+
{item.links && index < item.links.length - 1 && " · "}
62+
</span>
63+
);
64+
})}
6365
</p>
6466
</AccordionContent>
6567
</AccordionItem>

src/components/shared/InfoCard.tsx

Lines changed: 46 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,18 @@
11
"use client";
22

3+
import { cn } from "@/lib/utils";
34
import type { LucideIcon } from "lucide-react";
45
import type { ReactNode } from "react";
5-
import { cn } from "@/lib/utils";
66

77
interface InfoCardProps {
88
icon: LucideIcon;
99
title: string;
10-
description?: string;
10+
/** Single description or multiple paragraphs */
11+
description?: string | readonly string[];
12+
/** Label displayed above the title (e.g., "Step 1") */
13+
label?: string;
14+
/** Note displayed at the bottom of the content with special styling */
15+
note?: string;
1116
/** Content rendered next to the title (e.g., badges) */
1217
headerExtra?: ReactNode;
1318
/** Content rendered at the bottom of the card (e.g., buttons, selects) */
@@ -23,14 +28,21 @@ export function InfoCard({
2328
icon: Icon,
2429
title,
2530
description,
31+
label,
32+
note,
2633
headerExtra,
2734
footer,
2835
className,
2936
iconSize = "md",
3037
layout = "stacked",
3138
}: InfoCardProps) {
39+
const descriptionArray = Array.isArray(description)
40+
? description
41+
: description
42+
? [description]
43+
: [];
3244
const iconContainerClasses = cn(
33-
"bg-primary/10 rounded-xl flex items-center justify-center",
45+
"bg-primary/10 flex items-center justify-center transition-colors group-hover:bg-primary/20 rounded-xl",
3446
iconSize === "sm" ? "w-10 h-10" : "w-12 h-12",
3547
);
3648

@@ -42,7 +54,7 @@ export function InfoCard({
4254
return (
4355
<div
4456
className={cn(
45-
"bg-card p-5 rounded-2xl border border-border hover:border-primary/50 transition-all duration-200",
57+
"group bg-card p-5 rounded-2xl border border-border hover:border-primary/50 transition-all duration-200",
4658
className,
4759
)}
4860
>
@@ -51,25 +63,50 @@ export function InfoCard({
5163
<div className={cn(iconContainerClasses, "mb-4")}>
5264
<Icon className={iconClasses} />
5365
</div>
66+
{label && (
67+
<p className="text-sm font-medium text-muted-foreground mb-1">
68+
{label}
69+
</p>
70+
)}
5471
<h3 className="text-lg font-semibold mb-2">{title}</h3>
5572
</>
5673
) : (
57-
<div className="flex items-start gap-3 mb-4">
74+
<div className="flex items-start gap-4 mb-4">
5875
<div className={iconContainerClasses}>
5976
<Icon className={iconClasses} />
6077
</div>
6178
<div className="flex-1 min-w-0">
62-
<h3 className="font-semibold">{title}</h3>
79+
{label && (
80+
<p className="text-sm font-medium text-muted-foreground">
81+
{label}
82+
</p>
83+
)}
84+
<h3 className="text-xl font-semibold">{title}</h3>
6385
{headerExtra}
6486
</div>
6587
</div>
6688
)}
6789

68-
{description && (
69-
<p className="text-muted-foreground text-sm mb-4">{description}</p>
90+
{descriptionArray.length > 0 && (
91+
<div className={descriptionArray.length > 1 ? "space-y-4" : ""}>
92+
{descriptionArray.map((paragraph, idx) => (
93+
<p key={idx} className="text-muted-foreground leading-relaxed">
94+
{paragraph}
95+
</p>
96+
))}
97+
</div>
98+
)}
99+
100+
{note && (
101+
<div className="mt-4 p-4 rounded-lg bg-muted/50 border border-border">
102+
<p className="text-sm text-muted-foreground">
103+
<span className="font-semibold text-foreground">Note: </span>
104+
{note}
105+
</p>
106+
</div>
70107
)}
71108

72-
{footer}
109+
{footer && <div className="mt-4">{footer}</div>}
73110
</div>
74111
);
75112
}

src/constants/coretime.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import type { LucideIcon } from "lucide-react";
2+
import { RefreshCw, ShoppingCart, Wrench, Zap } from "lucide-react";
3+
4+
export interface CoretimeStep {
5+
readonly number: number;
6+
readonly icon: LucideIcon;
7+
readonly title: string;
8+
readonly content: readonly string[];
9+
readonly note?: string;
10+
}
11+
12+
export interface CoretimeCTA {
13+
readonly icon: LucideIcon;
14+
readonly title: string;
15+
readonly description: string;
16+
readonly linkLabel: string;
17+
}
18+
19+
export const CORETIME_CTA: CoretimeCTA = {
20+
icon: Wrench,
21+
title: "Simplifying the Process",
22+
description:
23+
"If you'd prefer a more streamlined experience, RegionX Hub provides tools that abstract away much of this process and can help simplify coretime management on Paseo.",
24+
linkLabel: "Visit RegionX Hub",
25+
} as const;
26+
27+
export const CORETIME_STEPS: readonly CoretimeStep[] = [
28+
{
29+
number: 1,
30+
icon: ShoppingCart,
31+
title: "Purchasing a Core",
32+
content: [
33+
"To buy a core (if availability remains), use the broker.purchase extrinsic on the Coretime chain.",
34+
"If the price exceeds 5,000 PAS, you'll need to contact the Paseo team to be whitelisted and obtain enough funds, otherwise you won't be able to purchase the core.",
35+
"Once purchased, call broker.assign to create a region and assign the core to your parachain. The region will be valid starting from a specific timeslice—typically the beginning of the next cycle.",
36+
"When the region starts, your chain will begin receiving coretime and producing blocks.",
37+
],
38+
},
39+
{
40+
number: 2,
41+
icon: RefreshCw,
42+
title: "Renewing Your Core",
43+
content: [
44+
"When the interlude period begins (the first ~2 days of each cycle), you can renew your core for the upcoming cycle using broker.renew.",
45+
"If you prefer automatic renewals, enable them with broker.enableAutoRenewal. This will renew your core each cycle as long as your parachain's sovereign account has sufficient funds to pay for the renewal.",
46+
],
47+
note: "Autorenewal should be called using the chain sovereign account on Coretime. Due to the PAS balance restriction, teams requiring auto-renewal should contact the Paseo support team to ensure adequate funds in their sovereign account + an open HRMP channel with the coretime chain.",
48+
},
49+
{
50+
number: 3,
51+
icon: Zap,
52+
title: "Starting Block Production Immediately",
53+
content: [
54+
"Don't want to wait up to a month for the next cycle to begin? Since Paseo is a testnet, there's a shortcut available.",
55+
"You still need to purchase coretime and assign the region as described above. However, the Paseo team can manually assign a core on the relay chain so your chain can start producing blocks right away—within the current cycle.",
56+
"Keep in mind this manual assignment is only valid for the current cycle. To guarantee coretime in future cycles, you must purchase it through the standard process.",
57+
],
58+
},
59+
] as const;

src/constants/faq.ts

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,14 @@
11
import { URLS } from "./urls";
22

3-
interface FAQItem {
3+
export interface FAQLink {
4+
readonly url: string;
5+
readonly label?: string;
6+
}
7+
8+
export interface FAQItem {
49
readonly question: string;
510
readonly answer: string;
11+
readonly links?: readonly FAQLink[];
612
}
713

814
// FAQ Content
@@ -12,7 +18,9 @@ export const FAQ_CONTENT = {
1218
items: [
1319
{
1420
question: "How do I obtain coretime in Paseo?",
15-
answer: `One can obtain coretime in Paseo by participating on coretime sales, just like in production. After getting some tokens from the faucet one can easily participate in sales via: ${URLS.regionXHub}`,
21+
answer:
22+
"One can obtain coretime in Paseo by participating on coretime sales, just like in production. Learn more about purchasing, renewing, and managing coretime.",
23+
links: [{ url: "/coretime", label: "View Coretime Guide" }],
1624
},
1725
{
1826
question:
@@ -22,20 +30,29 @@ export const FAQ_CONTENT = {
2230
},
2331
{
2432
question: "Who is managing Paseo?",
25-
answer: `The maintainers of the testnet are a distributed group of contributors taking care of the testnet runtimes and its infrastructure. The exact list can be seen at: ${URLS.githubGovernance}/`,
33+
answer:
34+
"The maintainers of the testnet are a distributed group of contributors taking care of the testnet runtimes and its infrastructure.",
35+
links: [{ url: URLS.githubGovernance, label: "View Contributors" }],
2636
},
2737
{
2838
question: "Are there any SLAs for incident management?",
29-
answer: `Yes, find them at: ${URLS.githubSLA}`,
39+
answer: "Yes, we have documented SLAs for incident management.",
40+
links: [{ url: URLS.githubSLA, label: "View SLAs" }],
3041
},
3142
{
3243
question:
3344
"Will it be possible to access the logs from the Relay and System chains?",
34-
answer: `Yes, most infrastructure providers send logs to a centralized logging service that can be accessed at: ${URLS.grafanaLogs}`,
45+
answer:
46+
"Yes, most infrastructure providers send logs to a centralized logging service.",
47+
links: [{ url: URLS.grafanaLogs, label: "Access Logs" }],
3548
},
3649
{
3750
question: "I am a bit lost or I need further support, what do I do?",
38-
answer: `The best ways of contacting the involved contributors are: Open an issue at: ${URLS.githubSupport} or Join the public paseo matrix room: ${URLS.matrixSupport}`,
51+
answer: "The best ways of contacting the involved contributors are:",
52+
links: [
53+
{ url: URLS.githubSupport, label: "Open an Issue" },
54+
{ url: URLS.matrixSupport, label: "Join Matrix Room" },
55+
],
3956
},
4057
{
4158
question: "My balance was reduced to 5K PAS suddenly, what's going on?",

0 commit comments

Comments
 (0)