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
@@ -1,14 +1,19 @@
<script lang="ts">
import { Popover, SkeletonText } from 'carbon-components-svelte';
import CaretDown from 'carbon-icons-svelte/lib/CaretDown.svelte';
import ChevronSort from 'carbon-icons-svelte/lib/ChevronSort.svelte';
import OrgSelectorItem from './OrgSelectorItem.svelte';
import { currentOrg, orgs, currentOrgPath } from '$lib/utils/store/org';
import { currentOrg, orgs, currentOrgPath, currentOrgPlan } from '$lib/utils/store/org';
import { goto } from '$app/navigation';
import { newOrgModal } from '$lib/components/Org/store';
import Avatar from '$lib/components/Avatar/index.svelte';
import type { CurrentOrg } from '$lib/utils/types/org';
import { setTheme } from '$lib/utils/functions/theme';
import TextChip from '$lib/components/Chip/Text.svelte';
import Plan from '$lib/components/Chip/Plan.svelte';
import { PLAN_NAMES } from 'shared-constants/src/plans/constants';
export let canAddOrg = true;
let open = false;
function handleAddOrg() {
@@ -19,33 +24,60 @@
function onClick(org: CurrentOrg) {
localStorage.setItem('classroomio_org_sitename', org.siteName);
currentOrg.set(org);
setTheme(org.theme);
goto($currentOrgPath);
open = false;
}
</script>
<div class="mt-5 relative" data-outline>
<div
class="org-selector px-4 py-3 border border-l-0 border-r-0 border-t-0 border-gray-200 dark:border-neutral-600 relative"
data-outline
>
{#if $currentOrg.name}
<button
class="flex items-center cursor-pointer max-w-[219px]"
class="flex items-center gap-2 w-full"
on:click={(e) => {
e.stopPropagation();
open = !open;
}}
>
<p class="dark:text-white text-lg font-bold whitespace-nowrap truncate">
{$currentOrg.name}
</p>
<CaretDown size={16} class="ml-2" />
{#if $currentOrg.avatar_url && $currentOrg.name}
<Avatar
src={$currentOrg.avatar_url}
name={$currentOrg.name}
shape="rounded-md"
width="w-7"
height="h-7"
/>
{:else if $currentOrg.shortName}
<TextChip
size="sm"
value={$currentOrg.shortName}
className="bg-primary-200 dark:text-black font-medium"
/>
{/if}
<div class="flex items-center cursor-pointer max-w-[219px] justify-between w-full">
<div class="flex flex-col items-start">
<p class="dark:text-white text-sm font-medium whitespace-nowrap truncate mb-1">
{$currentOrg.name}
</p>
<Plan name={$currentOrgPlan ? PLAN_NAMES[$currentOrgPlan.plan_name] : PLAN_NAMES.BASIC} />
</div>
<ChevronSort size={16} />
</div>
</button>
{:else}
<div class="w-[219px] h-[30px]">
<SkeletonText style="width: 100%; height: 100%;" />
</div>
{/if}
{#if canAddOrg}
<Popover bind:open closeOnOutsideClick align="bottom-left">
<Popover class="w-[95%] left-[2%] rounded-md" bind:open closeOnOutsideClick align="bottom-left">
{#each $orgs as org}
<OrgSelectorItem
size="sm"
@@ -62,3 +94,9 @@
</Popover>
{/if}
</div>
<style>
:global(.org-selector .bx--popover-contents) {
width: 100%;
}
</style>
@@ -15,9 +15,10 @@
<div class="relative">
<button
class="{disabled &&
'opacity-25'} flex items-center text-start w-[14rem] py-2 px-3 {hasDivider &&
'border-b border-gray-100'} {active && 'bg-primary-100 dark:bg-gray-500'}"
class="w-full {disabled &&
'opacity-25'} flex items-center text-start w-[14rem] py-1.5 px-2.5 {hasDivider &&
'border-b border-gray-100 dark:border-neutral-600'} {active &&
'bg-gray-200 dark:bg-neutral-700 dark:text-white'}"
on:click={disabled ? undefined : onClick}
>
{#if avatar}
@@ -25,14 +26,14 @@
src={avatar}
name={text}
shape="rounded-md"
width="w-7"
height="h-7"
width="w-[1.2rem]"
height="h-[1.2rem]"
className="mr-2"
/>
{:else if avatarText}
<TextChip
value={avatarText}
className="mr-2 bg-primary-200 font-bold dark:bg-gray-600"
className="mr-2 bg-primary-200 font-medium text-xs dark:text-black"
{size}
/>
{/if}