Skip to content

feat: add to calendar after ticket purchase #474

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

Merged
merged 6 commits into from
May 16, 2025

Conversation

joelhooks
Copy link
Collaborator

@joelhooks joelhooks commented May 15, 2025

Summary by CodeRabbit

  • New Features
    • Users who purchase live event products are now automatically added to the associated Google Calendar event and organization with a learner role.
    • Added an "Events" section to the admin sidebar navigation.
    • Introduced subscription flow enhancements including login verification, checkout handling for logged-in users, and an "Already Subscribed" page for managing team access and content.
  • Bug Fixes
    • Google Calendar event updates now explicitly disable notifications to attendees.
  • Chores
    • Improved internal handling of Google Calendar event guest permissions.
  • Improvements
    • Updated event location text for online events to clarify calendar invites are sent after purchase.
    • Enhanced event purchase confirmation display with invoice links and conditional pricing widget visibility.

Copy link

vercel bot commented May 15, 2025

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
ai-hero ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
course-builder-egghead ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
course-builder-poc ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
craft-of-ui ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
cursor-pro ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
epic-react-builder ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
epic-web-builder ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
epicai-pro 🛑 Canceled (Inspect) May 16, 2025 6:01am
go-local-first ✅ Ready (Inspect) Visit Preview 💬 Add feedback May 16, 2025 6:01am
1 Skipped Deployment
Name Status Preview Comments Updated (UTC)
astro-party ⬜️ Ignored (Inspect) Visit Preview May 16, 2025 6:01am

Copy link
Contributor

coderabbitai bot commented May 15, 2025

Caution

Review failed

The pull request is closed.

Walkthrough

The changes introduce a new Inngest function to handle post-purchase workflows for live event products, managing team and individual purchases with organization membership and calendar event updates. Google Calendar integration was enhanced with a function to add users to events and guest permission flags. Unused imports were removed, and calendar event updates now suppress notifications. A new "Events" navigation item was added to the admin sidebar. Several new subscription-related pages and flows were added, including login verification, subscription checkout for logged-in users, and an already-subscribed landing page. The checkout flow now includes live products in login redirection logic. Minor text updates and a new changeset for live product checkout support were also included.

Changes

File(s) Change Summary
apps/epicdev-ai/src/inngest/functions/calendar-sync.ts Removed unused type imports. Added guestsCanInviteOthers and guestsCanSeeOtherGuests boolean properties set to false in the Google Calendar event mapping function.
apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts Added a new exported Inngest function postEventPurchase to process post-purchase workflows for live event products. It fetches purchase, product, user, and event data, distinguishes team vs individual purchases, manages organization membership, and attempts to add users to Google Calendar events. Includes error handling and placeholders for notification alerts.
apps/epicdev-ai/src/lib/google-calendar.ts Updated updateGoogleCalendarEvent to disable notifications by setting sendNotifications: false in the API patch call. Added a new exported async function addUserToGoogleCalendarEvent that retrieves an event, adds a user email to attendees, and updates the event. Throws an error if the event is not found.
apps/epicdev-ai/src/app/admin/layout.tsx Added a new "Events" navigation item with a Presentation icon to the admin sidebar menu between "Pages" and "Products".
apps/epicdev-ai/src/inngest/inngest.config.ts Imported the new postEventPurchase function and appended it to the functions array in the inngestConfig export.
apps/epicdev-ai/src/app/(commerce)/subscribe/already-subscribed/page.tsx Added new React server component page AlreadySubscribedPage to show subscription management and content access options for users with active subscriptions, with redirects for unauthenticated or unsubscribed users.
apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx Added new Next.js server component page for logged-in users to create Stripe checkout sessions for subscription purchases, redirecting based on session and subscription status.
apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx Added new React server component page VerifyLoginPage to validate login and subscription status before allowing subscription checkout flows, with redirects and login UI rendering.
packages/core/src/lib/actions/checkout.ts Expanded login redirect condition to include products of type "live" along with "membership" and "cohort" before checkout.
apps/epicdev-ai/src/app/(content)/events/[slug]/_components/event-details.tsx Updated event location text for online events to state calendar invite will be sent after purchase, removing mention of confirmation email.
apps/epicdev-ai/src/app/(content)/events/[slug]/_components/event-pricing-widget-container.tsx Added top border styling classes to the pricing widget container div for both light and dark modes.
apps/epicdev-ai/src/app/(content)/events/[slug]/page.tsx Removed unused imports, restyled purchased ticket confirmation block with dark mode and layout improvements, conditionally rendered invoice link if merchantChargeId exists, and hid pricing widget if user already purchased the product.
.changeset/cuddly-poets-march.md Added new changeset patch for @coursebuilder/core package to include live products in checkout flow.

