Add guides and videos on dashboard

pull/8189/head
Kamran Ahmed 3 months ago
parent d02f0e5d54
commit f94c11d1ea
  1. 8
      src/components/Dashboard/DashboardPage.tsx
  2. 20
      src/components/Dashboard/PersonalDashboard.tsx
  3. 47
      src/components/FeaturedGuides.astro
  4. 51
      src/components/FeaturedGuides/FeaturedGuideList.tsx
  5. 57
      src/components/FeaturedGuides/GuideListItem.tsx
  6. 35
      src/components/FeaturedVideos.astro
  7. 39
      src/components/FeaturedVideos/FeaturedVideoList.tsx
  8. 38
      src/components/FeaturedVideos/VideoListItem.tsx
  9. 61
      src/components/GuideListItem.astro
  10. 2
      src/components/HeroSection/HeroItemsGroup.tsx
  11. 40
      src/components/VideoListItem.astro
  12. 2
      src/lib/question-group.ts
  13. 19
      src/pages/authors/[authorId].astro
  14. 6
      src/pages/dashboard.astro
  15. 8
      src/pages/guides/index.astro
  16. 12
      src/pages/index.astro
  17. 2
      src/pages/videos/index.astro

@ -11,6 +11,8 @@ import { DashboardTabButton } from './DashboardTabButton';
import { PersonalDashboard, type BuiltInRoadmap } from './PersonalDashboard';
import { TeamDashboard } from './TeamDashboard';
import type { QuestionGroupType } from '../../lib/question-group';
import type { GuideFileType } from '../../lib/guide';
import type { VideoFileType } from '../../lib/video';
type DashboardPageProps = {
builtInRoleRoadmaps?: BuiltInRoadmap[];
@ -18,6 +20,8 @@ type DashboardPageProps = {
builtInBestPractices?: BuiltInRoadmap[];
isTeamPage?: boolean;
questionGroups?: QuestionGroupType[];
guides?: GuideFileType[];
videos?: VideoFileType[];
};
export function DashboardPage(props: DashboardPageProps) {
@ -27,6 +31,8 @@ export function DashboardPage(props: DashboardPageProps) {
builtInSkillRoadmaps,
isTeamPage = false,
questionGroups,
guides,
videos,
} = props;
const currentUser = getUser();
@ -128,6 +134,8 @@ export function DashboardPage(props: DashboardPageProps) {
builtInSkillRoadmaps={builtInSkillRoadmaps}
builtInBestPractices={builtInBestPractices}
questionGroups={questionGroups}
guides={guides}
videos={videos}
/>
</div>
)}

@ -26,6 +26,11 @@ import type { ProjectStatusDocument } from '../Projects/ListProjectSolutions';
import type { UserProgress } from '../TeamProgress/TeamProgressPage';
import { projectGroups } from '../../pages/index.astro';
import type { QuestionGroupType } from '../../lib/question-group';
import { FeaturedGuideList } from '../FeaturedGuides/FeaturedGuideList';
import { FeaturedVideoList } from '../FeaturedVideos/FeaturedVideoList';
import type { GuideFileType } from '../../lib/guide';
import type { VideoFileType } from '../../lib/video';
type UserDashboardResponse = {
name: string;
email: string;
@ -56,6 +61,8 @@ type PersonalDashboardProps = {
builtInSkillRoadmaps?: BuiltInRoadmap[];
builtInBestPractices?: BuiltInRoadmap[];
questionGroups?: QuestionGroupType[];
guides?: GuideFileType[];
videos?: VideoFileType[];
};
type DashboardStatItemProps = {
@ -203,6 +210,8 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
builtInBestPractices = [],
builtInSkillRoadmaps = [],
questionGroups = [],
guides = [],
videos = [],
} = props;
const toast = useToast();
@ -529,6 +538,17 @@ export function PersonalDashboard(props: PersonalDashboardProps) {
</div>
</div>
</div>
<div className="grid grid-cols-1 gap-7 bg-gray-50 py-7 sm:gap-16 sm:py-16">
<FeaturedGuideList
heading="Guides"
guides={guides}
questions={questionGroups
.filter((questionGroup) => questionGroup.frontmatter.authorId)
.slice(0, 7)}
/>
<FeaturedVideoList heading="Videos" videos={videos} />
</div>
</div>
);
}

@ -1,47 +0,0 @@
---
import type { GuideFileType } from '../lib/guide';
import GuideListItem from './GuideListItem.astro';
import type { QuestionGroupType } from '../lib/question-group';
export interface Props {
heading: string;
guides: GuideFileType[];
questions: QuestionGroupType[];
}
const { heading, guides, questions = [] } = Astro.props;
const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
...guides,
...questions,
].sort((a, b) => {
const aDate = new Date(a.frontmatter.date as string);
const bDate = new Date(b.frontmatter.date as string);
return bDate.getTime() - aDate.getTime();
});
---
<div class='container'>
<h2 class='block text-2xl font-bold sm:text-3xl'>{heading}</h2>
<div class='mt-3 sm:my-5'>
{sortedGuides.map((guide) => <GuideListItem guide={guide} />)}
</div>
<a
href='/guides'
class='hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline'
>
View All Guides &rarr;
</a>
<div class='mt-3 block sm:hidden'>
<a
href='/guides'
class='font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50'
>
View All Guides &nbsp;&rarr;
</a>
</div>
</div>

@ -0,0 +1,51 @@
import type { GuideFileType } from '../../lib/guide';
import type { QuestionGroupType } from '../../lib/question-group';
import { GuideListItem } from './GuideListItem';
export interface FeaturedGuidesProps {
heading: string;
guides: GuideFileType[];
questions: QuestionGroupType[];
}
export function FeaturedGuideList(props: FeaturedGuidesProps) {
const { heading, guides, questions = [] } = props;
const sortedGuides: (QuestionGroupType | GuideFileType)[] = [
...guides,
...questions,
].sort((a, b) => {
const aDate = new Date(a.frontmatter.date as string);
const bDate = new Date(b.frontmatter.date as string);
return bDate.getTime() - aDate.getTime();
});
return (
<div className="container">
<h2 className="block text-2xl font-bold sm:text-3xl">{heading}</h2>
<div className="mt-3 sm:my-5">
{sortedGuides.map((guide) => (
<GuideListItem key={guide.id} guide={guide} />
))}
</div>
<a
href="/guides"
className="hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline"
>
View All Guides &rarr;
</a>
<div className="mt-3 block sm:hidden">
<a
href="/guides"
className="font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50"
>
View All Guides &nbsp;&rarr;
</a>
</div>
</div>
);
}

@ -0,0 +1,57 @@
import type { GuideFileType, GuideFrontmatter } from '../../lib/guide';
import { type QuestionGroupType } from '../../lib/question-group';
export interface GuideListItemProps {
guide: GuideFileType | QuestionGroupType;
}
function isQuestionGroupType(
guide: GuideFileType | QuestionGroupType,
): guide is QuestionGroupType {
return (guide as QuestionGroupType).questions !== undefined;
}
export function GuideListItem(props: GuideListItemProps) {
const { guide } = props;
const { frontmatter, id } = guide;
let pageUrl = '';
let guideType = '';
if (isQuestionGroupType(guide)) {
pageUrl = `/questions/${id}`;
guideType = 'Questions';
} else {
const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug;
pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`;
guideType = (frontmatter as GuideFrontmatter).type;
}
return (
<a
className="text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600"
href={pageUrl}
>
<span className="text-sm transition-transform group-hover:translate-x-2 md:text-base">
{frontmatter.title}
{frontmatter.isNew && (
<span className="ml-2.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900">
New
<span className="hidden sm:inline">
&nbsp;&middot;&nbsp;
{new Date(frontmatter.date || '').toLocaleString('default', {
month: 'long',
})}
</span>
</span>
)}
</span>
<span className="hidden text-xs capitalize text-gray-500 sm:block">
{guideType}
</span>
<span className="block text-xs text-gray-400 sm:hidden"> &raquo;</span>
</a>
);
}

@ -1,35 +0,0 @@
---
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'>
<h2 class='text-2xl sm:text-3xl font-bold block'>{heading}</h2>
<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 &rarr;
</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 &nbsp;&rarr;
</a>
</div>
</div>

@ -0,0 +1,39 @@
import type { VideoFileType } from '../../lib/video';
import { VideoListItem } from './VideoListItem';
export interface FeaturedVideoListProps {
heading: string;
videos: VideoFileType[];
}
export function FeaturedVideoList(props: FeaturedVideoListProps) {
const { heading, videos } = props;
return (
<div className="container">
<h2 className="block text-2xl font-bold sm:text-3xl">{heading}</h2>
<div className="mt-3 sm:my-5">
{videos.map((video) => (
<VideoListItem key={video.id} video={video} />
))}
</div>
<a
href="/videos"
className="hidden rounded-full bg-gradient-to-r from-slate-600 to-black px-3 py-2 text-xs font-medium text-white transition-colors hover:from-blue-600 hover:to-blue-800 sm:inline"
>
View All Videos &rarr;
</a>
<div className="mt-3 block sm:hidden">
<a
href="/videos"
className="font-regular block rounded-md border border-black p-2 text-center text-sm text-black hover:bg-black hover:text-gray-50"
>
View All Videos &nbsp;&rarr;
</a>
</div>
</div>
);
}

@ -0,0 +1,38 @@
import type { VideoFileType } from '../../lib/video';
export interface VideoListItemProps {
video: VideoFileType;
}
export function VideoListItem(props: VideoListItemProps) {
const { video } = props;
const { frontmatter, id } = video;
return (
<a
className="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 className="group-hover:translate-x-2 transition-transform">
{frontmatter.title}
{frontmatter.isNew && (
<span className="bg-green-300 text-green-900 text-xs font-medium px-1.5 py-0.5 rounded-sm uppercase ml-1.5">
New
<span className="hidden sm:inline">
&middot;
{new Date(frontmatter.date).toLocaleString('default', {
month: 'long',
})}
</span>
</span>
)}
</span>
<span className="capitalize text-gray-500 text-xs hidden sm:block">
{frontmatter.duration}
</span>
<span className="text-gray-400 text-xs block sm:hidden"> &raquo;</span>
</a>
);
}

@ -1,61 +0,0 @@
---
import type { GuideFileType, GuideFrontmatter } from '../lib/guide';
import { type QuestionGroupType } from '../lib/question-group';
export interface Props {
guide: GuideFileType | QuestionGroupType;
}
function isQuestionGroupType(
guide: GuideFileType | QuestionGroupType,
): guide is QuestionGroupType {
return (guide as QuestionGroupType).questions !== undefined;
}
const { guide } = Astro.props;
const { frontmatter, id } = guide;
let pageUrl = '';
let guideType = '';
if (isQuestionGroupType(guide)) {
pageUrl = `/questions/${id}`;
guideType = 'Questions';
} else {
const excludedBySlug = (frontmatter as GuideFrontmatter).excludedBySlug;
pageUrl = excludedBySlug ? excludedBySlug : `/guides/${id}`;
guideType = (frontmatter as GuideFrontmatter).type;
}
---
<a
class:list={[
'text-md group block flex items-center justify-between border-b py-2 text-gray-600 no-underline hover:text-blue-600',
]}
href={pageUrl}
>
<span
class='text-sm transition-transform group-hover:translate-x-2 md:text-base'
>
{frontmatter.title}
{
frontmatter.isNew && (
<span class='ml-1.5 rounded-sm bg-green-300 px-1.5 py-0.5 text-xs font-medium uppercase text-green-900'>
New
<span class='hidden sm:inline'>
&middot;
{new Date(frontmatter.date || '').toLocaleString('default', {
month: 'long',
})}
</span>
</span>
)
}
</span>
<span class='hidden text-xs capitalize text-gray-500 sm:block'>
{guideType}
</span>
<span class='block text-xs text-gray-400 sm:hidden'> &raquo;</span>
</a>

@ -46,7 +46,7 @@ export function HeroItemsGroup(props: HeroItemsGroupProps) {
'border-b border-gray-800/50',
{
'py-4': !isLoadingOrCollapsedOrEmpty,
'py-3': isLoadingOrCollapsedOrEmpty,
'py-4 ': isLoadingOrCollapsedOrEmpty,
'opacity-50 transition-opacity hover:opacity-100':
isCollapsed && !isLoading,
},

@ -1,40 +0,0 @@
---
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'>
&middot;
{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'> &raquo;</span>
</a>

@ -3,7 +3,7 @@ import slugify from 'slugify';
import { getAllAuthors, type AuthorFileType } from './author.ts';
import { getAllGuides } from './guide.ts';
interface RawQuestionGroupFrontmatter {
export interface RawQuestionGroupFrontmatter {
order: number;
briefTitle: string;
briefDescription: string;

@ -1,12 +1,12 @@
---
import BaseLayout from '../../layouts/BaseLayout.astro';
import AstroIcon from '../../components/AstroIcon.astro';
import { getGuidesByAuthor } from '../../lib/guide';
import { getVideosByAuthor } from '../../lib/video';
import GuideListItem from '../../components/GuideListItem.astro';
import { GuideListItem } from '../../components/FeaturedGuides/GuideListItem';
import { VideoListItem } from '../../components/FeaturedVideos/VideoListItem';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAuthorById, getAuthorIds } from '../../lib/author';
import VideoListItem from '../../components/VideoListItem.astro';
import { getGuidesByAuthor } from '../../lib/guide';
import { getAllQuestionGroups } from '../../lib/question-group';
import { getVideosByAuthor } from '../../lib/video';
interface Params extends Record<string, string | undefined> {}
@ -136,9 +136,12 @@ const videos = await getVideosByAuthor(authorId);
{
[...guides, ...questionGuides]
.sort((a, b) => {
const aDate = a.frontmatter.date || a.frontmatter.publishedAt;
const bDate = b.frontmatter.date || b.frontmatter.publishedAt;
return new Date(bDate) - new Date(aDate);
const aFrontmatter = a.frontmatter as any;
const bFrontmatter = b.frontmatter as any;
const aDate = aFrontmatter.date || aFrontmatter.publishedAt;
const bDate = bFrontmatter.date || bFrontmatter.publishedAt;
return new Date(bDate).getTime() - new Date(aDate).getTime();
})
.map((guide) => <GuideListItem guide={guide} />)
}

@ -4,11 +4,15 @@ import BaseLayout from '../layouts/BaseLayout.astro';
import { getAllBestPractices } from '../lib/best-practice';
import { getAllQuestionGroups } from '../lib/question-group';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllGuides } from '../lib/guide';
import { getAllVideos } from '../lib/video';
const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
const bestPractices = await getAllBestPractices();
const questionGroups = await getAllQuestionGroups();
const guides = await getAllGuides();
const videos = await getAllVideos();
const enrichedRoleRoadmaps = roleRoadmaps
.filter((roadmapItem) => !roadmapItem.frontmatter.isHidden)
@ -66,6 +70,8 @@ const enrichedBestPractices = bestPractices.map((bestPractice) => {
builtInSkillRoadmaps={enrichedSkillRoadmaps}
builtInBestPractices={enrichedBestPractices}
questionGroups={questionGroups}
guides={guides.slice(0, 7)}
videos={videos.slice(0, 7)}
client:load
/>
<div slot='open-source-banner'></div>

@ -1,5 +1,5 @@
---
import GuideListItem from '../../components/GuideListItem.astro';
import { GuideListItem } from '../../components/FeaturedGuides/GuideListItem';
import SimplePageHeader from '../../components/SimplePageHeader.astro';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllGuides } from '../../lib/guide';
@ -12,8 +12,8 @@ const questionGuides = (await getAllQuestionGroups()).filter(
const allGuides = [...guides, ...questionGuides];
const sortedGuides = allGuides.sort((a, b) => {
const aDate = new Date(a.frontmatter.date);
const bDate = new Date(b.frontmatter.date);
const aDate = new Date(a.frontmatter.date as string);
const bDate = new Date(b.frontmatter.date as string);
return bDate.getTime() - aDate.getTime();
});
@ -36,5 +36,5 @@ const sortedGuides = allGuides.sort((a, b) => {
</div>
</div>
</div>
<div slot="changelog-banner" />
<div slot='changelog-banner'></div>
</BaseLayout>

@ -1,15 +1,15 @@
---
import FeaturedVideos from '../components/FeaturedVideos.astro';
import FeaturedGuides from '../components/FeaturedGuides.astro';
import ChangelogBanner from '../components/ChangelogBanner.astro';
import { FeaturedGuideList } from '../components/FeaturedGuides/FeaturedGuideList';
import FeaturedItems from '../components/FeaturedItems/FeaturedItems.astro';
import { FeaturedVideoList } from '../components/FeaturedVideos/FeaturedVideoList';
import HeroSection from '../components/HeroSection/HeroSection.astro';
import BaseLayout from '../layouts/BaseLayout.astro';
import { getAllBestPractices } from '../lib/best-practice';
import { getAllGuides } from '../lib/guide';
import { getAllQuestionGroups } from '../lib/question-group';
import { getRoadmapsByTag } from '../lib/roadmap';
import { getAllVideos } from '../lib/video';
import { getAllQuestionGroups } from '../lib/question-group';
import ChangelogBanner from '../components/ChangelogBanner.astro';
const roleRoadmaps = await getRoadmapsByTag('role-roadmap');
const skillRoadmaps = await getRoadmapsByTag('skill-roadmap');
@ -105,12 +105,12 @@ const videos = await getAllVideos();
/>
<div class='grid grid-cols-1 gap-7 bg-gray-50 py-7 sm:gap-16 sm:py-16'>
<FeaturedGuides
<FeaturedGuideList
heading='Guides'
guides={guides.slice(0, 7)}
questions={questionGuides.slice(0, 7)}
/>
<FeaturedVideos heading='Videos' videos={videos.slice(0, 7)} />
<FeaturedVideoList heading='Videos' videos={videos.slice(0, 7)} />
</div>
</div>
<ChangelogBanner slot='changelog-banner' />

@ -1,6 +1,6 @@
---
import VideoListItem from '../../components/VideoListItem.astro';
import SimplePageHeader from '../../components/SimplePageHeader.astro';
import { VideoListItem } from '../../components/FeaturedVideos/VideoListItem';
import BaseLayout from '../../layouts/BaseLayout.astro';
import { getAllVideos } from '../../lib/video';

Loading…
Cancel
Save