mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
feat: org creation modal and fixes
This commit is contained in:
@@ -8,6 +8,7 @@
|
||||
export let value = false;
|
||||
export let required = false;
|
||||
export let disabled = false;
|
||||
export let tooltip: string = null;
|
||||
|
||||
let element: HTMLInputElement;
|
||||
let error: string;
|
||||
@@ -39,7 +40,20 @@
|
||||
on:invalid={handleInvalid} />
|
||||
|
||||
<div class="choice-item-content">
|
||||
<div class:u-hide={!showLabel} class="choice-item-title">{label}</div>
|
||||
<div class:u-hide={!showLabel} class="choice-item-title">
|
||||
{label}
|
||||
|
||||
{#if tooltip}
|
||||
<button class="tooltip" aria-label="variables info">
|
||||
<span class="icon-info" aria-hidden="true" />
|
||||
<span class="tooltip-popup" role="tooltip">
|
||||
<p class="text">
|
||||
{tooltip}
|
||||
</p>
|
||||
</span>
|
||||
</button>
|
||||
{/if}
|
||||
</div>
|
||||
{#if $$slots}
|
||||
<p class="choice-item-paragraph"><slot /></p>
|
||||
{/if}
|
||||
|
||||
@@ -60,3 +60,56 @@ export class Billing {
|
||||
return await this.client.call('patch', uri, { 'content-type': 'application/json' }, params);
|
||||
}
|
||||
}
|
||||
|
||||
export const apperanceLight = {
|
||||
variables: {
|
||||
colorPrimary: '#606a7b',
|
||||
colorText: '#373B4D',
|
||||
colorBackground: '#FFFFFF',
|
||||
color: '#606a7b',
|
||||
colorDanger: '#df1b41',
|
||||
fontFamily: 'Inter, arial, sans-serif',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
rules: {
|
||||
'.Input:hover': {
|
||||
border: 'solid 1px #C4C6D7',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input:focus': {
|
||||
border: 'solid 1px #C4C6D7',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input::placeholder': {
|
||||
color: '#C4C6D7'
|
||||
},
|
||||
'.Input--invalid': {
|
||||
border: 'solid 1px var(--colorDanger)',
|
||||
boxShadow: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
export const apperanceDark = {
|
||||
variables: {
|
||||
colorPrimary: '#606a7b',
|
||||
colorText: '#C5C7D8',
|
||||
colorBackground: '#161622',
|
||||
colorDanger: '#FF453A',
|
||||
fontFamily: 'Inter, arial, sans-serif',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
rules: {
|
||||
'.Input:hover': {
|
||||
border: 'solid 1px #4F5769',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input:focus': {
|
||||
border: 'solid 1px #4F5769',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input--invalid': {
|
||||
border: 'solid 1px var(--colorDanger)',
|
||||
boxShadow: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -47,20 +47,32 @@
|
||||
$createOrganization = {
|
||||
id: null,
|
||||
name: null,
|
||||
tier: Tier['PREMIUM'],
|
||||
region: 'eu-central-1'
|
||||
tier: Tier['PREMIUM']
|
||||
};
|
||||
});
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
|
||||
stepsComponents.set(1, {
|
||||
label: 'Project details',
|
||||
label: 'Organization details',
|
||||
component: Step1
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Select region',
|
||||
label: 'Payment details',
|
||||
component: Step2
|
||||
});
|
||||
stepsComponents.set(3, {
|
||||
label: 'Invite collaborators',
|
||||
component: Step2
|
||||
});
|
||||
stepsComponents.set(4, {
|
||||
label: 'Review & confirm',
|
||||
component: Step2
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Create a Project" steps={stepsComponents} on:finish={create} on:exit={onFinish} />
|
||||
<Wizard
|
||||
title="Create organization "
|
||||
steps={stepsComponents}
|
||||
on:finish={create}
|
||||
on:exit={onFinish} />
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { apperanceDark, apperanceLight } from '$lib/stores/billing';
|
||||
|
||||
export let show = false;
|
||||
|
||||
@@ -26,59 +27,6 @@
|
||||
let clientSecret: string;
|
||||
let paymentMethod: PaymentMethod;
|
||||
|
||||
const apperanceLight = {
|
||||
variables: {
|
||||
colorPrimary: '#606a7b',
|
||||
colorText: '#373B4D',
|
||||
colorBackground: '#FFFFFF',
|
||||
color: '#606a7b',
|
||||
colorDanger: '#df1b41',
|
||||
fontFamily: 'Inter, arial, sans-serif',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
rules: {
|
||||
'.Input:hover': {
|
||||
border: 'solid 1px #C4C6D7',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input:focus': {
|
||||
border: 'solid 1px #C4C6D7',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input::placeholder': {
|
||||
color: '#C4C6D7'
|
||||
},
|
||||
'.Input--invalid': {
|
||||
border: 'solid 1px var(--colorDanger)',
|
||||
boxShadow: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
const apperanceDark = {
|
||||
variables: {
|
||||
colorPrimary: '#606a7b',
|
||||
colorText: '#C5C7D8',
|
||||
colorBackground: '#161622',
|
||||
colorDanger: '#FF453A',
|
||||
fontFamily: 'Inter, arial, sans-serif',
|
||||
borderRadius: '4px'
|
||||
},
|
||||
rules: {
|
||||
'.Input:hover': {
|
||||
border: 'solid 1px #4F5769',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input:focus': {
|
||||
border: 'solid 1px #4F5769',
|
||||
boxShadow: 'none'
|
||||
},
|
||||
'.Input--invalid': {
|
||||
border: 'solid 1px var(--colorDanger)',
|
||||
boxShadow: 'none'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
stripe = await loadStripe(publicKey);
|
||||
try {
|
||||
@@ -151,7 +99,7 @@
|
||||
<!-- Elements will create form elements here -->
|
||||
</div>
|
||||
<p class="text u-small">
|
||||
You won’t be charged immediately. You will be charged for your plan on the first day of
|
||||
You won't be charged immediately. You will be charged for your plan on the first day of
|
||||
each month using the payment method you've specified above.
|
||||
</p>
|
||||
</FormList>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { Tier } from '$lib/system';
|
||||
import { createOrganization } from '../wizard/store';
|
||||
import { createProject } from './wizard/store';
|
||||
|
||||
const teamId = $page.params.organization;
|
||||
const dispatch = createEventDispatcher();
|
||||
@@ -24,19 +24,19 @@
|
||||
async function create() {
|
||||
try {
|
||||
const project = await sdk.forConsole.projects.create(
|
||||
$createOrganization?.id ?? ID.unique(),
|
||||
$createOrganization.name,
|
||||
$createProject?.id ?? ID.unique(),
|
||||
$createProject.name,
|
||||
teamId,
|
||||
'default'
|
||||
);
|
||||
dispatch('created', project);
|
||||
trackEvent(Submit.ProjectCreate, {
|
||||
customId: !!$createOrganization?.id,
|
||||
customId: !!$createProject?.id,
|
||||
teamId
|
||||
});
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${$createOrganization.name} has been created`
|
||||
message: `${$createProject.name} has been created`
|
||||
});
|
||||
await goto(`/console/project-${project.$id}`);
|
||||
} catch (e) {
|
||||
@@ -49,7 +49,7 @@
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
$createOrganization = {
|
||||
$createProject = {
|
||||
id: null,
|
||||
name: null,
|
||||
tier: Tier['PREMIUM']
|
||||
@@ -58,21 +58,13 @@
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Organization details',
|
||||
label: 'Project details',
|
||||
component: Step1
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Payment details',
|
||||
component: Step2
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Invite collaborators',
|
||||
component: Step2
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Review & confirm',
|
||||
label: 'Select region',
|
||||
component: Step2
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Create organization" steps={stepsComponents} on:finish={create} on:exit={onFinish} />
|
||||
<Wizard title="Create a project" steps={stepsComponents} on:finish={create} on:exit={onFinish} />
|
||||
|
||||
@@ -3,9 +3,12 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import { InputText, FormList } from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { organizationList } from '$lib/stores/organization';
|
||||
import { createOrganization } from './store';
|
||||
|
||||
let showCustomId = false;
|
||||
|
||||
$: anyOrgFree = $organizationList.teams?.find((org) => org?.tier === 'free');
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
@@ -45,25 +48,46 @@
|
||||
<ul
|
||||
class="u-flex u-flex-vertical u-gap-16 u-margin-block-start-16"
|
||||
style="--p-grid-item-size:16em; --p-grid-item-size-small-screens:16rem; --grid-gap: 1rem;">
|
||||
<li>
|
||||
<LabelCard name="plan" bind:group={$createOrganization.tier} value="free">
|
||||
<svelte:fragment slot="title">Free - $0/month</svelte:fragment>
|
||||
For personal, passion projects.
|
||||
</LabelCard>
|
||||
</li>
|
||||
{#if !anyOrgFree}
|
||||
<li>
|
||||
<LabelCard name="plan" bind:group={$createOrganization.tier} value="free">
|
||||
<svelte:fragment slot="custom">
|
||||
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
|
||||
<h4 class="body-text-2 u-bold">Free - $0/month</h4>
|
||||
<p class="u-color-text-gray u-small">For personal, passion projects.</p>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</LabelCard>
|
||||
</li>
|
||||
{/if}
|
||||
<li>
|
||||
<LabelCard name="plan" bind:group={$createOrganization.tier} value="starter">
|
||||
<svelte:fragment slot="title"
|
||||
>Starter - $10/month per organization member</svelte:fragment>
|
||||
For small organizations that want flexibility.
|
||||
<svelte:fragment slot="custom">
|
||||
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
|
||||
<h4 class="body-text-2 u-bold">
|
||||
Starter - $10/month per organization member
|
||||
</h4>
|
||||
<p class="u-color-text-gray u-small">
|
||||
For small organizations that want flexibility.
|
||||
</p>
|
||||
</div>
|
||||
<Pill>14 DAY FREE TRIAL</Pill>
|
||||
</svelte:fragment>
|
||||
</LabelCard>
|
||||
</li>
|
||||
<li>
|
||||
<LabelCard name="plan" bind:group={$createOrganization.tier} value="premium">
|
||||
<svelte:fragment slot="title">
|
||||
Pro - $20/month per organization member + exta usage
|
||||
<svelte:fragment slot="custom">
|
||||
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
|
||||
<h4 class="body-text-2 u-bold">
|
||||
Pro - $20/month per organization member + exta usage
|
||||
</h4>
|
||||
<p class="u-color-text-gray u-small">
|
||||
For organizations that need the ability scale easily.
|
||||
</p>
|
||||
</div>
|
||||
<Pill>14 DAY FREE TRIAL</Pill>
|
||||
</svelte:fragment>
|
||||
For organizations that need the ability scale easily.
|
||||
</LabelCard>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
@@ -1,23 +1,70 @@
|
||||
<script lang="ts">
|
||||
import { RegionCard } from '$lib/components';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { FormList, InputRadio, InputText } from '$lib/elements/forms';
|
||||
import InputChoice from '$lib/elements/forms/inputChoice.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { createOrganization } from './store';
|
||||
|
||||
let methods = [];
|
||||
let name: string;
|
||||
|
||||
onMount(async () => {
|
||||
methods = await sdk.forConsole.billing.listPaymentMethods('TEST');
|
||||
if (methods.length) {
|
||||
$createOrganization.payment = methods[0].id;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select region</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose a deployment region for your project. This region cannot be changed.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">Payment details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">Add a payment method to your organization.</svelte:fragment>
|
||||
|
||||
<p class="text u-margin-block-start-16">
|
||||
Edge Network is enabled on all regions. For more details, check out our <a
|
||||
class="link"
|
||||
href="http://#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Documentation</a
|
||||
>.
|
||||
</p>
|
||||
<FormList>
|
||||
<div class:boxes-wrapper={methods?.length}>
|
||||
{#each methods as method}
|
||||
<div class="box">test</div>
|
||||
<InputRadio
|
||||
id="payment-method"
|
||||
label="Payment method"
|
||||
value={method.id}
|
||||
name={method.id}
|
||||
group="payment" />
|
||||
{/each}
|
||||
|
||||
<div class="box">
|
||||
<InputRadio
|
||||
id="payment-method"
|
||||
label="Add new payment method"
|
||||
value={null}
|
||||
name="test"
|
||||
group={$createOrganization.payment} />
|
||||
{#if $createOrganization.payment === null}
|
||||
<InputText
|
||||
id="name"
|
||||
label="Cardholder name"
|
||||
placeholder="Cardholder name"
|
||||
bind:value={name}
|
||||
required
|
||||
autofocus={true} />
|
||||
|
||||
<div id="payment-element">
|
||||
<!-- Elements will create form elements here -->
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<InputChoice type="switchbox" id="budget" label="Enable budget cap" tooltip="lorem ipsum">
|
||||
<p class="text">
|
||||
Restrict your resource usage by setting a budget cap. <a
|
||||
class="link"
|
||||
href="http://#"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">Learn more about usage rates</a
|
||||
>.
|
||||
</p>
|
||||
</InputChoice>
|
||||
</FormList>
|
||||
</WizardStep>
|
||||
|
||||
@@ -5,8 +5,10 @@ export const createOrganization = writable<{
|
||||
id?: string;
|
||||
name: string;
|
||||
tier: Tier;
|
||||
payment: string;
|
||||
}>({
|
||||
id: null,
|
||||
name: null,
|
||||
tier: Tier['PREMIUM']
|
||||
tier: Tier['PREMIUM'],
|
||||
payment: null
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user