Sequence Diagram(s)

sequenceDiagram
    participant InngestFunction as postEventPurchase
    participant DB as DatabaseAdapter
    participant Calendar as GoogleCalendarAPI

    InngestFunction->>DB: Fetch purchase, product, user
    InngestFunction->>DB: Fetch event resource
    alt Team Purchase
        InngestFunction->>InngestFunction: (Placeholder) Send notification email
    else Individual Purchase (Valid/Restricted)
        InngestFunction->>DB: Add user to organization with "learner" role
        InngestFunction->>Calendar: addUserToGoogleCalendarEvent(calendarEventId, userEmail)
    else Invalid Purchase
        InngestFunction->>InngestFunction: (Placeholder) Send alert (e.g., Slack)
    end
    InngestFunction-->>InngestFunction: Return workflow result
Loading

Poem

🐇 Hopping through code with a twitch of my nose,
New events and purchases in neat little rows.
Guests can't invite, nor peek at the rest,
Calendar syncing now works at its best.
From team buys to solo, the workflows align,
Our rabbit code dances—oh, how divine! 🎉✨

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

Scope: all 3 workspace projects
 WARN  Ignoring not compatible lockfile at /tmp/eslint/pnpm-lock.yaml
Progress: resolved 1, reused 0, downloaded 0, added 0
 WARN  deprecated [email protected]: This version is no longer supported. Please see https://eslint.org/version-support for other options.
/tmp/eslint/packages/config/eslint-config:
 ERR_PNPM_WORKSPACE_PKG_NOT_FOUND  In packages/config/eslint-config: "@coursebuilder/eslint-plugin@workspace:*" is in the dependencies but no package named "@coursebuilder/eslint-plugin" is present in the workspace

This error happened while installing a direct dependency of /tmp/eslint/packages/config/eslint-config

Packages found in the workspace:

Note

⚡️ AI Code Reviews for VS Code, Cursor, Windsurf

CodeRabbit now has a plugin for VS Code, Cursor and Windsurf. This brings AI code reviews directly in the code editor. Each commit is reviewed immediately, finding bugs before the PR is raised. Seamless context handoff to your AI code agent ensures that you can easily incorporate review feedback.
Learn more here.


Note

⚡️ Faster reviews with caching

CodeRabbit now supports caching for code and dependencies, helping speed up reviews. This means quicker feedback, reduced wait times, and a smoother review experience overall. Cached data is encrypted and stored securely. This feature will be automatically enabled for all accounts on May 16th. To opt out, configure Review - Disable Cache at either the organization or repository level. If you prefer to disable all data retention across your organization, simply turn off the Data Retention setting under your Organization Settings.
Enjoy the performance boost—your workflow just got faster.


📜 Recent review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 0b13cf5 and 485dc02.

