Merge branch 'main' into feat-database-backups.

This commit is contained in:
ItzNotABug committed 2024-10-09 20:22:20 +05:30
commit e4e8a9cdb4
49 files changed
+1017 -761

No files matched your search

+7 -7
View File
@@ -19,11 +19,11 @@
"e2e:ui": "playwright test tests/e2e --ui"
},
"dependencies": {
"@appwrite.io/console": "^1.2.0",
"@appwrite.io/console": "^1.2.2",
"@appwrite.io/pink": "0.25.0",
"@appwrite.io/pink-icons": "0.25.0",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.32.0",
"@sentry/sveltekit": "^8.33.1",
"@stripe/stripe-js": "^3.5.0",
"ai": "^2.2.37",
"analytics": "^0.8.14",
@@ -42,13 +42,13 @@
"devDependencies": {
"@melt-ui/pp": "^0.3.2",
"@melt-ui/svelte": "^0.83.0",
"@playwright/test": "^1.47.2",
"@playwright/test": "^1.48.0",
"@sveltejs/adapter-static": "^3.0.5",
"@sveltejs/kit": "^2.6.0",
"@sveltejs/kit": "^2.6.3",
"@sveltejs/vite-plugin-svelte": "^3.1.2",
"@testing-library/dom": "^10.4.0",
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/svelte": "^5.2.1",
"@testing-library/svelte": "^5.2.3",
"@testing-library/user-event": "^14.5.2",
"@types/deep-equal": "^1.0.4",
"@types/prismjs": "^1.26.4",
@@ -62,12 +62,12 @@
"kleur": "^4.1.5",
"prettier": "^3.3.3",
"prettier-plugin-svelte": "^3.2.7",
"sass": "^1.79.3",
"sass": "^1.79.4",
"svelte": "^4.2.19",
"svelte-check": "^3.8.6",
"svelte-jester": "^2.3.2",
"svelte-preprocess": "^6.0.3",
"svelte-sequential-preprocessor": "^2.0.1",
"svelte-sequential-preprocessor": "^2.0.2",
"tslib": "^2.7.0",
"typescript": "^5.6.2",
"vite": "^5.4.8",
+629 -619
View File
File diff suppressed because it is too large. Load diff
+2
View File
@@ -5,10 +5,12 @@
export let series: BarSeriesOption[];
export let options: EChartsOption = null;
export let formatted: 'days' | 'hours' = 'days';
</script>
<Base
{options}
{formatted}
series={series.map((s) => {
s.type = 'bar';
s.stack = 'total';
+3 -1
View File
@@ -2,7 +2,7 @@
import { registerTheme, use, init } from 'echarts/core';
import { onDestroy, onMount } from 'svelte';
import { app } from '$lib/stores/app';
import { defaultConfig } from './config';
import { dailyFormat, hourlyFormat, defaultConfig } from './config';
import base from './base.json';
import light from './light.json';
import dark from './dark.json';
@@ -16,12 +16,14 @@
export let options: EChartsOption;
export let series: (BarSeriesOption | LineSeriesOption)[];
export let formatted: 'days' | 'hours' = 'days';
let chart: ECharts;
let container: HTMLDivElement;
$: option = {
...defaultConfig,
...(formatted === 'days' ? dailyFormat : hourlyFormat),
...options,
series
};
+29 -3
View File
@@ -1,3 +1,4 @@
import { toLocaleDate, toLocaleDateTime } from '$lib/helpers/date';
import type { EChartsOption } from 'echarts';
export enum Colors {
@@ -10,9 +11,6 @@ export enum Colors {
export const defaultConfig: EChartsOption = {
color: Object.values(Colors),
animation: false,
tooltip: {
trigger: 'axis'
},
grid: {
containLabel: true,
left: 0,
@@ -35,3 +33,31 @@ export const defaultConfig: EChartsOption = {
}
}
};
export const dailyFormat: EChartsOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
formatter: function (params) {
const date = new Date(params.value);
return toLocaleDate(date.toString());
}
}
}
}
};
export const hourlyFormat: EChartsOption = {
tooltip: {
trigger: 'axis',
axisPointer: {
label: {
formatter: function (params) {
const date = new Date(params.value);
return toLocaleDateTime(date.toString());
}
}
}
}
};
+2
View File
@@ -6,10 +6,12 @@
export let series: LineSeriesOption[];
export let options: EChartsOption = null;
export let formatted: 'days' | 'hours' = 'days';
</script>
<Base
{options}
{formatted}
series={series.map((s) => {
s.type = 'line';
s.stack = 'total';
@@ -8,6 +8,7 @@
export let billingPlan: Tier;
export let anyOrgFree = false;
export let isNewOrg = false;
export let selfService = true;
let classes: string = '';
export { classes as class };
@@ -22,7 +23,7 @@
<LabelCard
name="plan"
bind:group={billingPlan}
disabled={anyOrgFree}
disabled={anyOrgFree || !selfService}
value={BillingPlan.FREE}
tooltipShow={anyOrgFree}
tooltipText="You are limited to 1 Free organization per account."
@@ -49,9 +50,16 @@
</li>
<li>
<LabelCard name="plan" bind:group={billingPlan} value={BillingPlan.PRO} padding={1.5}>
<svelte:fragment slot="custom">
<div class="u-flex u-flex-vertical u-gap-4 u-width-full-line">
<LabelCard
name="plan"
disabled={!selfService}
bind:group={billingPlan}
value={BillingPlan.PRO}
padding={1.5}>
<svelte:fragment slot="custom" let:disabled>
<div
class="u-flex u-flex-vertical u-gap-4 u-width-full-line"
class:u-opacity-50={disabled}>
<h4 class="body-text-2 u-bold">
{tierPro.name}
{#if $organization?.billingPlan === BillingPlan.PRO && !isNewOrg}
+21 -12
View File
@@ -15,6 +15,7 @@
import { addBottomModalAlerts } from '$routes/(console)/bottomAlerts';
import { project } from '$routes/(console)/project-[project]/store';
import { page } from '$app/stores';
import { trackEvent } from '$lib/actions/analytics';
let currentIndex = 0;
let openModalOnMobile = false;
@@ -50,16 +51,12 @@
$: currentModalAlert = filteredModalAlerts[currentIndex] as BottomModalAlertItem;
function handleClose() {
const modalAlert = currentModalAlert;
dismissBottomModalAlert(modalAlert.id);
hideNotification(modalAlert.id, { coolOffPeriod: 24 * 365 });
if (modalAlert.closed) modalAlert.closed();
if (currentIndex === filteredModalAlerts.length - 1 && filteredModalAlerts.length > 1) {
currentIndex = currentIndex - 1;
} else {
currentIndex = currentIndex % filteredModalAlerts.length;
}
filteredModalAlerts.forEach((alert) => {
const modalAlert = alert;
dismissBottomModalAlert(modalAlert.id);
hideNotification(modalAlert.id, { coolOffPeriod: 24 * 365 });
if (modalAlert.closed) modalAlert.closed();
});
}
function showNext() {
@@ -171,7 +168,13 @@
project: $project
})}
external={!!currentModalAlert.cta.external}
fullWidthMobile>
fullWidthMobile
on:click={() => {
trackEvent('click_promo', {
promo: currentModalAlert.id,
type: shouldShowUpgrade ? 'upgrade' : 'try_now'
});
}}>
{currentModalAlert.cta.text}
</Button>
@@ -277,7 +280,13 @@
})}
external={!!currentModalAlert.cta.external}
fullWidthMobile
on:click={() => (openModalOnMobile = false)}>
on:click={() => {
openModalOnMobile = false;
trackEvent('click_promo', {
promo: currentModalAlert.id,
type: shouldShowUpgrade ? 'upgrade' : 'try_now'
});
}}>
{shouldShowUpgrade
? 'Upgrade plan'
: currentModalAlert.cta.text}
+1 -1
View File
@@ -79,7 +79,7 @@
: selected.service?.actions;
// We need to check if the selected action name is still
// on the available list, and if so, change tje action to the
// on the available list, and if so, change the action to the
// appropriate one, as it may contain different attributes
const availableAction = availableActions.find(
(a) => a.name === selected.action?.name
+1 -1
View File
@@ -34,7 +34,7 @@
<Modal
title="Label"
description="Labels allow you to grant access to users with the specfied label."
description="Labels allow you to grant access to users with the specified label."
bind:show
on:close={reset}
onSubmit={create}>
+2 -1
View File
@@ -468,7 +468,8 @@ export const eventServices: Array<EventService> = [
export enum BillingPlan {
FREE = 'tier-0',
PRO = 'tier-1',
SCALE = 'tier-2'
SCALE = 'tier-2',
CUSTOM = 'cont-1'
}
export const feedbackDowngradeOptions = [
+2 -2
View File
@@ -51,7 +51,7 @@ export const toLocaleDateTime = (datetime: string | number) => {
};
/**
* Returns a date string usig the local timezone in ISO format (yyyy-mm-dd)
* Returns a date string using the local timezone in ISO format (yyyy-mm-dd)
*
* @param datetime date string or milliseconds since the epoch
*/
@@ -67,7 +67,7 @@ export const toLocaleDateISO = (datetime: string | number) => {
};
/**
* Returns a time string usig the local timezone in ISO format (hh:mm:ss)
* Returns a time string using the local timezone in ISO format (hh:mm:ss)
*
* @param datetime date string or milliseconds since the epoch
*/
+12 -1
View File
@@ -7,7 +7,7 @@
<footer class="main-footer u-cross-center">
<div class="main-footer-start">
<ul class="inline-links is-no-padding-first-and-last u-x-small u-flex-wrap u-row-gap-16">
<ul class="inline-links is-no-padding-first-and-last u-x-small u-flex-wrap u-gap-16">
<li class="inline-links-item" style="line-height: 1.02;">
<div class="u-flex u-cross-center u-gap-8">
{#if isCloud}
@@ -79,7 +79,18 @@
margin-block-start: auto;
}
.main-footer-start .inline-links-item {
padding-inline-end: 1rem;
padding-inline-start: unset;
}
[class^='icon-']:not(.icon-cloud):not(:hover) {
color: hsl(var(--color-neutral-50));
}
@media (max-width: 1200px) {
.main-footer {
gap: 2rem;
}
}
</style>
+14 -2
View File
@@ -48,6 +48,10 @@
return queries;
}
function unescapeNewLines(logs: string) {
return logs.replace(/\\n/g, '\n');
}
beforeNavigate((n) => {
if (!$log.show) return;
if (n.type === 'popstate') {
@@ -343,7 +347,11 @@
{/if}
</Alert>
{/if}
<Code withCopy noMargin code={execution.logs} language="sh" />
<Code
withCopy
noMargin
code={unescapeNewLines(execution.logs)}
language="sh" />
{:else}
<Card isDashed isTile>
<p class="text u-text-center">No response was recorded.</p>
@@ -351,7 +359,11 @@
{/if}
{:else if selectedResponse === 'errors'}
{#if execution?.errors}
<Code withCopy noMargin code={execution.errors} language="sh" />
<Code
withCopy
noMargin
code={unescapeNewLines(execution.errors)}
language="sh" />
{:else}
<Card isDashed isTile>
<p class="text u-text-center">No response was recorded.</p>
+1
View File
@@ -101,6 +101,7 @@
<div class="u-margin-block-start-16" />
<div class="chart-container">
<BarChart
formatted={$page.params.period === '24h' ? 'hours' : 'days'}
series={[
{
name: countMetadata.legend,
+3 -1
View File
@@ -146,7 +146,7 @@ export type Aggregation = {
*/
amount: number;
/**
* Price for addional members
* Price for additional members
*/
additionalMembers: number;
/**
@@ -277,6 +277,8 @@ export type Plan = {
users: AdditionalResource;
};
trialDays: number;
isAvailable: boolean;
selfService: boolean;
};
export type PlansInfo = {
+8 -1
View File
@@ -34,7 +34,7 @@ import { user } from './user';
import { browser } from '$app/environment';
import { canSeeBilling } from './roles';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'cont-1';
export const roles = [
{
@@ -77,6 +77,8 @@ export function tierToPlan(tier: Tier) {
return tierPro;
case BillingPlan.SCALE:
return tierScale;
case BillingPlan.CUSTOM:
return tierCustom;
default:
return tierFree;
}
@@ -182,6 +184,11 @@ export const tierScale: TierData = {
description: 'For scaling teams and agencies that need dedicated support.'
};
export const tierCustom: TierData = {
name: 'Custom',
description: 'Team on a custom contract'
};
export const showUsageRatesModal = writable<boolean>(false);
export function checkForUsageFees(plan: Tier, id: PlanServices) {
+1 -1
View File
@@ -31,7 +31,7 @@ function createHeaderAlertStore() {
});
},
get: (): HeaderAlert => {
//return higest importance component
//return highest importance component
let component = {
id: '',
show: false,
+3 -3
View File
@@ -36,7 +36,7 @@ export async function initializeStripe() {
// Set up the options for the stripe elements
const options = {
clientSecret: clientSecret,
appearance: get(app).themeInUse === 'dark' ? apperanceDark : apperanceLight
appearance: get(app).themeInUse === 'dark' ? appearanceDark : appearanceLight
};
// Set up Elements and then create form
elements = get(stripe).elements(options);
@@ -184,7 +184,7 @@ export async function confirmSetup(
}
}
const apperanceLight = {
const appearanceLight = {
variables: {
colorPrimary: '#606a7b',
colorText: 'rgb(107, 107, 112)',
@@ -213,7 +213,7 @@ const apperanceLight = {
}
};
const apperanceDark = {
const appearanceDark = {
variables: {
colorPrimary: '#606a7b',
colorText: 'rgb(195, 195, 198)',
+2 -2
View File
@@ -159,7 +159,7 @@
</div>
<div class="body-text-2">
{runtimeDetail.name}
{#if runtimeDetail.name.toLowerCase() === 'go'}
{#if runtimeDetail.name.toLowerCase() === 'deno'}
<span class="inline-tag">New</span>
{/if}
</div>
@@ -192,7 +192,7 @@
<Button
text
class="u-margin-block-start-24 u-margin-inline-start-auto"
href={`${base}/project-${$page.params.project}/functions/templates?useCase=Starter`}>
href={`${base}/project-${$page.params.project}/functions/templates?useCase=starter`}>
All starter templates <span class="icon-cheveron-right" />
</Button>
<div class="u-sep-block-start common-section" />
@@ -36,7 +36,7 @@
/**
*
* @param field {string} The field to update.
* Representes the path to the field in the formData object e.g. 'users.root'
* Represents the path to the field in the formData object e.g. 'users.root'
*/
function handleInputChange(field: FormKeys) {
return (event: Event) => {
@@ -19,7 +19,7 @@
async function updateEmail() {
try {
await sdk.forConsole.account.updateEmail(email, emailPassword);
await invalidate(Dependencies.ACCOUNT);
await Promise.all([invalidate(Dependencies.ACCOUNT), invalidate(Dependencies.FACTORS)]);
addNotification({
message: 'Email has been updated',
type: 'success'
+78 -1
View File
@@ -14,6 +14,8 @@
import type { Models } from '@appwrite.io/console';
import MfaRegenerateCodes from './mfaRegenerateCodes.svelte';
import { Pill } from '$lib/elements';
import { page } from '$app/stores';
import { onMount } from 'svelte';
let showSetup: boolean = false;
let showDelete: boolean = false;
@@ -26,6 +28,56 @@
.map(([factor, _]) => factor);
$: enabledMethods = enabledFactors.filter((factor) => factor !== 'recoveryCode');
$: cleanUrl = $page.url.origin + $page.url.pathname;
let creatingVerification = false;
async function createVerification() {
creatingVerification = true;
try {
await sdk.forConsole.account.createVerification(cleanUrl);
addNotification({
message: 'Verification email has been sent',
type: 'success'
});
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
} finally {
creatingVerification = false;
}
}
async function updateEmailVerification() {
const searchParams = $page.url.searchParams;
const userId = searchParams.get('userId');
const secret = searchParams.get('secret');
history.replaceState(null, '', cleanUrl);
if (userId && secret) {
try {
await sdk.forConsole.account.updateVerification(userId, secret);
addNotification({
message: 'Email verified successfully',
type: 'success'
});
await Promise.all([
invalidate(Dependencies.ACCOUNT),
invalidate(Dependencies.FACTORS)
]);
} catch (error) {
addNotification({
message: error.message,
type: 'error'
});
}
}
}
async function updateMfa() {
try {
await sdk.forConsole.account.updateMFA(!$user.mfa);
@@ -77,6 +129,10 @@
$: if (!showRecoveryCodes) {
codes = null;
}
onMount(() => {
updateEmailVerification();
});
</script>
<CardGrid>
@@ -159,13 +215,34 @@
</div>
</div>
</div>
{:else if !$user.emailVerification}
<div
class="u-flex u-main-space-between u-sep-block-end"
style="padding-block-end: 16px">
<div class="u-flex u-gap-8 u-cross-baseline">
<div class="avatar is-size-x-small">
<span class="icon-mail" aria-hidden="true" />
</div>
<div class="u-flex-vertical u-gap-4">
<div class="u-flex u-gap-4 u-cross-center">
<span class="body-text-2 u-bold">Email</span>
<Pill>unverified</Pill>
</div>
<span>Verify your email to receive login MFA codes.</span>
</div>
</div>
<Button
secondary
disabled={creatingVerification}
on:click={() => createVerification()}>Verify</Button>
</div>
{/if}
{#if $factors.phone}
<div
class="u-flex u-main-space-between u-sep-block-end"
style="padding-block-end: 16px">
<div class="u-flex u-gap-8">
<div class="u-flex u-gap-8 u-cross-baseline">
<div class="avatar is-size-x-small">
<span class="icon-send" aria-hidden="true" />
</div>
@@ -17,8 +17,8 @@ export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.ORGANIZATION);
depends(Dependencies.MEMBERS);
depends(Dependencies.PAYMENT_METHODS);
let roles = isCloud ? [] : defaultScopes;
let scopes = isCloud ? [] : defaultRoles;
let roles = isCloud ? [] : defaultRoles;
let scopes = isCloud ? [] : defaultScopes;
try {
if (isCloud) {
@@ -17,6 +17,8 @@
let currentInvoice: Invoice;
let extraMembers = 0;
let currentPlan;
const today = new Date();
onMount(async () => {
const invoices = await sdk.forConsole.billing.listInvoices($organization.$id, [
@@ -26,9 +28,10 @@
currentInvoice = invoices.invoices[0];
const members = await sdk.forConsole.teams.listMemberships($organization.$id, []);
extraMembers = members.total > 1 ? members.total - 1 : 0;
currentPlan = await sdk.forConsole.billing.getPlan($organization?.$id);
});
$: currentPlan = $plansInfo?.get($organization?.billingPlan);
$: extraUsage = (currentInvoice?.amount ?? 0) - (currentPlan?.price ?? 0);
$: extraAddons = currentInvoice?.usage?.length ?? 0;
$: isTrial =
@@ -73,6 +73,7 @@
let feedbackDowngradeReason: string;
let feedbackMessage: string;
let selfService: boolean;
onMount(async () => {
if ($page.url.searchParams.has('code')) {
@@ -99,6 +100,9 @@
} else {
billingPlan = BillingPlan.PRO;
}
const currentPlan = await sdk.forConsole.billing.getPlan($organization?.$id);
selfService = currentPlan.selfService;
});
async function loadPaymentMethods() {
@@ -252,8 +256,14 @@
For more details on our plans, visit our
<Button href="https://appwrite.io/pricing" external link>pricing page</Button>.
</p>
{#if !selfService}
<Alert class="u-position-relative u-margin-block-start-16" type="info"
>Your contract is not eligible for manual changes. Please reach out to schedule
a call or setup a dialog.</Alert>
{/if}
<PlanSelection
bind:billingPlan
bind:selfService
anyOrgFree={!!anyOrgFree}
class={anyOrgFree && billingPlan !== BillingPlan.FREE
? 'u-margin-block-start-16'
@@ -323,14 +333,14 @@
{/if}
</Form>
<svelte:fragment slot="aside">
{#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== billingPlan}
{#if billingPlan !== BillingPlan.FREE && $organization.billingPlan !== billingPlan && $organization.billingPlan !== BillingPlan.CUSTOM}
<EstimatedTotalBox
{billingPlan}
{collaborators}
bind:couponData
bind:billingBudget
{isDowngrade} />
{:else}
{:else if $organization.billingPlan !== BillingPlan.CUSTOM}
<PlanComparisonBox downgrade={isDowngrade} />
{/if}
</svelte:fragment>
@@ -341,7 +351,7 @@
<Button
fullWidthMobile
on:click={() => formComponent.triggerSubmit()}
disabled={$isSubmitting || isButtonDisabled}>
disabled={$isSubmitting || isButtonDisabled || !selfService}>
Change plan
</Button>
</WizardSecondaryFooter>
@@ -8,16 +8,16 @@
<CardGrid>
<div>
<Heading tag="h6" size="7">Soc-2</Heading>
<Heading tag="h6" size="7">SOC-2</Heading>
</div>
<p class="text">After requesting Soc-2, we will contact you via email for the next steps.</p>
<p class="text">After requesting SOC-2, we will contact you via email for the next steps.</p>
<svelte:fragment slot="aside">
<Box>
<h6>
<b>Service Organization Control Type 2 (Soc-2)</b>
<b>Service Organization Control Type 2 (SOC-2)</b>
</h6>
<p class="text u-margin-block-start-8">
Soc-2 is a framework for managing and protecting sensitive information, ensuring
SOC-2 is a framework for managing and protecting sensitive information, ensuring
compliance with trust service criteria such as security, availability, processing
integrity, confidentiality, and privacy.
</p>
@@ -27,7 +27,7 @@
class="u-margin-block-start-16"
on:click={() => (show = true)}
event="request_soc-2">
<span class="text">Request Soc-2</span>
<span class="text">Request SOC-2</span>
</Button>
</Box>
</svelte:fragment>
@@ -63,10 +63,10 @@
subject: 'support',
email: email,
firstName: $user?.name ?? '',
message: 'Soc-2',
message: 'SOC-2',
tags: ['cloud'],
customFields: [
{ id: '41612', value: 'Soc-2' },
{ id: '41612', value: 'SOC-2' },
{ id: '48493', value: $user?.name ?? '' },
{ id: '48492', value: $organization?.$id ?? '' },
{ id: '48490', value: $user?.$id ?? '' }
@@ -99,7 +99,7 @@
onSubmit={handleSubmit}
size="big"
headerDivider={false}
title="Request Soc-2">
title="Request SOC-2">
<FormList>
<InputEmail label="Email" placeholder="Enter email" id="email" bind:value={email} />
<InputSelect
@@ -17,8 +17,8 @@ export const load: LayoutLoad = async ({ params, depends }) => {
const newPrefs = { ...prefs, organization: project.teamId };
sdk.forConsole.account.updatePrefs(newPrefs);
preferences.loadTeamPrefs(project.teamId);
let roles = isCloud ? [] : defaultScopes;
let scopes = isCloud ? [] : defaultRoles;
let roles = isCloud ? [] : defaultRoles;
let scopes = isCloud ? [] : defaultScopes;
if (isCloud) {
const res = await sdk.forConsole.billing.getRoles(project.teamId);
roles = res.roles;
@@ -5,7 +5,8 @@
Heading,
ViewSelector,
EmptyFilter,
EmptySearch
EmptySearch,
DropList
} from '$lib/components';
import { Container } from '$lib/layout';
import type { PageData } from './$types';
@@ -14,15 +15,34 @@
import { columns } from './store';
import { View } from '$lib/helpers/load';
import Create from './create.svelte';
import { Pill } from '$lib/elements';
export let data: PageData;
let showAdd = false;
let showMoreInfo = false;
</script>
<Container>
<div class="u-flex u-flex-vertical">
<div class="u-flex u-main-space-between u-cross-center">
<Heading tag="h2" size="5">Targets</Heading>
<div class="u-flex u-gap-16 u-cross-center">
<Heading tag="h2" size="5">Targets</Heading>
<DropList bind:show={showMoreInfo} width="16">
<Pill button on:click={() => (showMoreInfo = !showMoreInfo)}>
<span class="icon-info" />More info
</Pill>
<svelte:fragment slot="list">
<slot name="tooltip">
<p>
User targets include emails, phone numbers, and devices with your
app installed. These targets can subscribe to a topic and receive
messages published to it.
</p>
</slot>
</svelte:fragment>
</DropList>
</div>
<!-- TODO: Remove u-hide to add creating a target -->
<div class="is-only-mobile u-hide">
<Button on:click={() => (showAdd = true)} event="create_user_target">
@@ -67,17 +87,16 @@
</SearchQuery> -->
<div class="u-flex u-gap-16 is-only-mobile u-margin-block-start-16">
<div class="u-flex-basis-50-percent">
<!-- TODO: fix width -->
<ViewSelector
view={View.Table}
{columns}
hideView
allowNoColumns
showColsTextMobile />
showColsTextMobile
fullWidthMobile />
</div>
<div class="u-flex-basis-50-percent">
<!-- TODO: fix width -->
<Filters query={data.query} {columns} />
<Filters query={data.query} {columns} fullWidthMobile />
</div>
</div>
</div>
@@ -111,7 +130,7 @@
Need a hand? Check out our <Button
link
external
href="http://appwrite.io/docs/products/messaging/targets"
href="https://appwrite.io/docs/products/messaging/targets"
text>
documentation</Button
>.
@@ -10,10 +10,10 @@
import { sdk } from '$lib/stores/sdk';
import { user } from './store';
let showVerifcationDropdown = false;
let showVerificationDropdown = false;
async function updateVerificationEmail() {
showVerifcationDropdown = false;
showVerificationDropdown = false;
try {
await sdk.forProject.users.updateEmailVerification($user.$id, !$user.emailVerification);
await invalidate(Dependencies.USER);
@@ -33,7 +33,7 @@
}
}
async function updateVerificationPhone() {
showVerifcationDropdown = false;
showVerificationDropdown = false;
try {
await sdk.forProject.users.updatePhoneVerification($user.$id, !$user.phoneVerification);
await invalidate(Dependencies.USER);
@@ -135,10 +135,10 @@
</Button>
{#if $user.status}
{#if $user.phone && $user.email}
<DropList bind:show={showVerifcationDropdown} placement="top-start">
<DropList bind:show={showVerificationDropdown} placement="top-start">
<Button
secondary
on:click={() => (showVerifcationDropdown = !showVerifcationDropdown)}>
on:click={() => (showVerificationDropdown = !showVerificationDropdown)}>
{$user.emailVerification && $user.phoneVerification ? 'Unverify' : 'Verify'}
account
</Button>
@@ -88,7 +88,7 @@
label="Elements"
bind:tags={data.elements}
placeholder="Add elements here"
tooltip="Enum elements have a maxiumum length of 255 characters. This limit can not be exceeded."
tooltip="Enum elements have a maximum length of 255 characters. This limit can not be exceeded."
required>
</InputTags>
@@ -326,7 +326,7 @@
<FormList>
<InputTextarea
label="Commands"
placeholder="Enter an install commad (e.g. 'npm install')"
placeholder="Enter an install command (e.g. 'npm install')"
id="install"
tooltip="Enter a single command or chain multiple commands with the && operator"
bind:value={commands} />
@@ -20,6 +20,7 @@
import { connectTemplate } from '$lib/wizards/functions/cover.svelte';
import type { Models } from '@appwrite.io/console';
import { functionsList } from '../store';
import { debounce } from '$lib/helpers/debounce';
export let data;
@@ -29,7 +30,9 @@
if (add) {
target.searchParams.append(filter, value);
} else {
const previous = target.searchParams.getAll(filter).filter((n) => n !== value);
const previous = target.searchParams
.getAll(filter)
.filter((n) => n.toLowerCase() !== value.toLowerCase());
target.searchParams.delete(filter);
previous.forEach((n) => target.searchParams.append(filter, n));
}
@@ -39,8 +42,7 @@
function clearSearch() {
const target = new URL($page.url);
target.searchParams.delete('page');
target.searchParams.delete('search');
target.search = '';
goto(target.toString());
}
@@ -70,24 +72,46 @@
return 'dart';
case runtime.includes('bun'):
return 'bun';
case runtime.includes('go'):
return 'go';
default:
return undefined;
}
}
function applySearch(event: CustomEvent<string>) {
const value = event.detail;
const target = new URL($page.url);
debounce(() => {
const value = event.detail;
const target = new URL($page.url);
if (value.length > 0) {
target.searchParams.set('search', value);
} else {
target.searchParams.delete('search');
}
target.searchParams.delete('page');
goto(target.toString(), { keepFocus: true });
if (value.length > 0) {
target.searchParams.set('search', value);
} else {
target.searchParams.delete('search');
}
target.searchParams.delete('page');
goto(target.toString(), { keepFocus: true });
}, 250)();
}
$: isChecked = (useCase: string) => {
return $page.url.searchParams
.getAll('useCase')
.some((param) => param.toLowerCase() === useCase.toLowerCase());
};
$: getErrorMessage = () => {
const searchParams = $page.url.searchParams;
const paramsArray = Array.from(searchParams.entries());
if (paramsArray.length === 1) {
const [_, value] = paramsArray[0];
return `Sorry, we couldn't find "${value}".`;
} else if (paramsArray.length > 1) {
return `Sorry, we couldn't find any results with the applied filters.`;
}
};
$: buttonDisabled = isServiceLimited(
'functions',
$organization?.billingPlan,
@@ -106,6 +130,7 @@
<section>
<InputSearch
placeholder="Search templates"
value={$page.url.searchParams.get('search')}
on:clear={clearSearch}
on:change={applySearch} />
<div class="u-margin-block-start-24">
@@ -119,11 +144,9 @@
<input
type="checkbox"
class="is-small"
value={$page.url.searchParams
.getAll('useCase')
.includes(useCase)}
checked={isChecked(useCase)}
on:change={(e) => applyFilter('useCase', useCase, e)} />
<div class="u-trim-1">{useCase}</div>
<span class="u-trim-1">{useCase}</span>
</label>
</li>
{/each}
@@ -246,8 +269,7 @@
<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 "{$page.url.searchParams.get('search')}"</b>
<b class="body-text-2 u-bold">{getErrorMessage()}</b>
<p>There are no templates that match your search.</p>
</div>
<div class="u-flex u-gap-16 common-section u-main-center">
@@ -35,9 +35,10 @@ export const load: PageLoad = async ({ url, route, depends, parent }) => {
return false;
}
const filterLowerCases = filter.useCases.map((n) => n.toLowerCase());
if (
filter.useCases.length > 0 &&
!template.useCases.some((n) => filter.useCases.includes(n))
!template.useCases.some((n) => filterLowerCases.includes(n.toLowerCase()))
) {
return false;
}
@@ -19,8 +19,8 @@
The message has been processed with errors. Please refer to the logs below for more
information.
</p>
<div style="max-inline-size: 524px">
<Code language="html" code={errors.join('\n')} noMargin noBoxPadding allowScroll />
<div style="max-inline-size: 524px" class="wrapped-code-block-for-multi-line">
<Code language="html" code={errors.join('\n')} withCopy allowScroll />
</div>
</div>
@@ -28,3 +28,12 @@
<Button secondary on:click={() => (show = false)}>Close</Button>
</svelte:fragment>
</Modal>
<style>
/* Certain errors are in a single line; this makes them multi-line for better readability. */
.wrapped-code-block-for-multi-line :global(pre),
.wrapped-code-block-for-multi-line :global(code) {
padding: unset;
white-space: pre-wrap;
}
</style>
@@ -4,7 +4,7 @@
export let content = '';
export let classes = '';
function getIntials(name: string) {
function getInitials(name: string) {
let words = name.split(' ');
// If there is only one word, split it by underscore
if (words.length === 1) {
@@ -32,7 +32,7 @@
<div class="phone {classes}">
<div class="avatar-initials u-text-center u-width-full-line u-line-height-1-5 u-medium u-bold">
{getIntials($project.name)}
{getInitials($project.name)}
</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">
@@ -16,7 +16,7 @@
let time: string;
let dateTime: Date;
let docsUrl = `https://appwrite.io/docs/products/messaging`;
let showConfimation = false;
let showConfirmation = false;
let totalTargets = $messageParams[$providerType].targets?.length ?? 0;
@@ -60,14 +60,14 @@
async function askForConfirmation() {
$wizard.interceptorNotificationEnabled = false;
showConfimation = true;
showConfirmation = true;
throw 'Show confirmation';
}
async function submit() {
$wizard.interceptorNotificationEnabled = true;
await $wizard.finalAction();
showConfimation = false;
showConfirmation = false;
}
$: if (when === 'now') {
@@ -127,14 +127,14 @@
</Helper>
</WizardStep>
<Modal title="Send message" bind:show={showConfimation} headerDivider={false} onSubmit={submit}>
<Modal title="Send message" bind:show={showConfirmation} headerDivider={false} onSubmit={submit}>
<p>
You are about to send a message to an estimated <span class="u-bold">{totalTargets}</span> recipients.
Would you like to proceed?
</p>
<p class="u-bold">This action is irreversible.</p>
<svelte:fragment slot="footer">
<Button text on:click={() => (showConfimation = false)}>Cancel</Button>
<Button text on:click={() => (showConfirmation = false)}>Cancel</Button>
<Button secondary submit>Send</Button>
</svelte:fragment>
</Modal>
@@ -16,7 +16,7 @@
import { Container, type UsagePeriods } from '$lib/layout';
import { onMount } from 'svelte';
import { onboarding, project } from '../store';
import Bandwith from './bandwith.svelte';
import Bandwidth from './bandwidth.svelte';
import { createApiKey } from './keys/+page.svelte';
import Onboard from './onboard.svelte';
import Realtime from './realtime.svelte';
@@ -91,7 +91,7 @@
<section class="common-section">
<div class="grid-dashboard-1s-2m-6l">
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
<Bandwith {period} on:change={(e) => changePeriod(e.detail)} />
<Bandwidth {period} on:change={(e) => changePeriod(e.detail)} />
</div>
<div class="card is-2-columns-medium-screen is-3-columns-large-screen">
<Requests {period} on:change={(e) => changePeriod(e.detail)} />
@@ -18,7 +18,7 @@
value: number;
}>;
$: bandwith = humanFileSize(totalMetrics($usage?.network));
$: bandwidth = humanFileSize(totalMetrics($usage?.network));
$: if (period) {
showPeriod = false;
@@ -28,8 +28,8 @@
<div class="u-flex u-gap-16 u-main-space-between">
<div>
<div class="heading-level-4">
{bandwith.value}
<span class="body-text-2">{bandwith.unit}</span>
{bandwidth.value}
<span class="body-text-2">{bandwidth.unit}</span>
</div>
<div>Bandwidth</div>
</div>
@@ -45,7 +45,7 @@
</svelte:fragment>
</DropList>
</div>
{#if bandwith.value !== '0'}
{#if bandwidth.value !== '0'}
<div style="height: 12rem;">
<BarChart
options={{
@@ -3,15 +3,20 @@
import Id from '$lib/components/id.svelte';
import { WizardStep } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { isCloud } from '$lib/system';
const { endpoint, project } = sdk.forProject.client.config;
const code = `import io.appwrite.Client
import io.appwrite.services.Account
val client = Client(context)
${
isCloud
? `val client = Client(context).setProject("${project}")`
: `val client = Client(context)
.setEndpoint("${endpoint}")
.setProject("${project}")
.setSelfSigned(status = true) // For self signed certificates, only use for development`;
.setSelfSigned(status: true) // For self signed certificates, only use for development`
}`;
</script>
<WizardStep>
@@ -1,17 +1,21 @@
<script lang="ts">
import { Alert, Code } from '$lib/components';
import { WizardStep } from '$lib/layout';
import { isSelfHosted } from '$lib/system';
import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import Id from '$lib/components/id.svelte';
const { endpoint, project } = sdk.forProject.client.config;
const code = `import Appwrite
let client = Client()
${
isCloud
? `let client = Client().setProject("${project}")`
: `let client = Client()
.setEndpoint("${endpoint}")
.setProject("${project}")
.setSelfSigned(true) // For self signed certificates, only use for development`;
.setSelfSigned(true) // For self signed certificates, only use for development`
}`;
let showAlert = true;
</script>
@@ -21,16 +25,16 @@ let client = Client()
<h2 class="heading-level-7">Initialize your SDK</h2>
<p>
Initialize your SDK by pointing the client to your Appwrite project using your <Id
value={project}>Project ID</Id
>.
Initialize your SDK by pointing the client to your Appwrite project using your
<Id value={project}>Project ID</Id>
.
</p>
<Code label="Apple SDK" labelIcon="apple" language="swift" {code} withCopy withLineNumbers />
<p class="u-margin-block-start-24">
Before sending any API calls to your new Appwrite project, make sure your device or emulator
has network access to your Appwrite project's hostname or IP address.
</p>
{#if showAlert && isSelfHosted}
{#if showAlert && !isCloud}
<div class="common-section">
<Alert type="info" dismissible on:dismiss={() => (showAlert = false)}>
<svelte:fragment slot="title">For self-hosted solutions</svelte:fragment>
@@ -1,7 +1,7 @@
<script lang="ts">
import { Alert, Code } from '$lib/components';
import { WizardStep } from '$lib/layout';
import { isSelfHosted } from '$lib/system';
import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import Id from '$lib/components/id.svelte';
@@ -9,10 +9,14 @@
const code = `import 'package:appwrite/appwrite.dart';
Client client = Client();
client
${
isCloud
? `client.setProject('${project}');`
: `client
.setEndpoint('${endpoint}')
.setProject('${project}')
.setSelfSigned(status: true); // For self signed certificates, only use for development`;
.setSelfSigned(status: true); // For self signed certificates, only use for development;`
}`;
let showAlert = true;
</script>
@@ -22,8 +26,8 @@ client
<h2 class="heading-level-7">Initialize your SDK</h2>
<p data-private>
Initialize your SDK by pointing the client to your Appwrite project using your <Id
value={project}>Project ID</Id>
Initialize your SDK by pointing the client to your Appwrite project using your
<Id value={project}>Project ID</Id>
</p>
<div class="u-margin-block-start-16">
<Code
@@ -39,7 +43,7 @@ client
Before sending any API calls to your new Appwrite project, make sure your device or emulator
has network access to your Appwrite project's hostname or IP address.
</p>
{#if showAlert && isSelfHosted}
{#if showAlert && !isCloud}
<div class="common-section">
<Alert type="info" on:dismiss={() => (showAlert = false)}>
<svelte:fragment slot="title">For self-hosted solutions</svelte:fragment>
@@ -1,7 +1,7 @@
<script lang="ts">
import { Alert, Code } from '$lib/components';
import { WizardStep } from '$lib/layout';
import { isSelfHosted } from '$lib/system';
import { isCloud } from '$lib/system';
import { sdk } from '$lib/stores/sdk';
import { createPlatform } from '../store';
import Id from '$lib/components/id.svelte';
@@ -9,11 +9,15 @@
const { endpoint, project } = sdk.forProject.client.config;
const code = `import { Client, Account, ID } from 'react-native-appwrite';
const client = new Client();
const client
.setEndpoint('${endpoint}')
const client = new Client()${
isCloud
? ''
: `
.setEndpoint('${endpoint}')`
}
.setProject('${project}')
.setPlatform('${$createPlatform.key}');`;
.setPlatform('${$createPlatform.key}');
`;
let showAlert = true;
</script>
@@ -40,7 +44,7 @@ const client
Before sending any API calls to your new Appwrite project, make sure your device or emulator
has network access to your Appwrite project's hostname or IP address.
</p>
{#if showAlert && isSelfHosted}
{#if showAlert && !isCloud}
<div class="common-section">
<Alert type="info" on:dismiss={() => (showAlert = false)}>
<svelte:fragment slot="title">For self-hosted solutions</svelte:fragment>
@@ -3,13 +3,17 @@
import Id from '$lib/components/id.svelte';
import { WizardStep } from '$lib/layout';
import { sdk } from '$lib/stores/sdk';
import { isCloud } from '$lib/system';
const { endpoint, project } = sdk.forProject.client.config;
const code = `const client = new Client();
client
${
isCloud
? `client.setProject('${project}');`
: `client
.setEndpoint('${endpoint}')
.setProject('${project}');`;
.setProject('${project}');`
}`;
</script>
<WizardStep>
@@ -21,7 +21,7 @@
import deepEqual from 'deep-equal';
import { onMount } from 'svelte';
import { organization } from '$lib/stores/organization';
import { SMTPSecure } from '@appwrite.io/console';
import { type SMTPSecure } from '@appwrite.io/console';
import InputSelect from '$lib/elements/forms/inputSelect.svelte';
import { upgradeURL } from '$lib/stores/billing';
@@ -65,6 +65,7 @@
username = undefined;
password = undefined;
}
await sdk.forConsole.projects.updateSmtp(
$project.$id,
enabled,
@@ -75,7 +76,7 @@
port ? port : undefined,
username ? username : undefined,
password ? password : undefined,
secure ? SMTPSecure.Tls : undefined
secure ? (secure as SMTPSecure) : undefined
);
invalidate(Dependencies.PROJECT);
@@ -18,7 +18,7 @@
show = false;
addNotification({
type: 'success',
message: `${$project.name} has been transfered to ${teamName}`
message: `${$project.name} has been transferred to ${teamName}`
});
trackEvent(Submit.ProjectUpdateTeam);
await goto(`${base}/organization-${teamId}`);
@@ -73,5 +73,5 @@
</script>
<Wizard confirmExit title="Create Webhook" steps={stepsComponents} on:finish={create}>
<svelte:fragment slot="exit">webhook creation</svelte:fragment>
<svelte:fragment slot="exit">Are you sure you want to exit from this process?</svelte:fragment>
</Wizard>
+4 -4
View File
@@ -231,7 +231,7 @@
<svelte:window on:keydown={windowKeyDown} />
<div class="cb-card" class:active class:interacting style={styles} bind:this={cardEl}>
<div class="card__translater">
<div class="card__translator">
<button
class="card__rotator"
aria-label="Expand the Card"
@@ -320,12 +320,12 @@
}
}
.cb-card.active .card__translater,
.cb-card.active .card__translator,
.cb-card.active .card__rotator {
touch-action: none;
}
.card__translater,
.card__translator,
.card__rotator {
display: grid;
perspective: 600px;
@@ -334,7 +334,7 @@
will-change: transform;
}
.card__translater {
.card__translator {
width: auto;
position: relative;
transform: translate3d(var(--tx), var(--ty), 0) scale(var(--s));