-
Notifications
You must be signed in to change notification settings - Fork 34
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
Conversation
The latest updates on your projects. Learn more about Vercel for Git ↗︎
1 Skipped Deployment
|
Caution Review failedThe pull request is closed. WalkthroughThe 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
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
Poem
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
Scope: all 3 workspace projects 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, WindsurfCodeRabbit 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. Note ⚡️ Faster reviews with cachingCodeRabbit 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 📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (4)
✨ Finishing Touches
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed 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)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this 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
📒 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 deprecatedsendNotifications
sendNotifications
was deprecated in Calendar v3 in favor of the richersendUpdates
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 thesendUpdates
enum. ThesendNotifications
parameter is used when creating or updating events to specify whether to send notifications to attendees. ThesendUpdates
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 permissionsSetting
guestsCanInviteOthers
andguestsCanSeeOtherGuests
tofalse
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 logicThe
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.
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) | ||
} |
There was a problem hiding this comment.
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.
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.
const eventResourceId = product.resources?.find( | ||
(resource) => resource.resource?.type === 'event', | ||
)?.resource.id | ||
|
||
const eventResource = await step.run(`get event resource`, async () => { | ||
return getEvent(eventResourceId) | ||
}) | ||
|
There was a problem hiding this comment.
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.
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.
There was a problem hiding this 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 anevent
-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 deployingDebug statements should be removed from production code.
-console.log( - 'eventResource?.fields.calendarId', - eventResource?.fields.calendarId, -)
97-99
: Remove redundant validation checkThis 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 implementationThere'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 functionAdding 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
📒 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
} else { | ||
// send a slack message or something because it seems broken | ||
} |
There was a problem hiding this comment.
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.
} 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.
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, | ||
) | ||
}) | ||
} |
There was a problem hiding this comment.
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.
…n pages for logged-in users
There was a problem hiding this 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?.userapps/epicdev-ai/src/app/(commerce)/subscribe/verify-login/page.tsx (4)
7-7
: Remove unusedconfig
import to avoid linter warnings
config
is imported but never referenced in this file. Dead imports usually trip ESLint’sno-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
Callingheaders()
without using the returned value is a no-op. Because the page is already flagged asforce-dynamic
, you can safely remove this line unless you intend to read a header later.
39-44
: Remove debuggingconsole.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
📒 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.
searchParams: Promise<ParsedUrlQuery> | ||
}) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
apps/epicdev-ai/src/app/(commerce)/subscribe/logged-in/page.tsx
Outdated
Show resolved
Hide resolved
const product = await getProduct(checkoutParams.productId as string) | ||
|
||
let callbackUrl = `${env.COURSEBUILDER_URL}/subscribe/logged-in` |
There was a problem hiding this comment.
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.
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.
const providers = getProviders() | ||
const csrfToken = await getCsrf() | ||
const product = await getProduct(checkoutParams.productId as string) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
const checkoutSearchParams = new URLSearchParams( | ||
checkoutParams as Record<string, string>, | ||
) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
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.
const parsedCheckoutParams = CheckoutParamsSchema.safeParse(checkoutParams) | ||
|
||
console.dir({ parsedCheckoutParams }, { depth: null }) | ||
|
||
if (!parsedCheckoutParams.success) { | ||
return redirect('/login') | ||
} |
There was a problem hiding this comment.
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.
export default async function VerifyLoginPage({ | ||
searchParams, | ||
}: { | ||
searchParams: Promise<ParsedUrlQuery> | ||
}) { | ||
await headers() | ||
const { checkoutUrl, ...checkoutParams } = await searchParams |
There was a problem hiding this comment.
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.
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.
…or ticket purchases
Summary by CodeRabbit