mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge pull request #1052 from appwrite/sync-main-cloud
sync: main <- cloud
This commit is contained in:
Generated
+569
-492
File diff suppressed because it is too large
Load Diff
+2
-2
@@ -51,8 +51,8 @@
|
||||
"@testing-library/user-event": "^14.4.3",
|
||||
"@types/deep-equal": "^1.0.1",
|
||||
"@types/prismjs": "^1.26.0",
|
||||
"@typescript-eslint/eslint-plugin": "^6.5.0",
|
||||
"@typescript-eslint/parser": "^6.5.0",
|
||||
"@typescript-eslint/eslint-plugin": "^7.7.0",
|
||||
"@typescript-eslint/parser": "^7.7.0",
|
||||
"@vitest/ui": "^1.2.1",
|
||||
"eslint": "^8.48.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
|
||||
+3
-2
@@ -2,10 +2,11 @@ import { AppwriteException } from '@appwrite.io/console';
|
||||
import type { HandleClientError } from '@sveltejs/kit';
|
||||
|
||||
export const handleError: HandleClientError = async ({ error, message, status }) => {
|
||||
if (error instanceof AppwriteException && error.code === 0) {
|
||||
status = undefined;
|
||||
if (error instanceof AppwriteException) {
|
||||
status = error.code === 0 ? undefined : error.code;
|
||||
message = error.message;
|
||||
}
|
||||
|
||||
return {
|
||||
message,
|
||||
status
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
<script lang="ts">
|
||||
import { base } from '$app/paths';
|
||||
import { page } from '$app/stores';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { HeaderAlert } from '$lib/layout';
|
||||
import { readOnly, tierToPlan } from '$lib/stores/billing';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte';
|
||||
</script>
|
||||
|
||||
{#if $organization?.$id && $organization?.billingPlan === BillingPlan.STARTER && $readOnly && !$page.url.pathname.includes('/console/account')}
|
||||
<HeaderAlert
|
||||
type="error"
|
||||
title={`${$organization.name} usage has reached the ${tierToPlan($organization.billingPlan).name} plan limit`}>
|
||||
<svelte:fragment>
|
||||
Usage for the <b>{$organization.name}</b> organization has reached the limits of the Starter
|
||||
plan. Consider upgrading to increase your resource usage.
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button
|
||||
href={`${base}/console/organization-${$organization?.$id}/usage`}
|
||||
text
|
||||
fullWidthMobile>
|
||||
<span class="text">View usage</span>
|
||||
</Button>
|
||||
<Button
|
||||
on:click={() => {
|
||||
wizard.start(ChangeOrganizationTierCloud);
|
||||
trackEvent('click_organization_upgrade', {
|
||||
from: 'button',
|
||||
source: 'limit_reached_banner'
|
||||
});
|
||||
}}
|
||||
secondary
|
||||
fullWidthMobile>
|
||||
<span class="text">Upgrade plan</span>
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</HeaderAlert>
|
||||
{/if}
|
||||
@@ -33,4 +33,9 @@
|
||||
<svelte:fragment slot="title">This payment method has expired</svelte:fragment>
|
||||
</Alert>
|
||||
{/if}
|
||||
{#if paymentMethod?.lastError}
|
||||
<Alert type="error" class="u-margin-block-start-16 u-width-full-line">
|
||||
{paymentMethod.lastError}
|
||||
</Alert>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -24,9 +24,6 @@
|
||||
import CreateOrganizationCloud from '$routes/console/createOrganizationCloud.svelte';
|
||||
import { Feedback } from '$lib/components/feedback';
|
||||
import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { showExcess } from '$routes/console/organization-[organization]/store';
|
||||
import { readOnly } from '$lib/stores/billing';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
|
||||
let showDropdown = false;
|
||||
@@ -105,17 +102,6 @@
|
||||
<svelte:component this={$page.data.breadcrumbs} />
|
||||
{/if}
|
||||
|
||||
{#if !$page.url.pathname.includes('/console/account') && $readOnly}
|
||||
<div style="min-inline-size: fit-content">
|
||||
<Pill danger button on:click={() => ($showExcess = true)}>
|
||||
<div>
|
||||
<span class="icon-exclamation-circle" aria-hidden="true" />
|
||||
<span>limit reached</span>
|
||||
</div>
|
||||
</Pill>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<div class="main-header-end">
|
||||
<nav class="u-flex is-only-desktop u-cross-center">
|
||||
{#if isCloud && $organization?.billingPlan === BillingPlan.STARTER && !$page.url.pathname.startsWith('/console/account')}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
export let steps: WizardStepsType;
|
||||
export let confirmExit = false;
|
||||
export let finalAction = 'Create';
|
||||
export let finalMethod: () => Promise<void> = null;
|
||||
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
@@ -98,11 +99,18 @@
|
||||
}
|
||||
} else {
|
||||
$wizard.nextDisabled = true;
|
||||
trackEvent('wizard_finish');
|
||||
dispatch('finish');
|
||||
setTimeout(() => {
|
||||
if (finalMethod) {
|
||||
await finalMethod();
|
||||
trackEvent('wizard_finish');
|
||||
$wizard.nextDisabled = false;
|
||||
}, 2000);
|
||||
} else {
|
||||
trackEvent('wizard_finish');
|
||||
dispatch('finish');
|
||||
|
||||
setTimeout(() => {
|
||||
$wizard.nextDisabled = false;
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (steps.get($wizard.step + 1)?.disabled) {
|
||||
|
||||
+46
-3
@@ -19,6 +19,7 @@ export type PaymentMethodData = {
|
||||
failed: boolean;
|
||||
name: string;
|
||||
mandateId?: string;
|
||||
lastError?: string;
|
||||
};
|
||||
|
||||
export type PaymentList = {
|
||||
@@ -48,6 +49,7 @@ export type Invoice = {
|
||||
rate: number;
|
||||
desc: string;
|
||||
}[];
|
||||
lastError?: string;
|
||||
};
|
||||
|
||||
export type InvoiceList = {
|
||||
@@ -92,11 +94,11 @@ export type Credit = {
|
||||
/**
|
||||
* Provided credit amount
|
||||
*/
|
||||
credits: number;
|
||||
total: number;
|
||||
/**
|
||||
* Remaining up credit amount
|
||||
* Remaining credit amount
|
||||
*/
|
||||
creditsRemaining: number;
|
||||
credits: number;
|
||||
/**
|
||||
* Credit expiration time in ISO 8601 format.
|
||||
*/
|
||||
@@ -259,6 +261,7 @@ export type Plan = {
|
||||
storage: AdditionalResource;
|
||||
users: AdditionalResource;
|
||||
};
|
||||
trialDays: number;
|
||||
};
|
||||
|
||||
export type PlansInfo = {
|
||||
@@ -743,6 +746,46 @@ export class Billing {
|
||||
);
|
||||
}
|
||||
|
||||
async getOrganizationPaymentMethod(
|
||||
organizationId: string,
|
||||
paymentMethodId: string
|
||||
): Promise<PaymentMethodData> {
|
||||
const path = `/organizations/${organizationId}/payment-methods/${paymentMethodId}`;
|
||||
const params = {
|
||||
organizationId,
|
||||
paymentMethodId
|
||||
};
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'GET',
|
||||
uri,
|
||||
{
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
async getOrganizationBillingAddress(
|
||||
organizationId: string,
|
||||
billingAddressId: string
|
||||
): Promise<Address> {
|
||||
const path = `/organizations/${organizationId}/billing-addresses/${billingAddressId}`;
|
||||
const params = {
|
||||
organizationId,
|
||||
billingAddressId
|
||||
};
|
||||
const uri = new URL(this.client.config.endpoint + path);
|
||||
return await this.client.call(
|
||||
'GET',
|
||||
uri,
|
||||
{
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
params
|
||||
);
|
||||
}
|
||||
|
||||
//ACCOUNT
|
||||
|
||||
async listPaymentMethods(queries: [] = []): Promise<PaymentList> {
|
||||
|
||||
+69
-14
@@ -23,6 +23,10 @@ import MarkedForDeletion from '$lib/components/billing/alerts/markedForDeletion.
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
|
||||
import MissingPaymentMethod from '$lib/components/billing/alerts/missingPaymentMethod.svelte';
|
||||
import LimitReached from '$lib/components/billing/alerts/limitReached.svelte';
|
||||
import { wizard } from './wizard';
|
||||
import ChangeOrganizationTierCloud from '$routes/console/changeOrganizationTierCloud.svelte';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
export type Tier = 'tier-0' | 'tier-1' | 'tier-2';
|
||||
|
||||
@@ -77,18 +81,18 @@ export function getServiceLimit(serviceId: PlanServices, tier: Tier = null): num
|
||||
}
|
||||
|
||||
export const failedInvoice = cachedStore<
|
||||
false | Invoice,
|
||||
Invoice,
|
||||
{
|
||||
load: (orgId: string) => Promise<void>;
|
||||
}
|
||||
>('failedInvoice', function ({ set }) {
|
||||
return {
|
||||
load: async (orgId) => {
|
||||
if (!isCloud) set(false);
|
||||
if (!isCloud) set(null);
|
||||
const invoices = await sdk.forConsole.billing.listInvoices(orgId);
|
||||
const failedInvoices = invoices.invoices.filter((i) => i.status === 'failed');
|
||||
// const failedInvoices = invoices.invoices;
|
||||
if (failedInvoices.length > 0) {
|
||||
if (failedInvoices?.length > 0) {
|
||||
const firstFailed = failedInvoices[0];
|
||||
const today = new Date();
|
||||
const thirtyDaysAgo = new Date(today.setDate(today.getDate() - 30));
|
||||
@@ -96,7 +100,7 @@ export const failedInvoice = cachedStore<
|
||||
if (failedDate < thirtyDaysAgo) {
|
||||
readOnly.set(true);
|
||||
}
|
||||
} else set(false);
|
||||
} else set(null);
|
||||
}
|
||||
};
|
||||
});
|
||||
@@ -179,26 +183,74 @@ export function calculateTrialDay(org: Organization) {
|
||||
}
|
||||
|
||||
export async function checkForUsageLimit(org: Organization) {
|
||||
if (org?.billingPlan !== BillingPlan.STARTER) {
|
||||
readOnly.set(false);
|
||||
return;
|
||||
}
|
||||
if (!org?.billingLimits) {
|
||||
readOnly.set(false);
|
||||
return;
|
||||
}
|
||||
const { bandwidth, documents, executions, storage, users } = org?.billingLimits ?? {};
|
||||
const resources = [
|
||||
{ value: bandwidth, name: 'bandwidth' },
|
||||
{ value: documents, name: 'documents' },
|
||||
{ value: executions, name: 'executions' },
|
||||
{ value: storage, name: 'storage' },
|
||||
{ value: users, name: 'users' }
|
||||
];
|
||||
|
||||
const members = await sdk.forConsole.teams.listMemberships(org.$id);
|
||||
const plan = get(plansInfo)?.get(org.billingPlan);
|
||||
const membersOverflow =
|
||||
members?.total > plan.members ? members.total - (plan.members || members.total) : 0;
|
||||
|
||||
if (
|
||||
bandwidth >= 100 ||
|
||||
documents >= 100 ||
|
||||
executions >= 100 ||
|
||||
storage >= 100 ||
|
||||
users >= 100 ||
|
||||
membersOverflow > 0
|
||||
) {
|
||||
if (resources.some((r) => r.value >= 100) || membersOverflow > 0) {
|
||||
readOnly.set(true);
|
||||
} else readOnly.set(false);
|
||||
headerAlert.add({
|
||||
id: 'limitReached',
|
||||
component: LimitReached,
|
||||
show: true,
|
||||
importance: 7
|
||||
});
|
||||
} else if (resources.some((r) => r.value >= 75)) {
|
||||
readOnly.set(false);
|
||||
const lastNotification = parseInt(localStorage.getItem('limitReachedNotification')) ?? 0;
|
||||
const now = new Date().getTime();
|
||||
if (now - lastNotification < 1000 * 60 * 60 * 24) return;
|
||||
|
||||
localStorage.setItem('limitReachedNotification', now.toString());
|
||||
let message = `<b>${org.name}</b> has reached <b>75%</b> of the Starter plan's ${resources.find((r) => r.value >= 75).name} limit. Upgrade to ensure there are no service disruptions.`;
|
||||
if (resources.filter((r) => r.value >= 75)?.length > 1) {
|
||||
message = `Usage for <b>${org.name}</b> has reached 75% of the Starter plan limit. Upgrade to ensure there are no service disruptions.`;
|
||||
}
|
||||
addNotification({
|
||||
type: 'warning',
|
||||
isHtml: true,
|
||||
timeout: 0,
|
||||
message,
|
||||
buttons: [
|
||||
{
|
||||
name: 'View usage',
|
||||
method: () => {
|
||||
goto(`${base}/console/organization-${org.$id}/usage`);
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'Upgrade plan',
|
||||
method: () => {
|
||||
wizard.start(ChangeOrganizationTierCloud);
|
||||
trackEvent('click_organization_upgrade', {
|
||||
from: 'button',
|
||||
source: 'limit_reached_notification'
|
||||
});
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
readOnly.set(false);
|
||||
}
|
||||
}
|
||||
|
||||
export async function checkPaymentAuthorizationRequired(org: Organization) {
|
||||
@@ -223,7 +275,10 @@ export async function checkPaymentAuthorizationRequired(org: Organization) {
|
||||
|
||||
export async function paymentExpired(org: Organization) {
|
||||
if (!org?.paymentMethodId) return;
|
||||
const payment = await sdk.forConsole.billing.getPaymentMethod(org.paymentMethodId);
|
||||
const payment = await sdk.forConsole.billing.getOrganizationPaymentMethod(
|
||||
org.$id,
|
||||
org.paymentMethodId
|
||||
);
|
||||
if (!payment?.expiryYear) return;
|
||||
const year = new Date().getFullYear();
|
||||
const month = new Date().getMonth();
|
||||
|
||||
@@ -956,6 +956,698 @@ export const marketplace: MarketplaceTemplate[] = [
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'text-generation-with-huggingface',
|
||||
name: 'Text generation',
|
||||
tagline: 'Generate text using the Hugging Face inference API.',
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 30,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/text-generation-with-huggingface'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/text-generation-with-huggingface">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'HUGGINGFACE_ACCESS_TOKEN',
|
||||
description: `Secret for sending requests to the Hugging Face API. <a class="u-bold" target="_blank" href="https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token">Learn more</a>.`,
|
||||
placeholder: 'hf_MUvn...',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'language-translation-with-huggingface',
|
||||
name: 'Language translation',
|
||||
tagline: 'Translate text using the Hugging Face inference API.',
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 30,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/language-translation-with-huggingface'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/language-translation-with-huggingface">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'HUGGINGFACE_ACCESS_TOKEN',
|
||||
description: `Secret for sending requests to the Hugging Face API. <a class="u-bold" target="_blank" href="https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token">Learn more</a>.`,
|
||||
placeholder: 'hf_MUvn...',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'image-classification-with-huggingface',
|
||||
name: 'Image classification',
|
||||
tagline: 'Classify images using the Hugging Face inference API.',
|
||||
permissions: ['any'],
|
||||
events: ['buckets.*.files.*.create'],
|
||||
cron: '',
|
||||
timeout: 15,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/image-classification-with-huggingface'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/image-classification-with-huggingface">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's Server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
value: 'ai',
|
||||
placeholder: 'ai',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
value: 'image_classification',
|
||||
placeholder: 'image_classification',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_BUCKET_ID',
|
||||
description: `The ID of the bucket where the images are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/storage/buckets">Learn more</a>.`,
|
||||
value: 'image_classification',
|
||||
placeholder: 'image_classification',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'HUGGINGFACE_ACCESS_TOKEN',
|
||||
description: `Secret for sending requests to the Hugging Face API. <a class="u-bold" target="_blank" href="https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token">Learn more</a>.`,
|
||||
placeholder: 'hf_MUvn...',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'object-detection-with-huggingface',
|
||||
name: 'Object detection',
|
||||
tagline: 'Detect objects in images using the Hugging Face inference API.',
|
||||
permissions: ['any'],
|
||||
events: ['buckets.*.files.*.create'],
|
||||
cron: '',
|
||||
timeout: 15,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/object-detection-with-huggingface'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/object-detection-with-huggingface">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
value: 'ai',
|
||||
placeholder: 'ai',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
value: 'object_detection',
|
||||
placeholder: 'object_detection',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_BUCKET_ID',
|
||||
description: `The ID of the bucket where the images are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/storage/buckets">Learn more</a>.`,
|
||||
value: 'object_detection',
|
||||
placeholder: 'object_detection',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'HUGGINGFACE_ACCESS_TOKEN',
|
||||
description: `Secret for sending requests to the Hugging Face API. <a class="u-bold" target="_blank" href="https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token">Learn more</a>.`,
|
||||
placeholder: 'hf_MUvn...',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'speech-recognition-with-huggingface',
|
||||
name: 'Speech recognition',
|
||||
tagline: 'Transcribe audio to text using the Hugging Face inference API.',
|
||||
permissions: ['any'],
|
||||
events: ['buckets.*.files.*.create'],
|
||||
cron: '',
|
||||
timeout: 15,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/speech-recognition-with-huggingface'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speech-recognition-with-huggingface">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
value: 'ai',
|
||||
placeholder: 'ai',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
value: 'speech_recognition',
|
||||
placeholder: 'speech_recognition',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_BUCKET_ID',
|
||||
description: `The ID of the bucket where audio is stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/storage/buckets">Learn more</a>.`,
|
||||
value: 'speech_recognition',
|
||||
placeholder: 'speech_recognition',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'HUGGINGFACE_ACCESS_TOKEN',
|
||||
description: `Secret for sending requests to the Hugging Face API. <a class="u-bold" target="_blank" href="https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token">Learn more</a>.`,
|
||||
placeholder: 'hf_MUvn...',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'text-to-speech-with-huggingface',
|
||||
name: 'Text to speech',
|
||||
tagline: 'Convert text to speech using the Hugging Face inference API.',
|
||||
permissions: ['any'],
|
||||
events: ['databases.*.collections.*.documents.*.create'],
|
||||
cron: '',
|
||||
timeout: 15,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install && npm run setup',
|
||||
'src/main.js',
|
||||
'node/text-to-speech-with-huggingface'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/text-to-speech-with-huggingface">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
value: 'ai',
|
||||
placeholder: 'ai',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
value: 'speech_recognition',
|
||||
placeholder: 'speech_recognition',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_BUCKET_ID',
|
||||
description: `The ID of the bucket where audio is stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/storage/buckets">Learn more</a>.`,
|
||||
value: 'speech_recognition',
|
||||
placeholder: 'speech_recognition',
|
||||
required: false,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'HUGGINGFACE_ACCESS_TOKEN',
|
||||
description: `Secret for sending requests to the Hugging Face API. <a class="u-bold" target="_blank" href="https://huggingface.co/docs/api-inference/en/quicktour#get-your-api-token">Learn more</a>.`,
|
||||
placeholder: 'hf_MUvn...',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'generate-with-replicate',
|
||||
name: 'Generate with Replicate',
|
||||
tagline: "Generate text, audio and images using Replicate's API.",
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 300,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-replicate'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-with-replicate">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'REPLICATE_API_KEY',
|
||||
description: `A unique key used to authenticate with the Replicate API. <a class="u-bold" target="_blank" href="https://replicate.com/docs/get-started/nodejs">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'chat-with-perplexity-ai',
|
||||
name: 'Chat with Perplexity AI',
|
||||
tagline: 'Create a chatbot using the Perplexity AI API.',
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 15,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/chat-with-perplexity-ai'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/chat-with-perplexity-ai">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'PERPLEXITY_API_KEY',
|
||||
description: `A unique key used to authenticate with the Perplexity API. <a class="u-bold" target="_blank" href="https://docs.perplexity.ai/docs/getting-started">Learn more</a>.`,
|
||||
placeholder: `pplex-68...999`,
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'PERPLEXITY_MAX_TOKENS',
|
||||
description: `The maximum number of tokens to generate. <a class="u-bold" target="_blank" href="https://docs.perplexity.ai/docs/getting-started">Learn more</a>.`,
|
||||
placeholder: `512`,
|
||||
required: false,
|
||||
type: 'number'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'generate-with-replicate',
|
||||
name: 'Generate with Replicate',
|
||||
tagline: "Generate text, audio and images using Replicate's API.",
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 300,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/generate-with-replicate'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/generate-with-replicate">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'REPLICATE_API_KEY',
|
||||
description: `A unique key used to authenticate with the Replicate API. <a class="u-bold" target="_blank" href="https://replicate.com/docs/get-started/nodejs">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'sync-with-pinecone',
|
||||
name: 'Sync with Pinecone',
|
||||
tagline: "Sync your Appwrite database with Pinecone's vector database.",
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 30,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/sync-with-pinecone'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/sync-with-pinecone">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'OPENAI_API_KEY',
|
||||
description: `A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. <a class="u-bold" target="_blank" href="https://platform.openai.com/docs/quickstart/add-your-api-key">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'sk-wzG...vcy',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'PINECONE_API_KEY',
|
||||
description: `A unique key used to authenticate with the Pinecone API. <a class="u-bold" target="_blank" href="https://docs.pinecone.io/guides/getting-started/authentication">Learn more</a>.`,
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'PINECONE_INDEX_NAME',
|
||||
description: `The name of the index in Pinecone. <a class="u-bold" target="_blank" href="https://docs.pinecone.io/guides/getting-started/create-index">Learn more</a>.`,
|
||||
placeholder: 'my-index',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the documents are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
placeholder: 'my-database',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the documents are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
placeholder: 'my-collection',
|
||||
required: true,
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'rag-with-langchain',
|
||||
name: 'RAG with LangChain',
|
||||
tagline: 'Generate text using a LangChain RAG model',
|
||||
permissions: ['any'],
|
||||
events: [],
|
||||
cron: '',
|
||||
timeout: 30,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/rag-with-langchain'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/rag-with-langchain">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'OPENAI_API_KEY',
|
||||
description: `A unique key used to authenticate with the OpenAI API. This is a paid service and you will be charged for each request made to the API. <a class="u-bold" target="_blank" href="https://platform.openai.com/docs/quickstart/add-your-api-key">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'sk-wzG...vcy',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'PINECONE_API_KEY',
|
||||
description: `A unique key used to authenticate with the Pinecone API. <a class="u-bold" target="_blank" href="https://docs.pinecone.io/guides/getting-started/authentication">Learn more</a>.`,
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'PINECONE_INDEX_NAME',
|
||||
description: `The name of the index in Pinecone. <a class="u-bold" target="_blank" href="https://docs.pinecone.io/guides/getting-started/create-index">Learn more</a>.`,
|
||||
placeholder: 'my-index',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the documents are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
placeholder: 'my-database',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the documents are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
placeholder: 'my-collection',
|
||||
required: true,
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
icon: 'icon-chip',
|
||||
id: 'speak-with-elevenlabs',
|
||||
name: 'Speak with ElevenLabs',
|
||||
tagline: 'Convert text to speech using the ElevenLabs API.',
|
||||
permissions: ['any'],
|
||||
cron: '',
|
||||
events: [],
|
||||
timeout: 15,
|
||||
usecases: ['AI'],
|
||||
runtimes: [
|
||||
...getRuntimes(
|
||||
TemplateRuntimes.NODE,
|
||||
'npm install',
|
||||
'src/main.js',
|
||||
'node/speak-with-elevenlabs'
|
||||
)
|
||||
],
|
||||
instructions: `For documentation and instructions check out <a target="_blank" rel="noopener noreferrer" class="link" href="https://github.com/appwrite/templates/tree/main/node/speak-with-elevenlabs">file</a>.`,
|
||||
vcsProvider: 'github',
|
||||
providerRepositoryId: 'templates',
|
||||
providerOwner: 'appwrite',
|
||||
providerBranch: 'main',
|
||||
variables: [
|
||||
{
|
||||
name: 'ELEVENLABS_API_KEY',
|
||||
description: `A unique key used to authenticate with the ElevenLabs API. <a class="u-bold" target="_blank" href="https://elevenlabs.io/docs/api-reference/getting-started">Learn more</a>.`,
|
||||
placeholder: 'd03xxxxxxxx26',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_API_KEY',
|
||||
description: `The API Key to authenticate against Appwrite's server APIs. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/advanced/platform/api-keys">Learn more</a>.`,
|
||||
value: '',
|
||||
placeholder: 'd1efb...aec35',
|
||||
required: true,
|
||||
type: 'password'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_ENDPOINT',
|
||||
description: `The URL endpoint of the Appwrite server. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/references#api">Learn more</a>.`,
|
||||
value: 'https://cloud.appwrite.io/v1',
|
||||
placeholder: 'https://cloud.appwrite.io/v1',
|
||||
required: false,
|
||||
type: 'url'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_DATABASE_ID',
|
||||
description: `The ID of the database where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/databases">Learn more</a>.`,
|
||||
placeholder: 'my-database',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_COLLECTION_ID',
|
||||
description: `The ID of the collection where the responses are stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/databases/collections">Learn more</a>.`,
|
||||
placeholder: 'my-collection',
|
||||
required: true,
|
||||
type: 'text'
|
||||
},
|
||||
{
|
||||
name: 'APPWRITE_BUCKET_ID',
|
||||
description: `The ID of the bucket where audio is stored. <a class="u-bold" target="_blank" href="https://appwrite.io/docs/products/storage/buckets">Learn more</a>.`,
|
||||
placeholder: 'generated_speech',
|
||||
required: true,
|
||||
type: 'text'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
|
||||
@@ -21,7 +21,8 @@
|
||||
paymentExpired,
|
||||
checkForMarkedForDeletion,
|
||||
checkForMandate,
|
||||
checkForMissingPaymentMethod
|
||||
checkForMissingPaymentMethod,
|
||||
plansInfo
|
||||
} from '$lib/stores/billing';
|
||||
import { goto } from '$app/navigation';
|
||||
import { CommandCenter, registerCommands, registerSearchers } from '$lib/commandCenter';
|
||||
@@ -37,8 +38,7 @@
|
||||
import { stripe } from '$lib/stores/stripe';
|
||||
import MobileSupportModal from './wizard/support/mobileSupportModal.svelte';
|
||||
import { showSupportModal } from './wizard/support/store';
|
||||
import ExcesLimitModal from './organization-[organization]/excesLimitModal.svelte';
|
||||
import { showExcess } from './organization-[organization]/store';
|
||||
|
||||
import UsageRates from './wizard/cloudOrganization/usageRates.svelte';
|
||||
import { activeHeaderAlert, consoleVariables } from './store';
|
||||
import { headerAlert } from '$lib/stores/headerAlert';
|
||||
@@ -261,11 +261,14 @@
|
||||
await checkForUsageLimit(org);
|
||||
checkForMarkedForDeletion(org);
|
||||
if (org?.billingPlan !== BillingPlan.STARTER) {
|
||||
calculateTrialDay(org);
|
||||
await paymentExpired(org);
|
||||
await checkPaymentAuthorizationRequired(org);
|
||||
await checkForMandate(org);
|
||||
if ($plansInfo.get(org.billingPlan)?.trialDays) {
|
||||
calculateTrialDay(org);
|
||||
}
|
||||
}
|
||||
$activeHeaderAlert = headerAlert.get();
|
||||
}
|
||||
});
|
||||
|
||||
@@ -316,9 +319,6 @@
|
||||
<MobileSupportModal bind:show={$showSupportModal}></MobileSupportModal>
|
||||
{/if}
|
||||
|
||||
{#if isCloud && $showExcess}
|
||||
<ExcesLimitModal bind:show={$showExcess}></ExcesLimitModal>
|
||||
{/if}
|
||||
{#if isCloud && $showUsageRatesModal}
|
||||
<UsageRates bind:show={$showUsageRatesModal} tier={$organization?.billingPlan} />
|
||||
{/if}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
import { Pill } from '$lib/elements';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import { daysLeftInTrial } from '$lib/stores/billing';
|
||||
import { daysLeftInTrial, plansInfo } from '$lib/stores/billing';
|
||||
import { tooltip } from '$lib/actions/tooltip';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
@@ -82,7 +82,7 @@
|
||||
<Pill>FREE</Pill>
|
||||
</div>
|
||||
{/if}
|
||||
{#if organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.STARTER}
|
||||
{#if organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.STARTER && $plansInfo.get(organization.billingPlan)?.trialDays}
|
||||
<div
|
||||
class="u-flex u-cross-center"
|
||||
use:tooltip={{
|
||||
|
||||
@@ -22,6 +22,9 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import RetryPaymentModal from './retryPaymentModal.svelte';
|
||||
import { selectedInvoice, showRetryModal } from './store';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
|
||||
export let data;
|
||||
|
||||
$: defaultPaymentMethod = $paymentMethods?.paymentMethods?.find(
|
||||
(method: PaymentMethodData) => method.$id === $organization?.paymentMethodId
|
||||
@@ -76,13 +79,27 @@
|
||||
|
||||
<Container>
|
||||
{#if $failedInvoice}
|
||||
<Alert type="error" class="common-section">
|
||||
<svelte:fragment slot="title">
|
||||
The scheduled payment for {$organization.name} failed
|
||||
</svelte:fragment>
|
||||
To avoid service disruptions in organization's your projects, please verify your payment
|
||||
details and update them if necessary.
|
||||
</Alert>
|
||||
{#if $failedInvoice?.lastError}
|
||||
<Alert type="error" class="common-section">
|
||||
The scheduled payment for {$organization.name} failed due to following error: {$failedInvoice.lastError}
|
||||
<svelte:fragment slot="buttons">
|
||||
<Button
|
||||
text
|
||||
on:click={() => {
|
||||
$selectedInvoice = $failedInvoice;
|
||||
$showRetryModal = true;
|
||||
}}>Try again</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
{:else}
|
||||
<Alert type="error" class="common-section">
|
||||
<svelte:fragment slot="title">
|
||||
The scheduled payment for {$organization.name} failed
|
||||
</svelte:fragment>
|
||||
To avoid service disruptions in organization's your projects, please verify your payment
|
||||
details and update them if necessary.
|
||||
</Alert>
|
||||
{/if}
|
||||
{/if}
|
||||
{#if defaultPaymentMethod?.failed && !backupPaymentMethod}
|
||||
<Alert type="error" class="common-section">
|
||||
@@ -95,9 +112,8 @@
|
||||
{/if}
|
||||
{#if $organization?.billingPlanDowngrade}
|
||||
<Alert type="info" class="common-section">
|
||||
Your organization will change to a Starter plan once your current billing cycle ends on {toLocaleDate(
|
||||
$organization.billingNextInvoiceDate
|
||||
)}.
|
||||
Your organization will change to a Starter plan once your current billing cycle ends and
|
||||
your invoice is paid on {toLocaleDate($organization.billingNextInvoiceDate)}.
|
||||
</Alert>
|
||||
{/if}
|
||||
<div class="common-section">
|
||||
@@ -106,7 +122,7 @@
|
||||
<PlanSummary />
|
||||
<PaymentHistory />
|
||||
<PaymentMethods />
|
||||
<BillingAddress />
|
||||
<BillingAddress billingAddress={data.billingAddress} />
|
||||
<TaxId />
|
||||
<BudgetCap />
|
||||
{#if $organization?.billingPlan !== BillingPlan.STARTER && !!$organization?.billingBudget}
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Address } from '$lib/sdk/billing';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import type { PageLoad } from './$types';
|
||||
|
||||
@@ -10,9 +12,19 @@ export const load: PageLoad = async ({ parent, depends }) => {
|
||||
depends(Dependencies.INVOICES);
|
||||
depends(Dependencies.ADDRESS);
|
||||
|
||||
let billingAddress: Address = null;
|
||||
const billingAddressId = (organization as Organization)?.billingAddressId;
|
||||
if (billingAddressId) {
|
||||
billingAddress = await sdk.forConsole.billing.getOrganizationBillingAddress(
|
||||
organization.$id,
|
||||
billingAddressId
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
paymentMethods: await sdk.forConsole.billing.listPaymentMethods(),
|
||||
addressList: await sdk.forConsole.billing.listAddresses(),
|
||||
aggregationList: await sdk.forConsole.billing.listAggregation(organization.$id)
|
||||
aggregationList: await sdk.forConsole.billing.listAggregation(organization.$id),
|
||||
billingAddress
|
||||
};
|
||||
};
|
||||
|
||||
@@ -56,10 +56,8 @@
|
||||
request();
|
||||
}
|
||||
|
||||
$: balance = creditList?.credits?.reduce(
|
||||
(acc: number, curr: Credit) => acc + curr.creditsRemaining,
|
||||
0
|
||||
);
|
||||
$: balance =
|
||||
creditList?.credits?.reduce((acc: number, curr: Credit) => acc + curr.credits, 0) ?? 0;
|
||||
|
||||
$: {
|
||||
if (reloadOnWizardClose && !$wizard.show) {
|
||||
@@ -115,7 +113,7 @@
|
||||
{toLocaleDate(credit.expiration)}
|
||||
</TableCellText>
|
||||
<TableCellText title="amount">
|
||||
{formatCurrency(credit.credits)}
|
||||
{formatCurrency(credit.total)}
|
||||
</TableCellText>
|
||||
</TableRow>
|
||||
{/each}
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
import DropListItem from '$lib/components/dropListItem.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import type { Address } from '$lib/sdk/billing';
|
||||
import { addressList } from '$lib/stores/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
@@ -13,6 +14,8 @@
|
||||
import EditAddressModal from '$routes/console/account/payments/editAddressModal.svelte';
|
||||
import ReplaceAddress from './replaceAddress.svelte';
|
||||
|
||||
export let billingAddress: Address = null;
|
||||
|
||||
let showDropdown = false;
|
||||
let showBillingAddressDropdown = false;
|
||||
let showCreate = false;
|
||||
@@ -40,10 +43,6 @@
|
||||
trackError(error, Submit.OrganizationBillingAddressUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
$: billingAddress = $addressList?.billingAddresses?.find(
|
||||
(address) => address.$id === $organization?.billingAddressId
|
||||
);
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
|
||||
@@ -58,11 +58,10 @@
|
||||
headerDivider={false}
|
||||
title="Unable to delete payment method">
|
||||
<p data-private>
|
||||
The {isBackup ? 'backup' : 'default'} payment method cannot be deleted as
|
||||
The {isBackup ? 'backup' : 'default'} payment method cannot be removed as
|
||||
<b>{$organization?.name}</b>
|
||||
has an upcoming invoice. To proceed with deletion, set a {isBackup
|
||||
? 'default'
|
||||
: 'backup'} or add a new {isBackup ? 'backup' : 'default'} payment method.
|
||||
has an upcoming invoice. To proceed, set a {isBackup ? 'default' : 'backup'} or add a new
|
||||
{isBackup ? 'backup' : 'default'} payment method.
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
@@ -76,13 +75,13 @@
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
headerDivider={false}
|
||||
title="Delete payment method">
|
||||
title="Remove payment method">
|
||||
<p data-private>
|
||||
Are you sure you want to delete the payment method from <b>{$organization?.name}</b>?
|
||||
Are you sure you want to remove the payment method from <b>{$organization?.name}</b>?
|
||||
</p>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button secondary submit>Delete</Button>
|
||||
<Button secondary submit>Remove</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
{/if}
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { formatCurrency } from '$lib/helpers/numbers';
|
||||
import type { InvoiceList } from '$lib/sdk/billing';
|
||||
import type { Invoice, InvoiceList } from '$lib/sdk/billing';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { VARS } from '$lib/system';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
@@ -31,6 +31,7 @@
|
||||
import { selectedInvoice, showRetryModal } from './store';
|
||||
|
||||
let showDropdown = [];
|
||||
let showFailedError = false;
|
||||
|
||||
let offset = 0;
|
||||
let invoiceList: InvoiceList = {
|
||||
@@ -51,6 +52,11 @@
|
||||
]);
|
||||
}
|
||||
|
||||
function retryPayment(invoice: Invoice) {
|
||||
$selectedInvoice = invoice;
|
||||
$showRetryModal = true;
|
||||
}
|
||||
|
||||
$: if (offset !== null) {
|
||||
request();
|
||||
}
|
||||
@@ -82,14 +88,45 @@
|
||||
</TableCellText>
|
||||
|
||||
<TableCell title="status">
|
||||
<Pill
|
||||
danger={status === 'overdue' ||
|
||||
status === 'failed' ||
|
||||
status === 'requires_authentication'}
|
||||
success={status === 'paid' || status === 'succeeded'}
|
||||
warning={status === 'pending'}>
|
||||
{status === 'requires_authentication' ? 'failed' : status}
|
||||
</Pill>
|
||||
{#if invoice?.lastError}
|
||||
<DropList bind:show={showFailedError}>
|
||||
<Pill
|
||||
danger={status === 'overdue' ||
|
||||
status === 'failed' ||
|
||||
status === 'requires_authentication'}
|
||||
success={status === 'paid' ||
|
||||
status === 'succeeded'}
|
||||
warning={status === 'pending'}
|
||||
on:click={() => (showFailedError = true)}
|
||||
button>
|
||||
{status === 'requires_authentication'
|
||||
? 'failed'
|
||||
: status}
|
||||
</Pill>
|
||||
<svelte:fragment slot="list">
|
||||
<li>
|
||||
The scheduled payment has failed. <Button
|
||||
link
|
||||
on:click={() => {
|
||||
retryPayment(invoice);
|
||||
showFailedError = false;
|
||||
}}>Try again</Button
|
||||
>.
|
||||
</li>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{:else}
|
||||
<Pill
|
||||
danger={status === 'overdue' ||
|
||||
status === 'failed' ||
|
||||
status === 'requires_authentication'}
|
||||
success={status === 'paid' || status === 'succeeded'}
|
||||
warning={status === 'pending'}>
|
||||
{status === 'requires_authentication'
|
||||
? 'failed'
|
||||
: status}
|
||||
</Pill>
|
||||
{/if}
|
||||
</TableCell>
|
||||
<TableCellText title="due">
|
||||
{formatCurrency(invoice.amount)}
|
||||
@@ -132,8 +169,7 @@
|
||||
<DropListItem
|
||||
icon="refresh"
|
||||
on:click={() => {
|
||||
$selectedInvoice = invoice;
|
||||
$showRetryModal = true;
|
||||
retryPayment(invoice);
|
||||
showDropdown[i] = !showDropdown[i];
|
||||
trackEvent(`click_retry_payment`, {
|
||||
from: 'button',
|
||||
|
||||
@@ -77,13 +77,13 @@
|
||||
|
||||
$: if ($organization?.backupPaymentMethodId) {
|
||||
sdk.forConsole.billing
|
||||
.getPaymentMethod($organization.backupPaymentMethodId)
|
||||
.getOrganizationPaymentMethod($organization.$id, $organization.backupPaymentMethodId)
|
||||
.then((res) => (backupPaymentMethod = res));
|
||||
}
|
||||
|
||||
$: if ($organization?.paymentMethodId) {
|
||||
sdk.forConsole.billing
|
||||
.getPaymentMethod($organization.paymentMethodId)
|
||||
.getOrganizationPaymentMethod($organization.$id, $organization.paymentMethodId)
|
||||
.then((res) => (defaultPaymentMethod = res));
|
||||
}
|
||||
|
||||
@@ -137,7 +137,7 @@
|
||||
showDelete = true;
|
||||
showDropdown = false;
|
||||
}}>
|
||||
Delete
|
||||
Remove
|
||||
</DropListItem>
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
|
||||
@@ -29,7 +29,9 @@
|
||||
|
||||
$: currentPlan = $plansInfo?.get($organization?.billingPlan);
|
||||
$: extraUsage = currentInvoice?.amount - currentPlan?.price;
|
||||
$: isTrial = new Date($organization?.billingStartDate).getTime() - today.getTime() > 0;
|
||||
$: isTrial =
|
||||
new Date($organization?.billingStartDate).getTime() - today.getTime() > 0 &&
|
||||
$plansInfo.get($organization.billingPlan)?.trialDays;
|
||||
</script>
|
||||
|
||||
{#if $organization}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { submitStripeCard } from '$lib/stores/stripe';
|
||||
import { initializeStripe, isStripeInitialized, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { onMount } from 'svelte';
|
||||
import type { PaymentList } from '$lib/sdk/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -24,10 +24,18 @@
|
||||
|
||||
if (!$organization.paymentMethodId && !$organization.backupPaymentMethodId) {
|
||||
selectedPaymentMethodId = methods?.total ? methods.paymentMethods[0].$id : null;
|
||||
} else {
|
||||
selectedPaymentMethodId = isBackup
|
||||
? $organization.backupPaymentMethodId
|
||||
: $organization.paymentMethodId;
|
||||
// If the selected payment method does not belong to the current user, select the first one.
|
||||
if (
|
||||
methods?.total &&
|
||||
!methods.paymentMethods.some((method) => method.$id === selectedPaymentMethodId)
|
||||
) {
|
||||
selectedPaymentMethodId = methods.paymentMethods[0].$id;
|
||||
}
|
||||
}
|
||||
selectedPaymentMethodId = isBackup
|
||||
? $organization.backupPaymentMethodId
|
||||
: $organization.paymentMethodId;
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
@@ -82,6 +90,14 @@
|
||||
|
||||
$: filteredMethods = methods?.paymentMethods.filter((method) => !!method?.last4);
|
||||
|
||||
$: if (selectedPaymentMethodId === null && !$isStripeInitialized) {
|
||||
initializeStripe();
|
||||
}
|
||||
|
||||
$: if (selectedPaymentMethodId) {
|
||||
isStripeInitialized.set(false);
|
||||
}
|
||||
|
||||
$: if (!show) {
|
||||
selectedPaymentMethodId = null;
|
||||
}
|
||||
|
||||
@@ -30,8 +30,21 @@
|
||||
let paymentMethodId: string;
|
||||
let setAsDefault = false;
|
||||
|
||||
onMount(() => {
|
||||
paymentMethodId = $organization.paymentMethodId;
|
||||
onMount(async () => {
|
||||
if (!$organization.paymentMethodId && !$organization.backupPaymentMethodId) {
|
||||
paymentMethodId = $paymentMethods?.total ? $paymentMethods.paymentMethods[0].$id : null;
|
||||
} else {
|
||||
paymentMethodId = $organization.paymentMethodId
|
||||
? $organization.paymentMethodId
|
||||
: $organization.backupPaymentMethodId;
|
||||
// If the selected payment method does not belong to the current user, select the first one.
|
||||
if (
|
||||
$paymentMethods?.total &&
|
||||
!$paymentMethods.paymentMethods.some((method) => method.$id === paymentMethodId)
|
||||
) {
|
||||
paymentMethodId = $paymentMethods.paymentMethods[0].$id;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function handleSubmit() {
|
||||
|
||||
@@ -68,4 +68,4 @@
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Create project" steps={stepsComponents} on:finish={create} on:exit={onFinish} />
|
||||
<Wizard title="Create project" steps={stepsComponents} finalMethod={create} on:exit={onFinish} />
|
||||
|
||||
@@ -17,7 +17,13 @@
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { isTabSelected } from '$lib/helpers/load';
|
||||
import { Cover } from '$lib/layout';
|
||||
import { daysLeftInTrial, getServiceLimit, readOnly, tierToPlan } from '$lib/stores/billing';
|
||||
import {
|
||||
daysLeftInTrial,
|
||||
getServiceLimit,
|
||||
plansInfo,
|
||||
readOnly,
|
||||
tierToPlan
|
||||
} from '$lib/stores/billing';
|
||||
import {
|
||||
members,
|
||||
newMemberModal,
|
||||
@@ -105,7 +111,7 @@
|
||||
{#if isCloud && $organization?.billingPlan === BillingPlan.STARTER}
|
||||
<Pill>FREE</Pill>
|
||||
{/if}
|
||||
{#if isCloud && $organization?.billingTrialStartDate && $daysLeftInTrial > 0 && $organization.billingPlan !== BillingPlan.STARTER}
|
||||
{#if isCloud && $organization?.billingTrialStartDate && $daysLeftInTrial > 0 && $organization.billingPlan !== BillingPlan.STARTER && $plansInfo.get($organization.billingPlan)?.trialDays}
|
||||
<div
|
||||
class="u-flex u-cross-center"
|
||||
use:tooltip={{
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { derived, writable } from 'svelte/store';
|
||||
import { derived } from 'svelte/store';
|
||||
|
||||
export const showExcess = writable<boolean>(false);
|
||||
export const projects = derived(page, ($page) => $page.data?.projects as Models.ProjectList);
|
||||
|
||||
@@ -107,7 +107,8 @@
|
||||
<Heading tag="h6" size="7">Bandwidth</Heading>
|
||||
|
||||
<p class="text">
|
||||
Calculated for all bandwidth used across all projects in your organization.
|
||||
Calculated for all bandwidth used across all projects in your organization. Resets at
|
||||
the start of each billing cycle.
|
||||
</p>
|
||||
|
||||
<svelte:fragment slot="aside">
|
||||
|
||||
+3
-6
@@ -24,20 +24,17 @@
|
||||
|
||||
const sdkCreateVariable = async (key: string, value: string) => {
|
||||
await sdk.forProject.functions.createVariable($func.$id, key, value);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
await Promise.all([invalidate(Dependencies.VARIABLES), invalidate(Dependencies.FUNCTION)]);
|
||||
};
|
||||
|
||||
const sdkUpdateVariable = async (variableId: string, key: string, value: string) => {
|
||||
await sdk.forProject.functions.updateVariable($func.$id, variableId, key, value);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
await Promise.all([invalidate(Dependencies.VARIABLES), invalidate(Dependencies.FUNCTION)]);
|
||||
};
|
||||
|
||||
const sdkDeleteVariable = async (variableId: string) => {
|
||||
await sdk.forProject.functions.deleteVariable($func.$id, variableId);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
await Promise.all([invalidate(Dependencies.VARIABLES), invalidate(Dependencies.FUNCTION)]);
|
||||
};
|
||||
</script>
|
||||
|
||||
|
||||
@@ -2,10 +2,11 @@ import { page } from '$app/stores';
|
||||
import { derived } from 'svelte/store';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export const runtimesList = derived(
|
||||
page,
|
||||
async ($page) => (await $page.data.runtimesList) as Models.RuntimeList
|
||||
);
|
||||
export const runtimesList = derived(page, async ($page) => {
|
||||
const { runtimes } = (await $page.data.runtimesList) as Models.RuntimeList;
|
||||
const filtered = runtimes.filter((runtime) => !runtime.$id.startsWith('python-ml'));
|
||||
return { runtimes: filtered, total: filtered.length };
|
||||
});
|
||||
|
||||
export const baseRuntimesList = derived(runtimesList, async ($runtimesList) => {
|
||||
const baseRuntimes = new Map<string, Models.Runtime>();
|
||||
|
||||
@@ -104,7 +104,10 @@
|
||||
</div>
|
||||
<CardGrid>
|
||||
<Heading tag="h4" size="7">Bandwidth</Heading>
|
||||
<p class="text">Calculated for all bandwidth used in your project.</p>
|
||||
<p class="text">
|
||||
Calculated for all bandwidth used across your project. Resets at the start of each
|
||||
billing cycle.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
{#if network}
|
||||
{@const humanized = humanFileSize(total(network))}
|
||||
|
||||
+2
-2
@@ -29,7 +29,7 @@
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
await uploader.uploadFile(
|
||||
uploader.uploadFile(
|
||||
bucketId,
|
||||
$createFile.id ?? ID.unique(),
|
||||
$createFile.files[0],
|
||||
@@ -42,7 +42,7 @@
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `File has been uploaded`
|
||||
message: `File upload in progress`
|
||||
});
|
||||
trackEvent(Submit.FileCreate, {
|
||||
customId: !!$createFile.id
|
||||
|
||||
@@ -1,105 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Button, FormList, InputFile } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Modal, CustomId, Heading, Alert } from '$lib/components';
|
||||
import { createEventDispatcher } from 'svelte';
|
||||
import { page } from '$app/stores';
|
||||
import { uploader } from '$lib/stores/uploader';
|
||||
import { bucket } from './store';
|
||||
import { Permissions } from '$lib/components/permissions';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
|
||||
export let showCreate = false;
|
||||
|
||||
const bucketId = $page.params.bucket;
|
||||
const dispatch = createEventDispatcher();
|
||||
|
||||
let files: FileList;
|
||||
let permissions: string[] = [];
|
||||
let id: string = null;
|
||||
let error: string;
|
||||
let showCustomId = false;
|
||||
|
||||
async function create() {
|
||||
try {
|
||||
showCreate = false;
|
||||
await uploader.uploadFile(bucketId, id ?? ID.unique(), files[0], permissions);
|
||||
files = null;
|
||||
showCustomId = false;
|
||||
dispatch('created');
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `File has been uploaded`
|
||||
});
|
||||
trackEvent(Submit.FileCreate, {
|
||||
customId: !!id
|
||||
});
|
||||
} catch (e) {
|
||||
error = e.message;
|
||||
trackError(e, Submit.FileCreate);
|
||||
}
|
||||
}
|
||||
|
||||
$: if (!showCreate) {
|
||||
id = files = error = null;
|
||||
permissions = [];
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Create file" size="big" {error} bind:show={showCreate} onSubmit={create}>
|
||||
<FormList>
|
||||
<div>
|
||||
<InputFile
|
||||
bind:files
|
||||
allowedFileExtensions={$bucket.allowedFileExtensions}
|
||||
maxSize={$bucket.maximumFileSize} />
|
||||
</div>
|
||||
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
<Pill button on:click={() => (showCustomId = !showCustomId)}>
|
||||
<span class="icon-pencil" aria-hidden="true" /><span class="text">
|
||||
File ID
|
||||
</span>
|
||||
</Pill>
|
||||
</div>
|
||||
{:else}
|
||||
<CustomId bind:show={showCustomId} name="File" bind:id />
|
||||
{/if}
|
||||
<Heading tag="h6" size="7">Permissions</Heading>
|
||||
<p class="text">
|
||||
Choose who can access your buckets and files. For more information, visit our
|
||||
<a
|
||||
href="https://appwrite.io/docs/advanced/platform/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Permissions guide
|
||||
</a>.
|
||||
</p>
|
||||
{#if $bucket.fileSecurity}
|
||||
<div class="common-section">
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title">File security enabled</svelte:fragment>
|
||||
Users will be able to access this file if they have been granted
|
||||
<b>either File or Bucket permissions</b>.
|
||||
</Alert>
|
||||
</div>
|
||||
<div class="common-section">
|
||||
<Permissions bind:permissions />
|
||||
</div>
|
||||
{:else}
|
||||
<Alert type="info">
|
||||
<svelte:fragment slot="title">File security disabled</svelte:fragment>
|
||||
If you want to assign file permissions, navigate to Bucket settings and enable file security.
|
||||
Otherwise, only Bucket permissions will be used.
|
||||
</Alert>
|
||||
{/if}
|
||||
</FormList>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button secondary on:click={() => (showCreate = false)}>Cancel</Button>
|
||||
<Button submit>Create</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
@@ -138,9 +138,11 @@
|
||||
selectedVar = null;
|
||||
showPromoteModal = false;
|
||||
|
||||
await invalidate(Dependencies.FUNCTION);
|
||||
await invalidate(Dependencies.VARIABLES);
|
||||
await invalidate(Dependencies.PROJECT_VARIABLES);
|
||||
await Promise.all([
|
||||
invalidate(Dependencies.FUNCTION),
|
||||
invalidate(Dependencies.VARIABLES),
|
||||
invalidate(Dependencies.PROJECT_VARIABLES)
|
||||
]);
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
|
||||
@@ -48,12 +48,18 @@
|
||||
$: if ($createOrganization.billingPlan === BillingPlan.STARTER) {
|
||||
$createOrganizationFinalAction = 'Create organization';
|
||||
}
|
||||
|
||||
$: if (plan?.trialDays) {
|
||||
$createOrganizationFinalAction = 'Start trial';
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep beforeSubmit={handleBefore}>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Confirm the details of your new organization and start your free trial.
|
||||
Confirm the details of your new organization{!plan.trialDays
|
||||
? '.'
|
||||
: ' and start your free trial.'}
|
||||
</svelte:fragment>
|
||||
|
||||
<p class="body-text-1 u-bold">Organization name</p>
|
||||
@@ -123,9 +129,9 @@
|
||||
{#if $createOrganization.billingPlan !== BillingPlan.STARTER}
|
||||
<p class="text u-margin-block-start-16">
|
||||
This amount, and any additional usage fees, will be charged on a recurring 30-day
|
||||
billing cycle after your trial period ends on <b
|
||||
>{toLocaleDate(billingPayDate.toString())}</b
|
||||
>.
|
||||
billing cycle{!plan.trialDays
|
||||
? ''
|
||||
: ` after your trial period ends on ${toLocaleDate(billingPayDate.toString())}`}.
|
||||
</p>
|
||||
{/if}
|
||||
</Box>
|
||||
|
||||
@@ -108,7 +108,9 @@
|
||||
{tierPro.description}
|
||||
</p>
|
||||
</div>
|
||||
<Pill>14 DAY FREE TRIAL</Pill>
|
||||
{#if proPlan?.trialDays}
|
||||
<Pill>14 DAY FREE TRIAL</Pill>
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
</LabelCard>
|
||||
</li>
|
||||
@@ -131,7 +133,6 @@
|
||||
</p>
|
||||
</div>
|
||||
<div class:u-opacity-50={disabled}>
|
||||
<!-- <Pill>14 DAY FREE TRIAL</Pill> -->
|
||||
<Pill disabled>COMING SOON</Pill>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
import { initializeStripe, isStripeInitialized, submitStripeCard } from '$lib/stores/stripe';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { toLocaleDate } from '$lib/helpers/date';
|
||||
import { showUsageRatesModal } from '$lib/stores/billing';
|
||||
import { plansInfo, showUsageRatesModal } from '$lib/stores/billing';
|
||||
import { PaymentBoxes } from '$lib/components/billing';
|
||||
|
||||
const today = new Date();
|
||||
@@ -74,9 +74,11 @@
|
||||
<WizardStep beforeSubmit={handleSubmit}>
|
||||
<svelte:fragment slot="title">Payment details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Add a payment method to your organization. You will not be charged until your trial ends on <b
|
||||
>{toLocaleDate(billingPayDate.toString())}</b
|
||||
>.
|
||||
Add a payment method to your organization. {#if $plansInfo.get($createOrganization.billingPlan)?.trialDays}You
|
||||
will not be charged until your trial ends on <b
|
||||
>{toLocaleDate(billingPayDate.toString())}</b
|
||||
>.
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
|
||||
<FormList class="u-margin-block-start-8">
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
|
||||
import type { Tier } from '$lib/stores/billing';
|
||||
import { type Tier } from '$lib/stores/billing';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const createOrgSteps = writable<WizardStepsType>(new Map());
|
||||
export const createOrganizationFinalAction = writable<string>('Start trial');
|
||||
export const createOrganizationFinalAction = writable<string>('Create');
|
||||
|
||||
export const createOrganization = writable<{
|
||||
id?: string;
|
||||
|
||||
@@ -161,8 +161,8 @@
|
||||
<div class:u-opacity-50={disabled}>
|
||||
{#if $organization.billingPlan === BillingPlan.PRO}
|
||||
<Pill disabled>CURRENT PLAN</Pill>
|
||||
{:else}
|
||||
<Pill>14 DAY FREE TRIAL</Pill>
|
||||
{:else if proPlan?.trialDays}
|
||||
<Pill>{proPlan?.trialDays} DAY FREE TRIAL</Pill>
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -49,6 +49,10 @@
|
||||
$: if (!$isUpgrade) {
|
||||
$changeOrganizationFinalAction = 'Confirm plan change';
|
||||
}
|
||||
|
||||
$: if (plan?.trialDays) {
|
||||
$changeOrganizationFinalAction = 'Start trial';
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if downgradeToStarter}
|
||||
@@ -77,7 +81,11 @@
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Confirm the details of your new organization and start your free trial.
|
||||
Confirm the details of your new organization{!$plansInfo.get(
|
||||
$changeOrganizationTier.billingPlan
|
||||
)?.trialDays
|
||||
? '.'
|
||||
: ' and start your free trial.'}
|
||||
</svelte:fragment>
|
||||
|
||||
<p class="body-text-1 u-bold">Organization name</p>
|
||||
@@ -144,9 +152,9 @@
|
||||
|
||||
<p class="text u-margin-block-start-16">
|
||||
This amount, and any additional usage fees, will be charged on a recurring 30-day
|
||||
billing cycle after your trial period ends on <b
|
||||
>{toLocaleDate(billingPayDate.toString())}</b
|
||||
>.
|
||||
billing cycle{!$plansInfo.get($changeOrganizationTier.billingPlan)?.trialDays
|
||||
? ''
|
||||
: ` after your trial period ends on ${toLocaleDate(billingPayDate.toString())}`}.
|
||||
</p>
|
||||
</Box>
|
||||
</WizardStep>
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
|
||||
import type { Tier } from '$lib/stores/billing';
|
||||
import { type Tier } from '$lib/stores/billing';
|
||||
import { writable } from 'svelte/store';
|
||||
|
||||
export const changeTierSteps = writable<WizardStepsType>(new Map());
|
||||
export const isUpgrade = writable<boolean>(false);
|
||||
export const changeOrganizationFinalAction = writable<string>('Start trial');
|
||||
export const changeOrganizationFinalAction = writable<string>('Create');
|
||||
|
||||
export const changeOrganizationTier = writable<{
|
||||
billingPlan: Tier;
|
||||
|
||||
Reference in New Issue
Block a user