CLASSROOMIO-T-152: Add payment link + reload on org login
This commit is contained in:
@@ -15,6 +15,7 @@
|
||||
import { sendTeacherCourseWelcome } from './utils';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
import { getStudentInviteLink } from '$lib/utils/functions/course';
|
||||
|
||||
interface Tutor {
|
||||
id: number;
|
||||
@@ -109,21 +110,6 @@
|
||||
isLoadingTutors = false;
|
||||
}
|
||||
|
||||
function getInviteLink(_course: Course, siteName: string) {
|
||||
const hash = encodeURIComponent(
|
||||
btoa(
|
||||
JSON.stringify({
|
||||
groupId: _course.group?.id,
|
||||
name: _course.title,
|
||||
description: _course.description,
|
||||
orgSiteName: siteName
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return `https://${siteName}.classroomio.com/invite/s/${hash}`;
|
||||
}
|
||||
|
||||
function copyLink() {
|
||||
copy(link);
|
||||
copied = true;
|
||||
@@ -139,7 +125,7 @@
|
||||
|
||||
$: setTutors($currentOrg.id, $invitationModal.open);
|
||||
|
||||
$: link = getInviteLink($course, $currentOrg.siteName);
|
||||
$: link = getStudentInviteLink($course, $currentOrg.siteName);
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
|
||||
@@ -21,6 +21,7 @@ export const course: Writable<Course> = writable({
|
||||
videoUrl: '',
|
||||
showDiscount: false,
|
||||
discount: 0,
|
||||
paymentLink: '',
|
||||
reward: {
|
||||
show: false,
|
||||
description: ''
|
||||
|
||||
@@ -1,32 +1,36 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import cloneDeep from 'lodash/cloneDeep';
|
||||
import set from 'lodash/set';
|
||||
import get from 'lodash/get';
|
||||
import isEmpty from 'lodash/isEmpty';
|
||||
import { Toggle } from 'carbon-components-svelte';
|
||||
import { Toggle, Select, SelectItem } from 'carbon-components-svelte';
|
||||
|
||||
import TextField from '$lib/components/Form/TextField.svelte';
|
||||
import TextEditor from '$lib/components/TextEditor/index.svelte';
|
||||
import { isCourseFree } from '$lib/utils/functions/course';
|
||||
import type { Course } from '$lib/utils/types';
|
||||
|
||||
export let course = {};
|
||||
export let course: Course;
|
||||
|
||||
let discount = 0;
|
||||
let showDiscount;
|
||||
let giftToggled;
|
||||
let paymentLink = '';
|
||||
let showDiscount = false;
|
||||
let giftToggled = false;
|
||||
|
||||
let hasBeenSet = false;
|
||||
|
||||
function setDefaults(course) {
|
||||
function setDefaults(course: Course) {
|
||||
console.log('\n\nsetDefaults course', course);
|
||||
if (isEmpty(course) || hasBeenSet) return;
|
||||
hasBeenSet = true;
|
||||
|
||||
paymentLink = get(course, 'metadata.paymentLink', '');
|
||||
discount = get(course, 'metadata.discount', 0);
|
||||
showDiscount = get(course, 'metadata.showDiscount', false);
|
||||
giftToggled = get(course, 'metadata.reward.show', false);
|
||||
}
|
||||
|
||||
function setter(value, setterKey) {
|
||||
console.log('\n\nsetter course', value);
|
||||
function setter(value: string | number | boolean, setterKey: string) {
|
||||
if (typeof value !== 'boolean' && !value) return;
|
||||
|
||||
const _course = cloneDeep(course);
|
||||
@@ -34,66 +38,76 @@
|
||||
course = _course;
|
||||
}
|
||||
|
||||
function handleChange(html) {
|
||||
function handleChange(html: string) {
|
||||
const _course = cloneDeep(course);
|
||||
set(_course, 'metadata.reward.description', html);
|
||||
course = _course;
|
||||
}
|
||||
|
||||
$: setter(showDiscount, 'metadata.showDiscount');
|
||||
$: setter(paymentLink, 'metadata.paymentLink');
|
||||
$: setter(discount, 'metadata.discount');
|
||||
$: setter(giftToggled, 'metadata.reward.show');
|
||||
|
||||
$: setDefaults(course);
|
||||
</script>
|
||||
|
||||
<TextField
|
||||
className="mt-5"
|
||||
labelClassName="font-bold"
|
||||
label="Currency"
|
||||
value={course.currency}
|
||||
isDisabled={true}
|
||||
/>
|
||||
{#if typeof course !== 'undefined'}
|
||||
<Select labelText="Currency" bind:selected={course.currency}>
|
||||
<SelectItem value="NGN" />
|
||||
<SelectItem value="USD" />
|
||||
</Select>
|
||||
|
||||
<TextField
|
||||
className="mt-5"
|
||||
labelClassName="font-bold"
|
||||
label="Cost"
|
||||
type="number"
|
||||
bind:value={course.cost}
|
||||
/>
|
||||
|
||||
<div class="mt-5">
|
||||
<!-- <label for="text-field" class="m-0 font-bold mb-2">Discount</label> -->
|
||||
<Toggle labelText="Discount" bind:toggled={showDiscount}>
|
||||
<span slot="labelA">No</span>
|
||||
<span slot="labelB">Yes</span>
|
||||
</Toggle>
|
||||
</div>
|
||||
|
||||
{#if showDiscount}
|
||||
<TextField
|
||||
className="mt-5"
|
||||
labelClassName="font-bold"
|
||||
label="Discout Percent"
|
||||
label="Cost"
|
||||
type="number"
|
||||
bind:value={discount}
|
||||
helperMessage="In Percentage %"
|
||||
bind:value={course.cost}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="mt-5">
|
||||
<!-- <label for="text-field" class="m-0 font-bold mb-2">Discount</label> -->
|
||||
<Toggle labelText="Gift on Completion" bind:toggled={giftToggled}>
|
||||
<span slot="labelA">No</span>
|
||||
<span slot="labelB">Yes</span>
|
||||
</Toggle>
|
||||
</div>
|
||||
{#if !isCourseFree(course.cost || 0)}
|
||||
<TextField
|
||||
className="mt-5"
|
||||
labelClassName="font-bold"
|
||||
label="Payment Link"
|
||||
helperMessage="Stripe, Lemon Squeezy or any payment link"
|
||||
bind:value={paymentLink}
|
||||
/>
|
||||
{/if}
|
||||
|
||||
{#if giftToggled}
|
||||
<p class="dark:text-white font-bold mt-5">About Gift</p>
|
||||
|
||||
<div class="h-2/5">
|
||||
<TextEditor value={get(course, 'metadata.reward.description')} onChange={handleChange} />
|
||||
<div class="mt-5">
|
||||
<!-- <label for="text-field" class="m-0 font-bold mb-2">Discount</label> -->
|
||||
<Toggle labelText="Discount" bind:toggled={showDiscount}>
|
||||
<span slot="labelA">No</span>
|
||||
<span slot="labelB">Yes</span>
|
||||
</Toggle>
|
||||
</div>
|
||||
|
||||
{#if showDiscount}
|
||||
<TextField
|
||||
className="mt-5"
|
||||
labelClassName="font-bold"
|
||||
label="Discout Percent"
|
||||
type="number"
|
||||
bind:value={discount}
|
||||
helperMessage="In Percentage %"
|
||||
/>
|
||||
{/if}
|
||||
|
||||
<div class="mt-5">
|
||||
<!-- <label for="text-field" class="m-0 font-bold mb-2">Discount</label> -->
|
||||
<Toggle labelText="Gift on Completion" bind:toggled={giftToggled}>
|
||||
<span slot="labelA">No</span>
|
||||
<span slot="labelB">Yes</span>
|
||||
</Toggle>
|
||||
</div>
|
||||
|
||||
{#if giftToggled}
|
||||
<p class="dark:text-white font-bold mt-5">About Gift</p>
|
||||
|
||||
<div class="h-2/5">
|
||||
<TextEditor value={get(course, 'metadata.reward.description', '')} onChange={handleChange} />
|
||||
</div>
|
||||
{/if}
|
||||
{/if}
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
<script lang="ts">
|
||||
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
||||
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
||||
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';
|
||||
|
||||
export let className = '';
|
||||
export let editMode = false;
|
||||
export let courseData: Course = {
|
||||
id: '',
|
||||
title: '',
|
||||
@@ -15,6 +16,7 @@
|
||||
let calculatedCost = 0;
|
||||
let discount = 0;
|
||||
let formatter: Intl.NumberFormat | undefined;
|
||||
let isFree = false;
|
||||
|
||||
function calcDisc(percent: number, cost: number) {
|
||||
if (!percent) return cost;
|
||||
@@ -23,7 +25,15 @@
|
||||
return Math.round(discountedPrice);
|
||||
}
|
||||
|
||||
function addToCart() {}
|
||||
function handleJoinCourse() {
|
||||
if (editMode) return;
|
||||
|
||||
if (isFree) {
|
||||
// get invite link
|
||||
// redirect to link
|
||||
} else {
|
||||
}
|
||||
}
|
||||
|
||||
function buyNow() {}
|
||||
|
||||
@@ -32,9 +42,11 @@
|
||||
formatter = getCurrencyFormatter(currency);
|
||||
}
|
||||
|
||||
$: console.log('courseData.currency', courseData.currency);
|
||||
$: setFormatter(courseData.currency);
|
||||
$: discount = get(courseData, 'metadata.discount', 0);
|
||||
$: calculatedCost = calcDisc(discount, courseData.cost || 0);
|
||||
$: isFree = isCourseFree(calculatedCost);
|
||||
</script>
|
||||
|
||||
<!-- Pricing Details -->
|
||||
@@ -44,6 +56,9 @@
|
||||
<div class="mb-6">
|
||||
<p class="dark:text-white font-medium text-lg">
|
||||
{formatter?.format(calculatedCost) || calculatedCost}
|
||||
{#if isFree}
|
||||
<span class="text-sm">(Free)</span>
|
||||
{/if}
|
||||
</p>
|
||||
{#if courseData?.metadata?.showDiscount}
|
||||
<p class="dark:text-white font-light text-sm text-gray-500">
|
||||
@@ -58,15 +73,9 @@
|
||||
<!-- Call To Action Buttons -->
|
||||
<div class="flex flex-col w-full items-center">
|
||||
<PrimaryButton
|
||||
label="Add to Cart"
|
||||
label="Join Course"
|
||||
className="w-full sm:w-full py-3 mb-3"
|
||||
onClick={addToCart}
|
||||
/>
|
||||
<PrimaryButton
|
||||
label="Buy Now"
|
||||
className="w-full sm:w-full py-3 mb-3"
|
||||
variant={VARIANTS.OUTLINED}
|
||||
onClick={addToCart}
|
||||
onClick={handleJoinCourse}
|
||||
/>
|
||||
<p class="dark:text-white font-light text-sm text-gray-500">Early bird offer. Buy ASAP</p>
|
||||
</div>
|
||||
|
||||
@@ -25,6 +25,7 @@
|
||||
|
||||
dayjs.extend(relativeTime);
|
||||
|
||||
export let editMode = false;
|
||||
export let courseData: Course = {
|
||||
id: '',
|
||||
title: '',
|
||||
@@ -164,7 +165,11 @@
|
||||
{/if}
|
||||
</div>
|
||||
</header>
|
||||
<<<<<<< Updated upstream
|
||||
<!-- <PricingSection className="md:hidden" {courseData} /> -->
|
||||
=======
|
||||
<PricingSection className="md:hidden" {courseData} {editMode} />
|
||||
>>>>>>> Stashed changes
|
||||
<!-- Body -->
|
||||
<div class="bg-white dark:bg-gray-800 w-full">
|
||||
<div
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
<script>
|
||||
<script lang="ts">
|
||||
import { onMount } from 'svelte';
|
||||
import IconButton from '$lib/components/IconButton/index.svelte';
|
||||
import PasswordEye from '../Icons/PasswordEye.svelte';
|
||||
|
||||
export let label = '';
|
||||
export let placeholder = '';
|
||||
export let value = '';
|
||||
export let value: string | number;
|
||||
export let name = '';
|
||||
export let onKeyDown = () => {};
|
||||
export let className = '';
|
||||
|
||||
@@ -4,10 +4,10 @@
|
||||
|
||||
export let id = '';
|
||||
export let value: string;
|
||||
export let onChange = (html = '') => {};
|
||||
export let onChange = (html: string) => {};
|
||||
export let height = 300;
|
||||
export let placeholder = '';
|
||||
export let editorWindowRef;
|
||||
export let editorWindowRef = null;
|
||||
|
||||
const apiKey = PUBLIC_TINYMCE_API_KEY;
|
||||
|
||||
|
||||
@@ -0,0 +1,18 @@
|
||||
import type { Course } from '../types';
|
||||
|
||||
export const isCourseFree = (cost: number) => !(Number(cost) > 0);
|
||||
|
||||
export const getStudentInviteLink = (_course: Course, orgSiteName: string) => {
|
||||
const hash = encodeURIComponent(
|
||||
btoa(
|
||||
JSON.stringify({
|
||||
groupId: _course.group?.id,
|
||||
name: _course.title,
|
||||
description: _course.description,
|
||||
orgSiteName
|
||||
})
|
||||
)
|
||||
);
|
||||
|
||||
return `https://${orgSiteName}.classroomio.com/invite/s/${hash}`;
|
||||
};
|
||||
@@ -1,7 +1,8 @@
|
||||
export default (currency: string | undefined) => {
|
||||
return new Intl.NumberFormat('en-US', {
|
||||
const locale = currency == 'NGN' ? 'en-NG' : 'en-US';
|
||||
return new Intl.NumberFormat(locale, {
|
||||
style: 'currency',
|
||||
currency: currency,
|
||||
minimumFractionDigits: 2,
|
||||
minimumFractionDigits: 2
|
||||
});
|
||||
};
|
||||
|
||||
@@ -3,7 +3,7 @@ import type { MailService } from '@sendgrid/mail';
|
||||
import { SENDGRID_API_KEY } from '$env/static/private';
|
||||
|
||||
export const SENDGRID_FROM = 'Rotimi Best <best@classroomio.com>';
|
||||
export const SENDGRID_FROM_NOTIFY = 'ClassroomIO <notify@classroomio.com>';
|
||||
export const SENDGRID_FROM_NOREPLY = 'ClassroomIO <noreply@classroomio.com>';
|
||||
|
||||
export let sendgrid: MailService;
|
||||
|
||||
|
||||
@@ -8,6 +8,7 @@ interface CourseMetadata {
|
||||
videoUrl?: '';
|
||||
showDiscount: false;
|
||||
discount: 0;
|
||||
paymentLink?: string;
|
||||
reward?: {
|
||||
show: boolean;
|
||||
description: string;
|
||||
@@ -94,7 +95,7 @@ export interface Course {
|
||||
logo?: string;
|
||||
slug?: any; // type unknown;
|
||||
metadata?: CourseMetadata;
|
||||
cost?: number;
|
||||
cost: number;
|
||||
currency?: string;
|
||||
group?: Group;
|
||||
organization?: Organization;
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { render } from 'svelte-email';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import WelcomeTemplate from './template.svelte';
|
||||
import { getSendgrid, SENDGRID_FROM_NOTIFY } from '$lib/utils/services/sendgrid';
|
||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
const sendgrid = getSendgrid();
|
||||
@@ -30,7 +30,7 @@ export async function POST({ request }) {
|
||||
}
|
||||
|
||||
const options = {
|
||||
from: SENDGRID_FROM_NOTIFY,
|
||||
from: SENDGRID_FROM_NOREPLY,
|
||||
to,
|
||||
subject: `[${orgName}] Welcome to Class`,
|
||||
html: render({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { render } from 'svelte-email';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import WelcomeTemplate from './template.svelte';
|
||||
import { getSendgrid, SENDGRID_FROM_NOTIFY } from '$lib/utils/services/sendgrid';
|
||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
const sendgrid = getSendgrid();
|
||||
@@ -29,7 +29,7 @@ export async function POST({ request }) {
|
||||
}
|
||||
|
||||
const options = {
|
||||
from: SENDGRID_FROM_NOTIFY,
|
||||
from: SENDGRID_FROM_NOREPLY,
|
||||
to,
|
||||
subject: `[${courseName}] You've got a new student!`,
|
||||
html: render({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { render } from 'svelte-email';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import WelcomeTemplate from './template.svelte';
|
||||
import { getSendgrid, SENDGRID_FROM_NOTIFY } from '$lib/utils/services/sendgrid';
|
||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
const sendgrid = getSendgrid();
|
||||
@@ -32,7 +32,7 @@ export async function POST({ request }) {
|
||||
const inviteLink = `${origin}/org/${orgSiteName}/courses`;
|
||||
|
||||
const options = {
|
||||
from: SENDGRID_FROM_NOTIFY,
|
||||
from: SENDGRID_FROM_NOREPLY,
|
||||
to,
|
||||
subject: `You have been invited to a course in ${orgName}!`,
|
||||
html: render({
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { render } from 'svelte-email';
|
||||
import { json } from '@sveltejs/kit';
|
||||
import TeacherInviteTemplate from './template.svelte';
|
||||
import { getSendgrid, SENDGRID_FROM_NOTIFY } from '$lib/utils/services/sendgrid';
|
||||
import { getSendgrid, SENDGRID_FROM_NOREPLY } from '$lib/utils/services/sendgrid';
|
||||
import { getSupabase } from '$lib/utils/functions/supabase';
|
||||
|
||||
const sendgrid = getSendgrid();
|
||||
@@ -47,7 +47,7 @@ export async function POST({ request }) {
|
||||
console.log('inviteData', inviteData);
|
||||
|
||||
const options = {
|
||||
from: SENDGRID_FROM_NOTIFY,
|
||||
from: SENDGRID_FROM_NOREPLY,
|
||||
to: email,
|
||||
subject: `Join ${name} on ClassroomIO`,
|
||||
html: render({
|
||||
|
||||
@@ -35,6 +35,13 @@
|
||||
});
|
||||
console.log('data', data);
|
||||
if (error) throw error;
|
||||
|
||||
// reload page on org site cause for some reason the authlistener on +layout.svelte doesn't run
|
||||
// on org site
|
||||
if ($currentOrg.id && redirect) {
|
||||
console.log('Forcing full page reload for auth listener');
|
||||
window.location.reload();
|
||||
}
|
||||
} catch (error) {
|
||||
submitError = error.error_description || error.message;
|
||||
} finally {
|
||||
|
||||
Reference in New Issue
Block a user