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;
|
||||
---
|
||||
|
||||
<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} />)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
<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>
|
||||
</a>
|
||||
|
||||
<div class='block sm:hidden mt-3'>
|
||||
<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
|
||||
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
|
||||
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 FeaturedRoadmaps from '../components/FeaturedRoadmaps/FeaturedRoadmaps.astro';
|
||||
import FeaturedVideos from '../components/FeaturedVideos.astro';
|
||||
import BaseLayout from '../layouts/BaseLayout.astro';
|
||||
import { getAllGuides } from '../lib/guide';
|
||||
import { getRoadmapsByTag } from '../lib/roadmap';
|
||||
import { getAllVideos } from '../lib/video';
|
||||
|
||||
const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
|
||||
const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
|
||||
const guides = await getAllGuides();
|
||||
const videos = await getAllVideos();
|
||||
---
|
||||
|
||||
<BaseLayout title='Developer Roadmaps'>
|
||||
@@ -39,9 +42,8 @@ const guides = await getAllGuides();
|
||||
<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='container'>
|
||||
<FeaturedGuides heading='Guides' guides={guides.slice(0, 7)} />
|
||||
</div>
|
||||
<FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} />
|
||||
</div>
|
||||
</div>
|
||||
</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