Merge branch 'main' into supposed-improvements

This commit is contained in:
Darshan
2025-06-12 16:48:17 +05:30
committed by GitHub
30 changed files with 622 additions and 330 deletions
+2 -2
View File
@@ -22,11 +22,11 @@
},
"dependencies": {
"@ai-sdk/svelte": "^1.1.24",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19",
"@appwrite.io/console": "https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564",
"@appwrite.io/pink-icons": "0.25.0",
"@appwrite.io/pink-icons-svelte": "^2.0.0-RC.1",
"@appwrite.io/pink-legacy": "^1.0.3",
"@appwrite.io/pink-svelte": "https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4e03f34",
"@appwrite.io/pink-svelte": "https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778",
"@popperjs/core": "^2.11.8",
"@sentry/sveltekit": "^8.38.0",
"@stripe/stripe-js": "^3.5.0",
+15 -14
View File
@@ -12,8 +12,8 @@ importers:
specifier: ^1.1.24
version: 1.1.24(svelte@5.25.3)(zod@3.24.3)
'@appwrite.io/console':
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19
specifier: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
version: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564
'@appwrite.io/pink-icons':
specifier: 0.25.0
version: 0.25.0
@@ -24,8 +24,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3
'@appwrite.io/pink-svelte':
specifier: https://pkg.vc/-/@appwrite/@appwrite.io/pink-svelte@4e03f34
version: https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@4e03f34(svelte@5.25.3)
specifier: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778
version: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.25.3)
'@popperjs/core':
specifier: ^2.11.8
version: 2.11.8
@@ -257,12 +257,13 @@ packages:
'@analytics/type-utils@0.6.2':
resolution: {integrity: sha512-TD+xbmsBLyYy/IxFimW/YL/9L2IEnM7/EoV9Aeh56U64Ify8o27HJcKjo38XY9Tcn0uOq1AX3thkKgvtWvwFQg==}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19':
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564':
resolution: {tarball: https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564}
version: 1.8.0
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1':
resolution: {integrity: sha512-iLFlV55hj8mGuAbmxJGenxN5RaZMmVT4GJb9dv/MP1xBAtYibFq7JvBcxm18qV2KU8c31Rntf+Ub4GL7HwqTYg==}
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870}
version: 2.0.0-RC.1
peerDependencies:
svelte: ^4.0.0
@@ -281,8 +282,8 @@ packages:
'@appwrite.io/pink-legacy@1.0.3':
resolution: {integrity: sha512-GGde5fmPhs+s6/3aFeMPc/kKADG/gTFkYQSy6oBN8pK0y0XNCLrZZgBv+EBbdhwdtqVEWXa0X85Mv9w7jcIlwQ==}
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@4e03f34':
resolution: {tarball: https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@4e03f34}
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778':
resolution: {tarball: https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778}
version: 2.0.0-RC.2
peerDependencies:
svelte: ^4.0.0
@@ -3635,9 +3636,9 @@ snapshots:
'@analytics/type-utils@0.6.2': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@e190a19': {}
'@appwrite.io/console@https://pkg.pr.new/appwrite-labs/cloud/@appwrite.io/console@a5e5564': {}
'@appwrite.io/pink-icons-svelte@2.0.0-RC.1(svelte@5.25.3)':
'@appwrite.io/pink-icons-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)':
dependencies:
svelte: 5.25.3
@@ -3654,9 +3655,9 @@ snapshots:
'@appwrite.io/pink-icons': 1.0.0
the-new-css-reset: 1.11.3
'@appwrite.io/pink-svelte@https://pkg.vc/-/@appwrite/%40appwrite.io%2Fpink-svelte@4e03f34(svelte@5.25.3)':
'@appwrite.io/pink-svelte@https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-svelte@ee1b778(svelte@5.25.3)':
dependencies:
'@appwrite.io/pink-icons-svelte': 2.0.0-RC.1(svelte@5.25.3)
'@appwrite.io/pink-icons-svelte': https://pkg.pr.new/appwrite/pink/@appwrite.io/pink-icons-svelte@ee1b7788cd3f877c9aa1b6487976bd63a6196870(svelte@5.25.3)
'@floating-ui/dom': 1.6.13
'@melt-ui/pp': 0.3.2(@melt-ui/svelte@0.86.6(svelte@5.25.3))(svelte@5.25.3)
'@melt-ui/svelte': 0.86.6(svelte@5.25.3)
+7 -6
View File
@@ -167,13 +167,10 @@
<div style="padding: 1rem; padding-block-end: 0;">
<Alert.Inline
dismissible
title="We collect user responses to refine our experimental AI feature."
on:dismiss={() => {
$preferences.hideAiDisclaimer = true;
}}>
<span slot="title">
We collect user responses to refine our experimental AI feature.
</span>
</Alert.Inline>
}} />
</div>
{/if}
@@ -229,8 +226,11 @@
<Layout.Stack direction="row" gap="s">
<AvatarInitials size="s" name={$user.name} />
<form
class="input-text-wrapper u-width-full-line"
class="u-full-width input-text-wrapper"
style="--amount-of-buttons: 1;"
style:display="flex"
style:width="100%"
style:align-items="center"
on:submit|preventDefault={(e) => {
handleSubmit(e);
}}>
@@ -238,6 +238,7 @@
<input
type="text"
class="input-text"
style:width="100%"
placeholder="Ask a question..."
autofocus
bind:value={$input}
@@ -1,10 +1,13 @@
<script lang="ts">
import { base } from '$app/paths';
import { isTabletViewport } from '$lib/stores/viewport';
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
import { Button } from '$lib/elements/forms';
import { Icon } from '@appwrite.io/pink-svelte';
import { IconX } from '@appwrite.io/pink-icons-svelte';
import { isTabletViewport } from '$lib/stores/viewport';
import PinkBackground from '$lib/images/pink-background.svg';
import { createEventDispatcher, onMount, onDestroy } from 'svelte';
export let variant: 'gradient' | 'image' = 'gradient';
let container: HTMLElement;
const dispatch = createEventDispatcher();
@@ -19,8 +22,7 @@
const alertHeight = container?.getBoundingClientRect()?.height || 0;
const { header, sidebar, content } = queryLayoutElements();
const headerHeight = header?.getBoundingClientRect().height || 0;
const offset = alertHeight + (!isTabletViewport && header ? headerHeight : 0);
const offset = alertHeight + (!$isTabletViewport && header ? headerHeight : 0);
if (header) header.style.top = `${alertHeight}px`;
if (sidebar) {
sidebar.style.top = `${offset}px`;
@@ -35,23 +37,32 @@
<svelte:window on:resize={setNavigationHeight} />
<div bind:this={container} class="top-banner alert is-action is-action-and-top-sticky">
<div class="top-banner-bg">
<div class="top-banner-bg-1">
<img
src={`${base}/images/top-banner/bg-pink-desktop.svg`}
width="1283"
height="1278"
alt="" />
<div
bind:this={container}
class:darker={variant === 'image'}
class="top-banner alert is-action is-action-and-top-sticky">
{#if variant === 'gradient'}
<div class="top-banner-bg">
<div class="top-banner-bg-1">
<img
src={`${base}/images/top-banner/bg-pink-desktop.svg`}
width="1283"
height="1278"
alt="" />
</div>
<div class="top-banner-bg-2">
<img
src={`${base}/images/top-banner/bg-mint-desktop.svg`}
width="1051"
height="1271"
alt="" />
</div>
</div>
<div class="top-banner-bg-2">
<img
src={`${base}/images/top-banner/bg-mint-desktop.svg`}
width="1051"
height="1271"
alt="" />
{:else}
<div class="centered-image-only">
<img src={PinkBackground} width="1283" height="1278" alt="" />
</div>
</div>
{/if}
<div class="top-banner-content u-color-text-primary">
<slot />
@@ -62,17 +73,39 @@
</Button>
</div>
<style>
<style lang="scss">
.alert {
top: 0;
width: 100%;
z-index: 100;
position: fixed;
padding: 0.8rem;
&.darker {
background: var(--bgcolor-neutral-default);
}
}
@media (max-width: 768px) {
:global(.top-banner-button.position) {
.centered-image-only {
top: 0;
width: 100%;
height: 100%;
padding-left: 25vw;
position: absolute;
border-bottom: 1px solid var(--border-neutral);
@media (max-width: 768px) {
& img {
max-width: 100vw;
}
}
}
:global(.top-banner-button.position) {
z-index: 1;
@media (max-width: 768px) {
position: absolute;
padding-block-start: 1rem;
padding-block-end: 3.7625rem;
}
}
@@ -1,8 +1,8 @@
<script lang="ts">
import { BillingPlan } from '$lib/constants';
import { BASE_BILLING_PLANS, BillingPlan } from '$lib/constants';
import { formatCurrency } from '$lib/helpers/numbers';
import { plansInfo, type Tier, tierFree, tierPro, tierScale } from '$lib/stores/billing';
import { organization } from '$lib/stores/organization';
import { currentPlan, organization } from '$lib/stores/organization';
import { Badge, Layout, Typography } from '@appwrite.io/pink-svelte';
import { LabelCard } from '..';
@@ -14,6 +14,8 @@
$: freePlan = $plansInfo.get(BillingPlan.FREE);
$: proPlan = $plansInfo.get(BillingPlan.PRO);
$: scalePlan = $plansInfo.get(BillingPlan.SCALE);
$: isBasePlan = BASE_BILLING_PLANS.includes($currentPlan?.$id);
</script>
<Layout.Stack>
@@ -72,4 +74,25 @@
{formatCurrency(scalePlan?.price ?? 0)} per month + usage
</Typography.Text>
</LabelCard>
{#if $currentPlan && !isBasePlan}
<LabelCard
name="plan"
bind:group={billingPlan}
value={$currentPlan.$id}
title={$currentPlan.name}>
<svelte:fragment slot="action">
{#if $organization?.billingPlan === $currentPlan.$id && !isNewOrg}
<Badge variant="secondary" size="xs" content="Current plan" />
{/if}
</svelte:fragment>
<Typography.Caption variant="400">
{$currentPlan.desc}
</Typography.Caption>
<Typography.Text>
{@const isZeroPrice = ($currentPlan?.price ?? 0) <= 0}
{@const price = formatCurrency($currentPlan?.price ?? 0)}
{isZeroPrice ? price : `${price} per month + usage`}
</Typography.Text>
</LabelCard>
{/if}
</Layout.Stack>
+12 -4
View File
@@ -20,7 +20,7 @@
import { isCloud } from '$lib/system';
import { goto } from '$app/navigation';
import { base } from '$app/paths';
import { newOrgModal } from '$lib/stores/organization';
import { currentPlan, newOrgModal } from '$lib/stores/organization';
import { Click, trackEvent } from '$lib/actions/analytics';
import type { Models } from '@appwrite.io/console';
@@ -182,6 +182,14 @@
$: projectsBottomSheet = createProjectsBottomSheet(selectedOrg);
$: organizationsBottomSheet = createOrganizationBottomSheet(selectedOrg);
$: correctPlanName =
// the plan names are hardcoded in some cases and are not available locally,
// so we rely on the plan's source of truth - `$currentPlan`
$currentPlan &&
$currentPlan?.name.toLocaleLowerCase() !== selectedOrg?.tierName.toLocaleLowerCase()
? $currentPlan.name
: selectedOrg?.tierName; // fallback
</script>
<svelte:window on:resize={onResize} />
@@ -195,9 +203,9 @@
aria-label="Open organizations tab">
<span class="orgName">{selectedOrg?.name ?? 'Organization'}</span>
<span class="not-mobile"
>{#if selectedOrg?.tierName}<Badge
>{#if correctPlanName}<Badge
variant="secondary"
content={selectedOrg?.tierName} />{/if}</span>
content={correctPlanName} />{/if}</span>
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
</button>
{:else}
@@ -211,7 +219,7 @@
<span class="orgName" class:noProjects={!currentProject}
>{selectedOrg?.name ?? 'Organization'}</span>
<span class="not-mobile"
><Badge variant="secondary" content={selectedOrg?.tierName ?? ''} /></span>
><Badge variant="secondary" content={correctPlanName ?? ''} /></span>
<Icon icon={IconChevronDown} size="s" color="--fgcolor-neutral-secondary" />
</button>
{/if}
+37 -19
View File
@@ -18,6 +18,19 @@
children: Snippet<[toggle: () => void, selectedColumnsNumber: number]>;
} = $props();
let maxHeight = $state('none');
let containerRef;
const calcMaxHeight = () => {
if (containerRef) {
// get parent row element for correct top position
const parent = containerRef.parentElement.parentElement;
const { top } = parent.getBoundingClientRect();
maxHeight = `${window.innerHeight - top - 48}px`;
}
};
onMount(async () => {
if (isCustomCollection) {
const prefs = preferences.getCustomCollectionColumns(page.params.collection);
@@ -50,6 +63,8 @@
preferences.setColumns(columns);
}
});
calcMaxHeight();
});
let selectedColumnsNumber = $derived(
@@ -61,29 +76,32 @@
);
</script>
<svelte:window on:resize={calcMaxHeight} />
{#if $columns?.length}
<Popover let:toggle placement="bottom-end" padding="none">
{@render children(toggle, selectedColumnsNumber)}
<svelte:fragment slot="tooltip">
<ActionMenu.Root>
{#each $columns as column}
{#if !column?.exclude}
<ActionMenu.Item.Button
on:click={() => (column.hide = !column.hide)}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.hide !== true}>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
checked={!column.hide}
size="s"
on:click={() => (column.hide = !column.hide)} />
{column.title}
</Layout.Stack>
</ActionMenu.Item.Button>
{/if}
{/each}
</ActionMenu.Root>
<div style:max-height={maxHeight} style:overflow="scroll" bind:this={containerRef}>
<ActionMenu.Root>
{#each $columns as column}
{#if !column?.exclude}
<ActionMenu.Item.Button
on:click={() => (column.hide = !column.hide)}
disabled={allowNoColumns
? false
: selectedColumnsNumber <= 1 && column.hide !== true}>
<Layout.Stack direction="row" gap="s">
<Selector.Checkbox
checked={!column.hide}
size="s"
on:click={() => (column.hide = !column.hide)} />
{column.title}
</Layout.Stack>
</ActionMenu.Item.Button>
{/if}
{/each}
</ActionMenu.Root>
</div>
</svelte:fragment>
</Popover>
{/if}
+69 -100
View File
@@ -25,14 +25,13 @@
Navbar,
Icon,
Layout,
Link,
Tooltip,
Card,
ActionMenu,
ToggleButton,
Button,
Avatar,
Typography
Typography,
Popover
} from '@appwrite.io/pink-svelte';
import { toggleCommandCenter } from '$lib/commandCenter/commandCenter.svelte';
import {
@@ -214,91 +213,79 @@
</Button.Button>
<span slot="tooltip">{isMac() ? '⌘ + K' : 'Ctrl + K'}</span></Tooltip>
</Layout.Stack>
<Link.Button
on:click={() => {
showAccountMenu = !showAccountMenu;
shouldAnimateThemeToggle = false;
if (showAccountMenu) {
trackEvent(Click.MenuDropDownClick);
}
}}>
<div style:user-select="none">
<Popover let:toggle let:showing>
<button
type="button"
on:click|preventDefault={(e) => {
toggle(e);
shouldAnimateThemeToggle = false;
if (showing) {
trackEvent(Click.MenuDropDownClick);
}
}}
style:user-select="none">
<Avatar size="s" src={avatar} />
</div>
</Link.Button>
{#if showAccountMenu}
<div class="account-container">
<Card.Base padding="xxxs" shadow={true}>
</button>
<svelte:fragment slot="tooltip" let:toggle>
<ActionMenu.Root noPadding>
<Layout.Stack gap="xxs">
<ActionMenu.Root>
<Layout.Stack gap="xxs">
<div
style:padding-inline-start="10px"
style:padding-inline-end="8px"
style:padding-block="4px">
<Typography.Text variant="m-500">
{$user.email}
</Typography.Text>
</div>
<ActionMenu.Item.Anchor
trailingIcon={IconUser}
size="l"
href={`${base}/account`}>
Account</ActionMenu.Item.Anchor>
<div
style:padding-inline-start="10px"
style:padding-inline-end="8px"
style:padding-block="4px">
<Typography.Text variant="m-500">
{$user?.email}
</Typography.Text>
</div>
<ActionMenu.Item.Anchor
trailingIcon={IconUser}
on:click={toggle}
size="l"
href={`${base}/account`}>
Account</ActionMenu.Item.Anchor>
<ActionMenu.Item.Button
trailingIcon={IconLogoutRight}
size="l"
on:click={() => logout()}>Sign out</ActionMenu.Item.Button>
<div
style:padding-inline-start="10px"
style:padding-inline-end="8px">
<Layout.Stack
justifyContent="space-between"
direction="row"
alignItems="center">
<Typography.Text>Theme</Typography.Text>
<div
class:keepTransformTransition={shouldAnimateThemeToggle}>
<ToggleButton
bind:active={activeTheme}
on:change={() => {
setTimeout(() => {
shouldAnimateThemeToggle = true;
}, 150);
}}
buttons={[
{
id: 'light',
label: 'Light',
icon: IconSun
},
{
id: 'dark',
label: 'Dark',
icon: IconMoon
},
{
id: 'auto',
label: 'System',
icon: IconMode
}
]}></ToggleButton>
</div>
</Layout.Stack>
<ActionMenu.Item.Button
trailingIcon={IconLogoutRight}
size="l"
on:click={() => logout()}>Sign out</ActionMenu.Item.Button>
<div style:padding-inline-start="10px" style:padding-inline-end="8px">
<Layout.Stack
justifyContent="space-between"
direction="row"
alignItems="center">
<Typography.Text>Theme</Typography.Text>
<div class:keepTransformTransition={shouldAnimateThemeToggle}>
<ToggleButton
bind:active={activeTheme}
on:change={() => {
setTimeout(() => {
shouldAnimateThemeToggle = true;
}, 150);
}}
buttons={[
{
id: 'light',
label: 'Light',
icon: IconSun
},
{
id: 'dark',
label: 'Dark',
icon: IconMoon
},
{
id: 'auto',
label: 'System',
icon: IconMode
}
]}></ToggleButton>
</div>
</Layout.Stack>
</ActionMenu.Root>
</div>
</Layout.Stack>
</Card.Base>
</div>
<button
class="account-backdrop"
aria-label="Account menu"
on:click={() => {
showAccountMenu = false;
}}></button>
{/if}
</ActionMenu.Root>
</svelte:fragment>
</Popover>
</div>
</div>
</Navbar.Base>
@@ -410,24 +397,6 @@
}
}
.account-container {
position: absolute;
right: var(--space-7);
top: var(--base-44);
width: 244px;
display: flex;
z-index: 1;
}
.account-backdrop {
position: absolute;
left: 0;
top: 0;
width: 100vw;
height: 100vh;
background-color: transparent;
}
/* The default drop list has a max-inline width of 280px, which squeezes the support modal. */
:global(.extended-width) {
max-inline-size: none;
+2
View File
@@ -530,6 +530,8 @@ export enum BillingPlan {
ENTERPRISE = 'ent-1'
}
export const BASE_BILLING_PLANS: string[] = [BillingPlan.FREE, BillingPlan.PRO, BillingPlan.SCALE];
export const feedbackDowngradeOptions = [
{
value: 'availableFeatures',
+76
View File
@@ -0,0 +1,76 @@
<svg width="481" height="44" viewBox="0 0 481 44" fill="none" xmlns="http://www.w3.org/2000/svg">
<g style="mix-blend-mode:screen" opacity="0.25" filter="url(#filter0_f_2804_2539)">
<circle cx="242.19" cy="52.7439" r="110.744" fill="url(#paint0_radial_2804_2539)"/>
</g>
<g filter="url(#filter1_f_2804_2539)">
<ellipse cx="240.479" cy="56.2318" rx="40.0747" ry="119.968" transform="rotate(66.0796 240.479 56.2318)" fill="url(#paint1_radial_2804_2539)" fill-opacity="0.4"/>
</g>
<g filter="url(#filter2_f_2804_2539)">
<ellipse cx="228.238" cy="51.0802" rx="23.6296" ry="70.7377" transform="rotate(66.0796 228.238 51.0802)" fill="url(#paint2_radial_2804_2539)" fill-opacity="0.5"/>
</g>
<g filter="url(#filter3_f_2804_2539)">
<path d="M191.132 89.2277C140.692 116.602 92.3886 125.135 83.2442 108.286C74.0998 91.4362 107.577 55.5854 158.017 28.2107C208.458 0.835917 256.761 -7.69658 265.906 9.15282C275.05 26.0022 241.573 61.853 191.132 89.2277Z" fill="url(#paint3_radial_2804_2539)" fill-opacity="0.5"/>
</g>
<g filter="url(#filter4_f_2804_2539)">
<ellipse cx="128.998" cy="25.9598" rx="16.2007" ry="66.1069" transform="rotate(66.0796 128.998 25.9598)" fill="url(#paint4_radial_2804_2539)" fill-opacity="0.05"/>
</g>
<g filter="url(#filter5_f_2804_2539)">
<path d="M238.738 55.4355C188.298 82.8103 145.088 100.728 142.227 95.4567C139.366 90.1851 177.937 63.72 228.378 36.3452C278.818 8.97044 322.028 -8.94768 324.889 -3.67603C327.75 1.59561 289.179 28.0608 238.738 55.4355Z" fill="url(#paint5_radial_2804_2539)" fill-opacity="0.35"/>
</g>
<defs>
<filter id="filter0_f_2804_2539" x="70.3866" y="-119.06" width="343.607" height="343.607" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="30.5299" result="effect1_foregroundBlur_2804_2539"/>
</filter>
<filter id="filter1_f_2804_2539" x="0.195038" y="-134.073" width="480.567" height="380.61" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="64.7038" result="effect1_foregroundBlur_2804_2539"/>
</filter>
<filter id="filter2_f_2804_2539" x="62.8613" y="-84.8274" width="330.753" height="271.815" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="50" result="effect1_foregroundBlur_2804_2539"/>
</filter>
<filter id="filter3_f_2804_2539" x="49.7393" y="-31.4919" width="249.671" height="180.422" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_2804_2539"/>
</filter>
<filter id="filter4_f_2804_2539" x="47.2728" y="-25.6037" width="163.45" height="103.127" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="10.4674" result="effect1_foregroundBlur_2804_2539"/>
</filter>
<filter id="filter5_f_2804_2539" x="110.078" y="-36.5962" width="246.96" height="164.973" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
<feGaussianBlur stdDeviation="16" result="effect1_foregroundBlur_2804_2539"/>
</filter>
<radialGradient id="paint0_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(242.19 52.7439) rotate(90) scale(110.744)">
<stop stop-color="#FD366E"/>
<stop offset="1" stop-color="#FD366E" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint1_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(240.479 56.2318) rotate(90) scale(119.968 40.0747)">
<stop stop-color="#FE9567"/>
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint2_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(228.238 51.0802) rotate(90) scale(70.7377 23.6296)">
<stop stop-color="#FFEAE1"/>
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint3_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(174.575 58.7192) rotate(151.511) scale(103.914 34.7119)">
<stop stop-color="#F02E65"/>
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint4_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(128.998 25.9598) rotate(90) scale(66.1069 16.2007)">
<stop stop-color="#FFEAE1"/>
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
</radialGradient>
<radialGradient id="paint5_radial_2804_2539" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="translate(233.558 45.8904) rotate(151.511) scale(103.914 10.8603)">
<stop stop-color="#F02E65"/>
<stop offset="1" stop-color="#F02E65" stop-opacity="0"/>
</radialGradient>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB

@@ -17,7 +17,7 @@
let {
columns,
view = View.Table,
view = $bindable(View.Table),
hideView = false,
hideColumns = false,
hasSearch = false,
+35 -3
View File
@@ -11,7 +11,7 @@ import PaymentAuthRequired from '$lib/components/billing/alerts/paymentAuthRequi
import PaymentMandate from '$lib/components/billing/alerts/paymentMandate.svelte';
import { BillingPlan, NEW_DEV_PRO_UPGRADE_COUPON } from '$lib/constants';
import { cachedStore } from '$lib/helpers/cache';
import { sizeToBytes, type Size } from '$lib/helpers/sizeConvertion';
import { type Size, sizeToBytes } from '$lib/helpers/sizeConvertion';
import type {
AddressesList,
Aggregation,
@@ -28,12 +28,18 @@ import { AppwriteException, Query } from '@appwrite.io/console';
import { derived, get, writable } from 'svelte/store';
import { headerAlert } from './headerAlert';
import { addNotification, notifications } from './notifications';
import { organization, type Organization, type OrganizationError } from './organization';
import {
currentPlan,
organization,
type Organization,
type OrganizationError
} from './organization';
import { canSeeBilling } from './roles';
import { sdk } from './sdk';
import { user } from './user';
import BudgetLimitAlert from '$routes/(console)/organization-[organization]/budgetLimitAlert.svelte';
import TeamReadonlyAlert from '$routes/(console)/organization-[organization]/teamReadonlyAlert.svelte';
import EnterpriseTrial from '$routes/(console)/organization-[organization]/enterpriseTrial.svelte';
export type Tier = 'tier-0' | 'tier-1' | 'tier-2' | 'auto-1' | 'cont-1' | 'ent-1';
@@ -268,6 +274,32 @@ export function isServiceLimited(serviceId: PlanServices, plan: Tier, total: num
return isLimited && total >= limit && !hasUsageFees;
}
export function checkForEnterpriseTrial(org: Organization) {
if (!org || !org.billingNextInvoiceDate) return;
if (calculateEnterpriseTrial(org) > 0) {
headerAlert.add({
id: 'teamEnterpriseTrial',
component: EnterpriseTrial,
show: true,
importance: 11
});
}
}
export function calculateEnterpriseTrial(org: Organization) {
const endDate = new Date(org.billingNextInvoiceDate);
const startDate = new Date(org.billingCurrentInvoiceDate);
const today = new Date();
let diffCycle = endDate.getTime() - startDate.getTime();
diffCycle = Math.ceil(diffCycle / (1000 * 60 * 60 * 24));
if (diffCycle === 14) {
const remaining = endDate.getTime() - today.getTime();
return Math.ceil(remaining / (1000 * 60 * 60 * 24));
}
return 0;
}
export function calculateTrialDay(org: Organization) {
if (org?.billingPlan === BillingPlan.FREE) return false;
const endDate = new Date(org?.billingStartDate);
@@ -323,7 +355,7 @@ export async function checkForUsageLimit(org: Organization) {
];
const members = org.total;
const plan = get(plansInfo)?.get(org.billingPlan);
const plan = get(currentPlan);
const membersOverflow =
members > plan.addons.seats.limit ? members - (plan.addons.seats.limit || members) : 0;
+6 -2
View File
@@ -13,6 +13,10 @@ export type Account = Models.User<
>;
export const user = derived(page, ($page) => {
if (browser) sessionStorage.setItem('account', JSON.stringify($page.data.account));
return $page.data.account as Account;
if ($page.data?.account) {
if (browser) {
sessionStorage.setItem('account', JSON.stringify($page.data.account));
}
return $page.data.account as Account;
} else return null;
});
@@ -167,7 +167,10 @@
{error}
{formGroup}
groupKey={getAsType(groupKey)}
reportValue={report?.[getReportKey(groupKey)]} />
reportValue={report?.[getReportKey(groupKey)]}
on:updateFormGroup={(e) => {
$formData[groupKey] = e.detail;
}} />
{/if}
{/each}
{/if}
+2
View File
@@ -13,6 +13,7 @@
import Create from './createOrganization.svelte';
import {
calculateTrialDay,
checkForEnterpriseTrial,
checkForMandate,
checkForMarkedForDeletion,
checkForMissingPaymentMethod,
@@ -295,6 +296,7 @@
if (currentOrganizationId === org.$id) return;
if (isCloud) {
currentOrganizationId = org.$id;
checkForEnterpriseTrial(org);
await checkForUsageLimit(org);
checkForMarkedForDeletion(org);
await checkForNewDevUpgradePro(org);
@@ -20,7 +20,7 @@
let error: string;
let showCustomId = false;
let disabled: boolean = false;
let name: string = 'Appwrite project';
let name: string = 'New project';
async function create() {
try {
@@ -17,7 +17,7 @@
export let showCreateProjectCloud: boolean;
let id: string = null;
let name: string = 'Appwrite project';
let name: string = 'New project';
let region: string = Region.Fra;
let error: string = null;
@@ -39,12 +39,11 @@
let showDelete = false;
let selectedRecord: Models.DnsRecord = null;
function formatRecordName(name: string) {
const limit = 30;
function formatName(name: string, limit: number = 30) {
return {
value: name.length > limit ? `${name.slice(0, limit)}...` : name,
truncated: name.length > limit,
whole: name
value: name ? (name.length > limit ? `${name.slice(0, limit)}...` : name) : '-',
truncated: name ? name.length > limit : undefined,
whole: name ?? '-'
};
}
</script>
@@ -65,7 +64,7 @@
{#each $columns as column}
<Table.Cell column={column.id} {root}>
{#if column.id === 'name'}
{@const formatted = formatRecordName(record.name)}
{@const formatted = formatName(record.name)}
<Tooltip placement="bottom" disabled={!formatted.truncated}>
<Typography.Text truncate>{formatted.value}</Typography.Text>
<span
@@ -99,7 +98,22 @@
</Typography.Text>
{:else if column.id === 'comment'}
<Typography.Text truncate>
{record?.comment ?? '-'}
{@const formatted = formatName(record?.comment)}
<Tooltip
placement="bottom"
maxWidth="fit-content"
disabled={!formatted.truncated}>
<Typography.Text truncate>{formatted.value}</Typography.Text>
<span
slot="tooltip"
let:showing
style:white-space="pre-wrap"
style:word-break="break-all">
{#if showing}
{formatted.whole}
{/if}
</span>
</Tooltip>
</Typography.Text>
{:else if column.id === '$createdAt'}
<DualTimeView time={record.$createdAt} />
@@ -0,0 +1,69 @@
<script lang="ts">
import { page } from '$app/state';
import { Button } from '$lib/elements/forms';
import { organization } from '$lib/stores/organization';
import { calculateEnterpriseTrial, hideBillingHeaderRoutes } from '$lib/stores/billing';
import { base } from '$app/paths';
import GradientBanner from '$lib/components/billing/gradientBanner.svelte';
import { Badge, Typography } from '@appwrite.io/pink-svelte';
import { activeHeaderAlert } from '../store';
$: upgradeUrl = `${base}/organization-${$organization?.$id}/change-plan`;
$: remainingDays = calculateEnterpriseTrial($organization);
let show = true;
function handleClose() {
show = false;
const now = new Date().getTime();
localStorage.setItem($activeHeaderAlert.id, now.toString());
}
</script>
{#if $organization?.$id && remainingDays > 0 && !hideBillingHeaderRoutes.includes(page.url.pathname) && show}
<GradientBanner variant="image" on:close={handleClose}>
<div class="banner-fullwidth-grid">
<Typography.Text color="--fgcolor-neutral-primary">
Your enterprise trial expires in <Badge
size="xs"
variant="secondary"
content={remainingDays.toString()} /> days
</Typography.Text>
<Button size="xs" secondary fullWidthMobile href={upgradeUrl}>Upgrade</Button>
</div>
</GradientBanner>
{/if}
<style lang="scss">
.banner-fullwidth-grid {
width: 100vw;
display: grid;
padding: 0 3rem;
align-items: center;
grid-template-columns: 1fr auto 1fr;
> :global(:first-child) {
grid-column: 2;
justify-self: center;
@media (max-width: 768px) {
grid-column: unset;
}
}
> :global(:last-child) {
grid-column: 3;
justify-self: end;
margin-inline-end: 4px;
}
@media (max-width: 768px) {
gap: 12px;
width: 100%;
display: flex;
padding: unset;
flex-direction: column;
}
}
</style>
@@ -8,7 +8,7 @@
import { isTabSelected } from '$lib/helpers/load';
import { Cover } from '$lib/layout';
import { daysLeftInTrial, getServiceLimit, plansInfo, readOnly } from '$lib/stores/billing';
import { members, newMemberModal, organization } from '$lib/stores/organization';
import { members, newMemberModal, type Organization } from '$lib/stores/organization';
import {
canSeeBilling,
canSeeProjects,
@@ -18,20 +18,21 @@
} from '$lib/stores/roles';
import { GRACE_PERIOD_OVERRIDE, isCloud } from '$lib/system';
import { IconGithub, IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Icon, Tooltip, Typography, Layout, Badge } from '@appwrite.io/pink-svelte';
import { Badge, Icon, Layout, Tooltip, Typography } from '@appwrite.io/pink-svelte';
let areMembersLimited: boolean;
$: organization.subscribe(() => {
$: {
const limit = getServiceLimit('members') || Infinity;
const isLimited = limit !== 0 && limit < Infinity;
areMembersLimited =
isCloud &&
(($readOnly && !GRACE_PERIOD_OVERRIDE) || (isLimited && $members?.total >= limit));
});
}
$: organization = page.data.organization as Organization;
$: avatars = $members.memberships?.map((m) => m.userName || m.userEmail) ?? [];
$: organizationId = $organization?.$id ?? page.params.organization;
$: path = `${base}/organization-${organizationId}`;
$: path = `${base}/organization-${organization.$id}`;
$: tabs = [
{
href: path,
@@ -75,26 +76,26 @@
].filter((tab) => !tab.disabled);
</script>
{#if $organization.$id}
{#if organization?.$id}
<Cover>
<svelte:fragment slot="header">
<span class="u-flex u-cross-center u-gap-8 u-min-width-0">
<Typography.Title color="--fgcolor-neutral-primary" size="xl" truncate>
{$organization.name}
{organization.name}
</Typography.Title>
{#if isCloud && $organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
{#if isCloud && organization?.billingPlan === BillingPlan.GITHUB_EDUCATION}
<Badge variant="secondary" content="Education">
<Icon icon={IconGithub} size="s" slot="start" />
</Badge>
{:else if isCloud && $organization?.billingPlan === BillingPlan.FREE}
{:else if isCloud && organization?.billingPlan === BillingPlan.FREE}
<Badge variant="secondary" content="Free"></Badge>
{/if}
{#if isCloud && $organization?.billingTrialStartDate && $daysLeftInTrial > 0 && $organization.billingPlan !== BillingPlan.FREE && $plansInfo.get($organization.billingPlan)?.trialDays}
{#if isCloud && organization?.billingTrialStartDate && $daysLeftInTrial > 0 && organization.billingPlan !== BillingPlan.FREE && $plansInfo.get(organization.billingPlan)?.trialDays}
<Tooltip>
<Badge variant="secondary" content="Trial" />
<svelte:fragment slot="tooltip">
{`Your trial ends on ${toLocaleDate(
$organization.billingStartDate
organization.billingStartDate
)}. ${$daysLeftInTrial} days remaining.`}
</svelte:fragment>
</Tooltip>
@@ -1,37 +1,31 @@
<script lang="ts">
import { Empty, PaginationWithLimit, SearchQuery, ViewSelector } from '$lib/components';
import { Empty, PaginationWithLimit } from '$lib/components';
import { Button } from '$lib/elements/forms';
import { Container } from '$lib/layout';
import { Container, ResponsiveContainerHeader } from '$lib/layout';
import { columns, showCreate } from './store';
import Table from './table.svelte';
import Grid from './grid.svelte';
import type { PageData } from './$types';
import { canWriteCollections } from '$lib/stores/roles';
import { Icon, Layout } from '@appwrite.io/pink-svelte';
import { Icon } from '@appwrite.io/pink-svelte';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
export let data: PageData;
</script>
<Container>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery placeholder="Search by name or ID" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<ViewSelector
{columns}
view={data.view}
hideColumns={!data.collections.total}
hideView={!data.collections.total} />
{#if $canWriteCollections}
<Button on:click={() => ($showCreate = true)} event="create_collection">
<Icon icon={IconPlus} slot="start" size="s" />
Create collection
</Button>
{/if}
</Layout.Stack>
</Layout.Stack>
<ResponsiveContainerHeader
bind:view={data.view}
{columns}
hasSearch
searchPlaceholder="Search by name or ID">
{#if $canWriteCollections}
<Button on:click={() => ($showCreate = true)} event="create_collection">
<Icon icon={IconPlus} slot="start" size="s" />
Create collection
</Button>
{/if}
</ResponsiveContainerHeader>
{#if data.collections.total}
{#if data.view === 'grid'}
@@ -11,7 +11,7 @@
import { sortBranches } from '$lib/stores/vcs';
import { IconInfo } from '@appwrite.io/pink-icons-svelte';
import { LabelCard } from '$lib/components';
import { Runtime, StatusCode, type Models } from '@appwrite.io/console';
import { type Models, ProxyResourceType, Runtime, StatusCode } from '@appwrite.io/console';
import { statusCodeOptions } from '$lib/stores/domains';
import { writable } from 'svelte/store';
import { onMount } from 'svelte';
@@ -48,18 +48,21 @@
async function addDomain() {
const apexDomain = getApexDomain(domainName);
let domain = data.domains?.domains.find((d) => d.domain === apexDomain);
let domain = data.domains?.domains.find((d: Models.Domain) => d.domain === apexDomain);
if (apexDomain && !domain && isCloud) {
try {
domain = await sdk.forConsole.domains.create($project.teamId, apexDomain);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
return;
// apex might already be added on organization level, skip.
const alreadyAdded = error?.type === 'domain_already_exists';
if (!alreadyAdded) {
addNotification({
type: 'error',
message: error.message
});
return;
}
}
}
@@ -72,7 +75,13 @@
} else if (behaviour === 'REDIRECT') {
rule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.createRedirectRule(domainName, redirect, statusCode);
.proxy.createRedirectRule(
domainName,
redirect,
statusCode,
page.params.function,
ProxyResourceType.Function
);
} else if (behaviour === 'ACTIVE') {
rule = await sdk
.forProject(page.params.region, page.params.project)
@@ -1,19 +1,11 @@
<script lang="ts">
import { base } from '$app/paths';
import { page } from '$app/state';
import {
Empty,
EmptyFilter,
EmptySearch,
Id,
PaginationWithLimit,
SearchQuery,
ViewSelector
} from '$lib/components';
import { Filters, hasPageQueries } from '$lib/components/filters';
import { Empty, EmptyFilter, EmptySearch, Id, PaginationWithLimit } from '$lib/components';
import { hasPageQueries } from '$lib/components/filters';
import { Button } from '$lib/elements/forms';
import { toLocaleDateTime } from '$lib/helpers/date';
import { Container } from '$lib/layout';
import { Container, ResponsiveContainerHeader } from '$lib/layout';
import { MessagingProviderType } from '@appwrite.io/console';
import CreateMessageDropdown from './createMessageDropdown.svelte';
import FailedModal from './failedModal.svelte';
@@ -105,18 +97,18 @@
</script>
<Container>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery placeholder="Search by description, type, status, or ID" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Filters query={data.query} {columns} analyticsSource="messaging_messages" />
<ViewSelector view={data.view} {columns} hideView />
{#if $canWriteMessages}
<CreateMessageDropdown />
{/if}
</Layout.Stack>
</Layout.Stack>
<ResponsiveContainerHeader
{columns}
bind:view={data.view}
hideView
hasFilters
hasSearch
analyticsSource="messaging_messages"
searchPlaceholder="Search by description, type, status, or ID">
{#if $canWriteMessages}
<CreateMessageDropdown />
{/if}
</ResponsiveContainerHeader>
{#if data.messages.total}
<Table.Root
@@ -1,27 +1,20 @@
<script lang="ts">
import { page } from '$app/state';
import { Button } from '$lib/elements/forms';
import {
Empty,
EmptySearch,
SearchQuery,
PaginationWithLimit,
EmptyFilter,
ViewSelector
} from '$lib/components';
import { Empty, EmptySearch, PaginationWithLimit, EmptyFilter } from '$lib/components';
import Create from './create.svelte';
import { goto } from '$app/navigation';
import { Container } from '$lib/layout';
import { Container, ResponsiveContainerHeader } from '$lib/layout';
import { base } from '$app/paths';
import type { Models } from '@appwrite.io/console';
import type { PageData } from './$types';
import { showCreate } from './store';
import { Filters, hasPageQueries } from '$lib/components/filters';
import { hasPageQueries } from '$lib/components/filters';
import Table from './table.svelte';
import type { Column } from '$lib/helpers/types';
import { writable } from 'svelte/store';
import { canWriteTopics } from '$lib/stores/roles';
import { Icon, Layout } from '@appwrite.io/pink-svelte';
import { Icon } from '@appwrite.io/pink-svelte';
import { View } from '$lib/helpers/load';
import { IconPlus } from '@appwrite.io/pink-icons-svelte';
import { Click, trackEvent } from '$lib/actions/analytics';
@@ -72,26 +65,26 @@
</script>
<Container>
<Layout.Stack direction="row" justifyContent="space-between">
<Layout.Stack direction="row" alignItems="center">
<SearchQuery placeholder="Search by name or ID" />
</Layout.Stack>
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
<Filters query={data.query} {columns} analyticsSource="messaging_topics_filter" />
<ViewSelector view={View.Table} {columns} hideView />
{#if $canWriteTopics}
<Button
on:click={() => {
$showCreate = true;
trackEvent(Click.MessagingTopicCreateClick);
}}
event="create_topic">
<Icon icon={IconPlus} slot="start" size="s" />
Create topic
</Button>
{/if}
</Layout.Stack>
</Layout.Stack>
<ResponsiveContainerHeader
{columns}
view={View.Table}
hideView
hasFilters
hasSearch
analyticsSource="messaging_topics_filter"
searchPlaceholder="Search by name or ID">
{#if $canWriteTopics}
<Button
on:click={() => {
$showCreate = true;
trackEvent(Click.MessagingTopicCreateClick);
}}
event="create_topic">
<Icon icon={IconPlus} slot="start" size="s" />
Create topic
</Button>
{/if}
</ResponsiveContainerHeader>
{#if data.topics.total}
<Table columns={$columns} {data} />
@@ -115,9 +115,11 @@ static const String APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.reg
projectId,
platform,
$createPlatform.name,
$createPlatform.key || undefined,
platform === PlatformType.Flutterweb ? undefined : $createPlatform.key || undefined,
undefined,
undefined
platform === PlatformType.Flutterweb
? $createPlatform.hostname || undefined
: undefined
);
isPlatformCreated = true;
@@ -189,19 +191,35 @@ static const String APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.reg
bind:value={$createPlatform.name} />
<!-- Tooltips on InputText don't work as of now -->
<InputText
id="hostname"
label={hostname[platform]}
placeholder={placeholder[platform].hostname}
required
bind:value={$createPlatform.key}>
<Tooltip slot="info" maxWidth="15rem">
<Icon icon={IconInfo} size="s" />
<Typography.Caption variant="400" slot="tooltip">
{placeholder[platform].tooltip}
</Typography.Caption>
</Tooltip>
</InputText>
{#if platform === PlatformType.Flutterweb}
<InputText
id="hostname"
label={hostname[platform]}
placeholder={placeholder[platform].hostname}
required
bind:value={$createPlatform.hostname}>
<Tooltip slot="info" maxWidth="15rem">
<Icon icon={IconInfo} size="s" />
<Typography.Caption variant="400" slot="tooltip">
{placeholder[platform].tooltip}
</Typography.Caption>
</Tooltip>
</InputText>
{:else}
<InputText
id="key"
label={hostname[platform]}
placeholder={placeholder[platform].hostname}
required
bind:value={$createPlatform.key}>
<Tooltip slot="info" maxWidth="15rem">
<Icon icon={IconInfo} size="s" />
<Typography.Caption variant="400" slot="tooltip">
{placeholder[platform].tooltip}
</Typography.Caption>
</Tooltip>
</InputText>
{/if}
</Layout.Stack>
<Button
@@ -212,7 +230,7 @@ static const String APPWRITE_PUBLIC_ENDPOINT = "${sdk.forProject(page.params.reg
submissionLoader={isCreatingPlatform}
disabled={!platform ||
!$createPlatform.name ||
!$createPlatform.key ||
(!$createPlatform.key && !$createPlatform.hostname) ||
isCreatingPlatform}>
Create platform
</Button>
@@ -12,6 +12,7 @@
import { isCloud } from '$lib/system';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import { getApexDomain } from '$lib/helpers/tlds';
import type { Models } from '@appwrite.io/console';
const routeBase = `${base}/project-${page.params.region}-${page.params.project}/settings/domains`;
@@ -29,18 +30,21 @@
async function addDomain() {
const apexDomain = getApexDomain(domainName);
let domain = data.domains?.domains.find((d) => d.domain === apexDomain);
let domain = data.domains?.domains.find((d: Models.Domain) => d.domain === apexDomain);
if (apexDomain && !domain && isCloud) {
try {
domain = await sdk.forConsole.domains.create($project.teamId, apexDomain);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
return;
// apex might already be added on organization level, skip.
const alreadyAdded = error?.type === 'domain_already_exists';
if (!alreadyAdded) {
addNotification({
type: 'error',
message: error.message
});
return;
}
}
}
@@ -9,12 +9,15 @@
} from '@appwrite.io/pink-svelte';
import type { MigrationFormData } from '$lib/stores/migration';
import { Button } from '$lib/elements/forms';
import { createEventDispatcher } from 'svelte';
export let error: boolean = false;
export let groupKey: keyof MigrationFormData;
export let formGroup: MigrationFormData[typeof groupKey];
export let reportValue: string | number | undefined = undefined;
const dispatch = createEventDispatcher();
const labelMap = {
users: { root: 'Users', teams: 'Include teams' },
databases: { root: 'Databases', documents: 'Include documents' },
@@ -61,9 +64,12 @@
function onParentChange(event: CustomEvent<boolean | 'indeterminate'>) {
if (event.detail === 'indeterminate') return;
const updated = { ...formGroup };
for (const key of Object.keys(formGroup)) {
formGroup[key] = event.detail;
updated[key] = event.detail;
}
dispatch('updateFormGroup', updated);
}
$: isLoading = !error;
@@ -74,7 +80,13 @@
<Button extraCompact on:click={() => (formGroup.root = !formGroup.root)}>
<Layout.Stack direction="row" gap="s" alignItems="center">
<Layout.Stack inline direction="row" gap="l" alignItems="flex-start">
<Selector.Checkbox size="s" bind:checked={formGroup.root} />
<Selector.Checkbox
size="s"
bind:checked={formGroup.root}
on:change={(event) => {
const updated = { ...formGroup, root: event.detail };
dispatch('updateFormGroup', updated);
}} />
<Typography.Text variant="m-500" color="--fgcolor-neutral-primary">
{labelMap[groupKey]?.root ?? groupKey}
@@ -26,18 +26,10 @@ export const load = async ({ params, depends, url, route }) => {
.forProject(params.region, params.project)
.proxy.listRules(
[
Query.or([
Query.and([
Query.equal('type', RuleType.REDIRECT),
Query.equal('trigger', RuleTrigger.MANUAL)
]),
Query.and([
Query.equal('type', RuleType.DEPLOYMENT),
Query.equal('trigger', RuleTrigger.MANUAL),
Query.equal('deploymentResourceType', DeploymentResourceType.SITE),
Query.equal('deploymentResourceId', params.site)
])
]),
Query.equal('type', [RuleType.DEPLOYMENT, RuleType.REDIRECT]),
Query.equal('deploymentResourceType', DeploymentResourceType.SITE),
Query.equal('deploymentResourceId', params.site),
Query.equal('trigger', RuleTrigger.MANUAL),
Query.limit(limit),
Query.offset(offset),
Query.orderDesc(''),
@@ -16,13 +16,17 @@
BuildRuntime,
Framework,
type Models,
ProxyResourceType,
StatusCode
} from '@appwrite.io/console';
import { statusCodeOptions } from '$lib/stores/domains';
import { writable } from 'svelte/store';
import { onMount } from 'svelte';
import { ConnectRepoModal } from '$lib/components/git/index.js';
import { project } from '$routes/(console)/project-[region]-[project]/store';
import {
project,
regionalConsoleVariables
} from '$routes/(console)/project-[region]-[project]/store';
import { isCloud } from '$lib/system';
import { getApexDomain } from '$lib/helpers/tlds';
@@ -53,17 +57,23 @@
async function addDomain() {
const apexDomain = getApexDomain(domainName);
let domain = data.domains?.domains.find((d) => d.domain === apexDomain);
let domain = data.domains?.domains.find((d: Models.Domain) => d.domain === apexDomain);
if (apexDomain && !domain && isCloud) {
const isSiteDomain = domainName.endsWith($regionalConsoleVariables._APP_DOMAIN_SITES);
if (isCloud && apexDomain && !domain && !isSiteDomain) {
try {
domain = await sdk.forConsole.domains.create($project.teamId, apexDomain);
} catch (error) {
addNotification({
type: 'error',
message: error.message
});
return;
// apex might already be added on organization level, skip.
const alreadyAdded = error?.type === 'domain_already_exists';
if (!alreadyAdded) {
addNotification({
type: 'error',
message: error.message
});
return;
}
}
}
@@ -76,7 +86,13 @@
} else if (behaviour === 'REDIRECT') {
rule = await sdk
.forProject(page.params.region, page.params.project)
.proxy.createRedirectRule(domainName, redirect, statusCode);
.proxy.createRedirectRule(
domainName,
redirect,
statusCode,
page.params.site,
ProxyResourceType.Site
);
} else if (behaviour === 'ACTIVE') {
rule = await sdk
.forProject(page.params.region, page.params.project)
@@ -107,7 +107,7 @@
helper="Password must be at least 8 characters long"
required
bind:value={pass} />
<InputChoice required value={terms} id="terms" label="terms" showLabel={false}>
<InputChoice required bind:value={terms} id="terms" label="terms" showLabel={false}>
By registering, you agree that you have read, understand, and acknowledge our <Link.Anchor
href="https://appwrite.io/privacy"
target="_blank"
@@ -119,10 +119,16 @@
target="_blank"
rel="noopener noreferrer">General Terms of Use</Link.Anchor
>.</InputChoice>
<Button fullWidth submit {disabled}>Sign up</Button>
<Button fullWidth submit disabled={disabled || !terms}>Sign up</Button>
{#if isCloud}
<span class="with-separators eyebrow-heading-3">or</span>
<Button secondary fullWidth on:click={onGithubLogin} {disabled}>
<Button
secondary
fullWidth
on:click={onGithubLogin}
disabled={disabled || !terms}>
<span class="icon-github" aria-hidden="true"></span>
<span class="text">Sign up with GitHub</span>
</Button>