mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch '1.5.x' of github.com:appwrite/console into feat-new-array-filters
This commit is contained in:
Generated
+4
-4
@@ -6,7 +6,7 @@
|
||||
"": {
|
||||
"name": "@appwrite/console",
|
||||
"dependencies": {
|
||||
"@appwrite.io/console": "^0.6.0-rc.12",
|
||||
"@appwrite.io/console": "^0.6.0-rc.14",
|
||||
"@appwrite.io/pink": "0.4.0",
|
||||
"@appwrite.io/pink-icons": "0.4.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
@@ -158,9 +158,9 @@
|
||||
"integrity": "sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg=="
|
||||
},
|
||||
"node_modules/@appwrite.io/console": {
|
||||
"version": "0.6.0-rc.12",
|
||||
"resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.6.0-rc.12.tgz",
|
||||
"integrity": "sha512-pDov75TCkBmJdUPrWuzRW7uM28HYqiMFtVFrUoavDd/lMPBS7RnPXL4sBHsGY8AUldHE3oBcGVPkNoQfEAgLlw==",
|
||||
"version": "0.6.0-rc.14",
|
||||
"resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-0.6.0-rc.14.tgz",
|
||||
"integrity": "sha512-7Od0RuGRF01YUCWGTAvlTvgUG7sUCVHG9ig11OKz9jjBxE91BVcWsmqGWiXw+xb4HhF2jiyHd9ZU3sKAo0IC3w==",
|
||||
"dependencies": {
|
||||
"cross-fetch": "3.1.5",
|
||||
"isomorphic-form-data": "2.0.0"
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
"e2e": "playwright test tests/e2e"
|
||||
},
|
||||
"dependencies": {
|
||||
"@appwrite.io/console": "^0.6.0-rc.12",
|
||||
"@appwrite.io/console": "^0.6.0-rc.14",
|
||||
"@appwrite.io/pink": "0.4.0",
|
||||
"@appwrite.io/pink-icons": "0.4.0",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
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;
|
||||
message = error.message;
|
||||
}
|
||||
return {
|
||||
message,
|
||||
status
|
||||
};
|
||||
};
|
||||
@@ -301,7 +301,7 @@ export enum Submit {
|
||||
MessagingTopicCreate = 'submit_messaging_topic_create',
|
||||
MessagingTopicDelete = 'submit_messaging_topic_delete',
|
||||
MessagingTopicUpdateName = 'submit_messaging_topic_update_name',
|
||||
MessagingTopicUpdateDescription = 'submit_messaging_topic_update_description',
|
||||
MessagingTopicUpdatePermissions = 'submit_messaging_topic_update_permissions',
|
||||
MessagingTopicSubscriberAdd = 'submit_messaging_topic_subscriber_add',
|
||||
MessagingTopicSubscriberDelete = 'submit_messaging_topic_subscriber_delete'
|
||||
}
|
||||
|
||||
@@ -1,18 +1,39 @@
|
||||
<script lang="ts">
|
||||
export let href: string;
|
||||
export let href: string = null;
|
||||
export let external: boolean = false;
|
||||
</script>
|
||||
|
||||
<li class="clickable-list-item">
|
||||
<a {href} class="clickable-list-button" on:click>
|
||||
{#if $$slots.default}
|
||||
<h5 class="clickable-list-title u-trim-1">
|
||||
<slot />
|
||||
</h5>
|
||||
{/if}
|
||||
{#if $$slots.desc}
|
||||
<div class="clickable-list-desc">
|
||||
<p class="text u-margin-block-start-8"><slot name="desc" /></p>
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
{#if href}
|
||||
<a
|
||||
{href}
|
||||
class="clickable-list-button"
|
||||
target={external ? '_blank' : ''}
|
||||
rel={external ? 'noopener noreferrer' : ''}
|
||||
on:click>
|
||||
{#if $$slots.default}
|
||||
<h5 class="clickable-list-title u-trim-1">
|
||||
<slot />
|
||||
</h5>
|
||||
{/if}
|
||||
{#if $$slots.desc}
|
||||
<div class="clickable-list-desc">
|
||||
<p class="text u-margin-block-start-8"><slot name="desc" /></p>
|
||||
</div>
|
||||
{/if}
|
||||
</a>
|
||||
{:else}
|
||||
<button class="clickable-list-button u-width-full-line" on:click>
|
||||
{#if $$slots.default}
|
||||
<h5 class="clickable-list-title u-trim-1">
|
||||
<slot />
|
||||
</h5>
|
||||
{/if}
|
||||
{#if $$slots.desc}
|
||||
<div class="clickable-list-desc">
|
||||
<p class="text u-margin-block-start-8"><slot name="desc" /></p>
|
||||
</div>
|
||||
{/if}
|
||||
</button>
|
||||
{/if}
|
||||
</li>
|
||||
|
||||
@@ -3,10 +3,11 @@
|
||||
|
||||
export let withIndentation = false;
|
||||
export let open = false;
|
||||
export let disabled = false;
|
||||
</script>
|
||||
|
||||
<li class="collapsible-item">
|
||||
<details class="collapsible-wrapper" {open}>
|
||||
<details class="collapsible-wrapper" class:is-disabled={disabled} bind:open>
|
||||
<!-- svelte-ignore a11y-no-redundant-roles -->
|
||||
<summary
|
||||
class="collapsible-button u-position-relative"
|
||||
@@ -16,13 +17,18 @@
|
||||
tabindex="0">
|
||||
<slot name="beforetitle" />
|
||||
<div>
|
||||
<span class="text"><slot name="title" /></span>
|
||||
<span class="text" class:u-color-text-disabled={disabled}
|
||||
><slot name="title" /></span>
|
||||
{#if $$slots.subtitle}
|
||||
<span class="collapsible-button-optional"><slot name="subtitle" /></span>
|
||||
<span class="collapsible-button-optional" class:u-color-text-disabled={disabled}
|
||||
><slot name="subtitle" /></span>
|
||||
{/if}
|
||||
</div>
|
||||
<div class="icon">
|
||||
<span class="icon-cheveron-down" aria-hidden="true" />
|
||||
<span
|
||||
class="icon-cheveron-down u-font-size-20"
|
||||
class:u-color-text-disabled={disabled}
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
</summary>
|
||||
<div
|
||||
@@ -33,3 +39,24 @@
|
||||
</div>
|
||||
</details>
|
||||
</li>
|
||||
|
||||
<style lang="scss">
|
||||
// TODO: remove once pink is updated
|
||||
.collapsible-item {
|
||||
.collapsible-wrapper.is-disabled {
|
||||
&[open] .icon-cheveron-down {
|
||||
rotate: unset;
|
||||
}
|
||||
|
||||
cursor: not-allowed;
|
||||
* {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
.collapsible {
|
||||
&-content {
|
||||
display: none !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
<script lang="ts">
|
||||
import { app } from '$lib/stores/app';
|
||||
|
||||
export let darkSrc: string;
|
||||
export let lightSrc: string;
|
||||
export let alt: string;
|
||||
</script>
|
||||
|
||||
<a
|
||||
href={$app.themeInUse === 'dark' ? darkSrc : lightSrc}
|
||||
class="file-preview is-with-image"
|
||||
style="inline-size: 100%; block-size: 100%;"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="open file in new window">
|
||||
<div class="file-preview-image">
|
||||
<img src={$app.themeInUse === 'dark' ? darkSrc : lightSrc} {alt} />
|
||||
</div>
|
||||
<div class="file-preview-content">
|
||||
<div class="avatar">
|
||||
<span class="icon-external-link" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
@@ -72,3 +72,4 @@ 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';
|
||||
export { default as ImagePreview } from './imagePreview.svelte';
|
||||
|
||||
@@ -16,16 +16,13 @@
|
||||
|
||||
<svelte:window on:resize={throttle(onResize, 250)} />
|
||||
|
||||
<span class={`text ${alternativeTrim ? 'u-trim-1' : 'u-trim'}`} bind:this={container}>
|
||||
{#if showTooltip}
|
||||
<span
|
||||
use:tooltip={{
|
||||
content: container.innerText,
|
||||
maxWidth: '30rem'
|
||||
}}>
|
||||
<slot />
|
||||
</span>
|
||||
{:else}
|
||||
<span><slot /></span>
|
||||
{/if}
|
||||
<span
|
||||
class={`text ${alternativeTrim ? 'u-trim-1' : 'u-trim'}`}
|
||||
bind:this={container}
|
||||
use:tooltip={{
|
||||
disabled: !showTooltip,
|
||||
content: container?.innerText ?? undefined,
|
||||
maxWidth: '30rem'
|
||||
}}>
|
||||
<span><slot /></span>
|
||||
</span>
|
||||
|
||||
@@ -13,6 +13,8 @@
|
||||
|
||||
let element: HTMLOListElement;
|
||||
|
||||
let autoSubmitted = false;
|
||||
|
||||
const {
|
||||
elements: { root, input }
|
||||
} = createPinInput({
|
||||
@@ -20,14 +22,27 @@
|
||||
defaultValue: value.split(''),
|
||||
onValueChange: ({ next }) => {
|
||||
value = next.join('');
|
||||
|
||||
if (value.length === 6 && !autoSubmitted) {
|
||||
autoSubmitted = true;
|
||||
const firstInputElement = element.querySelector('input');
|
||||
firstInputElement?.form.requestSubmit();
|
||||
}
|
||||
|
||||
return next;
|
||||
}
|
||||
});
|
||||
|
||||
onMount(() => {
|
||||
if (element && autofocus) {
|
||||
element.querySelector('input')?.focus();
|
||||
}
|
||||
const interval = setInterval(() => {
|
||||
const input = element.querySelector('input');
|
||||
if (element) {
|
||||
if (input && autofocus) {
|
||||
input.focus();
|
||||
}
|
||||
clearInterval(interval);
|
||||
}
|
||||
}, 10);
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep beforeSubmit={createDomain}>
|
||||
<svelte:fragment slot="title">Add function domain</svelte:fragment>
|
||||
<svelte:fragment slot="title">Domain</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Use your self-owned domain as the endpoint of your Appwrite API. <a
|
||||
href="https://appwrite.io/docs/advanced/platform/custom-domains"
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import type { Client, Models, Query } from '@appwrite.io/console';
|
||||
import type { Client, Models } from '@appwrite.io/console';
|
||||
import type { Organization } from '../stores/organization';
|
||||
import type { PaymentMethod } from '@stripe/stripe-js';
|
||||
import type { Tier } from '$lib/stores/billing';
|
||||
@@ -849,7 +849,7 @@ export class Billing {
|
||||
);
|
||||
}
|
||||
|
||||
async listAddresses(queries: Query[] = []): Promise<AddressesList> {
|
||||
async listAddresses(queries: string[] = []): Promise<AddressesList> {
|
||||
const path = `/account/billing-addresses`;
|
||||
const params = {
|
||||
queries
|
||||
|
||||
+19
-17
@@ -35,21 +35,23 @@ export const createMigrationFormStore = () => {
|
||||
};
|
||||
};
|
||||
|
||||
const resources = [
|
||||
'user',
|
||||
'team',
|
||||
'membership',
|
||||
'file',
|
||||
'bucket',
|
||||
'function',
|
||||
'envVar',
|
||||
'deployment',
|
||||
'database',
|
||||
'collection',
|
||||
'index',
|
||||
'attribute',
|
||||
'document'
|
||||
] as const;
|
||||
export const ResourcesFriendly = {
|
||||
user: { singular: 'User', plural: 'Users' },
|
||||
team: { singular: 'Team', plural: 'Teams' },
|
||||
membership: { singular: 'Membership', plural: 'Memberships' },
|
||||
file: { singular: 'File', plural: 'Files' },
|
||||
bucket: { singular: 'Bucket', plural: 'Buckets' },
|
||||
function: { singular: 'Function', plural: 'Functions' },
|
||||
'environment variable': { singular: 'Environment Variable', plural: 'Environment Variables' },
|
||||
deployment: { singular: 'Deployment', plural: 'Deployments' },
|
||||
database: { singular: 'Database', plural: 'Databases' },
|
||||
collection: { singular: 'Collection', plural: 'Collections' },
|
||||
index: { singular: 'Index', plural: 'Indexes' },
|
||||
attribute: { singular: 'Attribute', plural: 'Attributes' },
|
||||
document: { singular: 'Document', plural: 'Documents' }
|
||||
};
|
||||
|
||||
const resources = Object.keys(ResourcesFriendly);
|
||||
|
||||
type Resource = (typeof resources)[number];
|
||||
|
||||
@@ -101,7 +103,7 @@ export const migrationFormToResources = (
|
||||
addResource('function');
|
||||
}
|
||||
if (formData.functions.env) {
|
||||
addResource('envVar');
|
||||
addResource('environment variable');
|
||||
}
|
||||
if (formData.functions.inactive) {
|
||||
addResource('deployment');
|
||||
@@ -155,7 +157,7 @@ export const resourcesToMigrationForm = (
|
||||
if (resources.includes('function') && isVersionAtLeast(version, '1.4.0')) {
|
||||
formData.functions.root = true;
|
||||
}
|
||||
if (resources.includes('envVar') && isVersionAtLeast(version, '1.4.0')) {
|
||||
if (resources.includes('environment variable') && isVersionAtLeast(version, '1.4.0')) {
|
||||
formData.functions.env = true;
|
||||
}
|
||||
if (resources.includes('deployment') && isVersionAtLeast(version, '1.4.0')) {
|
||||
|
||||
@@ -40,6 +40,11 @@ function createServices() {
|
||||
method: ApiService.Locale,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Messaging',
|
||||
method: ApiService.Messaging,
|
||||
value: null
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
method: ApiService.Storage,
|
||||
@@ -98,6 +103,11 @@ function createServices() {
|
||||
method: ApiService.Locale,
|
||||
value: project.serviceStatusForLocale
|
||||
},
|
||||
{
|
||||
label: 'Messaging',
|
||||
method: ApiService.Messaging,
|
||||
value: project.serviceStatusForMessaging
|
||||
},
|
||||
{
|
||||
label: 'Storage',
|
||||
method: ApiService.Storage,
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Execution permissions</svelte:fragment>
|
||||
<svelte:fragment slot="title">Permissions</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose who can execute this function using the client API. For more information, visit our
|
||||
<a
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<Unauthenticated>
|
||||
<Heading size="1" tag="h3">{$page.status}</Heading>
|
||||
<Heading size="1" tag="h3">{$page.error.status || 'Invalid Argument'}</Heading>
|
||||
<Heading size="3" tag="h4">{$page.error.message}</Heading>
|
||||
<Button href="/">Back to the console</Button>
|
||||
</Unauthenticated>
|
||||
|
||||
@@ -54,7 +54,8 @@
|
||||
'/recover',
|
||||
'/invite',
|
||||
'/card',
|
||||
'/hackathon'
|
||||
'/hackathon',
|
||||
'/mfa'
|
||||
];
|
||||
if ($user) {
|
||||
if (
|
||||
|
||||
@@ -13,9 +13,10 @@ export const ssr = false;
|
||||
export const load: LayoutLoad = async ({ depends, url }) => {
|
||||
depends(Dependencies.ACCOUNT);
|
||||
|
||||
redirectTo.set(url.searchParams.get('forceRedirect') || null);
|
||||
|
||||
url.searchParams.delete('forceRedirect');
|
||||
if (url.searchParams.has('forceRedirect')) {
|
||||
redirectTo.set(url.searchParams.get('forceRedirect') || null);
|
||||
url.searchParams.delete('forceRedirect');
|
||||
}
|
||||
|
||||
try {
|
||||
const account = await sdk.forConsole.account.get<{ organization?: string }>();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import { deepMap } from '$lib/helpers/object';
|
||||
import type { WritableValue } from '$lib/helpers/types';
|
||||
import { sdk, type getSdkForProject } from '$lib/stores/sdk';
|
||||
import { type getSdkForProject } from '$lib/stores/sdk';
|
||||
|
||||
import { onMount } from 'svelte';
|
||||
|
||||
@@ -107,19 +107,10 @@
|
||||
);
|
||||
break;
|
||||
case 'firebase':
|
||||
if ($provider.projectId) {
|
||||
// OAuth
|
||||
report = await sdk.forProject.migrations.getFirebaseReportOAuth(
|
||||
providerResources.firebase,
|
||||
$provider.projectId
|
||||
);
|
||||
} else if ($provider.serviceAccount) {
|
||||
// Manual auth
|
||||
report = await projectSdk.migrations.getFirebaseReport(
|
||||
providerResources.firebase,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
}
|
||||
report = await projectSdk.migrations.getFirebaseReport(
|
||||
providerResources.firebase,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
|
||||
break;
|
||||
case 'nhost':
|
||||
@@ -353,7 +344,7 @@
|
||||
<div />
|
||||
<span>Import all functions and their active deployment</span>
|
||||
<ul>
|
||||
{#if resources?.includes('envVar')}
|
||||
{#if resources?.includes('environment variable')}
|
||||
<li class="checkbox-field">
|
||||
<input
|
||||
type="checkbox"
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
let selectedOrg = organizations.length ? organizations[0].$id : null;
|
||||
|
||||
let projects = [] as Models.ProjectList['projects'];
|
||||
$: hasProjects = projects.length > 0;
|
||||
let loadingProjects = false;
|
||||
|
||||
let projectType = 'existing';
|
||||
@@ -43,9 +42,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">
|
||||
{hasProjects ? 'Select project' : 'Create project'}
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">Project</svelte:fragment>
|
||||
|
||||
<div class="u-flex u-flex-vertical u-gap-24">
|
||||
{#if organizations.length > 1}
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select resources</svelte:fragment>
|
||||
<svelte:fragment slot="title">Resources</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Select the resources you need to migrate to Appwrite. Some resources can be migrated, but
|
||||
with limitations. <a
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Heading size="1" tag="h3">{$page.status}</Heading>
|
||||
<Heading size="1" tag="h3">{$page.error.status || 'Invalid Argument'}</Heading>
|
||||
<Heading size="3" tag="h4">{$page.error.message}</Heading>
|
||||
<Button href="/console">Back to the console</Button>
|
||||
</Container>
|
||||
|
||||
@@ -39,14 +39,18 @@
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Delete authentication provider"
|
||||
title="Delete authentication method"
|
||||
bind:show={showDelete}
|
||||
onSubmit={deleteProvider}
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
bind:error
|
||||
headerDivider={false}>
|
||||
<p>Are you sure you want to delete this authentication method from your account?</p>
|
||||
<p class="u-bold">
|
||||
Deleting the authentication method will disable multi-factor authentication for your
|
||||
account. To re-enable it, you'll need to add a new authentication method.
|
||||
</p>
|
||||
<p>Enter the 6-digit verification code generated by your authenticator app to continue.</p>
|
||||
<FormList>
|
||||
<InputDigits autofocus required bind:value={code} />
|
||||
</FormList>
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
export let showSetup = false;
|
||||
export let showRecoveryCodes = false;
|
||||
|
||||
let copyParent: HTMLElement;
|
||||
let code: string;
|
||||
let type: Models.MfaType = null;
|
||||
async function addAuthenticator(): Promise<URL> {
|
||||
@@ -48,13 +49,43 @@
|
||||
Install an authenticator app on your mobile device, open it and scan the provided QR
|
||||
code or enter it manually.
|
||||
</p>
|
||||
<div class="qr">
|
||||
{#await addAuthenticator()}
|
||||
|
||||
{#await addAuthenticator()}
|
||||
<div class="qr">
|
||||
<LoadingDots />
|
||||
{:then qr}
|
||||
</div>
|
||||
{:then qr}
|
||||
<div class="qr">
|
||||
<img alt="MFA QR Code" class="code" src={qr.toString()} />
|
||||
{/await}
|
||||
</div>
|
||||
</div>
|
||||
<span class="with-separators eyebrow-heading-3">or</span>
|
||||
|
||||
<div>
|
||||
<label class="label" for="manual-code">Manual entry code</label>
|
||||
<button class="tooltip" aria-label="sec retinfo">
|
||||
<span class="icon-info" aria-hidden="true"></span>
|
||||
<span class="tooltip-popup" role="tooltip">
|
||||
<p class="text u-margin-block-start-8">
|
||||
Manually enter the following code into the authenticator app
|
||||
</p>
|
||||
</span>
|
||||
</button>
|
||||
|
||||
<div class="input-text-wrapper" style="--amount-of-buttons:1">
|
||||
<input id="manual-code" type="text" value={type.secret} readonly={true} />
|
||||
<div class="options-list">
|
||||
<div bind:this={copyParent}>
|
||||
{#key copyParent}
|
||||
<Copy value={type.secret} appendTo="parent">
|
||||
<span class="icon-duplicate" aria-hidden="true" />
|
||||
</Copy>
|
||||
{/key}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{/await}
|
||||
|
||||
<hr />
|
||||
<FormList>
|
||||
<label for="code">Enter the 6-digit one-time code generated by the app</label>
|
||||
|
||||
@@ -10,6 +10,7 @@ import { headerAlert } from '$lib/stores/headerAlert';
|
||||
import ProjectsAtRisk from '$lib/components/billing/alerts/projectsAtRisk.svelte';
|
||||
import { get } from 'svelte/store';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
@@ -37,7 +38,9 @@ export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
return {
|
||||
header: Header,
|
||||
breadcrumbs: Breadcrumbs,
|
||||
organization: await sdk.forConsole.teams.get(params.organization),
|
||||
organization: await (sdk.forConsole.teams.get(
|
||||
params.organization
|
||||
) as Promise<Organization>),
|
||||
members: await sdk.forConsole.teams.listMemberships(params.organization)
|
||||
};
|
||||
} catch (e) {
|
||||
|
||||
@@ -38,7 +38,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep beforeSubmit={validateCoupon}>
|
||||
<svelte:fragment slot="title">Add credits</svelte:fragment>
|
||||
<svelte:fragment slot="title">Credits</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Add Appwrite credits to your organization. A payment method is required before credits can
|
||||
be applied.
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep beforeSubmit={handleSubmit}>
|
||||
<svelte:fragment slot="title">Payment details</svelte:fragment>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Add a payment method to your organization. You will not be charged until you upgrade to a
|
||||
Pro or Scale plan.
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal title="Invite Member" {error} size="big" bind:show={showCreate} onSubmit={create}>
|
||||
<Modal title="Invite member" {error} size="big" bind:show={showCreate} onSubmit={create}>
|
||||
{#if isCloud}
|
||||
<Alert type="info">
|
||||
{#if $organization?.billingPlan === BillingPlan.SCALE}
|
||||
|
||||
@@ -13,7 +13,6 @@
|
||||
import ChangeOrganizationTierCloud from '../changeOrganizationTierCloud.svelte';
|
||||
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;
|
||||
@@ -58,25 +57,14 @@
|
||||
</script>
|
||||
|
||||
<Modal bind:show title="Limit reached">
|
||||
<svelte:fragment slot="title">
|
||||
Your usage exceeds the {tierToPlan($organization.billingPlan).name} plan limits
|
||||
</svelte:fragment>
|
||||
|
||||
{#if $organization.billingPlan === BillingPlan.STARTER}
|
||||
<p class="text">
|
||||
Usage for <b>{$organization.name}</b> organization has reached the limits of the {tierToPlan(
|
||||
$organization.billingPlan
|
||||
).name} plan. Consider upgrading to increase your resource usage.
|
||||
</p>
|
||||
{:else}
|
||||
<p class="text">
|
||||
Usage for <b>{$organization.name}</b> organization has reached the limits of the {tierToPlan(
|
||||
$organization.billingPlan
|
||||
).name} plan. Excess usage fees will apply.
|
||||
</p>
|
||||
{/if}
|
||||
<p class="text">
|
||||
Usage for <b>{$organization.name}</b> organization has reached the limits of the {tierToPlan(
|
||||
$organization.billingPlan
|
||||
).name} plan. Excess usage fees will apply.
|
||||
</p>
|
||||
|
||||
<PlanExcess {excess} currentTier={$organization.billingPlan} />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<div class="u-flex u-main-space-between u-width-full-line">
|
||||
<Button text on:click={() => (show = false)}>Cancel</Button>
|
||||
|
||||
@@ -78,7 +78,7 @@
|
||||
},
|
||||
...permanentTabSettings
|
||||
]
|
||||
: permanentTabs;
|
||||
: [...permanentTabs, ...permanentTabSettings];
|
||||
</script>
|
||||
|
||||
{#if $organization?.$id}
|
||||
|
||||
@@ -99,4 +99,6 @@
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
<Delete bind:showDelete invoices={data.invoices} />
|
||||
{#if isCloud && data.invoices}
|
||||
<Delete bind:showDelete invoices={data.invoices} />
|
||||
{/if}
|
||||
|
||||
@@ -2,12 +2,14 @@ import type { PageLoad } from './$types';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import { isCloud } from '$lib/system';
|
||||
|
||||
export const load: PageLoad = async ({ depends, parent }) => {
|
||||
const { organization } = await parent();
|
||||
depends(Dependencies.ORGANIZATION);
|
||||
|
||||
return {
|
||||
projects: await sdk.forConsole.projects.list([Query.equal('teamId', organization.$id)]),
|
||||
invoices: await sdk.forConsole.billing.listInvoices(organization.$id)
|
||||
invoices: isCloud ? await sdk.forConsole.billing.listInvoices(organization.$id) : undefined
|
||||
};
|
||||
};
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Project Details</svelte:fragment>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
label="Name"
|
||||
|
||||
@@ -5,6 +5,6 @@
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Heading size="1" tag="h3">{$page.status}</Heading>
|
||||
<Heading size="1" tag="h3">{$page.error.status || 'Invalid Argument'}</Heading>
|
||||
<p class="body-text-2 u-bold u-margin-block-start-4">{$page.error.message}</p>
|
||||
</Container>
|
||||
|
||||
@@ -5,6 +5,7 @@ import type { LayoutLoad } from './$types';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
import { failedInvoice } from '$lib/stores/billing';
|
||||
import { isCloud } from '$lib/system';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
|
||||
export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
depends(Dependencies.PROJECT);
|
||||
@@ -22,7 +23,7 @@ export const load: LayoutLoad = async ({ params, depends }) => {
|
||||
|
||||
return {
|
||||
project,
|
||||
organization: await sdk.forConsole.teams.get(project.teamId)
|
||||
organization: await (sdk.forConsole.teams.get(project.teamId) as Promise<Organization>)
|
||||
};
|
||||
} catch (e) {
|
||||
error(e.code, e.message);
|
||||
|
||||
@@ -4,8 +4,6 @@
|
||||
import { Modal } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button } from '$lib/elements/forms';
|
||||
import FormList from '$lib/elements/forms/formList.svelte';
|
||||
import InputDigits from '$lib/elements/forms/inputDigits.svelte';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { AuthenticatorType } from '@appwrite.io/console';
|
||||
@@ -13,12 +11,11 @@
|
||||
|
||||
export let showDelete = false;
|
||||
|
||||
let code: string;
|
||||
let error: string;
|
||||
|
||||
async function deleteProvider() {
|
||||
try {
|
||||
await sdk.forConsole.users.deleteAuthenticator($user.$id, AuthenticatorType.Totp, '');
|
||||
await sdk.forConsole.users.deleteAuthenticator($user.$id, AuthenticatorType.Totp);
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await invalidate(Dependencies.FACTORS);
|
||||
showDelete = false;
|
||||
@@ -34,23 +31,23 @@
|
||||
}
|
||||
|
||||
$: if (showDelete) {
|
||||
code = '';
|
||||
error = '';
|
||||
}
|
||||
</script>
|
||||
|
||||
<Modal
|
||||
title="Delete authentication provider"
|
||||
title="Delete authentication method"
|
||||
bind:show={showDelete}
|
||||
onSubmit={deleteProvider}
|
||||
icon="exclamation"
|
||||
state="warning"
|
||||
bind:error
|
||||
headerDivider={false}>
|
||||
<p class="u-bold">
|
||||
Deleting the authentication method will disable multi-factor authentication for this
|
||||
account. To re-enable it, user will need to add a new authentication method.
|
||||
</p>
|
||||
<p>Are you sure you want to delete this authentication method from your account?</p>
|
||||
<FormList>
|
||||
<InputDigits autofocus required bind:value={code} />
|
||||
</FormList>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Create data</svelte:fragment>
|
||||
<svelte:fragment slot="title">Data</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Provide document data based on attributes you created earlier.
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -121,11 +121,7 @@
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
</Alert>
|
||||
<InputTextarea
|
||||
label="Body"
|
||||
placeholder={`{ "myKey": "myValue" }`}
|
||||
id="body"
|
||||
bind:value={body} />
|
||||
<InputTextarea label="Body" placeholder={`Hello, World!`} id="body" bind:value={body} />
|
||||
{:else}
|
||||
<InputText label="Path" id="path" placeholder="/" bind:value={path} required />
|
||||
|
||||
@@ -214,7 +210,7 @@
|
||||
|
||||
<InputTextarea
|
||||
label="Body"
|
||||
placeholder={`{ "myKey": "myValue" }`}
|
||||
placeholder={`Hello, World!`}
|
||||
id="body"
|
||||
bind:value={body} />
|
||||
</FormList>
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Configuration function</svelte:fragment>
|
||||
<svelte:fragment slot="title">Configuration</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">Create your new Appwrite Function.</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
|
||||
@@ -28,7 +28,7 @@
|
||||
} from '$lib/elements/table';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { Container } from '$lib/layout';
|
||||
import { MessageStatus, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import type { PageData } from './$types';
|
||||
import CreateMessageDropdown from './createMessageDropdown.svelte';
|
||||
import FailedModal from './failedModal.svelte';
|
||||
@@ -141,7 +141,7 @@
|
||||
<TableCellCheck
|
||||
bind:selectedIds={selected}
|
||||
id={message.$id}
|
||||
disabled={message.status === MessageStatus.Processing} />
|
||||
disabled={message.status === 'processing'} />
|
||||
|
||||
{#each $columns as column (column.id)}
|
||||
{#if column.show}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import Wizard from '../wizard.svelte';
|
||||
import type { PageData } from './$types';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { MessageStatus, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import Topics from './topics.svelte';
|
||||
import Targets from './targets.svelte';
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
topics: $message.topics,
|
||||
users: $message.users,
|
||||
targets: $message.targets,
|
||||
status: MessageStatus.Draft,
|
||||
draft: $message.status === 'draft',
|
||||
scheduledAt: $message.scheduledAt
|
||||
};
|
||||
|
||||
@@ -98,21 +98,15 @@
|
||||
<Container>
|
||||
<Overview />
|
||||
{#if $message.providerType === MessagingProviderType.Email}
|
||||
<EmailPreview
|
||||
message={$message}
|
||||
onEdit={$message.status === MessageStatus.Draft ? onEdit : null} />
|
||||
<EmailPreview message={$message} onEdit={$message.status === 'draft' ? onEdit : null} />
|
||||
{:else if $message.providerType === MessagingProviderType.Sms}
|
||||
<SMSPreview
|
||||
message={$message}
|
||||
onEdit={$message.status === MessageStatus.Draft ? onEdit : null} />
|
||||
<SMSPreview message={$message} onEdit={$message.status === 'draft' ? onEdit : null} />
|
||||
{:else if $message.providerType === MessagingProviderType.Push}
|
||||
<PushPreview
|
||||
message={$message}
|
||||
onEdit={$message.status === MessageStatus.Draft ? onEdit : null} />
|
||||
<PushPreview message={$message} onEdit={$message.status === 'draft' ? onEdit : null} />
|
||||
{/if}
|
||||
<Topics topics={Object.values(data.topicsById)} />
|
||||
<Targets targets={Object.values(data.targetsById)} usersById={data.usersById} />
|
||||
{#if $message.status !== MessageStatus.Processing}
|
||||
{#if $message.status !== 'processing'}
|
||||
<Delete message={$message} />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
@@ -32,7 +32,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<CardGrid>
|
||||
<CardGrid hideFooter={$message.status != 'failed'}>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16" data-private>
|
||||
<ProviderType type={$message.providerType} size="l">
|
||||
<Heading tag="h6" size="7">{providerType}</Heading>
|
||||
@@ -51,15 +51,13 @@
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
{#if $message.status === 'failed'}
|
||||
<Button
|
||||
secondary
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
errors = $message.deliveryErrors;
|
||||
showFailed = true;
|
||||
}}>View logs</Button>
|
||||
{/if}
|
||||
<Button
|
||||
secondary
|
||||
on:click={(e) => {
|
||||
e.preventDefault();
|
||||
errors = $message.deliveryErrors;
|
||||
showFailed = true;
|
||||
}}>View logs</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
|
||||
@@ -11,6 +11,50 @@
|
||||
FCM = 'fcm',
|
||||
APNS = 'apns'
|
||||
}
|
||||
|
||||
export function getProviderDisplayNameAndIcon(provider: Providers | string) {
|
||||
let icon = '';
|
||||
let displayName = provider.charAt(0).toUpperCase() + provider.slice(1);
|
||||
|
||||
switch (provider) {
|
||||
case Providers.FCM:
|
||||
icon = 'firebase';
|
||||
displayName = 'FCM';
|
||||
break;
|
||||
case Providers.APNS:
|
||||
icon = 'apple';
|
||||
displayName = 'APNS';
|
||||
break;
|
||||
case Providers.Sendgrid:
|
||||
icon = 'sendgrid';
|
||||
break;
|
||||
case Providers.Mailgun:
|
||||
icon = 'mailgun';
|
||||
break;
|
||||
case Providers.SMTP:
|
||||
icon = 'smtp';
|
||||
displayName = 'SMTP';
|
||||
break;
|
||||
case Providers.Twilio:
|
||||
icon = 'twilio';
|
||||
break;
|
||||
case Providers.Telesign:
|
||||
icon = 'telesign';
|
||||
break;
|
||||
case Providers.Msg91:
|
||||
icon = 'msg91';
|
||||
displayName = 'MSG91';
|
||||
break;
|
||||
case Providers.Textmagic:
|
||||
icon = 'textmagic';
|
||||
displayName = 'Textmagic';
|
||||
break;
|
||||
case Providers.Vonage:
|
||||
icon = 'vonage';
|
||||
break;
|
||||
}
|
||||
return { icon, displayName };
|
||||
}
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
@@ -22,8 +66,7 @@
|
||||
export let noIcon = false;
|
||||
export let size: 's' | 'm' | 'l' = 'm';
|
||||
|
||||
let icon = '';
|
||||
let displayName = name || provider.charAt(0).toUpperCase() + provider.slice(1);
|
||||
const { icon, displayName } = getProviderDisplayNameAndIcon(provider);
|
||||
|
||||
let textSize = '1.25rem';
|
||||
switch (size) {
|
||||
@@ -34,44 +77,6 @@
|
||||
textSize = '1.5rem';
|
||||
break;
|
||||
}
|
||||
|
||||
switch (provider) {
|
||||
case Providers.FCM:
|
||||
icon = 'firebase';
|
||||
displayName = name || 'FCM';
|
||||
break;
|
||||
case Providers.APNS:
|
||||
icon = 'apple';
|
||||
displayName = name || 'APNS';
|
||||
break;
|
||||
case Providers.Sendgrid:
|
||||
icon = 'sendgrid';
|
||||
break;
|
||||
case Providers.Mailgun:
|
||||
icon = 'mailgun';
|
||||
break;
|
||||
case Providers.SMTP:
|
||||
icon = 'smtp';
|
||||
displayName = name || 'SMTP';
|
||||
break;
|
||||
case Providers.Twilio:
|
||||
icon = 'twilio';
|
||||
break;
|
||||
case Providers.Telesign:
|
||||
icon = 'telesign';
|
||||
break;
|
||||
case Providers.Msg91:
|
||||
icon = 'msg91';
|
||||
displayName = name || 'MSG91';
|
||||
break;
|
||||
case Providers.Textmagic:
|
||||
icon = 'textmagic';
|
||||
displayName = name || 'TextMagic';
|
||||
break;
|
||||
case Providers.Vonage:
|
||||
icon = 'vonage';
|
||||
break;
|
||||
}
|
||||
</script>
|
||||
|
||||
{#if icon === ''}
|
||||
@@ -89,12 +94,12 @@
|
||||
<img
|
||||
style:--p-text-size={textSize}
|
||||
src={`${base}/icons/${$app.themeInUse}/color/${icon}.svg`}
|
||||
alt={displayName} />
|
||||
alt={name || displayName} />
|
||||
{/if}
|
||||
</div>
|
||||
{/if}
|
||||
<slot>
|
||||
{displayName}
|
||||
{name || displayName}
|
||||
</slot>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<script lang="ts">
|
||||
import { onDestroy } from 'svelte';
|
||||
import { Wizard } from '$lib/layout';
|
||||
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
|
||||
import Provider from './wizard/provider.svelte';
|
||||
import Configure from './wizard/configure.svelte';
|
||||
import Settings from './wizard/settings.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -45,8 +44,8 @@
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].from,
|
||||
$providerParams[$provider].username,
|
||||
$providerParams[$provider].password,
|
||||
$providerParams[$provider].customerId,
|
||||
$providerParams[$provider].apiKey,
|
||||
$providerParams[$provider].enabled
|
||||
);
|
||||
break;
|
||||
@@ -130,6 +129,7 @@
|
||||
$providerParams[$provider].authKeyId,
|
||||
$providerParams[$provider].teamId,
|
||||
$providerParams[$provider].bundleId,
|
||||
$providerParams[$provider].sandbox,
|
||||
$providerParams[$provider].enabled
|
||||
);
|
||||
break;
|
||||
@@ -154,18 +154,14 @@
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
console.log('destroy');
|
||||
});
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Proivder',
|
||||
label: 'Provider',
|
||||
component: Provider
|
||||
});
|
||||
stepsComponents.set(2, {
|
||||
label: 'Configure',
|
||||
component: Configure
|
||||
label: 'Settings',
|
||||
component: Settings
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
<script lang="ts">
|
||||
import { ImagePreview } from '$lib/components';
|
||||
|
||||
export let lines = [];
|
||||
export let popoverImage: { src: { dark: string; light: string }; alt: string } | null = null;
|
||||
</script>
|
||||
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
{#each lines as line}
|
||||
<p>{@html line}</p>
|
||||
{/each}
|
||||
|
||||
{#if popoverImage}
|
||||
<ImagePreview
|
||||
darkSrc={popoverImage.src.dark}
|
||||
lightSrc={popoverImage.src.light}
|
||||
alt={popoverImage.alt} />
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
<style>
|
||||
div > p {
|
||||
color: hsl(var(--color-neutral-50));
|
||||
|
||||
&:first-child {
|
||||
color: hsl(var(--color-neutral-70));
|
||||
}
|
||||
}
|
||||
|
||||
:global(.theme-dark) div > p {
|
||||
color: hsl(var(--color-neutral-20));
|
||||
|
||||
&:first-child {
|
||||
color: hsl(var(--color-neutral-10));
|
||||
}
|
||||
}
|
||||
</style>
|
||||
+149
@@ -2,11 +2,160 @@
|
||||
import { Container } from '$lib/layout';
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdateSettings from './updateSettings.svelte';
|
||||
import UpdateStatus from './updateStatus.svelte';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import { Providers } from '../../provider.svelte';
|
||||
import { providers } from '../store';
|
||||
import { provider as providerData } from './store';
|
||||
|
||||
let providerType: MessagingProviderType;
|
||||
if (isValueOfStringEnum(MessagingProviderType, $providerData.type)) {
|
||||
providerType = $providerData.type;
|
||||
}
|
||||
|
||||
let provider: Providers;
|
||||
if (isValueOfStringEnum(Providers, $providerData.provider)) {
|
||||
provider = $providerData.provider;
|
||||
}
|
||||
|
||||
let params: object = {};
|
||||
|
||||
$: switch (provider) {
|
||||
case Providers.Twilio:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
accountSid: $providerData.credentials['accountSid'],
|
||||
authToken: $providerData.credentials['authToken'],
|
||||
from: $providerData.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Msg91:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
from: $providerData.options['from'],
|
||||
senderId: $providerData.credentials['senderId'],
|
||||
authKey: $providerData.credentials['authKey']
|
||||
};
|
||||
break;
|
||||
case Providers.Telesign:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
customerId: $providerData.credentials['customerId'],
|
||||
apiKey: $providerData.credentials['apiKey'],
|
||||
from: $providerData.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Textmagic:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
username: $providerData.credentials['username'],
|
||||
apiKey: $providerData.credentials['apiKey'],
|
||||
from: $providerData.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Vonage:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
apiKey: $providerData.credentials['apiKey'],
|
||||
apiSecret: $providerData.credentials['apiSecret'],
|
||||
from: $providerData.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Mailgun:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
isEuRegion: false,
|
||||
fromEmail: $providerData.options['fromEmail'],
|
||||
fromName: $providerData.options['fromName'],
|
||||
replyToEmail: $providerData.options['replyToEmail'],
|
||||
replyToName: $providerData.options['replyToName'],
|
||||
apiKey: $providerData.credentials['apiKey'],
|
||||
domain: $providerData.credentials['domain']
|
||||
};
|
||||
break;
|
||||
case Providers.Sendgrid:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
apiKey: $providerData.credentials['apiKey'],
|
||||
fromEmail: $providerData.options['fromEmail'],
|
||||
fromName: $providerData.options['fromName'],
|
||||
replyToEmail: $providerData.options['replyToEmail'],
|
||||
replyToName: $providerData.options['replyToName']
|
||||
};
|
||||
break;
|
||||
case Providers.SMTP:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
host: $providerData.credentials['host'],
|
||||
port: $providerData.credentials['port'],
|
||||
username: $providerData.credentials['username'],
|
||||
password: $providerData.credentials['password'],
|
||||
fromName: $providerData.options['fromName'],
|
||||
fromEmail: $providerData.options['fromEmail'],
|
||||
replyToName: $providerData.options['replyToName'],
|
||||
replyToEmail: $providerData.options['replyToEmail'],
|
||||
encryption: !$providerData.options['encryption']
|
||||
? 'none'
|
||||
: $providerData.options['encryption'],
|
||||
autoTLS: $providerData.options['autoTLS'],
|
||||
mailer: $providerData.options['mailer']
|
||||
};
|
||||
break;
|
||||
case Providers.FCM:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled
|
||||
};
|
||||
if ('serviceAccountJSON' in $providerData.credentials) {
|
||||
const serviceAccountJSON = $providerData.credentials['serviceAccountJSON'];
|
||||
if (typeof serviceAccountJSON === 'string') {
|
||||
params['serviceAccountJSON'] = serviceAccountJSON;
|
||||
} else if (serviceAccountJSON instanceof Object) {
|
||||
params['serviceAccountJSON'] = JSON.stringify(serviceAccountJSON);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Providers.APNS:
|
||||
params = {
|
||||
providerId: $providerData.$id,
|
||||
name: $providerData.name,
|
||||
enabled: $providerData.enabled,
|
||||
authKey: $providerData.credentials['authKey'],
|
||||
authKeyId: $providerData.credentials['authKeyId'],
|
||||
teamId: $providerData.credentials['teamId'],
|
||||
bundleId: $providerData.credentials['bundleId']
|
||||
};
|
||||
break;
|
||||
}
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<UpdateStatus />
|
||||
<UpdateName />
|
||||
<UpdateSettings
|
||||
inputs={providers[providerType].providers[provider].configure}
|
||||
providerId={$providerData.$id}
|
||||
{providerType}
|
||||
{provider}
|
||||
{params} />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
+1
-5
@@ -2,8 +2,4 @@ import { derived } from 'svelte/store';
|
||||
import { page } from '$app/stores';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
export const provider = derived(
|
||||
page,
|
||||
// TODO: Set actual type
|
||||
($page) => $page.data.provider as Models.Provider
|
||||
);
|
||||
export const provider = derived(page, ($page) => $page.data.provider as Models.Provider);
|
||||
|
||||
+2
-2
@@ -69,13 +69,13 @@
|
||||
);
|
||||
break;
|
||||
case Providers.FCM:
|
||||
response = await sdk.forProject.messaging.updateFCMProvider(
|
||||
response = await sdk.forProject.messaging.updateFcmProvider(
|
||||
providerId,
|
||||
providerName
|
||||
);
|
||||
break;
|
||||
case Providers.APNS:
|
||||
response = await sdk.forProject.messaging.updateAPNSProvider(
|
||||
response = await sdk.forProject.messaging.updateApnsProvider(
|
||||
providerId,
|
||||
providerName
|
||||
);
|
||||
|
||||
+233
@@ -0,0 +1,233 @@
|
||||
<script lang="ts">
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { invalidate } from '$app/navigation';
|
||||
import type { ProviderInput } from '../store';
|
||||
import { Providers, getProviderDisplayNameAndIcon } from '../../provider.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import SettingsFormList from '../settingsFormList.svelte';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { onMount } from 'svelte';
|
||||
import CreateMember from '$routes/console/organization-[organization]/createMember.svelte';
|
||||
|
||||
export let inputs: ProviderInput[];
|
||||
export let providerType: MessagingProviderType;
|
||||
export let provider: Providers;
|
||||
export let providerId: string;
|
||||
export let params: object;
|
||||
|
||||
let formValues: object = {};
|
||||
let originalfiles: Record<string, FileList> = {};
|
||||
let files: Record<string, FileList> = {};
|
||||
let updated = false;
|
||||
let newMemberModal = false;
|
||||
let message = '';
|
||||
|
||||
const { displayName } = getProviderDisplayNameAndIcon(provider);
|
||||
|
||||
switch (providerType) {
|
||||
case MessagingProviderType.Push:
|
||||
message = 'notifications';
|
||||
break;
|
||||
case MessagingProviderType.Sms:
|
||||
message = 'SMS';
|
||||
break;
|
||||
case MessagingProviderType.Email:
|
||||
message = 'emails';
|
||||
break;
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
for (const input of inputs) {
|
||||
const value = params[input.name];
|
||||
formValues[input.name] = value;
|
||||
if (input.type === 'file' && typeof value == 'string' && value.length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
const f = new File([value], `${input.name}.${input.allowedFileExtensions}`);
|
||||
dataTransfer.items.add(f);
|
||||
files[input.name] = dataTransfer.files;
|
||||
originalfiles[input.name] = dataTransfer.files;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function update() {
|
||||
try {
|
||||
const promises = [];
|
||||
for (const [key, value] of Object.entries(files)) {
|
||||
const promise = value[0].text().then((text) => {
|
||||
formValues[key] = text;
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
let response: Models.Provider;
|
||||
switch (provider) {
|
||||
case Providers.Twilio:
|
||||
response = await sdk.forProject.messaging.updateTwilioProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['accountSid'],
|
||||
formValues['authToken'],
|
||||
formValues['from']
|
||||
);
|
||||
break;
|
||||
case Providers.Msg91:
|
||||
response = await sdk.forProject.messaging.updateMsg91Provider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['senderId'],
|
||||
formValues['authKey'],
|
||||
formValues['from']
|
||||
);
|
||||
break;
|
||||
case Providers.Telesign:
|
||||
response = await sdk.forProject.messaging.updateTelesignProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['customerId'],
|
||||
formValues['apiKey'],
|
||||
formValues['from']
|
||||
);
|
||||
break;
|
||||
case Providers.Textmagic:
|
||||
response = await sdk.forProject.messaging.updateTextmagicProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['username'],
|
||||
formValues['apiKey'],
|
||||
formValues['from']
|
||||
);
|
||||
break;
|
||||
case Providers.Vonage:
|
||||
response = await sdk.forProject.messaging.updateVonageProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['apiKey'],
|
||||
formValues['apiSecret'],
|
||||
formValues['from']
|
||||
);
|
||||
break;
|
||||
case Providers.Mailgun:
|
||||
response = await sdk.forProject.messaging.updateMailgunProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
formValues['apiKey'],
|
||||
formValues['domain'],
|
||||
formValues['isEuRegion'],
|
||||
undefined,
|
||||
formValues['fromName'] || undefined,
|
||||
formValues['fromEmail'],
|
||||
formValues['replyToName'] || undefined,
|
||||
formValues['replyToEmail'] || undefined
|
||||
);
|
||||
break;
|
||||
case Providers.Sendgrid:
|
||||
response = await sdk.forProject.messaging.updateSendgridProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['apiKey'],
|
||||
formValues['fromName'] || undefined,
|
||||
formValues['fromEmail'],
|
||||
formValues['replyToName'] || undefined,
|
||||
formValues['replyToEmail'] || undefined
|
||||
);
|
||||
break;
|
||||
case Providers.SMTP:
|
||||
response = await sdk.forProject.messaging.updateSmtpProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
formValues['host'],
|
||||
formValues['port'] || undefined,
|
||||
formValues['username'] || undefined,
|
||||
formValues['password'] || undefined,
|
||||
formValues['encryption'],
|
||||
formValues['autoTLS'],
|
||||
undefined,
|
||||
formValues['fromName'] || undefined,
|
||||
formValues['fromEmail'],
|
||||
formValues['replyToName'] || undefined,
|
||||
formValues['replyToEmail'] || undefined
|
||||
);
|
||||
break;
|
||||
case Providers.FCM:
|
||||
response = await sdk.forProject.messaging.updateFcmProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
JSON.parse(formValues['serviceAccountJSON'])
|
||||
);
|
||||
break;
|
||||
case Providers.APNS:
|
||||
response = await sdk.forProject.messaging.updateApnsProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
undefined,
|
||||
formValues['authKey'],
|
||||
formValues['authKeyId'],
|
||||
formValues['teamId'],
|
||||
formValues['bundleId']
|
||||
);
|
||||
break;
|
||||
}
|
||||
originalfiles = files;
|
||||
await invalidate(Dependencies.MESSAGING_PROVIDER);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${response.name} has been updated`
|
||||
});
|
||||
trackEvent(Submit.MessagingProviderUpdate, {
|
||||
provider: provider
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.MessagingProviderUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
$: updated =
|
||||
inputs
|
||||
.filter((input) => input.type !== 'file')
|
||||
.some((input) => formValues[input.name] !== params[input.name]) ||
|
||||
Object.keys(files).some((key) => files[key] !== originalfiles[key]);
|
||||
</script>
|
||||
|
||||
<Form onSubmit={update}>
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16" data-private>
|
||||
<Heading tag="h6" size="7">Settings</Heading>
|
||||
</div>
|
||||
<p>
|
||||
Configure the settings to <Button
|
||||
link
|
||||
href={`https://appwrite.io/docs/products/messaging/providers/${provider}`}
|
||||
external>enable {displayName}</Button> to send {message}, or <Button
|
||||
link
|
||||
on:click={() => (newMemberModal = true)}>invite a team member</Button> to complete the
|
||||
provider settings.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<SettingsFormList bind:files {inputs} bind:params={formValues} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<div class="u-flex u-flex-wrap u-gap-12">
|
||||
<Button disabled={!updated} submit>Update</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
|
||||
<CreateMember bind:showCreate={newMemberModal} />
|
||||
+13
-157
@@ -5,167 +5,24 @@
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, InputSwitch } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { isValueOfStringEnum } from '$lib/helpers/types';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { onMount } from 'svelte';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import Provider, { Providers } from '../../provider.svelte';
|
||||
import ProviderType from '../../providerType.svelte';
|
||||
import Update from '../update.svelte';
|
||||
import { providerParams, providerType, provider as wizardProvider } from '../wizard/store';
|
||||
import { provider } from './store';
|
||||
import { provider as providerData } from './store';
|
||||
|
||||
let enabled: boolean = null;
|
||||
|
||||
onMount(() => {
|
||||
enabled ??= $provider.enabled;
|
||||
enabled ??= $providerData.enabled;
|
||||
});
|
||||
|
||||
function configure() {
|
||||
if (!isValueOfStringEnum(MessagingProviderType, $provider.type)) {
|
||||
throw new Error(`Invalid provider type: ${$provider.type}`);
|
||||
}
|
||||
|
||||
if (!isValueOfStringEnum(Providers, $provider.provider)) {
|
||||
throw new Error(`Invalid provider: ${$provider.provider}`);
|
||||
}
|
||||
$providerType = $provider.type;
|
||||
$wizardProvider = $provider.provider;
|
||||
|
||||
switch ($wizardProvider) {
|
||||
case Providers.Twilio:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
accountSid: $provider.credentials['accountSid'],
|
||||
authToken: $provider.credentials['authToken'],
|
||||
from: $provider.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Msg91:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
from: $provider.options['from'],
|
||||
senderId: $provider.credentials['senderId'],
|
||||
authKey: $provider.credentials['authKey']
|
||||
};
|
||||
break;
|
||||
case Providers.Telesign:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
username: $provider.credentials['username'],
|
||||
password: $provider.credentials['password'],
|
||||
from: $provider.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Textmagic:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
username: $provider.credentials['username'],
|
||||
apiKey: $provider.credentials['apiKey'],
|
||||
from: $provider.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Vonage:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
apiKey: $provider.credentials['apiKey'],
|
||||
apiSecret: $provider.credentials['apiSecret'],
|
||||
from: $provider.options['from']
|
||||
};
|
||||
break;
|
||||
case Providers.Mailgun:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
isEuRegion: false,
|
||||
fromEmail: $provider.options['fromEmail'],
|
||||
fromName: $provider.options['fromName'],
|
||||
replyToEmail: $provider.options['replyToEmail'],
|
||||
replyToName: $provider.options['replyToName'],
|
||||
apiKey: $provider.credentials['apiKey'],
|
||||
domain: $provider.credentials['domain']
|
||||
};
|
||||
break;
|
||||
case Providers.Sendgrid:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
apiKey: $provider.credentials['apiKey'],
|
||||
fromEmail: $provider.options['fromEmail'],
|
||||
fromName: $provider.options['fromName'],
|
||||
replyToEmail: $provider.options['replyToEmail'],
|
||||
replyToName: $provider.options['replyToName']
|
||||
};
|
||||
break;
|
||||
case Providers.SMTP:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
host: $provider.credentials['host'],
|
||||
port: $provider.credentials['port'],
|
||||
username: $provider.credentials['username'],
|
||||
password: $provider.credentials['password'],
|
||||
fromName: $provider.options['fromName'],
|
||||
fromEmail: $provider.options['fromEmail'],
|
||||
replyToName: $provider.options['replyToName'],
|
||||
replyToEmail: $provider.options['replyToEmail'],
|
||||
encryption: $provider.options['encryption'],
|
||||
autoTLS: $provider.options['autoTLS'],
|
||||
mailer: $provider.options['mailer']
|
||||
};
|
||||
break;
|
||||
case Providers.FCM:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled
|
||||
};
|
||||
if ('serviceAccountJSON' in $provider.credentials) {
|
||||
const serviceAccountJSON = $provider.credentials['serviceAccountJSON'];
|
||||
if (typeof serviceAccountJSON === 'string') {
|
||||
$providerParams[$wizardProvider].serviceAccountJSON = serviceAccountJSON;
|
||||
} else if (serviceAccountJSON instanceof Object) {
|
||||
$providerParams[$wizardProvider].serviceAccountJSON =
|
||||
JSON.stringify(serviceAccountJSON);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case Providers.APNS:
|
||||
$providerParams[$wizardProvider] = {
|
||||
providerId: $provider.$id,
|
||||
name: $provider.name,
|
||||
enabled: $provider.enabled,
|
||||
authKey: $provider.credentials['authKey'],
|
||||
authKeyId: $provider.credentials['authKeyId'],
|
||||
teamId: $provider.credentials['teamId'],
|
||||
bundleId: $provider.credentials['bundleId']
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
wizard.start(Update);
|
||||
}
|
||||
|
||||
async function updateStatus() {
|
||||
try {
|
||||
let response = { $id: '', name: '' };
|
||||
const providerId = $provider.$id;
|
||||
switch ($provider.provider) {
|
||||
const providerId = $providerData.$id;
|
||||
switch ($providerData.provider) {
|
||||
case Providers.Twilio:
|
||||
response = await sdk.forProject.messaging.updateTwilioProvider(
|
||||
providerId,
|
||||
@@ -237,14 +94,14 @@
|
||||
);
|
||||
break;
|
||||
case Providers.FCM:
|
||||
response = await sdk.forProject.messaging.updateFCMProvider(
|
||||
response = await sdk.forProject.messaging.updateFcmProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
enabled
|
||||
);
|
||||
break;
|
||||
case Providers.APNS:
|
||||
response = await sdk.forProject.messaging.updateAPNSProvider(
|
||||
response = await sdk.forProject.messaging.updateApnsProvider(
|
||||
providerId,
|
||||
undefined,
|
||||
enabled
|
||||
@@ -257,7 +114,7 @@
|
||||
message: `${response.name} has been ${enabled ? 'enabled' : 'disabled'}`
|
||||
});
|
||||
trackEvent(Submit.MessagingProviderUpdate, {
|
||||
provider: $provider
|
||||
provider: $providerData
|
||||
});
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
@@ -271,8 +128,8 @@
|
||||
|
||||
<CardGrid>
|
||||
<div class="grid-1-2-col-1 u-flex u-cross-center u-gap-16" data-private>
|
||||
<Provider provider={$provider.provider} size="l">
|
||||
<Heading tag="h6" size="7">{$provider.name}</Heading>
|
||||
<Provider provider={$providerData.provider} size="l">
|
||||
<Heading tag="h6" size="7">{$providerData.name}</Heading>
|
||||
</Provider>
|
||||
</div>
|
||||
<svelte:fragment slot="aside">
|
||||
@@ -284,17 +141,16 @@
|
||||
label={enabled ? 'Enabled' : 'Disabled'}
|
||||
bind:value={enabled} />
|
||||
</ul>
|
||||
<p class="title">Provider: <Provider noIcon provider={$provider.provider} /></p>
|
||||
<p class="title">Channel: <ProviderType noIcon type={$provider.type} /></p>
|
||||
<p>Created: {toLocaleDateTime($provider.$createdAt)}</p>
|
||||
<p class="title">Provider: <Provider noIcon provider={$providerData.provider} /></p>
|
||||
<p class="title">Channel: <ProviderType noIcon type={$providerData.type} /></p>
|
||||
<p>Created: {toLocaleDateTime($providerData.$createdAt)}</p>
|
||||
</div>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<div class="u-flex u-flex-wrap u-gap-12">
|
||||
<Button secondary on:click={() => configure()}>Configure</Button>
|
||||
<Button disabled={$provider.enabled === enabled} on:click={() => updateStatus()}
|
||||
<Button disabled={$providerData.enabled === enabled} on:click={() => updateStatus()}
|
||||
>Update</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -0,0 +1,141 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
FormList,
|
||||
InputDomain,
|
||||
InputEmail,
|
||||
InputFile,
|
||||
InputSelect,
|
||||
InputPhone,
|
||||
InputSwitch,
|
||||
InputText,
|
||||
InputPassword
|
||||
} from '$lib/elements/forms';
|
||||
import InputCheckbox from '$lib/elements/forms/inputCheckbox.svelte';
|
||||
import PopoverContent from './popoverContent.svelte';
|
||||
import type {
|
||||
ProviderInput,
|
||||
TwilioProviderParams,
|
||||
Msg91ProviderParams,
|
||||
TelesignProviderParams,
|
||||
TextmagicProviderParams,
|
||||
VonageProviderParams,
|
||||
MailgunProviderParams,
|
||||
SendgridProviderParams,
|
||||
SMTPProviderParams,
|
||||
FCMProviderParams,
|
||||
APNSProviderParams
|
||||
} from './store';
|
||||
|
||||
export let files: Record<string, FileList>;
|
||||
export let inputs: ProviderInput[];
|
||||
export let params: Partial<
|
||||
| TwilioProviderParams
|
||||
| Msg91ProviderParams
|
||||
| TelesignProviderParams
|
||||
| TextmagicProviderParams
|
||||
| VonageProviderParams
|
||||
| MailgunProviderParams
|
||||
| SendgridProviderParams
|
||||
| SMTPProviderParams
|
||||
| FCMProviderParams
|
||||
| APNSProviderParams
|
||||
>;
|
||||
</script>
|
||||
|
||||
<FormList>
|
||||
{#each inputs as input}
|
||||
{@const popoverImage = input.popoverImage}
|
||||
{#if input.type === 'text'}
|
||||
<InputText
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={params[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<PopoverContent lines={input.popover} {popoverImage} />
|
||||
</svelte:fragment>
|
||||
</InputText>
|
||||
{:else if input.type === 'password'}
|
||||
<InputPassword
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
showPasswordButton
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={params[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<PopoverContent lines={input.popover} {popoverImage} />
|
||||
</svelte:fragment>
|
||||
</InputPassword>
|
||||
{:else if input.type === 'email'}
|
||||
<InputEmail
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={params[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<PopoverContent lines={input.popover} {popoverImage} />
|
||||
</svelte:fragment>
|
||||
</InputEmail>
|
||||
{:else if input.type === 'domain'}
|
||||
<InputDomain
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={params[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<PopoverContent lines={input.popover} {popoverImage} />
|
||||
</svelte:fragment>
|
||||
</InputDomain>
|
||||
{:else if input.type === 'phone'}
|
||||
<InputPhone
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={params[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<PopoverContent lines={input.popover} {popoverImage} />
|
||||
</svelte:fragment>
|
||||
</InputPhone>
|
||||
{:else if input.type === 'file'}
|
||||
<InputFile
|
||||
label={input.label}
|
||||
allowedFileExtensions={input.allowedFileExtensions}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:files={files[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<PopoverContent lines={input.popover} {popoverImage} />
|
||||
</svelte:fragment>
|
||||
</InputFile>
|
||||
{:else if input.type === 'switch'}
|
||||
<InputSwitch label={input.label} id={input.name} bind:value={params[input.name]}>
|
||||
<svelte:fragment slot="description">
|
||||
<p>{@html input.description}</p>
|
||||
</svelte:fragment>
|
||||
</InputSwitch>
|
||||
{:else if input.type === 'checkbox'}
|
||||
<InputCheckbox label={input.label} id={input.name} bind:checked={params[input.name]}>
|
||||
<svelte:fragment slot="description">
|
||||
{input.description}
|
||||
</svelte:fragment>
|
||||
</InputCheckbox>
|
||||
{:else if input.type === 'select'}
|
||||
<InputSelect
|
||||
label={input.label}
|
||||
id={input.name}
|
||||
options={input.options}
|
||||
required={!input.optional}
|
||||
bind:value={params[input.name]} />
|
||||
{/if}
|
||||
{/each}
|
||||
</FormList>
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Column } from '$lib/helpers/types';
|
||||
import { writable } from 'svelte/store';
|
||||
import { Providers } from '../provider.svelte';
|
||||
import { MessagingProviderType, Encryption } from '@appwrite.io/console';
|
||||
import { MessagingProviderType, SmtpEncryption } from '@appwrite.io/console';
|
||||
|
||||
export const columns = writable<Column[]>([
|
||||
{ id: '$id', title: 'Provider ID', type: 'string', show: true },
|
||||
@@ -11,6 +11,28 @@ export const columns = writable<Column[]>([
|
||||
{ id: 'enabled', title: 'Status', type: 'boolean', show: true }
|
||||
]);
|
||||
|
||||
export type ProviderInput = {
|
||||
label: string;
|
||||
name: string;
|
||||
type:
|
||||
| 'text'
|
||||
| 'password'
|
||||
| 'phone'
|
||||
| 'email'
|
||||
| 'domain'
|
||||
| 'file'
|
||||
| 'switch'
|
||||
| 'select'
|
||||
| 'checkbox';
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
popover?: string[];
|
||||
popoverImage?: { src: { light: string; dark: string }; alt: string };
|
||||
allowedFileExtensions?: string[];
|
||||
optional?: boolean;
|
||||
options?: { label: string; value: string | number | boolean }[];
|
||||
};
|
||||
|
||||
type ProvidersMap = {
|
||||
[key in MessagingProviderType]: {
|
||||
name: string;
|
||||
@@ -23,25 +45,7 @@ type ProvidersMap = {
|
||||
title: string;
|
||||
description: string;
|
||||
needAHand?: string[];
|
||||
configure: {
|
||||
label: string;
|
||||
name: string;
|
||||
type:
|
||||
| 'text'
|
||||
| 'password'
|
||||
| 'phone'
|
||||
| 'email'
|
||||
| 'domain'
|
||||
| 'file'
|
||||
| 'switch'
|
||||
| 'select';
|
||||
placeholder?: string;
|
||||
description?: string;
|
||||
popover?: string[];
|
||||
allowedFileExtensions?: string[];
|
||||
optional?: boolean;
|
||||
options?: { label: string; value: string | number | boolean }[];
|
||||
}[];
|
||||
configure: ProviderInput[];
|
||||
};
|
||||
};
|
||||
};
|
||||
@@ -73,7 +77,14 @@ export const providers: ProvidersMap = {
|
||||
'<b>How to get the <a class="link" href="https://firebase.google.com" target="_blank" rel="noopener noreferrer">FCM</a> service account JSON?</b>',
|
||||
'Head to <b>Project settings -> Service accounts -> Generate new private key.</b>',
|
||||
'Generating the new key will result in the download of a JSON file.'
|
||||
]
|
||||
],
|
||||
popoverImage: {
|
||||
src: {
|
||||
light: '/images/messaging/fcm-service-account-json-light.png',
|
||||
dark: '/images/messaging/fcm-service-account-json-dark.png'
|
||||
},
|
||||
alt: 'Screenshot of how to generate the Firebase Service Account JSON file'
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -89,8 +100,15 @@ export const providers: ProvidersMap = {
|
||||
placeholder: 'Enter team ID',
|
||||
popover: [
|
||||
'<b>How to get the team ID?</b>',
|
||||
'Head to <b><a class="link" href="https://idmsa.apple.com/IDMSWebAuth/signin?appIdKey=891bd3417a7776362562d2197f89480a8547b108fd934911bcbea0110d07f757&path=%2Faccount%2F&rv=1" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a> -> Membership details -> Team ID.</b>'
|
||||
]
|
||||
'Head to <b><a style="link" href="https://developer.apple.com/account" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a> -> Membership details -> Team ID.</b>'
|
||||
],
|
||||
popoverImage: {
|
||||
src: {
|
||||
light: '/images/messaging/apns-team-id.png',
|
||||
dark: '/images/messaging/apns-team-id.png'
|
||||
},
|
||||
alt: 'Screenshot of where to find the Team ID in the Apple Developer Member Center'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Bundle ID',
|
||||
@@ -99,27 +117,15 @@ export const providers: ProvidersMap = {
|
||||
placeholder: 'Enter bundle ID',
|
||||
popover: [
|
||||
'<b>How to get the bundle ID?</b>',
|
||||
'Head to <b><a class="link" href="https://idmsa.apple.com/IDMSWebAuth/signin?appIdKey=891bd3417a7776362562d2197f89480a8547b108fd934911bcbea0110d07f757&path=%2Faccount%2F&rv=1" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a> -> Certificates, Identifiers & Profiles -> Identifiers.</b>',
|
||||
`<a
|
||||
href="/images/apns-bundle-id.png"
|
||||
class="file-preview is-with-image"
|
||||
target="_blank" rel="noopener noreferrer"
|
||||
rel="noopener noreferrer"
|
||||
aria-label="open file in new window">
|
||||
<div class="file-preview-image">
|
||||
<img
|
||||
width="205"
|
||||
height="125"
|
||||
src="/images/apns-bundle-id.png"
|
||||
alt="Screenshot of Bundle ID in Apple" />
|
||||
</div>
|
||||
<div class="file-preview-content">
|
||||
<div class="avatar">
|
||||
<span class="icon-external-link" aria-hidden="true" />
|
||||
</div>
|
||||
</div>
|
||||
</a>`
|
||||
]
|
||||
'Head to <b><a class="link" href="https://developer.apple.com/account" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a> -> Certificates, Identifiers & Profiles -> Identifiers.</b>'
|
||||
],
|
||||
popoverImage: {
|
||||
src: {
|
||||
light: '/images/messaging/apns-bundle-id.png',
|
||||
dark: '/images/messaging/apns-bundle-id.png'
|
||||
},
|
||||
alt: 'Screenshot of where to find the Bundle ID in the Apple Developer Member Center'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Authentication key ID',
|
||||
@@ -128,9 +134,16 @@ export const providers: ProvidersMap = {
|
||||
placeholder: 'Enter key ID',
|
||||
popover: [
|
||||
'<b>How to get the auth key ID?</b>',
|
||||
'Head to <b><a class="link" href="https://idmsa.apple.com/IDMSWebAuth/signin?appIdKey=891bd3417a7776362562d2197f89480a8547b108fd934911bcbea0110d07f757&path=%2Faccount%2F&rv=1" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a> -> Certificates, Identifiers & Profiles -> Keys.</b>',
|
||||
'Head to <b><a class="link" href="https://developer.apple.com/account" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a> -> Certificates, Identifiers & Profiles -> Keys.</b>',
|
||||
'Click on your key to view details.'
|
||||
]
|
||||
],
|
||||
popoverImage: {
|
||||
src: {
|
||||
light: '/images/messaging/apns-authentication-key-id.png',
|
||||
dark: '/images/messaging/apns-authentication-key-id.png'
|
||||
},
|
||||
alt: 'Screenshot of how to find the Authentication Key ID in the Apple Developer Member Center'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Auth key (.p8 file)',
|
||||
@@ -139,9 +152,23 @@ export const providers: ProvidersMap = {
|
||||
allowedFileExtensions: ['p8'],
|
||||
popover: [
|
||||
'<b>How to get the authentication key?</b>',
|
||||
'Head to <b><a class="link" href="https://idmsa.apple.com/IDMSWebAuth/signin?appIdKey=891bd3417a7776362562d2197f89480a8547b108fd934911bcbea0110d07f757&path=%2Faccount%2F&rv=1" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a></b> (under Program resources) <b>-> Certificates, Identifiers & Profiles -> Keys.</b>',
|
||||
'Head to <b><a class="link" href="https://developer.apple.com/account" target="_blank" rel="noopener noreferrer">Apple Developer Member Center</a></b> (under Program resources) <b>-> Certificates, Identifiers & Profiles -> Keys.</b>',
|
||||
'Create a key and give it a name. Enable the Apple Push Notifications service (APNS), and register your key.'
|
||||
]
|
||||
],
|
||||
popoverImage: {
|
||||
src: {
|
||||
light: '/images/messaging/apns-auth-key.png',
|
||||
dark: '/images/messaging/apns-auth-key.png'
|
||||
},
|
||||
alt: 'Screenshot of where to download the Authentication Key in the Apple Developer Member Center'
|
||||
}
|
||||
},
|
||||
{
|
||||
label: 'Sandbox',
|
||||
name: 'sandbox',
|
||||
type: 'switch',
|
||||
description:
|
||||
'Enable sandbox mode for testing on apps signed with development provisioning profiles. <a class="link" href="https://appwrite.io/docs/products/messaging/apns#configure-provider" target="_blank" rel="noopener noreferrer">Learn more</a>.'
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -300,7 +327,7 @@ export const providers: ProvidersMap = {
|
||||
label: 'Server host',
|
||||
name: 'host',
|
||||
type: 'text',
|
||||
placeholder: 'Enter host'
|
||||
placeholder: 'smtp.server.com'
|
||||
},
|
||||
{
|
||||
label: 'Server port',
|
||||
@@ -328,16 +355,16 @@ export const providers: ProvidersMap = {
|
||||
name: 'encryption',
|
||||
type: 'select',
|
||||
options: [
|
||||
{ label: 'None', value: Encryption.None },
|
||||
{ label: 'SSL', value: Encryption.Ssl },
|
||||
{ label: 'TLS', value: Encryption.Tls }
|
||||
{ label: 'None', value: SmtpEncryption.None },
|
||||
{ label: 'SSL', value: SmtpEncryption.Ssl },
|
||||
{ label: 'TLS', value: SmtpEncryption.Tls }
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Auto TLS',
|
||||
name: 'autoTLS',
|
||||
description: 'Automatically uses TLS encryption',
|
||||
type: 'switch',
|
||||
description: 'Automatically uses TLS encryption if available.',
|
||||
type: 'checkbox',
|
||||
optional: true
|
||||
},
|
||||
{
|
||||
@@ -345,7 +372,11 @@ export const providers: ProvidersMap = {
|
||||
name: 'mailer',
|
||||
type: 'text',
|
||||
optional: true,
|
||||
placeholder: 'Enter mailer'
|
||||
placeholder: 'Enter mailer',
|
||||
popover: [
|
||||
'<b>What is the Mailer?</b>',
|
||||
'The value to use for the X-Mailer header.'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -439,16 +470,21 @@ export const providers: ProvidersMap = {
|
||||
description: '',
|
||||
configure: [
|
||||
{
|
||||
label: 'Username',
|
||||
name: 'username',
|
||||
label: 'Customer ID',
|
||||
name: 'customerId',
|
||||
type: 'text',
|
||||
placeholder: 'Enter username'
|
||||
placeholder: 'Enter customer ID'
|
||||
},
|
||||
{
|
||||
label: 'Password',
|
||||
name: 'password',
|
||||
label: 'API Key',
|
||||
name: 'apiKey',
|
||||
type: 'password',
|
||||
placeholder: 'Enter password'
|
||||
placeholder: 'Enter API key',
|
||||
popover: [
|
||||
'<b>How to get the API key?</b>',
|
||||
'Create an account in <a class="link" href="https://portal.telesign.com/" target="_blank" rel="noopener noreferrer">Telesign</a>.',
|
||||
'Head to <b>Telesign portal -> Profile -> API Keys.</b>'
|
||||
]
|
||||
},
|
||||
{
|
||||
label: 'Sender number',
|
||||
@@ -525,3 +561,96 @@ export const providers: ProvidersMap = {
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
type ProviderParams = {
|
||||
providerId: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* SMS providers
|
||||
*/
|
||||
export type TwilioProviderParams = ProviderParams & {
|
||||
accountSid: string;
|
||||
authToken: string;
|
||||
from: string;
|
||||
};
|
||||
|
||||
export type Msg91ProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
senderId: string;
|
||||
authKey: string;
|
||||
};
|
||||
|
||||
export type TelesignProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
customerId: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export type TextmagicProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
username: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export type VonageProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
apiKey: string;
|
||||
apiSecret: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Email providers
|
||||
*/
|
||||
export type MailgunProviderParams = ProviderParams & {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
replyToEmail: string;
|
||||
replyToName: string;
|
||||
isEuRegion: boolean;
|
||||
apiKey: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
export type SendgridProviderParams = ProviderParams & {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
replyToEmail: string;
|
||||
replyToName: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export type SMTPProviderParams = ProviderParams & {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
replyToEmail: string;
|
||||
replyToName: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
encryption: SmtpEncryption;
|
||||
autoTLS: boolean;
|
||||
mailer: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push providers
|
||||
*/
|
||||
export type FCMProviderParams = ProviderParams & {
|
||||
serviceAccountJSON: string;
|
||||
};
|
||||
|
||||
export type APNSProviderParams = ProviderParams & {
|
||||
authKey: string;
|
||||
authKeyId: string;
|
||||
teamId: string;
|
||||
bundleId: string;
|
||||
sandbox: boolean;
|
||||
};
|
||||
|
||||
export type MQTTProviderParams = ProviderParams & {
|
||||
serverKey: string;
|
||||
};
|
||||
|
||||
@@ -1,164 +0,0 @@
|
||||
<script lang="ts">
|
||||
import { Wizard } from '$lib/layout';
|
||||
import type { WizardStepsType } from '$lib/layout/wizard.svelte';
|
||||
import Configure from './wizard/configure.svelte';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { project } from '../../store';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { provider, providerParams } from './wizard/store';
|
||||
import { Providers } from '../provider.svelte';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
|
||||
async function update() {
|
||||
try {
|
||||
let response: Models.Provider;
|
||||
const providerId = $providerParams[$provider].providerId;
|
||||
switch ($provider) {
|
||||
case Providers.Twilio:
|
||||
response = await sdk.forProject.messaging.updateTwilioProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].accountSid,
|
||||
$providerParams[$provider].authToken,
|
||||
$providerParams[$provider].from
|
||||
);
|
||||
break;
|
||||
case Providers.Msg91:
|
||||
response = await sdk.forProject.messaging.updateMsg91Provider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].senderId,
|
||||
$providerParams[$provider].authKey,
|
||||
$providerParams[$provider].from
|
||||
);
|
||||
break;
|
||||
case Providers.Telesign:
|
||||
response = await sdk.forProject.messaging.updateTelesignProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].username,
|
||||
$providerParams[$provider].password,
|
||||
$providerParams[$provider].from
|
||||
);
|
||||
break;
|
||||
case Providers.Textmagic:
|
||||
response = await sdk.forProject.messaging.updateTextmagicProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].username,
|
||||
$providerParams[$provider].apiKey,
|
||||
$providerParams[$provider].from
|
||||
);
|
||||
break;
|
||||
case Providers.Vonage:
|
||||
response = await sdk.forProject.messaging.updateVonageProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].apiKey,
|
||||
$providerParams[$provider].apiSecret,
|
||||
$providerParams[$provider].from
|
||||
);
|
||||
break;
|
||||
case Providers.Mailgun:
|
||||
response = await sdk.forProject.messaging.updateMailgunProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].apiKey,
|
||||
$providerParams[$provider].domain,
|
||||
$providerParams[$provider].isEuRegion,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].fromName || undefined,
|
||||
$providerParams[$provider].fromEmail,
|
||||
$providerParams[$provider].replyToName || undefined,
|
||||
$providerParams[$provider].replyToEmail || undefined
|
||||
);
|
||||
break;
|
||||
case Providers.Sendgrid:
|
||||
response = await sdk.forProject.messaging.updateSendgridProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].apiKey,
|
||||
$providerParams[$provider].fromName || undefined,
|
||||
$providerParams[$provider].fromEmail,
|
||||
$providerParams[$provider].replyToName || undefined,
|
||||
$providerParams[$provider].replyToEmail || undefined
|
||||
);
|
||||
break;
|
||||
case Providers.SMTP:
|
||||
response = await sdk.forProject.messaging.updateSmtpProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].host,
|
||||
$providerParams[$provider].port || undefined,
|
||||
$providerParams[$provider].username || undefined,
|
||||
$providerParams[$provider].password || undefined,
|
||||
$providerParams[$provider].encryption,
|
||||
$providerParams[$provider].autoTLS,
|
||||
$providerParams[$provider].mailer || undefined,
|
||||
$providerParams[$provider].fromName || undefined,
|
||||
$providerParams[$provider].fromEmail,
|
||||
$providerParams[$provider].replyToName || undefined,
|
||||
$providerParams[$provider].replyToEmail || undefined,
|
||||
$providerParams[$provider].enabled
|
||||
);
|
||||
break;
|
||||
case Providers.FCM:
|
||||
response = await sdk.forProject.messaging.updateFCMProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
JSON.parse($providerParams[$provider].serviceAccountJSON)
|
||||
);
|
||||
break;
|
||||
case Providers.APNS:
|
||||
response = await sdk.forProject.messaging.updateAPNSProvider(
|
||||
providerId,
|
||||
$providerParams[$provider].name,
|
||||
$providerParams[$provider].enabled,
|
||||
$providerParams[$provider].authKey,
|
||||
$providerParams[$provider].authKeyId,
|
||||
$providerParams[$provider].teamId,
|
||||
$providerParams[$provider].bundleId
|
||||
);
|
||||
break;
|
||||
}
|
||||
await invalidate(Dependencies.MESSAGING_PROVIDER);
|
||||
wizard.hide();
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: `${response.name} has been updated`
|
||||
});
|
||||
trackEvent(Submit.MessagingProviderUpdate, {
|
||||
provider: $provider
|
||||
});
|
||||
await goto(
|
||||
`${base}/console/project-${$project.$id}/messaging/providers/provider-${response.$id}`
|
||||
);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
type: 'error',
|
||||
message: error.message
|
||||
});
|
||||
trackError(error, Submit.MessagingProviderUpdate);
|
||||
}
|
||||
}
|
||||
|
||||
const stepsComponents: WizardStepsType = new Map();
|
||||
stepsComponents.set(1, {
|
||||
label: 'Configure',
|
||||
component: Configure
|
||||
});
|
||||
</script>
|
||||
|
||||
<Wizard title="Update provider" steps={stepsComponents} on:finish={update} finalAction="Update" />
|
||||
@@ -1,244 +0,0 @@
|
||||
<script lang="ts">
|
||||
import {
|
||||
FormList,
|
||||
InputDomain,
|
||||
InputEmail,
|
||||
InputFile,
|
||||
InputSelect,
|
||||
InputSwitch,
|
||||
InputText,
|
||||
InputPassword
|
||||
} from '$lib/elements/forms';
|
||||
import InputPhone from '$lib/elements/forms/inputPhone.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { providers } from '../store';
|
||||
import { providerType, provider, providerParams } from './store';
|
||||
import { Table, TableBody, TableCell, TableRowButton, TableRowLink } from '$lib/elements/table';
|
||||
import { newMemberModal } from '$lib/stores/organization';
|
||||
import CreateMember from '$routes/console/organization-[organization]/createMember.svelte';
|
||||
import Collapsible from '$lib/components/collapsible.svelte';
|
||||
import CollapsibleItem from '$lib/components/collapsibleItem.svelte';
|
||||
import Provider from '../../provider.svelte';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import ProviderTypeComponent from '$routes/console/project-[project]/messaging/providerType.svelte';
|
||||
|
||||
let files: Record<string, FileList> = {};
|
||||
const inputs = providers[$providerType].providers[$provider].configure;
|
||||
|
||||
onMount(() => {
|
||||
for (const input of inputs) {
|
||||
if (input.type === 'file' && $providerParams[$provider][input.name].length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
const f = new File(
|
||||
[$providerParams[$provider][input.name]],
|
||||
`${input.name}.${input.allowedFileExtensions}`
|
||||
);
|
||||
dataTransfer.items.add(f);
|
||||
files[input.name] = dataTransfer.files;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function beforeSubmit() {
|
||||
const promises = [];
|
||||
for (const [key, value] of Object.entries(files)) {
|
||||
const promise = value[0].text().then((text) => {
|
||||
$providerParams[$provider][key] = text;
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Configure</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Set up the credentials below to enable {providers[$providerType].providers[$provider].title}
|
||||
for sending
|
||||
{providers[$providerType].text}.
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
{#each inputs as input}
|
||||
{#if input.type === 'text'}
|
||||
<InputText
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={$providerParams[$provider][input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
{@html input.popover?.join('<br/><br/>')}
|
||||
</svelte:fragment>
|
||||
</InputText>
|
||||
{:else if input.type === 'password'}
|
||||
<InputPassword
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
showPasswordButton
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={$providerParams[$provider][input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
{@html input.popover?.join('<br/><br/>')}
|
||||
</svelte:fragment>
|
||||
</InputPassword>
|
||||
{:else if input.type === 'email'}
|
||||
<InputEmail
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={$providerParams[$provider][input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<p class="body-text-2 u-margin-block-end-16">
|
||||
{@html input.popover?.join('<br/><br/>')}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</InputEmail>
|
||||
{:else if input.type === 'domain'}
|
||||
<InputDomain
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={$providerParams[$provider][input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<p class="body-text-2 u-margin-block-end-16">
|
||||
{@html input.popover?.join('<br/><br/>')}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</InputDomain>
|
||||
{:else if input.type === 'phone'}
|
||||
<InputPhone
|
||||
id={input.name}
|
||||
label={input.label}
|
||||
placeholder={input.placeholder}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:value={$providerParams[$provider][input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<p class="body-text-2 u-margin-block-end-16">
|
||||
{@html input.popover?.join('<br/><br/>')}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</InputPhone>
|
||||
{:else if input.type === 'file'}
|
||||
<InputFile
|
||||
label={input.label}
|
||||
allowedFileExtensions={input.allowedFileExtensions}
|
||||
required={!input.optional}
|
||||
isPopoverDefined={input.popover !== undefined}
|
||||
bind:files={files[input.name]}>
|
||||
<svelte:fragment slot="popover">
|
||||
<p class="body-text-2 u-margin-block-end-16">
|
||||
{@html input.popover?.join('<br/><br/>')}
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</InputFile>
|
||||
{:else if input.type === 'switch'}
|
||||
<InputSwitch
|
||||
label={input.label}
|
||||
id={input.name}
|
||||
bind:value={$providerParams[$provider][input.name]}>
|
||||
<svelte:fragment slot="description">
|
||||
{input.description}
|
||||
</svelte:fragment>
|
||||
</InputSwitch>
|
||||
{:else if input.type === 'select'}
|
||||
<InputSelect
|
||||
label={input.label}
|
||||
id={input.name}
|
||||
options={input.options}
|
||||
required={!input.optional}
|
||||
bind:value={$providerParams[$provider][input.name]} />
|
||||
{/if}
|
||||
{/each}
|
||||
</FormList>
|
||||
|
||||
<div class="need-a-hand u-flex-vertical u-gap-8">
|
||||
<p class="body-text-2 u-bold u-margin-block-start-48">Need a hand?</p>
|
||||
|
||||
{#if providers[$providerType].providers[$provider].needAHand}
|
||||
{@const needAHand = providers[$providerType].providers[$provider].needAHand}
|
||||
<div class="box u-padding-inline-8 u-padding-0 u-border-width-0">
|
||||
<Collapsible>
|
||||
<CollapsibleItem withIndentation>
|
||||
<svelte:fragment slot="beforetitle">
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
class="icon-info"
|
||||
style:--p-text-size="1.25rem"
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
<span class="body-text-2 u-small u-bold"
|
||||
>How to enable <Provider provider={$provider} noIcon />
|
||||
{#if $providerType == MessagingProviderType.Push}notifications{:else}<ProviderTypeComponent
|
||||
type={$providerType}
|
||||
noIcon />{/if} service?</span>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
{#each needAHand as p}
|
||||
<p>
|
||||
{@html p}
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
<Table noMargin noStyles>
|
||||
<TableBody>
|
||||
<TableRowLink href={`https://appwrite.io/docs/messaging/${$provider}`}>
|
||||
<TableCell>
|
||||
<div class="u-flex u-cross-center u-main-space-between u-padding-8">
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
class="icon-book-open"
|
||||
style:--p-text-size="1.25rem"
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
Read the full guide in the documentation
|
||||
</div>
|
||||
<span class="icon-arrow-right" aria-hidden="true" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRowLink>
|
||||
<TableRowButton on:click={() => ($newMemberModal = true)}>
|
||||
<TableCell>
|
||||
<div class="u-flex u-cross-center u-main-space-between u-padding-8">
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
class="icon-user-group"
|
||||
style:--p-text-size="1.25rem"
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
Invite a team member to complete this step
|
||||
</div>
|
||||
<span class="icon-arrow-right" aria-hidden="true" />
|
||||
</div>
|
||||
</TableCell>
|
||||
</TableRowButton>
|
||||
</TableBody>
|
||||
</Table>
|
||||
</div>
|
||||
</WizardStep>
|
||||
|
||||
<CreateMember bind:showCreate={$newMemberModal} />
|
||||
|
||||
<style lang="scss">
|
||||
.need-a-hand :global(.table-row):last-child:last-child {
|
||||
border-block-end: none;
|
||||
}
|
||||
</style>
|
||||
@@ -6,15 +6,13 @@
|
||||
import { FormList, InputText } from '$lib/elements/forms';
|
||||
import { Pill } from '$lib/elements';
|
||||
import { Providers } from '../../provider.svelte';
|
||||
import { Encryption } from '@appwrite.io/console';
|
||||
import { SmtpEncryption } from '@appwrite.io/console';
|
||||
|
||||
let name = '';
|
||||
let showCustomId = false;
|
||||
let id: string = null;
|
||||
|
||||
async function beforeSubmit() {
|
||||
console.log($provider);
|
||||
|
||||
switch ($provider) {
|
||||
case Providers.Twilio:
|
||||
$providerParams[$provider] = {
|
||||
@@ -41,8 +39,8 @@
|
||||
providerId: id,
|
||||
name: name,
|
||||
enabled: true,
|
||||
username: '',
|
||||
password: '',
|
||||
customerId: '',
|
||||
apiKey: '',
|
||||
from: ''
|
||||
};
|
||||
break;
|
||||
@@ -102,7 +100,7 @@
|
||||
username: '',
|
||||
password: '',
|
||||
autoTLS: true,
|
||||
encryption: Encryption.Tls,
|
||||
encryption: SmtpEncryption.Tls,
|
||||
mailer: '',
|
||||
fromEmail: '',
|
||||
fromName: '',
|
||||
@@ -126,7 +124,8 @@
|
||||
authKey: '',
|
||||
authKeyId: '',
|
||||
teamId: '',
|
||||
bundleId: ''
|
||||
bundleId: '',
|
||||
sandbox: false
|
||||
};
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,174 @@
|
||||
<script lang="ts">
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { onMount } from 'svelte';
|
||||
import { providers } from '../store';
|
||||
import { providerType, provider, providerParams } from './store';
|
||||
import SettingsFormList from '../settingsFormList.svelte';
|
||||
import { newMemberModal } from '$lib/stores/organization';
|
||||
import CreateMember from '$routes/console/organization-[organization]/createMember.svelte';
|
||||
import Provider from '../../provider.svelte';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import ProviderTypeComponent from '$routes/console/project-[project]/messaging/providerType.svelte';
|
||||
import {
|
||||
Collapsible,
|
||||
CollapsibleItem,
|
||||
ClickableList,
|
||||
ClickableListItem
|
||||
} from '$lib/components';
|
||||
|
||||
let files: Record<string, FileList> = {};
|
||||
const inputs = providers[$providerType].providers[$provider].configure;
|
||||
let open = false;
|
||||
|
||||
onMount(() => {
|
||||
for (const input of inputs) {
|
||||
if (input.type === 'file' && $providerParams[$provider][input.name].length > 0) {
|
||||
const dataTransfer = new DataTransfer();
|
||||
const f = new File(
|
||||
[$providerParams[$provider][input.name]],
|
||||
`${input.name}.${input.allowedFileExtensions}`
|
||||
);
|
||||
dataTransfer.items.add(f);
|
||||
files[input.name] = dataTransfer.files;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
async function beforeSubmit() {
|
||||
const promises = [];
|
||||
for (const [key, value] of Object.entries(files)) {
|
||||
const promise = value[0].text().then((text) => {
|
||||
$providerParams[$provider][key] = text;
|
||||
});
|
||||
promises.push(promise);
|
||||
}
|
||||
await Promise.all(promises);
|
||||
}
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Settings</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Set up the credentials below to enable {providers[$providerType].providers[$provider].title}
|
||||
for sending
|
||||
{providers[$providerType].text}.
|
||||
</svelte:fragment>
|
||||
|
||||
<SettingsFormList bind:files {inputs} bind:params={$providerParams[$provider]} />
|
||||
|
||||
<div class="need-a-hand u-flex-vertical u-gap-8">
|
||||
<p class="body-text-2 u-bold u-margin-block-start-48">Need a hand?</p>
|
||||
|
||||
<div>
|
||||
{#if providers[$providerType].providers[$provider].needAHand}
|
||||
{@const needAHand = providers[$providerType].providers[$provider].needAHand}
|
||||
<div
|
||||
class="how-to-enable u-padding-inline-8 u-padding-0"
|
||||
style={!open ? '' : 'background-color: hsl(var(--p-bg-color-hover));'}>
|
||||
<Collapsible>
|
||||
<CollapsibleItem withIndentation bind:open>
|
||||
<svelte:fragment slot="beforetitle">
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
class="icon-info"
|
||||
style:--p-text-size="1.25rem"
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
<span class="title body-text-2 u-small" class:u-bold={open}
|
||||
>How to enable <Provider provider={$provider} noIcon />
|
||||
{#if $providerType == MessagingProviderType.Push}notifications{:else}<ProviderTypeComponent
|
||||
type={$providerType}
|
||||
noIcon />{/if} service?</span>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
<div class="u-flex-vertical u-gap-16">
|
||||
{#each needAHand as p}
|
||||
<p>
|
||||
{@html p}
|
||||
</p>
|
||||
{/each}
|
||||
</div>
|
||||
</CollapsibleItem>
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="u-margin-block-start-4 u-sep-block-end" />
|
||||
{/if}
|
||||
|
||||
<ClickableList>
|
||||
<ClickableListItem
|
||||
href={`https://appwrite.io/docs/products/messaging/${$provider}`}
|
||||
external>
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
class="icon-book-open"
|
||||
style:--p-text-size="1.25rem"
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
<p>Read the full guide in the documentation</p>
|
||||
</div>
|
||||
<span class="icon-arrow-sm-right u-font-size-20" aria-hidden="true" />
|
||||
</div>
|
||||
</ClickableListItem>
|
||||
<ClickableListItem on:click={() => ($newMemberModal = true)}>
|
||||
<div class="u-flex u-cross-center u-main-space-between">
|
||||
<div class="u-flex u-cross-center u-gap-16">
|
||||
<div class="avatar is-size-small">
|
||||
<span
|
||||
class="icon-user-group"
|
||||
style:--p-text-size="1.25rem"
|
||||
aria-hidden="true" />
|
||||
</div>
|
||||
<p>Invite a team member to complete this step</p>
|
||||
</div>
|
||||
<span class="icon-arrow-sm-right u-font-size-20" aria-hidden="true" />
|
||||
</div>
|
||||
</ClickableListItem>
|
||||
</ClickableList>
|
||||
</div>
|
||||
</div>
|
||||
</WizardStep>
|
||||
|
||||
<CreateMember bind:showCreate={$newMemberModal} />
|
||||
|
||||
<style lang="scss">
|
||||
.need-a-hand {
|
||||
--p-bg-color-hover: var(--color-neutral-5);
|
||||
.how-to-enable {
|
||||
border-radius: var(--border-radius-small);
|
||||
|
||||
&:hover,
|
||||
&:focus {
|
||||
background-color: hsl(var(--p-bg-color-hover));
|
||||
|
||||
.title {
|
||||
font-weight: 600;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:global(.clickable-list) {
|
||||
--color-border: var(--color-neutral-5);
|
||||
--p-clickable-button-bg-color-hover: var(--color-border);
|
||||
|
||||
:global(.theme-dark) & {
|
||||
--color-border: var(--color-neutral-85);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.clickable-list-button) {
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
:global(.theme-dark) & {
|
||||
--p-bg-color-hover: var(--color-neutral-85);
|
||||
}
|
||||
}
|
||||
|
||||
:global(.clickable-list-button):hover p,
|
||||
:global(.clickable-list-button):focus p {
|
||||
font-weight: 600;
|
||||
}
|
||||
</style>
|
||||
@@ -1,101 +1,18 @@
|
||||
import type { MessagingProviderType } from '@appwrite.io/console';
|
||||
import { writable } from 'svelte/store';
|
||||
import type { Providers } from '../../provider.svelte';
|
||||
import type { MessagingProviderType, Encryption } from '@appwrite.io/console';
|
||||
|
||||
type ProviderParams = {
|
||||
providerId: string;
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
};
|
||||
|
||||
/**
|
||||
* SMS providers
|
||||
*/
|
||||
|
||||
export type TwilioProviderParams = ProviderParams & {
|
||||
accountSid: string;
|
||||
authToken: string;
|
||||
from: string;
|
||||
};
|
||||
|
||||
export type Msg91ProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
senderId: string;
|
||||
authKey: string;
|
||||
};
|
||||
|
||||
export type TelesignProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
username: string;
|
||||
password: string;
|
||||
};
|
||||
|
||||
export type TextmagicProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
username: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export type VonageProviderParams = ProviderParams & {
|
||||
from: string;
|
||||
apiKey: string;
|
||||
apiSecret: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Email providers
|
||||
*/
|
||||
|
||||
export type MailgunProviderParams = ProviderParams & {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
replyToEmail: string;
|
||||
replyToName: string;
|
||||
isEuRegion: boolean;
|
||||
apiKey: string;
|
||||
domain: string;
|
||||
};
|
||||
|
||||
export type SendgridProviderParams = ProviderParams & {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
replyToEmail: string;
|
||||
replyToName: string;
|
||||
apiKey: string;
|
||||
};
|
||||
|
||||
export type SMTPProviderParams = ProviderParams & {
|
||||
fromEmail: string;
|
||||
fromName: string;
|
||||
replyToEmail: string;
|
||||
replyToName: string;
|
||||
host: string;
|
||||
port: number;
|
||||
username: string;
|
||||
password: string;
|
||||
encryption: Encryption;
|
||||
autoTLS: boolean;
|
||||
mailer: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Push providers
|
||||
*/
|
||||
|
||||
export type FCMProviderParams = ProviderParams & {
|
||||
serviceAccountJSON: string;
|
||||
};
|
||||
|
||||
export type APNSProviderParams = ProviderParams & {
|
||||
authKey: string;
|
||||
authKeyId: string;
|
||||
teamId: string;
|
||||
bundleId: string;
|
||||
};
|
||||
|
||||
export type MQTTProviderParams = ProviderParams & {
|
||||
serverKey: string;
|
||||
};
|
||||
import type {
|
||||
APNSProviderParams,
|
||||
FCMProviderParams,
|
||||
MailgunProviderParams,
|
||||
Msg91ProviderParams,
|
||||
SMTPProviderParams,
|
||||
SendgridProviderParams,
|
||||
TelesignProviderParams,
|
||||
TextmagicProviderParams,
|
||||
TwilioProviderParams,
|
||||
VonageProviderParams
|
||||
} from '../store';
|
||||
|
||||
export const providerType = writable<MessagingProviderType>(null);
|
||||
export const provider = writable<Providers>(null);
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</div>
|
||||
<div class="project-name u-text-center u-x-small u-width-full-line">{$project.name}</div>
|
||||
<div class="chat u-text-center u-width-full-line">
|
||||
<div class="time u-text-center u-x-small u-width-full-line u-line-height-0-7">
|
||||
<div class="time u-text-center u-width-full-line u-line-height-0-7">
|
||||
Today {getTime()}
|
||||
</div>
|
||||
<div class="chat-bubble-container">
|
||||
@@ -51,7 +51,7 @@
|
||||
.phone {
|
||||
position: relative;
|
||||
width: 320px;
|
||||
height: 539px;
|
||||
height: 410px;
|
||||
background-image: url('./sms-preview-light.svg');
|
||||
background-repeat: no-repeat;
|
||||
|
||||
|
||||
@@ -3,10 +3,12 @@
|
||||
import DangerZone from './dangerZone.svelte';
|
||||
import Details from './details.svelte';
|
||||
import UpdateName from './updateName.svelte';
|
||||
import UpdatePermissions from './updatePermissions.svelte';
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
<Details />
|
||||
<UpdateName />
|
||||
<UpdatePermissions />
|
||||
<DangerZone />
|
||||
</Container>
|
||||
|
||||
+12
-6
@@ -46,9 +46,14 @@
|
||||
const targetIds = Object.keys($targetsById).filter(
|
||||
(targetId) => !(targetId in subscribersByTargetId)
|
||||
);
|
||||
const promises = targetIds.map((targetId) =>
|
||||
sdk.forProject.messaging.createSubscriber($page.params.topic, ID.unique(), targetId)
|
||||
);
|
||||
const promises = targetIds.map(async (targetId) => {
|
||||
const subscriber = await sdk.forProject.messaging.createSubscriber(
|
||||
$page.params.topic,
|
||||
ID.unique(),
|
||||
targetId
|
||||
);
|
||||
subscribersByTargetId[targetId] = subscriber;
|
||||
});
|
||||
|
||||
try {
|
||||
await Promise.all(promises);
|
||||
@@ -141,7 +146,7 @@
|
||||
<Empty
|
||||
single
|
||||
on:click={() => (showAdd = true)}
|
||||
href="https://appwrite.io/docs/products/messaging/topics/#subscribe-targets-to-topics"
|
||||
href="https://appwrite.io/docs/products/messaging/topics#subscribe-targets-to-topics"
|
||||
target="subscriber" />
|
||||
{/if}
|
||||
</Container>
|
||||
@@ -154,10 +159,11 @@
|
||||
<svelte:fragment slot="description">
|
||||
<p class="text">
|
||||
Add subscribers to this topic by selecting the targets for directing messages. <a
|
||||
href="https://appwrite.io/docs/products/messaging/topics"
|
||||
href="https://appwrite.io/docs/products/messaging/topics#subscribe-targets-to-topics"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">Learn more about subscribers.</a>
|
||||
class="link">Learn more about subscribers</a
|
||||
>.
|
||||
</p>
|
||||
</svelte:fragment>
|
||||
</UserTargetsModal>
|
||||
|
||||
+65
@@ -0,0 +1,65 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { CardGrid, Heading } from '$lib/components';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { Button, Form } from '$lib/elements/forms';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { onMount } from 'svelte';
|
||||
import { Roles } from '$lib/components/permissions';
|
||||
import { symmetricDifference } from '$lib/helpers/array';
|
||||
import { topic } from './store';
|
||||
|
||||
const topicId = $page.params.topic;
|
||||
|
||||
let arePermsDisabled = true;
|
||||
let permissions: string[] = [];
|
||||
|
||||
onMount(async () => {
|
||||
permissions = $topic.subscribe || [];
|
||||
});
|
||||
|
||||
async function updatePermissions() {
|
||||
try {
|
||||
await sdk.forProject.messaging.updateTopic(topicId, undefined, permissions);
|
||||
await invalidate(Dependencies.MESSAGING_TOPIC);
|
||||
addNotification({
|
||||
message: 'Permissions have been updated',
|
||||
type: 'success'
|
||||
});
|
||||
trackEvent(Submit.MessagingTopicUpdatePermissions);
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
message: error.message,
|
||||
type: 'error'
|
||||
});
|
||||
trackError(error, Submit.MessagingTopicUpdatePermissions);
|
||||
}
|
||||
}
|
||||
|
||||
$: arePermsDisabled = symmetricDifference(permissions, $topic.subscribe).length == 0;
|
||||
</script>
|
||||
|
||||
<Form onSubmit={updatePermissions}>
|
||||
<CardGrid>
|
||||
<Heading tag="h6" size="7" id="permissions">Subscription access</Heading>
|
||||
<p>
|
||||
Choose who can subscribe to this topic using the client API. Learn more about <a
|
||||
href="https://appwrite.io/docs/advanced/platform/permissions"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link">
|
||||
Permissions</a
|
||||
>.
|
||||
</p>
|
||||
<svelte:fragment slot="aside">
|
||||
<Roles bind:roles={permissions} />
|
||||
</svelte:fragment>
|
||||
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={arePermsDisabled} submit>Update</Button>
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
</Form>
|
||||
@@ -91,86 +91,112 @@
|
||||
</script>
|
||||
|
||||
<Modal {title} bind:show onSubmit={submit} on:close={reset} headerDivider={false} size="big">
|
||||
<p class="text">
|
||||
Select existing topics you want to send this message to its targets. The message will be
|
||||
sent only to <ProviderType type={providerType} noIcon /> targets.
|
||||
</p>
|
||||
<InputSearch
|
||||
autofocus
|
||||
disabled={totalResults === 0 && !search}
|
||||
placeholder="Search for topics"
|
||||
bind:value={search} />
|
||||
{#if Object.keys(topicResultsById).length > 0}
|
||||
<FormList>
|
||||
<Table noMargin noStyles>
|
||||
<TableBody>
|
||||
{#each Object.entries(topicResultsById) as [topicId, topic]}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<InputCheckbox
|
||||
id={topicId}
|
||||
disabled={!!topicsById[topicId]}
|
||||
checked={!!selected[topicId]}
|
||||
on:change={(event) => onTopicSelection(event, topic)}>
|
||||
<svelte:fragment slot="description">
|
||||
<span class="title">
|
||||
<span class="u-line-height-1-5">
|
||||
<span class="body-text-1 u-bold" data-private>
|
||||
{topic.name}
|
||||
</span>
|
||||
<span
|
||||
class="collapsible-button-optional"
|
||||
style="--p-toggle-optional-color: var(--color-neutral-50);">
|
||||
({getTotal(topic)} subscribers)
|
||||
</span>
|
||||
</span></span>
|
||||
</svelte:fragment>
|
||||
</InputCheckbox>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</FormList>
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text">Total results: {totalResults}</p>
|
||||
<PaginationInline limit={5} bind:offset sum={totalResults} hidePages />
|
||||
<div class="u-flex u-flex-vertical u-gap-32">
|
||||
<p class="text">
|
||||
Select existing topics you want to send this message to its targets. The message will be
|
||||
sent only to <ProviderType type={providerType} noIcon /> targets.
|
||||
</p>
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<InputSearch
|
||||
autofocus
|
||||
disabled={totalResults === 0 && !search}
|
||||
placeholder="Search for topics"
|
||||
bind:value={search} />
|
||||
<div>
|
||||
{#if Object.keys(topicResultsById).length > 0}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<FormList>
|
||||
<Table noMargin noStyles>
|
||||
<TableBody>
|
||||
{#each Object.entries(topicResultsById) as [topicId, topic]}
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<InputCheckbox
|
||||
id={topicId}
|
||||
disabled={!!topicsById[topicId]}
|
||||
checked={!!selected[topicId]}
|
||||
on:change={(event) =>
|
||||
onTopicSelection(event, topic)}>
|
||||
<svelte:fragment slot="description">
|
||||
<span class="title">
|
||||
<span class="u-line-height-1-5">
|
||||
<span
|
||||
class="body-text-1 u-bold"
|
||||
data-private>
|
||||
{topic.name}
|
||||
</span>
|
||||
<span
|
||||
class="collapsible-button-optional"
|
||||
style="--p-toggle-optional-color: var(--color-neutral-50);">
|
||||
({getTotal(topic)} targets)
|
||||
</span>
|
||||
</span></span>
|
||||
</svelte:fragment>
|
||||
</InputCheckbox>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
{/each}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</FormList>
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<p class="text">Total results: {totalResults}</p>
|
||||
<PaginationInline limit={5} bind:offset sum={totalResults} hidePages />
|
||||
</div>
|
||||
</div>
|
||||
{:else if search}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<EmptySearch hidePagination>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold"
|
||||
>Sorry we couldn't find "{search}"</b>
|
||||
<p>There are no topics that match your search.</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 common-section u-main-center">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/messaging/topics"
|
||||
text>Documentation</Button>
|
||||
<Button secondary on:click={() => (search = '')}
|
||||
>Clear search</Button>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<p class="text">Total results: {totalResults}</p>
|
||||
<PaginationInline limit={5} bind:offset sum={totalResults} hidePages />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<EmptySearch hidePagination>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<p class="text u-line-height-1-5">
|
||||
You have no topics. Create a topic to see them here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Learn more in our <Button
|
||||
link
|
||||
external
|
||||
href="https://appwrite.io/docs/products/messaging/topics"
|
||||
>documentation</Button
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
</div>
|
||||
</div>
|
||||
{:else if search}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold">Sorry we couldn't find "{search}"</b>
|
||||
<p>There are no topics that match your search.</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 common-section u-main-center">
|
||||
<Button external href="https://appwrite.io/docs/products/messaging/topics" text
|
||||
>Documentation</Button>
|
||||
<Button secondary on:click={() => (search = '')}>Clear search</Button>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<p class="text u-line-height-1-5">
|
||||
You have no topics. Create a topic to see them here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Learn more in our <a
|
||||
href="https://appwrite.io/docs/products/messaging/topics"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
documentation</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit disabled={!hasSelection}>Add</Button>
|
||||
<div class="u-flex u-gap-16 u-cross-center">
|
||||
<div class="u-flex u-gap-8">
|
||||
<span class="inline-tag"><span class="text">{selectedSize}</span></span>
|
||||
<span class="body-text-2">Topics selected</span>
|
||||
</div>
|
||||
<Button submit disabled={!hasSelection}>Add</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
let totalResults = 0;
|
||||
let userResultsById: Record<string, Models.User<Record<string, unknown>>> = {}; // use a hash map so we can quickly look up a user by id
|
||||
let selected: Record<string, Models.Target> = {};
|
||||
let selectedSize = 0;
|
||||
let selectedUsers = 0;
|
||||
let hasSelection = false;
|
||||
|
||||
function reset() {
|
||||
@@ -108,7 +110,16 @@
|
||||
request();
|
||||
}
|
||||
|
||||
$: selectedSize = Object.keys(selected).length;
|
||||
$: {
|
||||
selectedSize = 0;
|
||||
const users = new Set();
|
||||
for (const s in selected) {
|
||||
const target = selected[s];
|
||||
users.add(target.userId);
|
||||
selectedSize++;
|
||||
}
|
||||
selectedUsers = users.size;
|
||||
}
|
||||
|
||||
$: hasSelection = selectedSize > 0;
|
||||
|
||||
@@ -118,120 +129,167 @@
|
||||
</script>
|
||||
|
||||
<Modal {title} bind:show onSubmit={submit} on:close={reset} size="big" headerDivider={false}>
|
||||
<slot name="description" />
|
||||
<div class="u-flex u-flex-vertical u-gap-32">
|
||||
<slot name="description" />
|
||||
|
||||
<InputSearch
|
||||
autofocus
|
||||
disabled={totalResults === 0 && !search}
|
||||
placeholder="Search by name, email, phone or ID"
|
||||
bind:value={search} />
|
||||
{#if Object.keys(userResultsById).length > 0}
|
||||
<div class="u-sep-block-end">
|
||||
<Collapsible>
|
||||
{#each Object.entries(userResultsById) as [userId, user] (userId)}
|
||||
{@const selectedCount = user.targets.filter(
|
||||
(target) => selected[target.$id]
|
||||
).length}
|
||||
<CollapsibleItem withIndentation>
|
||||
<svelte:fragment slot="beforetitle">
|
||||
<InputCheckbox
|
||||
id={userId}
|
||||
size="small"
|
||||
disabled={user.targets.length > 0 &&
|
||||
user.targets.every((target) => targetsById[target.$id])}
|
||||
checked={selectedCount > 0 && selectedCount === user.targets.length}
|
||||
on:change={(event) => onUserSelection(event, userId)} />
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
<span class="u-line-height-1-5">
|
||||
<span
|
||||
class="body-text-1 u-bold"
|
||||
style="color:hsl(var(--color-neutral-80))"
|
||||
data-private>
|
||||
{#if user.name}
|
||||
{user.name}
|
||||
{:else if user.email}
|
||||
{user.email}
|
||||
{:else if user.phone}
|
||||
{user.phone}
|
||||
{:else}
|
||||
{userId}
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
({selectedCount}/{user.targets.length} targets)
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
{#each user.targets as target}
|
||||
<div class="u-flex">
|
||||
<InputCheckbox
|
||||
id={target.$id}
|
||||
size="small"
|
||||
disabled={!!targetsById[target.$id]}
|
||||
checked={!!selected[target.$id]}
|
||||
on:change={(event) => onTargetSelection(event, target)}>
|
||||
<svelte:fragment slot="description">
|
||||
<div class="u-inline-flex u-gap-8">
|
||||
<span class="inline-tag u-normal u-x-small"
|
||||
><ProviderType
|
||||
type={target.providerType}
|
||||
noIcon /></span>
|
||||
{#if target.providerType !== MessagingProviderType.Push}
|
||||
{target.identifier}
|
||||
{:else}
|
||||
{target.name}
|
||||
{/if}
|
||||
</div>
|
||||
<div class="u-flex u-flex-vertical u-gap-16">
|
||||
<InputSearch
|
||||
autofocus
|
||||
disabled={totalResults === 0 && !search}
|
||||
placeholder="Search by name, email, phone or ID"
|
||||
bind:value={search} />
|
||||
<div>
|
||||
{#if Object.keys(userResultsById).length > 0}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<div class="u-sep-block-end">
|
||||
<Collapsible>
|
||||
{#each Object.entries(userResultsById) as [userId, user] (userId)}
|
||||
{@const selectedCount = user.targets.filter(
|
||||
(target) => selected[target.$id]
|
||||
).length}
|
||||
<CollapsibleItem
|
||||
withIndentation
|
||||
disabled={!user.targets.length}>
|
||||
<svelte:fragment slot="beforetitle">
|
||||
<InputCheckbox
|
||||
id={userId}
|
||||
size="small"
|
||||
disabled={!user.targets.length ||
|
||||
(user.targets.length > 0 &&
|
||||
user.targets.every(
|
||||
(target) => targetsById[target.$id]
|
||||
))}
|
||||
checked={selectedCount > 0 &&
|
||||
selectedCount === user.targets.length}
|
||||
on:change={(event) =>
|
||||
onUserSelection(event, userId)} />
|
||||
</svelte:fragment>
|
||||
</InputCheckbox>
|
||||
<svelte:fragment slot="title">
|
||||
<span class="u-line-height-1-5">
|
||||
<span
|
||||
class="user-name body-text-1 u-bold"
|
||||
data-private>
|
||||
{#if user.name}
|
||||
{user.name}
|
||||
{:else if user.email}
|
||||
{user.email}
|
||||
{:else if user.phone}
|
||||
{user.phone}
|
||||
{:else}
|
||||
{userId}
|
||||
{/if}
|
||||
</span>
|
||||
</span>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
{#if user.targets.length === 0}
|
||||
(0 targets)
|
||||
{:else}
|
||||
({selectedCount}/{user.targets.length} targets)
|
||||
{/if}
|
||||
</svelte:fragment>
|
||||
<FormList>
|
||||
{#each user.targets as target}
|
||||
<div class="u-flex">
|
||||
<InputCheckbox
|
||||
id={target.$id}
|
||||
size="small"
|
||||
disabled={!!targetsById[target.$id]}
|
||||
checked={!!selected[target.$id]}
|
||||
on:change={(event) =>
|
||||
onTargetSelection(event, target)}>
|
||||
<svelte:fragment slot="description">
|
||||
<div class="u-inline-flex u-gap-8">
|
||||
<span
|
||||
class="inline-tag u-normal u-x-small"
|
||||
><ProviderType
|
||||
type={target.providerType}
|
||||
noIcon /></span>
|
||||
{#if target.providerType !== MessagingProviderType.Push}
|
||||
{target.identifier}
|
||||
{:else}
|
||||
{target.name}
|
||||
{/if}
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</InputCheckbox>
|
||||
</div>
|
||||
{/each}
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
{/each}
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<p class="text">Total results: {totalResults}</p>
|
||||
<PaginationInline limit={5} bind:offset sum={totalResults} hidePages />
|
||||
</div>
|
||||
</div>
|
||||
{:else if search}
|
||||
<div class="u-flex-vertical u-gap-8">
|
||||
<EmptySearch hidePagination>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold"
|
||||
>Sorry we couldn't find "{search}"</b>
|
||||
<p>There are no Users that match your search.</p>
|
||||
</div>
|
||||
{/each}
|
||||
</FormList>
|
||||
</CollapsibleItem>
|
||||
{/each}
|
||||
</Collapsible>
|
||||
</div>
|
||||
<div class="u-flex u-main-space-between">
|
||||
<p class="text">Total results: {totalResults}</p>
|
||||
<PaginationInline limit={5} bind:offset sum={totalResults} hidePages />
|
||||
</div>
|
||||
{:else if search}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<b class="body-text-2 u-bold">Sorry we couldn't find "{search}"</b>
|
||||
<p>There are no Users that match your search.</p>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 common-section u-main-center">
|
||||
<Button external href="https://appwrite.io/docs/products/auth/accounts" text
|
||||
>Documentation</Button>
|
||||
<Button secondary on:click={() => (search = '')}>Clear search</Button>
|
||||
</div>
|
||||
<div class="u-flex u-gap-16 common-section u-main-center">
|
||||
<Button
|
||||
external
|
||||
href="https://appwrite.io/docs/products/auth/accounts"
|
||||
text>Documentation</Button>
|
||||
<Button secondary on:click={() => (search = '')}
|
||||
>Clear search</Button>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
<div class="u-flex u-main-space-between u-cross-center">
|
||||
<p class="text">Total results: {totalResults}</p>
|
||||
<PaginationInline limit={5} bind:offset sum={totalResults} hidePages />
|
||||
</div>
|
||||
</div>
|
||||
{:else}
|
||||
<EmptySearch hidePagination>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<p class="text u-line-height-1-5">
|
||||
You have no users. Create a user to see them here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Learn more in our <Button
|
||||
link
|
||||
external
|
||||
href="https://appwrite.io/docs/products/auth/quick-start"
|
||||
>documentation</Button
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{:else}
|
||||
<EmptySearch hidePages>
|
||||
<div class="common-section">
|
||||
<div class="u-text-center common-section">
|
||||
<p class="text u-line-height-1-5">
|
||||
You have no users. Create a user to see them here.
|
||||
</p>
|
||||
<p class="text u-line-height-1-5">
|
||||
Need a hand? Learn more in our <a
|
||||
href="https://appwrite.io/docs/products/auth/quick-start"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer">
|
||||
documentation</a
|
||||
>.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
</EmptySearch>
|
||||
{/if}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
<svelte:fragment slot="footer">
|
||||
<Button submit disabled={!hasSelection}>Add</Button>
|
||||
<div class="u-flex u-gap-16 u-cross-center">
|
||||
<div class="u-flex u-gap-8">
|
||||
<span class="inline-tag"><span class="text">{selectedUsers}</span></span>
|
||||
<span class="body-text-2">Users selected</span>
|
||||
</div>
|
||||
<Button submit disabled={!hasSelection}>Add</Button>
|
||||
</div>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
<style lang="scss">
|
||||
:global(.collapsible-wrapper:not(.is-disabled)) {
|
||||
.user-name {
|
||||
color: hsl(var(--color-neutral-80));
|
||||
|
||||
:global(.theme-dark) & {
|
||||
color: hsl(var(--color-neutral-20));
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
import { project } from '../store';
|
||||
import { wizard } from '$lib/stores/wizard';
|
||||
import { providerType, messageParams, operation } from './wizard/store';
|
||||
import { ID, MessageStatus, MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import { ID, MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
async function create() {
|
||||
@@ -32,7 +32,7 @@
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$messageParams[$providerType].status,
|
||||
$messageParams[$providerType].draft,
|
||||
$messageParams[$providerType].html,
|
||||
$messageParams[$providerType].scheduledAt
|
||||
);
|
||||
@@ -44,7 +44,7 @@
|
||||
$messageParams[$providerType].topics,
|
||||
$messageParams[$providerType].users,
|
||||
$messageParams[$providerType].targets,
|
||||
$messageParams[$providerType].status,
|
||||
$messageParams[$providerType].draft,
|
||||
$messageParams[$providerType].scheduledAt
|
||||
);
|
||||
break;
|
||||
@@ -74,7 +74,7 @@
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$messageParams[$providerType].status,
|
||||
$messageParams[$providerType].draft,
|
||||
$messageParams[$providerType].scheduledAt
|
||||
);
|
||||
}
|
||||
@@ -83,14 +83,13 @@
|
||||
wizard.hide();
|
||||
let message = '';
|
||||
switch (response.status) {
|
||||
case MessageStatus.Draft:
|
||||
case 'draft':
|
||||
message = 'The message has been saved as draft.';
|
||||
break;
|
||||
case MessageStatus.Processing:
|
||||
case 'processing':
|
||||
message = 'The message is queued for processing.';
|
||||
break;
|
||||
case 'scheduled':
|
||||
// TODO: fix message status
|
||||
message = 'The message has been scheduled.';
|
||||
break;
|
||||
}
|
||||
@@ -131,7 +130,7 @@
|
||||
$messageParams[$providerType].targets,
|
||||
$messageParams[$providerType].subject,
|
||||
$messageParams[$providerType].content,
|
||||
$messageParams[$providerType].status,
|
||||
$messageParams[$providerType].draft,
|
||||
$messageParams[$providerType].html,
|
||||
undefined,
|
||||
undefined,
|
||||
@@ -145,7 +144,7 @@
|
||||
$messageParams[$providerType].users,
|
||||
$messageParams[$providerType].targets,
|
||||
$messageParams[$providerType].content,
|
||||
$messageParams[$providerType].status,
|
||||
$messageParams[$providerType].draft,
|
||||
$messageParams[$providerType].scheduledAt
|
||||
);
|
||||
break;
|
||||
@@ -175,7 +174,7 @@
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
$messageParams[$providerType].status,
|
||||
$messageParams[$providerType].draft,
|
||||
$messageParams[$providerType].scheduledAt
|
||||
);
|
||||
}
|
||||
@@ -184,14 +183,13 @@
|
||||
wizard.hide();
|
||||
let message = '';
|
||||
switch (response.status) {
|
||||
case MessageStatus.Draft:
|
||||
case 'draft':
|
||||
message = 'The message has been saved as draft.';
|
||||
break;
|
||||
case MessageStatus.Processing:
|
||||
case 'processing':
|
||||
message = 'The message is queued for processing.';
|
||||
break;
|
||||
case 'scheduled':
|
||||
// TODO: fix message status
|
||||
message = 'The message has been scheduled.';
|
||||
break;
|
||||
}
|
||||
@@ -202,7 +200,7 @@
|
||||
await invalidate(Dependencies.MESSAGING_MESSAGE);
|
||||
trackEvent(Submit.MessagingMessageUpdate, {
|
||||
providerType: $providerType,
|
||||
status: params.status
|
||||
status: response.status
|
||||
});
|
||||
await goto(`${base}/console/project-${$project.$id}/messaging/message-${response.$id}`);
|
||||
} catch (error) {
|
||||
@@ -215,7 +213,7 @@
|
||||
}
|
||||
|
||||
async function saveDraft() {
|
||||
$messageParams[$providerType].status = MessageStatus.Draft;
|
||||
$messageParams[$providerType].draft = true;
|
||||
if ($operation === 'create') {
|
||||
create();
|
||||
} else {
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import { ID, MessageStatus, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { ID, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
let showCustomId = false;
|
||||
@@ -36,7 +36,7 @@
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
MessageStatus.Processing,
|
||||
undefined,
|
||||
$messageParams[MessagingProviderType.Email]?.html || false,
|
||||
undefined
|
||||
);
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import { ID, MessageStatus, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { ID, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import PushPhone from '../pushPhone.svelte';
|
||||
import { onMount } from 'svelte';
|
||||
@@ -76,7 +76,7 @@
|
||||
undefined,
|
||||
undefined,
|
||||
undefined,
|
||||
MessageStatus.Processing,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
import { CustomId, Modal } from '$lib/components';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { clickOnEnter } from '$lib/helpers/a11y';
|
||||
import { ID, MessageStatus, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { ID, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import SMSPhone from '../smsPhone.svelte';
|
||||
|
||||
@@ -24,7 +24,7 @@
|
||||
$messageParams[MessagingProviderType.Sms]?.topics || [],
|
||||
$messageParams[MessagingProviderType.Sms]?.users || [],
|
||||
$messageParams[MessagingProviderType.Sms]?.targets || [],
|
||||
MessageStatus.Processing,
|
||||
undefined,
|
||||
undefined
|
||||
);
|
||||
}
|
||||
@@ -102,6 +102,6 @@
|
||||
</FormList>
|
||||
<SMSPhone content={$messageParams[$providerType]['content']} classes="is-only-desktop" />
|
||||
</div>
|
||||
<div class="u-flex u-main-center u-margin-block-start-24">
|
||||
<SMSPhone content={$messageParams[$providerType]['content']} classes="is-not-desktop" />
|
||||
<div class="u-flex u-main-center u-margin-block-start-24 is-not-desktop">
|
||||
<SMSPhone content={$messageParams[$providerType]['content']} />
|
||||
</div>
|
||||
|
||||
@@ -89,7 +89,7 @@
|
||||
{topic.name}
|
||||
</span>
|
||||
<span class="collapsible-button-optional">
|
||||
({getTotal(topic)} subscribers)
|
||||
({getTotal(topic)} targets)
|
||||
</span>
|
||||
</span></span>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Button, FormList, InputDate, InputSelect, InputTime } from '$lib/elements/forms';
|
||||
import Helper from '$lib/elements/forms/helper.svelte';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import { MessageStatus, MessagingProviderType } from '@appwrite.io/console';
|
||||
import { MessagingProviderType } from '@appwrite.io/console';
|
||||
import { messageParams, providerType } from './store';
|
||||
|
||||
let when: 'now' | 'later' = 'now';
|
||||
@@ -41,9 +41,7 @@
|
||||
};
|
||||
|
||||
async function beforeSubmit() {
|
||||
$messageParams[$providerType].status = MessageStatus.Processing;
|
||||
if (when === 'later') {
|
||||
$messageParams[$providerType].status = 'scheduled' as MessageStatus;
|
||||
$messageParams[$providerType].scheduledAt = dateTime.toISOString();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { MessageStatus, MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import { MessagingProviderType, type Models } from '@appwrite.io/console';
|
||||
import { get, writable } from 'svelte/store';
|
||||
|
||||
export type MessageParams = {
|
||||
@@ -6,7 +6,7 @@ export type MessageParams = {
|
||||
topics: string[];
|
||||
users: string[];
|
||||
targets: string[];
|
||||
status: MessageStatus;
|
||||
draft: boolean;
|
||||
scheduledAt?: string;
|
||||
};
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Create an API key</svelte:fragment>
|
||||
<svelte:fragment slot="title">API key</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">Let's create an API key.</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Add Scopes</svelte:fragment>
|
||||
<svelte:fragment slot="title">Scopes</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose which permission scopes to grant your application. It is best practice to allow only
|
||||
the permissions you need to meet your project goals.
|
||||
|
||||
@@ -41,7 +41,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Register your Android app</svelte:fragment>
|
||||
<svelte:fragment slot="title">Android registration</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Get the SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Install</svelte:fragment>
|
||||
<p>First, add this to your root level <code class="inline-code">build.gradle</code> file.</p>
|
||||
<Code label="Groovy" labelIcon="android" language="kotlin" code={example1} withCopy />
|
||||
<p class="common-section">And add this to your project's build.gradle file.</p>
|
||||
|
||||
@@ -15,7 +15,7 @@ val client = Client(context)
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Initialize SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Initialize</svelte:fragment>
|
||||
|
||||
<h2 class="heading-level-7">Initialize your SDK</h2>
|
||||
<p>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Register your bundle ID</svelte:fragment>
|
||||
<svelte:fragment slot="title">Apple registration</svelte:fragment>
|
||||
|
||||
<FormList isCommonSection>
|
||||
<FormItem>
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Get the SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Install</svelte:fragment>
|
||||
<div class="u-flex u-gap-16 u-margin-block-start-8">
|
||||
<Pill button on:click={() => (method = Method.Xcode)} selected={method === Method.Xcode}>
|
||||
Xcode
|
||||
|
||||
@@ -17,7 +17,7 @@ let client = Client()
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Initialize SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Initialize</svelte:fragment>
|
||||
|
||||
<h2 class="heading-level-7">Initialize your SDK</h2>
|
||||
<p>
|
||||
|
||||
@@ -149,16 +149,16 @@
|
||||
|
||||
$: registee = {
|
||||
[PlatformType.Flutterandroid]: 'package name',
|
||||
[PlatformType.Flutterios]: 'bundle ID',
|
||||
[PlatformType.Flutterlinux]: 'package name',
|
||||
[PlatformType.Fluttermacos]: 'bundle ID',
|
||||
[PlatformType.Flutterwindows]: 'package name',
|
||||
[PlatformType.Flutterweb]: 'hostname'
|
||||
[PlatformType.Flutterios]: 'Bundle ID',
|
||||
[PlatformType.Flutterlinux]: 'Package name',
|
||||
[PlatformType.Fluttermacos]: 'Bundle ID',
|
||||
[PlatformType.Flutterwindows]: 'Package name',
|
||||
[PlatformType.Flutterweb]: 'Hostname'
|
||||
}[platform];
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Register your {registee}</svelte:fragment>
|
||||
<svelte:fragment slot="title">{registee} registration</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
<div class="u-flex u-gap-16 u-margin-block-start-8 u-flex-wrap">
|
||||
<Pill
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Get the SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Install</svelte:fragment>
|
||||
|
||||
<p>
|
||||
Add Appwrite SDK to your package's <code class="inline-code">pubspec.yaml</code> file. You can
|
||||
|
||||
@@ -18,7 +18,7 @@ client
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Initialize SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Initialize</svelte:fragment>
|
||||
|
||||
<h2 class="heading-level-7">Initialize your SDK</h2>
|
||||
<p data-private>
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">You're ready to go 🎉</svelte:fragment>
|
||||
<svelte:fragment slot="title">All set</svelte:fragment>
|
||||
<p>
|
||||
{#if isFirstProject}
|
||||
Congratulations on adding the first platform to your project! Start exploring by
|
||||
|
||||
@@ -44,7 +44,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep {beforeSubmit}>
|
||||
<svelte:fragment slot="title">Register your hostname</svelte:fragment>
|
||||
<svelte:fragment slot="title">Hostname registration</svelte:fragment>
|
||||
<FormList>
|
||||
<InputText
|
||||
id="name"
|
||||
|
||||
@@ -23,7 +23,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Get the SDK</svelte:fragment>
|
||||
<svelte:fragment slot="title">Install</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose your preferred method of installation
|
||||
<div class="u-flex u-gap-16 u-margin-block-start-8">
|
||||
|
||||
@@ -1,19 +1,13 @@
|
||||
<script lang="ts">
|
||||
import { invalidate } from '$app/navigation';
|
||||
import { page } from '$app/stores';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import {
|
||||
FormList,
|
||||
InputNumber,
|
||||
InputPassword,
|
||||
InputSelect,
|
||||
InputText,
|
||||
InputTextarea
|
||||
} from '$lib/elements/forms';
|
||||
import { WizardStep } from '$lib/layout';
|
||||
import type { Provider } from '$lib/stores/migration';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { provider } from '.';
|
||||
|
||||
const providers: Record<Provider, string> = {
|
||||
@@ -22,38 +16,10 @@
|
||||
supabase: 'Supabase',
|
||||
nhost: 'NHost'
|
||||
};
|
||||
|
||||
// function connectGoogle() {
|
||||
// const redirect = new URL($page.url);
|
||||
// const target = new URL(
|
||||
// `${sdk.forProject.client.config.endpoint}/migrations/firebase/connect`
|
||||
// );
|
||||
// target.searchParams.set('redirect', redirect.toString());
|
||||
// target.searchParams.set('project', $page.params.project);
|
||||
// target.searchParams.set('mode', 'admin');
|
||||
// return target;
|
||||
// }
|
||||
|
||||
function deauthorizeGoogle() {
|
||||
sdk.forProject.migrations.deleteFirebaseAuth().then(() => {
|
||||
firebaseProjects = [];
|
||||
});
|
||||
|
||||
addNotification({
|
||||
type: 'success',
|
||||
message: 'Signed out of Google successfully'
|
||||
});
|
||||
|
||||
invalidate(Dependencies.MIGRATIONS);
|
||||
}
|
||||
|
||||
$: firebaseProjects = $page.data.firebaseProjects;
|
||||
|
||||
// let showAuth = false;
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select source</svelte:fragment>
|
||||
<svelte:fragment slot="title">Source</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Select a source platform with the project you want to migrate. <a
|
||||
class="link"
|
||||
@@ -102,24 +68,7 @@
|
||||
bind:value={$provider.apiKey} />
|
||||
</FormList>
|
||||
{:else if $provider.provider === 'firebase'}
|
||||
{#if !firebaseProjects?.length}
|
||||
<!-- <div class="box u-flex u-flex-vertical u-gap-16 u-cross-center u-margin-block-start-24">
|
||||
<p class="u-text-center u-bold">Sign in with Google to get started</p>
|
||||
<Button secondary href={connectGoogle().toString()}>
|
||||
<SvgIcon name="google" />Sign in
|
||||
</Button>
|
||||
</div> -->
|
||||
|
||||
<!-- <button
|
||||
class="tag u-margin-block-start-16"
|
||||
type="button"
|
||||
on:click={() => (showAuth = !showAuth)}
|
||||
class:is-selected={showAuth}>
|
||||
<span class="icon-lock-closed" aria-hidden="true" />
|
||||
<span class="text">Manual authentication</span>
|
||||
</button> -->
|
||||
|
||||
<!-- {#if showAuth} -->
|
||||
<FormList class="u-margin-block-start-24">
|
||||
<div class="u-margin-block-start-16">
|
||||
<InputTextarea
|
||||
id="credentials"
|
||||
@@ -128,23 +77,7 @@
|
||||
bind:value={$provider.serviceAccount}
|
||||
placeholder="Enter account credentials" />
|
||||
</div>
|
||||
<!-- {/if} -->
|
||||
{:else}
|
||||
<FormList class="u-margin-block-start-24">
|
||||
<InputSelect
|
||||
id="firebase-project"
|
||||
label="Firebase project"
|
||||
required
|
||||
bind:value={$provider.projectId}
|
||||
options={firebaseProjects.map((project) => ({
|
||||
label: project.displayName,
|
||||
value: project.projectId
|
||||
}))} />
|
||||
</FormList>
|
||||
<p class="u-text-center u-margin-block-start-24">
|
||||
<button class="u-bold" on:click|preventDefault={deauthorizeGoogle}>Sign out</button>
|
||||
</p>
|
||||
{/if}
|
||||
</FormList>
|
||||
{:else if $provider.provider === 'supabase'}
|
||||
<FormList class="u-margin-block-start-24">
|
||||
<p class="body-text-1 u-bold">Postgres credentials</p>
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Select source</svelte:fragment>
|
||||
<svelte:fragment slot="title">Resources</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Select the resources you need to migrate to Appwrite. Some resources can be migrated, but
|
||||
with limitations. <a
|
||||
|
||||
@@ -49,19 +49,10 @@
|
||||
break;
|
||||
}
|
||||
case 'firebase': {
|
||||
if ($provider.projectId) {
|
||||
// OAuth
|
||||
await sdk.forProject.migrations.createFirebaseOAuthMigration(
|
||||
resources,
|
||||
$provider.projectId
|
||||
);
|
||||
} else if ($provider.serviceAccount) {
|
||||
// Manual auth
|
||||
await sdk.forProject.migrations.createFirebaseMigration(
|
||||
resources,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
}
|
||||
await sdk.forProject.migrations.createFirebaseMigration(
|
||||
resources,
|
||||
$provider.serviceAccount
|
||||
);
|
||||
invalidate(Dependencies.MIGRATIONS);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,6 @@
|
||||
import { Dependencies } from '$lib/constants.js';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
|
||||
async function getFirebaseProjects() {
|
||||
try {
|
||||
const res = await sdk.forProject.migrations.listFirebaseProjects();
|
||||
return res.projects;
|
||||
} catch (e) {
|
||||
if (e.type === 'user_identity_not_found') {
|
||||
return [];
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function load({ depends }) {
|
||||
depends(Dependencies.MIGRATIONS);
|
||||
|
||||
@@ -21,8 +8,7 @@ export async function load({ depends }) {
|
||||
const { migrations } = await sdk.forProject.migrations.list();
|
||||
|
||||
return {
|
||||
migrations,
|
||||
firebaseProjects: getFirebaseProjects()
|
||||
migrations
|
||||
};
|
||||
} catch {
|
||||
return {
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
import { parseIfString } from '$lib/helpers/object';
|
||||
import { formatNum } from '$lib/helpers/string';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { ResourcesFriendly } from '$lib/stores/migration';
|
||||
|
||||
export let migrations: Models.Migration[] = [];
|
||||
export let migrationId: string = null;
|
||||
@@ -106,7 +107,10 @@
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<span class="u-capitalize">{entity + 's'}</span>
|
||||
<span class="u-capitalize"
|
||||
>{total(Object.values(entityCounter)) > 1
|
||||
? ResourcesFriendly[entity].plural
|
||||
: ResourcesFriendly[entity].singular}</span>
|
||||
<span class="inline-tag">{totalItems(entityCounter)}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,24 +1,22 @@
|
||||
import type { Aggregation, Invoice } from '$lib/sdk/billing';
|
||||
import { accumulateUsage } from '$lib/sdk/usage';
|
||||
import { getSdkForProject, sdk } from '$lib/stores/sdk';
|
||||
import { Query } from '@appwrite.io/console';
|
||||
import type { PageLoad } from './$types';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
import type { Aggregation, Invoice } from '$lib/sdk/billing';
|
||||
import { accumulateUsage } from '$lib/sdk/usage';
|
||||
|
||||
export const load: PageLoad = async ({ params, parent }) => {
|
||||
const { invoice, project } = params;
|
||||
const parentData = await parent();
|
||||
const org = parentData.organization as Organization;
|
||||
const { organization } = await parent();
|
||||
|
||||
let startDate: string = org.billingCurrentInvoiceDate;
|
||||
let endDate: string = org.billingNextInvoiceDate;
|
||||
let startDate: string = organization.billingCurrentInvoiceDate;
|
||||
let endDate: string = organization.billingNextInvoiceDate;
|
||||
let currentInvoice: Invoice = undefined;
|
||||
let currentAggregation: Aggregation = undefined;
|
||||
|
||||
if (invoice) {
|
||||
currentInvoice = await sdk.forConsole.billing.getInvoice(org.$id, invoice);
|
||||
currentInvoice = await sdk.forConsole.billing.getInvoice(organization.$id, invoice);
|
||||
currentAggregation = await sdk.forConsole.billing.getAggregation(
|
||||
org.$id,
|
||||
organization.$id,
|
||||
currentInvoice.aggregationId
|
||||
);
|
||||
|
||||
@@ -27,7 +25,7 @@ export const load: PageLoad = async ({ params, parent }) => {
|
||||
}
|
||||
|
||||
const [invoices, usage] = await Promise.all([
|
||||
sdk.forConsole.billing.listInvoices(org.$id, [Query.orderDesc('from')]),
|
||||
sdk.forConsole.billing.listInvoices(organization.$id, [Query.orderDesc('from')]),
|
||||
/**
|
||||
* Workaround because project id might not be populated yet.
|
||||
*/
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Add your webhook</svelte:fragment>
|
||||
<svelte:fragment slot="title">Configuration</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Use webhooks to build integrations that subscribe to specific events on Appwrite. Appwrite
|
||||
will send an HTTP POST payload to the webhook's configured URL when one of the specified
|
||||
|
||||
+1
-1
@@ -18,7 +18,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Create file</svelte:fragment>
|
||||
<svelte:fragment slot="title">File</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">Upload a file to add it to your bucket.</svelte:fragment>
|
||||
|
||||
{#if isCloud}
|
||||
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Update Permissions</svelte:fragment>
|
||||
<svelte:fragment slot="title">Permissions</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Choose who can access your buckets and files. For more information, check out the
|
||||
<a
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep beforeSubmit={handleBefore}>
|
||||
<svelte:fragment slot="title">Confirm your details</svelte:fragment>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Confirm the details of your new organization and start your free trial.
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Invite organization members</svelte:fragment>
|
||||
<svelte:fragment slot="title">Invites</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Invite team members to collaborate with you in the Appwrite console. Members will have
|
||||
access to all services and payment data within your organization.
|
||||
|
||||
@@ -103,7 +103,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep beforeSubmit={handleBefore}>
|
||||
<svelte:fragment slot="title">Choose a plan</svelte:fragment>
|
||||
<svelte:fragment slot="title">Plan selection</svelte:fragment>
|
||||
|
||||
<p class="body-text-1 u-bold common-section">Plan</p>
|
||||
<p class="text u-margin-block-start-4">
|
||||
|
||||
@@ -49,7 +49,7 @@
|
||||
|
||||
{#if downgradeToStarter}
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Confirm plan change</svelte:fragment>
|
||||
<svelte:fragment slot="title">Change confirmation</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Your feedback is important to us and helps us improve the services Appwrite offer. If
|
||||
there is a specific reason you chose to change your plan at this time, please let us
|
||||
@@ -65,7 +65,7 @@
|
||||
</WizardStep>
|
||||
{:else}
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Confirm your details</svelte:fragment>
|
||||
<svelte:fragment slot="title">Details</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Confirm the details of your new organization and start your free trial.
|
||||
</svelte:fragment>
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Invite organization members</svelte:fragment>
|
||||
<svelte:fragment slot="title">Invites</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
Invite team members to collaborate with you in the Appwrite console. Members will have
|
||||
access to all services and payment data within your organization.
|
||||
|
||||
@@ -7,8 +7,8 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title"
|
||||
>Your usage exceeds the limits of the {tierToPlan($organization.billingPlan).name} plan</svelte:fragment>
|
||||
<svelte:fragment slot="title">
|
||||
Your usage exceeds the limits of the {tierToPlan($organization.billingPlan).name} plan</svelte:fragment>
|
||||
<svelte:fragment slot="subtitle">
|
||||
View the current usage for <b>{$organization.name}</b> and where you will exceed the limits of
|
||||
your new plan.
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
</script>
|
||||
|
||||
<WizardStep>
|
||||
<svelte:fragment slot="title">Thanks for the feedback!</svelte:fragment>
|
||||
<svelte:fragment slot="title">Thank you</svelte:fragment>
|
||||
|
||||
<div class="u-flex u-main-center">
|
||||
{#if $app.themeInUse === 'dark'}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user