Skip to content

Conversation

pax2678
Copy link

@pax2678 pax2678 commented Sep 2, 2025

fix: clean up dashboard settings navigation and remove a duplicate setting link

Changes Made:

  • Removed duplicate Settings link from sidebar
    navigation
    • Eliminated redundant Settings entry in navSecondary
      array in app-sidebar.tsx
  • Fixed Settings link in user dropdown navigation
    • Added proper routing to Settings dropdown item in
      nav-user.tsx
    • Settings link now navigates to /dashboard/settings
      when clicked
    • Used asChild pattern with React Router Link for
      proper navigation

Result:

  • Eliminates confusing duplicate Settings links in
    dashboard
  • Provides single, functional Settings access through
    user dropdown
  • Improves user experience with cleaner navigation
    structure

Summary by cubic

Removed a duplicate Settings link in the dashboard and fixed the dropdown link to route correctly. Also cleaned up subscription handling to avoid stale price data and always read the latest record.

  • Bug Fixes
    • Navigation: removed duplicate Settings from the sidebar; dropdown Settings now links to /dashboard/settings using React Router Link.
    • Subscriptions: stop sending priceId in Polar checkout metadata (keep userId only); update polarPriceId on webhook updates; when querying by userId, select the latest subscription.

Summary by CodeRabbit

  • New Features

    • Settings is now accessed via the user menu with proper in-app navigation.
  • Refactor

    • Removed the Settings link from the sidebar to streamline navigation.
    • Updated the user dropdown to use router-based navigation for Settings.
  • Bug Fixes

    • Subscription status now reliably reflects the most recent subscription.
    • Correctly persists the subscription’s price ID from updates.
    • Removed redundant price ID from checkout metadata to prevent inconsistencies.

pax2678 added 4 commits July 10, 2025 14:30
…dated in Polar. Select the latest subscription when querying subscription based on userId.
…. priceId is not updated after a user changes the subscription creating confusion.

Changes Summary:

Checkout Creation (lines
  52-54):
  - REMOVED: priceId:
  productPriceId, from the metadata
   object
  - RESULT: Metadata now only
  contains userId when creating
  Polar checkouts

Impact:

  - Polar subscriptions: Will only
  store userId in metadata (no
  priceId)
  - Convex subscriptions: Will only
   store userId in metadata (no
  priceId)
  - Price tracking: Still works via
   the polarPriceId field in the
  subscription record
  - User linking: Preserved via
  userId in metadata for webhook
  processing
Changes Made:

  - Removed duplicate Settings link from sidebar
  navigation
    - Eliminated redundant Settings entry in navSecondary
  array in app-sidebar.tsx
  - Fixed Settings link in user dropdown navigation
    - Added proper routing to Settings dropdown item in
  nav-user.tsx
    - Settings link now navigates to /dashboard/settings
  when clicked
    - Used asChild pattern with React Router Link for
  proper navigation

  Result:

  - Eliminates confusing duplicate Settings links in
  dashboard
  - Provides single, functional Settings access through
  user dropdown
  - Improves user experience with cleaner navigation
  structure
Copy link

vercel bot commented Sep 2, 2025

@pax2678 is attempting to deploy a commit to the Goshen Labs Team on Vercel.

A member of the Team first needs to authorize it.

Copy link

coderabbitai bot commented Sep 2, 2025

Walkthrough

Removed the Settings link from the sidebar. Converted the user dropdown’s Settings item to a router Link. Updated subscription logic to always use the most recent subscription, removed priceId from checkout metadata, and on webhook subscription.updated, store price_id into polarPriceId.

Changes

Cohort / File(s) Summary
Dashboard navigation adjustments
app/components/dashboard/app-sidebar.tsx, app/components/dashboard/nav-user.tsx
Sidebar: cleared secondary nav (removes Settings). User menu: wrapped Settings in asChild Link to "/dashboard/settings" for router-based navigation.
Subscription retrieval and webhook handling
convex/subscriptions.ts
Removed priceId from checkout metadata. Subscription lookups now select the most recent by createdAt desc. Webhook subscription.updated stores incoming price_id into polarPriceId.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  actor User
  participant NavUser as NavUser Dropdown
  participant Router as Router
  participant Settings as Settings Page

  User->>NavUser: Open menu
  User->>NavUser: Click "Settings"
  NavUser->>Router: Link to /dashboard/settings
  Router-->>Settings: Render page
