1
0
mirror of https://github.com/kamranahmedse/developer-roadmap.git synced 2025-09-02 22:02:39 +02:00

Revert "chore: replace roadmap listing"

This reverts commit c4c28944ee.
This commit is contained in:
Kamran Ahmed
2025-09-01 18:51:35 +01:00
parent a89c2d454f
commit 80dfd5b206
14 changed files with 267 additions and 191 deletions

View File

@@ -23,12 +23,7 @@ type EditorRoadmapProps = {
}; };
export function EditorRoadmap(props: EditorRoadmapProps) { export function EditorRoadmap(props: EditorRoadmapProps) {
const { const { resourceId, resourceType = 'roadmap', dimensions, hasChat = true } = props;
resourceId,
resourceType = 'roadmap',
dimensions,
hasChat = true,
} = props;
const [hasSwitchedRoadmap, setHasSwitchedRoadmap] = useState(false); const [hasSwitchedRoadmap, setHasSwitchedRoadmap] = useState(false);
const [isLoading, setIsLoading] = useState(true); const [isLoading, setIsLoading] = useState(true);

View File

@@ -1,4 +1,6 @@
--- ---
import type { RoadmapFileType } from '../lib/roadmap';
export interface Props { export interface Props {
url: string; url: string;
title: string; title: string;
@@ -25,7 +27,7 @@ const { url, title, description, isNew } = Astro.props;
{ {
isNew && ( isNew && (
<span class='absolute right-1 bottom-1.5 flex items-center gap-1.5 rounded-xs text-xs font-semibold text-purple-500 uppercase sm:px-1.5'> <span class='flex items-center gap-1.5 absolute bottom-1.5 right-1 rounded-xs text-xs font-semibold uppercase text-purple-500 sm:px-1.5'>
<span class='relative flex h-2 w-2'> <span class='relative flex h-2 w-2'>
<span class='absolute inline-flex h-full w-full animate-ping rounded-full bg-purple-400 opacity-75' /> <span class='absolute inline-flex h-full w-full animate-ping rounded-full bg-purple-400 opacity-75' />
<span class='relative inline-flex h-2 w-2 rounded-full bg-purple-500' /> <span class='relative inline-flex h-2 w-2 rounded-full bg-purple-500' />

View File

@@ -1,8 +1,5 @@
import {
officialRoadmapDetails,
type OfficialRoadmapDocument,
} from '../queries/official-roadmap';
import type { MarkdownFileType } from './file'; import type { MarkdownFileType } from './file';
import { getRoadmapById, type RoadmapFileType } from './roadmap';
export const projectDifficulties = [ export const projectDifficulties = [
'beginner', 'beginner',
@@ -31,7 +28,7 @@ export interface ProjectFrontmatter {
export type ProjectFileType = MarkdownFileType<ProjectFrontmatter> & { export type ProjectFileType = MarkdownFileType<ProjectFrontmatter> & {
id: string; id: string;
roadmaps: OfficialRoadmapDocument[]; roadmaps: RoadmapFileType[];
}; };
/** /**
@@ -88,7 +85,7 @@ export async function getProjectById(
const project = await import(`../data/projects/${groupId}.md`); const project = await import(`../data/projects/${groupId}.md`);
const roadmapIds = project.frontmatter.roadmapIds || []; const roadmapIds = project.frontmatter.roadmapIds || [];
const roadmaps = await Promise.all( const roadmaps = await Promise.all(
roadmapIds.map((roadmapId: string) => officialRoadmapDetails(roadmapId)), roadmapIds.map((roadmapId: string) => getRoadmapById(roadmapId)),
); );
return { return {

View File

@@ -1,7 +1,19 @@
import type { PageType } from '../components/CommandMenu/CommandMenu'; import type { PageType } from '../components/CommandMenu/CommandMenu';
import type { MarkdownFileType } from './file';
import { httpGet } from './http'; import { httpGet } from './http';
import type { ResourceType } from './resource-progress'; import type { ResourceType } from './resource-progress';
export function resourceTitleFromId(id: string): string {
if (id === 'devops') {
return 'DevOps';
}
return id
.split('-')
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
.join(' ');
}
export type AllowedRoadmapRenderer = 'balsamiq' | 'editor'; export type AllowedRoadmapRenderer = 'balsamiq' | 'editor';
export interface RoadmapFrontmatter { export interface RoadmapFrontmatter {
@@ -64,6 +76,99 @@ export interface RoadmapFrontmatter {
renderer?: AllowedRoadmapRenderer; renderer?: AllowedRoadmapRenderer;
} }
export type RoadmapFileType = MarkdownFileType<RoadmapFrontmatter> & {
id: string;
};
function roadmapPathToId(filePath: string): string {
const fileName = filePath.split('/').pop() || '';
return fileName.replace('.md', '');
}
/**
* Gets the IDs of all the roadmaps available on the website
*
* @returns string[] Array of roadmap IDs
*/
export async function getRoadmapIds() {
const roadmapFiles = import.meta.glob<RoadmapFileType>(
'/src/data/roadmaps/*/*.md',
{
eager: true,
},
);
return Object.keys(roadmapFiles).map(roadmapPathToId);
}
/**
* Gets the roadmap files which have the given tag assigned
*
* @param tag Tag assigned to roadmap
* @returns Promisified RoadmapFileType[]
*/
export async function getRoadmapsByTag(
tag: string,
): Promise<RoadmapFileType[]> {
const roadmapFilesMap = import.meta.glob<RoadmapFileType>(
'/src/data/roadmaps/*/*.md',
{
eager: true,
},
);
const roadmapFiles: RoadmapFileType[] = Object.values(roadmapFilesMap);
const filteredRoadmaps = roadmapFiles
.filter((roadmapFile) => roadmapFile.frontmatter.tags?.includes(tag))
.map((roadmapFile) => ({
...roadmapFile,
id: roadmapPathToId(roadmapFile.file),
}));
return filteredRoadmaps.sort(
(a, b) => a.frontmatter.order - b.frontmatter.order,
);
}
export async function getRoadmapById(id: string): Promise<RoadmapFileType> {
const roadmapFilesMap: Record<string, RoadmapFileType> =
import.meta.glob<RoadmapFileType>('/src/data/roadmaps/*/*.md', {
eager: true,
});
const roadmapFile = Object.values(roadmapFilesMap).find((roadmapFile) => {
return roadmapPathToId(roadmapFile.file) === id;
});
if (!roadmapFile) {
throw new Error(`Roadmap with ID ${id} not found`);
}
return {
...roadmapFile,
id: roadmapPathToId(roadmapFile.file),
};
}
export async function getRoadmapsByIds(
ids: string[],
): Promise<RoadmapFileType[]> {
if (!ids?.length) {
return [];
}
return Promise.all(ids.map((id) => getRoadmapById(id)));
}
export async function getRoadmapFaqsById(roadmapId: string): Promise<string[]> {
const { faqs } = await import(
`../data/roadmaps/${roadmapId}/faqs.astro`
).catch(() => ({}));
return faqs || [];
}
export async function getResourceMeta( export async function getResourceMeta(
resourceType: ResourceType, resourceType: ResourceType,
resourceId: string, resourceId: string,

View File

@@ -1,13 +1,10 @@
--- ---
import Icon from '../components/AstroIcon.astro'; import Icon from '../components/AstroIcon.astro';
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from '../layouts/BaseLayout.astro';
import { listOfficialRoadmaps } from '../queries/official-roadmap'; import { getRoadmapIds } from '../lib/roadmap';
const roadmapIds = await listOfficialRoadmaps(); const roadmapIds = await getRoadmapIds();
const legacyRoadmapUrls = [ const legacyRoadmapUrls = [...roadmapIds.map((id) => `/${id}/`), '/roadmaps/'];
...roadmapIds.map((roadmap) => `/${roadmap.slug}/`),
'/roadmaps/',
];
--- ---
<BaseLayout title='Page not found' permalink={'/404'} noIndex={true}> <BaseLayout title='Page not found' permalink={'/404'} noIndex={true}>
@@ -21,26 +18,20 @@ const legacyRoadmapUrls = [
</script> </script>
<div class='bg-gray-100'> <div class='bg-gray-100'>
<div <div class='py-10 md:py-32 container flex flex-col md:flex-row items-center justify-center gap-7'>
class='container flex flex-col items-center justify-center gap-7 py-10 md:flex-row md:py-32'
>
<Icon icon='construction' class='hidden md:block' /> <Icon icon='construction' class='hidden md:block' />
<div class='text-left md:text-left'> <div class='text-left md:text-left'>
<h1 <h1
class='bg-linear-to-t from-black to-gray-600 bg-clip-text text-2xl leading-normal font-extrabold text-transparent md:text-5xl md:leading-normal' class='font-extrabold text-transparent text-2xl leading-normal md:text-5xl md:leading-normal bg-clip-text bg-linear-to-t from-black to-gray-600'
> >
Page not found! Page not found!
</h1> </h1>
<p class='text-md mb-2 md:text-xl'> <p class='text-md md:text-xl mb-2'>Sorry, we couldn't find the page you are looking for.</p>
Sorry, we couldn't find the page you are looking for.
</p>
<p> <p>
<a class='text-blue-700 underline' href='/'>Homepage</a> &middot; <a <a class='underline text-blue-700' href='/'>Homepage</a> &middot; <a
href='/roadmaps' href='/roadmaps'
class='text-blue-700 underline'>Roadmaps</a class='underline text-blue-700'>Roadmaps</a
> &middot; <a href='/best-practices' class='text-blue-700 underline' > &middot; <a href='/best-practices' class='underline text-blue-700'>Best Practices</a>
>Best Practices</a
>
</p> </p>
</div> </div>
</div> </div>

View File

@@ -1,4 +1,4 @@
<!-- --- ---
import RoadmapHeader from '../../components/RoadmapHeader.astro'; import RoadmapHeader from '../../components/RoadmapHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getOpenGraphImageUrl } from '../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../lib/open-graph';
@@ -163,4 +163,4 @@ const courses = roadmapData.courses || [];
</div> </div>
</div> </div>
</div> </div>
</BaseLayout> --> </BaseLayout>

View File

@@ -5,19 +5,16 @@ import { ProjectsList } from '../../components/Projects/ProjectsList';
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getProjectsByRoadmapId } from '../../lib/project'; import { getProjectsByRoadmapId } from '../../lib/project';
import { getOpenGraphImageUrl } from '../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
import { projectApi } from '../../api/project'; import { projectApi } from '../../api/project';
import {
listOfficialRoadmaps,
officialRoadmapDetails,
} from '../../queries/official-roadmap';
export const prerender = true; export const prerender = true;
export async function getStaticPaths() { export async function getStaticPaths() {
const roadmapIds = await listOfficialRoadmaps(); const roadmapIds = await getRoadmapIds();
return roadmapIds.map((roadmap) => ({ return roadmapIds.map((roadmapId) => ({
params: { roadmapId: roadmap.slug }, params: { roadmapId },
})); }));
} }
@@ -26,14 +23,15 @@ interface Params extends Record<string, string | undefined> {
} }
const { roadmapId } = Astro.params as Params; const { roadmapId } = Astro.params as Params;
const roadmapData = await officialRoadmapDetails(roadmapId); const roadmapFile = await import(
if (!roadmapData) { `../../data/roadmaps/${roadmapId}/${roadmapId}.md`
return Astro.rewrite('/404'); );
}
const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
// update og for projects // update og for projects
const ogImageUrl = const ogImageUrl =
roadmapData?.openGraph?.image || roadmapData?.seo?.ogImageUrl ||
getOpenGraphImageUrl({ getOpenGraphImageUrl({
group: 'roadmap', group: 'roadmap',
resourceId: roadmapId, resourceId: roadmapId,
@@ -46,13 +44,13 @@ const descriptionNoun: Record<string, string> = {
'Product Manager': 'Product Management', 'Product Manager': 'Product Management',
}; };
const title = `${roadmapData.title.card} Projects`; const title = `${roadmapData.briefTitle} Projects`;
const description = `Project ideas to take you from beginner to advanced in ${descriptionNoun[roadmapData.title.card] || roadmapData.title.card}`; const description = `Project ideas to take you from beginner to advanced in ${descriptionNoun[roadmapData.briefTitle] || roadmapData.briefTitle}`;
// `Seeking backend projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!` // `Seeking backend projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`
const seoTitle = `${roadmapData.title.card} Projects`; const seoTitle = `${roadmapData.briefTitle} Projects`;
const nounTitle = const nounTitle =
descriptionNoun[roadmapData?.title.card] || roadmapData.title.card; descriptionNoun[roadmapData?.briefTitle] || roadmapData.briefTitle;
const seoDescription = `Seeking ${nounTitle.toLowerCase()} projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`; const seoDescription = `Seeking ${nounTitle.toLowerCase()} projects to enhance your skills? Explore our top 20 project ideas, from simple apps to complex systems. Start building today!`;
const projects = await getProjectsByRoadmapId(roadmapId); const projects = await getProjectsByRoadmapId(roadmapId);
@@ -67,7 +65,7 @@ const { response: userCounts } =
permalink={`/${roadmapId}/projects`} permalink={`/${roadmapId}/projects`}
title={seoTitle} title={seoTitle}
description={seoDescription} description={seoDescription}
briefTitle={roadmapData.title.card} briefTitle={roadmapData.briefTitle}
ogImageUrl={ogImageUrl} ogImageUrl={ogImageUrl}
keywords={roadmapData.seo.keywords} keywords={roadmapData.seo.keywords}
noIndex={projects.length === 0} noIndex={projects.length === 0}
@@ -75,16 +73,15 @@ const { response: userCounts } =
resourceType='roadmap' resourceType='roadmap'
> >
<div class='bg-gray-50'> <div class='bg-gray-50'>
<!-- // TODO: get courses count -->
<RoadmapHeader <RoadmapHeader
title={title} title={title}
description={description} description={description}
partner={roadmapData.partner} partner={roadmapData.partner}
roadmapId={roadmapId} roadmapId={roadmapId}
isForkable={true} isForkable={roadmapData.isForkable}
activeTab='projects' activeTab='projects'
projectCount={projects.length} projectCount={projects.length}
coursesCount={0} coursesCount={roadmapData.courses?.length || 0}
hasAIChat={true} hasAIChat={true}
/> />

View File

@@ -1,19 +1,17 @@
--- ---
import { EditorRoadmap } from '../../components/EditorRoadmap/EditorRoadmap'; import { EditorRoadmap } from '../../components/EditorRoadmap/EditorRoadmap';
import FrameRenderer from '../../components/FrameRenderer/FrameRenderer.astro';
import SkeletonLayout from '../../layouts/SkeletonLayout.astro'; import SkeletonLayout from '../../layouts/SkeletonLayout.astro';
import { getOpenGraphImageUrl } from '../../lib/open-graph'; import { getOpenGraphImageUrl } from '../../lib/open-graph';
import { import { type RoadmapFrontmatter, getRoadmapIds } from '../../lib/roadmap';
listOfficialRoadmaps,
officialRoadmapDetails,
} from '../../queries/official-roadmap';
export const prerender = true; export const prerender = true;
export async function getStaticPaths() { export async function getStaticPaths() {
const roadmapIds = await listOfficialRoadmaps(); const roadmapIds = await getRoadmapIds();
return roadmapIds.map((roadmap) => ({ return roadmapIds.map((roadmapId) => ({
params: { roadmapId: roadmap.slug }, params: { roadmapId },
})); }));
} }
@@ -22,13 +20,13 @@ interface Params extends Record<string, string | undefined> {
} }
const { roadmapId } = Astro.params as Params; const { roadmapId } = Astro.params as Params;
const roadmapData = await officialRoadmapDetails(roadmapId); const roadmapFile = await import(
if (!roadmapData) { `../../data/roadmaps/${roadmapId}/${roadmapId}.md`
return Astro.rewrite('/404'); );
} const roadmapData = roadmapFile.frontmatter as RoadmapFrontmatter;
const ogImageUrl = const ogImageUrl =
roadmapData?.openGraph?.image || roadmapData?.seo?.ogImageUrl ||
getOpenGraphImageUrl({ getOpenGraphImageUrl({
group: 'roadmap', group: 'roadmap',
resourceId: roadmapId, resourceId: roadmapId,
@@ -37,8 +35,8 @@ const ogImageUrl =
<SkeletonLayout <SkeletonLayout
permalink={`/${roadmapId}`} permalink={`/${roadmapId}`}
title={roadmapData?.seo?.title || roadmapData.title.page} title={roadmapData?.seo?.title}
briefTitle={roadmapData.title.card} briefTitle={roadmapData.briefTitle}
ogImageUrl={ogImageUrl} ogImageUrl={ogImageUrl}
description={roadmapData.seo.description} description={roadmapData.seo.description}
keywords={roadmapData.seo.keywords} keywords={roadmapData.seo.keywords}
@@ -46,13 +44,23 @@ const ogImageUrl =
resourceType='roadmap' resourceType='roadmap'
noIndex={true} noIndex={true}
> >
<div class='relative container max-w-[1000px]!'> <div class='container relative max-w-[1000px]!'>
<EditorRoadmap {
resourceId={roadmapId} roadmapData?.renderer === 'editor' ? (
resourceType='roadmap' <EditorRoadmap
dimensions={roadmapData.dimensions!} resourceId={roadmapId}
client:load resourceType='roadmap'
hasChat={false} dimensions={roadmapData.dimensions!}
/> client:load
hasChat={false}
/>
) : (
<FrameRenderer
resourceType={'roadmap'}
resourceId={roadmapId}
dimensions={roadmapData.dimensions}
/>
)
}
</div> </div>
</SkeletonLayout> </SkeletonLayout>

View File

@@ -1,53 +1,56 @@
--- ---
import { DateTime } from 'luxon';
import { DashboardPage } from '../components/Dashboard/DashboardPage'; import { DashboardPage } from '../components/Dashboard/DashboardPage';
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from '../layouts/BaseLayout.astro';
import { getAllBestPractices } from '../lib/best-practice'; import { getAllBestPractices } from '../lib/best-practice';
import { getAllQuestionGroups } from '../lib/question-group'; import { getAllQuestionGroups } from '../lib/question-group';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video'; import { getAllVideos } from '../lib/video';
import { listOfficialGuides } from '../queries/official-guide'; import { listOfficialGuides } from '../queries/official-guide';
import {
isNewRoadmap,
listOfficialRoadmaps,
} from '../queries/official-roadmap';
import type { BuiltInRoadmap } from '../components/Dashboard/PersonalDashboard';
const roadmaps = await listOfficialRoadmaps(); const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
const roleRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'role'); const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
const skillRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'skill');
const bestPractices = await getAllBestPractices(); const bestPractices = await getAllBestPractices();
const questionGroups = await getAllQuestionGroups(); const questionGroups = await getAllQuestionGroups();
const guides = await listOfficialGuides(); const guides = await listOfficialGuides();
const videos = await getAllVideos(); const videos = await getAllVideos();
const enrichedRoleRoadmaps: BuiltInRoadmap[] = roleRoadmaps.map((roadmap) => { const enrichedRoleRoadmaps = roleRoadmaps
return { .filter((roadmapItem) => !roadmapItem.frontmatter.isHidden)
id: roadmap.slug, .map((roadmap) => {
url: `/${roadmap.slug}`, const { frontmatter } = roadmap;
title: roadmap.title.card,
description: roadmap.description, return {
relatedRoadmapIds: roadmap.relatedRoadmaps, id: roadmap.id,
renderer: 'editor', url: `/${roadmap.id}`,
isNew: isNewRoadmap(roadmap.createdAt), title: frontmatter.briefTitle,
metadata: { description: frontmatter.briefDescription,
tags: ['role-roadmap'], relatedRoadmapIds: frontmatter.relatedRoadmaps,
}, renderer: frontmatter.renderer,
}; isNew: frontmatter.isNew,
}); metadata: {
const enrichedSkillRoadmaps: BuiltInRoadmap[] = skillRoadmaps.map((roadmap) => { tags: frontmatter.tags,
return { },
id: roadmap.slug, };
url: `/${roadmap.slug}`, });
title: roadmap.title.card === 'Go' ? 'Go Roadmap' : roadmap.title.card, const enrichedSkillRoadmaps = skillRoadmaps
description: roadmap.description, .filter((roadmapItem) => !roadmapItem.frontmatter.isHidden)
relatedRoadmapIds: roadmap.relatedRoadmaps, .map((roadmap) => {
renderer: 'editor', const { frontmatter } = roadmap;
isNew: isNewRoadmap(roadmap.createdAt),
metadata: { return {
tags: ['skill-roadmap'], id: roadmap.id,
}, url: `/${roadmap.id}`,
}; title:
}); frontmatter.briefTitle === 'Go' ? 'Go Roadmap' : frontmatter.briefTitle,
description: frontmatter.briefDescription,
relatedRoadmapIds: frontmatter.relatedRoadmaps,
renderer: frontmatter.renderer,
isNew: frontmatter.isNew,
metadata: {
tags: frontmatter.tags,
},
};
});
const enrichedBestPractices = bestPractices.map((bestPractice) => { const enrichedBestPractices = bestPractices.map((bestPractice) => {
const { frontmatter } = bestPractice; const { frontmatter } = bestPractice;

View File

@@ -1,5 +1,4 @@
--- ---
import { DateTime } from 'luxon';
import ChangelogBanner from '../components/ChangelogBanner.astro'; import ChangelogBanner from '../components/ChangelogBanner.astro';
import { FeaturedGuideList } from '../components/FeaturedGuides/FeaturedGuideList'; import { FeaturedGuideList } from '../components/FeaturedGuides/FeaturedGuideList';
import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro'; import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro';
@@ -7,16 +6,12 @@ import { FeaturedVideoList } from '../components/FeaturedVideos/FeaturedVideoLis
import HeroSection from '../components/HeroSection/HeroSection.astro'; import HeroSection from '../components/HeroSection/HeroSection.astro';
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from '../layouts/BaseLayout.astro';
import { getAllBestPractices } from '../lib/best-practice'; import { getAllBestPractices } from '../lib/best-practice';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video'; import { getAllVideos } from '../lib/video';
import { listOfficialGuides } from '../queries/official-guide'; import { listOfficialGuides } from '../queries/official-guide';
import {
isNewRoadmap,
listOfficialRoadmaps,
} from '../queries/official-roadmap';
const roadmaps = await listOfficialRoadmaps(); const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
const roleRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'role'); const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
const skillRoadmaps = roadmaps.filter((roadmap) => roadmap.type === 'skill');
const bestPractices = await getAllBestPractices(); const bestPractices = await getAllBestPractices();
export const projectGroups = [ export const projectGroups = [
@@ -52,32 +47,33 @@ const videos = await getAllVideos();
<FeaturedItems <FeaturedItems
heading='Role-based Roadmaps' heading='Role-based Roadmaps'
featuredItems={roleRoadmaps.map((roadmapItem) => { featuredItems={roleRoadmaps
const isNew = isNewRoadmap(roadmapItem.createdAt); .filter((roadmapItem) => !roadmapItem.frontmatter.isHidden)
.map((roadmapItem) => ({
return { text: roadmapItem.frontmatter.briefTitle,
text: roadmapItem.title.card, url: `/${roadmapItem.id}`,
url: `/${roadmapItem.slug}`, isNew: roadmapItem.frontmatter.isNew,
isNew, isUpcoming: roadmapItem.frontmatter.isUpcoming,
}; }))}
})}
showCreateRoadmap={true} showCreateRoadmap={true}
/> />
<FeaturedItems <FeaturedItems
heading='Skill-based Roadmaps' heading='Skill-based Roadmaps'
featuredItems={skillRoadmaps.map((roadmapItem) => { featuredItems={skillRoadmaps
const isNew = isNewRoadmap(roadmapItem.createdAt); .filter((roadmapItem) => !roadmapItem.frontmatter.isHidden)
.map((roadmapItem) => ({
return {
text: text:
roadmapItem.title.card === 'Go' roadmapItem.frontmatter.briefTitle === 'Go'
? 'Go Roadmap' ? 'Go Roadmap'
: roadmapItem.title.card.replace('Software Design', 'Design'), : roadmapItem.frontmatter.briefTitle.replace(
url: `/${roadmapItem.slug}`, 'Software Design',
isNew, 'Design',
}; ),
})} url: `/${roadmapItem.id}`,
isNew: roadmapItem.frontmatter.isNew,
isUpcoming: roadmapItem.frontmatter.isUpcoming,
}))}
showCreateRoadmap={true} showCreateRoadmap={true}
/> />

View File

@@ -1,4 +1,5 @@
import { getAllBestPractices } from '../lib/best-practice'; import { getAllBestPractices } from '../lib/best-practice';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video'; import { getAllVideos } from '../lib/video';
import { getAllQuestionGroups } from '../lib/question-group'; import { getAllQuestionGroups } from '../lib/question-group';
import { getAllProjects } from '../lib/project'; import { getAllProjects } from '../lib/project';
@@ -6,7 +7,6 @@ import {
listOfficialAuthors, listOfficialAuthors,
listOfficialGuides, listOfficialGuides,
} from '../queries/official-guide'; } from '../queries/official-guide';
import { listOfficialRoadmaps } from '../queries/official-roadmap';
// Add utility to fetch beginner roadmap file IDs // Add utility to fetch beginner roadmap file IDs
function getBeginnerRoadmapIds() { function getBeginnerRoadmapIds() {
@@ -25,49 +25,40 @@ export async function GET() {
const authors = await listOfficialAuthors(); const authors = await listOfficialAuthors();
const videos = await getAllVideos(); const videos = await getAllVideos();
const questionGroups = await getAllQuestionGroups(); const questionGroups = await getAllQuestionGroups();
const roadmaps = await listOfficialRoadmaps(); const roadmaps = await getRoadmapsByTag('roadmap');
const bestPractices = await getAllBestPractices(); const bestPractices = await getAllBestPractices();
const projects = await getAllProjects(); const projects = await getAllProjects();
// Transform main roadmaps into page objects first so that we can reuse their meta for beginner variants // Transform main roadmaps into page objects first so that we can reuse their meta for beginner variants
const roadmapPages = roadmaps const roadmapPages = roadmaps.map((roadmap) => ({
.map((roadmap) => { id: roadmap.id,
const isBeginner = roadmap.slug.endsWith('-beginner'); url: `/${roadmap.id}`,
if (!isBeginner) { title: roadmap.frontmatter.briefTitle,
return { shortTitle: roadmap.frontmatter.title,
id: roadmap.slug, description: roadmap.frontmatter.briefDescription,
url: `/${roadmap.slug}`, group: 'Roadmaps',
title: roadmap.title.card, metadata: {
shortTitle: roadmap.title.card, tags: roadmap.frontmatter.tags,
description: roadmap.description, },
group: 'Roadmaps', renderer: roadmap?.frontmatter?.renderer || 'balsamiq',
metadata: { }));
tags:
roadmap.type === 'role' ? ['role-roadmap'] : ['skill-roadmap'],
},
renderer: 'editor',
};
}
const parentSlug = roadmap.slug.replace('-beginner', ''); // Generate beginner roadmap page objects
const parentMeta = roadmaps.find((r) => r.slug === parentSlug); const beginnerRoadmapPages = getBeginnerRoadmapIds()
.map((beginnerId) => {
const parentId = beginnerId.replace('-beginner', '');
const parentMeta = roadmapPages.find((page) => page.id === parentId);
if (!parentMeta) { if (!parentMeta) {
return null; return null;
} }
return { return {
id: roadmap.slug, ...parentMeta,
url: `/${parentSlug}?r=${roadmap.slug}`, id: beginnerId,
title: `${parentMeta.title.page} Beginner`, url: `/${parentId}?r=${beginnerId}`,
shortTitle: `${parentMeta.title.page} Beginner`, title: `${parentMeta.title} Beginner`,
description: parentMeta.description, shortTitle: `${parentMeta.shortTitle} Beginner`,
group: 'Roadmaps',
metadata: {
tags: ['beginner-roadmap'],
},
renderer: 'editor',
}; };
}) })
.filter(Boolean); .filter(Boolean);
@@ -75,6 +66,7 @@ export async function GET() {
return new Response( return new Response(
JSON.stringify([ JSON.stringify([
...roadmapPages, ...roadmapPages,
...beginnerRoadmapPages,
...bestPractices.map((bestPractice) => ({ ...bestPractices.map((bestPractice) => ({
id: bestPractice.id, id: bestPractice.id,
url: `/best-practices/${bestPractice.id}`, url: `/best-practices/${bestPractice.id}`,

View File

@@ -1,27 +1,23 @@
--- ---
import BaseLayout from '../../layouts/BaseLayout.astro'; import BaseLayout from '../../layouts/BaseLayout.astro';
import { getRoadmapsProjects } from '../../lib/project'; import { getRoadmapsProjects } from '../../lib/project';
import { getRoadmapsByIds } from '../../lib/roadmap';
import { ProjectsPageHeader } from '../../components/Projects/ProjectsPageHeader'; import { ProjectsPageHeader } from '../../components/Projects/ProjectsPageHeader';
import { ProjectsPage } from '../../components/Projects/ProjectsPage'; import { ProjectsPage } from '../../components/Projects/ProjectsPage';
import { projectApi } from '../../api/project'; import { projectApi } from '../../api/project';
import { listOfficialRoadmaps } from '../../queries/official-roadmap';
const roadmapProjects = await getRoadmapsProjects(); const roadmapProjects = await getRoadmapsProjects();
const allRoadmapIds = Object.keys(roadmapProjects); const allRoadmapIds = Object.keys(roadmapProjects);
const roadmaps = await listOfficialRoadmaps(); const allRoadmaps = await getRoadmapsByIds(allRoadmapIds);
const allRoadmaps = roadmaps.filter((roadmap) =>
allRoadmapIds.includes(roadmap.slug),
);
const enrichedRoadmaps = allRoadmaps.map((roadmap) => { const enrichedRoadmaps = allRoadmaps.map((roadmap) => {
const projects = (roadmapProjects[roadmap.slug] || []).sort((a, b) => { const projects = (roadmapProjects[roadmap.id] || []).sort((a, b) => {
return a.frontmatter.sort - b.frontmatter.sort; return a.frontmatter.sort - b.frontmatter.sort;
}); });
return { return {
id: roadmap.slug, id: roadmap.id,
title: roadmap.title.card, title: roadmap.frontmatter.briefTitle,
projects, projects,
}; };
}); });
@@ -46,5 +42,5 @@ const { response: userCounts } =
userCounts={userCounts || {}} userCounts={userCounts || {}}
client:load client:load
/> />
<div slot='changelog-banner'></div> <div slot="changelog-banner" />
</BaseLayout> </BaseLayout>

View File

@@ -1,7 +1,10 @@
--- ---
import { RoadmapsPage } from '../components/Roadmaps/RoadmapsPage'; import { RoadmapsPage } from '../components/Roadmaps/RoadmapsPage';
import { RoadmapsPageHeader } from '../components/Roadmaps/RoadmapsPageHeader'; import { RoadmapsPageHeader } from '../components/Roadmaps/RoadmapsPageHeader';
import GridItem from '../components/GridItem.astro';
import SimplePageHeader from '../components/SimplePageHeader.astro';
import BaseLayout from '../layouts/BaseLayout.astro'; import BaseLayout from '../layouts/BaseLayout.astro';
import { getRoadmapsByTag } from '../lib/roadmap';
import ChangelogBanner from '../components/ChangelogBanner.astro'; import ChangelogBanner from '../components/ChangelogBanner.astro';
--- ---

View File

@@ -1,7 +1,6 @@
import { queryOptions } from '@tanstack/react-query'; import { queryOptions } from '@tanstack/react-query';
import { FetchError, httpGet } from '../lib/query-http'; import { FetchError, httpGet } from '../lib/query-http';
import type { Node, Edge } from '@roadmapsh/editor'; import type { Node, Edge } from '@roadmapsh/editor';
import { DateTime } from 'luxon';
export const allowedOfficialRoadmapType = ['skill', 'role'] as const; export const allowedOfficialRoadmapType = ['skill', 'role'] as const;
export type AllowedOfficialRoadmapType = export type AllowedOfficialRoadmapType =
@@ -104,11 +103,3 @@ export async function listOfficialRoadmaps() {
throw error; throw error;
} }
} }
export function isNewRoadmap(createdAt: Date) {
return (
createdAt &&
DateTime.now().diff(DateTime.fromJSDate(new Date(createdAt)), 'days').days <
45
);
}