CLASSROOMIO-T-152: Landing page fully ready
This commit is contained in:
@@ -154,7 +154,7 @@
|
||||
$menu.hidden
|
||||
? '-translate-x-[100%] absolute md:translate-x-0 md:relative z-[40]'
|
||||
: 'translate-x-0 absolute md:relative z-[40]'
|
||||
} transition w-[250px] min-w-[250px] bg-gray-100 dark:bg-gray-800 h-[calc(100vh-48px)] border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
} transition w-[350px] min-w-[350px] md:max-w-[350px] bg-gray-100 dark:bg-gray-800 h-[calc(100vh-48px)] overflow-y-auto border border-l-0 border-t-0 border-b-0 border-r-1`}
|
||||
>
|
||||
<div class="h-full flex flex-col">
|
||||
<ul class="my-5">
|
||||
|
||||
@@ -0,0 +1,111 @@
|
||||
<script lang="ts">
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import Modal from '$lib/components/Modal/index.svelte';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { coursePaymentValidation } from '$lib/utils/functions/validator';
|
||||
import { Link } from 'carbon-components-svelte';
|
||||
import { sendTeacherStudentJoinRequest, sendStudentRequestReceipt } from './utils';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
|
||||
export let open = false;
|
||||
export let paymentLink = '';
|
||||
export let teacherEmail = '';
|
||||
export let courseName = '';
|
||||
|
||||
let fields = {
|
||||
fullname: '',
|
||||
email: ''
|
||||
};
|
||||
|
||||
let errors = {
|
||||
fullname: '',
|
||||
email: ''
|
||||
};
|
||||
|
||||
const STEPS = {
|
||||
STEP_1: 'ENTER_DETAILS',
|
||||
STEP_2: 'GO_TO_PAYMENT'
|
||||
};
|
||||
|
||||
let step = STEPS.STEP_1;
|
||||
|
||||
function onSubmit() {
|
||||
errors = coursePaymentValidation(fields);
|
||||
console.log('coursePayment errors', errors);
|
||||
|
||||
if (Object.keys(errors).length) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (paymentLink) {
|
||||
step = STEPS.STEP_2;
|
||||
|
||||
// Send email to first added teacher of student wanting to join
|
||||
sendTeacherStudentJoinRequest({
|
||||
to: teacherEmail,
|
||||
courseName,
|
||||
studentEmail: fields.email,
|
||||
studentFullname: fields.fullname
|
||||
});
|
||||
} else {
|
||||
open = false;
|
||||
}
|
||||
}
|
||||
|
||||
function onClickPaymentLink() {
|
||||
// Send email to student to send proof of payment to teacher
|
||||
sendStudentRequestReceipt({
|
||||
to: fields.email,
|
||||
courseName,
|
||||
teacherEmail,
|
||||
studentFullname: fields.fullname,
|
||||
orgName: $currentOrg.name
|
||||
});
|
||||
|
||||
fields.fullname = '';
|
||||
fields.email = '';
|
||||
open = false;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal onClose={() => (open = false)} bind:open width="w-96" modalHeading="Process course payment">
|
||||
{#if step === STEPS.STEP_1}
|
||||
<form on:submit|preventDefault={onSubmit}>
|
||||
<TextField
|
||||
label="Your Fullname"
|
||||
bind:value={fields.fullname}
|
||||
autoFocus={true}
|
||||
placeholder="John Doe"
|
||||
className="mb-4"
|
||||
isRequired={true}
|
||||
autoComplete={false}
|
||||
errorMessage={errors.fullname}
|
||||
/>
|
||||
<TextField
|
||||
label="Your Fullname"
|
||||
bind:value={fields.email}
|
||||
placeholder="johndoe@email.com"
|
||||
className="mb-4"
|
||||
isRequired={true}
|
||||
autoComplete={false}
|
||||
errorMessage={errors.email}
|
||||
/>
|
||||
|
||||
<div class="mt-5 flex items-center flex-row-reverse">
|
||||
<PrimaryButton
|
||||
className="px-6 py-3"
|
||||
label={paymentLink ? 'Next' : 'Finish'}
|
||||
type="submit"
|
||||
/>
|
||||
</div>
|
||||
</form>
|
||||
{:else if step === STEPS.STEP_2}
|
||||
<p>
|
||||
You will now be taken to a payment page, once you've paid send a proof of payment to the
|
||||
course admin
|
||||
</p>
|
||||
<div class="mt-5 flex items-center flex-row-reverse">
|
||||
<Link href={paymentLink} target="_blank" on:click={onClickPaymentLink}>Go to payment</Link>
|
||||
</div>
|
||||
{/if}
|
||||
</Modal>
|
||||
@@ -1,17 +1,19 @@
|
||||
<script lang="ts">
|
||||
import get from 'lodash/get';
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import getCurrencyFormatter from '$lib/utils/functions/getCurrencyFormatter';
|
||||
import { isCourseFree } from '$lib/utils/functions/course';
|
||||
import get from 'lodash/get';
|
||||
import { getStudentInviteLink } from '$lib/utils/functions/course';
|
||||
import { currentOrg } from '$lib/utils/store/org';
|
||||
import { goto } from '$app/navigation';
|
||||
import PaymentModal from './PaymentModal.svelte';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { ROLE } from '$lib/utils/constants/roles';
|
||||
|
||||
export let className = '';
|
||||
export let editMode = false;
|
||||
export let courseData: Course = {
|
||||
id: '',
|
||||
title: '',
|
||||
description: ''
|
||||
};
|
||||
export let courseData: Course;
|
||||
export let startCoursePayment = false;
|
||||
|
||||
let calculatedCost = 0;
|
||||
let discount = 0;
|
||||
@@ -29,27 +31,39 @@
|
||||
if (editMode) return;
|
||||
|
||||
if (isFree) {
|
||||
// get invite link
|
||||
// redirect to link
|
||||
const link = getStudentInviteLink(courseData, $currentOrg.siteName);
|
||||
goto(link);
|
||||
} else {
|
||||
startCoursePayment = true;
|
||||
}
|
||||
}
|
||||
|
||||
function buyNow() {}
|
||||
|
||||
function setFormatter(currency: string | undefined) {
|
||||
if (!currency) return;
|
||||
formatter = getCurrencyFormatter(currency);
|
||||
}
|
||||
|
||||
function getTeacherEmail(group: Course['group']) {
|
||||
const firstTutor = group?.members?.find((m) => m.role_id === ROLE.TUTOR);
|
||||
|
||||
return firstTutor?.profile?.email || '';
|
||||
}
|
||||
|
||||
$: setFormatter(courseData.currency);
|
||||
$: discount = get(courseData, 'metadata.discount', 0);
|
||||
$: calculatedCost = calcDisc(discount, courseData.cost || 0);
|
||||
$: isFree = isCourseFree(calculatedCost);
|
||||
</script>
|
||||
|
||||
<PaymentModal
|
||||
bind:open={startCoursePayment}
|
||||
paymentLink={get(courseData, 'metadata.paymentLink', '')}
|
||||
courseName={courseData.title}
|
||||
teacherEmail={getTeacherEmail(courseData.group)}
|
||||
/>
|
||||
|
||||
<!-- Pricing Details -->
|
||||
<aside class="{className} price-container lg:sticky lg:top-0 lg:shadow-2xl lg:rounded-lg m-h-fit">
|
||||
<aside class="{className} price-container lg:sticky lg:top-10 lg:shadow-2xl lg:rounded-lg m-h-fit">
|
||||
<div class="p-10">
|
||||
<!-- Pricing -->
|
||||
<div class="mb-6">
|
||||
@@ -72,7 +86,7 @@
|
||||
<!-- Call To Action Buttons -->
|
||||
<div class="flex flex-col w-full items-center">
|
||||
<PrimaryButton
|
||||
label="Join Course"
|
||||
label={isFree ? 'Join Course' : 'Buy Now'}
|
||||
className="w-full sm:w-full py-3 mb-3"
|
||||
onClick={handleJoinCourse}
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
import { supabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
export async function sendTeacherStudentJoinRequest(params: {
|
||||
to: string;
|
||||
courseName: string;
|
||||
studentEmail: string;
|
||||
studentFullname: string;
|
||||
}) {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const accessToken = data.session?.access_token || '';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/email/course/teacher_student_buycourse', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: accessToken
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
const result = await response.json();
|
||||
|
||||
console.log('Sent invite email', result);
|
||||
} catch (error) {
|
||||
console.error('Error sending invite email', error);
|
||||
}
|
||||
}
|
||||
|
||||
export async function sendStudentRequestReceipt(params: {
|
||||
to: string;
|
||||
courseName: string;
|
||||
orgName: string;
|
||||
studentFullname: string;
|
||||
teacherEmail: string;
|
||||
}) {
|
||||
const { data } = await supabase.auth.getSession();
|
||||
const accessToken = data.session?.access_token || '';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/email/course/student_prove_payment', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: accessToken
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(await response.text());
|
||||
}
|
||||
const result = await response.json();
|
||||
|
||||
console.log('Sent invite email', result);
|
||||
} catch (error) {
|
||||
console.error('Error sending invite email', error);
|
||||
}
|
||||
}
|
||||
@@ -26,11 +26,7 @@
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export let editMode = false;
|
||||
export let courseData: Course = {
|
||||
id: '',
|
||||
title: '',
|
||||
description: ''
|
||||
};
|
||||
export let courseData: Course;
|
||||
const ratingsImg = [
|
||||
'/images/rating-1.svg',
|
||||
'/images/rating-2.svg',
|
||||
@@ -44,6 +40,7 @@
|
||||
let player: any;
|
||||
let averageRating = 0;
|
||||
let totalRatings = 0;
|
||||
let startCoursePayment = false;
|
||||
|
||||
// initialize the expandDescription array with 'false' values for each review.
|
||||
let expandDescription = Array(reviews.length).fill(false);
|
||||
@@ -62,16 +59,6 @@
|
||||
activeNav = window.location.hash;
|
||||
}
|
||||
|
||||
function addToCart() {}
|
||||
|
||||
function buyNow() {
|
||||
// if (courseData.cost) {
|
||||
// paystackApi.tra;
|
||||
// } else {
|
||||
// // Course is free
|
||||
// }
|
||||
}
|
||||
|
||||
function initPlyr(_player: any, _video: string | undefined) {
|
||||
if (!player) return;
|
||||
|
||||
@@ -120,7 +107,13 @@
|
||||
<p class="dark:text-white author my-3 text-sm">
|
||||
{get(courseData, 'metadata.instructor.name', '')}
|
||||
</p>
|
||||
<PrimaryButton label="Start Course" className="px-6 py-5 mt-6 sm:w-fit" />
|
||||
<PrimaryButton
|
||||
label="Start Course"
|
||||
className="px-6 py-5 mt-6 sm:w-fit"
|
||||
onClick={() => {
|
||||
startCoursePayment = true;
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<!-- Banner Image getEmbedId(videoUrl) -->
|
||||
@@ -165,16 +158,16 @@
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
<PricingSection className="md:hidden" {courseData} {editMode} />
|
||||
<PricingSection className="md:hidden" {courseData} {editMode} bind:startCoursePayment />
|
||||
<!-- Body -->
|
||||
<div class="bg-white dark:bg-gray-800 w-full">
|
||||
<div
|
||||
class="py-8 lg:w-11/12 w-full m-2 lg:m-auto flex flex-col-reverse lg:flex-row items-center lg:items-start justify-between"
|
||||
class="py-8 lg:w-11/12 w-full m-2 lg:m-auto flex flex-col-reverse lg:flex-row items-center lg:items-start justify-between max-w-[1200px]"
|
||||
>
|
||||
<!-- Course Details -->
|
||||
<div class="course-content w-full p-3 lg:w-10/12 lg:mr-14">
|
||||
<!-- Navigation -->
|
||||
<nav class="flex items-center border-b border-gray-300 pb-3">
|
||||
<nav class="flex items-center border-b border-gray-300 py-3 sticky top-11 bg-white">
|
||||
{#each NAV_ITEMS as navItem}
|
||||
<a
|
||||
href="{$page.url.pathname}{navItem.key}"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
export let logo;
|
||||
export let orgName;
|
||||
export let isOrgSite = false;
|
||||
export let backgroundColor = 'bg-blue';
|
||||
export let backgroundColor = 'bg-white';
|
||||
|
||||
let navClass = '';
|
||||
|
||||
|
||||
Reference in New Issue
Block a user