Skip to content

Conversation

@jobenjada
Copy link
Member

No description provided.

@jobenjada jobenjada requested a review from mattinannt December 2, 2025 17:50
@sonarqubecloud
Copy link

sonarqubecloud bot commented Dec 2, 2025

@coderabbitai
Copy link

coderabbitai bot commented Dec 2, 2025

Walkthrough

Introduces a new client-side React page component at apps/playground/app/dashboard/page.tsx. The component initializes the Formbricks SDK using environment variables NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID and NEXT_PUBLIC_FORMBRICKS_API_HOST. It implements user identification functionality through a Sign In card, a dashboard modal that displays analytics information including stat cards and a chart placeholder, and an upgrade action with event tracking. The component includes handlers for opening and closing the dashboard modal, tracking dashboard-viewed events on modal closure, and managing initialization state to display a Connected badge.

Pre-merge checks

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive No pull request description was provided by the author, so the check cannot evaluate whether a description relates to the changeset. Add a pull request description that explains the purpose, changes, and testing performed for this new dashboard component.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add dashboard for demo resons' is related to the changeset, which adds a new dashboard page component, but contains a typo ('resons' instead of 'reasons') and is slightly vague.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

📜 Review details

Configuration used: CodeRabbit UI

Review profile: ASSERTIVE

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 44936b0 and b7b6db4.

📒 Files selected for processing (1)
  • apps/playground/app/dashboard/page.tsx (1 hunks)
🧰 Additional context used
🪛 Biome (2.1.2)
apps/playground/app/dashboard/page.tsx

[error] 97-102: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 130-135: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 162-167: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 208-213: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 270-276: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

Comment on lines +11 to +33
useEffect(() => {
const initFormbricks = () => {
const addFormbricksDebugParam = (): void => {
const url = new URL(globalThis.location.href);
if (!url.searchParams.has("formbricksDebug")) {
url.searchParams.set("formbricksDebug", "true");
globalThis.history.replaceState({}, "", url.href);
}
};
addFormbricksDebugParam();
if (
process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID &&
process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST
) {
formbricks.setup({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
appUrl: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
});
setIsInitialized(true);
}
};
initFormbricks();
}, []);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add error handling for SDK initialization.

The formbricks.setup() call lacks error handling. If initialization fails (network issues, invalid config), it will throw an uncaught error and leave isInitialized as false without user feedback.

Apply this diff to add error handling:

   useEffect(() => {
     const initFormbricks = () => {
       const addFormbricksDebugParam = (): void => {
         const url = new URL(globalThis.location.href);
         if (!url.searchParams.has("formbricksDebug")) {
           url.searchParams.set("formbricksDebug", "true");
           globalThis.history.replaceState({}, "", url.href);
         }
       };
       addFormbricksDebugParam();
       if (
         process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID &&
         process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST
       ) {
-        formbricks.setup({
-          environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
-          appUrl: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
-        });
-        setIsInitialized(true);
+        try {
+          formbricks.setup({
+            environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
+            appUrl: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
+          });
+          setIsInitialized(true);
+        } catch (error) {
+          console.error("Failed to initialize Formbricks:", error);
+        }
       }
     };
     initFormbricks();
   }, []);
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
useEffect(() => {
const initFormbricks = () => {
const addFormbricksDebugParam = (): void => {
const url = new URL(globalThis.location.href);
if (!url.searchParams.has("formbricksDebug")) {
url.searchParams.set("formbricksDebug", "true");
globalThis.history.replaceState({}, "", url.href);
}
};
addFormbricksDebugParam();
if (
process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID &&
process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST
) {
formbricks.setup({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
appUrl: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
});
setIsInitialized(true);
}
};
initFormbricks();
}, []);
useEffect(() => {
const initFormbricks = () => {
const addFormbricksDebugParam = (): void => {
const url = new URL(globalThis.location.href);
if (!url.searchParams.has("formbricksDebug")) {
url.searchParams.set("formbricksDebug", "true");
globalThis.history.replaceState({}, "", url.href);
}
};
addFormbricksDebugParam();
if (
process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID &&
process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST
) {
try {
formbricks.setup({
environmentId: process.env.NEXT_PUBLIC_FORMBRICKS_ENVIRONMENT_ID,
appUrl: process.env.NEXT_PUBLIC_FORMBRICKS_API_HOST,
});
setIsInitialized(true);
} catch (error) {
console.error("Failed to initialize Formbricks:", error);
}
}
};
initFormbricks();
}, []);
🤖 Prompt for AI Agents
In apps/playground/app/dashboard/page.tsx around lines 11 to 33, wrap the
formbricks.setup(...) call in a try/catch so initialization failures are caught;
on success call setIsInitialized(true), on error catch the exception, log the
error (console.error or app logger) with contextual message, and optionally set
a failure state or leave isInitialized false to drive UI feedback; ensure the
environment checks remain the same and only attempt setup inside the try block
so no uncaught exceptions escape the effect.

