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:
committed by
Kamran Ahmed
parent
ffb1cb5059
commit
c4c28944ee
@@ -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);
|
||||
|
@@ -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' />
|
||||
|
@@ -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 {
|
||||
|
@@ -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,
|
||||
|
@@ -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> · <a
|
||||
<a class='text-blue-700 underline' href='/'>Homepage</a> · <a
|
||||
href='/roadmaps'
|
||||
class='underline text-blue-700'>Roadmaps</a
|
||||
> · <a href='/best-practices' class='underline text-blue-700'>Best Practices</a>
|
||||
class='text-blue-700 underline'>Roadmaps</a
|
||||
> · <a href='/best-practices' class='text-blue-700 underline'
|
||||
>Best Practices</a
|
||||
>
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
@@ -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> -->
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
||||
|
@@ -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>
|
||||
|
@@ -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;
|
||||
|
@@ -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}
|
||||
/>
|
||||
|
||||
|
@@ -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}`,
|
||||
|
@@ -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>
|
||||
|
@@ -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';
|
||||
---
|
||||
|
||||
|
@@ -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
|
||||
);
|
||||
}
|
||||
|
Reference in New Issue
Block a user