diff --git a/src/lib/components/billing/gradientBanner.svelte b/src/lib/components/billing/gradientBanner.svelte
index 7f9b62a644..228e1f6542 100644
--- a/src/lib/components/billing/gradientBanner.svelte
+++ b/src/lib/components/billing/gradientBanner.svelte
@@ -6,10 +6,18 @@
import { isTabletViewport } from '$lib/stores/viewport';
import PinkBackground from '$lib/images/pink-background.svg';
import { bannerSpacing } from '$lib/layout/headerAlert.svelte';
- import { createEventDispatcher, onMount, onDestroy } from 'svelte';
+ import { createEventDispatcher, getContext, onMount, onDestroy } from 'svelte';
export let variant: 'gradient' | 'image' = 'gradient';
+ /**
+ * An AlertStack parent is itself fixed and measures its children to offset the shell. This
+ * banner is position:fixed, so inside a stack it measures as 0 and the stack resets the
+ * header offset we just set, leaving the banner covering the navigation until the next
+ * resize. Mirror headerAlert.svelte and stand down when the stack is in charge.
+ */
+ const isInStack: boolean = getContext('isInAlertStack') ?? false;
+
let container: HTMLElement;
const dispatch = createEventDispatcher();
@@ -20,6 +28,8 @@
});
const setNavigationHeight = () => {
+ if (isInStack) return;
+
const alertHeight = container?.getBoundingClientRect()?.height || 0;
const { header, sidebar, content } = queryLayoutElements();
const headerHeight = header?.getBoundingClientRect().height || 0;
@@ -51,6 +61,7 @@
{#if variant === 'gradient'}
@@ -92,6 +103,11 @@
position: fixed;
padding: 0.8rem;
+ &.in-stack {
+ position: relative;
+ z-index: unset;
+ }
+
&.darker {
background: var(--bgcolor-neutral-default);
}
diff --git a/src/lib/components/index.ts b/src/lib/components/index.ts
index 09b4d248c3..84a530f097 100644
--- a/src/lib/components/index.ts
+++ b/src/lib/components/index.ts
@@ -76,6 +76,7 @@ export { default as EmptyCardImageCloud } from './emptyCardImageCloud.svelte';
export { default as ImagePreview } from './imagePreview.svelte';
export { default as MfaChallengeFormList } from './mfaChallengeFormList.svelte';
export { default as BottomModalAlert } from './bottomModalAlert.svelte';
+export { default as NewConsoleBanner } from './newConsoleBanner.svelte';
export { default as Navbar } from './navbar.svelte';
export { default as Breadcrumbs } from './breadcrumbs.svelte';
export { default as Sidebar } from './sidebar.svelte';
diff --git a/src/lib/components/newConsoleBanner.svelte b/src/lib/components/newConsoleBanner.svelte
new file mode 100644
index 0000000000..82857f99c3
--- /dev/null
+++ b/src/lib/components/newConsoleBanner.svelte
@@ -0,0 +1,46 @@
+
+
+
+
+
+ Introducing the new Appwrite Console, rebuilt from the ground up.
+
+
+
+
+
diff --git a/src/routes/(console)/+layout.svelte b/src/routes/(console)/+layout.svelte
index b0f4267981..2cd9e62be6 100644
--- a/src/routes/(console)/+layout.svelte
+++ b/src/routes/(console)/+layout.svelte
@@ -44,7 +44,7 @@
import { headerAlert } from '$lib/stores/headerAlert';
import { UsageRates } from '$lib/components/billing';
import { canSeeProjects } from '$lib/stores/roles';
- import { BottomModalAlert } from '$lib/components';
+ import { BottomModalAlert, NewConsoleBanner } from '$lib/components';
import { isSmallViewport } from '$lib/stores/viewport';
import {
IconAnnotation,
@@ -325,6 +325,19 @@
$registerSearchers(orgSearcher, projectsSearcher);
+ onMount(() => {
+ // Same shape as newDevUpgradePro: promo tier (importance 1) so it can never outrank a
+ // payment or usage warning, dismissal keyed on the alert id in localStorage.
+ if (isCloud && !localStorage.getItem('newConsoleBanner')) {
+ headerAlert.add({
+ id: 'newConsoleBanner',
+ component: NewConsoleBanner,
+ show: true,
+ importance: 1
+ });
+ }
+ });
+
$: (void $headerAlert, ($activeHeaderAlert = headerAlert.getExcluding('impersonation')));