Refactor unauthenticated flows to profiles

- Move unauthenticated components to profiles/(unauthenticated) - Wire
profile-based unauthenticated across routes - Add login-studio.png asset
- Add studio unauthenticated component and export it - Remove
Unauthenticated export from layout - Use
resolvedProfile.component.unauthenticated in layout - Update MFA routes
to use profile unauthenticated - Update
login/register/recover/invite/error routes - Use resolve('/') for hrefs
and profile logo data - Remove INLINE_START_BASE_OFFSET in studio widget
- Improve layout load to fetch organizations for cloud and non-cloud
This commit is contained in:
Torsten Dittmann
2025-11-18 03:24:46 +04:00
parent 6e4286d4e7
commit a2a311550c
16 changed files with 243 additions and 29 deletions
Binary file not shown.

After

Width:  |  Height:  |  Size: 172 KiB

-1
View File
@@ -9,7 +9,6 @@ export { default as Shell } from './shell.svelte';
export { default as WizardWithSteps } from './wizardWithSteps.svelte';
export { default as WizardStep } from './wizardStep.svelte';
export { default as Breadcrumbs } from './breadcrumbs.svelte';
export { default as Unauthenticated } from './unauthenticated.svelte';
export { default as Usage, type UsagePeriods } from './usage.svelte';
export { default as UsageMultiple } from './usageMultiple.svelte';
export { default as Activity } from './activity.svelte';
@@ -1,5 +1,5 @@
<script lang="ts">
import { base } from '$app/paths';
import { resolve } from '$app/paths';
import { AvatarInitials, Card } from '$lib/components';
import AppwriteLogoDark from '$lib/images/appwrite-logo-dark.svg';
import AppwriteLogoLight from '$lib/images/appwrite-logo-light.svg';
@@ -57,19 +57,19 @@
class="logo u-flex u-gap-16"
class:is-not-mobile={variation === 'default'}
class:logo-variation={variation !== 'default'}>
<a href={base}>
<a href={resolve('/')}>
{#if $app.themeInUse === 'dark'}
<img
src={AppwriteLogoDark}
src={resolvedProfile.logo.src.dark}
width="160"
class="u-block u-only-dark"
alt="{resolvedProfile.platform} Logo" />
alt={resolvedProfile.logo.alt} />
{:else}
<img
src={AppwriteLogoLight}
src={resolvedProfile.logo.src.light}
width="160"
class="u-block u-only-light"
alt="{resolvedProfile.platform} Logo" />
alt={resolvedProfile.logo.alt} />
{/if}
</a>
</div>
@@ -169,7 +169,7 @@
</div>
<div
class="logo u-flex u-gap-16 u-margin-inline-auto is-only-mobile u-margin-block-start-32 mobile-logo">
<a href={base}>
<a href={resolve('/')}>
{#if $app.themeInUse === 'dark'}
<img
src={AppwriteLogoDark}
@@ -0,0 +1,202 @@
<script lang="ts">
import { resolve } from '$app/paths';
import LoginBackground from '$lib/images/login/login-studio.png';
import { app } from '$lib/stores/app';
import { Typography, Card } from '@appwrite.io/pink-svelte';
import { isSmallViewport } from '$lib/stores/viewport';
import { resolvedProfile } from '$lib/profiles/index.svelte';
export const imgLight = LoginBackground;
export const imgDark = LoginBackground;
export let align: 'start' | 'center' | 'end' = 'start';
</script>
<main class="grid-1-1 is-full-page" id="main">
<section
class={`u-flex u-flex-vertical ${!$isSmallViewport ? 'side-default' : ''}`}
style:--url={`url(${$app.themeInUse === 'dark' ? imgDark : imgLight})`}>
</section>
<section class="grid-1-1-col-2 u-flex u-flex-vertical u-cross-center u-main-center">
<slot name="top" />
<div class="container u-flex u-flex-vertical u-cross-center u-main-center u-gap-32">
<Typography.Title size="l" {align}>Welcome to Imagine</Typography.Title>
<div class="auth-container u-width-full-line">
<Card.Base>
<slot />
</Card.Base>
<ul
class="inline-links is-center is-with-sep u-margin-block-start-32"
class:u-hide={!$$slots?.links}>
<slot name="links" />
</ul>
</div>
<div
class="logo u-flex u-gap-16 u-margin-inline-auto is-only-mobile u-margin-block-start-32 mobile-logo">
<a href={resolve('/')}>
{#if $app.themeInUse === 'dark'}
<img
src={resolvedProfile.logo.src.dark}
width="120"
class="u-block u-only-dark"
alt={resolvedProfile.logo.alt} />
{:else}
<img
src={resolvedProfile.logo.src.light}
width="120"
class="u-block u-only-light"
alt={resolvedProfile.logo.alt} />
{/if}
</a>
</div>
</div>
</section>
</main>
<style lang="scss">
@use '@appwrite.io/pink-legacy/src/abstract/variables/devices';
.side-bg {
position: relative;
overflow: hidden;
z-index: 1;
height: 100%;
width: 100%;
padding-block: 2rem;
padding-inline: 1rem;
background-color: #ededf0 !important;
&-container {
position: absolute;
inset: 0;
block-size: 100%;
inline-size: 100%;
z-index: -1;
&::before {
position: absolute;
inset-block-start: -200px;
inset-inline-end: -50px;
content: '';
display: block;
inline-size: 32%;
block-size: 32%;
background: radial-gradient(49.55% 43.54% at 47% 50.69%, #e7f8f7 0%, #85dbd8 100%);
filter: blur(150px);
@media #{devices.$break1} {
filter: blur(100px);
}
}
&::after {
position: absolute;
inset-block-end: -50px;
inset-inline-start: -50px;
content: '';
display: block;
inline-size: 30%;
block-size: 30%;
background: radial-gradient(
50% 46.73% at 50% 53.27%,
#fe9567 28.17%,
#fd366e 59.38%
);
filter: blur(200px);
@media #{devices.$break1} {
filter: blur(100px);
}
}
}
&-img {
padding-inline: 6.25rem;
max-width: 40rem;
@media #{devices.$break2} {
max-width: 28rem;
}
@media #{devices.$break1} {
margin-block-start: 3rem;
max-inline-size: 23rem;
padding-inline: 4rem;
}
}
}
:global(.theme-dark) .side-bg {
background-color: #131315 !important;
}
:global(.theme-dark) .side-bg-container::before {
inline-size: 20%;
block-size: 20%;
filter: blur(180px);
}
:global(.theme-dark) .side-bg-container::after {
filter: blur(250px);
}
/* Default (including mobile) */
.side-default {
padding-block-start: 2.25rem;
padding-block-end: 2rem;
}
/* for smaller screens */
@media #{devices.$break2open} {
.side-default {
background: var(--url);
background-repeat: no-repeat;
background-position: top;
background-size: cover;
padding-block-start: 6.25rem;
padding-block-end: 6.875rem;
}
}
:global(.theme-dark) .tag-line {
background: linear-gradient(45deg, white, white 60%, #fd376f);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
:global(.theme-light) .tag-line {
background: linear-gradient(45deg, #19191d, #19191d 60%, #fd376f);
background-clip: text;
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.auth-container {
max-inline-size: 27.5rem;
}
.review-container {
max-inline-size: 30rem;
}
.review-footer-container {
padding-block-start: 10rem;
@media #{devices.$break1} {
padding-block-start: 5rem;
}
}
// mobile logo positioned at bottom in mobile view
.mobile-logo {
position: absolute;
bottom: 20px;
left: 50%;
transform: translate(-50%, -50%);
max-width: 100%;
margin-block-start: 0 !important;
}
@media #{devices.$break1} {
.auth-container {
margin-block-end: 5rem;
margin-block-start: 2rem;
}
.container {
min-height: 100%;
position: relative;
}
}
</style>
+12
View File
@@ -3,6 +3,9 @@ import type { ResolvedPathname } from '$app/types';
import { Platform } from '@appwrite.io/console';
import { env } from '$env/dynamic/public';
import { BillingPlan } from '@appwrite.io/console';
import type { Component } from 'svelte';
import UnauthenticatedAppwrite from './(unauthenticated)/appwrite.svelte';
import UnauthenticatedStudio from './(unauthenticated)/studio.svelte';
export const enum ProfileMode {
STUDIO = 'studio',
@@ -41,6 +44,9 @@ export type Profile = {
showOrgInBreadcrumbs: boolean;
minimalOrgHeader: boolean;
getProjectRoute: (params: { region: string; project: string }) => ResolvedPathname;
component: {
unauthenticated: Component;
};
};
export const base: Profile = {
@@ -55,6 +61,9 @@ export const base: Profile = {
},
alt: 'Logo Appwrite'
},
component: {
unauthenticated: UnauthenticatedAppwrite
},
showOnboarding: true,
useCommandCenter: true,
showGithubIssueSupport: true,
@@ -94,6 +103,9 @@ export const studio: Profile = {
},
alt: 'Imagine Appwrite'
},
component: {
unauthenticated: UnauthenticatedStudio
},
showOnboarding: false,
useCommandCenter: false,
showGithubIssueSupport: false,
+2 -4
View File
@@ -1,6 +1,4 @@
import { VARS } from '$lib/system';
import { resolvedProfile } from '$lib/profiles/index.svelte';
import type { Component } from 'svelte';
import { browser } from '$app/environment';
import { get, writable } from 'svelte/store';
@@ -31,12 +29,12 @@ export type FeedbackOption = {
export const feedbackOptions: FeedbackOption[] = [
{
type: 'general',
desc: `${resolvedProfile.platform} evolves with your input. Share your thoughts and help us improve ${resolvedProfile.platform}.`,
desc: `Appwrite evolves with your input. Share your thoughts and help us improve Appwrite.`,
component: FeedbackGeneral
},
{
type: 'nps',
desc: `How likely are you to recommend ${resolvedProfile.platform} to a friend or colleague?`,
desc: `How likely are you to recommend Appwrite to a friend or colleague?`,
component: FeedbackNps
}
];
-1
View File
@@ -10,7 +10,6 @@ import { getSessionId } from '$lib/sentry';
const COMPONENT_SELECTOR = 'imagine-web-components-wrapper[data-appwrite-studio]';
const STYLE_ATTRIBUTE = 'data-appwrite-studio-style';
const BLOCK_START_BASE_OFFSET = 48;
const INLINE_START_BASE_OFFSET = 8;
export const CDN_URL = env?.PUBLIC_IMAGINE_CDN_URL + '/web-components.js';
export const CDN_CSS_URL = env?.PUBLIC_IMAGINE_CDN_URL + '/web-components.css';
const DEV_OVERRIDE_WEB_COMPONENTS = env?.PUBLIC_AI_OVERRIDE_WEB_COMPONENTS === 'true';
+1 -2
View File
@@ -3,7 +3,6 @@
import { base } from '$app/paths';
import { Button, Form } from '$lib/elements/forms';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import type { Models } from '@appwrite.io/console';
import MfaChallengeFormList, { verify } from '$lib/components/mfaChallengeFormList.svelte';
import { page } from '$app/state';
@@ -13,7 +12,7 @@
import { resolvedProfile } from '$lib/profiles/index.svelte';
export let data;
const Unauthenticated = resolvedProfile.component.unauthenticated;
const factors = data.factors as Models.MfaFactors & { recoveryCode: boolean };
let disabled = false;
@@ -9,7 +9,7 @@ import { get } from 'svelte/store';
import { headerAlert } from '$lib/stores/headerAlert';
import PaymentFailed from '$lib/components/billing/alerts/paymentFailed.svelte';
import { loadAvailableRegions } from '$routes/(console)/regions';
import type { Organization, OrganizationList } from '$lib/stores/organization';
import type { Organization } from '$lib/stores/organization';
import { redirect } from '@sveltejs/kit';
import { resolvedProfile } from '$lib/profiles/index.svelte';
import { resolve } from '$app/paths';
@@ -46,9 +46,7 @@ export const load: LayoutLoad = async ({ params, route, depends, parent }) => {
project.region ??= 'default';
// fast path without a network call!
let organization = (organizations as OrganizationList)?.teams?.find(
(org) => org.$id === project.teamId
);
let organization = organizations?.teams?.find((org) => org.$id === project.teamId);
const includedInBasePlans = plansInfo.has(organization.billingPlan);
@@ -4,7 +4,6 @@
import { Button, Form, InputEmail, InputPassword } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import { Dependencies } from '$lib/constants';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { isCloud } from '$lib/system';
@@ -18,6 +17,7 @@
let mail: string, pass: string, disabled: boolean;
export let data;
const Unauthenticated = resolvedProfile.component.unauthenticated;
async function login() {
try {
@@ -11,7 +11,6 @@
} from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import { Dependencies } from '$lib/constants';
import { Submit, trackError, trackEvent } from '$lib/actions/analytics';
import { ID, OAuthProvider } from '@appwrite.io/console';
@@ -114,6 +113,8 @@
scopes: ['read:user', 'user:email']
});
}
const Unauthenticated = resolvedProfile.component.unauthenticated;
</script>
<svelte:head>
@@ -11,7 +11,6 @@
} from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import { Dependencies } from '$lib/constants';
import { Submit, trackEvent } from '$lib/actions/analytics';
import { onMount } from 'svelte';
@@ -96,6 +95,7 @@
});
}
}
const Unauthenticated = resolvedProfile.component.unauthenticated;
</script>
<svelte:head>
+1 -1
View File
@@ -4,7 +4,6 @@
import { Button, Form, InputChoice } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import { page } from '$app/state';
import { onMount } from 'svelte';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
@@ -43,6 +42,7 @@
trackError(error, Submit.MembershipUpdateStatus);
}
};
const Unauthenticated = resolvedProfile.component.unauthenticated;
</script>
<svelte:head>
+1 -1
View File
@@ -3,7 +3,6 @@
import { Button, Form, InputEmail, InputPassword } from '$lib/elements/forms';
import { addNotification } from '$lib/stores/notifications';
import { sdk } from '$lib/stores/sdk';
import { Unauthenticated } from '$lib/layout';
import { Submit, trackEvent, trackError } from '$lib/actions/analytics';
import { onMount } from 'svelte';
import { page } from '$app/state';
@@ -65,6 +64,7 @@
});
}
}
const Unauthenticated = resolvedProfile.component.unauthenticated;
</script>
<svelte:head>
+3 -1
View File
@@ -1,8 +1,10 @@
<script>
import { base } from '$app/paths';
import { Unauthenticated } from '$lib/layout';
import { Button } from '$lib/elements/forms';
import { resolvedProfile } from '$lib/profiles/index.svelte';
import { Badge, Typography, Layout } from '@appwrite.io/pink-svelte';
const Unauthenticated = resolvedProfile.component.unauthenticated;
</script>
<Unauthenticated>
+8 -4
View File
@@ -13,6 +13,7 @@ import { isCloud, VARS } from '$lib/system';
import { checkPricingRefAndRedirect } from '$lib/helpers/pricingRedirect';
import { identify } from '$lib/sentry';
import { resolvedProfile } from '$lib/profiles/index.svelte';
import type { OrganizationList } from '$lib/stores/organization';
export const ssr = false;
@@ -41,14 +42,17 @@ export const load: LayoutLoad = async ({ depends, url, route }) => {
redirect(303, resolve('/verify-email'));
}
}
return {
account: account,
organizations: !isCloud
const organizations = (
!isCloud
? await sdk.forConsole.teams.list()
: await sdk.forConsole.billing.listOrganization([
Query.equal('platform', resolvedProfile.organizationPlatform)
])
) as OrganizationList;
return {
account: account,
organizations
};
}