diff --git a/client/src/app/layout.tsx b/client/src/app/layout.tsx
index e39dc88e..c261c203 100644
--- a/client/src/app/layout.tsx
+++ b/client/src/app/layout.tsx
@@ -9,6 +9,7 @@ import { NuqsAdapter } from "nuqs/adapters/next/app";
import { config } from "@/app/auth/api/[...nextauth]/config";
+import IntroModal from "@/containers/intro-modal";
import MainNav from "@/containers/nav";
import { SidebarProvider } from "@/components/ui/sidebar";
@@ -57,6 +58,7 @@ export default async function RootLayout({
+
{children}
diff --git a/client/src/containers/intro-modal/index.tsx b/client/src/containers/intro-modal/index.tsx
new file mode 100644
index 00000000..beb920e9
--- /dev/null
+++ b/client/src/containers/intro-modal/index.tsx
@@ -0,0 +1,112 @@
+"use client";
+import { useCallback, useState } from "react";
+
+import {
+ ClipboardPenIcon,
+ FileQuestionIcon,
+ LayoutDashboardIcon,
+ UserIcon,
+} from "lucide-react";
+
+import { introModalManager } from "@/lib/utils";
+
+import { Button } from "@/components/ui/button";
+import { CheckboxWrapper } from "@/components/ui/checkbox";
+import {
+ Dialog,
+ DialogContent,
+ DialogDescription,
+ DialogFooter,
+ DialogHeader,
+ DialogTitle,
+} from "@/components/ui/dialog";
+
+const introItems = [
+ {
+ title: "Project Overview",
+ description:
+ "Explore and compare project scenarios with ease. Apply filters for location, ecosystem type, activity, cost, abatement potential, and more. The global map visualizes project distributions, enabling comparisons based on cost-to-abatement ratios. Additionally, use the comparison table for detailed cost and score analyses, and select a project for additional details.",
+ icon: LayoutDashboardIcon,
+ },
+ {
+ title: "Create custom projects",
+ description:
+ "Design custom scenarios by modifying original parameters and exploring the resulting data in detail. Save your projects to revisit them anytime and quickly compare all the scenarios you’ve created.",
+ icon: ClipboardPenIcon,
+ },
+ {
+ title: "Methodology",
+ description:
+ "Explore for detailed information on assumptions, estimations, and data sources.",
+ icon: FileQuestionIcon,
+ },
+ {
+ title: "User area",
+ description: "Access information about your personal account.",
+ icon: UserIcon,
+ },
+];
+
+const IntroModal = () => {
+ const [isOpen, setIsOpen] = useState(introModalManager.showIntroModal());
+ const [dontShowAgain, setDontShowAgain] = useState(false);
+ const handleClose = useCallback(
+ (open = false) => {
+ setIsOpen(open);
+
+ if (dontShowAgain) {
+ introModalManager.setHideIntroModal();
+ }
+ },
+ [dontShowAgain],
+ );
+
+ return (
+
+ );
+};
+
+export default IntroModal;
diff --git a/client/src/lib/utils.ts b/client/src/lib/utils.ts
index 0ea627fb..fd3436fd 100644
--- a/client/src/lib/utils.ts
+++ b/client/src/lib/utils.ts
@@ -43,3 +43,27 @@ export const parseTableData = <
value: formatCurrency(data[key as K], { maximumFractionDigits: 0 }),
}));
};
+class IntroModalManager {
+ private static instance: IntroModalManager;
+ private isServer: boolean = typeof window === "undefined";
+
+ public static getInstance(): IntroModalManager {
+ if (!IntroModalManager.instance) {
+ IntroModalManager.instance = new IntroModalManager();
+ }
+ return IntroModalManager.instance;
+ }
+
+ public showIntroModal(): boolean {
+ if (this.isServer) return false;
+ return (
+ !!process.env.HIDE_INTRO_MODAL || !localStorage.getItem("hideIntroModal")
+ );
+ }
+
+ public setHideIntroModal(): void {
+ if (this.isServer) return;
+ localStorage.setItem("hideIntroModal", "true");
+ }
+}
+export const introModalManager = IntroModalManager.getInstance();
diff --git a/e2e/playwright.config.ts b/e2e/playwright.config.ts
index e918884a..ae3f6d44 100644
--- a/e2e/playwright.config.ts
+++ b/e2e/playwright.config.ts
@@ -49,6 +49,22 @@ export default defineConfig({
baseURL: APP_URL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: "on-first-retry",
+ storageState: {
+ cookies: [],
+ origins: [
+ {
+ origin: APP_URL,
+ localStorage: [
+ // Prevent intro modal from always showing during tests
+ // TODO: Write a separate test to check if the intro modal is shown
+ {
+ name: "hideIntroModal",
+ value: "true",
+ },
+ ],
+ },
+ ],
+ },
},
/* Configure projects for major browsers */
projects: [