Comment on lines +35 to +52
const handleIdentifyUser = () => {
formbricks.setUserId(userId);
formbricks.setAttribute("userType", "Demo-customer");
formbricks.setAttribute("plan", "enterprise");
};

const handleOpenDashboard = () => {
setIsDashboardOpen(true);
};

const handleCloseDashboard = () => {
setIsDashboardOpen(false);
formbricks.track("dashboard-viewed");
};

const handleUpgradeAction = () => {
formbricks.track("upgrade-clicked");
};
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard Formbricks API calls with initialization check.

All handlers invoke Formbricks methods (setUserId, setAttribute, track) without verifying isInitialized. If the SDK fails to initialize or the user interacts before initialization completes, these calls will throw runtime errors.

Apply this diff to guard the API calls:

   const handleIdentifyUser = () => {
+    if (!isInitialized) {
+      console.warn("Formbricks not initialized");
+      return;
+    }
     formbricks.setUserId(userId);
     formbricks.setAttribute("userType", "Demo-customer");
     formbricks.setAttribute("plan", "enterprise");
   };

   const handleOpenDashboard = () => {
     setIsDashboardOpen(true);
   };

   const handleCloseDashboard = () => {
     setIsDashboardOpen(false);
+    if (!isInitialized) return;
     formbricks.track("dashboard-viewed");
   };

   const handleUpgradeAction = () => {
+    if (!isInitialized) {
+      console.warn("Formbricks not initialized");
+      return;
+    }
     formbricks.track("upgrade-clicked");
   };

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/playground/app/dashboard/page.tsx around lines 35 to 52, the Formbricks
API calls (setUserId, setAttribute, track) are invoked without checking SDK
initialization; update each handler to first check isInitialized (and that
required values like userId exist) and only call formbricks methods when true,
otherwise no-op (or early return); specifically wrap handleIdentifyUser,
handleOpenDashboard, handleCloseDashboard, and handleUpgradeAction so they guard
formbricks calls with if (!isInitialized) return (or if (isInitialized) { ... })
to prevent runtime errors when the SDK hasn’t finished initializing.

Comment on lines +97 to +102
<svg
className="h-6 w-6 text-blue-600"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Add accessibility attributes to decorative SVGs.

Five SVG elements lack alternative text. Since these icons are decorative (adjacent text provides context), add aria-hidden="true" to hide them from screen readers.

As per static analysis hints.

Apply this diff to the first SVG (repeat pattern for all five):

             <svg
+              aria-hidden="true"
               className="h-6 w-6 text-blue-600"
               fill="none"
               stroke="currentColor"
               viewBox="0 0 24 24"
             >

Alternatively, if these icons are meant to convey meaning, add a <title> element inside each SVG:

             <svg
               className="h-6 w-6 text-blue-600"
               fill="none"
               stroke="currentColor"
               viewBox="0 0 24 24"
             >
