mirror of
https://github.com/appwrite/console.git
synced 2026-06-06 19:27:48 +00:00
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:
Binary file not shown.
|
After Width: | Height: | Size: 172 KiB |
@@ -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';
|
||||
|
||||
+7
-7
@@ -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>
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
}
|
||||
];
|
||||
|
||||
@@ -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';
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user