Merge branch 'dev-1.4.x' of github.com:appwrite/console into billing

This commit is contained in:
Arman
2023-12-05 17:43:41 +01:00
15 changed files with 8046 additions and 163 deletions
+9 -9
View File
@@ -7,8 +7,8 @@
"name": "@appwrite/console",
"dependencies": {
"@appwrite.io/console": "^0.3.0",
"@appwrite.io/pink": "0.1.0",
"@appwrite.io/pink-icons": "^0.1.0",
"@appwrite.io/pink": "0.2.0",
"@appwrite.io/pink-icons": "0.2.0",
"@popperjs/core": "^2.11.8",
"@sentry/svelte": "^7.66.0",
"@sentry/tracing": "^7.66.0",
@@ -168,19 +168,19 @@
}
},
"node_modules/@appwrite.io/pink": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink/-/pink-0.1.0.tgz",
"integrity": "sha512-z+4wiTH2PBMrbxvXiNjGQY0hs6ziCbKSb2rK0O/2M3aa9CHN/dBDSTpWCcED8G9BWDkeS5XMVZrQbyrdqMVc/Q==",
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink/-/pink-0.2.0.tgz",
"integrity": "sha512-ni6O/yPiWkCYQ68PNcbmus6QrPpIEl7xmzKHU2dkV2L19RVio8thtBtT9ckJJ8uBMGsczASWs0fUqOxIvBmtNw==",
"dependencies": {
"@appwrite.io/pink-icons": "0.1.0",
"@appwrite.io/pink-icons": "0.2.0",
"normalize.css": "^8.0.1",
"the-new-css-reset": "^1.11.2"
}
},
"node_modules/@appwrite.io/pink-icons": {
"version": "0.1.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-0.1.0.tgz",
"integrity": "sha512-Y5DmvosdCoBD0oyqKV+ncbXTzQuhX0WOe4N7uonMaz1NaFm3ODaIb/CF77hV2vg86oOQ+f+BirdhfPsBue2pNA=="
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@appwrite.io/pink-icons/-/pink-icons-0.2.0.tgz",
"integrity": "sha512-wThBUGdvpwT0q2fcUapiugfBiB4RFDvTlW6p+U7Dol61mmCpNteFSIoBPiPJzN5015n1v4UEWL/rdOia4CWdBQ=="
},
"node_modules/@babel/code-frame": {
"version": "7.23.4",
+2 -2
View File
@@ -19,8 +19,8 @@
},
"dependencies": {
"@appwrite.io/console": "^0.3.0",
"@appwrite.io/pink": "0.1.0",
"@appwrite.io/pink-icons": "^0.1.0",
"@appwrite.io/pink": "0.2.0",
"@appwrite.io/pink-icons": "0.2.0",
"@popperjs/core": "^2.11.8",
"@sentry/svelte": "^7.66.0",
"@sentry/tracing": "^7.66.0",
+7647
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -143,7 +143,7 @@
.drop-arrow::before {
content: '';
transform: rotate(45deg);
background: hsl(var(--color-neutral-200));
background: hsl(var(--color-neutral-85));
body.theme-light & {
background: hsl(var(--color-neutral-10));
+2
View File
@@ -69,3 +69,5 @@ export { default as FloatingActionBar } from './floatingActionBar.svelte';
export { default as LoadingDots } from './loadingDots.svelte';
export { default as FakeModal } from './fakeModal.svelte';
export { default as RadioBoxes } from './radioBoxes.svelte';
export { default as ModalWrapper } from './modalWrapper.svelte';
export { default as ModalSideCol } from './modalSideCol.svelte';
+66 -130
View File
@@ -1,6 +1,5 @@
<script lang="ts">
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import { Alert } from '$lib/components';
import { Alert, ModalWrapper } from '$lib/components';
import { trackEvent } from '$lib/actions/analytics';
import { Form } from '$lib/elements/forms';
import { disableCommands } from '$lib/commandCenter';
@@ -18,61 +17,8 @@
export let title = '';
export let description = '';
let dialog: HTMLDialogElement;
let alert: HTMLElement;
const dispatch = createEventDispatcher();
onMount(() => {
if (show) openModal();
});
onDestroy(() => {
if (show) closeModal();
});
function handleBLur(event: MouseEvent) {
if (event.target === dialog) {
trackEvent('click_close_modal', {
from: 'backdrop'
});
closeModal();
}
}
function openModal() {
if (dialog && !dialog.open) {
dialog.showModal();
document.documentElement.classList.add('u-overflow-hidden');
}
}
function closeModal() {
if (closable) {
if (dialog && dialog.open) {
dispatch('close');
dialog.close();
show = false;
document.documentElement.classList.remove('u-overflow-hidden');
}
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
event.preventDefault();
trackEvent('click_close_modal', {
from: 'escape'
});
closeModal();
}
}
$: if (show) {
openModal();
} else {
closeModal();
}
$: $disableCommands(show);
$: if (error) {
@@ -80,82 +26,72 @@
}
</script>
<svelte:window on:mousedown={handleBLur} on:keydown={handleKeydown} />
<dialog
class="modal"
class:is-small={size === 'small'}
class:is-big={size === 'big'}
class:is-separate-header={headerDivider}
bind:this={dialog}
on:cancel|preventDefault>
{#if show}
<Form isModal {onSubmit}>
<header class="modal-header">
<div class="u-flex u-main-space-between u-cross-center u-gap-16">
<div class="u-flex u-cross-center u-gap-16">
{#if icon}
<div
class="avatar is-medium"
class:is-success={state === 'success'}
class:is-warning={state === 'warning'}
class:is-danger={state === 'error'}
class:is-info={state === 'info'}>
<span class={`icon-${icon}`} aria-hidden="true" />
</div>
{/if}
<h4 class="modal-title heading-level-5">
<slot name="title">
{title}
</slot>
</h4>
</div>
{#if closable}
<button
type="button"
class="button is-text is-only-icon"
style="--button-size:1.5rem;"
aria-label="Close Modal"
title="Close Modal"
on:click={() =>
trackEvent('click_close_modal', {
from: 'button'
})}
on:click={closeModal}>
<span class="icon-x" aria-hidden="true" />
</button>
<ModalWrapper bind:show {size} {headerDivider} let:close>
<Form isModal {onSubmit}>
<header class="modal-header">
<div class="u-flex u-main-space-between u-cross-center u-gap-16">
<div class="u-flex u-cross-center u-gap-16">
{#if icon}
<div
class="avatar is-medium"
class:is-success={state === 'success'}
class:is-warning={state === 'warning'}
class:is-danger={state === 'error'}
class:is-info={state === 'info'}>
<span class={`icon-${icon}`} aria-hidden="true" />
</div>
{/if}
</div>
<p class="u-margin-block-start-4">
<slot name="description">
{description}
</slot>
</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>
{#if $$slots.footer}
<div class="modal-footer">
<div class="u-flex u-main-end u-gap-16">
<slot name="footer" />
</div>
<h4 class="modal-title heading-level-5">
<slot name="title">
{title}
</slot>
</h4>
</div>
{#if closable}
<button
type="button"
class="button is-text is-only-icon"
style="--button-size:1.5rem;"
aria-label="Close Modal"
title="Close Modal"
on:click={() =>
trackEvent('click_close_modal', {
from: 'button'
})}
on:click={close}>
<span class="icon-x" aria-hidden="true" />
</button>
{/if}
</div>
<p class="u-margin-block-start-4">
<slot name="description">
{description}
</slot>
</p>
</header>
<div class="modal-content">
{#if error}
<div bind:this={alert}>
<Alert
dismissible
type="warning"
on:dismiss={() => {
error = null;
}}>
{error}
</Alert>
</div>
{/if}
</Form>
{/if}
</dialog>
<slot />
</div>
{#if $$slots.footer}
<div class="modal-footer">
<div class="u-flex u-main-end u-gap-16">
<slot name="footer" />
</div>
</div>
{/if}
</Form>
</ModalWrapper>
+67
View File
@@ -0,0 +1,67 @@
<script lang="ts">
import { ModalWrapper } from '$lib/components';
import { trackEvent } from '$lib/actions/analytics';
export let show = false;
export let title = '';
export let description = '';
export let style = '';
</script>
<ModalWrapper bind:show let:close {style}>
<div class="grid-1-1 u-width-full-line mk-grid">
<div class="mk-grid-item-1 u-width-full-line">
<slot name="side" />
</div>
<div class="u-padding-32 mk-grid-item-2">
<header class="modal-header">
<div class="u-flex u-main-space-between u-gap-16">
<h4 class="modal-title heading-level-5">
<slot name="title">
{title}
</slot>
</h4>
<button
type="button"
class="button is-text is-only-icon"
style="--button-size:1.5rem;"
aria-label="Close Modal"
title="Close Modal"
on:click={() =>
trackEvent('click_close_modal', {
from: 'button'
})}
on:click={close}>
<span class="icon-x" aria-hidden="true" />
</button>
</div>
<p class="u-margin-block-start-4">
<slot name="description">
{description}
</slot>
</p>
</header>
<div class="modal-content">
<slot />
</div>
</div>
</div>
</ModalWrapper>
<style lang="scss">
.mk-grid {
overflow: hidden;
}
@media screen and (max-width: 768px) {
.mk-grid {
&-item-1 {
order: 2;
max-height: 16rem;
}
&-item-2 {
order: 1;
}
}
}
</style>
+82
View File
@@ -0,0 +1,82 @@
<script lang="ts">
import { createEventDispatcher, onDestroy, onMount } from 'svelte';
import { trackEvent } from '$lib/actions/analytics';
import { disableCommands } from '$lib/commandCenter';
export let show = false;
export let size: 'small' | 'big' = null;
export let closable = true;
export let headerDivider = true;
export let style = '';
let dialog: HTMLDialogElement;
const dispatch = createEventDispatcher();
onMount(() => {
if (show) openModal();
});
onDestroy(() => {
if (show) closeModal();
});
function handleBLur(event: MouseEvent) {
if (event.target === dialog) {
trackEvent('click_close_modal', {
from: 'backdrop'
});
closeModal();
}
}
function openModal() {
if (dialog && !dialog.open) {
dialog.showModal();
document.documentElement.classList.add('u-overflow-hidden');
}
}
function closeModal() {
if (closable) {
if (dialog && dialog.open) {
dispatch('close');
dialog.close();
show = false;
document.documentElement.classList.remove('u-overflow-hidden');
}
}
}
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') {
event.preventDefault();
trackEvent('click_close_modal', {
from: 'escape'
});
closeModal();
}
}
$: if (show) {
openModal();
} else {
closeModal();
}
$: $disableCommands(show);
</script>
<svelte:window on:mousedown={handleBLur} on:keydown={handleKeydown} />
<dialog
class="modal"
class:is-small={size === 'small'}
class:is-big={size === 'big'}
class:is-separate-header={headerDivider}
{style}
bind:this={dialog}
on:cancel|preventDefault>
{#if show}
<slot close={closeModal} />
{/if}
</dialog>
+19 -17
View File
@@ -93,25 +93,27 @@
<div
class="git-container u-position-relative"
class:u-margin-block-start-24={isSelfHosted && !isVcsEnabled}>
<div
class:u-hide={!isSelfHosted || isVcsEnabled}
class="overlay u-flex-vertical u-position-absolute u-height-100-percent u-width-full-line u-z-index-1 card u-text-center">
{#if isSelfHosted && !isVcsEnabled}
<div
class="u-flex-vertical u-height-100-percent u-main-center u-cross-center u-gap-16">
<Heading size="7" tag="h6"
>Configure your self-hosted instance to connect to Git</Heading>
<p>
Connect your function to a Git repository or use a pre-made template<br />after
configuring your self-hosted instance. Learn more in our
<a
href="https://appwrite.io/docs/advanced/self-hosting/functions#git"
target="_blank"
rel="noopener noreferrer"
class="link">documentation</a
>.
</p>
class="overlay u-flex-vertical u-position-absolute u-height-100-percent u-width-full-line u-z-index-1 card u-text-center">
<div
class="u-flex-vertical u-height-100-percent u-main-center u-cross-center u-gap-16">
<Heading size="7" tag="h6">
Configure your self-hosted instance to connect to Git
</Heading>
<p>
Connect your function to a Git repository or use a pre-made template<br />after
configuring your self-hosted instance. Learn more in our
<a
href="https://appwrite.io/docs/advanced/self-hosting/functions#git"
target="_blank"
rel="noopener noreferrer"
class="link">documentation</a
>.
</p>
</div>
</div>
</div>
{/if}
<div class="grid-1-1 u-gap-24">
<div class="card u-cross-child-start u-height-100-percent">
<Heading size="6" tag="h6">Connect Git repository</Heading>
@@ -0,0 +1,91 @@
<script lang="ts">
import { Box, ModalSideCol } from '$lib/components';
import SideLight from './side-light.png';
import SideDark from './side-dark.png';
import { app } from '$lib/stores/app';
import { Button } from '$lib/elements/forms';
import { trackEvent } from '$lib/actions/analytics';
export let show = false;
</script>
<ModalSideCol bind:show title="Get ready for Appwrite Pro" style="max-width: min(53rem,95%)">
<svelte:fragment slot="side">
{#if $app.themeInUse === 'dark'}
<img
src={SideDark}
style="object-fit: cover; object-position: 30% 100%; height: 100%; width: 100%; aspect-ratio: 1 / 1;"
alt="appwrite pro"
aria-hidden="true"
width="376" />
{:else}
<img
src={SideLight}
style="object-fit: cover; object-position: 30% 100%; height: 100%; width: 100%; aspect-ratio: 1 / 1;"
alt="appwrite pro"
aria-hidden="true"
width="376" />
{/if}
</svelte:fragment>
<div class="u-flex u-flex-vertical u-gap-16">
<p class="text">
Appwrite will enable billing before the end of the year, which means access to our long
awaited Pro plan. It also means you will be limited to <b
>one free organization per account</b
>.
</p>
<p class="text">
Consider upgrading and take advantage of the extra resources that come with the Pro
plan. You can also use our Migrations service to self-host some of your projects
instead. Learn more in our <a
class="link"
href="https://appwrite.io/docs/advanced/migrations"
target="_blank"
rel="noopener noreferrer"
on:click={() =>
trackEvent('click_open_website', {
source: 'pre_billing_release_modal',
destination: 'docs_migration'
})}>documentation</a
>.
</p>
</div>
<Box>
<b class="body-text-1">Pro - $15 per member per billing period</b>
<ul class="list u-flex-vertical u-gap-8 u-margin-block-start-24">
<li class="u-flex u-gap-8 u-cross-center">
<span class="icon-check-circle" aria-hidden="true" />
<span class="text">Unlimited org. members</span>
</li>
<li class="u-flex u-gap-8 u-cross-center">
<span class="icon-check-circle" aria-hidden="true" />
<span class="text">300GB bandwidth</span>
</li>
<li class="u-flex u-gap-8 u-cross-center">
<span class="icon-check-circle" aria-hidden="true" />
<span class="text">200,000 monthly active users</span>
</li>
<li class="u-flex u-gap-8 u-cross-center">
<span class="icon-check-circle" aria-hidden="true" />
<span class="text">Email support</span>
</li>
<li class="u-flex u-gap-8 u-cross-center">
<span class="icon-check-circle" aria-hidden="true" />
<span class="text">And more</span>
</li>
</ul>
<Button
fullWidth
class="u-margin-block-start-24"
href="https://appwrite.io/pricing"
on:click={() =>
trackEvent('click_open_website', {
source: 'pre_billing_release_modal',
destination: 'pricing'
})}>
Learn more about Pro
</Button>
</Box>
</ModalSideCol>
Binary file not shown.

After

Width:  |  Height:  |  Size: 619 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 507 KiB

+50 -3
View File
@@ -22,9 +22,7 @@
} from '$lib/stores/billing';
import { diffDays, toLocaleDate } from '$lib/helpers/date';
import { base } from '$app/paths';
import { goto } from '$app/navigation';
import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels';
import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers';
@@ -33,7 +31,6 @@
import { openMigrationWizard } from './(migration-wizard)';
import { project } from './project-[project]/store';
import { feedback } from '$lib/stores/feedback';
import { consoleVariables } from './store';
import { VARS, hasStripePublicKey, isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { loadStripe } from '@stripe/stripe-js';
@@ -44,6 +41,8 @@
import { showExcess } from './organization-[organization]/store';
import UsageRates from './wizard/cloudOrganization/usageRates.svelte';
import { Button } from '$lib/elements/forms';
import { consoleVariables, showPrereleaseModal } from './store';
import PreReleaseModal from './(billing-modal)/preReleaseModal.svelte';
function kebabToSentenceCase(str: string) {
return str
@@ -243,6 +242,10 @@
onMount(async () => {
loading.set(false);
if (isCloud && !$page.url.pathname.includes('/console/onboarding')) {
checkForPreReleaseProModal();
}
setInterval(() => {
checkForFeedback(INTERVAL);
}, INTERVAL);
@@ -374,6 +377,47 @@
});
}
}
function checkForPreReleaseProModal() {
const modalTime = localStorage.getItem('preReleaseProModal');
const notificationTime = localStorage.getItem('preReleaseProNotification');
const now = Date.now();
// show the modal if it was never shown
if (!modalTime) {
localStorage.setItem('preReleaseProModal', Date.now().toString());
localStorage.setItem('preReleaseProNotification', Date.now().toString());
showPrereleaseModal.set(true);
} else {
const interval = 5 * 24 * 60 * 60 * 1000;
const sinceLastModal = now - parseInt(modalTime);
// show the modal if it was shown more than 5 days ago
if (sinceLastModal > interval) {
localStorage.setItem('preReleaseProModal', Date.now().toString());
localStorage.setItem('preReleaseProNotification', Date.now().toString());
showPrereleaseModal.set(true);
}
//if the modal has been shown more than 24 ago and the notification has not been shown for 24 hours
else if (
sinceLastModal > 24 * 60 * 60 * 1000 &&
now - (notificationTime ? parseInt(notificationTime) : 0) > 24 * 60 * 60 * 1000
) {
localStorage.setItem('preReleaseProNotification', Date.now().toString());
addNotification({
type: 'warning',
timeout: 10000,
message:
'Appwrite Pro is coming soon, which means you will be limited to one free organization per account. To avoid service disruptions in your projects, consider upgrading to Pro.',
buttons: [
{
name: 'Learn more',
method: () => {
window.open('https://appwrite.io/pricing', '_blank');
}
}
]
});
}
}
}
$: if (!$log.show) {
$log.data = null;
@@ -457,3 +501,6 @@
{#if isCloud && $showUsageRatesModal}
<UsageRates bind:show={$showUsageRatesModal} tier={$organization?.billingPlan} />
{/if}
{#if isCloud && $showPrereleaseModal && !$page.url.pathname.includes('/console/onboarding')}
<PreReleaseModal bind:show={$showPrereleaseModal} />
{/if}
@@ -177,6 +177,13 @@
<style lang="scss">
@import '@appwrite.io/pink/src/abstract/variables/_devices.scss';
.card {
background: #fbfbfc;
:global(.theme-dark) & {
background: #19191c !important;
}
}
@media #{$break2open} {
.card-header {
background-image: var(--url);
+3 -1
View File
@@ -1,9 +1,11 @@
import { page } from '$app/stores';
import type { Models } from '@appwrite.io/console';
import { derived } from 'svelte/store';
import { derived, writable } from 'svelte/store';
export const version = derived(page, ($page) => $page.data.version as string | null);
export const consoleVariables = derived(
page,
($page) => $page.data.consoleVariables as Models.ConsoleVariables
);
export const showPrereleaseModal = writable(false);