229 lines
7.2 KiB
Svelte
229 lines
7.2 KiB
Svelte
<script lang="ts">
|
|
import { onMount, onDestroy } from 'svelte';
|
|
import type { OrganizationPlan } from '$lib/utils/types';
|
|
import { supabase } from '$lib/utils/functions/supabase';
|
|
import { goto } from '$app/navigation';
|
|
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
|
|
import { page } from '$app/stores';
|
|
import Checkmark from 'carbon-icons-svelte/lib/Checkmark.svelte';
|
|
import PLANS from 'shared/src/plans/data.json';
|
|
import { profile } from '$lib/utils/store/user';
|
|
import { subscribeToProduct } from '$lib/utils/services/lemonsqueezy/subscribe';
|
|
import { snackbar } from '$lib/components/Snackbar/store';
|
|
import { Loading } from 'carbon-components-svelte';
|
|
import Modal from '$lib/components/Modal/index.svelte';
|
|
import StepDoneIcon from '$lib/components/Icons/StepDoneIcon.svelte';
|
|
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
|
|
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
|
|
import type { RealtimeChannel, RealtimePostgresChangesPayload } from '@supabase/supabase-js';
|
|
import Confetti from '$lib/components/Confetti/index.svelte';
|
|
import { toggleConfetti } from '$lib/components/Confetti/store';
|
|
import { t } from '$lib/utils/functions/translations';
|
|
|
|
const disabledClass = 'bg-gray-300 text-gray-400 hover:cursor-not-allowed';
|
|
|
|
let isLoadingPlan: string | null = null;
|
|
let orgPlanChannel: RealtimeChannel;
|
|
let open = false;
|
|
let upgraded = false;
|
|
|
|
async function handleClick(plan, planName: string) {
|
|
if (plan.CTA.IS_DISABLED || !$profile.id) {
|
|
return;
|
|
}
|
|
|
|
if (!plan.CTA.PRODUCT_ID && plan.CTA.DASHBOARD_LINK) {
|
|
window.open(plan.CTA.DASHBOARD_LINK, '_blank');
|
|
return;
|
|
}
|
|
|
|
isLoadingPlan = planName;
|
|
|
|
try {
|
|
const { checkoutURL } = await subscribeToProduct({
|
|
productId: plan.CTA.PRODUCT_ID,
|
|
email: $profile.email,
|
|
name: $profile.fullname,
|
|
triggeredBy: $currentOrg.memberId,
|
|
orgId: $currentOrg.id
|
|
});
|
|
|
|
if (!checkoutURL) {
|
|
snackbar.error('upgrade.generate_fail');
|
|
return;
|
|
}
|
|
|
|
window.open(checkoutURL, '_blank');
|
|
} catch (error) {
|
|
console.error('Error subscribing', error);
|
|
|
|
snackbar.error('upgrade.failed');
|
|
}
|
|
|
|
isLoadingPlan = null;
|
|
}
|
|
|
|
async function handleInsert(payload: RealtimePostgresChangesPayload<OrganizationPlan>) {
|
|
const newPlan = payload.new as OrganizationPlan;
|
|
|
|
console.log('A new plan was inserted');
|
|
// If plan was successfully generated
|
|
if (newPlan.org_id === $currentOrg.id && newPlan.is_active) {
|
|
upgraded = true;
|
|
toggleConfetti();
|
|
|
|
setTimeout(() => {
|
|
toggleConfetti();
|
|
}, 1000);
|
|
}
|
|
}
|
|
|
|
function onClose() {
|
|
if (upgraded) {
|
|
window.location.href = $currentOrgPath;
|
|
} else {
|
|
goto($page.url.pathname);
|
|
}
|
|
}
|
|
|
|
function onLearnMore() {
|
|
window.open('https://classroomio.com/blog/early-adopter', '_blank');
|
|
}
|
|
|
|
onMount(() => {
|
|
orgPlanChannel = supabase
|
|
.channel('any')
|
|
.on(
|
|
'postgres_changes',
|
|
{ event: 'INSERT', schema: 'public', table: 'organization_plan' },
|
|
handleInsert
|
|
)
|
|
.subscribe();
|
|
});
|
|
|
|
onDestroy(() => {
|
|
if (orgPlanChannel) {
|
|
supabase.removeChannel(orgPlanChannel);
|
|
}
|
|
});
|
|
|
|
$: {
|
|
const query = new URLSearchParams($page.url.search);
|
|
open = (query.get('upgrade') || '') === 'true';
|
|
}
|
|
$: planNames = Object.keys(PLANS);
|
|
</script>
|
|
|
|
{#if upgraded}
|
|
<Confetti />
|
|
{/if}
|
|
|
|
<Modal
|
|
{onClose}
|
|
{open}
|
|
width="w-4/5"
|
|
maxWidth={upgraded ? 'max-w-[600px]' : undefined}
|
|
modalHeading={$t('pricing.modal.heading')}
|
|
>
|
|
{#if upgraded}
|
|
<div class="flex flex-col items-center justify-center mb-4 w-full relative">
|
|
<StepDoneIcon />
|
|
<h4 class="text-2xl">{$t('pricing.modal.thanks')}</h4>
|
|
<p class="mb-4 text-center">
|
|
{$t('pricing.modal.plan')}
|
|
</p>
|
|
<div class="flex items-center gap-4">
|
|
<PrimaryButton
|
|
label={$t('pricing.modal.close')}
|
|
variant={VARIANTS.OUTLINED}
|
|
onClick={onClose}
|
|
/>
|
|
<PrimaryButton
|
|
label={$t('pricing.modal.learn')}
|
|
variant={VARIANTS.CONTAINED_DARK}
|
|
onClick={onLearnMore}
|
|
/>
|
|
</div>
|
|
</div>
|
|
{:else}
|
|
<div class="flex flex-col items-center justify-center">
|
|
<div class="isolate grid grid-cols-1 gap-3 lg:grid-cols-3">
|
|
{#each planNames as planName}
|
|
<div
|
|
class="max-w-xl rounded-xl {planName === 'EARLY_ADOPTER' &&
|
|
'bg-primary-900'} p-4 ring-1 ring-gray-200 lg:max-w-sm"
|
|
>
|
|
<p
|
|
class="mb-2 text-lg font-semibold leading-8 {planName === 'EARLY_ADOPTER'
|
|
? 'text-white'
|
|
: 'text-gray-900'} dark:text-white lg:text-xl"
|
|
>
|
|
{$t(PLANS[planName].NAME)}
|
|
</p>
|
|
<p
|
|
class="text-baseline flex items-baseline gap-x-1 font-medium {planName ===
|
|
'EARLY_ADOPTER'
|
|
? 'text-white'
|
|
: 'text-black'} dark:text-gray-300"
|
|
>
|
|
{PLANS[planName].PRICE.CURRENCY}
|
|
{$t(PLANS[planName].PRICE.MONTHLY)}
|
|
{#if !PLANS[planName].PRICE.IS_PREMIUM}
|
|
/{$t('pricing.modal.month')}
|
|
{/if}
|
|
</p>
|
|
<p
|
|
class=" mt-4 text-sm font-light leading-6 {planName === 'EARLY_ADOPTER'
|
|
? 'text-white'
|
|
: 'text-black'} dark:text-gray-300 lg:leading-6"
|
|
>
|
|
{$t(PLANS[planName].DESCRIPTION)}
|
|
</p>
|
|
|
|
<button
|
|
class="text-md mt-4 block w-full rounded-md {isLoadingPlan === planName &&
|
|
disabledClass} {planName === 'EARLY_ADOPTER'
|
|
? 'bg-white text-slate-900 hover:bg-indigo-50'
|
|
: PLANS[planName].CTA.IS_DISABLED
|
|
? disabledClass
|
|
: 'bg-primary-900 hover:bg-primary-700 text-white'} py-3 text-center font-medium hover:no-underline lg:rounded-md lg:py-3 lg:text-lg lg:font-semibold flex items-center justify-center"
|
|
on:click={() => {
|
|
if (isLoadingPlan === planName) return;
|
|
|
|
handleClick(PLANS[planName], planName);
|
|
}}
|
|
>
|
|
{#if isLoadingPlan === planName}
|
|
<Loading withOverlay={false} small />
|
|
{:else}
|
|
{$t(PLANS[planName].CTA.DASHBOARD_LABEL)}
|
|
{/if}
|
|
</button>
|
|
|
|
<ul
|
|
class="mt-4 space-y-2 text-sm {planName === 'EARLY_ADOPTER'
|
|
? 'text-white'
|
|
: 'text-black'} dark:text-gray-300"
|
|
>
|
|
{#each PLANS[planName].FEATURES as features}
|
|
<li class="flex items-center">
|
|
<div>
|
|
<Checkmark
|
|
size={16}
|
|
fill={planName === 'EARLY_ADOPTER' ? '#fff' : '#1D4EE2'}
|
|
class="mr-2 lg:mr-3"
|
|
/>
|
|
</div>
|
|
<p class="text-sm">
|
|
{$t(features)}
|
|
</p>
|
|
</li>
|
|
{/each}
|
|
</ul>
|
|
</div>
|
|
{/each}
|
|
</div>
|
|
</div>
|
|
{/if}
|
|
</Modal>
|