feat: dashboard billing (#193)

* feat: move plans into sharable json

* remove unused code

* update pnpm lock

* update plans.json

* implement early adopter pricing

* docs: update meta title and description

* Finished the UI for upgrading plans

* Updated the pricing plans

* Updated the cards ui

* use relative import

* improve styling of upgrade plan card

* styling improvements, lemonsqueezy setup

* trigger lemonsqueezy payment and add webhook

* update pnpm lock

* add logout route

* feat: complete billing functionality

* generate lock file

* make crypto false on browser

* feat: adding upgrade prompts to org

* chore: add upgrade icon to upload video

* feat: add upgrade prompt to certificate and settings

* feat: implement onboarding setup page (#229)

* working on the onboarding setup

* working on the onboarding page

* Almost done with the onboarding UI

* finished the UI of the onBoarding setup

* finished the UI of the onBoarding setup

* Implementing the functions to carryout the logic of updating the UI onmount if a user has carried out a certain step or not

* Implementing the functions to carryout the logic of updating the UI onmount if a user has carried out a certain step or not

* making some chnages

* finished the onBoarding task

* updated some logic

* Removed the uneeded svg from the static folder

* fixed isCoursePublished not returning a boolean

* improved the setup link protection

* chore: make menuItems reactive

* improve formatting

* format supabase queries

---------

Co-authored-by: Rotimi Best <best@classroomio.com>

* revamp setup

* improve the upgrade experience

* migration

* feat: add lemonsqueezy customer portal

---------

Co-authored-by: emmanuel <chifez1@gmail.com>
This commit is contained in:
Rotimi Best
2024-04-07 06:08:34 +01:00
committed by GitHub
co-authored by emmanuel
parent 136e057954
commit fcd0bcc656
105 changed files with 1743 additions and 463 deletions
@@ -0,0 +1,46 @@
<script>
import { Grid, Row, Column } from 'carbon-components-svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import { currentOrgPlan, isFreePlan } from '$lib/utils/store/org';
import SectionTitle from '../SectionTitle.svelte';
let isRedirecting = false;
async function onOpenBilling() {
console.log({
plan: $currentOrgPlan
});
if ($currentOrgPlan?.subscriptionId) {
isRedirecting = true;
fetch('/api/lmz/subscriptions?subscriptionId=' + $currentOrgPlan?.subscriptionId)
.then((response) => response.json())
.then((res) => {
isRedirecting = false;
window.open(res.customerPortal, '_blank');
});
}
}
</script>
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Billing details</SectionTitle></Column>
<Column sm={8} md={8} lg={8}>
<h4 class="dark:text-white lg:mt-0">Manage your billing</h4>
{#if $isFreePlan}
You don't have any active billing
{:else}
<p class="mb-3">We use Lemonsqueezy to help manage your billing</p>
<PrimaryButton
label="Open billing"
variant={VARIANTS.CONTAINED_DARK}
onClick={onOpenBilling}
isLoading={isRedirecting}
/>
{/if}
</Column>
</Row>
</Grid>
@@ -72,7 +72,7 @@
$: setSiteName($currentOrg.siteName);
</script>
<Grid class="border rounded border-gray-200 w-full mt-5">
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg"><SectionTitle>Add</SectionTitle></Column>
<Column sm={2} md={6} lg={8}>
@@ -48,7 +48,7 @@
}
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5">
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex lg:flex-row flex-col justify-center lg:justify-start py-7 border-bottom-c">
<Column sm={4} md={4} lg={4} class="flex items-center justify-center">
<img src="/telegram-svg.svg" alt="" class="w-10 mr-2" />
@@ -117,7 +117,7 @@
$: setDefault($currentOrg?.landingpage as OrgLandingPageJson);
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5 relative">
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}>
<SectionTitle>Landing Page</SectionTitle>
@@ -1,6 +1,7 @@
<script>
import { goto } from '$app/navigation';
import { Grid, Row, Column } from 'carbon-components-svelte';
import FlashFilled from 'carbon-icons-svelte/lib/FlashFilled.svelte';
import TextField from '$lib/components/Form/TextField.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
@@ -9,6 +10,7 @@
import { snackbar } from '$lib/components/Snackbar/store';
import { currentOrg, currentOrgPath } from '$lib/utils/store/org';
import SectionTitle from '../SectionTitle.svelte';
import { isFreePlan } from '$lib/utils/store/org';
let avatar;
let loading = false;
@@ -94,7 +96,7 @@
}
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5">
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex lg:flex-row flex-col py-7 border-bottom-c">
<Column sm={4} md={4} lg={4}><SectionTitle>Organization Profile</SectionTitle></Column>
<Column sm={8} md={8} lg={8} class="mt-2 lg:mt-0 flex flex-col items-center lg:items-start">
@@ -175,7 +177,7 @@
</p>
<PrimaryButton
label="Edit domain"
className="my-7 py-5 px-10 text-primary-700"
className="my-7 py-5 px-10"
variant={VARIANTS.OUTLINED}
onClick={() => gotoSetting('/domains')}
/>
@@ -189,11 +191,15 @@
Add team mates to your organization so you can both collaborate on projects.
</p>
<PrimaryButton
label="Manage Team"
className="my-7 py-5 px-10 text-primary-700"
className="my-7 py-5 px-10 flex items-center gap-2 justify-center"
variant={VARIANTS.OUTLINED}
onClick={() => gotoSetting('/teams')}
/>
>
{#if $isFreePlan}
<FlashFilled size={16} class="text-blue-700" />
{/if}
Manage Team
</PrimaryButton>
</Column>
</Row>
</Grid>
@@ -3,6 +3,7 @@
import TextField from '$lib/components/Form/TextField.svelte';
import SectionTitle from '../SectionTitle.svelte';
import PrimaryButton from '$lib/components/PrimaryButton/index.svelte';
import { VARIANTS } from '$lib/components/PrimaryButton/constants';
import UploadImage from '$lib/components/UploadImage/index.svelte';
import { supabase } from '$lib/utils/functions/supabase';
import { profile } from '$lib/utils/store/user';
@@ -61,7 +62,7 @@
}
</script>
<Grid class="border-c rounded border-gray-200 w-full mt-5">
<Grid class="border-c rounded border-gray-200 dark:border-neutral-600 w-full mt-5">
<Row class="flex flex-col lg:flex-row items-center lg:items-start py-7 border-bottom-c ">
<Column sm={4} md={8} lg={4} class="mt-2 md:mt-0">
<SectionTitle>Profile Picture</SectionTitle>
@@ -83,6 +84,7 @@
<Row class="m-5 w-full flex items-center gap-2 lg:justify-center">
<PrimaryButton
label="Update Profile"
variant={VARIANTS.CONTAINED_DARK}
className="mr-5"
isLoading={loading}
isDisabled={loading}
@@ -18,6 +18,7 @@
triggerSendEmail,
NOTIFICATION_NAME
} from '$lib/utils/services/notification/notification';
import { isFreePlan } from '$lib/utils/store/org';
import type { OrgTeamMember } from '$lib/utils/types/org';
let emailsStr = '';
@@ -132,7 +133,7 @@
$: fetchTeam($currentOrg.id);
</script>
<Grid class="border rounded border-gray-200 w-full mt-5">
<Grid class="border rounded border-gray-200 dark:border-neutral-600 w-full mt-5 relative">
<Row class="py-7 border-bottom-c">
<Column sm={2} md={2} lg={4} class="text-lg"><SectionTitle>Add</SectionTitle></Column>
<Column sm={2} md={6} lg={8}>
@@ -146,10 +147,11 @@
placeholder="email comma separated"
bind:value={emailsStr}
className="mb-3"
isDisabled={$isFreePlan}
{errorMessage}
/>
<Select labelText="Role" bind:selected={role} class="mb-5 w-40">
<Select labelText="Role" bind:selected={role} class="mb-5 w-40" disabled={$isFreePlan}>
<!-- <SelectItem value={ROLE.ADMIN} text={ROLE_LABEL[ROLE.ADMIN]} /> -->
<SelectItem value={ROLE.TUTOR} text={ROLE_LABEL[ROLE.TUTOR]} />
</Select>
@@ -158,7 +160,7 @@
label="Send Invite"
onClick={onSendInvite}
{isLoading}
isDisabled={isLoading}
isDisabled={isLoading || $isFreePlan}
/>
</div>
</Column>
@@ -5,6 +5,7 @@
import LandingpageSettings from './LandingpageSettings.svelte';
// import Account from './Account.svelte';
import OrgSettings from './OrgSettings.svelte';
import Billing from './Billing.svelte';
import { onMount } from 'svelte';
import { browser } from '$app/environment';
import { goto } from '$app/navigation';
@@ -64,6 +65,13 @@
},
{
key: 3,
label: 'Billing',
tabKey: 'billing',
href: `${$page.url.pathname}?tab=billing`,
disabled: !$isOrgAdmin
},
{
key: 4,
label: 'Integrations',
tabKey: 'integrations',
href: `${$page.url.pathname}?tab=integrations`,
@@ -91,6 +99,9 @@
<TabContent class="w-full p-0">
<LandingpageSettings />
</TabContent>
<TabContent class="w-full p-0">
<Billing />
</TabContent>
<TabContent class="w-full p-0">
<Integrations />
</TabContent>