mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-13 20:54:16 +02:00
Add video listing on homepage
This commit is contained in:
@@ -10,24 +10,26 @@ export interface Props {
|
|||||||
const { heading, guides } = Astro.props;
|
const { heading, guides } = Astro.props;
|
||||||
---
|
---
|
||||||
|
|
||||||
<h1 class='text-2xl sm:text-3xl font-bold block'>{heading}</h1>
|
<div class='container'>
|
||||||
|
<h1 class='text-2xl sm:text-3xl font-bold block'>{heading}</h1>
|
||||||
|
|
||||||
<div class='mt-3 sm:my-5'>
|
<div class='mt-3 sm:my-5'>
|
||||||
{guides.map((guide) => <GuideListItem guide={guide} />)}
|
{guides.map((guide) => <GuideListItem guide={guide} />)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a
|
|
||||||
href='/guides'
|
|
||||||
class='hidden sm:inline transition-colors py-2 px-3 text-xs font-medium rounded-full bg-gradient-to-r from-slate-600 to-black hover:from-blue-600 hover:to-blue-800 text-white'
|
|
||||||
>
|
|
||||||
View All Guides →
|
|
||||||
</a>
|
|
||||||
|
|
||||||
<div class='block sm:hidden mt-3'>
|
|
||||||
<a
|
<a
|
||||||
href='/guides'
|
href='/guides'
|
||||||
class='text-sm font-regular block p-2 border border-black text-black rounded-md text-center hover:bg-black hover:text-gray-50'
|
class='hidden sm:inline transition-colors py-2 px-3 text-xs font-medium rounded-full bg-gradient-to-r from-slate-600 to-black hover:from-blue-600 hover:to-blue-800 text-white'
|
||||||
>
|
>
|
||||||
View All Guides →
|
View All Guides →
|
||||||
</a>
|
</a>
|
||||||
</div>
|
|
||||||
|
<div class='block sm:hidden mt-3'>
|
||||||
|
<a
|
||||||
|
href='/guides'
|
||||||
|
class='text-sm font-regular block p-2 border border-black text-black rounded-md text-center hover:bg-black hover:text-gray-50'
|
||||||
|
>
|
||||||
|
View All Guides →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
35
src/components/FeaturedVideos.astro
Normal file
35
src/components/FeaturedVideos.astro
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
---
|
||||||
|
import type { VideoFileType } from '../lib/video';
|
||||||
|
import VideoListItem from './VideoListItem.astro';
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
heading: string;
|
||||||
|
videos: VideoFileType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const { heading, videos } = Astro.props;
|
||||||
|
---
|
||||||
|
|
||||||
|
<div class='container'>
|
||||||
|
<h1 class='text-2xl sm:text-3xl font-bold block'>{heading}</h1>
|
||||||
|
|
||||||
|
<div class='mt-3 sm:my-5'>
|
||||||
|
{videos.map((video) => <VideoListItem video={video} />)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<a
|
||||||
|
href='/videos'
|
||||||
|
class='hidden sm:inline transition-colors py-2 px-3 text-xs font-medium rounded-full bg-gradient-to-r from-slate-600 to-black hover:from-blue-600 hover:to-blue-800 text-white'
|
||||||
|
>
|
||||||
|
View All Videos →
|
||||||
|
</a>
|
||||||
|
|
||||||
|
<div class='block sm:hidden mt-3'>
|
||||||
|
<a
|
||||||
|
href='/videos'
|
||||||
|
class='text-sm font-regular block p-2 border border-black text-black rounded-md text-center hover:bg-black hover:text-gray-50'
|
||||||
|
>
|
||||||
|
View All Videos →
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
40
src/components/VideoListItem.astro
Normal file
40
src/components/VideoListItem.astro
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
---
|
||||||
|
import type { VideoFileType } from "../lib/video";
|
||||||
|
|
||||||
|
export interface Props {
|
||||||
|
video: VideoFileType;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { video } = Astro.props;
|
||||||
|
const { frontmatter, id } = video;
|
||||||
|
---
|
||||||
|
|
||||||
|
<a
|
||||||
|
class:list={[
|
||||||
|
"block no-underline py-2 group text-md items-center text-gray-600 hover:text-blue-600 flex justify-between border-b",
|
||||||
|
]}
|
||||||
|
href={`/videos/${id}`}
|
||||||
|
>
|
||||||
|
<span class="group-hover:translate-x-2 transition-transform">
|
||||||
|
{frontmatter.title}
|
||||||
|
|
||||||
|
{
|
||||||
|
frontmatter.isNew && (
|
||||||
|
<span class="bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5">
|
||||||
|
New
|
||||||
|
<span class="hidden sm:inline">
|
||||||
|
·
|
||||||
|
{new Date(frontmatter.date).toLocaleString("default", {
|
||||||
|
month: "long",
|
||||||
|
})}
|
||||||
|
</span>
|
||||||
|
</span>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
</span>
|
||||||
|
<span class="capitalize text-gray-500 text-xs hidden sm:block">
|
||||||
|
{frontmatter.duration}
|
||||||
|
</span>
|
||||||
|
|
||||||
|
<span class="text-gray-400 text-xs block sm:hidden"> »</span>
|
||||||
|
</a>
|
@@ -40,7 +40,7 @@ Now that we know what basic authentication is, the question is, how does it work
|
|||||||
### Step 1
|
### Step 1
|
||||||
When the browser first requests the server, the server tries to check the availability of the `Authorization` header in the request. Because it is the first request, no `Authorization` header is found in the request. So the server responds with the `401 Unauthorized` response code and also sends the `WWW-Authenticate` header with the value set to `Basic`, which tells the browser that it needs to trigger the basic authentication flow.
|
When the browser first requests the server, the server tries to check the availability of the `Authorization` header in the request. Because it is the first request, no `Authorization` header is found in the request. So the server responds with the `401 Unauthorized` response code and also sends the `WWW-Authenticate` header with the value set to `Basic`, which tells the browser that it needs to trigger the basic authentication flow.
|
||||||
|
|
||||||
```text
|
```plaintext
|
||||||
401 Unauthorized
|
401 Unauthorized
|
||||||
WWW-Authenticate: Basic realm='user_pages'
|
WWW-Authenticate: Basic realm='user_pages'
|
||||||
```
|
```
|
||||||
|
61
src/lib/video.ts
Normal file
61
src/lib/video.ts
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
import type { MarkdownFileType } from './file';
|
||||||
|
|
||||||
|
export interface VideoFrontmatter {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
author: {
|
||||||
|
name: string;
|
||||||
|
url: string;
|
||||||
|
imageUrl: string;
|
||||||
|
};
|
||||||
|
seo: {
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
};
|
||||||
|
isNew: boolean;
|
||||||
|
duration: string;
|
||||||
|
date: string;
|
||||||
|
sitemap: {
|
||||||
|
priority: number;
|
||||||
|
changefreq: 'daily' | 'weekly' | 'monthly' | 'yealry';
|
||||||
|
};
|
||||||
|
tags: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VideoFileType = MarkdownFileType<VideoFrontmatter> & {
|
||||||
|
id: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Generates id from the given video file
|
||||||
|
* @param filePath Markdown file path
|
||||||
|
*
|
||||||
|
* @returns unique video identifier
|
||||||
|
*/
|
||||||
|
function videoPathToId(filePath: string): string {
|
||||||
|
const fileName = filePath.split('/').pop() || '';
|
||||||
|
|
||||||
|
return fileName.replace('.md', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets all the videos sorted by the publishing date
|
||||||
|
* @returns Promisifed video files
|
||||||
|
*/
|
||||||
|
export async function getAllVideos(): Promise<VideoFileType[]> {
|
||||||
|
const videos = await import.meta.glob<VideoFileType>('/src/videos/*.md', {
|
||||||
|
eager: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const videoFiles = Object.values(videos);
|
||||||
|
const enrichedVideos = videoFiles.map((videoFile) => ({
|
||||||
|
...videoFile,
|
||||||
|
id: videoPathToId(videoFile.file),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return enrichedVideos.sort(
|
||||||
|
(a, b) =>
|
||||||
|
new Date(b.frontmatter.date).valueOf() -
|
||||||
|
new Date(a.frontmatter.date).valueOf()
|
||||||
|
);
|
||||||
|
}
|
@@ -1,13 +1,16 @@
|
|||||||
---
|
---
|
||||||
import FeaturedGuides from '../components/FeaturedGuides.astro';
|
import FeaturedGuides from '../components/FeaturedGuides.astro';
|
||||||
import FeaturedRoadmaps from '../components/FeaturedRoadmaps/FeaturedRoadmaps.astro';
|
import FeaturedRoadmaps from '../components/FeaturedRoadmaps/FeaturedRoadmaps.astro';
|
||||||
|
import FeaturedVideos from '../components/FeaturedVideos.astro';
|
||||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||||
import { getAllGuides } from '../lib/guide';
|
import { getAllGuides } from '../lib/guide';
|
||||||
import { getRoadmapsByTag } from '../lib/roadmap';
|
import { getRoadmapsByTag } from '../lib/roadmap';
|
||||||
|
import { getAllVideos } from '../lib/video';
|
||||||
|
|
||||||
const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
|
const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
|
||||||
const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
|
const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
|
||||||
const guides = await getAllGuides();
|
const guides = await getAllGuides();
|
||||||
|
const videos = await getAllVideos();
|
||||||
---
|
---
|
||||||
|
|
||||||
<BaseLayout title='Developer Roadmaps'>
|
<BaseLayout title='Developer Roadmaps'>
|
||||||
@@ -39,9 +42,8 @@ const guides = await getAllGuides();
|
|||||||
<FeaturedRoadmaps heading='Skill based Roadmaps' roadmaps={skillRoadmaps} />
|
<FeaturedRoadmaps heading='Skill based Roadmaps' roadmaps={skillRoadmaps} />
|
||||||
|
|
||||||
<div class='grid grid-cols-1 gap-7 sm:gap-16 bg-gray-50 py-7 sm:py-16'>
|
<div class='grid grid-cols-1 gap-7 sm:gap-16 bg-gray-50 py-7 sm:py-16'>
|
||||||
<div class='container'>
|
<FeaturedGuides heading='Guides' guides={guides.slice(0, 7)} />
|
||||||
<FeaturedGuides heading='Guides' guides={guides.slice(0, 7)} />
|
<FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} />
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</BaseLayout>
|
</BaseLayout>
|
||||||
|
23
src/pages/videos/index.astro
Normal file
23
src/pages/videos/index.astro
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
---
|
||||||
|
import VideoListItem from '../../components/VideoListItem.astro';
|
||||||
|
import SimplePageHeader from '../../components/SimplePageHeader.astro';
|
||||||
|
import BaseLayout from '../../layouts/BaseLayout.astro';
|
||||||
|
import { getAllVideos } from '../../lib/video';
|
||||||
|
|
||||||
|
const guides = await getAllGuides();
|
||||||
|
---
|
||||||
|
|
||||||
|
<BaseLayout title='Guides'>
|
||||||
|
<SimplePageHeader
|
||||||
|
title='Guides'
|
||||||
|
description='Succinct graphical explanations to engineering topics.'
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class='pb-20 pt-2 bg-gray-50'>
|
||||||
|
<div class='container'>
|
||||||
|
<div class='mt-3 sm:my-5'>
|
||||||
|
{guides.map((guide) => <GuideListItem guide={guide} />)}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</BaseLayout>
|
Reference in New Issue
Block a user