Merge branch 'main' of github.com:appwrite/console into fix-nps-feedback-design-review

This commit is contained in:
Arman
2024-01-26 17:45:51 +01:00
33 changed files with 218 additions and 7723 deletions
+4
View File
@@ -0,0 +1,4 @@
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock
+5 -5
View File
@@ -49,8 +49,8 @@
"eslint-plugin-svelte": "^2.33.0",
"jsdom": "^22.1.0",
"kleur": "^4.1.5",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"prettier": "^3.2.2",
"prettier-plugin-svelte": "^3.1.2",
"sass": "^1.66.1",
"svelte": "^4.2.0",
"svelte-check": "^3.5.1",
@@ -7519,9 +7519,9 @@
}
},
"node_modules/prettier": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.1.0.tgz",
"integrity": "sha512-TQLvXjq5IAibjh8EpBIkNKxO749UEWABoiIZehEPiY4GNpVdhaFKqSTu+QrlU6D2dPAfubRmtJTi4K4YkQ5eXw==",
"version": "3.2.4",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.4.tgz",
"integrity": "sha512-FWu1oLHKCrtpO1ypU6J0SbK2d9Ckwysq6bHj/uaCP26DxrPpppCLQRGVuqAxSTvhF00AcvDRyYrLNW7ocBhFFQ==",
"dev": true,
"bin": {
"prettier": "bin/prettier.cjs"
+4 -4
View File
@@ -10,8 +10,8 @@
"sync": "svelte-kit sync",
"check": "svelte-check --tsconfig ./tsconfig.json --fail-on-warnings --threshold warning",
"check:watch": "svelte-check --tsconfig ./tsconfig.json --watch",
"lint": "prettier --ignore-path .gitignore --check --plugin prettier-plugin-svelte . && eslint .",
"format": "prettier --ignore-path .gitignore --write --plugin prettier-plugin-svelte .",
"lint": "prettier --check . && eslint .",
"format": "prettier --write .",
"test": "vitest run",
"test:ui": "vitest --ui",
"test:watch": "vitest watch",
@@ -61,8 +61,8 @@
"eslint-plugin-svelte": "^2.33.0",
"jsdom": "^22.1.0",
"kleur": "^4.1.5",
"prettier": "^3.0.3",
"prettier-plugin-svelte": "^3.0.3",
"prettier": "^3.2.2",
"prettier-plugin-svelte": "^3.1.2",
"sass": "^1.66.1",
"svelte": "^4.2.0",
"svelte-check": "^3.5.1",
-7647
View File
File diff suppressed because it is too large Load Diff
@@ -55,7 +55,7 @@
<div>
<span class="icon-exclamation-circle u-color-text-danger" />
<span>
{couponData.code} is not a valid promo code
{couponData.code.toUpperCase()} is not a valid promo code
</span>
</div>
{:else if couponData?.status === 'active'}
@@ -64,11 +64,11 @@
<span class="icon-tag u-color-text-success" />
<slot data={couponData}>
<span>
{couponData.code} applied (-${couponData.credits})
{couponData.code.toUpperCase()} applied (-${couponData.credits})
</span>
</slot>
</div>
<Button text on:click={removeCoupon}>Remove</Button>
<Button round text on:click={removeCoupon}><span class="icon-x"></span></Button>
</div>
{/if}
</FormList>
+13 -11
View File
@@ -110,17 +110,19 @@
</div>
</header>
<div class="modal-content">
{#if error}
<Alert
dismissible
type="warning"
on:dismiss={() => {
error = null;
}}>
{error}
</Alert>
{/if}
<slot />
<div class="modal-content-spacer u-flex-vertical u-gap-24 u-width-full-line">
{#if error}
<Alert
dismissible
type="warning"
on:dismiss={() => {
error = null;
}}>
{error}
</Alert>
{/if}
<slot />
</div>
</div>
{#if $$slots.footer}
+15 -13
View File
@@ -71,19 +71,21 @@
</p>
</header>
<div class="modal-content">
{#if error}
<div bind:this={alert}>
<Alert
dismissible
type="warning"
on:dismiss={() => {
error = null;
}}>
{error}
</Alert>
</div>
{/if}
<slot />
<div class="modal-content-spacer u-flex-vertical u-gap-24 u-width-full-line">
{#if error}
<div bind:this={alert}>
<Alert
dismissible
type="warning"
on:dismiss={() => {
error = null;
}}>
{error}
</Alert>
</div>
{/if}
<slot />
</div>
</div>
{#if $$slots.footer}
+4 -2
View File
@@ -41,8 +41,10 @@
</slot>
</p>
</header>
<div class="modal-content mk-content">
<slot />
<div class="modal-content">
<div class="modal-content-spacer u-flex-vertical u-gap-24 u-width-full-line">
<slot />
</div>
</div>
</div>
</div>
+1 -1
View File
@@ -13,7 +13,7 @@
{#if total}
{#each elements as element}
{@const value = element[variableName]?.toString()}
<div class="box">
<div class="box" data-private>
<InputRadio
id={`${name}-${value}`}
{value}
+12 -1
View File
@@ -11,6 +11,8 @@
import { isCloud } from '$lib/system';
import { organization } from '$lib/stores/organization';
import { BillingPlan } from '$lib/constants';
import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte';
import { trackEvent } from '$lib/actions/analytics';
export let show = false;
@@ -43,7 +45,16 @@
{/if}
</div>
{#if $organization?.billingPlan === BillingPlan.STARTER}
<Button fullWidth href="https://appwrite.io/pricing" external>
<Button
fullWidth
external
on:click={() => {
wizard.start(ChangeOrganizationTierCloud);
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'support_menu'
});
}}>
<span class="text">Get Premium support</span>
</Button>
{:else}
@@ -24,7 +24,6 @@
export let hideRequired = false;
export let disabled = false;
export let fullWidth = false;
export let fullWidthDrop = true;
export let autofocus = false;
export let interactiveOutput = false;
// stretch is used inside of a flex container to give the element flex:1
@@ -120,7 +119,6 @@
scrollable
placement="bottom-end"
position="static"
fullWidth={fullWidthDrop}
fixed>
<Label {required} {hideRequired} {optionalText} hide={!showLabel} for={id} {tooltip}>
{label}
+10 -1
View File
@@ -1,4 +1,5 @@
<script lang="ts">
import { trackEvent } from '$lib/actions/analytics';
import { getServiceLimit, type PlanServices } from '$lib/stores/billing';
import { wizard } from '$lib/stores/wizard';
import { isCloud } from '$lib/system';
@@ -10,6 +11,7 @@
export let service: PlanServices = null;
export let name = service;
export let total: number = null;
export let event: string = null;
let columns = 0;
@@ -34,7 +36,14 @@
<span class="u-flex u-gap-24 u-main-center u-cross-center">
<slot name="limit" {upgradeMethod} {limit}>
<span class="text">Upgrade your plan to add {name} to your organization</span>
<Button secondary on:click={upgradeMethod}>Upgrade plan</Button>
<Button
secondary
on:click={upgradeMethod}
on:click={() =>
trackEvent('click_organization_upgrade', {
from: 'button',
source: event ?? 'table_row_limit_reached'
})}>Upgrade plan</Button>
</slot>
</span>
</td>
+6 -2
View File
@@ -24,13 +24,17 @@ export function bytesToSize(value: number, unit: Size) {
return value / Math.pow(1024, index);
}
export function humanFileSize(bytes: number): {
export function humanFileSize(
bytes: number,
useBits = false
): {
value: string;
unit: Size;
} {
if (typeof bytes !== 'number') return { value: '0', unit: 'Bytes' };
const value = prettyBytes(bytes, {
locale: 'en'
locale: 'en',
bits: useBits
}).split(' ');
return {
+4 -4
View File
@@ -73,12 +73,12 @@ export function createTimeUnitPair(initialValue = 0) {
return { ...createValueUnitPair(initialValue, units), units };
}
export function createByteUnitPair(initialValue = 0) {
export function createByteUnitPair(initialValue = 0, base = 1024) {
const units: Unit[] = [
{ name: 'Bytes', value: 1 },
{ name: 'Kilobytes', value: 1024 },
{ name: 'Megabytes', value: 1024 ** 2 },
{ name: 'Gigabytes', value: 1024 ** 3 }
{ name: 'Kilobytes', value: base },
{ name: 'Megabytes', value: base ** 2 },
{ name: 'Gigabytes', value: base ** 3 }
];
return { ...createValueUnitPair(initialValue, units), units };
}
+13 -3
View File
@@ -18,6 +18,7 @@
import { ContainerButton } from '.';
import { Button } from '$lib/elements/forms';
import { BillingPlan } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
export let isFlex = true;
export let title: string;
@@ -92,8 +93,12 @@
<span class="text">
You've reached the {services} limit for the {tier} plan. <Button
link
on:click={upgradeMethod}>Upgrade</Button> your organization for additional
resources.
on:click={upgradeMethod}
on:click={() =>
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'inline_alert'
})}>Upgrade</Button> your organization for additional resources.
</span>
</Alert>
{/if}
@@ -124,7 +129,12 @@
{title.toLocaleLowerCase()} per project on the {tier} plan.
{#if $organization?.billingPlan === BillingPlan.STARTER}<Button
link
on:click={upgradeMethod}>Upgrade</Button>
on:click={upgradeMethod}
on:click={() =>
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'resource_limit_tag'
})}>Upgrade</Button>
for addtional {title.toLocaleLowerCase()}.
{/if}
</p>
+7 -1
View File
@@ -121,7 +121,13 @@
{#if isCloud && $organization?.billingPlan === BillingPlan.STARTER && !$page.url.pathname.startsWith('/console/account')}
<Button
disabled={$organization?.markedForDeletion}
on:click={() => wizard.start(ChangeOrganizationTierCloud)}>
on:click={() => {
wizard.start(ChangeOrganizationTierCloud);
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'top_nav'
});
}}>
Upgrade
</Button>
{/if}
+1 -1
View File
@@ -36,7 +36,7 @@
class={icon ? `icon-${icon}` : ''}
aria-hidden="true" />
</div>
<div class="alert-sticky-content">
<div class="alert-sticky-content" data-private>
{#if title}
<h4 class="alert-sticky-title">{title}</h4>
{/if}
@@ -88,6 +88,7 @@
org.$id,
$changeOrganizationTier.couponCode
);
trackEvent(Submit.CreditRedeem);
}
//Add budget
@@ -56,6 +56,12 @@
);
}
//Add coupon
if ($createOrganization?.couponCode) {
await sdk.forConsole.billing.addCredit(org.$id, $createOrganization.couponCode);
trackEvent(Submit.CreditRedeem);
}
//Add collaborators
if ($createOrganization?.collaborators?.length) {
$createOrganization.collaborators.forEach(async (collaborator) => {
@@ -110,7 +116,8 @@
paymentMethodId: null,
collaborators: [],
billingAddressId: null,
taxId: null
taxId: null,
couponCode: null
};
});
@@ -71,9 +71,10 @@
<p class="text">Appwrite credit will automatically be applied to your next invoice.</p>
<svelte:fragment slot="aside">
<div class="u-flex u-cross-center u-main-space-between">
<h4 class="body-text-1 u-bold">
Credit balance <span class="inline-tag">${balance}</span>
</h4>
<div class="u-flex u-gap-8 u-cross-center">
<h4 class="body-text-1 u-bold">Credit balance</h4>
<span class="inline-tag">${balance}</span>
</div>
{#if creditList?.total}
<Button secondary on:click={handleCredits}>
<span class="icon-plus" aria-hidden="true"></span>
@@ -91,7 +91,6 @@
label="Percentage (%) of budget cap"
placeholder="Select a percentage"
id="alerts"
fullWidthDrop={false}
{options}
bind:search
bind:value={selectedAlert}
@@ -8,6 +8,8 @@
import { addNotification } from '$lib/stores/notifications';
import { organization } from '$lib/stores/organization';
import { sdk } from '$lib/stores/sdk';
import { wizard } from '$lib/stores/wizard';
import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte';
import { onMount } from 'svelte';
let capActive = false;
@@ -92,7 +94,19 @@
</svelte:fragment>
<svelte:fragment slot="actions">
<Button disabled={$organization?.billingBudget === budget} submit>Update</Button>
{#if $organization?.billingPlan === BillingPlan.STARTER}
<Button
secondary
on:click={() => {
wizard.start(ChangeOrganizationTierCloud);
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'billing_budget_cap'
});
}}>Upgrade to Pro</Button>
{:else}
<Button disabled={$organization?.billingBudget === budget} submit>Update</Button>
{/if}
</svelte:fragment>
</CardGrid>
</Form>
@@ -15,6 +15,7 @@
import { abbreviateNumber, formatNumberWithCommas } from '$lib/helpers/numbers';
import { humanFileSize } from '$lib/helpers/sizeConvertion';
import { BillingPlan } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
let currentInvoice: Invoice;
const today = new Date();
@@ -136,7 +137,12 @@
</Button>
<Button
disabled={$organization?.markedForDeletion}
on:click={() => wizard.start(ChangeOrganizationTierCloud)}>
on:click={() => wizard.start(ChangeOrganizationTierCloud)}
on:click={() =>
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'billing_tab'
})}>
Upgrade
</Button>
</div>
@@ -145,7 +151,12 @@
<Button
text
disabled={$organization?.markedForDeletion}
on:click={() => wizard.start(ChangeOrganizationTierCloud)}>
on:click={() => wizard.start(ChangeOrganizationTierCloud)}
on:click={() =>
trackEvent('click_organization_plan_update', {
from: 'button',
source: 'billing_tab'
})}>
Change plan
</Button>
<Button
@@ -4,6 +4,7 @@
import type { Coupon } from '$lib/sdk/billing';
import { sdk } from '$lib/stores/sdk';
import { wizard } from '$lib/stores/wizard';
import { onMount } from 'svelte';
import { addCreditWizardStore } from '../store';
let coupon: string;
@@ -18,6 +19,7 @@
try {
const response = await sdk.forConsole.billing.getCoupon(coupon);
couponData = response;
$addCreditWizardStore.coupon = coupon;
coupon = null;
} catch (error) {
couponData.code = coupon;
@@ -26,6 +28,13 @@
throw new Error(error);
}
}
onMount(() => {
if ($addCreditWizardStore.coupon) {
coupon = $addCreditWizardStore.coupon;
validateCoupon();
}
});
</script>
<WizardStep beforeSubmit={validateCoupon}>
@@ -41,6 +50,6 @@
$addCreditWizardStore.coupon = e.detail.code;
}}
let:data>
<span>{data.code} has been successfully added</span>
<span>{data.code.toUpperCase()} has been successfully added</span>
</CouponInput>
</WizardStep>
@@ -14,6 +14,7 @@
import { goto } from '$app/navigation';
import { last } from '$lib/helpers/array';
import { BillingPlan } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
export let show = false;
const plan = $plansInfo?.get($organization.billingPlan);
@@ -89,13 +90,20 @@
on:click={() => {
show = false;
goto(`/console/organization-${$organization.$id}/usage`);
}}>View usage</Button>
}}>
View usage
</Button>
<Button
on:click={() => {
show = false;
wizard.start(ChangeOrganizationTierCloud);
}}>Upgrade plan</Button>
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'limit_reached_modal'
});
}}>
Upgrade plan
</Button>
</div>
</div>
</svelte:fragment>
@@ -70,7 +70,10 @@
<TableCellHead width={90} />
<TableCellHead width={30} />
</TableHeader>
<TableBody service="members" total={data.organizationMembers.total}>
<TableBody
service="members"
total={data.organizationMembers.total}
event="members_list">
{#each data.organizationMembers.memberships as member}
<TableRow>
<TableCell title="Name">
@@ -13,6 +13,7 @@
import { accumulateFromEndingTotal, total } from '$lib/layout/usage.svelte';
import type { OrganizationUsage } from '$lib/sdk/billing';
import { BillingPlan } from '$lib/constants';
import { trackEvent } from '$lib/actions/analytics';
export let data;
@@ -42,7 +43,14 @@
<Heading tag="h2" size="5">Usage</Heading>
{#if $organization?.billingPlan === BillingPlan.STARTER}
<Button on:click={() => wizard.start(ChangeOrganizationTierCloud)}>
<Button
on:click={() => {
wizard.start(ChangeOrganizationTierCloud);
trackEvent('click_organization_upgrade', {
from: 'button',
source: 'organization_usage'
});
}}>
<span class="text">Upgrade</span>
</Button>
{/if}
@@ -14,7 +14,7 @@
import { updateBucket } from './+page.svelte';
const service = getServiceLimit('fileSize');
const { value, unit, baseValue, units } = createByteUnitPair($bucket.maximumFileSize);
const { value, unit, baseValue, units } = createByteUnitPair($bucket.maximumFileSize, 1000);
const options = units.map((v) => ({ label: v.name, value: v.name }));
function updateMaxSize() {
@@ -36,7 +36,7 @@
<p class="text">Set the maximum file size allowed in the bucket.</p>
<svelte:fragment slot="aside">
{#if isCloud}
{@const size = humanFileSize(sizeToBytes(service, 'GB', 1024))}
{@const size = humanFileSize(sizeToBytes(service, 'MB', 1000))}
{@const plan = tierToPlan($organization?.billingPlan)}
<Alert type="info">
<p class="text">
@@ -6,6 +6,7 @@
import { createOrganization } from './store';
import type { AddressesList } from '$lib/sdk/billing';
import { RadioBoxes } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
let options = [
{
@@ -33,7 +34,9 @@
addressLine2 ? addressLine2 : undefined
);
$createOrganization.billingAddressId = response.$id;
trackEvent(Submit.BillingAddressCreate);
} catch (error) {
trackError(error, Submit.BillingAddressCreate);
throw new Error(error.message);
}
}
@@ -1,9 +1,11 @@
<script lang="ts">
import { Box, CreditCardBrandImage } from '$lib/components';
import { CouponInput } from '$lib/components/billing';
import { BillingPlan } from '$lib/constants';
import { Pill } from '$lib/elements';
import { toLocaleDate } from '$lib/helpers/date';
import { WizardStep } from '$lib/layout';
import type { Coupon } from '$lib/sdk/billing';
import { plansInfo } from '$lib/stores/billing';
import { sdk } from '$lib/stores/sdk';
import { createOrganization, createOrganizationFinalAction } from './store';
@@ -15,6 +17,13 @@
const today = new Date();
const billingPayDate = new Date(today.getTime() + 44 * 24 * 60 * 60 * 1000);
let coupon: string = null;
let couponData: Partial<Coupon> = {
code: null,
status: null,
credits: null
};
async function fetchCard() {
try {
const card = await sdk.forConsole.billing.getPaymentMethod(
@@ -77,6 +86,10 @@
{/if}
<Box class="u-margin-block-start-32 u-flex u-flex-vertical u-gap-16" radius="small">
{#if $createOrganization.billingPlan !== BillingPlan.STARTER}
<CouponInput
bind:coupon
bind:couponData
on:validation={(e) => ($createOrganization.couponCode = e.detail.code)} />
<span class="u-flex u-main-space-between">
<p class="text">{plan.name} plan</p>
<p class="text">${plan.price}</p>
@@ -85,11 +98,23 @@
<p class="text">Additional members ({collaboratorsNumber})</p>
<p class="text">${collaboratorPrice * collaboratorsNumber}</p>
</span>
{#if couponData?.status === 'active'}
<span class="u-flex u-main-space-between">
<p class="text">Credits applied ({couponData.credits})</p>
<p class="text">-${couponData.credits}</p>
</span>
{/if}
<div class="u-sep-block-start" />
{/if}
<span class="u-flex u-main-space-between">
<p class="text">Estimated total</p>
<p class="text">${totalExpences}</p>
<p class="text">
${couponData?.status === 'active'
? totalExpences - couponData.credits >= 0
? totalExpences - couponData.credits
: 0
: totalExpences}
</p>
</span>
{#if $createOrganization.billingPlan !== BillingPlan.STARTER}
@@ -15,6 +15,7 @@ export const createOrganization = writable<{
collaborators?: string[];
billingBudget?: number;
taxId?: string;
couponCode?: string;
}>({
id: null,
name: null,
@@ -22,5 +23,6 @@ export const createOrganization = writable<{
paymentMethodId: null,
collaborators: [],
billingAddressId: null,
taxId: null
taxId: null,
couponCode: null
});
@@ -7,6 +7,7 @@
import { organization } from '$lib/stores/organization';
import type { AddressesList } from '$lib/sdk/billing';
import { RadioBoxes } from '$lib/components';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
let options = [
{
@@ -34,7 +35,9 @@
addressLine2 ? addressLine2 : undefined
);
$changeOrganizationTier.billingAddressId = response.$id;
trackEvent(Submit.BillingAddressCreate);
} catch (error) {
trackError(error, Submit.BillingAddressCreate);
throw new Error(error.message);
}
}
@@ -97,11 +97,11 @@
{/if}
<Box class="u-margin-block-start-32 u-flex u-flex-vertical u-gap-16" radius="small">
<CouponInput
bind:coupon
bind:couponData
on:validation={(e) => ($changeOrganizationTier.couponCode = e.detail.code)} />
{#if $changeOrganizationTier.billingPlan !== BillingPlan.STARTER}
<CouponInput
bind:coupon
bind:couponData
on:validation={(e) => ($changeOrganizationTier.couponCode = e.detail.code)} />
<span class="u-flex u-main-space-between">
<p class="text">{plan.name} plan</p>
<p class="text">${plan.price}</p>