-
Notifications
You must be signed in to change notification settings - Fork 0
feat: add dashboard for demo resons #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
WalkthroughIntroduces a new client-side React page component at Pre-merge checks❌ Failed checks (1 warning, 1 inconclusive)
✅ Passed checks (1 passed)
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. Comment |
There was a problem hiding this 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
📒 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)
| 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(); | ||
| }, []); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| 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.
| 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"); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <svg | ||
| className="h-6 w-6 text-blue-600" | ||
| fill="none" | ||
| stroke="currentColor" | ||
| viewBox="0 0 24 24" | ||
| > |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
| <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> |
There was a problem hiding this comment.
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.
| {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> | ||
| )} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Enhance modal accessibility and UX.
The modal implementation is missing several accessibility and UX features:
- No focus trap – keyboard users can tab to background content
- No Escape key handler – modal should close on Escape
- Backdrop click – clicking outside should close the modal
- 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.



No description provided.