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

chore: replace roadmap listing

This commit is contained in:
Arik Chakma
2025-08-28 13:36:23 +06:00
committed by Kamran Ahmed
parent ffb1cb5059
commit c4c28944ee
14 changed files with 191 additions and 267 deletions

View File

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

View File

@@ -1,6 +1,4 @@
---
import type { RoadmapFileType } from '../lib/roadmap';
export interface Props {
url: string;
title: string;
@@ -27,7 +25,7 @@ const { url, title, description, isNew } = Astro.props;
{
isNew && (
<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='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='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='relative inline-flex h-2 w-2 rounded-full bg-purple-500' />

View File

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

View File

@@ -1,19 +1,7 @@
import type { PageType } from '../components/CommandMenu/CommandMenu';
import type { MarkdownFileType } from './file';
import { httpGet } from './http';
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 interface RoadmapFrontmatter {
@@ -76,99 +64,6 @@ export interface RoadmapFrontmatter {
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(
resourceType: ResourceType,
resourceId: string,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,10 +1,7 @@
---
import { RoadmapsPage } from '../components/Roadmaps/RoadmapsPage';
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 { getRoadmapsByTag } from '../lib/roadmap';
import ChangelogBanner from '../components/ChangelogBanner.astro';
---

View File

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