mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
Merge branch 'main' into 'refactor-terminologies'.
This commit is contained in:
@@ -11,7 +11,12 @@ test('upgrade - free tier', async ({ page }) => {
|
||||
await page.waitForURL(/\/organization-[^/]+\/change-plan/);
|
||||
await page.locator('input[value="tier-1"]').click();
|
||||
await page.getByRole('button', { name: 'add' }).first().click();
|
||||
|
||||
await enterCreditCard(page);
|
||||
|
||||
// wait for a second after adding a card to update the UI.
|
||||
await page.waitForSelector('button#method[role="combobox"]');
|
||||
|
||||
// skip members
|
||||
await page.getByRole('button', { name: 'change plan' }).click();
|
||||
await page.waitForURL(/\/console\/project-(?:[a-z0-9]+-)?([^/]+)\/get-started/);
|
||||
|
||||
@@ -21,7 +21,7 @@
|
||||
try {
|
||||
const card = await submitStripeCard(name, page?.params?.organization ?? null);
|
||||
modal.closeModal();
|
||||
invalidate(Dependencies.PAYMENT_METHODS);
|
||||
await invalidate(Dependencies.PAYMENT_METHODS);
|
||||
dispatch('submit', card);
|
||||
addNotification({
|
||||
type: 'success',
|
||||
|
||||
@@ -20,8 +20,19 @@
|
||||
|
||||
async function cardSaved(event: CustomEvent<PaymentMethodData>) {
|
||||
value = event.detail.$id;
|
||||
invalidate(Dependencies.UPGRADE_PLAN);
|
||||
invalidate(Dependencies.CREATE_ORGANIZATION);
|
||||
|
||||
if (value) {
|
||||
methods = {
|
||||
...methods,
|
||||
total: methods.total + 1,
|
||||
paymentMethods: [...methods.paymentMethods, event.detail]
|
||||
};
|
||||
}
|
||||
|
||||
await Promise.all([
|
||||
invalidate(Dependencies.UPGRADE_PLAN),
|
||||
invalidate(Dependencies.ORGANIZATION)
|
||||
]);
|
||||
}
|
||||
|
||||
onMount(() => {
|
||||
|
||||
@@ -58,6 +58,7 @@
|
||||
placeholder="Promo code"
|
||||
id="code"
|
||||
label="Add promo code"
|
||||
autofocus
|
||||
bind:value={coupon} />
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
|
||||
@@ -101,7 +101,8 @@
|
||||
|
||||
async function createProjectsBottomSheet(organization: Organization): Promise<SheetMenu> {
|
||||
isLoadingProjects = true;
|
||||
loadedProjects = await projects;
|
||||
// null on non-org/project path like `onboarding`.
|
||||
loadedProjects = (await projects) ?? loadedProjects;
|
||||
isLoadingProjects = false;
|
||||
|
||||
const createProjectItem = {
|
||||
|
||||
@@ -31,49 +31,62 @@
|
||||
}
|
||||
};
|
||||
|
||||
onMount(async () => {
|
||||
const saveColumnPreferences = () => {
|
||||
const shownColumns = $columns.filter((n) => n.hide !== true).map((n) => n.id);
|
||||
|
||||
if (isCustomCollection) {
|
||||
const prefs = preferences.getCustomTableColumns(page.params.table);
|
||||
columns.set(
|
||||
$columns.map((column) => {
|
||||
column.hide = prefs?.includes(column.id) ?? false;
|
||||
preferences.setCustomCollectionColumns(page.params.collection, shownColumns);
|
||||
} else {
|
||||
preferences.setColumns(shownColumns);
|
||||
}
|
||||
};
|
||||
|
||||
onMount(() => {
|
||||
if (isCustomCollection) {
|
||||
const shownColumns = preferences.getCustomCollectionColumns(page.params.collection);
|
||||
|
||||
columns.update((columns) => {
|
||||
return columns.map((column) => {
|
||||
column.hide = !shownColumns.includes(column.id);
|
||||
return column;
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
} else {
|
||||
const prefs = preferences.get(page.route);
|
||||
|
||||
// Override the shown columns only if a preference was set
|
||||
if (prefs?.columns) {
|
||||
columns.set(
|
||||
$columns.map((column) => {
|
||||
column.hide = prefs.columns?.includes(column.id) ?? false;
|
||||
if (prefs?.columns && prefs.columns.length > 0) {
|
||||
columns.update((cols) => {
|
||||
return cols.map((column) => {
|
||||
column.hide = !prefs.columns.includes(column.id);
|
||||
return column;
|
||||
})
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
columns.subscribe((ctx) => {
|
||||
const columns = ctx.filter((n) => n.hide === true).map((n) => n.id);
|
||||
|
||||
if (isCustomCollection) {
|
||||
preferences.setCustomCollectionColumns(columns);
|
||||
} else {
|
||||
preferences.setColumns(columns);
|
||||
}
|
||||
});
|
||||
|
||||
calcMaxHeight();
|
||||
});
|
||||
|
||||
let selectedColumnsNumber = $derived(
|
||||
$columns.reduce((acc, column) => {
|
||||
if (column.hide === true) return acc;
|
||||
if (column.hide) return acc;
|
||||
|
||||
return ++acc;
|
||||
}, 0)
|
||||
);
|
||||
|
||||
function toggleColumn(column: Column) {
|
||||
columns.update((cols) =>
|
||||
cols.map((col) => {
|
||||
if (col.id === column.id) {
|
||||
col.hide = !column.hide;
|
||||
}
|
||||
return col;
|
||||
})
|
||||
);
|
||||
|
||||
saveColumnPreferences();
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:window on:resize={calcMaxHeight} />
|
||||
@@ -86,7 +99,7 @@
|
||||
{#each $columns as column}
|
||||
{#if !column?.exclude}
|
||||
<ActionMenu.Item.Button
|
||||
on:click={() => (column.hide = !column.hide)}
|
||||
on:click={() => toggleColumn(column)}
|
||||
disabled={allowNoColumns
|
||||
? false
|
||||
: selectedColumnsNumber <= 1 && column.hide !== true}>
|
||||
@@ -94,7 +107,7 @@
|
||||
<Selector.Checkbox
|
||||
checked={!column.hide}
|
||||
size="s"
|
||||
on:click={() => (column.hide = !column.hide)} />
|
||||
on:click={() => toggleColumn(column)} />
|
||||
{column.title}
|
||||
</Layout.Stack>
|
||||
</ActionMenu.Item.Button>
|
||||
|
||||
@@ -10,7 +10,12 @@
|
||||
style="padding-block: 0.13rem">
|
||||
{#each Array(11) as _, i}
|
||||
<li>
|
||||
<Tag size="m" selected={value === i} on:click={() => (value = i)}>
|
||||
<Tag
|
||||
size="m"
|
||||
selected={value === i}
|
||||
autofocus={i === 0}
|
||||
data-rating={i}
|
||||
on:click={() => (value = i)}>
|
||||
{i}
|
||||
</Tag>
|
||||
</li>
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
<InputTextarea
|
||||
required
|
||||
id="feedback"
|
||||
autofocus
|
||||
bind:value={$feedbackData.message}
|
||||
label="Tell us more about your experience"
|
||||
placeholder="Share your suggestions and feature requests..." />
|
||||
|
||||
@@ -51,7 +51,7 @@
|
||||
import { base } from '$app/paths';
|
||||
import { logout } from '$lib/helpers/logout';
|
||||
import { app } from '$lib/stores/app';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { isTabletViewport, isSmallViewport } from '$lib/stores/viewport';
|
||||
import { isCloud } from '$lib/system.js';
|
||||
import { user } from '$lib/stores/user';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
@@ -166,20 +166,22 @@
|
||||
>Upgrade</Button.Anchor>
|
||||
{/if}
|
||||
|
||||
<DropList show={$feedback.show} class="extended-width">
|
||||
<Button.Button
|
||||
type="button"
|
||||
variant="compact"
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent(Click.FeedbackSubmitClick, { source: 'top_nav' });
|
||||
}}
|
||||
>Feedback
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
<Feedback />
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{#if !$isSmallViewport}
|
||||
<DropList show={$feedback.show} class="extended-width">
|
||||
<Button.Button
|
||||
type="button"
|
||||
variant="compact"
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent(Click.FeedbackSubmitClick, { source: 'top_nav' });
|
||||
}}
|
||||
>Feedback
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
<Feedback />
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{/if}
|
||||
<DropList
|
||||
noArrow
|
||||
scrollable
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
import MobileSupportModal from '$routes/(console)/wizard/support/mobileSupportModal.svelte';
|
||||
import MobileFeedbackModal from '$routes/(console)/wizard/feedback/mobileFeedbackModal.svelte';
|
||||
import { getSidebarState, updateSidebarState } from '$lib/helpers/sidebar';
|
||||
import { isTabletViewport } from '$lib/stores/viewport';
|
||||
import { isTabletViewport, isSmallViewport } from '$lib/stores/viewport';
|
||||
import { Click, trackEvent } from '$lib/actions/analytics';
|
||||
|
||||
import type { HTMLAttributes } from 'svelte/elements';
|
||||
@@ -255,7 +255,7 @@
|
||||
</Tooltip>
|
||||
</div>
|
||||
</Layout.Stack>
|
||||
{:else}
|
||||
{:else if $isSmallViewport}
|
||||
<div class="action-buttons">
|
||||
<Layout.Stack direction="column" gap="s">
|
||||
<DropList show={$feedback.show} scrollable>
|
||||
@@ -282,7 +282,6 @@
|
||||
trackEvent(Click.SupportOpenClick, { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
|
||||
<svelte:fragment slot="other">
|
||||
<MobileSupportModal bind:show={$showSupportModal}
|
||||
></MobileSupportModal>
|
||||
@@ -314,43 +313,33 @@
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
{#if project}
|
||||
<div class="only-mobile">
|
||||
<div class="action-buttons">
|
||||
<Layout.Stack direction="column" gap="s">
|
||||
<DropList show={$feedback.show} scrollable>
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'side_nav' });
|
||||
}}
|
||||
><span>Feedback</span>
|
||||
</Button.Button>
|
||||
<svelte:fragment slot="other">
|
||||
<MobileFeedbackModal />
|
||||
</svelte:fragment>
|
||||
</DropList>
|
||||
{#if project && $isSmallViewport}
|
||||
<div class="action-buttons">
|
||||
<Layout.Stack direction="column" gap="s">
|
||||
<DropList show={$feedback.show} scrollable>
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => {
|
||||
toggleFeedback();
|
||||
trackEvent('click_menu_feedback', { source: 'side_nav' });
|
||||
}}
|
||||
><span>Feedback</span>
|
||||
</Button.Button>
|
||||
</DropList>
|
||||
|
||||
<DropList show={$showSupportModal} scrollable>
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => {
|
||||
$showSupportModal = true;
|
||||
trackEvent(Click.SupportOpenClick, { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
|
||||
<svelte:fragment slot="other">
|
||||
<MobileSupportModal bind:show={$showSupportModal}
|
||||
></MobileSupportModal>
|
||||
</svelte:fragment>
|
||||
</Button.Button>
|
||||
</DropList>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
<DropList show={$showSupportModal} scrollable>
|
||||
<Button.Button
|
||||
variant="secondary"
|
||||
size="s"
|
||||
on:click={() => {
|
||||
$showSupportModal = true;
|
||||
trackEvent(Click.SupportOpenClick, { source: 'side_nav' });
|
||||
}}>
|
||||
<span>Support</span>
|
||||
</Button.Button>
|
||||
</DropList>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
{/if}
|
||||
</div>
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
label="Name"
|
||||
placeholder="Project name"
|
||||
required
|
||||
autofocus
|
||||
bind:value={projectName} />
|
||||
{#if !showCustomId}
|
||||
<div>
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
if (confirmExit) {
|
||||
showExitModal = true;
|
||||
} else {
|
||||
goto(href);
|
||||
goBack();
|
||||
trackEvent('wizard_exit', {
|
||||
from: 'escape'
|
||||
});
|
||||
@@ -58,6 +58,8 @@
|
||||
}
|
||||
}
|
||||
|
||||
const goBack = () => goto(href);
|
||||
|
||||
onMount(() => ($isNewWizardStatusOpen = true));
|
||||
|
||||
onDestroy(() => ($isNewWizardStatusOpen = false));
|
||||
@@ -77,7 +79,7 @@
|
||||
if (confirmExit) {
|
||||
showExitModal = true;
|
||||
} else {
|
||||
goto(href);
|
||||
goBack();
|
||||
trackEvent('wizard_exit', {
|
||||
from: 'button'
|
||||
});
|
||||
|
||||
@@ -29,7 +29,7 @@ type PreferencesStore = {
|
||||
} & { hideAiDisclaimer?: boolean };
|
||||
|
||||
async function updateConsolePreferences(store: PreferencesStore): Promise<void> {
|
||||
const currentPreferences = get(user).prefs ?? (await sdk.forConsole.account.getPrefs());
|
||||
const currentPreferences = get(user)?.prefs ?? (await sdk.forConsole.account.getPrefs());
|
||||
if (!currentPreferences?.console || Array.isArray(currentPreferences.console)) {
|
||||
currentPreferences.console = {};
|
||||
}
|
||||
@@ -78,9 +78,9 @@ function createPreferences() {
|
||||
let newPrefsSnapshot: PreferencesStore;
|
||||
|
||||
update((currentPrefs) => {
|
||||
oldPrefsSnapshot = currentPrefs;
|
||||
oldPrefsSnapshot = structuredClone(currentPrefs);
|
||||
callback(currentPrefs);
|
||||
newPrefsSnapshot = currentPrefs;
|
||||
newPrefsSnapshot = structuredClone(currentPrefs);
|
||||
return currentPrefs;
|
||||
});
|
||||
|
||||
@@ -148,16 +148,14 @@ function createPreferences() {
|
||||
|
||||
return n;
|
||||
}),
|
||||
setCustomCollectionColumns: (columns: Preferences['columns']) =>
|
||||
setCustomCollectionColumns: (tableId: string, columns: Preferences['columns']) =>
|
||||
updateAndSync((n) => {
|
||||
const table = page.params.table;
|
||||
if (!n?.tables?.[table]) {
|
||||
if (!n?.tables?.[tableId]) {
|
||||
n ??= {};
|
||||
n.tables ??= {};
|
||||
}
|
||||
|
||||
n.tables[table] = columns;
|
||||
|
||||
n.tables[tableId] = Array.from(new Set(columns));
|
||||
return n;
|
||||
}),
|
||||
loadTeamPrefs: async (id: string) => {
|
||||
@@ -189,6 +187,7 @@ function createPreferences() {
|
||||
|
||||
return n;
|
||||
});
|
||||
|
||||
await sdk.forConsole.teams.updatePrefs(orgId, teamPrefs);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -316,15 +316,19 @@
|
||||
|
||||
$: checkForUsageLimits($organization);
|
||||
|
||||
$: projects = sdk.forConsole.projects.list([
|
||||
Query.equal(
|
||||
'teamId',
|
||||
// id from page params ?? id from store ?? id from preferences
|
||||
page.params.organization ?? currentOrganizationId ?? data.currentOrgId
|
||||
),
|
||||
Query.limit(5),
|
||||
Query.orderDesc('$updatedAt')
|
||||
]);
|
||||
$: isOnOnboarding = page.route?.id?.includes('/(console)/onboarding');
|
||||
|
||||
$: projects = isOnOnboarding
|
||||
? null
|
||||
: sdk.forConsole.projects.list([
|
||||
Query.equal(
|
||||
'teamId',
|
||||
// id from page params ?? id from store ?? id from preferences
|
||||
page.params.organization ?? currentOrganizationId ?? data.currentOrgId
|
||||
),
|
||||
Query.limit(5),
|
||||
Query.orderDesc('$updatedAt')
|
||||
]);
|
||||
|
||||
$: if ($requestedMigration) {
|
||||
openMigrationWizard();
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
bind:value={name}
|
||||
label="Organization name"
|
||||
placeholder="Enter organization name"
|
||||
autofocus
|
||||
id="name"
|
||||
required />
|
||||
</Fieldset>
|
||||
|
||||
@@ -5,8 +5,9 @@ import type { Coupon } from '$lib/sdk/billing';
|
||||
import type { Organization } from '$lib/stores/organization';
|
||||
|
||||
export const load: PageLoad = async ({ url, parent, depends }) => {
|
||||
depends(Dependencies.CREATE_ORGANIZATION);
|
||||
const { organizations } = await parent();
|
||||
depends(Dependencies.CREATE_ORGANIZATION);
|
||||
|
||||
const [coupon, paymentMethods] = await Promise.all([
|
||||
getCoupon(url),
|
||||
sdk.forConsole.billing.listPaymentMethods()
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
import { isCloud } from '$lib/system';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { ID } from '@appwrite.io/console';
|
||||
import { BillingPlan } from '$lib/constants';
|
||||
import { BillingPlan, Dependencies } from '$lib/constants';
|
||||
import { tierToPlan } from '$lib/stores/billing';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { loadAvailableRegions } from '$routes/(console)/regions';
|
||||
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
|
||||
import { Button, Card, Layout, Input, Typography, Spinner } from '@appwrite.io/pink-svelte';
|
||||
import { Form } from '$lib/elements/forms/index.js';
|
||||
import { goto } from '$app/navigation';
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
|
||||
let isLoading = false;
|
||||
@@ -47,6 +47,10 @@
|
||||
if (organization) {
|
||||
loadAvailableRegions(organization?.$id).then();
|
||||
await goto(`${base}/organization-${organization.$id}`);
|
||||
|
||||
// fixes an edge case where
|
||||
// the org is not available for some reason!
|
||||
await invalidate(Dependencies.CREATE_ORGANIZATION);
|
||||
}
|
||||
isLoading = false;
|
||||
}
|
||||
@@ -66,6 +70,7 @@
|
||||
|
||||
<Input.Text
|
||||
required
|
||||
autofocus
|
||||
disabled={isLoading}
|
||||
label="Organization name"
|
||||
bind:value={organizationName}
|
||||
|
||||
@@ -102,7 +102,7 @@
|
||||
on:submit={createProject}>
|
||||
<svelte:fragment slot="submit">
|
||||
<Layout.Stack direction="row" justifyContent="flex-end">
|
||||
<Button.Button autofocus type="submit" variant="primary" size="s">
|
||||
<Button.Button type="submit" variant="primary" size="s">
|
||||
Create
|
||||
</Button.Button>
|
||||
</Layout.Stack>
|
||||
|
||||
@@ -271,11 +271,7 @@
|
||||
<title>Change plan - Appwrite</title>
|
||||
</svelte:head>
|
||||
|
||||
<Wizard
|
||||
title="Change plan"
|
||||
href={`${base}/organization-${page.params.organization}`}
|
||||
bind:showExitModal
|
||||
confirmExit>
|
||||
<Wizard title="Change plan" href={previousPage} bind:showExitModal confirmExit>
|
||||
<Form bind:this={formComponent} onSubmit={handleSubmit} bind:isSubmitting>
|
||||
<Layout.Stack gap="xxl">
|
||||
<Fieldset legend="Select plan">
|
||||
@@ -338,12 +334,12 @@
|
||||
</Layout.Stack>
|
||||
</Layout.Stack>
|
||||
</Fieldset>
|
||||
{:then paymentMethods}
|
||||
{:then}
|
||||
<Fieldset legend="Payment">
|
||||
<SelectPaymentMethod
|
||||
methods={paymentMethods}
|
||||
bind:taxId
|
||||
bind:value={paymentMethodId}
|
||||
bind:taxId>
|
||||
bind:methods={paymentMethods}>
|
||||
<svelte:fragment slot="actions">
|
||||
{#if !selectedCoupon?.code}
|
||||
{#if paymentMethodId}
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
id="email"
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
autofocus={true}
|
||||
autofocus
|
||||
bind:value={email} />
|
||||
<InputText id="member-name" label="Name" placeholder="Enter name" bind:value={name} />
|
||||
{#if isCloud}
|
||||
|
||||
@@ -50,6 +50,7 @@
|
||||
name="domain"
|
||||
bind:value={domainName}
|
||||
required
|
||||
autofocus
|
||||
placeholder="example.com" />
|
||||
|
||||
<Divider />
|
||||
|
||||
+7
-1
@@ -84,7 +84,13 @@
|
||||
DNS records map domain names to IP addresses or other resources.
|
||||
</span>
|
||||
<Layout.Stack gap="l">
|
||||
<InputText id="name" label="Name" placeholder="subdomain" bind:value={name} required />
|
||||
<InputText
|
||||
id="name"
|
||||
label="Name"
|
||||
placeholder="subdomain"
|
||||
bind:value={name}
|
||||
required
|
||||
autofocus />
|
||||
<Layout.Stack gap="xs">
|
||||
<InputSelect options={recordTypes} bind:value={type} id="type" label="Type" required />
|
||||
<Input.Helper state="default">
|
||||
|
||||
@@ -73,7 +73,7 @@
|
||||
<ConsoleButton
|
||||
size="s"
|
||||
event="create_user"
|
||||
on:mousedown={() => newMemberModal.set(true)}
|
||||
on:click={() => newMemberModal.set(true)}
|
||||
disabled={isCloud ? !$currentPlan?.addons?.seats?.supported : false}>
|
||||
<Icon size="s" icon={IconPlus} slot="start" />
|
||||
<span class="text">Invite</span>
|
||||
|
||||
+1
-1
@@ -115,7 +115,7 @@
|
||||
{ id: 'reads', hide: !databaseOperationMetric },
|
||||
{ id: 'writes', hide: !databaseOperationMetric },
|
||||
{ id: 'metric', hide: !!databaseOperationMetric },
|
||||
{ id: 'costs' }
|
||||
{ id: 'costs', hide: true }
|
||||
]}
|
||||
let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@
|
||||
</Button>
|
||||
</Layout.Stack>
|
||||
</div>
|
||||
<Paginator items={members.memberships} hideFooter={members?.total <= 5}>
|
||||
<Paginator items={members?.memberships} hideFooter={members?.total <= 5}>
|
||||
{#snippet children(paginatedItems: typeof members.memberships)}
|
||||
<Table.Root columns={2} let:root>
|
||||
<svelte:fragment slot="header" let:root>
|
||||
|
||||
@@ -34,7 +34,9 @@ export const load: LayoutLoad = async ({ params, depends, parent }) => {
|
||||
// fetch if not available in `plansInfo`
|
||||
includedInBasePlans
|
||||
? plansInfo.get(organization.billingPlan)
|
||||
: sdk.forConsole.billing.getOrganizationPlan(organization.$id),
|
||||
: isCloud
|
||||
? sdk.forConsole.billing.getOrganizationPlan(organization.$id)
|
||||
: null,
|
||||
|
||||
loadAvailableRegions(project.teamId)
|
||||
]);
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
</Layout.Stack>
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<ViewSelector view={View.Table} {columns} hideView />
|
||||
<Button on:mousedown={() => ($showCreateUser = true)} event="create_user" size="s">
|
||||
<Button on:click={() => ($showCreateUser = true)} event="create_user" size="s">
|
||||
<Icon size="s" icon={IconPlus} slot="start" />
|
||||
<span class="text">Create user</span>
|
||||
</Button>
|
||||
@@ -173,7 +173,7 @@
|
||||
href="https://appwrite.io/docs/references/cloud/server-nodejs/users"
|
||||
target="user"
|
||||
allowCreate={$canWriteUsers}
|
||||
on:mousedown={() => showCreateUser.set(true)} />
|
||||
on:click={() => showCreateUser.set(true)} />
|
||||
{/if}
|
||||
</Container>
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
</script>
|
||||
|
||||
<Modal title="Create user" {error} bind:show={showCreate} onSubmit={create}>
|
||||
<InputText id="name" label="Name" placeholder="Enter name" autofocus={true} bind:value={name} />
|
||||
<InputText autofocus id="name" label="Name" placeholder="Enter name" bind:value={name} />
|
||||
<InputEmail id="email" label="Email" placeholder="Enter email" bind:value={mail} />
|
||||
<InputPhone id="phone" label="Phone" placeholder="Enter phone" bind:value={phone} />
|
||||
<InputPassword id="password" label="Password" placeholder="Enter password" bind:value={pass} />
|
||||
|
||||
+6
-4
@@ -7,11 +7,13 @@
|
||||
import { createTimeUnitPair } from '$lib/helpers/unit';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { project } from '../../store';
|
||||
import { project as projectStore } from '../../store';
|
||||
import { Layout } from '@appwrite.io/pink-svelte';
|
||||
import { page } from '$app/state';
|
||||
|
||||
const { value, unit, baseValue, units } = createTimeUnitPair($project?.authDuration);
|
||||
const options = units.map((v) => ({ label: v.name, value: v.name }));
|
||||
const project = $derived($projectStore ?? page.data?.project);
|
||||
const { value, unit, baseValue, units } = $derived(createTimeUnitPair(project?.authDuration));
|
||||
const options = $derived(units.map((v) => ({ label: v.name, value: v.name })));
|
||||
|
||||
async function updateSessionLength() {
|
||||
try {
|
||||
@@ -43,7 +45,7 @@
|
||||
</Layout.Stack>
|
||||
</svelte:fragment>
|
||||
<svelte:fragment slot="actions">
|
||||
<Button disabled={$baseValue === $project.authDuration} on:click={updateSessionLength}>
|
||||
<Button disabled={$baseValue === project.authDuration} on:click={updateSessionLength}>
|
||||
Update
|
||||
</Button>
|
||||
</svelte:fragment>
|
||||
|
||||
+1
-1
@@ -52,7 +52,7 @@
|
||||
label="Email"
|
||||
placeholder="Enter email"
|
||||
required={true}
|
||||
autofocus={true}
|
||||
autofocus
|
||||
bind:value={email} />
|
||||
<InputText id="name" label="Name" placeholder="Enter name" bind:value={name} />
|
||||
<Alert.Inline status="info">
|
||||
|
||||
+4
-4
@@ -40,7 +40,7 @@
|
||||
id: column.key,
|
||||
title: column.key,
|
||||
type: column.type as ColumnType,
|
||||
show: selected?.includes(column.key) ?? true,
|
||||
hide: !selected?.includes(column.key),
|
||||
array: column?.array,
|
||||
format: 'format' in column && column?.format === 'enum' ? column.format : null,
|
||||
elements: 'elements' in column ? column.elements : null
|
||||
@@ -51,7 +51,7 @@
|
||||
...['$id', '$createdAt', '$updatedAt'].map((id) => ({
|
||||
id,
|
||||
title: id,
|
||||
show: true,
|
||||
hide: false,
|
||||
type: (id === '$id' ? 'string' : 'datetime') as ColumnType
|
||||
}))
|
||||
]);
|
||||
@@ -95,11 +95,11 @@
|
||||
<Layout.Stack direction="row" justifyContent="space-between">
|
||||
<Filters
|
||||
query={data.query}
|
||||
columns={tableColumns}
|
||||
columns={filterColumns}
|
||||
disabled={!(hasColumns && hasValidColumns)}
|
||||
analyticsSource="database_rows" />
|
||||
<Layout.Stack direction="row" alignItems="center" justifyContent="flex-end">
|
||||
<ViewSelector view={data.view} columns={tableColumns} hideView />
|
||||
<ViewSelector view={data.view} columns={tableColumns} hideView isCustomCollection />
|
||||
{#if flags.showCsvImport(data)}
|
||||
<Button
|
||||
secondary
|
||||
|
||||
+17
-2
@@ -11,19 +11,34 @@
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { isRelationship } from '../row-[row]/columns/store';
|
||||
import Confirm from '$lib/components/confirm.svelte';
|
||||
import { preferences } from '$lib/stores/preferences';
|
||||
|
||||
export let showDelete = false;
|
||||
export let selectedColumn: Columns;
|
||||
|
||||
const databaseId = page.params.database;
|
||||
let checked = false;
|
||||
|
||||
let error: string;
|
||||
let checked = false;
|
||||
|
||||
async function updateTableColumns() {
|
||||
const selectedColumns = preferences
|
||||
.getCustomTableColumns($table.$id)
|
||||
.filter((column) => column != selectedColumn.key);
|
||||
|
||||
// todo: change method name to table
|
||||
await preferences.setCustomCollectionColumns($table.$id, selectedColumns);
|
||||
await invalidate(Dependencies.TABLE);
|
||||
}
|
||||
|
||||
async function handleDelete() {
|
||||
try {
|
||||
await sdk
|
||||
.forProject(page.params.region, page.params.project)
|
||||
.tables.deleteColumn(databaseId, $table.$id, selectedColumn.key);
|
||||
await invalidate(Dependencies.TABLE);
|
||||
|
||||
await updateTableColumns();
|
||||
|
||||
showDelete = false;
|
||||
addNotification({
|
||||
type: 'success',
|
||||
|
||||
+8
-4
@@ -29,14 +29,18 @@
|
||||
};
|
||||
let error: string;
|
||||
|
||||
async function updateTableColumns() {
|
||||
const selectedColumns = preferences.getCustomTableColumns(tableId);
|
||||
selectedColumns.push(key ?? data?.key);
|
||||
await preferences.setCustomCollectionColumns(tableId, selectedColumns);
|
||||
await invalidate(Dependencies.COLLECTION);
|
||||
}
|
||||
|
||||
async function submit() {
|
||||
try {
|
||||
await $option.create(databaseId, tableId, key, data);
|
||||
await updateTableColumns();
|
||||
|
||||
let selectedColumns = preferences.getCustomTableColumns(tableId);
|
||||
selectedColumns.push(key ?? data?.key);
|
||||
preferences.setCustomCollectionColumns(selectedColumns);
|
||||
await invalidate(Dependencies.TABLE);
|
||||
if (!page.url.pathname.includes('columns')) {
|
||||
await goto(
|
||||
`${base}/project-${page.params.region}-${page.params.project}/databases/database-${databaseId}/table-${tableId}/columns`
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@
|
||||
id: column.key,
|
||||
title: column.key,
|
||||
type: column.type as ColumnType,
|
||||
show: selected?.includes(column.key) ?? true,
|
||||
hide: !selected?.includes(column.key),
|
||||
array: column?.array,
|
||||
width: { min: 168 },
|
||||
format: 'format' in column && column?.format === 'enum' ? column.format : null,
|
||||
|
||||
+7
-1
@@ -9,6 +9,12 @@
|
||||
$: count = data.executions;
|
||||
$: gbHoursTotal = data.executionsMbSecondsTotal / 1000 / 3600;
|
||||
$: mbSecondsCount = data.executionsMbSeconds;
|
||||
$: gbHoursCount = data.executionsMbSeconds
|
||||
?.map((metric) => ({
|
||||
...metric,
|
||||
value: metric.value / 1000 / 3600
|
||||
}))
|
||||
.filter(({ value }) => value);
|
||||
</script>
|
||||
|
||||
<Container>
|
||||
@@ -33,7 +39,7 @@
|
||||
title: 'Total GB hours'
|
||||
}}
|
||||
total={gbHoursTotal}
|
||||
count={mbSecondsCount} />
|
||||
count={gbHoursCount} />
|
||||
{/if}
|
||||
</Layout.Stack>
|
||||
</Container>
|
||||
|
||||
+1
@@ -7,6 +7,7 @@ export const load: PageLoad = async ({ params }) => {
|
||||
const period = isValueOfStringEnum(FunctionUsageRange, params.period)
|
||||
? params.period
|
||||
: FunctionUsageRange.ThirtyDays;
|
||||
|
||||
return sdk
|
||||
.forProject(params.region, params.project)
|
||||
.functions.getUsage(params.function, period);
|
||||
|
||||
+1
@@ -106,6 +106,7 @@
|
||||
id="subject"
|
||||
label="Subject"
|
||||
required
|
||||
autofocus={true}
|
||||
placeholder="Enter subject"
|
||||
bind:value={subject}>
|
||||
</InputText>
|
||||
|
||||
+1
@@ -116,6 +116,7 @@
|
||||
id="title"
|
||||
label="Title"
|
||||
required
|
||||
autofocus
|
||||
placeholder="Enter title"
|
||||
bind:value={title}>
|
||||
</InputText>
|
||||
|
||||
+1
@@ -96,6 +96,7 @@
|
||||
id="message"
|
||||
label="Message"
|
||||
required
|
||||
autofocus
|
||||
maxlength={900}
|
||||
placeholder="Type here..."
|
||||
bind:value={content}>
|
||||
|
||||
+3
-4
@@ -35,9 +35,8 @@
|
||||
const gitCloneCode =
|
||||
'\ngit clone https://github.com/appwrite/starter-for-react-native\ncd starter-for-react-native\n';
|
||||
|
||||
const updateConfigCode = `const EXPO_PUBLIC_APPWRITE_PROJECT_ID = "${projectId}";
|
||||
const EXPO_PUBLIC_APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}";
|
||||
`;
|
||||
const updateConfigCode = `EXPO_PUBLIC_APPWRITE_PROJECT_ID=${projectId}
|
||||
EXPO_PUBLIC_APPWRITE_ENDPOINT=${sdk.forProject(page.params.region, page.params.project).client.config.endpoint}`;
|
||||
|
||||
export let platform: PlatformType = PlatformType.Reactnativeandroid;
|
||||
|
||||
@@ -232,7 +231,7 @@ const EXPO_PUBLIC_APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="javascript" lineNumbers code={updateConfigCode} />
|
||||
<Code lang="dotenv" lineNumbers code={updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
|
||||
@@ -235,6 +235,7 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
id="hostname"
|
||||
label="Hostname"
|
||||
placeholder="localhost"
|
||||
autofocus
|
||||
error={hostnameError && 'Please enter a valid hostname'}
|
||||
bind:value={hostname}>
|
||||
<Tooltip slot="info">
|
||||
@@ -302,7 +303,7 @@ ${prefix}APPWRITE_ENDPOINT = "${sdk.forProject(page.params.region, page.params.p
|
||||
|
||||
<!-- Temporary fix: Remove this div once Code splitting issue with stack spacing is resolved -->
|
||||
<div class="pink2-code-margin-fix">
|
||||
<Code lang="bash" lineNumbers code={selectedFramework.updateConfigCode} />
|
||||
<Code lang="dotenv" lineNumbers code={selectedFramework.updateConfigCode} />
|
||||
</div>
|
||||
|
||||
<Typography.Text variant="m-500"
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { goto, invalidate } from '$app/navigation';
|
||||
import { base } from '$app/paths';
|
||||
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
|
||||
import { BoxAvatar, Confirm, CardGrid } from '$lib/components';
|
||||
import { BoxAvatar, CardGrid, Modal } from '$lib/components';
|
||||
import { Button, InputText } from '$lib/elements/forms';
|
||||
import { toLocaleDateTime } from '$lib/helpers/date';
|
||||
import { addNotification } from '$lib/stores/notifications';
|
||||
@@ -11,7 +11,6 @@
|
||||
import { project, projectRegion } from '../store';
|
||||
import { organization } from '$lib/stores/organization';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
|
||||
let error: string;
|
||||
let showDelete = false;
|
||||
let name: string = null;
|
||||
@@ -62,16 +61,12 @@
|
||||
</svelte:fragment>
|
||||
</CardGrid>
|
||||
|
||||
<Confirm
|
||||
disabled={name !== $project.name}
|
||||
onSubmit={handleDelete}
|
||||
title="Delete project"
|
||||
bind:open={showDelete}
|
||||
bind:error>
|
||||
<p>
|
||||
<b>This project will be deleted</b>, along with all of its metadata, stats, and other
|
||||
resources. <b>This action is irreversible</b>.
|
||||
</p>
|
||||
<Modal size="s" bind:show={showDelete} title="Delete project" onSubmit={handleDelete} bind:error>
|
||||
<svelte:fragment slot="description">
|
||||
This project will be deleted along with all of its metadata, stats, and other resources.
|
||||
<b>This action is irreversible.</b>
|
||||
</svelte:fragment>
|
||||
|
||||
<InputText
|
||||
label={`Enter "${$project.name}" to continue`}
|
||||
placeholder="Enter name"
|
||||
@@ -79,4 +74,9 @@
|
||||
autofocus
|
||||
required
|
||||
bind:value={name} />
|
||||
</Confirm>
|
||||
|
||||
<svelte:fragment slot="footer">
|
||||
<Button text on:click={() => (showDelete = false)}>Cancel</Button>
|
||||
<Button submissionLoader submit disabled={name !== $project.name}>Delete</Button>
|
||||
</svelte:fragment>
|
||||
</Modal>
|
||||
|
||||
+1
@@ -77,6 +77,7 @@
|
||||
id="domain"
|
||||
bind:value={domainName}
|
||||
required
|
||||
autofocus
|
||||
placeholder="appwrite.example.com" />
|
||||
</Form>
|
||||
|
||||
|
||||
@@ -10,14 +10,7 @@
|
||||
</script>
|
||||
|
||||
<Layout.Stack direction="row" alignItems="center" inline>
|
||||
{#if ['processing'].includes(status)}
|
||||
<Typography.Code color="--fgcolor-neutral-secondary">
|
||||
<Layout.Stack direction="row" alignItems="center" inline>
|
||||
0s
|
||||
<Spinner size="s" />
|
||||
</Layout.Stack>
|
||||
</Typography.Code>
|
||||
{:else if ['building'].includes(status)}
|
||||
{#if ['processing', 'building'].includes(status)}
|
||||
<Typography.Code color="--fgcolor-neutral-secondary">
|
||||
<Layout.Stack direction="row" alignItems="center" inline>
|
||||
<p use:timer={{ start: deployment.$createdAt }}></p>
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { humanFileSize } from '$lib/helpers/sizeConvertion';
|
||||
import { formatTimeDetailed } from '$lib/helpers/timeConversion';
|
||||
import type { Models } from '@appwrite.io/console';
|
||||
import { page } from '$app/state';
|
||||
import {
|
||||
Badge,
|
||||
Divider,
|
||||
@@ -45,7 +46,7 @@
|
||||
}
|
||||
|
||||
function getFilePreview(fileId: string) {
|
||||
return sdk.forConsole.storage.getFileView('screenshots', fileId);
|
||||
return sdk.forConsoleIn(page.params.region).storage.getFileView('screenshots', fileId);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
@@ -33,7 +33,7 @@
|
||||
}
|
||||
|
||||
function getFilePreview(fileId: string) {
|
||||
return sdk.forConsole.storage.getFileView('screenshots', fileId);
|
||||
return sdk.forConsoleIn(page.params.region).storage.getFileView('screenshots', fileId);
|
||||
}
|
||||
</script>
|
||||
|
||||
|
||||
+1
-1
@@ -88,7 +88,7 @@
|
||||
|
||||
<Layout.Stack>
|
||||
<Layout.Stack gap="s">
|
||||
<InputText id="domain" placeholder="my-domain" bind:value={domain}>
|
||||
<InputText id="domain" placeholder="my-domain" bind:value={domain} autofocus>
|
||||
<svelte:fragment slot="end">
|
||||
<Typography.Text variant="m-400" color="--fgcolor-neutral-tertiary">
|
||||
.{$regionalConsoleVariables._APP_DOMAIN_SITES}
|
||||
|
||||
@@ -44,10 +44,7 @@
|
||||
hideColumns={!data.buckets.total}
|
||||
hideView={!data.buckets.total} />
|
||||
{#if $canWriteBuckets}
|
||||
<Button
|
||||
on:mousedown={() => ($showCreateBucket = true)}
|
||||
event="create_bucket"
|
||||
size="s">
|
||||
<Button on:click={() => ($showCreateBucket = true)} event="create_bucket" size="s">
|
||||
<Icon icon={IconPlus} slot="start" size="s" />
|
||||
Create bucket
|
||||
</Button>
|
||||
|
||||
+12
-2
@@ -169,7 +169,15 @@
|
||||
</Table.Cell>
|
||||
<Table.Cell column="actions" {root}>
|
||||
<Popover let:toggle placement="bottom-start" padding="none">
|
||||
<Button text icon ariaLabel="more options" on:click={toggle}>
|
||||
<Button
|
||||
text
|
||||
icon
|
||||
ariaLabel="more options"
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
toggle();
|
||||
}}>
|
||||
<Icon icon={IconDotsHorizontal} size="s" />
|
||||
</Button>
|
||||
<ActionMenu.Root slot="tooltip">
|
||||
@@ -178,7 +186,9 @@
|
||||
</ActionMenu.Item.Anchor>
|
||||
<ActionMenu.Item.Button
|
||||
leadingIcon={IconTrash}
|
||||
on:click={() => {
|
||||
on:click={(e) => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
selectedFile = file;
|
||||
showDelete = true;
|
||||
}}>
|
||||
|
||||
+1
-1
@@ -32,5 +32,5 @@
|
||||
</script>
|
||||
|
||||
<Confirm {onSubmit} title="Delete file" bind:open={showDelete} bind:error>
|
||||
Are you sure you want to delete <b>{file.name}</b>?
|
||||
<p>Are you sure you want to delete <b>{file.name}</b>?</p>
|
||||
</Confirm>
|
||||
|
||||
@@ -32,7 +32,11 @@
|
||||
}
|
||||
|
||||
if (data?.couponData?.code) {
|
||||
trackEvent(Submit.AccountCreate, { campaign_name: data?.couponData?.code });
|
||||
trackEvent(Submit.AccountCreate, {
|
||||
campaign_name: data?.couponData?.code,
|
||||
email: mail,
|
||||
name: $user?.name
|
||||
});
|
||||
await goto(`${base}/apply-credit?code=${data?.couponData?.code}`);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -32,7 +32,11 @@
|
||||
await sdk.forConsole.account.create(ID.unique(), mail, pass, name ?? '');
|
||||
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
|
||||
|
||||
trackEvent(Submit.AccountCreate, { campaign_name: data?.couponData?.code });
|
||||
trackEvent(Submit.AccountCreate, {
|
||||
campaign_name: data?.couponData?.code,
|
||||
email: mail,
|
||||
name: name
|
||||
});
|
||||
|
||||
if (data?.couponData?.code) {
|
||||
await goto(`${base}/apply-credit?code=${data?.couponData?.code}`);
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
import { sdk } from '$lib/stores/sdk';
|
||||
import { Unauthenticated } from '$lib/layout';
|
||||
import { Dependencies } from '$lib/constants';
|
||||
import { trackEvent } from '$lib/actions/analytics';
|
||||
import { Submit, trackEvent } from '$lib/actions/analytics';
|
||||
import { onMount } from 'svelte';
|
||||
import { page } from '$app/state';
|
||||
import LoginLight from '$lib/images/login/login-light-mode.svg';
|
||||
@@ -74,10 +74,16 @@
|
||||
await sdk.forConsole.account.createEmailPasswordSession(mail, pass);
|
||||
const prefs = await sdk.forConsole.account.getPrefs();
|
||||
const newPrefs = { ...prefs, code };
|
||||
await sdk.forConsole.account.updatePrefs(newPrefs);
|
||||
await invalidate(Dependencies.ACCOUNT);
|
||||
await Promise.all([
|
||||
sdk.forConsole.account.updatePrefs(newPrefs),
|
||||
invalidate(Dependencies.ACCOUNT)
|
||||
]);
|
||||
await goto(base);
|
||||
trackEvent('submit_account_create', { code: code });
|
||||
trackEvent(Submit.AccountCreate, {
|
||||
email: mail,
|
||||
name: name,
|
||||
code: code
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
addNotification({
|
||||
|
||||
@@ -16,6 +16,7 @@ export const ssr = false;
|
||||
|
||||
export const load: LayoutLoad = async ({ depends, url, route }) => {
|
||||
depends(Dependencies.ACCOUNT);
|
||||
depends(Dependencies.CREATE_ORGANIZATION);
|
||||
|
||||
const [account, error] = (await sdk.forConsole.account
|
||||
.get()
|
||||
|
||||
Reference in New Issue
Block a user