Skip to content

fix(offerings): open the editor for consultation and subscription plans - #1088

Merged
teetangh merged 7 commits into
devfrom
fix/offering-editor
Aug 1, 2026
Merged

fix(offerings): open the editor for consultation and subscription plans#1088
teetangh merged 7 commits into
devfrom
fix/offering-editor

Conversation

@teetangh

@teetangh teetangh commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Clicking Edit on a consultation or subscription plan in the Event Planner always 404'd. Webinars and classes worked.

Root cause

The edit page sourced its candidate rows from the planner query for all four offering types:

const events = [
  ...(data?.consultationPlans ?? []),
  ...(data?.subscriptionPlans ?? []),
  ...(data?.webinars ?? []),
  ...(data?.classes ?? []),
];
if (!initialEvent) notFound();

But /api/dashboard/consultant/[consultantId]/planner returns only { webinars, classes, participantCounts }. consultationPlans and subscriptionPlans are never on that payload, so for those two types the row was never found and notFound() fired every time. The doc comment on the page asserted the planner "already loads every offering this consultant owns", which is what made the mistake durable — it has been corrected.

An earlier theory blamed an empty id collapsing a double slash in the URL. That was wrong: the URLs are well-formed with valid ids, confirmed in the browser.

Fix

Consultation and subscription rows now come from the same /api/plans/* endpoints the planner list itself uses, via useConsultationPlans / useSubscriptionPlans. Only the query for the requested type runs; the other two stay disabled.

Two details that matter:

  • The flat rows those endpoints return are wrapped into the same event shape the planner list builds ({ type, id, consultationPlan }), because that is what the editor's adapters read.
  • notFound() is unreachable while any enabled query is in flight, so the editor no longer 404s on a row that was about to arrive.

Also fixed

A silent data-loss bug found on the way. GET /api/plans/consultations and /api/plans/subscriptions did not include FAQ/content relations. The editor hydrates from that list and PUTs the whole array back, so opening a plan and saving it wrote an empty set over the existing FAQs. Both list endpoints now include the content relations.

Breadcrumb prefetch 404s. The consultant layout linked every non-id path segment, including .../offerings and .../offerings/subscription, neither of which has a route. Next prefetched them and the console filled with ?_rsc= 404s. Segments with no route now render unlinked.

Fields that did not round-trip. imageUrl was in the manifests and on all four Prisma models but missing from the Zod schemas and every save path, so a chosen image was silently dropped. The class start-date field was named schedulingStartDate in the manifest and startDate in the schema, and the adapter never passed it on save. subscriptionContents was bound in the editor but absent from SubscriptionPlanSchema. No schema changes were needed for any of these.

Org billing page server crash (separate commit): Attempted to call workspaceBillingQueryKey() from the server but workspaceBillingQueryKey is on the client. The query key now lives in its own module on the correct side of the boundary.

Test plan

  • tsc --noEmit clean from a cold .tsbuildinfo
  • eslint clean on all changed files, zero warnings
  • Edit a consultation plan from the planner and confirm the editor opens with values populated
  • Edit a subscription plan and confirm the same
  • Confirm webinar and class editing still work
  • Save a plan that has FAQs and confirm they survive
  • Confirm the console has no ?_rsc= 404s on an offerings URL
  • Load the org-workspace billing page and confirm it renders

Made with Cursor

Summary by CodeRabbit

  • New Features

    • Marketplace consultation and subscription plans now include complete content, such as FAQs and roadmaps.
    • Offering editors now load the correct consultation, subscription, webinar, or class details.
    • Unsaved offerings clearly disable editing and deletion actions until they are saved.
  • Bug Fixes

    • Improved class scheduling date handling during editing and saving.
    • Prevented invalid editor links for offerings without saved IDs.
    • Improved consultant dashboard breadcrumbs for dynamic routes.
    • Standardized workspace billing data across dashboard pages.

teetangh and others added 3 commits August 1, 2026 17:23
Editing either type always 404'd. The edit page built its candidate list
from the planner query, but that payload carries only webinars, classes
and participant counts — consultationPlans/subscriptionPlans were never
on it, so notFound() fired unconditionally for those two. Each type now
reads the source that actually owns it, and only that one runs.

Also in the authoring path:

- The planner's Edit control no longer navigates with a missing id. Both
  spellings produced an unreachable URL (`id ?? ""` collapsed to a double
  slash, a bare id stringified "undefined"), and the card now disables
  Edit and Delete for a row that has no id to address.
- Breadcrumbs stop linking segments that own no page. `offerings` and
  `participants` only have dynamic children, and a dynamic param's VALUE
  is never a URL of its own, so Next was prefetching routes that 404.
- ClassPlanSchema's start date is renamed to the field the manifest
  authors (`schedulingStartDate`); it was `startDate` here, so the
  resolver stripped the value and no class ever sent one. The adapter now
  maps it to the API's ISO `startDate` and hydrates it back from the Class
  row's schedulingPeriodStartsAt.
- SubscriptionPlanSchema gains `subscriptionContents`. Absent, the
  resolver stripped the roadmap and every save posted an empty list,
  which the PUT treats as "replace with nothing".
- The consultation and subscription list reads now include `faqs`. The
  editor hydrates from them and PUTs the whole array back, so omitting
  them meant saving an empty set over a plan's FAQ.

Co-authored-by: Cursor <cursoragent@cursor.com>
The billing page is a server component that imported
workspaceBillingQueryKey from useWorkspaceBilling.ts to SSR-prefetch the
roll-up. That module is "use client", so every export of it is a client
reference and calling one from the server threw "Attempted to call
workspaceBillingQueryKey() from the server but workspaceBillingQueryKey
is on the client" — taking the live page down.

The key moves to a module with no directive, callable from either side.
The operator home shell had the identical import and the same latent
crash, so it moves too.

Co-authored-by: Cursor <cursoragent@cursor.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@netlify

netlify Bot commented Aug 1, 2026

Copy link
Copy Markdown

Deploy Preview for familiarise ready!

Name Link
🔨 Latest commit 0009eda
🔍 Latest deploy log https://app.netlify.com/projects/familiarise/deploys/6a6df6cdf9675400084cd7d0
😎 Deploy Preview https://deploy-preview-1088--familiarise.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.
Lighthouse
Lighthouse
1 paths audited
Performance: 78 (🟢 up 20 from production)
Accessibility: 99 (🟢 up 3 from production)
Best Practices: 92 (🟢 up 9 from production)
SEO: 99 (no change from production)
PWA: -
View the detailed breakdown and full score reports

To edit notification comments on pull requests, go to your Netlify project configuration.

@dosubot

dosubot Bot commented Aug 1, 2026

Copy link
Copy Markdown

📄 Knowledge review

Dosu skipped reviewing this PR because your organization has used its 200 included credits for the month. Your usage will reset on 2026-09-01. To have Dosu review this PR before then, ask your organization admin to upgrade to a pro account.


Leave Feedback Ask Dosu about familiarise_web Add Dosu to your team

@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@teetangh, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 12 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 1a62783a-a0eb-4a70-a0db-9067f9cec7f9

📥 Commits

Reviewing files that changed from the base of the PR and between 0df2ed5 and 0009eda.

📒 Files selected for processing (18)
  • app/dashboard/consultant/[consultantId]/(features)/appointments/[appointmentId]/reschedule/RescheduleClient.tsx
  • app/dashboard/consultant/[consultantId]/(features)/appointments/[appointmentId]/reschedule/page.tsx
  • app/dashboard/consultant/[consultantId]/(features)/appointments/[appointmentId]/timings/ManageTimingsClient.tsx
  • app/dashboard/consultant/[consultantId]/(features)/appointments/[appointmentId]/timings/page.tsx
  • app/dashboard/consultant/[consultantId]/(features)/offerings/[type]/[offeringId]/edit/page.tsx
  • app/dashboard/consultant/[consultantId]/(features)/requests/[requestId]/allocate/AllocateClient.tsx
  • app/dashboard/consultant/[consultantId]/(features)/requests/[requestId]/allocate/page.tsx
  • app/dashboard/consultant/[consultantId]/layout.tsx
  • app/dashboard/consultee/[consulteeId]/(features)/appointments/[appointmentId]/reschedule/RescheduleClient.tsx
  • app/dashboard/consultee/[consulteeId]/(features)/appointments/[appointmentId]/reschedule/page.tsx
  • components/dashboard/DashboardViewportFill.tsx
  • components/dashboard/PersonalDashboardShell.tsx
  • components/offerings/editor/OfferingEditor.tsx
  • components/offerings/editor/adapters.ts
  • components/planner/services/events/class-service.ts
  • components/planner/services/planner.ts
  • components/planner/services/types.ts
  • components/scheduling/UnifiedCalendar.tsx
📝 Walkthrough

Walkthrough

The PR updates offering schemas and retrieval, fixes type-specific offering editor loading, normalizes class scheduling dates, safeguards planner actions, makes consultant breadcrumbs route-aware, and centralizes workspace billing query keys.

Changes

Offering workflows

Layer / File(s) Summary
Offering schema and request contracts
schemas/plans.ts, app/api/bookings/classes/crud-with-plan/route.ts
Reusable content schemas and subscription roadmap validation were added. Class plans now use schedulingStartDate, while the booking route accepts an ISO startDate.
Plan retrieval and type-specific editor loading
app/api/plans/*/route.ts, components/planner/hooks/usePlanner.ts, app/dashboard/consultant/.../edit/page.tsx
Plan content is included in consultation and subscription responses. The editor enables only the query for the requested offering type.
Class scheduling date hydration and save
components/offerings/editor/adapters.ts
Class scheduling dates are normalized during hydration and passed to ClassService.saveClass.
Planner event management safeguards
components/planner/components/EventCard.tsx, EventCarousel.tsx, EventManagementDashboard.tsx
Unsaved events cannot be edited or deleted. Edit navigation now requires a persisted offering ID.

