feat: preview templates, thumbnail features, 2 new org templates
This commit is contained in:
@@ -0,0 +1,97 @@
|
||||
<script lang="ts">
|
||||
import type { CourseItem, OrgLandingPageLabels } from '../types';
|
||||
import * as Item from '../../../base/item';
|
||||
import { CourseCard } from '../../course-card';
|
||||
import { calcCourseDiscount, formatExerciseCountLabel, getCourseTypeLandingMeta } from '../landing-page-utils';
|
||||
import { cn } from '../../../tools';
|
||||
|
||||
interface Props {
|
||||
course: CourseItem;
|
||||
disableCourseLinks?: boolean;
|
||||
labels?: OrgLandingPageLabels;
|
||||
}
|
||||
|
||||
let { course, disableCourseLinks = false, labels }: Props = $props();
|
||||
|
||||
const courseTypeMeta = $derived(getCourseTypeLandingMeta(course));
|
||||
const pricingData = $derived({
|
||||
cost: course.cost ?? 0,
|
||||
discount: course.metadata?.discount ?? 0,
|
||||
showDiscount: !!course.metadata?.showDiscount
|
||||
});
|
||||
const displayCost = $derived(calcCourseDiscount(pricingData.discount, pricingData.cost, pricingData.showDiscount));
|
||||
const currencyFormatter = $derived(
|
||||
new Intl.NumberFormat('en-US', {
|
||||
style: 'currency',
|
||||
currency: course.currency ?? 'USD'
|
||||
})
|
||||
);
|
||||
|
||||
const href = $derived.by(() => {
|
||||
if (disableCourseLinks) return undefined;
|
||||
return course.link || (course.slug ? `/course/${course.slug}` : undefined);
|
||||
});
|
||||
</script>
|
||||
|
||||
<CourseCard
|
||||
{href}
|
||||
bannerImage={course.logo || ''}
|
||||
bannerAlt={course.title}
|
||||
title={course.title}
|
||||
description={course.description}
|
||||
typeBadge={courseTypeMeta
|
||||
? {
|
||||
label: courseTypeMeta.label,
|
||||
icon: courseTypeMeta.icon,
|
||||
iconClass: courseTypeMeta.iconClass
|
||||
}
|
||||
: undefined}
|
||||
class={cn('ui:group ui:relative', !disableCourseLinks && 'ui:cursor-pointer ui:hover:shadow-md ui:transition-shadow')}
|
||||
>
|
||||
{#snippet tags()}
|
||||
{#if course.tags && course.tags.length > 0}
|
||||
<div class="ui:flex ui:flex-wrap ui:gap-1 ui:min-w-0">
|
||||
{#each course.tags as tag (tag.id)}
|
||||
<Item.SubDescription
|
||||
class="ui:border-[var(--landing-border)] ui:inline-flex ui:shrink-0 ui:items-center ui:gap-1.5 ui:rounded-full ui:border ui:px-2"
|
||||
>
|
||||
<span
|
||||
class="ui:bg-[var(--landing-accent)]/60 ui:inline-block ui:h-1.5 ui:w-1.5 ui:shrink-0 ui:rounded-full"
|
||||
style={tag.color ? `background-color: ${tag.color}` : undefined}
|
||||
aria-hidden="true"
|
||||
></span>
|
||||
<span>{tag.name}</span>
|
||||
</Item.SubDescription>
|
||||
{/each}
|
||||
</div>
|
||||
{/if}
|
||||
{/snippet}
|
||||
|
||||
{#snippet footer()}
|
||||
<div class="ui:flex ui:justify-between ui:w-full">
|
||||
<div class="ui:w-[80%]">
|
||||
<p class="ui:text-xs ui:pl-2 ui:dark:text-white">
|
||||
<span>{course.lessonCount || 0} lessons</span>
|
||||
&
|
||||
<span>{formatExerciseCountLabel(course.exerciseCount ?? 0)}</span>
|
||||
</p>
|
||||
<div class="ui:py-2 ui:text-xs">
|
||||
{#if course.price != null && String(course.price).length > 0}
|
||||
<span class="ui:px-2">{course.price}</span>
|
||||
{:else if !displayCost}
|
||||
<span class="ui:px-2">{labels?.freeLabel ?? 'Free'}</span>
|
||||
{:else if pricingData.showDiscount}
|
||||
<span class="ui:px-2">
|
||||
{currencyFormatter.format(displayCost)}
|
||||
<span class="line-through">
|
||||
{currencyFormatter.format(pricingData.cost)}
|
||||
</span>
|
||||
</span>
|
||||
{:else}
|
||||
<span class="ui:px-2">{currencyFormatter.format(displayCost)}</span>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/snippet}
|
||||
</CourseCard>
|
||||
@@ -2,7 +2,6 @@
|
||||
import type { OrgLandingPageProps } from '../types';
|
||||
import SecondaryActionButton from '../secondary-action-button.svelte';
|
||||
import { Button } from '../../../base/button';
|
||||
import { BlurFade } from '../../animation/blurfade';
|
||||
import { BlurIn } from '../../animation/blurin';
|
||||
import { DotPattern } from '../../animation/dot-pattern';
|
||||
import EditableLandingSection from '../editable-section.svelte';
|
||||
@@ -16,7 +15,7 @@
|
||||
|
||||
<EditableLandingSection sectionKey="hero">
|
||||
<section
|
||||
class="ui:relative ui:bg-foreground ui:text-background ui:overflow-hidden ui:py-24 ui:sm:py-32 ui:px-6 ui:lg:px-8"
|
||||
class="ui:relative ui:bg-[var(--landing-fg)] ui:text-[var(--landing-bg)] ui:overflow-hidden ui:py-24 ui:sm:py-32 ui:px-6 ui:lg:px-8"
|
||||
>
|
||||
{#if hero.image}
|
||||
<div class="ui:absolute ui:inset-0 ui:opacity-10">
|
||||
@@ -30,28 +29,24 @@
|
||||
>
|
||||
{hero.heading}
|
||||
</BlurIn>
|
||||
<BlurFade delay={0.15} once={true}>
|
||||
<p class="ui:text-lg ui:sm:text-xl ui:text-background/80 ui:mb-10">{hero.subheading}</p>
|
||||
</BlurFade>
|
||||
<BlurFade delay={0.3} once={true}>
|
||||
<div class="ui:flex ui:flex-col ui:sm:flex-row ui:justify-center ui:gap-4">
|
||||
<Button
|
||||
href={hero.primaryAction.href}
|
||||
disabled={hero.primaryAction.disabled ?? false}
|
||||
size="lg"
|
||||
class="ui:px-8 ui:font-semibold"
|
||||
>
|
||||
{hero.primaryAction.label}
|
||||
</Button>
|
||||
{#if hero.secondaryAction}
|
||||
<SecondaryActionButton
|
||||
href={hero.secondaryAction.href}
|
||||
label={hero.secondaryAction.label}
|
||||
variant="classic"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</BlurFade>
|
||||
<p class="ui:text-lg ui:sm:text-xl ui:text-[var(--landing-bg)]/80 ui:mb-10">{hero.subheading}</p>
|
||||
<div class="ui:flex ui:flex-col ui:sm:flex-row ui:justify-center ui:gap-4">
|
||||
<Button
|
||||
href={hero.primaryAction.href}
|
||||
disabled={hero.primaryAction.disabled ?? false}
|
||||
size="lg"
|
||||
class="ui:px-8 ui:font-semibold"
|
||||
>
|
||||
{hero.primaryAction.label}
|
||||
</Button>
|
||||
{#if hero.secondaryAction}
|
||||
<SecondaryActionButton
|
||||
href={hero.secondaryAction.href}
|
||||
label={hero.secondaryAction.label}
|
||||
variant="classic"
|
||||
/>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</EditableLandingSection>
|
||||
|
||||
@@ -17,9 +17,12 @@
|
||||
</script>
|
||||
|
||||
<EditableLandingSection sectionKey="navigation">
|
||||
<header class="ui:bg-foreground ui:text-background">
|
||||
<header class="ui:bg-[var(--landing-fg)] ui:text-[var(--landing-bg)]">
|
||||
<div class="ui:flex ui:items-center ui:justify-between ui:px-6 ui:lg:px-8 ui:h-20 ui:max-w-7xl ui:mx-auto">
|
||||
<a href="/" class="ui:flex ui:items-center ui:gap-3 ui:text-xl ui:font-bold ui:tracking-wide ui:no-underline">
|
||||
<a
|
||||
href="/"
|
||||
class="ui:flex ui:items-center ui:gap-3 ui:text-xl ui:font-bold ui:tracking-wide ui:no-underline ui:cursor-pointer"
|
||||
>
|
||||
{#if logoUrl}
|
||||
<img src={logoUrl} alt={orgName} class="ui:h-8 ui:w-auto" />
|
||||
{/if}
|
||||
@@ -30,7 +33,7 @@
|
||||
{#each navItems as item}
|
||||
<a
|
||||
href={item.href}
|
||||
class="ui:text-sm ui:font-medium ui:text-background/70 ui:hover:text-background ui:transition-colors"
|
||||
class="ui:text-sm ui:font-medium ui:text-[var(--landing-bg)]/70 ui:hover:text-[var(--landing-bg)] ui:transition-colors ui:cursor-pointer"
|
||||
>{item.label}</a
|
||||
>
|
||||
{/each}
|
||||
@@ -39,7 +42,7 @@
|
||||
<Button
|
||||
href={authAction.href}
|
||||
variant="outline"
|
||||
class="ui:bg-transparent ui:text-background ui:border-background/40 ui:hover:bg-background/10 ui:hover:text-background ui:hover:border-background/60"
|
||||
class="ui:bg-transparent ui:text-[var(--landing-bg)] ui:border-[var(--landing-bg)]/40 ui:hover:bg-[var(--landing-bg)]/10 ui:hover:text-[var(--landing-bg)] ui:hover:border-[var(--landing-bg)]/60"
|
||||
>
|
||||
{authAction.label}
|
||||
</Button>
|
||||
|
||||
Reference in New Issue
Block a user