Skip to content

Commit c48f662

Browse files
committed
Refactor guide page to use static generation; enhance guide data structure with additional fields
1 parent c32efae commit c48f662

File tree

3 files changed

+258
-59
lines changed

3 files changed

+258
-59
lines changed

packages/guides/data/guides.json

Lines changed: 122 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,30 +3,143 @@
33
"title": "Building Subgraphs 101",
44
"slug": "building-subgraphs-101",
55
"description": "Learn the basics of creating a subgraph on The Graph network.",
6-
"categories": ["Subgraphs", "Getting Started"],
6+
"products": ["Subgraphs", "The Graph Network"],
77
"date": "2024-05-18",
88
"author": "Graph Docs Team",
9-
"types": ["blog"],
10-
"url": "https://thegraph.com/blog/building-subgraphs-101"
9+
"types": ["Blog"],
10+
"difficulty": "Easy",
11+
"content": "# Building Subgraphs 101\n\nThis is a stub page for the guide content..."
1112
},
1213
{
1314
"title": "Indexing Smart Contracts with The Graph",
1415
"slug": "indexing-smart-contracts",
1516
"description": "Step-by-step video tutorial for indexing contracts using Graph CLI.",
16-
"categories": ["Indexing", "Video"],
17+
"products": ["Graph Node", "Subgraphs"],
1718
"date": "2024-03-02",
1819
"author": "Graph Protocol",
19-
"types": ["video"],
20-
"url": "https://youtube.com/watch?v=dQw4w9WgXcQ"
20+
"types": ["Video", "Workshop"],
21+
"difficulty": "Intermediate",
22+
"content": "# Indexing Smart Contracts with The Graph\nVideo embed or details here..."
2123
},
2224
{
2325
"title": "Using Graph Client in React",
2426
"slug": "using-graph-client-react",
2527
"description": "How to query subgraphs efficiently in a React front-end.",
26-
"categories": ["Client", "React"],
28+
"products": ["Token API", "Subgraphs"],
2729
"date": "2023-12-10",
2830
"author": "Edge & Node",
29-
"types": ["blog", "repo"],
30-
"url": "https://github.com/graphprotocol/example"
31+
"types": ["Blog", "Repository"],
32+
"difficulty": "Easy",
33+
"content": "# Using Graph Client in React\nContent placeholder."
34+
},
35+
{
36+
"title": "Substreams Rust Workshop",
37+
"slug": "substreams-rust-workshop",
38+
"description": "Hands-on workshop on authoring Substreams modules in Rust.",
39+
"products": ["Substreams"],
40+
"date": "2024-01-15",
41+
"author": "StreamingFast",
42+
"types": ["Workshop", "Video"],
43+
"difficulty": "Advanced",
44+
"content": "# Substreams Rust Workshop\nContent stub."
45+
},
46+
{
47+
"title": "Deploying Graph Node on Kubernetes",
48+
"slug": "deploying-graph-node-k8s",
49+
"description": "Guide to deploy an indexing node on Kubernetes cluster.",
50+
"products": ["Graph Node", "The Graph Network"],
51+
"date": "2024-02-20",
52+
"author": "Graph Community",
53+
"types": ["Blog"],
54+
"difficulty": "Advanced",
55+
"content": "# Deploying Graph Node on Kubernetes\nStub article."
56+
},
57+
{
58+
"title": "Building Token API Queries",
59+
"slug": "building-token-api-queries",
60+
"description": "Learn to query token metadata using The Graph Token API.",
61+
"products": ["Token API"],
62+
"date": "2024-04-02",
63+
"author": "Graph Docs Team",
64+
"types": ["Blog"],
65+
"difficulty": "Intermediate",
66+
"content": "# Building Token API Queries\nStub."
67+
},
68+
{
69+
"title": "Intro to The Graph Network",
70+
"slug": "intro-graph-network",
71+
"description": "High-level overview of how the decentralized network works.",
72+
"products": ["The Graph Network"],
73+
"date": "2023-11-05",
74+
"author": "Graph Foundation",
75+
"types": ["Video"],
76+
"difficulty": "Easy",
77+
"content": "# Intro to The Graph Network\nStub."
78+
},
79+
{
80+
"title": "Optimizing Subgraph Performance",
81+
"slug": "optimizing-subgraph-performance",
82+
"description": "Best practices for faster indexing and query responses.",
83+
"products": ["Subgraphs"],
84+
"date": "2024-05-01",
85+
"author": "Edge & Node",
86+
"types": ["Blog"],
87+
"difficulty": "Intermediate",
88+
"content": "# Optimizing Subgraph Performance\nStub."
89+
},
90+
{
91+
"title": "GraphQL Basics for The Graph",
92+
"slug": "graphql-basics",
93+
"description": "Essential GraphQL concepts every Graph builder should know.",
94+
"products": ["Subgraphs", "Token API"],
95+
"date": "2023-10-01",
96+
"author": "Graph Docs Team",
97+
"types": ["Blog", "Video"],
98+
"difficulty": "Easy",
99+
"content": "# GraphQL Basics\nStub."
100+
},
101+
{
102+
"title": "Migrating from Hosted Service to Network",
103+
"slug": "migrating-to-network",
104+
"description": "Step-by-step migration strategy.",
105+
"products": ["The Graph Network", "Subgraphs"],
106+
"date": "2024-06-10",
107+
"author": "Graph Ops",
108+
"types": ["Blog", "Workshop"],
109+
"difficulty": "Advanced",
110+
"content": "# Migration Guide\nStub."
111+
},
112+
{
113+
"title": "Query Token Prices Using Substreams",
114+
"slug": "token-prices-substreams",
115+
"description": "Create a Substreams pipeline for token price data.",
116+
"products": ["Substreams", "Token API"],
117+
"date": "2024-04-25",
118+
"author": "StreamingFast",
119+
"types": ["Repository"],
120+
"difficulty": "Intermediate",
121+
"content": "# Token Prices Substreams\nStub."
122+
},
123+
{
124+
"title": "Graph Node Metrics and Monitoring",
125+
"slug": "graph-node-metrics",
126+
"description": "Expose Prometheus metrics from Graph Node and set up Grafana dashboards.",
127+
"products": ["Graph Node"],
128+
"date": "2024-02-28",
129+
"author": "Graph Community",
130+
"types": ["Blog", "Repository"],
131+
"difficulty": "Advanced",
132+
"content": "# Graph Node Metrics\nStub."
133+
},
134+
{
135+
"title": "Deploying a Subgraph on Arbitrum",
136+
"slug": "deploying-subgraph-arbitrum",
137+
"description": "End-to-end guide to deploy a subgraph for Arbitrum network.",
138+
"products": ["Subgraphs"],
139+
"date": "2024-05-30",
140+
"author": "Graph Docs Team",
141+
"types": ["Blog"],
142+
"difficulty": "Intermediate",
143+
"content": "# Deploying Subgraph on Arbitrum\nStub."
31144
}
32145
]
Lines changed: 55 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,59 @@
1-
import { GetServerSideProps } from 'next';
2-
import guides from '@/data/guides.json';
1+
import { GetStaticPaths, GetStaticProps } from 'next';
2+
import Link from 'next/link';
3+
import guides from '../../data/guides.json';
34