Consultant dashboard breadcrumbs

Layer / File(s) Summary
Route-aware breadcrumb links
app/dashboard/consultant/[consultantId]/layout.tsx
Dynamic parameter values and pathless route segments are rendered without links. Breadcrumbs update when route parameters change.

Workspace billing query keys

Layer / File(s) Summary
Shared workspace billing query key
app/dashboard/org-workspace/[orgWorkspaceId]/workspace-billing-keys.ts, app/dashboard/org-workspace/.../hooks/useWorkspaceBilling.ts, app/dashboard/org-workspace/.../billing/page.tsx, app/dashboard/org-workspace/.../home/page.tsx
Server pages and the billing hook now use the shared workspaceBillingQueryKey helper.

Estimated code review effort: 4 (Complex) | ~45 minutes

Possibly related PRs

Poem

A rabbit hops through plans anew,
With dates made clear and content too.
Unsaved cards stay safely still,
Breadcrumbs link where routes will.
Shared keys keep billing bright—
“Thump,” says the hare, “the paths are right!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the primary fix for opening consultation and subscription plan editors.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/offering-editor

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In
`@app/dashboard/consultant/`[consultantId]/(features)/offerings/[type]/[offeringId]/edit/page.tsx:
- Around line 50-66: Replace the paginated plan-list lookup in the edit page
with an authenticated type-and-ID query for the requested offering, using the
existing planner/consultation/subscription query symbols as appropriate. Keep
notFound() reserved for a successful lookup returning no resource, and render or
propagate query failures through the page’s error handling instead of treating
them as 404s.