Loading
sequenceDiagram
  autonumber
  participant Client as Client/API
  participant Subs as subscriptions.ts
  participant DB as Database
  Note over Subs: Status fetch
  Client->>Subs: checkUserSubscriptionStatus(...)
  Subs->>DB: query subscriptions ORDER BY createdAt DESC
  DB-->>Subs: latest subscription
  Subs-->>Client: status/result

  Note over Subs: Checkout creation
  Client->>Subs: createCheckout(...)
  Subs->>DB: store checkout metadata (without priceId)
  DB-->>Subs: ack
  Subs-->>Client: checkout link

  Note over Subs: Webhook update
  participant Webhook as Webhook Handler
  Webhook->>Subs: subscription.updated(price_id)
  Subs->>DB: update subscription.polarPriceId = price_id
  DB-->>Subs: ack
  Subs-->>Webhook: 200 OK
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • SSR #5 — Similar updates to sidebar/user navigation and subscription handling, indicating overlapping navigation and subscription logic changes.

Poem

A bunny taps the sidebar thin,
The Settings link hops out and in—
Now dropdown doors will guide the way,
While freshest subs have final say.
Webhooks nibble price_id seeds,
polarPriceId stores the feeds.
Thump! Clean trails for future deeds.

✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.
    • 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.
  • 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 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/Issue comments)

Type @coderabbitai help to get the list of available commands.

Other keywords and placeholders

  • Add @coderabbitai ignore or @coderabbit 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

Status, Documentation and Community

  • Visit our Status Page to check the current availability of CodeRabbit.
  • 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.

Copy link

qodo-merge-pro bot commented Sep 2, 2025

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

⏱️ Estimated effort to review: 2 🔵🔵⚪⚪⚪
🧪 No relevant tests
🔒 No security concerns identified
⚡ Recommended focus areas for review

API Compatibility

Verify that adding .order("desc") on Convex queries is supported for the used index and returns the latest subscription as intended; ensure it orders by the correct field (typically createdAt) and that the index supports this ordering.

const subscription = await ctx.db
  .query("subscriptions")
  .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
  .order("desc")  // Order by createdAt in descending order
  .first();
Type Safety

Confirm that args.body.data.price_id exists and matches the schema when patching polarPriceId during webhook handling to avoid runtime undefined assignments.

