fix: stuff

This commit is contained in:
Torsten Dittmann
2024-07-16 03:59:03 +02:00
parent 6c7b35620e
commit 6da5eadbaa
66 changed files with 6850 additions and 10095 deletions
+1
View File
@@ -7,3 +7,4 @@ node_modules
.env
.env.*
!.env.example
/playwright-report
+6 -3
View File
@@ -1,5 +1,8 @@
FROM node:20-alpine as build
ENV PNPM_HOME="/pnpm"
ENV PATH="$PNPM_HOME:$PATH"
RUN corepack enable
WORKDIR /app
ADD ./build.js /app/build.js
@@ -7,7 +10,7 @@ ADD ./tsconfig.json /app/tsconfig.json
ADD ./svelte.config.js /app/svelte.config.js
ADD ./vite.config.ts /app/vite.config.ts
ADD ./package.json /app/package.json
ADD ./package-lock.json /app/package-lock.json
ADD ./pnpm-lock.yaml /app/pnpm-lock.yaml
ADD ./src /app/src
ADD ./static /app/static
@@ -21,8 +24,8 @@ ENV VITE_APPWRITE_GROWTH_ENDPOINT=$VITE_APPWRITE_GROWTH_ENDPOINT
ENV VITE_CONSOLE_MODE=$VITE_CONSOLE_MODE
ENV VITE_STRIPE_PUBLIC_KEY=$VITE_STRIPE_PUBLIC_KEY
RUN npm ci
RUN npm run build
RUN --mount=type=cache,id=pnpm,target=/pnpm/store pnpm install --frozen-lockfile
RUN pnpm run build
FROM nginx:1.25-alpine
+9
View File
@@ -7,6 +7,15 @@ services:
VITE_APPWRITE_ENDPOINT: ${VITE_APPWRITE_ENDPOINT}
VITE_APPWRITE_GROWTH_ENDPOINT: ${VITE_APPWRITE_GROWTH_ENDPOINT}
VITE_STRIPE_PUBLIC_KEY: ${VITE_STRIPE_PUBLIC_KEY}
develop:
watch:
- action: rebuild
path: ./
ignore:
- .github
- tests/
- node_modules/
- build/
environment:
- VITE_CONSOLE_MODE
- VITE_APPWRITE_ENDPOINT
-9901
View File
File diff suppressed because it is too large Load Diff
+3 -2
View File
@@ -1,7 +1,7 @@
{
"name": "@appwrite/console",
"engines": {
"node": ">=16"
"node": ">=20"
},
"scripts": {
"dev": "vite dev",
@@ -74,5 +74,6 @@
"vite": "^5.0.11",
"vitest": "^1.2.1"
},
"type": "module"
"type": "module",
"packageManager": "pnpm@9.5.0+sha512.140036830124618d624a2187b50d04289d5a087f326c9edfc0ccd733d76c4f52c3a313d4fc148794a2a9d81553016004e6742e8cf850670268a7387fc220c903"
}
+1 -1
View File
@@ -5,7 +5,7 @@ const config: PlaywrightTestConfig = {
reportSlowTests: null,
reporter: [['html', { open: 'never' }]],
use: {
baseURL: 'http://localhost:4173/console'
baseURL: 'http://localhost:4173/console/'
},
webServer: {
timeout: 120000,
+6636
View File
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -21,7 +21,7 @@ export enum View {
export function getView(url: URL, route: Page['route'], fallback: View): View {
return (url.searchParams.get('view') ?? preferences.get(route).view) === View.Grid
? View.Grid
: View.Table ?? fallback;
: (View.Table ?? fallback);
}
export function getColumns(route: Page['route'], fallback: string[]): string[] {
+1 -1
View File
@@ -16,7 +16,7 @@
export let coupon: Coupon = null;
$: selectedCampaign = campaigns.get(coupon?.campaign ?? campaign);
$: variation = (coupon?.campaign ?? campaign ? selectedCampaign?.template : 'default') as
$: variation = ((coupon?.campaign ?? campaign) ? selectedCampaign?.template : 'default') as
| 'default'
| CampaignData['template'];
+2 -2
View File
@@ -71,8 +71,8 @@ export const feedbackData = createFeedbackDataStore();
function createFeedbackStore() {
const { subscribe, update } = writable<Feedback>({
elapsed: browser ? parseInt(localStorage.getItem('feedbackElapsed')) ?? 0 : 0,
visualized: browser ? parseInt(localStorage.getItem('feedbackVisualized')) ?? 0 : 0,
elapsed: browser ? (parseInt(localStorage.getItem('feedbackElapsed')) ?? 0) : 0,
visualized: browser ? (parseInt(localStorage.getItem('feedbackVisualized')) ?? 0) : 0,
notification: false,
type: 'general',
show: false
+8 -4
View File
@@ -1,8 +1,12 @@
import { page } from '$app/stores';
import { derived } from 'svelte/store';
import type { Models } from '@appwrite.io/console';
import { browser } from '$app/environment';
export const user = derived(
page,
($page) => $page.data.account as Models.User<Record<string, string>>
);
export type Account = Models.User<{ organization?: string } & Record<string, string>>;
export const user = derived(page, ($page) => {
console.log($page.data.account);
if (browser) sessionStorage.setItem('account', JSON.stringify($page.data.account));
return $page.data.account as Account;
});
+11
View File
@@ -0,0 +1,11 @@
import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ parent }) => {
const { account } = await parent();
if (!account) {
redirect(303, base);
}
};
+2 -1
View File
@@ -11,7 +11,8 @@
import { newOrgModal, organization } from '$lib/stores/organization';
import { wizard } from '$lib/stores/wizard';
import { afterUpdate, onMount } from 'svelte';
import { loading, requestedMigration } from '../store';
import { loading } from '$routes/store';
import { requestedMigration } from '../store';
import Create from './createOrganization.svelte';
import {
showUsageRatesModal,
+1 -1
View File
@@ -6,7 +6,7 @@ import { isCloud } from '$lib/system';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ fetch, depends, parent }) => {
await parent(); // ensure user is authenticated before proceeding
await parent();
depends(Dependencies.RUNTIMES);
depends(Dependencies.CONSOLE_VARIABLES);
+2 -4
View File
@@ -8,12 +8,10 @@ export const load: LayoutLoad = async ({ depends }) => {
depends(Dependencies.FACTORS);
depends(Dependencies.IDENTITIES);
const promises = [
const [factors, identities] = await Promise.all([
sdk.forConsole.account.listMfaFactors(),
sdk.forConsole.account.listIdentities()
];
const [factors, identities] = await Promise.all(promises);
]);
return {
factors,
@@ -2,12 +2,17 @@ import { Dependencies } from '$lib/constants';
import { sdk } from '$lib/stores/sdk';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent, depends }) => {
await parent();
export const load: PageLoad = async ({ depends }) => {
depends(Dependencies.PAYMENT_METHODS);
depends(Dependencies.ADDRESS);
const [paymentMethods, addressList] = await Promise.all([
sdk.forConsole.billing.listPaymentMethods(),
sdk.forConsole.billing.listAddresses()
]);
return {
paymentMethods: await sdk.forConsole.billing.listPaymentMethods(),
addressList: await sdk.forConsole.billing.listAddresses()
paymentMethods,
addressList
};
};
@@ -32,16 +32,22 @@ export const load: LayoutLoad = async ({ params, depends }) => {
try {
const prefs = await sdk.forConsole.account.getPrefs();
const newPrefs = { ...prefs, organization: params.organization };
sdk.forConsole.account.updatePrefs(newPrefs);
preferences.loadTeamPrefs(params.organization);
if (prefs.organization !== params.organization) {
const newPrefs = { ...prefs, organization: params.organization };
sdk.forConsole.account.updatePrefs(newPrefs);
}
const [organization, members] = await Promise.all([
sdk.forConsole.teams.get(params.organization) as Promise<Organization>,
sdk.forConsole.teams.listMemberships(params.organization),
preferences.loadTeamPrefs(params.organization)
]);
return {
header: Header,
breadcrumbs: Breadcrumbs,
organization: await (sdk.forConsole.teams.get(
params.organization
) as Promise<Organization>),
members: await sdk.forConsole.teams.listMemberships(params.organization)
organization,
members
};
} catch (e) {
const prefs = await sdk.forConsole.account.getPrefs();
@@ -12,23 +12,24 @@ export const load: PageLoad = async ({ parent, depends }) => {
depends(Dependencies.INVOICES);
depends(Dependencies.ADDRESS);
let billingAddress: Address = null;
const billingAddressId = (organization as Organization)?.billingAddressId;
if (billingAddressId) {
try {
billingAddress = await sdk.forConsole.billing.getOrganizationBillingAddress(
organization.$id,
billingAddressId
);
} catch {
billingAddress = null;
}
}
const billingAddressPromise: Promise<Address> = billingAddressId
? sdk.forConsole.billing
.getOrganizationBillingAddress(organization.$id, billingAddressId)
.catch(() => null)
: null;
const [paymentMethods, addressList, aggregationList, billingAddress] = await Promise.all([
sdk.forConsole.billing.listPaymentMethods(),
sdk.forConsole.billing.listAddresses(),
sdk.forConsole.billing.listAggregation(organization.$id),
billingAddressPromise
]);
return {
paymentMethods: await sdk.forConsole.billing.listPaymentMethods(),
addressList: await sdk.forConsole.billing.listAddresses(),
aggregationList: await sdk.forConsole.billing.listAggregation(organization.$id),
paymentMethods,
addressList,
aggregationList,
billingAddress
};
};
@@ -4,12 +4,16 @@ import { sdk } from '$lib/stores/sdk';
import { Query } from '@appwrite.io/console';
import { isCloud } from '$lib/system';
export const load: PageLoad = async ({ depends, parent }) => {
const { organization } = await parent();
export const load: PageLoad = async ({ depends, params }) => {
depends(Dependencies.ORGANIZATION);
const [projects, invoices] = await Promise.all([
sdk.forConsole.projects.list([Query.equal('teamId', params.organization)]),
isCloud ? sdk.forConsole.billing.listInvoices(params.organization) : undefined
]);
return {
projects: await sdk.forConsole.projects.list([Query.equal('teamId', organization.$id)]),
invoices: isCloud ? await sdk.forConsole.billing.listInvoices(organization.$id) : undefined
projects,
invoices
};
};
@@ -10,6 +10,7 @@ export const load: PageLoad = async ({ url, route }) => {
const search = getSearch(url);
const limit = getLimit(url, route, PAGE_LIMIT);
const offset = pageToOffset(page, limit);
if (typeof url.searchParams.get('create') === 'string') {
showCreateUser.set(true);
}
@@ -9,11 +9,16 @@ export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.USER);
try {
const [user, userFactors] = await Promise.all([
sdk.forProject.users.get(params.user),
sdk.forProject.users.listMfaFactors(params.user)
]);
return {
header: Header,
breadcrumbs: Breadcrumbs,
user: await sdk.forProject.users.get(params.user),
userFactors: await sdk.forProject.users.listMfaFactors(params.user)
user,
userFactors
};
} catch (e) {
error(e.code, e.message);
@@ -10,17 +10,17 @@ import { Query } from '@appwrite.io/console';
export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.COLLECTION);
try {
const [collection, allCollections] = await Promise.all([
sdk.forProject.databases.getCollection(params.database, params.collection),
sdk.forProject.databases.listCollections(params.database, [Query.orderDesc('')])
]);
return {
header: Header,
breadcrumbs: Breadcrumbs,
collection: await sdk.forProject.databases.getCollection(
params.database,
params.collection
),
subNavigation: SubNavigation,
allCollections: await sdk.forProject.databases.listCollections(params.database, [
Query.orderDesc('')
])
collection,
allCollections
};
} catch (e) {
error(e.code, e.message);
@@ -26,6 +26,7 @@
import { parseExpression } from 'cron-parser';
import { onMount } from 'svelte';
import { functionsList } from './store';
import type { Models } from '@appwrite.io/console';
export let data;
@@ -59,6 +60,10 @@
wizard.showCover(Initial);
}
function getNextScheduledExecution(func: Models.Function) {
return toLocaleDateTime(parseExpression(func.schedule, { utc: true }).next().toString());
}
$: $registerCommands([
{
label: 'Create function',
@@ -108,7 +113,7 @@
aria-hidden="true"
use:tooltip={{
content: `Next execution:
${toLocaleDateTime(parseExpression(func.schedule, { utc: true }).next().toString())}`
${getNextScheduledExecution(func)}`
}} />
</li>
{/if}
@@ -11,15 +11,20 @@ export const load: LayoutLoad = async ({ params, depends }) => {
depends(Dependencies.DEPLOYMENTS);
try {
return {
header: Header,
breadcrumbs: Breadcrumbs,
function: await sdk.forProject.functions.get(params.function),
proxyRuleList: await sdk.forProject.proxy.listRules([
const [func, proxyRuleList] = await Promise.all([
sdk.forProject.functions.get(params.function),
sdk.forProject.proxy.listRules([
Query.equal('resourceType', 'function'),
Query.equal('resourceId', params.function),
Query.limit(1)
])
]);
return {
header: Header,
breadcrumbs: Breadcrumbs,
function: func,
proxyRuleList
};
} catch (e) {
error(e.code, e.message);
@@ -9,13 +9,15 @@ export const load: PageLoad = async ({ depends, url }) => {
const limit = PAGE_LIMIT;
const offset = Number(url.searchParams.get('offset') ?? 0);
const [variables, installations] = await Promise.all([
sdk.forProject.projectApi.listVariables(),
sdk.forProject.vcs.listInstallations([Query.limit(limit), Query.offset(offset)])
]);
return {
limit,
offset,
variables: await sdk.forProject.projectApi.listVariables(),
installations: await sdk.forProject.vcs.listInstallations([
Query.limit(limit),
Query.offset(offset)
])
variables,
installations
};
};
+11
View File
@@ -0,0 +1,11 @@
import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
import type { LayoutLoad } from './$types';
export const load: LayoutLoad = async ({ parent }) => {
const { account } = await parent();
if (account) {
redirect(303, base);
}
};

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 6.1 KiB

After

Width:  |  Height:  |  Size: 6.1 KiB

Before

Width:  |  Height:  |  Size: 24 KiB

After

Width:  |  Height:  |  Size: 24 KiB

Before

Width:  |  Height:  |  Size: 8.8 KiB

After

Width:  |  Height:  |  Size: 8.8 KiB

Before

Width:  |  Height:  |  Size: 8.9 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

+16 -67
View File
@@ -8,12 +8,13 @@
import { app } from '$lib/stores/app';
import { isCloud } from '$lib/system';
import { onMount } from 'svelte';
import Loading from './loading.svelte';
import { loading, requestedMigration } from './store';
import { requestedMigration } from './store';
import { parseIfString } from '$lib/helpers/object';
import { sdk } from '$lib/stores/sdk';
import type { Models } from '@appwrite.io/console';
import { campaigns } from '$lib/stores/campaigns';
import { user } from '$lib/stores/user';
import { loading } from '$routes/store';
import Loading from './loading.svelte';
onMount(async () => {
// handle sources
@@ -40,79 +41,27 @@
requestedMigration.set(parseIfString(migrateData) as Record<string, string>);
}
/**
* Handle initial load.
*/
function shouldRedirect(route: string, routes: string[]) {
return !routes.some((n) => route.startsWith(n));
}
const authenticationRoutes = ['/auth', '/git'];
const acceptedUnauthenticatedRoutes = [
'/login',
'/register',
'/recover',
'/invite',
'/card',
'/hackathon',
'/mfa'
];
const acceptedAuthenticatedRoutes = [
'/console',
'/invite',
'/card',
'/hackathon',
'/recover'
];
const pathname = $page.url.pathname;
const user = $page.data.account as Models.User<Record<string, string>>;
if ($page.url.searchParams.has('code')) {
const code = $page.url.searchParams.get('code');
try {
const couponData = await sdk.forConsole.billing.getCoupon(code);
if (couponData?.campaign && campaigns.has(couponData.campaign)) {
if (user) {
goto(`${base}/apply-credit?code=${code}`);
loading.set(false);
return;
}
}
} catch (error) {
// Do nothing
}
}
if ($page.url.searchParams.has('campaign')) {
const campaign = $page.url.searchParams.get('campaign');
if (campaigns.has(campaign)) {
if (user) {
goto(`${base}/apply-credit?campaign=${campaign}`);
const coupon = await sdk.forConsole.billing.getCoupon(code).catch<null>(() => null);
if (coupon?.campaign && campaigns.has(coupon.campaign)) {
if ($user) {
goto(`${base}/apply-credit?code=${code}`);
loading.set(false);
return;
}
}
}
if (shouldRedirect(pathname, authenticationRoutes)) {
if (user?.$id) {
if (shouldRedirect(pathname, acceptedAuthenticatedRoutes)) {
await goto(base, {
replaceState: true
});
}
} else {
if (acceptedUnauthenticatedRoutes.some((n) => pathname.startsWith(n))) {
await goto(`${base}${pathname}${$page.url.search}`);
} else {
await goto(`${base}/login`, {
replaceState: true
});
}
if (user && $page.url.searchParams.has('campaign')) {
const campaign = $page.url.searchParams.get('campaign');
if (campaigns.has(campaign)) {
goto(`${base}/apply-credit?campaign=${campaign}`);
loading.set(false);
return;
}
loading.set(false);
}
loading.set(false);
});
afterNavigate((navigation) => {
+26 -28
View File
@@ -7,49 +7,47 @@ import { Dependencies } from '$lib/constants';
import type { LayoutLoad } from './$types';
import { redirectTo } from './store';
import { base } from '$app/paths';
import type { Account } from '$lib/stores/user';
import type { AppwriteException } from '@appwrite.io/console';
export const ssr = false;
export const load: LayoutLoad = async ({ depends, url }) => {
export const load: LayoutLoad = async ({ depends, url, route }) => {
depends(Dependencies.ACCOUNT);
const [account, error] = (await sdk.forConsole.account
.get()
.then((response) => [response, null])
.catch((error) => [null, error])) as [Account, AppwriteException];
if (url.searchParams.has('forceRedirect')) {
redirectTo.set(url.searchParams.get('forceRedirect') || null);
url.searchParams.delete('forceRedirect');
}
try {
const account = await sdk.forConsole.account.get<{ organization?: string }>();
if (account) {
return {
account,
account: account,
organizations: await sdk.forConsole.teams.list()
};
} catch (error) {
const acceptedRoutes = [
'/login',
'/register',
'/recover',
'/invite',
'/auth/magic-url',
'/auth/oauth2/success',
'/auth/oauth2/failure',
'/card',
'/hackathon',
'/mfa'
];
}
const redirectUrl = url.pathname && url.pathname !== '/' ? `redirect=${url.pathname}` : '';
const path = url.search ? `${url.search}&${redirectUrl}` : `?${redirectUrl}`;
const isPublicRoute = route.id?.startsWith('/(public)');
if (url.pathname && !isPublicRoute) {
url.searchParams.set('redirect', url.pathname);
}
if (error.type === 'user_more_factors_required') {
if (url.pathname === `${base}/mfa`) return {}; // Ensure any previous account/organizations are cleared
redirect(303, `${base}/mfa${path}`);
}
if (error.type === 'user_more_factors_required') {
if (url.pathname === `${base}/mfa`) return; // Ensure any previous account/organizations are cleared
redirect(303, withParams(`${base}/mfa`, url.searchParams));
}
if (!acceptedRoutes.some((n) => url.pathname.startsWith(n))) {
redirect(303, `${base}/login${path}`);
}
return {}; // Ensure any previous account/organizations are cleared
if (!isPublicRoute) {
redirect(303, withParams(`${base}/login`, url.searchParams));
}
};
function withParams(pathname: string, searchParams: URLSearchParams) {
if (searchParams.size > 0) return `${pathname}?${searchParams.toString()}`;
return pathname;
}
+13 -13
View File
@@ -2,19 +2,19 @@ import { redirect } from '@sveltejs/kit';
import { base } from '$app/paths';
import type { PageLoad } from './$types';
export const load: PageLoad = async ({ parent, url }) => {
export const load: PageLoad = async ({ parent, url, untrack }) => {
const { organizations, account } = await parent();
if (!account) {
redirect(302, `${base}/login`);
}
if (organizations.total) {
const teamId = account.prefs.organization ?? organizations.teams[0].$id;
if (!teamId) {
redirect(303, `${base}/account/organizations${url.search ?? ''}`);
} else redirect(303, `${base}/organization-${teamId}${url.search ?? ''}`);
} else {
redirect(303, `${base}/onboarding${url.search ?? ''}`);
}
untrack(() => {
if (organizations.total) {
const teamId = account.prefs.organization ?? organizations.teams[0].$id;
if (!teamId) {
redirect(303, `${base}/account/organizations${url.search}`);
} else {
redirect(303, `${base}/organization-${teamId}${url.search}`);
}
} else {
redirect(303, `${base}/onboarding${url.search}`);
}
});
};
+1 -1
View File
@@ -1,5 +1,5 @@
import { writable } from 'svelte/store';
export const loading = writable(true);
export const requestedMigration = writable<Record<string, string> | null>(null);
export const redirectTo = writable<string | null>(null);
export const loading = writable(true);
+2 -2
View File
@@ -8,12 +8,12 @@ test('upgrade - free tier', async ({ page }) => {
await createFreeProject(page);
await test.step('upgrade project', async () => {
await page.getByRole('link', { name: 'upgrade' }).click();
await page.waitForURL('/console/organization-**/change-plan');
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);
// skip members
await page.getByRole('button', { name: 'change plan' }).click();
await page.waitForURL('/console/organization-**');
await page.waitForURL('./organization-**');
});
});
+2 -2
View File
@@ -9,7 +9,7 @@ type Metadata = {
export function registerUserStep(page: Page): Promise<Metadata> {
return test.step('register user', async () => {
const seed = crypto.randomUUID();
await page.goto('/register');
await page.goto('./register');
// await page.getByRole('button', { name: 'only required' }).click();
const inputs = {
name: page.locator('id=name'),
@@ -27,7 +27,7 @@ export function registerUserStep(page: Page): Promise<Metadata> {
await inputs.password.fill(values.password);
await inputs.terms.check();
await page.getByRole('button', { name: 'Sign up', exact: true }).click();
await page.waitForURL('/console/onboarding');
await page.waitForURL('./onboarding');
return values;
});
+5 -5
View File
@@ -8,23 +8,23 @@ type Metadata = {
export async function createFreeProject(page: Page): Promise<Metadata> {
const organizationId = await test.step('create organization', async () => {
await page.goto('/console');
await page.waitForURL('/console/onboarding');
await page.goto('./');
await page.waitForURL('./onboarding');
await page.locator('id=name').fill('test org');
await page.locator('id=plan').selectOption('tier-0');
await page.getByRole('button', { name: 'get started' }).click();
await page.waitForURL('/console/organization-**');
await page.waitForURL('./organization-**');
return getOrganizationIdFromUrl(page.url());
});
const projectId = await test.step('create project', async () => {
await page.waitForURL('/console/organization-**');
await page.waitForURL('./organization-**');
await page.getByRole('button', { name: 'create project' }).first().click();
await page.locator('id=name').fill('test project');
await page.getByRole('button', { name: 'next' }).click();
await page.locator('label').filter({ hasText: 'Frankfurt' }).click();
await page.getByRole('button', { name: 'create' }).click();
await page.waitForURL('/console/project-**/overview/platforms');
await page.waitForURL('./project-**/overview/platforms');
expect(page.url()).toContain('/console/project-');
return getProjectIdFromUrl(page.url());
+7 -7
View File
@@ -18,31 +18,31 @@ export async function enterCreditCard(page: Page) {
export async function createProProject(page: Page): Promise<Metadata> {
const organizationId = await test.step('create organization', async () => {
await page.goto('/console');
await page.waitForURL('/console/onboarding');
await page.goto('./');
await page.waitForURL('./onboarding');
await page.locator('id=name').fill('test org');
await page.locator('id=plan').selectOption('tier-1');
await page.getByRole('button', { name: 'get started' }).click();
await page.waitForURL('/console/create-organization**');
await page.waitForURL('./create-organization**');
await new Promise((r) => setTimeout(r, 1000));
await page.getByRole('button', { name: 'add' }).first().click();
await enterCreditCard(page);
// skip members
await page.getByRole('button', { name: 'create organization' }).click();
await page.waitForURL('/console/organization-**');
await page.waitForURL('./organization-**');
return getOrganizationIdFromUrl(page.url());
});
const projectId = await test.step('create project', async () => {
await page.waitForURL('/console/organization-**');
await page.waitForURL('./organization-**');
await page.getByRole('button', { name: 'create project' }).first().click();
await page.getByPlaceholder('project name').fill('test project');
await page.getByRole('button', { name: 'next' }).click();
await page.locator('label').filter({ hasText: 'frankfurt' }).click();
await page.getByRole('button', { name: 'create' }).click();
await page.waitForURL('/console/project-**/overview/platforms');
expect(page.url()).toContain('/console/project-');
await page.waitForURL('./project-**/overview/platforms');
expect(page.url()).toContain('./project-');
return getProjectIdFromUrl(page.url());
});