+              <title>User icon</title>
               <path
                 strokeLinecap="round"
                 strokeLinejoin="round"
                 strokeWidth={2}
                 d="M16 7a4 4 0 11-8 0 4 4 0 018 0zM12 14a7 7 0 00-7 7h14a7 7 0 00-7-7z"
               />
             </svg>

Apply similar changes to lines 130-135, 162-167, 208-213, and 270-276.

Also applies to: 130-135, 162-167, 208-213, 270-276

🧰 Tools
🪛 Biome (2.1.2)

[error] 97-102: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🤖 Prompt for AI Agents
In apps/playground/app/dashboard/page.tsx around lines 97-102 (and similarly at
130-135, 162-167, 208-213, 270-276), the SVG icons are missing accessibility
attributes; for decorative icons add aria-hidden="true" to each <svg> element
(or if an icon conveys meaning, include a <title> with a concise description
inside the SVG and ensure it has role="img" and a unique id referenced by
aria-labelledby); apply the aria-hidden change to the first SVG at 97-102 and
repeat the same for the other four ranges unless you determine an icon is
semantic, in which case add the <title> and appropriate aria attributes instead.

Comment on lines +118 to +124
<button
type="button"
onClick={handleIdentifyUser}
className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700"
>
Sign In as User
</button>
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick | 🔵 Trivial

Disable action buttons when SDK is not initialized.

The action buttons remain clickable even when isInitialized is false, which can confuse users if they click before the SDK is ready.

Add disabled attribute tied to initialization state:

           <button
             type="button"
             onClick={handleIdentifyUser}
+            disabled={!isInitialized}
-            className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700"
+            className="w-full rounded-lg bg-blue-600 px-4 py-2.5 text-sm font-medium text-white transition-colors hover:bg-blue-700 disabled:cursor-not-allowed disabled:opacity-50"
           >
             Sign In as User
           </button>

Apply similar changes to the other two action buttons (lines 150-156 and 182-188).

Also applies to: 150-156, 182-188

🤖 Prompt for AI Agents
In apps/playground/app/dashboard/page.tsx around lines 118-124 (and similarly
150-156 and 182-188), the action buttons are clickable even when SDK is not
initialized; update each <button> to set the disabled attribute based on the
initialization state (e.g. disabled={!isInitialized}), and adjust classNames if
needed to reflect disabled styling (grayed out and remove hover effects) and
include an accessible indicator (aria-disabled or visually apparent) so users
cannot interact with actions before the SDK is ready.