📒 Files selected for processing (4)
  • apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx (1 hunks)
  • apps/epicdev-ai/src/app/(content)/events/[slug]/_components/event-details.tsx (2 hunks)
  • apps/epicdev-ai/src/app/(content)/events/[slug]/_components/event-pricing-widget-container.tsx (1 hunks)
  • apps/epicdev-ai/src/app/(content)/events/[slug]/page.tsx (3 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings

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.
    • @coderabbitai modularize this function.
  • 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.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

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

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

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 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.

Copy link
Contributor

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

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between b9d98ee and 3f7bf95.

📒 Files selected for processing (3)
  • apps/epicdev-ai/src/inngest/functions/calendar-sync.ts (1 hunks)
  • apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (1 hunks)
  • apps/epicdev-ai/src/lib/google-calendar.ts (2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (4)
packages/core/src/inngest/commerce/event-new-purchase-created.ts (1)
  • NEW_PURCHASE_CREATED_EVENT (3-3)
packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (1)
  • FULL_PRICE_COUPON_REDEEMED_EVENT (3-4)
apps/epicdev-ai/src/lib/events-query.ts (1)
  • getEvent (30-74)
apps/epicdev-ai/src/lib/google-calendar.ts (1)
  • addUserToGoogleCalendarEvent (236-259)
⏰ Context from checks skipped due to timeout of 90000ms (3)
  • GitHub Check: e2e-node (course-builder-web)
  • GitHub Check: format
  • GitHub Check: build
🔇 Additional comments (3)
apps/epicdev-ai/src/lib/google-calendar.ts (1)

155-160:

❌ Incorrect review comment

Prefer the modern sendUpdates: 'none' flag over the deprecated sendNotifications

sendNotifications was deprecated in Calendar v3 in favor of the richer sendUpdates enum. While the boolean still works today, relying on a deprecated flag risks silent breakage when Google eventually removes it.

-		const response = await calendar.events.patch({
+		const response = await calendar.events.patch({
 			calendarId: 'primary',
 			eventId: calendarEventId,
 			requestBody: eventDetails,
-			sendNotifications: false,
+			// Do not generate any email/Push notifications
+			sendUpdates: 'none',
 		})

Would you like me to search the latest API docs to confirm the deprecation timeline?


🌐 Web query:

Is the sendNotifications parameter deprecated in Google Calendar API v3 in favor of the sendUpdates enum?

💡 Result:

As of my knowledge cutoff in June 2024, the sendNotifications parameter in the Google Calendar API v3 was not deprecated in favor of the sendUpdates enum. The sendNotifications parameter is used when creating or updating events to specify whether to send notifications to attendees. The sendUpdates parameter, on the other hand, is used to control how updates to an event are communicated to attendees.

However, since my information may be outdated, I recommend consulting the official Google Calendar API documentation or the Google API changelog for the most current information regarding these parameters.


Clarify sendNotifications status and optional sendUpdates usage

The sendNotifications flag in Calendar API v3 is still supported and not officially deprecated. You can continue using it safely. If you’d like richer update controls, you may optionally replace:

-   sendNotifications: false,
+  // Do not send any emails or push notifications
+   sendUpdates: 'none',

This change isn’t required today, but adopting sendUpdates can provide more granular behavior and may ease future migration if Google deprecates the boolean flag.

Likely an incorrect or invalid review comment.

apps/epicdev-ai/src/inngest/functions/calendar-sync.ts (1)

59-60: Good call explicitly locking down guest permissions

Setting guestsCanInviteOthers and guestsCanSeeOtherGuests to false prevents unwanted chain-invitations and reduces attendee e-mail leakage. Nice security/privacy touch.

apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (1)

90-101: Potential duplicate attendee entries – rely on dedup logic

The addUserToGoogleCalendarEvent helper now deduplicates attendees (see earlier comment). Ensure you pull the updated helper before merging, otherwise repeated replays of this step may still fail with “Duplicate attendees” errors from Google.

Comment on lines 236 to 259
export async function addUserToGoogleCalendarEvent(
calendarEventId: string,
userEmail: string,
) {
const authClient = getAuthClient()
const calendar = google.calendar({ version: 'v3', auth: authClient })

const response = await calendar.events.get({
calendarId: 'primary',
eventId: calendarEventId,
})

const event = response.data

if (!event) {
throw new Error('Event not found')
}

event.attendees?.push({
email: userEmail,
})

return updateGoogleCalendarEvent(calendarEventId, event)
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

attendees can be undefined & duplicates are possible – ensure array initialisation and deduplication

event.attendees?.push becomes a no-op when the property is missing, leaving the user off the event.
Additionally, re-invoking this function for the same user will add duplicate entries, which Google rejects with a 400.

-	event.attendees?.push({
-		email: userEmail,
-	})
+	// Ensure we have an attendees array
+	if (!event.attendees) {
+		event.attendees = []
+	}
+
+	// Avoid duplicate attendees (Google API returns 400 on dupes)
+	if (!event.attendees.some((a) => a.email === userEmail)) {
+		event.attendees.push({ email: userEmail })
+	}

Optional: you could reuse the calendar client instead of calling updateGoogleCalendarEvent, avoiding a second auth handshake.

📝 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
export async function addUserToGoogleCalendarEvent(
calendarEventId: string,
userEmail: string,
) {
const authClient = getAuthClient()
const calendar = google.calendar({ version: 'v3', auth: authClient })
const response = await calendar.events.get({
calendarId: 'primary',
eventId: calendarEventId,
})
const event = response.data
if (!event) {
throw new Error('Event not found')
}
event.attendees?.push({
email: userEmail,
})
return updateGoogleCalendarEvent(calendarEventId, event)
}
export async function addUserToGoogleCalendarEvent(
calendarEventId: string,
userEmail: string,
) {
const authClient = getAuthClient()
const calendar = google.calendar({ version: 'v3', auth: authClient })
const response = await calendar.events.get({
calendarId: 'primary',
eventId: calendarEventId,
})
const event = response.data
if (!event) {
throw new Error('Event not found')
}
// Ensure we have an attendees array
if (!event.attendees) {
event.attendees = []
}
// Avoid duplicate attendees (Google API returns 400 on dupes)
if (!event.attendees.some((a) => a.email === userEmail)) {
event.attendees.push({ email: userEmail })
}
return updateGoogleCalendarEvent(calendarEventId, event)
}
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/lib/google-calendar.ts between lines 236 and 259, the
code does not initialize the attendees array if it is undefined, causing the
push operation to be skipped and the user not added. Also, it allows duplicate
attendees which Google rejects. Fix this by initializing the attendees array if
missing and checking for existing userEmail before adding to prevent duplicates.
Optionally, refactor to reuse the calendar client for updating the event to
avoid a second authentication.

Comment on lines +51 to +58
const eventResourceId = product.resources?.find(
(resource) => resource.resource?.type === 'event',
)?.resource.id

const eventResource = await step.run(`get event resource`, async () => {
return getEvent(eventResourceId)
})

Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Guard against missing event resource before querying

eventResourceId can be undefined if the product doesn’t embed an event-type resource, leading to an invalid DB call (getEvent(undefined)) and hard-to-trace SQL errors.

-		const eventResourceId = product.resources?.find(
+		const eventResourceId = product.resources?.find(
 			(resource) => resource.resource?.type === 'event',
 		)?.resource.id
+
+		if (!eventResourceId) {
+			throw new Error(
+				`Product ${product.id} has no linked event resource – cannot sync calendar.`,
+			)
+		}
📝 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
const eventResourceId = product.resources?.find(
(resource) => resource.resource?.type === 'event',
)?.resource.id
const eventResource = await step.run(`get event resource`, async () => {
return getEvent(eventResourceId)
})
const eventResourceId = product.resources?.find(
(resource) => resource.resource?.type === 'event',
)?.resource.id
if (!eventResourceId) {
throw new Error(
`Product ${product.id} has no linked event resource – cannot sync calendar.`,
)
}
const eventResource = await step.run(`get event resource`, async () => {
return getEvent(eventResourceId)
})
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts around lines 51
to 58, the code calls getEvent with eventResourceId without checking if
eventResourceId is undefined, which can cause invalid database queries. Add a
guard condition to check if eventResourceId is defined before calling getEvent,
and handle the case where it is undefined appropriately to prevent invalid DB
calls and errors.

Copy link
Contributor

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

♻️ Duplicate comments (1)
apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (1)

51-57: Guard against missing event resource before querying

eventResourceId can be undefined if the product doesn't embed an event-type resource, leading to an invalid DB call (getEvent(undefined)) and hard-to-trace SQL errors.

 const eventResourceId = product.resources?.find(
 	(resource) => resource.resource?.type === 'event',
 )?.resource.id

+if (!eventResourceId) {
+	throw new Error(
+		`Product ${product.id} has no linked event resource – cannot sync calendar.`,
+	)
+}
🧹 Nitpick comments (4)
apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (4)

88-91: Remove console.log statements before deploying

Debug statements should be removed from production code.

-console.log(
-	'eventResource?.fields.calendarId',
-	eventResource?.fields.calendarId,
-)

97-99: Remove redundant validation check

This validation is redundant since you already check for the existence of eventResource?.fields.calendarId in the if condition on line 92.

-if (!eventResource.fields.calendarId) {
-	throw new Error(`eventResource.fields.calendarId is required`)
-}

61-64: Team purchase handling needs implementation

There's a placeholder comment for team purchase handling. Consider implementing this functionality or creating a follow-up task to track this work.

if (isTeamPurchase) {
-	// send an email to the purchaser explaining next steps
-	// this might happen in welcome email already?
+	// TODO: Implement team purchase handling
+	// 1. Send an email to the purchaser explaining next steps (if not covered by welcome email)
+	// 2. Consider any additional team-specific onboarding requirements
}

Would you like me to help implement the team purchase handling logic or create an issue to track this work?


8-22: Add JSDoc documentation for the function

Adding JSDoc documentation would improve the maintainability of this code by clearly communicating its purpose, inputs, and outputs.

+/**
+ * Post-purchase workflow handler for live event products
+ * 
+ * This function is triggered when a live event product is purchased or a full-price coupon is redeemed.
+ * It handles adding users to organizations and Google Calendar events associated with the purchased event.
+ * 
+ * @triggers {NEW_PURCHASE_CREATED_EVENT} When a new purchase is created for a live event
+ * @triggers {FULL_PRICE_COUPON_REDEEMED_EVENT} When a full-price coupon is redeemed for a live event
+ */
export const postEventPurchase = inngest.createFunction(
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 3f7bf95 and 19e3bac.

📒 Files selected for processing (3)
  • apps/epicdev-ai/src/app/admin/layout.tsx (2 hunks)
  • apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (1 hunks)
  • apps/epicdev-ai/src/inngest/inngest.config.ts (2 hunks)
✅ Files skipped from review due to trivial changes (2)
  • apps/epicdev-ai/src/app/admin/layout.tsx
  • apps/epicdev-ai/src/inngest/inngest.config.ts
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts (4)
packages/core/src/inngest/commerce/event-new-purchase-created.ts (1)
  • NEW_PURCHASE_CREATED_EVENT (3-3)
packages/core/src/inngest/commerce/event-full-price-coupon-redeemed.ts (1)
  • FULL_PRICE_COUPON_REDEEMED_EVENT (3-4)
apps/epicdev-ai/src/lib/events-query.ts (1)
  • getEvent (30-74)
apps/epicdev-ai/src/lib/google-calendar.ts (1)
  • addUserToGoogleCalendarEvent (236-259)
⏰ Context from checks skipped due to timeout of 90000ms (1)
  • GitHub Check: format

Comment on lines +106 to +108
} else {
// send a slack message or something because it seems broken
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Implement error handling for invalid purchase status

Currently, there's only a comment indicating that an alert should be sent when a purchase has an invalid status. Consider implementing proper error handling or logging here.

} else {
-	// send a slack message or something because it seems broken
+	console.error(`Purchase ${purchase.id} has invalid status: ${purchase.status}`)
+	// TODO: Implement proper alerting for invalid purchase status
}
📝 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
} else {
// send a slack message or something because it seems broken
}
} else {
console.error(`Purchase ${purchase.id} has invalid status: ${purchase.status}`)
// TODO: Implement proper alerting for invalid purchase status
}
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts around lines 106
to 108, the else block for handling invalid purchase status is empty except for
a comment. Implement proper error handling by adding logging or sending an alert
(e.g., a Slack message) to notify that the purchase status is invalid or
unexpected. This will help in monitoring and debugging issues related to
purchase processing.

Comment on lines +92 to +105
if (eventResource?.fields.calendarId) {
gCalEvent = await step.run(`add user to calendar event`, async () => {
if (!user.email) {
throw new Error(`user.email is required`)
}
if (!eventResource.fields.calendarId) {
throw new Error(`eventResource.fields.calendarId is required`)
}
return await addUserToGoogleCalendarEvent(
eventResource.fields.calendarId,
user.email,
)
})
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Add error handling for Google Calendar operations

If the Google Calendar operation fails, the entire function will fail. Consider adding a try/catch block to handle potential API errors more gracefully.

if (eventResource?.fields.calendarId) {
+	try {
		gCalEvent = await step.run(`add user to calendar event`, async () => {
			if (!user.email) {
				throw new Error(`user.email is required`)
			}
			return await addUserToGoogleCalendarEvent(
				eventResource.fields.calendarId,
				user.email,
			)
		})
+	} catch (error) {
+		console.error(`Failed to add user to calendar event:`, error)
+		// Continue function execution even if calendar sync fails
+	}
}

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/epicdev-ai/src/inngest/functions/post-event-purchase.ts between lines 92
and 105, the Google Calendar operation lacks error handling, causing the entire
function to fail if the API call fails. Wrap the call to
addUserToGoogleCalendarEvent inside a try/catch block within the step.run
function to catch any errors, log or handle them appropriately, and prevent the
whole function from crashing due to Google Calendar API failures.

Copy link
Contributor

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

🧹 Nitpick comments (6)
.changeset/cuddly-poets-march.md (1)

5-5: Consider rephrasing the changeset message for clarity.

The message could be clearer with the article "the" before "live products" or by rephrasing to something like "Add support for live products in checkout flow".

-include live products in checkout flow
+include live products in the checkout flow
🧰 Tools
🪛 LanguageTool

[uncategorized] ~5-~5: You might be missing the article “the” here.
Context: ...e': patch --- include live products in checkout flow

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx (1)

22-26: Duplicate headers retrieval.

You've already retrieved headers on line 18 (though not using the result). Consider consolidating these calls for efficiency.

-	await headers()
-	const checkoutParams = CheckoutParamsSchema.parse(await searchParams)
-	const { session } = await getServerAuthSession()
-	const user = session?.user
-	const headersList = await headers()
+	const headersList = headers()
+	const checkoutParams = CheckoutParamsSchema.parse(searchParams)
+	const { session } = await getServerAuthSession()
+	const user = session?.user
apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx (4)

7-7: Remove unused config import to avoid linter warnings
config is imported but never referenced in this file. Dead imports usually trip ESLint’s no-unused-vars / unused-imports rules and add noise to diffs.

-import config from '@/config'

31-32: ability is destructured but never used
The variable is created and then ignored, which is a small smell and will cause “unused variable” lint errors.

- const { session, ability } = await getServerAuthSession()
+ const { session } = await getServerAuthSession()

29-29: await headers() has no side-effects here
Calling headers() without using the returned value is a no-op. Because the page is already flagged as force-dynamic, you can safely remove this line unless you intend to read a header later.


39-44: Remove debugging console.log / console.dir before merging
Leaving verbose logs in a server component pollutes production logs and could leak sensitive checkout data.

- console.log({ checkoutUrl, checkoutParams, product })
- 
- console.dir({ parsedCheckoutParams }, { depth: null })
📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
Cache: Disabled due to data retention organization setting
Knowledge Base: Disabled due to data retention organization setting

📥 Commits

Reviewing files that changed from the base of the PR and between 19e3bac and 938d2d2.

📒 Files selected for processing (5)
  • .changeset/cuddly-poets-march.md (1 hunks)
  • apps/epicdev-ai/src/app/(commerce)/subscribe/already-subscribed/page.tsx (1 hunks)
  • apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx (1 hunks)
  • apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx (1 hunks)
  • packages/core/src/lib/actions/checkout.ts (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
apps/epicdev-ai/src/app/(commerce)/subscribe/already-subscribed/page.tsx (2)
apps/ai-hero/src/lib/subscriptions.ts (1)
  • getSubscriptionStatus (5-33)
packages/ui/index.tsx (5)
  • Card (194-194)
  • CardHeader (198-198)
  • CardTitle (199-199)
  • CardContent (195-195)
  • Button (175-175)
🪛 LanguageTool
.changeset/cuddly-poets-march.md

[uncategorized] ~5-~5: You might be missing the article “the” here.
Context: ...e': patch --- include live products in checkout flow

(AI_EN_LECTOR_MISSING_DETERMINER_THE)

⏰ Context from checks skipped due to timeout of 90000ms (5)
  • GitHub Check: lint
  • GitHub Check: typecheck
  • GitHub Check: format
  • GitHub Check: build
  • GitHub Check: e2e-node (course-builder-web)
🔇 Additional comments (6)
packages/core/src/lib/actions/checkout.ts (1)

52-55: Looks good - extending product type check to include 'live' products.

The conditional check is now correctly including "live" products along with "membership" and "cohort" types, ensuring live event purchases go through the login verification flow.

apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx (2)

34-38: Good subscription status check.

The logic to check for active subscriptions and redirect to an appropriate page is well-implemented.


40-44: Proper checkout session creation and redirect.

The Stripe checkout session creation with user ID and organization ID is correctly implemented.

apps/epicdev-ai/src/app/(commerce)/subscribe/already-subscribed/page.tsx (3)

32-32: TODO comment needs to be addressed.

This TODO comment indicates that permission checks need to be implemented to ensure users can perform the actions offered on this page.

Consider implementing the permission checks or clarifying the required permissions before production deployment.


34-107: Well-structured UI for already subscribed users.

The UI provides clear options for users who are already subscribed, with appropriate sections for team management and content access.


17-30: Good authentication and subscription validation.

The page correctly checks for user authentication and active subscription status before rendering content, with appropriate redirects.

Comment on lines +16 to +17
searchParams: Promise<ParsedUrlQuery>
}) {
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Incorrect type for searchParams parameter.

The searchParams parameter is typed as a Promise, but in Next.js, this should be a regular object, not a Promise.

-	searchParams: Promise<ParsedUrlQuery>
+	searchParams: ParsedUrlQuery
📝 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
searchParams: Promise<ParsedUrlQuery>
}) {
searchParams: ParsedUrlQuery
}) {
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx around lines
16 to 17, the searchParams parameter is incorrectly typed as a Promise. Change
the type of searchParams from Promise<ParsedUrlQuery> to just ParsedUrlQuery to
reflect that it is a regular object, not a Promise, as expected by Next.js.

Comment on lines +35 to +37
const product = await getProduct(checkoutParams.productId as string)

let callbackUrl = `${env.COURSEBUILDER_URL}/subscribe/logged-in`
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Leverage the validated productId when fetching the product
After adopting safeCheckoutParams (see previous comment), you avoid the unsafe cast and possible undefined access.

- const product = await getProduct(checkoutParams.productId as string)
+ const product = await getProduct(safeCheckoutParams.productId)
📝 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
const product = await getProduct(checkoutParams.productId as string)
let callbackUrl = `${env.COURSEBUILDER_URL}/subscribe/logged-in`
const product = await getProduct(safeCheckoutParams.productId)
let callbackUrl = `${env.COURSEBUILDER_URL}/subscribe/logged-in`
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx around
lines 35 to 37, replace the unsafe cast of checkoutParams.productId with the
validated productId from safeCheckoutParams when calling getProduct. This
ensures you use a guaranteed defined and safe productId, avoiding potential
runtime errors from undefined values.

Comment on lines +33 to +35
const providers = getProviders()
const csrfToken = await getCsrf()
const product = await getProduct(checkoutParams.productId as string)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Await the getProviders call so <Login> receives data, not a Promise
getProviders() is typically asynchronous (NextAuth). Passing a pending Promise through React props can cause runtime warnings in React 18 server components.

- const providers = getProviders()
+ const providers = await getProviders()
📝 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
const providers = getProviders()
const csrfToken = await getCsrf()
const product = await getProduct(checkoutParams.productId as string)
const providers = await getProviders()
const csrfToken = await getCsrf()
const product = await getProduct(checkoutParams.productId as string)
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx around
lines 33 to 35, the call to getProviders() is missing an await, causing a
Promise to be passed to the Login component instead of resolved data. Fix this
by adding await before getProviders() so that the providers variable holds the
resolved data before being passed as props.

Comment on lines +65 to +67
const checkoutSearchParams = new URLSearchParams(
checkoutParams as Record<string, string>,
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

URLSearchParams may throw if any value is not a string
Directly spreading checkoutParams (whose values could be arrays/undefined) is risky. Using the schema-validated object (strings only) eliminates that edge-case.

- const checkoutSearchParams = new URLSearchParams(
-   checkoutParams as Record<string, string>,
- )
+ const checkoutSearchParams = new URLSearchParams(safeCheckoutParams)
📝 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
const checkoutSearchParams = new URLSearchParams(
checkoutParams as Record<string, string>,
)
const checkoutSearchParams = new URLSearchParams(safeCheckoutParams)
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx around
lines 65 to 67, the code creates a URLSearchParams instance directly from
checkoutParams, which may contain non-string values like arrays or undefined,
causing URLSearchParams to throw an error. To fix this, ensure that
checkoutParams is first validated and transformed by the schema to guarantee all
values are strings before passing it to URLSearchParams. Replace the direct
usage with the schema-validated object that contains only string values.

Comment on lines +41 to +47
const parsedCheckoutParams = CheckoutParamsSchema.safeParse(checkoutParams)

console.dir({ parsedCheckoutParams }, { depth: null })

if (!parsedCheckoutParams.success) {
return redirect('/login')
}
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

Use the parsed/validated values, not the raw query, to guarantee type-safety
You parse checkoutParams with CheckoutParamsSchema but then discard the result and keep using the unchecked object. This defeats the purpose of validation and may still let malformed arrays/undefineds slip through.

- const parsedCheckoutParams = CheckoutParamsSchema.safeParse(checkoutParams)
+ const parsedResult = CheckoutParamsSchema.safeParse(checkoutParams)

- if (!parsedCheckoutParams.success) {
+ if (!parsedResult.success) {
   return redirect('/login')
 }
+const safeCheckoutParams = parsedResult.data

All downstream consumers (getProduct, URLSearchParams, etc.) should reference safeCheckoutParams.

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx around
lines 41 to 47, the code parses checkoutParams with CheckoutParamsSchema but
continues using the original unchecked checkoutParams object. To ensure type
safety and prevent malformed data, replace all uses of checkoutParams with the
validated parsedCheckoutParams.data after confirming success. Update downstream
consumers like getProduct and URLSearchParams to reference this validated data
instead of the raw query parameters.

Comment on lines +24 to +30
export default async function VerifyLoginPage({
searchParams,
}: {
searchParams: Promise<ParsedUrlQuery>
}) {
await headers()
const { checkoutUrl, ...checkoutParams } = await searchParams
Copy link
Contributor

Choose a reason for hiding this comment

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

🛠️ Refactor suggestion

searchParams should not be a Promise – remove the unnecessary await
searchParams handed to a Next.js page.tsx component is a plain object. Typing it as a Promise and immediately awaiting it is confusing to readers and can hide type-level mistakes.

-export default async function VerifyLoginPage({
-	searchParams,
-}: {
-	searchParams: Promise<ParsedUrlQuery>
-}) {
-	await headers()
-	const { checkoutUrl, ...checkoutParams } = await searchParams
+export default async function VerifyLoginPage({
+	searchParams,
+}: {
+	searchParams: ParsedUrlQuery
+}) {
+	await headers() // still forces dynamic mode
+	const { checkoutUrl, ...checkoutParams } = searchParams
📝 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
export default async function VerifyLoginPage({
searchParams,
}: {
searchParams: Promise<ParsedUrlQuery>
}) {
await headers()
const { checkoutUrl, ...checkoutParams } = await searchParams
export default async function VerifyLoginPage({
searchParams,
}: {
searchParams: ParsedUrlQuery
}) {
await headers() // still forces dynamic mode
const { checkoutUrl, ...checkoutParams } = searchParams
🤖 Prompt for AI Agents
In apps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx around
lines 24 to 30, the searchParams parameter is incorrectly typed as a Promise and
awaited, which is unnecessary because Next.js passes searchParams as a plain
object. Remove the Promise type from searchParams and eliminate the await
keyword when accessing it to correctly reflect its synchronous nature.

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.

2 participants