diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 0967ef424..000000000 --- a/.vscode/settings.json +++ /dev/null @@ -1 +0,0 @@ -{} diff --git a/src/lib/actions/analytics.ts b/src/lib/actions/analytics.ts index 84c178cc4..664d86d46 100644 --- a/src/lib/actions/analytics.ts +++ b/src/lib/actions/analytics.ts @@ -118,6 +118,7 @@ export enum Submit { UserCreate = 'submit_user_create', UserDelete = 'submit_user_delete', UserUpdateEmail = 'submit_user_update_email', + UserUpdateLabels = 'submit_user_update_labels', UserUpdateName = 'submit_user_update_name', UserUpdatePassword = 'submit_user_update_password', UserUpdatePhone = 'submit_user_update_phone', @@ -131,6 +132,7 @@ export enum Submit { ProjectCreate = 'submit_project_create', ProjectDelete = 'submit_project_delete', ProjectUpdateName = 'submit_project_update_name', + ProjectUpdateTeam = 'submit_project_update_team', ProjectService = 'submit_project_service', MemberCreate = 'submit_member_create', MemberDelete = 'submit_member_delete', diff --git a/src/lib/actions/portal.ts b/src/lib/actions/portal.ts index d0e90b767..05531b47c 100644 --- a/src/lib/actions/portal.ts +++ b/src/lib/actions/portal.ts @@ -6,7 +6,7 @@ export type PortalConfig = string | HTMLElement | undefined; export const portal: Action = (el, target = 'body') => { let targetEl; - async function update(newTarget: HTMLElement | string | undefined) { + async function update(newTarget: PortalConfig) { target = newTarget; if (typeof target === 'string') { targetEl = document.querySelector(target); diff --git a/src/lib/commandCenter/commandCenter.svelte b/src/lib/commandCenter/commandCenter.svelte index 3bae64543..3dcf142f9 100644 --- a/src/lib/commandCenter/commandCenter.svelte +++ b/src/lib/commandCenter/commandCenter.svelte @@ -90,8 +90,6 @@ } $commandCenterKeyDownHandler(e); }; - - $: console.log($subPanels); diff --git a/src/lib/commandCenter/commands.ts b/src/lib/commandCenter/commands.ts index 1e4609615..64d3ea0c9 100644 --- a/src/lib/commandCenter/commands.ts +++ b/src/lib/commandCenter/commands.ts @@ -1,5 +1,6 @@ import { debounce } from '$lib/helpers/debounce'; import { isMac } from '$lib/helpers/platform'; +import { wizard } from '$lib/stores/wizard'; import { onMount } from 'svelte'; import { derived, writable } from 'svelte/store'; import { nanoid } from 'nanoid/non-secure'; @@ -106,8 +107,8 @@ function hasDisputing(command: KeyedCommand, allCommands: Command[]) { } export const commandCenterKeyDownHandler = derived( - [commandMap, commandsEnabled], - ([$commandMap, enabled]) => { + [commandMap, commandsEnabled, wizard], + ([$commandMap, enabled, $wizard]) => { const commandsArr = Array.from($commandMap.values()).flat(); let recentKeyCodes: number[] = []; let validCommands: KeyedCommand[] = []; @@ -178,9 +179,9 @@ export const commandCenterKeyDownHandler = derived( for (const command of commandsArr) { if (!isKeyedCommand(command)) continue; if (!command.forceEnable) { - if (command.disabled) continue; - if (!enabled) continue; - if (isInputEvent(event)) continue; + if (command.disabled || !enabled || isInputEvent(event) || $wizard.show) { + continue; + } } const { keys, ctrl: meta, shift, alt } = command; diff --git a/src/lib/components/empty.svelte b/src/lib/components/empty.svelte index 2c01f69fa..9dc0c6103 100644 --- a/src/lib/components/empty.svelte +++ b/src/lib/components/empty.svelte @@ -21,7 +21,8 @@ {#if single}
-
+
diff --git a/src/lib/components/feedback/feedbackGeneral.svelte b/src/lib/components/feedback/feedbackGeneral.svelte new file mode 100644 index 000000000..183921a56 --- /dev/null +++ b/src/lib/components/feedback/feedbackGeneral.svelte @@ -0,0 +1,19 @@ + + + + + + + diff --git a/src/lib/components/feedback/feedbackNPS.svelte b/src/lib/components/feedback/feedbackNPS.svelte new file mode 100644 index 000000000..613e10514 --- /dev/null +++ b/src/lib/components/feedback/feedbackNPS.svelte @@ -0,0 +1,30 @@ + + + + How likely are you to recommend Appwrite to a friend or colleague? + +{#if $feedbackData.value} + + + + + +{/if} diff --git a/src/lib/components/feedback/index.ts b/src/lib/components/feedback/index.ts new file mode 100644 index 000000000..15d522683 --- /dev/null +++ b/src/lib/components/feedback/index.ts @@ -0,0 +1,3 @@ +export { default as Feedback } from './feedback.svelte'; +export { default as FeedbackGeneral } from './feedbackGeneral.svelte'; +export { default as FeedbackNPS } from './feedbackNPS.svelte'; diff --git a/src/lib/components/feedbackNPS.svelte b/src/lib/components/feedbackNPS.svelte deleted file mode 100644 index a9276b4a7..000000000 --- a/src/lib/components/feedbackNPS.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - -
-
-

How are we doing?

- -
-
- At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you - think. -
- - - - How likely are you to recommend Appwrite to a friend or colleague? - - {#if value} - - - - - - {/if} -
- - -
- -
diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts index 3f59e53a1..581b39146 100644 --- a/src/lib/components/index.ts +++ b/src/lib/components/index.ts @@ -32,9 +32,6 @@ export { default as Box } from './box.svelte'; export { default as Search } from './search.svelte'; export { default as SearchQuery } from './searchQuery.svelte'; export { default as GridItem1 } from './gridItem1.svelte'; -export { default as FeedbackGeneral } from './feedbackGeneral.svelte'; -export { default as FeedbackNPS } from './feedbackNPS.svelte'; -export { default as Evaluation } from './evaluation.svelte'; export { default as Steps } from './steps.svelte'; export { default as Step } from './step.svelte'; export { default as Code } from './code.svelte'; diff --git a/src/lib/components/modal.svelte b/src/lib/components/modal.svelte index 3801b0fb7..5b4e58757 100644 --- a/src/lib/components/modal.svelte +++ b/src/lib/components/modal.svelte @@ -3,6 +3,7 @@ import { Alert } from '$lib/components'; import { trackEvent } from '$lib/actions/analytics'; import { Form } from '$lib/elements/forms'; + import { disableCommands } from '$lib/commandCenter'; export let show = false; export let size: 'small' | 'big' = null; @@ -70,6 +71,8 @@ closeModal(); } + $: $disableCommands(show); + $: if (error) { alert?.scrollIntoView({ behavior: 'smooth', block: 'start', inline: 'nearest' }); } diff --git a/src/lib/components/tab.svelte b/src/lib/components/tab.svelte index 246d9db0d..f3825acbf 100644 --- a/src/lib/components/tab.svelte +++ b/src/lib/components/tab.svelte @@ -1,19 +1,95 @@
  • {#if href} - + {:else} @@ -22,7 +98,10 @@ class="tabs-button" class:is-selected={selected} on:click|preventDefault - on:click={track}> + on:click={handleClick} + tabindex={selected ? 0 : -1} + on:keydown={handleKeyDown} + role="tab"> {/if} diff --git a/src/lib/elements/table/cellCheck.svelte b/src/lib/elements/table/cellCheck.svelte new file mode 100644 index 000000000..3c55aa395 --- /dev/null +++ b/src/lib/elements/table/cellCheck.svelte @@ -0,0 +1,29 @@ + + + + { + // Prevent the link from being followed + e.preventDefault(); + const el = e.currentTarget; + if (!isHTMLInputElement(el)) return; + + selectedIds = toggle(selectedIds, id); + + // Hack to make sure the checkbox is checked, independent of the + // preventDefault() call above + window.setTimeout(() => { + el.checked = selectedIds.includes(id); + }); + }} /> + diff --git a/src/lib/elements/table/cellHeadCheck.svelte b/src/lib/elements/table/cellHeadCheck.svelte new file mode 100644 index 000000000..9ef8a22d7 --- /dev/null +++ b/src/lib/elements/table/cellHeadCheck.svelte @@ -0,0 +1,33 @@ + + + + + diff --git a/src/lib/elements/table/index.ts b/src/lib/elements/table/index.ts index c702ca645..ca4752e8a 100644 --- a/src/lib/elements/table/index.ts +++ b/src/lib/elements/table/index.ts @@ -9,6 +9,8 @@ export { default as TableRowLink } from './rowLink.svelte'; export { default as TableRowButton } from './rowButton.svelte'; export { default as TableCell } from './cell.svelte'; export { default as TableCellHead } from './cellHead.svelte'; +export { default as TableCellHeadCheck } from './cellHeadCheck.svelte'; export { default as TableCellLink } from './cellLink.svelte'; export { default as TableCellAvatar } from './cellAvatar.svelte'; export { default as TableCellText } from './cellText.svelte'; +export { default as TableCellCheck } from './cellCheck.svelte'; diff --git a/src/lib/elements/table/tableScroll.svelte b/src/lib/elements/table/tableScroll.svelte index 7a4017c0a..01b4186a7 100644 --- a/src/lib/elements/table/tableScroll.svelte +++ b/src/lib/elements/table/tableScroll.svelte @@ -3,11 +3,28 @@ export let isSticky = false; export let noMargin = false; + let isOverflowing = false; - const hasOverflow: Action void> = (node, callback) => { + const hasOverflow: Action = (node) => { const observer = new ResizeObserver((entries) => { for (const entry of entries) { - callback(entry.contentRect.width < entry.target.scrollWidth); + let overflowing = false; + if (entry.contentRect.width < entry.target.scrollWidth) { + overflowing = true; + } + + const cols = entry.target.querySelectorAll('.table-thead-col'); + for (let i = 0; i < cols.length; i++) { + const col = cols[i]; + const cs = getComputedStyle(col); + const innerWidth = + col.clientWidth - parseFloat(cs.paddingLeft) - parseFloat(cs.paddingRight); + if (innerWidth < 32) { + overflowing = true; + } + } + + isOverflowing = overflowing; } }); @@ -19,8 +36,6 @@ } }; }; - - let isOverflowing = false;
    diff --git a/src/lib/helpers/date.ts b/src/lib/helpers/date.ts index 8558f315f..68f9c31a7 100644 --- a/src/lib/helpers/date.ts +++ b/src/lib/helpers/date.ts @@ -1,5 +1,10 @@ export const toLocaleDate = (datetime: string) => { const date = new Date(datetime); + + if (isNaN(date.getTime())) { + return 'n/a'; + } + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', @@ -11,6 +16,11 @@ export const toLocaleDate = (datetime: string) => { export const toLocaleDateTime = (datetime: string | number) => { const date = new Date(datetime); + + if (isNaN(date.getTime())) { + return 'n/a'; + } + const options: Intl.DateTimeFormatOptions = { year: 'numeric', month: 'short', diff --git a/src/lib/helpers/style.ts b/src/lib/helpers/style.ts new file mode 100644 index 000000000..fef15c8cc --- /dev/null +++ b/src/lib/helpers/style.ts @@ -0,0 +1,16 @@ +type Direction = 'rtl' | 'ltr'; + +function isDirection(dir: string): dir is Direction { + return dir === 'rtl' || dir === 'ltr'; +} + +function parseDirection(dir: string): Direction { + return isDirection(dir) ? dir : 'ltr'; +} + +export function getElementDir(el: HTMLElement): Direction { + if (window.getComputedStyle) { + return parseDirection(window.getComputedStyle(el, null).getPropertyValue('direction')); + } + return parseDirection(el.style.direction); +} diff --git a/src/lib/helpers/waitUntil.ts b/src/lib/helpers/waitUntil.ts new file mode 100644 index 000000000..c3b3a85e4 --- /dev/null +++ b/src/lib/helpers/waitUntil.ts @@ -0,0 +1,14 @@ +export async function waitUntil(condition: () => boolean, timeout = 1000) { + return new Promise((resolve, reject) => { + const start = Date.now(); + const interval = setInterval(() => { + if (condition()) { + clearInterval(interval); + resolve(undefined); + } else if (Date.now() - start > timeout) { + clearInterval(interval); + reject(new Error('Timeout')); + } + }, 10); + }); +} diff --git a/src/lib/images/feedback/feedback-dark.svg b/src/lib/images/feedback/feedback-dark.svg new file mode 100644 index 000000000..8f0b553d6 --- /dev/null +++ b/src/lib/images/feedback/feedback-dark.svg @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/images/feedback/feedback-light.svg b/src/lib/images/feedback/feedback-light.svg new file mode 100644 index 000000000..d7ace402f --- /dev/null +++ b/src/lib/images/feedback/feedback-light.svg @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/lib/layout/header.svelte b/src/lib/layout/header.svelte index f5d98c4c1..4ca351ba5 100644 --- a/src/lib/layout/header.svelte +++ b/src/lib/layout/header.svelte @@ -1,27 +1,22 @@ @@ -122,6 +136,24 @@ Storage
  • +
  • + trackEvent('click_menu_settings')} + class:is-selected={$page.url.pathname.startsWith( + `${projectPath}/settings` + )} + title="Settings" + use:tooltip={{ + content: 'Settings', + placement: 'right', + disabled: !narrow + }}> + +
  • @@ -129,7 +161,7 @@ {/if} diff --git a/src/lib/stores/app.ts b/src/lib/stores/app.ts index 2ca46f625..644e4cb1c 100644 --- a/src/lib/stores/app.ts +++ b/src/lib/stores/app.ts @@ -1,20 +1,13 @@ import { browser } from '$app/environment'; import { VARS } from '$lib/system'; import { derived, writable } from 'svelte/store'; +import type { Feedback } from './feedback'; export type AppStore = { theme: 'light' | 'dark' | 'auto'; themeInUse: 'light' | 'dark'; }; -export type Feedback = { - elapsed: number; - visualized: number; - notification: boolean; - type: 'nps' | 'general'; - show: boolean; -}; - export const app = writable({ theme: 'auto', themeInUse: 'light' diff --git a/src/lib/stores/feedback.ts b/src/lib/stores/feedback.ts new file mode 100644 index 000000000..d63e1d2ec --- /dev/null +++ b/src/lib/stores/feedback.ts @@ -0,0 +1,139 @@ +import { browser } from '$app/environment'; +import { VARS } from '$lib/system'; +import { writable } from 'svelte/store'; +import type { SvelteComponent } from 'svelte'; +import FeedbackGeneral from '$lib/components/feedback/feedbackGeneral.svelte'; +import FeedbackNps from '$lib/components/feedback/feedbackNPS.svelte'; + +export type Feedback = { + elapsed: number; + visualized: number; + notification: boolean; + type: 'nps' | 'general'; + show: boolean; +}; + +export type FeedbackData = { + message: string; + name?: string; + email?: string; + value?: number; +}; + +export type FeedbackOption = { + type: Feedback['type']; + title: string; + desc: string; + component: typeof SvelteComponent; +}; + +export const feedbackOptions: FeedbackOption[] = [ + { + type: 'general', + title: 'How can we improve?', + desc: 'Your feedback is important to us. Please be honest and tell us what you think.', + component: FeedbackGeneral + }, + { + type: 'nps', + title: 'How are we doing?', + desc: "At Appwrite, we value the feedback of our users. That means you! We'd love to hear what you think.", + component: FeedbackNps + } +]; + +export const selectedFeedback = writable(); + +function createFeedbackDataStore() { + const { subscribe, update } = writable({ + message: '', + name: '', + email: '', + value: 0 + }); + return { + subscribe, + update, + reset: () => { + update((feedbackData) => { + feedbackData.message = ''; + feedbackData.name = ''; + feedbackData.email = ''; + feedbackData.value = 0; + return feedbackData; + }); + } + }; +} + +export const feedbackData = createFeedbackDataStore(); + +function createFeedbackStore() { + const { subscribe, update } = writable({ + elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) : 0, + visualized: browser ? parseInt(localStorage.getItem('feedbackVisualized')) : 0, + notification: false, + type: 'general', + show: false + }); + return { + subscribe, + update, + toggleFeedback: () => { + update((feedback) => { + feedback.show = !feedback.show; + return feedback; + }); + }, + toggleNotification: () => + update((feedback) => { + feedback.notification = !feedback.notification; + return feedback; + }), + switchType: (feedType: Feedback['type']) => + update((feedback) => { + feedback.type = feedType; + return feedback; + }), + addVisualization: () => + update((feedback) => { + feedback.visualized += 1; + localStorage.setItem('feedbackVisualized', feedback.visualized.toString()); + return feedback; + }), + + increaseElapsed: (time: number) => { + update((feedback) => { + feedback.elapsed += time; + localStorage.setItem('feedbackElapsed', feedback.elapsed.toString()); + return feedback; + }); + }, + submitFeedback: async ( + subject: string, + message: string, + firstname?: string, + email?: string, + value?: number + ) => { + if (!VARS.GROWTH_ENDPOINT) return; + const response = await fetch(`${VARS.GROWTH_ENDPOINT}/feedback`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + subject, + message, + email, + firstname: firstname ? firstname : undefined, + customFields: value ? [{ id: '40655', value }] : undefined + }) + }); + if (response.status >= 400) { + throw new Error('Failed to submit feedback'); + } + } + }; +} +export const feedback = createFeedbackStore(); diff --git a/src/lib/stores/migration.ts b/src/lib/stores/migration.ts index 0eaf52592..953f7b695 100644 --- a/src/lib/stores/migration.ts +++ b/src/lib/stores/migration.ts @@ -177,6 +177,7 @@ type AppwriteInput = { type FirebaseInput = { provider: 'firebase'; serviceAccount?: string; + projectId?: string; }; type SupabaseInput = { diff --git a/src/lib/stores/oauth-providers.ts b/src/lib/stores/oauth-providers.ts index 8047d6bdb..c43a597df 100644 --- a/src/lib/stores/oauth-providers.ts +++ b/src/lib/stores/oauth-providers.ts @@ -1,15 +1,18 @@ -import { writable } from 'svelte/store'; import type { Models } from '@appwrite.io/console'; import type { SvelteComponent } from 'svelte'; +import { writable } from 'svelte/store'; import Apple from '../../routes/console/project-[project]/auth/appleOAuth.svelte'; -import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte'; -import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte'; import Auth0 from '../../routes/console/project-[project]/auth/auth0OAuth.svelte'; import Authentik from '../../routes/console/project-[project]/auth/authentikOAuth.svelte'; import GitLab from '../../routes/console/project-[project]/auth/gitlabOAuth.svelte'; +import Google from '../../routes/console/project-[project]/auth/googleOAuth.svelte'; import Main from '../../routes/console/project-[project]/auth/mainOAuth.svelte'; +import Microsoft from '../../routes/console/project-[project]/auth/microsoftOAuth.svelte'; +import Oidc from '../../routes/console/project-[project]/auth/oidcOAuth.svelte'; +import Okta from '../../routes/console/project-[project]/auth/oktaOAuth.svelte'; export type Provider = Models.Provider & { + key: string; icon: string; docs?: string; component?: typeof SvelteComponent; @@ -22,11 +25,12 @@ export type Providers = { const setProviders = (project: Models.Project): Provider[] => { return ( project?.providers.map((n) => { + const p = n as Models.Provider & { key: string }; let docs: Provider['docs']; - let icon: Provider['icon'] = n.name.toLowerCase(); + let icon: Provider['icon'] = p.key.toLowerCase(); let component: Provider['component'] = Main; - switch (n.name.toLowerCase()) { + switch (p.key.toLowerCase()) { case 'amazon': docs = 'https://developer.amazon.com/apps-and-games/services-and-apis'; break; @@ -81,6 +85,7 @@ const setProviders = (project: Models.Project): Provider[] => { break; case 'google': docs = 'https://support.google.com/googleapi/answer/6158849'; + component = Google; break; case 'linkedin': docs = 'https://developer.linkedin.com/'; @@ -92,6 +97,10 @@ const setProviders = (project: Models.Project): Provider[] => { case 'notion': docs = 'https://developers.notion.com/docs'; break; + case 'oidc': + docs = 'https://openid.net/connect/faq/'; + component = Oidc; + break; case 'okta': docs = 'https://developer.okta.com'; component = Okta; @@ -149,7 +158,7 @@ const setProviders = (project: Models.Project): Provider[] => { } return { - ...n, + ...p, icon, docs, component diff --git a/src/lib/stores/wizard.ts b/src/lib/stores/wizard.ts index de58eb407..fee71cfcf 100644 --- a/src/lib/stores/wizard.ts +++ b/src/lib/stores/wizard.ts @@ -1,11 +1,11 @@ import { trackEvent } from '$lib/actions/analytics'; -import type { SvelteComponent } from 'svelte'; +import type { ComponentType } from 'svelte'; import { writable } from 'svelte/store'; export type WizardStore = { show: boolean; media?: string; - component?: typeof SvelteComponent; + component?: ComponentType; interceptor?: () => Promise; nextDisabled: boolean; }; @@ -22,10 +22,11 @@ function createWizardStore() { return { subscribe, set, - start: (component: typeof SvelteComponent, media: string = null) => + start: (component: ComponentType, media: string = null) => update((n) => { n.show = true; n.component = component; + n.interceptor = null; n.media = media; trackEvent('wizard_start'); return n; @@ -46,6 +47,7 @@ function createWizardStore() { update((n) => { n.show = false; n.component = null; + n.interceptor = null; n.media = null; return n; diff --git a/src/routes/auth/oauth2/failure/+page.svelte b/src/routes/auth/oauth2/failure/+page.svelte index 359c6ffdc..928ad82c4 100644 --- a/src/routes/auth/oauth2/failure/+page.svelte +++ b/src/routes/auth/oauth2/failure/+page.svelte @@ -2,20 +2,56 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { Heading } from '$lib/components'; - import { onMount } from 'svelte'; - onMount(async () => { - const project = $page.url.searchParams.get('project'); - if (project) { - await goto(`appwrite-callback-${project}://${$page.url.search}`); + const project = $page.url.searchParams.get('project'); + const link = `appwrite-callback-${project}://${$page.url.search}`; + + const redirect = new Promise((resolve, reject) => { + if (!project) { + reject('no-project'); } + // this timeout is needed because goto does not + // throw an exception if the redirect does not work + setTimeout(() => reject('timeout'), 500); + // goto will resolve on successful redirect + goto(link).then(resolve); }); -Missing Redirect URL -

    - Your OAuth login flow is missing a proper redirect URL. Please check the - OAuth docs - and send request for new session with a valid callback URL. -

    +{#await redirect then} +
    +
    + Login failed +

    You will be automatically redirected back to your app shortly.

    +

    + If you are not redirected, please click on the following + link. +

    +
    +
    +{:catch} +
    +
    + Missing Redirect URL +

    + Your OAuth login flow is missing a proper redirect URL. Please check the + OAuth docs + and send request for new session with a valid callback URL. +

    +
    +
    +{/await} + + diff --git a/src/routes/auth/oauth2/success/+page.svelte b/src/routes/auth/oauth2/success/+page.svelte index 359c6ffdc..b67ad0ce6 100644 --- a/src/routes/auth/oauth2/success/+page.svelte +++ b/src/routes/auth/oauth2/success/+page.svelte @@ -2,20 +2,56 @@ import { goto } from '$app/navigation'; import { page } from '$app/stores'; import { Heading } from '$lib/components'; - import { onMount } from 'svelte'; - onMount(async () => { - const project = $page.url.searchParams.get('project'); - if (project) { - await goto(`appwrite-callback-${project}://${$page.url.search}`); + const project = $page.url.searchParams.get('project'); + const link = `appwrite-callback-${project}://${$page.url.search}`; + + const redirect = new Promise((resolve, reject) => { + if (!project) { + reject('no-project'); } + // this timeout is needed because goto does not + // throw an exception if the redirect does not work + setTimeout(() => reject('timeout'), 500); + // goto will resolve on successful redirect + goto(link).then(resolve); }); -Missing Redirect URL -

    - Your OAuth login flow is missing a proper redirect URL. Please check the - OAuth docs - and send request for new session with a valid callback URL. -

    +{#await redirect then} +
    +
    + You're now logged in +

    You will be automatically redirected back to your app shortly.

    +

    + If you are not redirected, please click on the following + link. +

    +
    +
    +{:catch} +
    +
    + Missing Redirect URL +

    + Your OAuth login flow is missing a proper redirect URL. Please check the + OAuth docs + and send request for new session with a valid callback URL. +

    +
    +
    +{/await} + + diff --git a/src/routes/console/(migration-wizard)/index.ts b/src/routes/console/(migration-wizard)/index.ts index a9b23e663..da09c5181 100644 --- a/src/routes/console/(migration-wizard)/index.ts +++ b/src/routes/console/(migration-wizard)/index.ts @@ -7,8 +7,7 @@ import Wizard from './wizard.svelte'; export const formData = createMigrationFormStore(); export function openMigrationWizard() { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - wizard.start(Wizard as any); + wizard.start(Wizard); const migData = get(requestedMigration); provider.set({ provider: 'appwrite', diff --git a/src/routes/console/(migration-wizard)/resource-form.svelte b/src/routes/console/(migration-wizard)/resource-form.svelte index 5a0a730dc..07c7c5ec9 100644 --- a/src/routes/console/(migration-wizard)/resource-form.svelte +++ b/src/routes/console/(migration-wizard)/resource-form.svelte @@ -98,11 +98,17 @@ break; } case 'firebase': { - const res = await sdk.forProject.migrations.getFirebaseReport( - providerResources.firebase, - $provider.serviceAccount - ); - report = res; + if ($provider.projectId) { + // OAuth + } else if ($provider.serviceAccount) { + // Manual auth + const res = await sdk.forProject.migrations.getFirebaseReport( + providerResources.firebase, + $provider.serviceAccount + ); + report = res; + } + break; } case 'nhost': { @@ -131,7 +137,6 @@ }; }); - $: console.log(report); $: resources = providerResources[$provider.provider]; // $: wizard.setNextDisabled(!report); @@ -158,15 +163,30 @@

    Make sure to have enough storage in your project plan when importing files

    -
    -
    - + {#if $provider.provider === 'firebase'} +
    +
    + +
    +
    +

    Possible charges by Firebase

    +

    + Appwrite does not impose charges for importing data, but please note that + Firebase may have its own pricing for this service +

    +
    -
    -

    Transfer is free of charge

    -

    You won't be charged for Appwrite bandwidth usage for importing data

    + {:else} +
    +
    + +
    +
    +

    Transfer is free of charge

    +

    You won't be charged for Appwrite bandwidth usage for importing data

    +
    -
    + {/if}
    diff --git a/src/routes/console/(migration-wizard)/wizard.svelte b/src/routes/console/(migration-wizard)/wizard.svelte index 0dbc81147..397fae0e5 100644 --- a/src/routes/console/(migration-wizard)/wizard.svelte +++ b/src/routes/console/(migration-wizard)/wizard.svelte @@ -46,4 +46,4 @@ }); - + diff --git a/src/routes/console/+layout.svelte b/src/routes/console/+layout.svelte index 38a720306..db2ffad3b 100644 --- a/src/routes/console/+layout.svelte +++ b/src/routes/console/+layout.svelte @@ -21,8 +21,8 @@ import { AIPanel, OrganizationsPanel, ProjectsPanel } from '$lib/commandCenter/panels'; import { orgSearcher, projectsSearcher } from '$lib/commandCenter/searchers'; import { addSubPanel } from '$lib/commandCenter/subPanels'; - import { openMigrationWizard } from './(migration-wizard)'; import { addNotification } from '$lib/stores/notifications'; + import { openMigrationWizard } from './(migration-wizard)'; $: $registerCommands([ { @@ -34,7 +34,7 @@ icon: 'light-bulb' }, { - label: 'Go to Account', + label: 'Go to account', callback: () => { goto('/console/account'); }, @@ -42,7 +42,7 @@ group: 'navigation' }, { - label: 'Find an Organization', + label: 'Find an organization', callback: () => { addSubPanel(OrganizationsPanel); }, @@ -60,7 +60,7 @@ icon: 'search' }, { - label: 'Create new Organization', + label: 'Create new organization', callback: () => { newOrgModal.set(true); }, @@ -68,7 +68,7 @@ group: 'organizations' }, { - label: 'Go to Home', + label: 'Go to home', callback: () => { goto('/console'); }, @@ -127,6 +127,7 @@ icon: 'switch-horizontal' } ]); + let isOpen = false; onMount(() => { loading.set(false); @@ -168,12 +169,13 @@
    - +