Conversation
Remove the old InboxLoadingSkeleton and introduce an animated OnboardingLoader using motion and react-icons. The new loader shows a logo, animated onboarding steps (ONBOARDING_STEPS) with timed progression (STEP_DELAY_MS) and masked scrolling, improving the onboarding feedback. Also update InboxPlayground to add a Skip button and center-align action buttons (add items-center), keeping Send notification / Next Step behavior intact. Small layout and import adjustments (LogoCircle, motion, icons) to support the new UX.
Add Cursor deep-link support and related prompt helpers for framework guides: introduce FRAMEWORK_PACKAGES/FRAMEWORK_DOCS, buildCondensedPrompt, buildCursorDeepLink and extractConfigFromPrompt. Update StepButton UI to include an "Open in Cursor" action (RiCursorLine), preserve copy feedback, and track telemetry for cursor deeplinks. Add skip flow to InboxFrameworkGuide by injecting a footer into InstructionsPanel and wiring navigation/telemetry. Tidy up InstructionsPanel (remove CLI tab, adjust layout/overflow) and update inbox embed page copy to prompt adding the Inbox component.
Add a new cursor SVG asset and use it in the framework guides (replace RiCursorLine import/usage with an <img> referencing /images/cursor-icon.svg). Also simplify the Complete Onboarding button in the inbox-connected guide by removing the disabled/loading/error checks so the button is always enabled. Modified files: apps/dashboard/public/images/cursor-icon.svg, apps/dashboard/src/components/welcome/framework-guides.tsx, apps/dashboard/src/components/welcome/inbox-connected-guide.tsx.
✅ Deploy Preview for dashboard-v2-novu-staging ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
Hey there and thank you for opening this pull request! 👋 We require pull request titles to follow specific formatting rules and it looks like your proposed title needs to be adjusted. Your PR title is: Requirements:
Expected format: Details: PR title must end with 'fixes TICKET-ID' (e.g., 'fixes NOV-123') or include ticket ID in branch name |
WalkthroughThis pull request modifies the Inbox onboarding flow across multiple dashboard components. Changes include adding Skip button options in playground and framework guide components, removing conditional disabling from the Complete Onboarding button, introducing framework-specific code snippet generation with Cursor deep linking in the framework guides, eliminating the CLI installation tab from the instructions panel, adding a footer prop to the InstructionsPanel component, and replacing a static loading skeleton with an animated OnboardingLoader component featuring multi-step progress visualization. The description text for the embed page use case was also updated. 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. 📝 Coding Plan
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 Tip CodeRabbit can use TruffleHog to scan for secrets in your code with verification capabilities.Add a TruffleHog config file (e.g. trufflehog-config.yml, trufflehog.yml) to your project to customize detectors and scanning behavior. The tool runs only when a config file is present. |
There was a problem hiding this comment.
Actionable comments posted: 5
🧹 Nitpick comments (1)
apps/dashboard/src/components/welcome/inbox-framework-guide/instructions-panel.tsx (1)
29-37: Retire the hiddenclimode when removing its tab.Line 29 no longer exposes a CLI tab, but Line 59 still treats
'cli'as a valid mode. That leaves a state callers can set even though users can no longer select or recover from it in this UI. Tighten the union/remove the branch if CLI is intentionally gone.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/dashboard/src/components/welcome/inbox-framework-guide/instructions-panel.tsx` around lines 29 - 37, The UI no longer exposes a "cli" tab but the code still accepts/handles 'cli' via installationMethod/handleMethodChange; remove the 'cli' branch and tighten the installationMethod type to only 'ai-assist' | 'manual' (and update any defaultValue if needed), and delete any conditional logic or switch cases that check for 'cli' (look for usages of installationMethod, handleMethodChange, and any switch/if branches referencing 'cli') so callers cannot set an unsupported mode.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@apps/dashboard/src/components/auth/inbox-playground.tsx`:
- Around line 149-155: The Skip button currently calls handleNextStepClick which
emits INBOX_NEXT_STEP_CLICKED; change it to emit a distinct
SKIP_ONBOARDING_CLICKED event before performing navigation so the skip action is
tracked separately. Locate the Skip button in inbox-playground.tsx and replace
the call to handleNextStepClick() (used at lines where the Skip CTA is rendered)
with a handler that first logs SKIP_ONBOARDING_CLICKED (using the same
telemetry/logger utility used for INBOX_NEXT_STEP_CLICKED) and then performs the
same navigation or state change as handleNextStepClick would; ensure the
original handleNextStepClick remains used for real "next step" CTAs so funnel
telemetry is not skewed.
In `@apps/dashboard/src/components/welcome/framework-guides.tsx`:
- Line 419: The img tag in framework-guides.tsx currently hardcodes
src="/images/cursor-icon.svg"; update it to use the dashboard asset pipeline /
Figma Dev Mode MCP Server assets endpoint instead (e.g., call the existing asset
helper or construct the URL via the FIGMA/MCP asset helper function such as
getFigmaAssetUrl("cursor-icon.svg") or by using the shared assets base URL from
config/env), preserving the alt and className attributes; ensure the asset name
matches the Figma Dev Mode asset key and that the helper you use is the standard
dashboard asset helper so the SVG is served via the asset pipeline rather than
from /images.
- Around line 192-344: The code duplicates prompt/source data and brittlely
scrapes config from copy text: remove the ad-hoc extraction
(extractConfigFromPrompt) and stop deriving applicationIdentifier/subscriberId
from copyText; instead use the existing framework maps (FRAMEWORK_PACKAGES,
FRAMEWORK_DOCS) and the canonical prompt builder (buildCondensedPrompt) as the
single source of truth, update buildCursorDeepLink to accept
applicationIdentifier and subscriberId directly (or accept an object passed from
the caller), and have the caller (e.g., the component that renders StepButton)
pass the concrete applicationIdentifier/subscriberId into buildCursorDeepLink so
the deeplink is built from the same data used to render the prompt; update or
remove extractConfigFromPrompt, ensure FRAMEWORK_PACKAGES/FRAMEWORK_DOCS include
all frameworks supported by simple-prompt-getter, and wire StepButton to receive
the config instead of relying on scraped values.
In `@apps/dashboard/src/components/welcome/inbox-connected-guide.tsx`:
- Around line 211-214: The "Complete Onboarding" Button (in
inbox-connected-guide.tsx) currently calls handleCompleteOnboarding even when
trigger detection is loading, errored, or has never seen an API call; add a
guard that requires a successful trigger-detected state before enabling
completion: use the trigger detection flags (e.g., isTriggerDetected,
triggerLoading, triggerError or equivalent selector/state used in this
component) to disable the Button and prevent onClick from invoking
handleCompleteOnboarding until detection is confirmed; also render a
disabled/tooltip state (or no-op onClick) while loading or on error so users
cannot navigate to the success page prematurely.
In `@apps/dashboard/src/components/welcome/inbox-framework-guide.tsx`:
- Around line 123-125: The navigate call uses the template ROUTES.WELCOME
("/env/:environmentSlug/welcome") directly, causing a literal ":environmentSlug"
in the URL; update the onClick handler to build the real path using the current
environment slug before calling navigate (e.g., obtain environmentSlug from
props/context and call navigate(ROUTES.WELCOME.replace(':environmentSlug',
environmentSlug)) or use react-router's generatePath to interpolate the slug),
keeping the existing track(TelemetryEvent.SKIP_ONBOARDING_CLICKED, ...) call
intact.
---
Nitpick comments:
In
`@apps/dashboard/src/components/welcome/inbox-framework-guide/instructions-panel.tsx`:
- Around line 29-37: The UI no longer exposes a "cli" tab but the code still
accepts/handles 'cli' via installationMethod/handleMethodChange; remove the
'cli' branch and tighten the installationMethod type to only 'ai-assist' |
'manual' (and update any defaultValue if needed), and delete any conditional
logic or switch cases that check for 'cli' (look for usages of
installationMethod, handleMethodChange, and any switch/if branches referencing
'cli') so callers cannot set an unsupported mode.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 391eeb31-ea42-447c-9e67-c89efc5b8e12
⛔ Files ignored due to path filters (1)
apps/dashboard/public/images/cursor-icon.svgis excluded by!**/*.svg
📒 Files selected for processing (7)
apps/dashboard/src/components/auth/inbox-playground.tsxapps/dashboard/src/components/welcome/framework-guides.tsxapps/dashboard/src/components/welcome/inbox-connected-guide.tsxapps/dashboard/src/components/welcome/inbox-framework-guide.tsxapps/dashboard/src/components/welcome/inbox-framework-guide/instructions-panel.tsxapps/dashboard/src/pages/inbox-embed-page.tsxapps/dashboard/src/pages/inbox-usecase-page.tsx
| <button | ||
| type="button" | ||
| onClick={handleNextStepClick} | ||
| className="text-text-soft hover:text-text-sub cursor-pointer text-xs transition-colors mr-3" | ||
| > | ||
| Skip | ||
| </button> |
There was a problem hiding this comment.
Track the new Skip CTA separately from Next Step.
Lines 151 and 172 reuse handleNextStepClick(), which fires INBOX_NEXT_STEP_CLICKED. The new Skip action will therefore be logged as successful progression, which skews the funnel telemetry this PR is adding. Emit SKIP_ONBOARDING_CLICKED here before navigating.
Also applies to: 170-176
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/auth/inbox-playground.tsx` around lines 149 -
155, The Skip button currently calls handleNextStepClick which emits
INBOX_NEXT_STEP_CLICKED; change it to emit a distinct SKIP_ONBOARDING_CLICKED
event before performing navigation so the skip action is tracked separately.
Locate the Skip button in inbox-playground.tsx and replace the call to
handleNextStepClick() (used at lines where the Skip CTA is rendered) with a
handler that first logs SKIP_ONBOARDING_CLICKED (using the same telemetry/logger
utility used for INBOX_NEXT_STEP_CLICKED) and then performs the same navigation
or state change as handleNextStepClick would; ensure the original
handleNextStepClick remains used for real "next step" CTAs so funnel telemetry
is not skewed.
| const FRAMEWORK_PACKAGES: Record<string, string> = { | ||
| 'Next.js': '@novu/nextjs', | ||
| React: '@novu/react', | ||
| Remix: '@novu/react', | ||
| Native: '@novu/react-native', | ||
| Angular: '@novu/js', | ||
| JavaScript: '@novu/js', | ||
| }; | ||
|
|
||
| const FRAMEWORK_DOCS: Record<string, string> = { | ||
| 'Next.js': 'https://docs.novu.co/platform/quickstart/nextjs', | ||
| React: 'https://docs.novu.co/platform/quickstart/react', | ||
| Remix: 'https://docs.novu.co/platform/quickstart/remix', | ||
| Native: 'https://docs.novu.co/platform/quickstart/react-native', | ||
| Angular: 'https://docs.novu.co/platform/quickstart/angular', | ||
| JavaScript: 'https://docs.novu.co/platform/quickstart/js', | ||
| }; | ||
|
|
||
| function getFrameworkCodeSnippet( | ||
| frameworkName: string, | ||
| applicationIdentifier: string, | ||
| subscriberId: string | ||
| ): string { | ||
| switch (frameworkName) { | ||
| case 'Next.js': | ||
| return `'use client'; | ||
| import { Inbox } from '@novu/nextjs'; | ||
|
|
||
| export default function NotificationInbox() { | ||
| return ( | ||
| <Inbox | ||
| applicationIdentifier="${applicationIdentifier}" | ||
| subscriberId="${subscriberId}" | ||
| /> | ||
| ); | ||
| }`; | ||
| case 'React': | ||
| return `import { Inbox } from '@novu/react'; | ||
| import { useNavigate } from 'react-router-dom'; | ||
|
|
||
| export default function NotificationInbox() { | ||
| const navigate = useNavigate(); | ||
|
|
||
| return ( | ||
| <Inbox | ||
| applicationIdentifier="${applicationIdentifier}" | ||
| subscriberId="${subscriberId}" | ||
| routerPush={(path) => navigate(path)} | ||
| /> | ||
| ); | ||
| }`; | ||
| default: | ||
| return `import { Inbox } from '${FRAMEWORK_PACKAGES[frameworkName] ?? '@novu/js'}'; | ||
|
|
||
| // applicationIdentifier: "${applicationIdentifier}" | ||
| // subscriberId: "${subscriberId}"`; | ||
| } | ||
| } | ||
|
|
||
| function buildCondensedPrompt(frameworkName: string, applicationIdentifier: string, subscriberId: string): string { | ||
| const pkg = FRAMEWORK_PACKAGES[frameworkName] ?? '@novu/js'; | ||
| const docs = FRAMEWORK_DOCS[frameworkName] ?? 'https://docs.novu.co'; | ||
| const snippet = getFrameworkCodeSnippet(frameworkName, applicationIdentifier, subscriberId); | ||
|
|
||
| return `# Add Novu Inbox to ${frameworkName} App | ||
|
|
||
| Install \`${pkg}\`. Add the \`<Inbox />\` component to your header, navbar, or sidebar. | ||
|
|
||
| Latest docs: ${docs} | ||
|
|
||
| ## Install | ||
|
|
||
| \`\`\`bash | ||
| npm install ${pkg} | ||
| \`\`\` | ||
|
|
||
| ## Component | ||
|
|
||
| \`\`\`tsx | ||
| ${snippet} | ||
| \`\`\` | ||
|
|
||
| ## Subscriber ID | ||
|
|
||
| Use the app's existing auth system to get a unique user identifier for subscriberId. Check for Clerk, NextAuth, Firebase, Supabase, or custom auth. If no auth system exists, use the provided subscriberId "${subscriberId}". | ||
|
|
||
| ## Appearance | ||
|
|
||
| Extract design tokens from the host app (Tailwind config, CSS variables, theme objects) and apply via the appearance prop: | ||
|
|
||
| \`\`\`tsx | ||
| <Inbox | ||
| appearance={{ | ||
| variables: { | ||
| colorPrimary: '', | ||
| colorForeground: '', | ||
| colorBackground: '', | ||
| }, | ||
| }} | ||
| /> | ||
| \`\`\` | ||
|
|
||
| Only set values extracted from the host app's design system. Do not add empty or placeholder values. | ||
|
|
||
| ## Rules | ||
|
|
||
| ALWAYS: | ||
|
|
||
| - Detect the project's package manager and use it for installation | ||
| - Extract design tokens and apply via the appearance prop | ||
| - Place <Inbox /> inline in existing UI - no new pages or wrappers | ||
| - Use TypeScript, no comments, no empty props | ||
| - Follow ${frameworkName} conventions | ||
| - Use the existing auth system to source subscriberId when available | ||
|
|
||
| NEVER: | ||
|
|
||
| - Add empty appearance values or placeholder props | ||
| - Create wrapper components or new pages just for the inbox | ||
| - Add code comments | ||
| - Introduce styles not in the host app | ||
| - Add unused props or imports | ||
|
|
||
| ## Verify Before Responding | ||
|
|
||
| 1. Is \`${pkg}\` installed with the project's package manager? | ||
| 2. Is <Inbox /> placed inline in existing UI? | ||
| 3. Are design tokens extracted and applied? | ||
| 4. Are all props non-empty and properly typed? | ||
| 5. Is subscriberId sourced from the auth system when available? | ||
|
|
||
| If any fails, revise.`; | ||
| } | ||
|
|
||
| function safeCursorEncode(text: string): string { | ||
| return encodeURIComponent(text).replace(/[!'()*~]/g, (c) => `%${c.charCodeAt(0).toString(16).toUpperCase()}`); | ||
| } | ||
|
|
||
| function buildCursorDeepLink(frameworkName: string, applicationIdentifier: string, subscriberId: string): string { | ||
| const prompt = buildCondensedPrompt(frameworkName, applicationIdentifier, subscriberId); | ||
|
|
||
| return `https://cursor.com/link/prompt?text=${safeCursorEncode(prompt)}`; | ||
| } | ||
|
|
||
| function extractConfigFromPrompt(copyText: string): { applicationIdentifier: string; subscriberId: string } { | ||
| const appIdMatch = copyText.match(/NOVU_APPLICATION_IDENTIFIER=(\S+)/); | ||
| const subIdMatch = copyText.match(/subscriberId="([^"]+)"/); | ||
|
|
||
| return { | ||
| applicationIdentifier: appIdMatch?.[1] ?? 'YOUR_APPLICATION_IDENTIFIER', | ||
| subscriberId: subIdMatch?.[1] ?? 'YOUR_SUBSCRIBER_ID', | ||
| }; | ||
| } |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Keep one source of truth for Cursor prompt data.
Lines 192-208 create a second framework matrix and Lines 336-343 scrape applicationIdentifier / subscriberId back out of copyText, which Line 372 then feeds into the deeplink builder. That is already drifting from the existing prompt source (simple-prompt-getter.ts supports Vue, these maps do not), and any prompt copy change will silently fall back to generic docs or YOUR_* placeholders. Derive the deeplink from the existing framework definitions and pass the config into StepButton directly.
As per coding guidelines, "Prefer iteration and modularization over code duplication".
Also applies to: 371-372
🧰 Tools
🪛 GitHub Check: Spell check
[warning] 276-276:
Unknown word (Supabase)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/welcome/framework-guides.tsx` around lines 192
- 344, The code duplicates prompt/source data and brittlely scrapes config from
copy text: remove the ad-hoc extraction (extractConfigFromPrompt) and stop
deriving applicationIdentifier/subscriberId from copyText; instead use the
existing framework maps (FRAMEWORK_PACKAGES, FRAMEWORK_DOCS) and the canonical
prompt builder (buildCondensedPrompt) as the single source of truth, update
buildCursorDeepLink to accept applicationIdentifier and subscriberId directly
(or accept an object passed from the caller), and have the caller (e.g., the
component that renders StepButton) pass the concrete
applicationIdentifier/subscriberId into buildCursorDeepLink so the deeplink is
built from the same data used to render the prompt; update or remove
extractConfigFromPrompt, ensure FRAMEWORK_PACKAGES/FRAMEWORK_DOCS include all
frameworks supported by simple-prompt-getter, and wire StepButton to receive the
config instead of relying on scraped values.
| Copied! | ||
| </div> | ||
| </button> | ||
| <img src="/images/cursor-icon.svg" alt="Cursor" className="size-3.5" /> |
There was a problem hiding this comment.
🛠️ Refactor suggestion | 🟠 Major
Serve the Cursor icon through the dashboard asset pipeline.
Line 419 hard-codes /images/cursor-icon.svg, which bypasses the Figma Dev Mode asset source the dashboard rules require for new SVGs.
As per coding guidelines, "Use the assets endpoint from Figma Dev Mode MCP Server to serve image and SVG assets".
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/welcome/framework-guides.tsx` at line 419, The
img tag in framework-guides.tsx currently hardcodes
src="/images/cursor-icon.svg"; update it to use the dashboard asset pipeline /
Figma Dev Mode MCP Server assets endpoint instead (e.g., call the existing asset
helper or construct the URL via the FIGMA/MCP asset helper function such as
getFigmaAssetUrl("cursor-icon.svg") or by using the shared assets base URL from
config/env), preserving the alt and className attributes; ensure the asset name
matches the Figma Dev Mode asset key and that the helper you use is the standard
dashboard asset helper so the SVG is served via the asset pipeline rather than
from /images.
| <Button onClick={handleCompleteOnboarding} variant="primary" mode="gradient"> | ||
| <RiCheckboxCircleFill className="mr-1 h-4 w-4" /> | ||
| Complete Onboarding | ||
| </Button> |
There was a problem hiding this comment.
Keep completion gated on a detected trigger.
Line 211 is now always clickable, so users can reach the success page while trigger detection is still loading, has errored, or has never seen the first API call. That regresses the confirmation step.
Suggested guard
- <Button onClick={handleCompleteOnboarding} variant="primary" mode="gradient">
+ <Button
+ onClick={handleCompleteOnboarding}
+ variant="primary"
+ mode="gradient"
+ disabled={
+ !hasValidApiKey ||
+ !hasDetectedFirstTrigger ||
+ isTriggerDetectionLoading ||
+ isTriggerDetectionError ||
+ isWorkflowsError
+ }
+ >📝 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.
| <Button onClick={handleCompleteOnboarding} variant="primary" mode="gradient"> | |
| <RiCheckboxCircleFill className="mr-1 h-4 w-4" /> | |
| Complete Onboarding | |
| </Button> | |
| <Button | |
| onClick={handleCompleteOnboarding} | |
| variant="primary" | |
| mode="gradient" | |
| disabled={ | |
| !hasValidApiKey || | |
| !hasDetectedFirstTrigger || | |
| isTriggerDetectionLoading || | |
| isTriggerDetectionError || | |
| isWorkflowsError | |
| } | |
| > | |
| <RiCheckboxCircleFill className="mr-1 h-4 w-4" /> | |
| Complete Onboarding | |
| </Button> |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/welcome/inbox-connected-guide.tsx` around lines
211 - 214, The "Complete Onboarding" Button (in inbox-connected-guide.tsx)
currently calls handleCompleteOnboarding even when trigger detection is loading,
errored, or has never seen an API call; add a guard that requires a successful
trigger-detected state before enabling completion: use the trigger detection
flags (e.g., isTriggerDetected, triggerLoading, triggerError or equivalent
selector/state used in this component) to disable the Button and prevent onClick
from invoking handleCompleteOnboarding until detection is confirmed; also render
a disabled/tooltip state (or no-op onClick) while loading or on error so users
cannot navigate to the success page prematurely.
| onClick={() => { | ||
| track(TelemetryEvent.SKIP_ONBOARDING_CLICKED, { skippedFrom: 'inbox-embed-setup-later' }); | ||
| navigate(ROUTES.WELCOME); |
There was a problem hiding this comment.
Resolve the welcome route before navigating.
Line 125 calls navigate(ROUTES.WELCOME), but ROUTES.WELCOME is the template /env/:environmentSlug/welcome. That sends the skip CTA to a literal :environmentSlug path instead of the active environment. Build the destination with the current environment slug before navigating.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.
In `@apps/dashboard/src/components/welcome/inbox-framework-guide.tsx` around lines
123 - 125, The navigate call uses the template ROUTES.WELCOME
("/env/:environmentSlug/welcome") directly, causing a literal ":environmentSlug"
in the URL; update the onClick handler to build the real path using the current
environment slug before calling navigate (e.g., obtain environmentSlug from
props/context and call navigate(ROUTES.WELCOME.replace(':environmentSlug',
environmentSlug)) or use react-router's generatePath to interpolate the slug),
keeping the existing track(TelemetryEvent.SKIP_ONBOARDING_CLICKED, ...) call
intact.
What changed? Why was the change needed?
Screenshots
Expand for optional sections
Related enterprise PR
Special notes for your reviewer