Skip to content

Commit 80cfa81

Browse files
authored
Merge pull request #199 from dotCMS/fixing-learning-center
learning center fixed
2 parents 5ee27a7 + 1ab1ad9 commit 80cfa81

49 files changed

Lines changed: 2115 additions & 799 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

app/learning/[...slug]/page.tsx

Lines changed: 0 additions & 172 deletions
This file was deleted.

app/learning/[slug]/page.tsx

Lines changed: 175 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,175 @@
1+
import Header from "@/components/header/header";
2+
import Footer from "@/components/footer";
3+
import DevResourceDetailPageComponent from "@/components/learning/devresource-detail-page-component";
4+
import { getDevResources } from "@/services/learning/getDevResources";
5+
import { ErrorPage } from "@/components/error";
6+
import { notFound } from "next/navigation";
7+
import { extractAssetId } from "@/util/utils";
8+
import { Config } from "@/util/config";
9+
import Script from "next/script";
10+
11+
export async function generateMetadata({
12+
params,
13+
}: {
14+
params: Promise<{ slug: string }>;
15+
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
16+
}) {
17+
const slug = (await params).slug;
18+
19+
if (!slug) {
20+
return notFound();
21+
}
22+
23+
const devResources = await getDevResources({
24+
slug,
25+
needBody: true,
26+
});
27+
28+
if (!devResources?.devResources?.length) {
29+
return notFound();
30+
}
31+
32+
const devResource = devResources.devResources[0];
33+
34+
// Check if the resource's tags include 'dot:meta-no-index'
35+
const tags = devResource.tags || [];
36+
const shouldNoIndex = Array.isArray(tags)
37+
? tags.includes("dot:meta-no-index")
38+
: typeof tags === "string" && tags.includes("dot:meta-no-index");
39+
40+
const hostname = Config.CDNHost;
41+
42+
const imageUrl = devResource.image?.idPath
43+
? `${hostname}/dA/${extractAssetId(devResource.image.idPath)}/70q/1000maxw/${devResource.inode}/${devResource.image.title}`
44+
: `${hostname}/dA/a6e0a89831/70q/1000maxw/dotcms-dev-site.webp`;
45+
46+
return {
47+
alternates: {
48+
canonical: `${hostname}/learning/${devResource.slug}`,
49+
},
50+
title: devResource.title,
51+
description: devResource.teaser || `Read ${devResource.title} on dotCMS Developer Blog`,
52+
metadataBase: new URL(hostname),
53+
54+
openGraph: {
55+
title: devResource.title,
56+
description: devResource.teaser,
57+
url: `${hostname}/learning/${devResource.slug}`,
58+
siteName: "dotCMS Dev Site",
59+
images: [
60+
{
61+
url: imageUrl,
62+
width: 1200,
63+
height: 630,
64+
alt: devResource.image?.description || devResource.title,
65+
},
66+
],
67+
locale: "en_US",
68+
type: "article",
69+
},
70+
71+
twitter: {
72+
card: "summary_large_image",
73+
title: devResource.title,
74+
description: devResource.teaser,
75+
images: [imageUrl],
76+
creator: "@dotcms",
77+
site: "@dotcms",
78+
},
79+
80+
// Article specific
81+
article: {
82+
publishedTime: devResource.publishDate,
83+
modifiedTime: devResource.modDate,
84+
authors: devResource.author
85+
? [`${devResource.author.firstName} ${devResource.author.lastName}`]
86+
: ["dotCMS Team"],
87+
tags: devResource.tags,
88+
},
89+
90+
keywords: devResource.tags?.join(", "),
91+
robots: shouldNoIndex
92+
? "noindex, nofollow"
93+
: {
94+
index: true,
95+
follow: true,
96+
},
97+
};
98+
}
99+
100+
export default async function DevResourceDetailPage({
101+
params,searchParams
102+
}: {
103+
params: Promise<{ slug: string }>;
104+
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
105+
}) {
106+
const slug = (await params).slug;
107+
const finalSearchParams = await searchParams;
108+
const page = finalSearchParams?.page ||1;
109+
const tagFilter = finalSearchParams?.tagFilter ||"";
110+
const devResourceResult = await getDevResources({
111+
slug,
112+
needBody: true,
113+
});
114+
115+
if (!devResourceResult.devResources.length) {
116+
return <ErrorPage error={{ message: "Resource not found", status: 404 }} />;
117+
}
118+
119+
const devResource = devResourceResult.devResources[0];
120+
const hostname = Config.CDNHost;
121+
122+
const imageUrl = devResource.image?.idPath
123+
? `${hostname}/dA/${extractAssetId(devResource.image.idPath)}/70q/1000maxw/${devResource.inode}/${devResource.image.title}`
124+
: `${hostname}/dA/a6e0a89831/70q/1000maxw/dotcms-dev-site.webp`;
125+
126+
// Create JSON-LD structured data
127+
const jsonLd = {
128+
"@context": "https://schema.org",
129+
"@type": "TechArticle",
130+
headline: devResource.title,
131+
description:
132+
devResource.teaser || `Read ${devResource.title} on dotCMS Developer Blog`,
133+
image: imageUrl,
134+
datePublished: devResource.publishDate,
135+
dateModified: devResource.modDate,
136+
author: devResource.author
137+
? {
138+
"@type": "Person",
139+
name: `${devResource.author.firstName} ${devResource.author.lastName}`,
140+
}
141+
: {
142+
"@type": "Organization",
143+
name: "dotCMS Team",
144+
},
145+
publisher: {
146+
"@type": "Organization",
147+
name: "dotCMS",
148+
logo: {
149+
"@type": "ImageObject",
150+
url: `${hostname}/dA/a6e0a89831/70q/1000maxw/dotcms-dev-site.webp`,
151+
},
152+
},
153+
mainEntityOfPage: {
154+
"@type": "WebPage",
155+
"@id": `${hostname}/learning/${devResource.slug}`,
156+
},
157+
keywords: devResource.tags?.join(", ") || "",
158+
articleSection: devResource.type1 ? devResource.type1[0] : "Learning",
159+
};
160+
161+
return (
162+
<div className="flex min-h-screen flex-col">
163+
<Script
164+
id="structured-data"
165+
type="application/ld+json"
166+
dangerouslySetInnerHTML={{ __html: JSON.stringify(jsonLd) }}
167+
/>
168+
<Header />
169+
<div className="container relative">
170+
<DevResourceDetailPageComponent devResource={devResource} linkback={`/learning/listing?tagFilter=${tagFilter}&page=${page}`}/>
171+
</div>
172+
<Footer />
173+
</div>
174+
);
175+
}

app/learning/listing/page.tsx

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
import type { Metadata } from "next";
2+
3+
import Header from "@/components/header/header";
4+
import Footer from "@/components/footer";
5+
import DevResourceListing from "@/components/learning/devresource-listing";
6+
import { getDevResources } from "@/services/learning/getDevResources";
7+
8+
export const metadata: Metadata = {
9+
title: "dotCMS Learning Center",
10+
description:
11+
"Explore tutorials, guides, examples, videos and knowledge base articles to master dotCMS",
12+
};
13+
14+
export default async function DevResourceListingPage({
15+
searchParams,
16+
}: {
17+
searchParams?: Promise<{ [key: string]: string | string[] | undefined }>;
18+
}) {
19+
const finalSearchParams = await searchParams;
20+
21+
const type = (finalSearchParams?.type as string) || "all";
22+
const tagFilter = (finalSearchParams?.tagFilter as string) || "";
23+
const page = Number(finalSearchParams?.page) || 1;
24+
const limit = Number(finalSearchParams?.limit) || 20;
25+
26+
const { devResources, pagination } = await getDevResources({
27+
tagFilter,
28+
page,
29+
limit,
30+
type,
31+
});
32+
33+
return (
34+
<div className="flex min-h-screen flex-col">
35+
<Header />
36+
<div className="container relative">
37+
<DevResourceListing
38+
devResources={devResources}
39+
pagination={pagination}
40+
tagFilter={tagFilter}
41+
/>
42+
</div>
43+
<Footer />
44+
</div>
45+
);
46+
}

0 commit comments

Comments
 (0)