Comment on lines +194 to +305
{isDashboardOpen && (
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
<div className="relative w-full max-w-4xl rounded-2xl bg-white shadow-2xl">
{/* Modal Header */}
<div className="border-b border-slate-200 px-6 py-4">
<div className="flex items-center justify-between">
<h3 className="text-xl font-semibold text-slate-900">
Analytics Dashboard
</h3>
<button
type="button"
onClick={handleCloseDashboard}
className="rounded-lg p-1 text-slate-400 transition-colors hover:bg-slate-100 hover:text-slate-600"
>
<svg
className="h-6 w-6"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M6 18L18 6M6 6l12 12"
/>
</svg>
</button>
</div>
</div>

{/* Modal Content */}
<div className="p-6">
<div className="grid gap-4 md:grid-cols-3">
{/* Stat Cards */}
<div className="rounded-lg border border-slate-200 bg-gradient-to-br from-blue-50 to-blue-100 p-4">
<div className="text-sm font-medium text-blue-900">
Total Users
</div>
<div className="mt-2 text-3xl font-bold text-blue-600">
12,458
</div>
<div className="mt-1 text-xs text-blue-700">
↑ 12% from last month
</div>
</div>

<div className="rounded-lg border border-slate-200 bg-gradient-to-br from-purple-50 to-purple-100 p-4">
<div className="text-sm font-medium text-purple-900">
Active Sessions
</div>
<div className="mt-2 text-3xl font-bold text-purple-600">
3,247
</div>
<div className="mt-1 text-xs text-purple-700">
↑ 8% from last week
</div>
</div>

<div className="rounded-lg border border-slate-200 bg-gradient-to-br from-emerald-50 to-emerald-100 p-4">
<div className="text-sm font-medium text-emerald-900">
Server Uptime
</div>
<div className="mt-2 text-3xl font-bold text-emerald-600">
99.9%
</div>
<div className="mt-1 text-xs text-emerald-700">
Last 30 days
</div>
</div>
</div>

{/* Chart Placeholder */}
<div className="mt-6 rounded-lg border border-slate-200 bg-slate-50 p-8">
<div className="flex h-48 items-center justify-center">
<div className="text-center">
<svg
className="mx-auto h-16 w-16 text-slate-400"
fill="none"
stroke="currentColor"
viewBox="0 0 24 24"
>
<path
strokeLinecap="round"
strokeLinejoin="round"
strokeWidth={2}
d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"
/>
</svg>
<p className="mt-2 text-sm text-slate-600">
Performance metrics chart
</p>
</div>
</div>
</div>
</div>

{/* Modal Footer */}
<div className="border-t border-slate-200 bg-slate-50 px-6 py-4">
<div className="flex justify-end gap-3">
<button
type="button"
onClick={handleCloseDashboard}
className="rounded-lg bg-[#CF470C] px-6 py-2 text-sm font-medium text-white transition-colors hover:bg-[#CF470C]/80"
>
Close Dashboard
</button>
</div>
</div>
</div>
</div>
)}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Enhance modal accessibility and UX.

The modal implementation is missing several accessibility and UX features:

  1. No focus trap – keyboard users can tab to background content
  2. No Escape key handler – modal should close on Escape
  3. Backdrop click – clicking outside should close the modal
  4. Focus management – focus should move to modal on open and return to trigger on close

Consider using a library like @headlessui/react or react-modal that handles these patterns, or implement manually:

// Add Escape key handler in useEffect
useEffect(() => {
  const handleEscape = (e: KeyboardEvent) => {
    if (e.key === "Escape" && isDashboardOpen) {
      handleCloseDashboard();
    }
  };
  
  if (isDashboardOpen) {
    document.addEventListener("keydown", handleEscape);
    return () => document.removeEventListener("keydown", handleEscape);
  }
}, [isDashboardOpen]);

For backdrop click, update the modal wrapper:

-        <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4">
+        <div 
+          className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50 p-4"
+          onClick={handleCloseDashboard}
+        >
-          <div className="relative w-full max-w-4xl rounded-2xl bg-white shadow-2xl">
+          <div 
+            className="relative w-full max-w-4xl rounded-2xl bg-white shadow-2xl"
+            onClick={(e) => e.stopPropagation()}
+          >
🧰 Tools
🪛 Biome (2.1.2)

[error] 208-213: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)


[error] 270-276: Alternative text title element cannot be empty

For accessibility purposes, SVGs should have an alternative text, provided via title element. If the svg element has role="img", you should add the aria-label or aria-labelledby attribute.

(lint/a11y/noSvgWithoutTitle)

🤖 Prompt for AI Agents
In apps/playground/app/dashboard/page.tsx around lines 194-305, the modal lacks
accessibility and UX behaviors (focus trap, Escape to close, backdrop click, and
focus management); implement either a vetted library (e.g., @headlessui/react or
react-modal) or manually: add role="dialog" and aria-modal="true" with
aria-labelledby, save document.activeElement before opening and restore it on
close, move focus into the modal (first focusable) when opened and trap
tab/shift+tab so focus cannot escape, add a keydown listener to close on Escape
while open, and make the backdrop clickable to call handleCloseDashboard while
stopping propagation on the modal content to prevent immediate close.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants