Skip to content
This repository was archived by the owner on Dec 24, 2025. It is now read-only.

Upgrade premium feature buttons to show subscription prompt#82

Open
polvallverdu wants to merge 2 commits intomainfrom
cursor/upgrade-premium-feature-buttons-to-show-subscription-prompt-50f3
Open

Upgrade premium feature buttons to show subscription prompt#82
polvallverdu wants to merge 2 commits intomainfrom
cursor/upgrade-premium-feature-buttons-to-show-subscription-prompt-50f3

Conversation

@polvallverdu
Copy link
Copy Markdown
Contributor

@polvallverdu polvallverdu commented Aug 11, 2025

Make the locked voice input button open a popover with subscription options instead of being disabled.


Open in Cursor Open in Web

Summary by CodeRabbit

  • New Features
    • Voice input is now a premium feature: non-subscribers see a lock popover with details and a Subscribe button.
    • Added an in-app popover that explains premium features and directs users to upgrade.
    • For subscribers, the mic button remains available and reflects current availability (e.g., loading or error states).

@cursor
Copy link
Copy Markdown

cursor Bot commented Aug 11, 2025

Cursor Agent can help with this pull request. Just @cursor in comments and I'll start working on changes in this branch.
Learn more about Cursor Agents

@changeset-bot
Copy link
Copy Markdown

changeset-bot Bot commented Aug 11, 2025

⚠️ No Changeset found

Latest commit: 4b91d81

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

This PR includes no changesets

When changesets are added to this PR, you'll see the packages that this PR includes changesets for and the associated semver types

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Aug 11, 2025

Walkthrough

Implements a premium lock for voice input in ChatMessageInput by conditionally rendering the mic control based on subscription status. Introduces a new PremiumLockPopover Svelte component and exports it via the premium-badge index. No changes to exported functions beyond adding the new component.

Changes

Cohort / File(s) Summary
Chat input premium gating
src/lib/components/chatInput/ChatMessageInput.svelte
Replaces always-present mic button with conditional rendering: subscribers get functional mic button; non-subscribers see mic wrapped in PremiumLockPopover with no click handler. Adjusts disabled condition to remove subscription check.
Premium lock popover component
src/lib/components/ui/premium-badge/PremiumLockPopover.svelte
Adds PremiumLockPopover component rendering a popover with “Premium feature” message, optional subscribeHref, externally controllable open prop, and children-based trigger via snippet.
Public export addition
src/lib/components/ui/premium-badge/index.ts
Re-exports PremiumLockPopover as part of the premium-badge module alongside existing exports.

Sequence Diagram(s)

sequenceDiagram
  actor User
  participant ChatMessageInput
  participant PremiumWrapper
  participant PremiumLockPopover
  participant VoiceService as voiceMessageService
  participant Billing as /settings/billing

  User->>ChatMessageInput: Open chat input
  ChatMessageInput->>PremiumWrapper: Render voice input area
  alt isUserSubscribed
    User->>ChatMessageInput: Click mic
    ChatMessageInput->>VoiceService: start/handle voice input (if not loading/error)
  else not subscribed
    ChatMessageInput->>PremiumLockPopover: Render locked mic trigger
    User->>PremiumLockPopover: Click locked mic
    PremiumLockPopover-->>User: Show "Premium feature" popover
    User->>PremiumLockPopover: Click Subscribe
    PremiumLockPopover->>Billing: Navigate to subscribeHref or default
  end
Loading

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~8 minutes

Possibly related PRs

Poem

I tap the mic—oh wait, a golden lock!
A whisper: “Premium paths around the clock.”
Subscribers sing, their voices flow,
While I, small hare, still practice low.
One hop to billing, then back to play—
Unlock the tune, and chat away! 🐇🎙️

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch cursor/upgrade-premium-feature-buttons-to-show-subscription-prompt-50f3

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
🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai generate unit tests to generate unit tests for this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

@polvallverdu polvallverdu marked this pull request as ready for review August 11, 2025 21:56
<MicIcon class="h-4 w-4" />
</Button>
</PremiumLockPopover>
{/if}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bug: Voice Button UX Issues for Non-Subscribers

The voice input button for non-subscribed users, wrapped by PremiumLockPopover, lacks proper disabled state logic. Unlike the subscribed user's button, it is not disabled when the voice feature is unavailable (e.g., during loading, in unsupported browsers, or if the voice service has errors). This allows users to click the button and see the subscription popover even when the feature is technically unavailable, leading to poor UX.

Additionally, for non-subscribed users, both PremiumWrapper (displaying a badge) and PremiumLockPopover are rendered for the voice input button. This creates redundant premium messaging (a badge and a popover), causing UI clutter and potential confusion. The PremiumWrapper should be removed or its logic adjusted when PremiumLockPopover is used.

Fix in Cursor Fix in Web

Copy link
Copy Markdown

@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: 1

🧹 Nitpick comments (2)
src/lib/components/chatInput/ChatMessageInput.svelte (2)

48-48: Consolidate imports from the same module (nit)

Importing from the same path twice is unnecessary. Combine with the existing import at Line 42 for consistency.

Apply this change:

- import { PremiumLockPopover } from "../ui/premium-badge";

And update the earlier import (Line 42) to include both:

import { PremiumWrapper, PremiumLockPopover } from "../ui/premium-badge";

489-503: Improve subscription UX by preserving return path

Pass a subscribeHref including a returnTo query so users are routed back to their chat after subscribing.

-<PremiumLockPopover>
+<PremiumLockPopover
+  subscribeHref={`/settings/billing?returnTo=${encodeURIComponent(page.url.pathname + page.url.search)}`}
+>
   <Button variant="secondary" size="icon" aria-label="Voice input (Premium feature)" title="Voice input is a Premium feature">
     <MicIcon class="h-4 w-4" />
   </Button>
 </PremiumLockPopover>
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3b58cfe and 4b91d81.

📒 Files selected for processing (3)
  • src/lib/components/chatInput/ChatMessageInput.svelte (2 hunks)
  • src/lib/components/ui/premium-badge/PremiumLockPopover.svelte (1 hunks)
  • src/lib/components/ui/premium-badge/index.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/lib/components/chatInput/ChatMessageInput.svelte (1)
src/lib/client/services/voiceMessageService.svelte.ts (1)
  • VoiceMessageService (58-520)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: Cursor Bugbot
  • GitHub Check: build-and-push
  • GitHub Check: Lint
🔇 Additional comments (4)
src/lib/components/ui/premium-badge/PremiumLockPopover.svelte (3)

7-16: Bindable open prop is correctly wired for two-way control

Use of Snippet for children and $bindable(false) on open aligns with Svelte 5 runes and allows external control via bind:open. LGTM.


18-22: Verify PopoverTrigger composition to avoid nested interactive elements

If children() renders a Button (as in ChatMessageInput), ensure your PopoverTrigger implementation composes the child element (e.g., “asChild”-style) rather than rendering its own button. Otherwise you risk a button inside a button, which breaks a11y and events.

Would you confirm ../popover’s Trigger composes its child instead of nesting interactive elements?


31-34: Check Button’s href behavior; prefer client-side navigation if supported

If your Button supports href by rendering an anchor under the hood, this is fine. If not, consider using your router’s navigation method (or a dedicated Link component) for SPA navigation to billing, preserving app state.

Can you confirm ../button supports href (anchor rendering and proper a11y/ARIA)? If not, I can suggest an alternative.

src/lib/components/ui/premium-badge/index.ts (1)

3-3: Export looks good

Publicly re-exporting PremiumLockPopover is consistent with existing index patterns.

Comment on lines +489 to +503
<Button
variant="secondary"
size="icon"
disabled={loading || !browser || voiceMessageService.state === "error"}
onclick={startRecording}
>
<MicIcon class="h-4 w-4" />
</Button>
{:else}
<PremiumLockPopover>
<Button variant="secondary" size="icon">
<MicIcon class="h-4 w-4" />
</Button>
</PremiumLockPopover>
{/if}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add accessible labels for icon-only buttons

Icon-only buttons should have an accessible name. Add aria-label (and optionally title) to both the subscriber mic and the locked mic trigger.

-<Button
+<Button
+  aria-label="Start voice input"
   variant="secondary"
   size="icon"
   disabled={loading || !browser || voiceMessageService.state === "error"}
   onclick={startRecording}
 >
   <MicIcon class="h-4 w-4" />
 </Button>
-<PremiumLockPopover>
+<PremiumLockPopover>
   <Button variant="secondary" size="icon"
+    aria-label="Voice input (Premium feature)"
+    title="Voice input is a Premium feature"
   >
     <MicIcon class="h-4 w-4" />
   </Button>
 </PremiumLockPopover>
📝 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
<Button
variant="secondary"
size="icon"
disabled={loading || !browser || voiceMessageService.state === "error"}
onclick={startRecording}
>
<MicIcon class="h-4 w-4" />
</Button>
{:else}
<PremiumLockPopover>
<Button variant="secondary" size="icon">
<MicIcon class="h-4 w-4" />
</Button>
</PremiumLockPopover>
{/if}
<Button
aria-label="Start voice input"
variant="secondary"
size="icon"
disabled={loading || !browser || voiceMessageService.state === "error"}
onclick={startRecording}
>
<MicIcon class="h-4 w-4" />
</Button>
{:else}
<PremiumLockPopover>
<Button variant="secondary" size="icon"
aria-label="Voice input (Premium feature)"
title="Voice input is a Premium feature"
>
<MicIcon class="h-4 w-4" />
</Button>
</PremiumLockPopover>
{/if}
🤖 Prompt for AI Agents
In src/lib/components/chatInput/ChatMessageInput.svelte around lines 489 to 503,
the icon-only Mic buttons lack accessible names; add aria-label attributes (and
optionally title attributes) to both Button components — for the
active/subscriber button use a descriptive label like "Start voice recording"
(or "Start recording") and for the locked/premium button use "Voice messaging
locked" or "Unlock voice messages" — add these attributes directly on the Button
elements and ensure the labels make sense with the disabled state.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants