feat: new billing onboarding flow

This commit is contained in:
Arman committed 2023-12-28 17:35:48 +01:00
1 parent bf2969614d
commit ca24b0b82e
7 files changed
+170 -55

No files matched your search

@@ -2,6 +2,7 @@
import { FormList, InputText } from '$lib/elements/forms';
import { onDestroy, onMount } from 'svelte';
import { CreditCardBrandImage, RadioBoxes } from '..';
import { unmountPaymentElement } from '$lib/stores/stripe';
export let methods: Record<string, unknown>[];
export let group: string;
@@ -33,6 +34,7 @@
onDestroy(() => {
observer.disconnect();
unmountPaymentElement();
});
$: if (element) {
+1
View File
@@ -122,6 +122,7 @@
} else {
$wizard.step--;
}
wizard.setInterceptor(null);
trackEvent('wizard_back');
}
+9 -1
View File
@@ -37,7 +37,15 @@ export async function initializeStripe() {
paymentElement.mount('#payment-element');
}
// TODO: fix redirect
export async function unmountPaymentElement() {
isStripeInitialized.set(false);
if (paymentElement) {
paymentElement.unmount();
}
clientSecret = null;
paymentMethod = null;
elements = null;
}
export async function submitStripeCard(name: string, urlRoute?: string) {
try {
+6 -2
View File
@@ -31,14 +31,18 @@ function createWizardStore() {
return {
subscribe,
set,
start: (component: typeof SvelteComponent<unknown>, media: string = null) =>
start: (
component: typeof SvelteComponent<unknown>,
media: string = null,
step: number = 1
) =>
update((n) => {
n.show = true;
n.component = component;
n.interceptor = null;
n.interceptorNotificationEnabled = true;
n.media = media;
n.step = 1;
n.step = step;
n.cover = null;
n.nextDisabled = false;
n.finalAction = null;
@@ -28,6 +28,23 @@
async function create() {
try {
// Create free organization if coming from onboarding
if ($page.url.pathname.includes('/console/onboarding')) {
const freeOrg = await sdk.forConsole.billing.createOrganization(
ID.unique(),
'Personal Projects',
BillingPlan.STARTER,
null,
null
);
await sdk.forConsole.projects.create(
ID.unique(),
'My first project',
freeOrg.$id,
'fra'
);
}
const org = await sdk.forConsole.billing.createOrganization(
$createOrganization.id ?? ID.unique(),
$createOrganization.name,
@@ -64,6 +81,25 @@
await sdk.forConsole.billing.updateTaxId(org.$id, $createOrganization.taxId);
}
trackEvent(Submit.OrganizationCreate, {
customId: !!$createOrganization.id,
plan: tierToPlan($createOrganization.billingPlan)?.name,
budget_cap_enabled: !!$createOrganization?.billingBudget,
members_invited: $createOrganization?.collaborators?.length
});
//Create first pro project if onboarding
if ($page.url.pathname.includes('/console/onboarding')) {
await sdk.forConsole.projects.create(
ID.unique(),
'My first Pro project',
org.$id,
'fra'
);
trackEvent(Submit.ProjectCreate);
}
await invalidate(Dependencies.ACCOUNT);
await preloadData(`/console/organization-${org.$id}`);
await goto(`/console/organization-${org.$id}`);
@@ -71,12 +107,7 @@
type: 'success',
message: `${$createOrganization.name ?? 'Organization'} has been created`
});
trackEvent(Submit.OrganizationCreate, {
customId: !!$createOrganization.id,
plan: tierToPlan($createOrganization.billingPlan)?.name,
budget_cap_enabled: !!$createOrganization?.billingBudget,
members_invited: $createOrganization?.collaborators?.length
});
wizard.hide();
if (org.billingPlan === BillingPlan.PRO) {
wizard.showCover(HoodieCover);
+110 -44
View File
@@ -2,11 +2,11 @@
import { goto, invalidate } from '$app/navigation';
import { page } from '$app/stores';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { Card } from '$lib/components';
import { Card, Heading } from '$lib/components';
import CustomId from '$lib/components/customId.svelte';
import { BillingPlan, Dependencies } from '$lib/constants';
import { Pill } from '$lib/elements';
import { Button, Form, InputText } from '$lib/elements/forms';
import { Button, Form, InputSelect, InputText } from '$lib/elements/forms';
import FormList from '$lib/elements/forms/formList.svelte';
import { Container } from '$lib/layout';
import { addNotification } from '$lib/stores/notifications';
@@ -16,10 +16,18 @@
import { ID } from '@appwrite.io/console';
import { onMount } from 'svelte';
import CreateOrganizationCloud from '../createOrganizationCloud.svelte';
import { tierToPlan, type Tier } from '$lib/stores/billing';
import { createOrganization } from '../wizard/cloudOrganization/store';
let name: string;
let id: string;
let showCustomId = false;
let plan: Tier;
const options = [
{ value: BillingPlan.STARTER, label: 'Starter - $0/billing period' },
{ value: BillingPlan.PRO, label: 'Pro - $15/billing period + add-ons' }
];
onMount(() => {
if (isCloud) {
@@ -32,66 +40,124 @@
}
});
async function createProject() {
try {
const org = await createOrganization();
const project = await sdk.forConsole.projects.create(
id ?? ID.unique(),
name,
org.$id,
isCloud ? 'fra' : 'default'
);
await invalidate(Dependencies.ACCOUNT);
goto(`/console/project-${project.$id}`);
trackEvent(Submit.ProjectCreate, {
customId: !!id,
teamId: org.$id
});
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.ProjectCreate);
}
}
async function createOrganization() {
async function handleSubmit() {
if (isCloud) {
return await sdk.forConsole.billing.createOrganization(
ID.unique(),
'Personal Projects',
BillingPlan.STARTER,
null,
null
);
} else return await sdk.forConsole.teams.create(ID.unique(), 'Personal Projects');
if (plan === BillingPlan.STARTER) {
try {
const org = await sdk.forConsole.billing.createOrganization(
id ?? ID.unique(),
name ?? 'Personal Projects',
plan,
null,
null
);
trackEvent(Submit.OrganizationCreate, {
customId: !!id,
plan: tierToPlan(plan)?.name
});
await sdk.forConsole.projects.create(
ID.unique(),
'My first project',
org.$id,
'fra'
);
await invalidate(Dependencies.ACCOUNT);
await goto(`/console/organization-${org.$id}`);
addNotification({
message: `${
name?.length ? name : 'Personal Projects'
} organization successfully created`,
type: 'success'
});
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.OrganizationCreate);
}
} else {
wizard.start(CreateOrganizationCloud, null, 2);
$createOrganization.name = name?.length ? name : 'Personal Projects';
$createOrganization.billingPlan = plan;
$createOrganization.id = id;
}
} else {
try {
const org = await sdk.forConsole.teams.create(
id ?? ID.unique(),
name?.length ? name : 'Personal Projects'
);
const project = await sdk.forConsole.projects.create(
ID.unique(),
'My first project',
org.$id,
'default'
);
await invalidate(Dependencies.ACCOUNT);
await goto(`/console/project-${project.$id}`);
addNotification({
message: `${name ?? 'Personal Projects'} organization successfully created`,
type: 'success'
});
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
trackError(error, Submit.OrganizationCreate);
}
}
}
</script>
<Container overlapCover size="large">
<Card>
<Form onSubmit={createProject}>
<FormList>
<Heading size="4" tag="h2">Create a new organization</Heading>
<Form onSubmit={handleSubmit}>
<FormList gap={16}>
<InputText
id="name"
label="Project name"
placeholder="First Appwrite Project"
required
label="Name"
placeholder="Organization name"
hideRequired
bind:value={name} />
{#if !showCustomId}
<div>
<Pill button on:click={() => (showCustomId = !showCustomId)}>
<span class="icon-pencil" aria-hidden="true" /><span class="text">
Project ID
Organization ID
</span>
</Pill>
</div>
{:else}
<CustomId bind:show={showCustomId} name="Project" isProject bind:id />
<CustomId bind:show={showCustomId} name="Organization" isProject bind:id />
{/if}
<Button fullWidth submit disabled={name === ''} event="create_project">
Create project
{#if isCloud}
<div class="u-margin-block-start-8">
<h3><b>Plan</b></h3>
<p>
For more details on our plans, visit our <Button
link
external
href="https://appwrite.io/pricing">pricing page</Button
>.
</p>
<FormList class="u-margin-block-start-8">
<InputSelect
placeholder="Select a plan"
label="Select plan"
showLabel={false}
id="plan"
required
hideRequired
{options}
bind:value={plan} />
</FormList>
</div>
{/if}
<Button fullWidth submit disabled={isCloud && !plan} event="create_organization">
Get started
</Button>
</FormList>
</Form>
+5 -2
View File
@@ -1,7 +1,10 @@
<script lang="ts">
import { Cover, CoverTitle } from '$lib/layout';
import { Heading } from '$lib/components';
import { Cover } from '$lib/layout';
</script>
<Cover size="large">
<CoverTitle>Let's create your first project</CoverTitle>
<div class="u-flex u-cross-center u-main-center u-width-full-line">
<Heading size="1" tag="h1" trimmed={false}>Welcome to Appwrite</Heading>
</div>
</Cover>