In `@components/offerings/editor/adapters.ts`:
- Around line 45-53: Update the PATCH payload flow involving toIsoDate and
ClassService.buildRequestBody to distinguish an omitted start date from an
explicitly cleared date. Preserve an omission sentinel for unchanged fields,
while serializing a cleared editor value as the nullable/empty date
representation consumed by crud-with-plan/route.ts. Ensure the route updates
schedulingPeriodStartsAt for explicit clears but leaves it unchanged when the
field was omitted.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro Plus

Run ID: 8f5eb5fa-91b7-4523-8510-da8295206a38

📥 Commits

Reviewing files that changed from the base of the PR and between 1c64bf8 and 0df2ed5.

📒 Files selected for processing (15)
  • app/api/bookings/classes/crud-with-plan/route.ts
  • app/api/plans/consultations/route.ts
  • app/api/plans/subscriptions/route.ts
  • app/dashboard/consultant/[consultantId]/(features)/offerings/[type]/[offeringId]/edit/page.tsx
  • app/dashboard/consultant/[consultantId]/layout.tsx
  • app/dashboard/org-workspace/[orgWorkspaceId]/billing/page.tsx
  • app/dashboard/org-workspace/[orgWorkspaceId]/home/page.tsx
  • app/dashboard/org-workspace/[orgWorkspaceId]/hooks/useWorkspaceBilling.ts
  • app/dashboard/org-workspace/[orgWorkspaceId]/workspace-billing-keys.ts
  • components/offerings/editor/adapters.ts
  • components/planner/components/EventCard.tsx
  • components/planner/components/EventCarousel.tsx
  • components/planner/components/EventManagementDashboard.tsx
  • components/planner/hooks/usePlanner.ts
  • schemas/plans.ts

Comment thread components/offerings/editor/adapters.ts
The dashboard shell let tall pages grow the document, so window-scroll
carried the context bar and sidebar off-screen with the form. Clip the
shell to the viewport and scroll only <main>.

Pin the offering title + section tabs (Basics / Pricing / …) under that
bar with sticky positioning and scroll-margin on each section so the
tabs stay clickable and land the right block. Offerings breadcrumbs now
link to the Event Planner listings instead of a pathless URL.

Co-authored-by: Cursor <cursoragent@cursor.com>
teetangh and others added 3 commits August 1, 2026 18:48
…t date

The right-panel overflow-hidden created a second scrollport so Basics /
Pricing / FAQ never stuck under the context bar. Clip only the shell;
sticky chrome stays on <main>.

Edit fetches /api/plans/{type}/{id} instead of a paginated marketplace
list, so ORG_ONLY and off-page plans no longer 404. Cleared class start
dates serialize as null so PATCH can null schedulingPeriodStartsAt.

Part of #1088.

Co-authored-by: Cursor <cursoragent@cursor.com>
…croll

Drop the 500px calendar cap and size Timings/Allocate/Reschedule to the
dashboard content column so the week grid owns the only scrollport.

Co-authored-by: Cursor <cursoragent@cursor.com>
@sonarqubecloud

sonarqubecloud Bot commented Aug 1, 2026

Copy link
Copy Markdown

@teetangh
teetangh merged commit 546592f into dev Aug 1, 2026
8 checks passed
@teetangh
teetangh deleted the fix/offering-editor branch August 1, 2026 14:07
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.

1 participant