if (existingSub) {
  await ctx.db.patch(existingSub._id, {
    polarPriceId: args.body.data.price_id,
    amount: args.body.data.amount,
    status: args.body.data.status,
Router Integration

Ensure importing Link from "react-router" aligns with the app’s router setup (react-router vs react-router-dom) and that DropdownMenuItem asChild correctly forwards refs/props for navigation.

<DropdownMenuItem asChild>
  <Link to="/dashboard/settings">

    <SettingsIcon />
    Settings
  </Link>
</DropdownMenuItem>

Copy link

qodo-merge-pro bot commented Sep 2, 2025

PR Code Suggestions ✨

Explore these optional code suggestions:

CategorySuggestion                                                                                                                                    Impact
High-level
Don’t equate “latest” with “active”

Relying on .order("desc").first() to infer hasActiveSubscription assumes the
newest record is the active one, which is brittle with renewals, scheduled
cancellations, trials, or overlapping records. Instead, explicitly query for a
subscription in an active/billable status (and optionally the newest among
those) or enforce a single active subscription per user at the data model level.
This avoids false negatives/positives in entitlement checks across the app.

Examples:

convex/subscriptions.ts [197-201]
    const subscription = await ctx.db
      .query("subscriptions")
      .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
      .order("desc")  // Order by createdAt in descending order
      .first();
convex/subscriptions.ts [234-238]
    const subscription = await ctx.db
      .query("subscriptions")
      .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
      .order("desc")  // Order by createdAt in descending order
      .first();

Solution Walkthrough:

Before:

const subscription = await ctx.db
  .query("subscriptions")
  .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
  .order("desc")  // Order by creation time
  .first();

// This check can fail if the newest record is not the active one
// (e.g., a future scheduled subscription or a recently cancelled one).
const hasActiveSubscription = subscription?.status === "active";
return { hasActiveSubscription };

After:

// A more robust way to find if a user has any active subscription.
const activeSubscription = await ctx.db
  .query("subscriptions")
  .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
  .filter((q) => q.eq(q.field("status"), "active")) // Directly filter for active status
  .first();

const hasActiveSubscription = !!activeSubscription;
return { hasActiveSubscription };
Suggestion importance[1-10]: 9

__

Why: This suggestion correctly identifies a critical logic flaw where fetching the latest subscription record via .order("desc") does not guarantee it is the active one, potentially causing incorrect entitlement checks.

High
Possible issue
Guard against undefined webhook fields

Avoid writing undefined into Convex documents. Only set polarPriceId when
price_id is present on the webhook payload to prevent runtime errors and
unintended field removal.

convex/subscriptions.ts [332-337]

-await ctx.db.patch(existingSub._id, {
-  polarPriceId: args.body.data.price_id,
+const update: any = {
   amount: args.body.data.amount,
   status: args.body.data.status,
-  currentPeriodStart: new Date(
-    args.body.data.current_period_start
+  currentPeriodStart: new Date(args.body.data.current_period_start),
+  // ... other unchanged fields
+};
+if (args.body.data.price_id) {
+  update.polarPriceId = args.body.data.price_id;
+}
+await ctx.db.patch(existingSub._id, update);

[To ensure code accuracy, apply this suggestion manually]

Suggestion importance[1-10]: 8

__

Why: This suggestion correctly identifies a potential bug where args.body.data.price_id could be undefined, which would remove the polarPriceId field during a patch, and provides a robust solution to prevent data loss.

Medium
Ensure deterministic latest subscription

.order("desc") won't reliably return the latest record unless the index also
sorts by creation time. Use a composite index that includes _creationTime, then
order by it and take the first result. Apply this change to all three
subscription lookups added in this PR.

convex/subscriptions.ts [197-201]

 const subscription = await ctx.db
   .query("subscriptions")
-  .withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
-  .order("desc")  // Order by createdAt in descending order
+  .withIndex("userId_creationTime", (q) => q.eq("userId", user.tokenIdentifier))
+  .order("desc")
   .first();
  • Apply / Chat
Suggestion importance[1-10]: 7

__

Why: The suggestion correctly identifies that using .order("desc") without a composite index is inefficient and proposes using a userId_creationTime index, which is a best practice for performance and query reliability.

Medium
  • More

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: 0

🧹 Nitpick comments (5)
app/components/dashboard/app-sidebar.tsx (1)

51-53: Render NavSecondary only when items exist.

Avoids extra empty markup and future layout quirks at the footer.

         <NavMain items={data.navMain} />
-        <NavSecondary items={data.navSecondary} className="mt-auto" />
+        {data.navSecondary.length > 0 && (
+          <NavSecondary items={data.navSecondary} className="mt-auto" />
+        )}
app/components/dashboard/nav-user.tsx (1)

89-95: Good switch to asChild Link; consider prefetch + icon set consistency.

  • Add prefetch for parity with other Links (if supported in your router setup).
  • Use the existing Tabler icon set here for consistency and smaller bundles.
-              <DropdownMenuItem asChild>
-                <Link to="/dashboard/settings">
-                
-                  <SettingsIcon />
-                  Settings
-                </Link>
+              <DropdownMenuItem asChild>
+                <Link to="/dashboard/settings" prefetch="intent">
+                  <IconSettings />
+                  Settings
+                </Link>
               </DropdownMenuItem>

Additionally update imports (outside this range):

-  IconDotsVertical,
-  IconLogout,
-  IconUserCircle,
+  IconDotsVertical,
+  IconLogout,
+  IconUserCircle,
+  IconSettings,
convex/subscriptions.ts (3)

234-242: Repeat: verify ordering + align comment.

Same concern as above for Clerk-based lookup; update the comment.

-      .order("desc")  // Order by createdAt in descending order
+      .order("desc")  // Newest first for this user (descending)

262-269: Repeat: verify ordering + align comment.

Ensure this returns the latest subscription; update the comment for clarity.

-      .order("desc")  // Order by createdAt in descending order
+      .order("desc")  // Newest first for this user (descending)

197-205: Ensure correct index for desired subscription ordering

  • The subscriptions table currently only has
    .index("userId", ["userId"])
    so .order("desc") falls back to document _id (insertion time). If that matches your “newest subscription” intent, update the inline comment:
    - .order("desc")  // Order by createdAt in descending order
    + .order("desc")  // Newest by creation (_id) first for this user
  • If you need to sort by a specific timestamp field (e.g. startedAt), add a composite index and use it in the query:
    .index("byUserStartedAt", ["userId", "startedAt"])
    .withIndex("byUserStartedAt", q => q.eq("userId", user.tokenIdentifier))
      .order("desc")
      .first();
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between a3f7c3e and 0cf77d6.

📒 Files selected for processing (3)
  • app/components/dashboard/app-sidebar.tsx (1 hunks)
  • app/components/dashboard/nav-user.tsx (2 hunks)
  • convex/subscriptions.ts (5 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
app/components/dashboard/app-sidebar.tsx (2)
app/components/dashboard/nav-secondary.tsx (2)
  • NavSecondary (15-58)
  • item (31-53)
app/routes/dashboard/settings.tsx (1)
  • Page (4-16)
app/components/dashboard/nav-user.tsx (2)
app/components/ui/dropdown-menu.tsx (3)
  • DropdownMenuItem (248-248)
  • DropdownMenuItem (62-83)
  • DropdownMenu (9-13)
app/routes/dashboard/settings.tsx (1)
  • Page (4-16)
convex/subscriptions.ts (2)
app/routes/pricing.tsx (2)
  • priceId (57-97)
  • handleSubscribe (209-209)
app/components/homepage/pricing.tsx (1)
  • priceId (26-65)
⏰ 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). (1)
  • GitHub Check: cubic · AI code reviewer
🔇 Additional comments (3)
app/components/dashboard/app-sidebar.tsx (1)

29-29: LGTM: duplicate Settings removed from sidebar.

Change aligns with the PR goal and avoids redundant navigation.

app/components/dashboard/nav-user.tsx (1)

8-8: LGTM: router Link import added.

Consistent with Link usage elsewhere in this codebase.

convex/subscriptions.ts (1)

331-345: LGTM: persist latest price on subscription.updated.

Storing polarPriceId on updates keeps the record consistent with plan changes.

Copy link

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

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

1 issue found across 3 files

React with 👍 or 👎 to teach cubic. You can also tag @cubic-dev-ai to give feedback, ask questions, or re-run the review.

const subscription = await ctx.db
.query("subscriptions")
.withIndex("userId", (q) => q.eq("userId", user.tokenIdentifier))
.order("desc") // Order by createdAt in descending order
Copy link

Choose a reason for hiding this comment

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

Selecting the newest subscription record to infer active status can yield incorrect entitlement checks. Filter for an active status when querying instead of relying on recency.

Prompt for AI agents
Address the following comment on convex/subscriptions.ts at line 200:

<comment>Selecting the newest subscription record to infer active status can yield incorrect entitlement checks. Filter for an active status when querying instead of relying on recency.</comment>

<file context>
@@ -194,10 +193,11 @@ export const checkUserSubscriptionStatus = query({
     const subscription = await ctx.db
       .query(&quot;subscriptions&quot;)
       .withIndex(&quot;userId&quot;, (q) =&gt; q.eq(&quot;userId&quot;, user.tokenIdentifier))
+      .order(&quot;desc&quot;)  // Order by createdAt in descending order
       .first();
 
</file context>
Suggested change
.order("desc") // Order by createdAt in descending order
.filter((q) => q.eq(q.field("status"), "active"))

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant