parent
d02f0e5d54
commit
f94c11d1ea
17 changed files with 243 additions and 204 deletions
@ -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 → |
|
||||||
</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 → |
|
||||||
</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 → |
||||||
|
</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 → |
||||||
|
</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"> |
||||||
|
· |
||||||
|
{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"> »</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 → |
|
||||||
</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> |
|
@ -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 → |
||||||
|
</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 → |
||||||
|
</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"> |
||||||
|
· |
||||||
|
{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"> »</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'> |
|
||||||
· |
|
||||||
{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'> »</span> |
|
||||||
</a> |
|
@ -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'> |
|
||||||
· |
|
||||||
{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> |
|
Loading…
Reference in new issue