4-
export const getServerSideProps: GetServerSideProps = async ({ params }) => {
5-
const guide = (guides as any[]).find((g) => g.slug === params?.slug);
6-
if (guide) {
7-
return {
8-
redirect: {
9-
destination: guide.url,
10-
permanent: false,
11-
},
12-
};
13-
}
14-
return { notFound: true };
5+
interface Guide {
6+
title: string;
7+
slug: string;
8+
description: string;
9+
products: string[];
10+
date: string;
11+
author: string;
12+
types: string[];
13+
difficulty: string;
14+
content: string;
15+
url?: string;
16+
}
17+
18+
export const getStaticPaths: GetStaticPaths = async () => {
19+
return {
20+
paths: (guides as Guide[]).map((g) => ({ params: { slug: g.slug } })),
21+
fallback: false,
22+
};
23+
};
24+
25+
export const getStaticProps: GetStaticProps = async ({ params }) => {
26+
const guide = (guides as Guide[]).find((g) => g.slug === params?.slug);
27+
if (!guide) return { notFound: true };
28+
return {
29+
props: { guide },
30+
};
1531
};
1632

17-
export default function Guide() {
18-
return null;
33+
export default function GuidePage({ guide }: { guide: Guide }) {
34+
return (
35+
<article className="prose lg:prose-lg mx-auto px-4 py-6">
36+
<Link href="/guides" className="text-sm text-blue-600">← Back to Guides</Link>
37+
<h1>{guide.title}</h1>
38+
<p className="text-gray-600 text-sm mb-4">{guide.description}</p>
39+
<div className="text-xs text-gray-500 flex flex-wrap gap-2 mb-4">
40+
{guide.products.map((p) => (
41+
<span key={p} className="bg-gray-200 px-2 py-0.5 rounded">{p}</span>
42+
))}
43+
{guide.types.map((t) => (
44+
<span key={t} className="bg-purple-200 px-2 py-0.5 rounded">{t}</span>
45+
))}
46+
<span className="bg-blue-200 px-2 py-0.5 rounded">{guide.difficulty}</span>
47+
<span>{new Date(guide.date).toLocaleDateString()}</span>
48+
</div>
49+
<div dangerouslySetInnerHTML={{ __html: guide.content.replace(/\n/g, '<br/>') }} />
50+
{guide.url && (
51+
<p className="mt-6">
52+
<a href={guide.url} target="_blank" rel="noopener noreferrer" className="text-blue-600 underline">
53+
View original resource
54+
</a>
55+
</p>
56+
)}
57+
</article>
58+
);
1959
}

packages/guides/pages/guides/index.tsx

Lines changed: 81 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,113 @@
1-
import { useMemo, useState } from 'react';
1+
import React, { useMemo, useState } from 'react';
22
import Fuse from 'fuse.js';
3-
import guidesData from '@/data/guides.json';
3+
import guidesData from '../../data/guides.json';
44
import Link from 'next/link';
55

66
interface Guide {
77
title: string;
88
slug: string;
99
description: string;
10-
categories: string[];
10+
products: string[];
1111
date: string;
1212
author: string;
1313
types: string[];
14-
url: string;
14+
url?: string;
15+
difficulty: string;
16+
content: string;
1517
}
1618

19+
const PRODUCTS = [
20+
'Subgraphs',
21+
'Substreams',
22+
'Token API',
23+
'The Graph Network',
24+
'Graph Node',
25+
] as const;
26+
27+
const GUIDE_TYPES = ['Blog', 'Repository', 'Video', 'Workshop'] as const;
28+
29+
const DIFFICULTIES = ['Easy', 'Intermediate', 'Advanced'] as const;
30+
1731
const fuse = new Fuse(guidesData as Guide[], {
1832
includeScore: true,
1933
threshold: 0.4,
20-
keys: ['title', 'description', 'categories', 'types'],
34+
keys: ['title', 'description', 'products', 'types'],
2135
});
2236

23-
const allCategories = Array.from(
24-
new Set((guidesData as Guide[]).flatMap((g) => g.categories)),
25-
).sort();
26-
2737
export default function GuidesPage() {
2838
const [query, setQuery] = useState('');
29-
const [selected, setSelected] = useState<Set<string>>(new Set());
39+
const [selectedProducts, setSelectedProducts] = useState<Set<string>>(new Set());
40+
const [selectedTypes, setSelectedTypes] = useState<Set<string>>(new Set());
41+
const [selectedDifficulties, setSelectedDifficulties] = useState<Set<string>>(new Set());
3042

3143
const guides = useMemo(() => {
3244
let filtered: Guide[] = guidesData as Guide[];
33-
if (selected.size) {
34-
filtered = filtered.filter((g) => g.categories.some((c) => selected.has(c)));
45+
46+
if (selectedProducts.size) {
47+
filtered = filtered.filter((g) => g.products.some((p) => selectedProducts.has(p)));
48+
}
49+
if (selectedTypes.size) {
50+
filtered = filtered.filter((g) => g.types.some((t) => selectedTypes.has(t)));
51+
}
52+
if (selectedDifficulties.size) {
53+
filtered = filtered.filter((g) => selectedDifficulties.has(g.difficulty));
3554
}
3655
if (query.trim()) {
37-
return fuse.search(query).map((r) => r.item).filter((g) => filtered.includes(g));
56+
return fuse
57+
.search(query)
58+
.map((r) => r.item)
59+
.filter((g) => filtered.includes(g));
3860
}
3961
return filtered;
40-
}, [query, selected]);
62+
}, [query, selectedProducts, selectedTypes, selectedDifficulties]);
4163

42-
const toggleCategory = (cat: string) => {
43-
const next = new Set(selected);
44-
if (next.has(cat)) next.delete(cat);
45-
else next.add(cat);
46-
setSelected(next);
64+
const toggle = (value: string, set: React.Dispatch<React.SetStateAction<Set<string>>>) => {
65+
set((prev) => {
66+
const next = new Set(prev);
67+
next.has(value) ? next.delete(value) : next.add(value);
68+
return next;
69+
});
4770
};
4871

72+
const Checkbox = ({ label, checked, onChange }: { label: string; checked: boolean; onChange(): void }) => (
73+
<li className="flex items-center space-x-2">
74+
<input
75+
type="checkbox"
76+
checked={checked}
77+
onChange={onChange}
78+
className="h-4 w-4 rounded border-gray-400"
79+
/>
80+
<span className="text-sm select-none">{label}</span>
81+
</li>
82+
);
83+
4984
return (
5085
<div className="flex min-h-screen p-6 gap-8 font-sans">
51-
<aside className="w-64 border-r pr-4">
52-
<h2 className="text-xl font-semibold mb-4">Categories</h2>
53-
<ul className="space-y-2">
54-
{allCategories.map((cat) => (
55-
<li key={cat} className="flex items-center space-x-2">
56-
<input
57-
type="checkbox"
58-
checked={selected.has(cat)}
59-
onChange={() => toggleCategory(cat)}
60-
className="h-4 w-4 rounded border-gray-400"
61-
/>
62-
<span className="text-sm">{cat}</span>
63-
</li>
64-
))}
65-
</ul>
86+
<aside className="w-64 border-r pr-4 space-y-6">
87+
<div>
88+
<h2 className="text-xl font-semibold mb-4">Products</h2>
89+
<ul className="space-y-2">
90+
{PRODUCTS.map((p) => (
91+
<Checkbox key={p} label={p} checked={selectedProducts.has(p)} onChange={() => toggle(p, setSelectedProducts)} />
92+
))}
93+
</ul>
94+
</div>
95+
<div>
96+
<h2 className="text-xl font-semibold mb-4">Guide Type</h2>
97+
<ul className="space-y-2">
98+
{GUIDE_TYPES.map((t) => (
99+
<Checkbox key={t} label={t} checked={selectedTypes.has(t)} onChange={() => toggle(t, setSelectedTypes)} />
100+
))}
101+
</ul>
102+
</div>
103+
<div>
104+
<h2 className="text-xl font-semibold mb-4">Difficulty</h2>
105+
<ul className="space-y-2">
106+
{DIFFICULTIES.map((d) => (
107+
<Checkbox key={d} label={d} checked={selectedDifficulties.has(d)} onChange={() => toggle(d, setSelectedDifficulties)} />
108+
))}
109+
</ul>
110+
</div>
66111
</aside>
67112
<main className="flex-1">
68113
<div className="mb-6">
@@ -82,7 +127,7 @@ export default function GuidesPage() {
82127
</Link>
83128
<p className="text-sm text-gray-600 mb-2">{g.description}</p>
84129
<div className="text-xs text-gray-500 flex flex-wrap gap-2">
85-
{g.categories.map((c) => (
130+
{g.products.map((c) => (
86131
<span key={c} className="bg-gray-200 px-2 py-0.5 rounded">
87132
{c}
88133
</span>
@@ -93,6 +138,7 @@ export default function GuidesPage() {
93138
</span>
94139
))}
95140
<span>{new Date(g.date).toLocaleDateString()}</span>
141+
<span className="bg-blue-200 px-2 py-0.5 rounded">{g.difficulty}</span>
96142
</div>
97143
</li>
98144
))}

0 commit comments

Comments
 (0)