CLASSROOMIO-T-152: Add payment link + reload on org login

This commit is contained in:
Rotimi Best
2023-09-14 23:04:51 +01:00
parent 530ea721a2
commit bc7a5e1e4d
16 changed files with 133 additions and 91 deletions
@@ -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
+1
View File
@@ -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
+2 -2
View File
@@ -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 = '';
+2 -2
View File
@@ -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;
+18
View File
@@ -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
});
};
+1 -1
View File
@@ -3,7 +3,7 @@ import type { MailService } from '@sendgrid/mail';
import { SENDGRID_API_KEY } from '$env/static/private';
export const SENDGRID_FROM = 'Rotimi Best <[email protected]>';
export const SENDGRID_FROM_NOTIFY = 'ClassroomIO <notif[email protected]>';
export const SENDGRID_FROM_NOREPLY = 'ClassroomIO <norepl[email protected]>';
export let sendgrid: MailService;
+2 -1
View File
@@ -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({
+2 -2
View File
@@ -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({
+7
View File
@@ -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 {