2023-01-03 09:26:08 +01:00
|
|
|
<script lang="ts">
|
2024-12-26 07:24:41 +01:00
|
|
|
import { goto } from '$app/navigation';
|
2025-11-21 12:18:04 +01:00
|
|
|
import { Badge } from '@cio/ui/base/badge';
|
|
|
|
|
import UserIcon from '@lucide/svelte/icons/user';
|
|
|
|
|
import * as DropdownMenu from '@cio/ui/base/dropdown-menu';
|
|
|
|
|
import CircleDotIcon from '@lucide/svelte/icons/circle-dot';
|
|
|
|
|
import TrendingUpIcon from '@lucide/svelte/icons/trending-up';
|
|
|
|
|
import EllipsisVerticalIcon from '@lucide/svelte/icons/ellipsis-vertical';
|
|
|
|
|
|
2023-09-28 11:45:50 +01:00
|
|
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
2025-11-21 12:18:04 +01:00
|
|
|
import ImageRenderer from '$lib/components/Org/ImageRenderer.svelte';
|
2023-09-28 11:45:50 +01:00
|
|
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
2025-11-21 12:18:04 +01:00
|
|
|
|
|
|
|
|
import { COURSE_TYPE } from '$lib/utils/types';
|
|
|
|
|
import { t } from '$lib/utils/functions/translations';
|
2024-12-26 07:24:41 +01:00
|
|
|
import { calcCourseDiscount } from '$lib/utils/functions/course';
|
|
|
|
|
import getCurrencyFormatter from '$lib/utils/functions/getCurrencyFormatter';
|
2025-12-02 09:54:38 +01:00
|
|
|
import { copyCourseModal, deleteCourseModal } from '$lib/features/course/utils/store';
|
2024-04-20 14:40:44 +01:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
interface Props {
|
|
|
|
|
bannerImage: string | undefined;
|
|
|
|
|
id?: string;
|
|
|
|
|
slug?: string;
|
|
|
|
|
title?: string;
|
|
|
|
|
description?: string;
|
|
|
|
|
isPublished?: boolean;
|
|
|
|
|
totalLessons?: number;
|
|
|
|
|
totalStudents?: number;
|
|
|
|
|
currency?: string;
|
|
|
|
|
isOnLandingPage?: boolean;
|
|
|
|
|
isLMS?: boolean;
|
|
|
|
|
isExplore?: boolean;
|
|
|
|
|
progressRate?: number;
|
|
|
|
|
type: COURSE_TYPE;
|
|
|
|
|
pricingData?: {
|
|
|
|
|
cost: number;
|
|
|
|
|
currency?: string;
|
|
|
|
|
showDiscount?: boolean;
|
|
|
|
|
discount?: number;
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let {
|
|
|
|
|
bannerImage,
|
|
|
|
|
id = '',
|
|
|
|
|
slug = '',
|
|
|
|
|
title = '',
|
|
|
|
|
description = '',
|
|
|
|
|
isPublished = false,
|
|
|
|
|
totalLessons = 0,
|
|
|
|
|
totalStudents = 0,
|
|
|
|
|
currency = 'USD',
|
|
|
|
|
isOnLandingPage = false,
|
|
|
|
|
isLMS = false,
|
|
|
|
|
isExplore = false,
|
|
|
|
|
progressRate = 45,
|
|
|
|
|
type,
|
|
|
|
|
pricingData = {
|
|
|
|
|
cost: 0
|
|
|
|
|
}
|
|
|
|
|
}: Props = $props();
|
|
|
|
|
|
|
|
|
|
let formatter = $derived(getCurrencyFormatter(currency));
|
2023-12-20 18:44:00 +01:00
|
|
|
|
|
|
|
|
function handleCloneCourse() {
|
2025-10-29 09:57:29 +01:00
|
|
|
$copyCourseModal.open = true;
|
|
|
|
|
$copyCourseModal.id = id;
|
|
|
|
|
$copyCourseModal.title = title;
|
|
|
|
|
$copyCourseModal.description = description;
|
2023-12-20 18:44:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleShareCourse() {
|
2024-12-23 23:20:52 +01:00
|
|
|
goto(`/courses/${id}/settings#share`);
|
2023-12-20 18:44:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleInvite() {
|
2024-12-23 23:20:52 +01:00
|
|
|
goto(`/courses/${id}/people?add=true`);
|
2023-12-20 18:44:00 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
function handleDeleteCourse() {
|
2025-10-29 09:57:29 +01:00
|
|
|
$deleteCourseModal.open = true;
|
|
|
|
|
$deleteCourseModal.id = id;
|
|
|
|
|
$deleteCourseModal.title = title;
|
2024-05-17 01:28:59 +01:00
|
|
|
}
|
2024-05-21 00:14:47 +01:00
|
|
|
|
|
|
|
|
const COURSE_TAG: Record<
|
|
|
|
|
string,
|
|
|
|
|
{
|
|
|
|
|
style: string;
|
|
|
|
|
label: string;
|
|
|
|
|
icon: any;
|
|
|
|
|
iconStyle?: string;
|
|
|
|
|
}
|
|
|
|
|
> = {
|
|
|
|
|
[COURSE_TYPE.LIVE_CLASS]: {
|
|
|
|
|
style: '',
|
|
|
|
|
label: $t('course.navItem.settings.live_class'),
|
2025-11-01 15:49:29 +01:00
|
|
|
icon: CircleDotIcon,
|
|
|
|
|
iconStyle: 'custom text-red-700'
|
2024-05-21 00:14:47 +01:00
|
|
|
},
|
|
|
|
|
[COURSE_TYPE.SELF_PACED]: {
|
|
|
|
|
style: '',
|
|
|
|
|
label: $t('course.navItem.settings.self_paced'),
|
2025-11-01 15:49:29 +01:00
|
|
|
icon: UserIcon,
|
|
|
|
|
iconStyle: 'custom text-primary-700'
|
2024-05-21 00:14:47 +01:00
|
|
|
},
|
|
|
|
|
SPECIALIZATION: {
|
|
|
|
|
style: '',
|
|
|
|
|
label: $t('specialization.course_tag'),
|
2025-11-01 15:49:29 +01:00
|
|
|
icon: TrendingUpIcon
|
2024-05-21 00:14:47 +01:00
|
|
|
}
|
|
|
|
|
};
|
2024-09-09 01:14:55 +01:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let cost = $derived(calcCourseDiscount(pricingData.discount, pricingData.cost ?? 0, !!pricingData.showDiscount));
|
2024-12-23 23:20:52 +01:00
|
|
|
|
2025-11-01 15:49:29 +01:00
|
|
|
let courseUrl = $derived(
|
|
|
|
|
isOnLandingPage || isExplore ? `/course/${slug}` : `/courses/${id}${isLMS ? '/lessons?next=true' : ''}`
|
|
|
|
|
);
|
2021-08-29 07:23:19 +03:00
|
|
|
</script>
|
|
|
|
|
|
2024-12-23 23:20:52 +01:00
|
|
|
<div
|
|
|
|
|
role="button"
|
|
|
|
|
tabindex="0"
|
2025-11-01 15:49:29 +01:00
|
|
|
onclick={() => {
|
2024-12-23 23:20:52 +01:00
|
|
|
goto(courseUrl);
|
|
|
|
|
}}
|
2025-11-01 15:49:29 +01:00
|
|
|
onkeydown={(e) => {
|
2024-12-23 23:20:52 +01:00
|
|
|
if (e.key === 'Enter') {
|
|
|
|
|
goto(courseUrl);
|
|
|
|
|
}
|
|
|
|
|
}}
|
|
|
|
|
class="border-gray group relative w-full max-w-[320px] rounded border text-black dark:border-neutral-600"
|
2023-09-13 20:22:15 +01:00
|
|
|
>
|
2023-09-27 04:23:45 +01:00
|
|
|
<div class="p-4">
|
2024-05-21 00:14:47 +01:00
|
|
|
<div class="relative mb-5">
|
2024-12-23 23:20:52 +01:00
|
|
|
{#if !isLMS && !isOnLandingPage}
|
2025-11-21 12:18:04 +01:00
|
|
|
<DropdownMenu.Root>
|
|
|
|
|
<DropdownMenu.Trigger
|
|
|
|
|
class="absolute right-2 top-2 z-40 flex items-center justify-center rounded-full bg-gray-200 p-2 opacity-0 transition-all delay-150 duration-200 ease-in-out group-hover:opacity-100 dark:bg-neutral-800"
|
|
|
|
|
onclick={(e) => e.stopPropagation()}
|
|
|
|
|
>
|
|
|
|
|
<EllipsisVerticalIcon size={16} />
|
|
|
|
|
</DropdownMenu.Trigger>
|
|
|
|
|
<DropdownMenu.Content align="end">
|
|
|
|
|
<DropdownMenu.Item onclick={handleCloneCourse}>
|
|
|
|
|
{$t('courses.course_card.context_menu.clone')}
|
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
|
<DropdownMenu.Item onclick={handleShareCourse}>
|
|
|
|
|
{$t('courses.course_card.context_menu.share')}
|
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
|
<DropdownMenu.Item onclick={handleInvite}>
|
|
|
|
|
{$t('courses.course_card.context_menu.invite')}
|
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
|
<DropdownMenu.Separator />
|
|
|
|
|
<DropdownMenu.Item class="text-red-600" onclick={handleDeleteCourse}>
|
|
|
|
|
{$t('courses.course_card.context_menu.delete')}
|
|
|
|
|
</DropdownMenu.Item>
|
|
|
|
|
</DropdownMenu.Content>
|
|
|
|
|
</DropdownMenu.Root>
|
2024-12-23 23:20:52 +01:00
|
|
|
{/if}
|
|
|
|
|
|
2025-11-21 12:18:04 +01:00
|
|
|
<ImageRenderer
|
2023-09-27 04:23:45 +01:00
|
|
|
src={bannerImage}
|
2025-11-21 12:18:04 +01:00
|
|
|
alt="Banner Image"
|
|
|
|
|
className="relative h-[200px] w-full rounded dark:border dark:border-neutral-600"
|
|
|
|
|
/>
|
|
|
|
|
|
2024-05-21 00:14:47 +01:00
|
|
|
{#if type}
|
|
|
|
|
{@const tag = COURSE_TAG[type]}
|
|
|
|
|
<span
|
2024-12-23 23:20:52 +01:00
|
|
|
class="bg-primary-50 absolute bottom-2 left-2 z-10 flex items-center gap-1 rounded-sm p-1 font-mono text-xs capitalize"
|
2024-05-21 00:14:47 +01:00
|
|
|
>
|
2025-11-01 15:49:29 +01:00
|
|
|
<tag.icon class={tag.iconStyle} />
|
2024-05-21 00:14:47 +01:00
|
|
|
{tag.label}
|
|
|
|
|
</span>
|
|
|
|
|
{/if}
|
2023-09-13 06:38:12 +01:00
|
|
|
</div>
|
2023-05-15 15:13:42 +00:00
|
|
|
|
2024-12-23 23:20:52 +01:00
|
|
|
<h3 class="title text-xl dark:text-white">{title}</h3>
|
|
|
|
|
<p class="description mt-2 text-sm text-gray-500 dark:text-gray-300">
|
2023-09-27 04:23:45 +01:00
|
|
|
{description}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
2023-09-28 11:45:50 +01:00
|
|
|
<div
|
2024-12-23 23:20:52 +01:00
|
|
|
class="border-gray flex justify-between border border-b-0 border-l-0 border-r-0 px-4 py-2 dark:border-neutral-600 {isLMS &&
|
2023-12-07 09:37:12 +03:00
|
|
|
'items-center'}"
|
2023-09-28 11:45:50 +01:00
|
|
|
>
|
|
|
|
|
<div>
|
2024-07-09 21:17:34 +01:00
|
|
|
<p class="text-xs {!isLMS && 'pl-2'} dark:text-white">
|
2024-04-19 05:45:48 +01:00
|
|
|
{totalLessons}
|
2024-04-20 15:05:45 +01:00
|
|
|
{$t('courses.course_card.lessons_number')}
|
2024-04-19 05:45:48 +01:00
|
|
|
</p>
|
2024-12-23 23:20:52 +01:00
|
|
|
<p class="py-2 text-xs">
|
2023-09-28 11:45:50 +01:00
|
|
|
{#if isOnLandingPage}
|
2024-09-09 01:14:55 +01:00
|
|
|
<span class="px-2">
|
|
|
|
|
{#if !cost}
|
2024-09-12 08:14:28 +01:00
|
|
|
{$t('course.navItem.landing_page.pricing_section.free')}
|
2024-09-09 01:14:55 +01:00
|
|
|
{:else if pricingData.showDiscount}
|
|
|
|
|
{formatter.format(cost)}
|
|
|
|
|
<span class="line-through">
|
|
|
|
|
{formatter?.format(pricingData?.cost)}
|
|
|
|
|
</span>
|
|
|
|
|
{:else}
|
|
|
|
|
{formatter.format(cost)}
|
|
|
|
|
{/if}
|
|
|
|
|
</span>
|
2023-09-28 11:45:50 +01:00
|
|
|
{:else if isLMS}
|
2024-07-09 21:17:34 +01:00
|
|
|
{#if !isExplore}
|
|
|
|
|
<div class="flex items-center gap-2">
|
2024-12-23 23:20:52 +01:00
|
|
|
<div class=" relative h-1 w-[50px] bg-[#EAEAEA]">
|
2025-11-01 15:49:29 +01:00
|
|
|
<div style="width:{progressRate}%" class="bg-primary-700 absolute left-0 top-0 h-full"></div>
|
2024-07-09 21:17:34 +01:00
|
|
|
</div>
|
|
|
|
|
<p class="text-xs text-[#656565] dark:text-white">{progressRate}%</p>
|
2023-09-28 11:45:50 +01:00
|
|
|
</div>
|
2024-07-09 21:17:34 +01:00
|
|
|
{/if}
|
2023-09-28 11:45:50 +01:00
|
|
|
{:else}
|
2025-11-21 12:18:04 +01:00
|
|
|
<Badge variant={isPublished ? 'default' : 'secondary'}>
|
2023-09-28 11:45:50 +01:00
|
|
|
{#if isPublished}
|
2024-04-20 15:05:45 +01:00
|
|
|
{$t('courses.course_card.published')}
|
2023-09-28 11:45:50 +01:00
|
|
|
{:else}
|
2024-04-20 15:05:45 +01:00
|
|
|
{$t('courses.course_card.unpublished')}
|
2023-09-28 11:45:50 +01:00
|
|
|
{/if}
|
2025-11-21 12:18:04 +01:00
|
|
|
</Badge>
|
2023-09-28 11:45:50 +01:00
|
|
|
{/if}
|
|
|
|
|
</p>
|
|
|
|
|
</div>
|
|
|
|
|
|
|
|
|
|
{#if isLMS}
|
|
|
|
|
<PrimaryButton
|
2025-11-01 15:49:29 +01:00
|
|
|
label={isExplore ? $t('courses.course_card.learn_more') : $t('courses.course_card.continue_course')}
|
2023-09-28 11:45:50 +01:00
|
|
|
variant={VARIANTS.OUTLINED}
|
2024-04-19 06:26:13 +01:00
|
|
|
className="rounded-none"
|
2023-09-28 11:45:50 +01:00
|
|
|
/>
|
2023-12-07 09:37:12 +03:00
|
|
|
{:else if !isOnLandingPage}
|
|
|
|
|
<div class="flex flex-col justify-between">
|
2024-12-23 23:20:52 +01:00
|
|
|
<p class="pl-2 text-xs dark:text-white">
|
2024-04-19 05:45:48 +01:00
|
|
|
{totalStudents}
|
2024-04-20 15:05:45 +01:00
|
|
|
{$t('courses.course_card.students')}
|
2024-04-19 05:45:48 +01:00
|
|
|
</p>
|
2023-12-07 09:37:12 +03:00
|
|
|
<div></div>
|
|
|
|
|
</div>
|
2023-09-28 11:45:50 +01:00
|
|
|
{/if}
|
2023-01-03 09:26:08 +01:00
|
|
|
</div>
|
2024-12-23 23:20:52 +01:00
|
|
|
</div>
|
2021-08-29 07:23:19 +03:00
|
|
|
|
|
|
|
|
<style>
|
2023-01-03 09:26:08 +01:00
|
|
|
.title,
|
2021-08-29 07:23:19 +03:00
|
|
|
.description {
|
2023-09-28 11:45:50 +01:00
|
|
|
height: 42px;
|
2021-08-29 07:23:19 +03:00
|
|
|
line-height: 20px;
|
|
|
|
|
display: -webkit-box;
|
|
|
|
|
-webkit-box-orient: vertical;
|
|
|
|
|
-moz-box-orient: vertical;
|
|
|
|
|
-ms-box-orient: vertical;
|
|
|
|
|
box-orient: vertical;
|
2023-01-03 09:26:08 +01:00
|
|
|
-webkit-line-clamp: 2;
|
|
|
|
|
-moz-line-clamp: 2;
|
|
|
|
|
-ms-line-clamp: 2;
|
|
|
|
|
line-clamp: 2;
|
2021-08-29 07:23:19 +03:00
|
|
|
overflow: hidden;
|
|
|
|
|
}
|
|
|
|
|
</style>
|