Conversation
… yearly subscriptions, tested minimally
|
The latest updates on your projects. Learn more about Vercel for Git ↗︎
|
WalkthroughThis update introduces support for both monthly and yearly subscription types across the application. It adds a Changes
Sequence Diagram(s)sequenceDiagram
participant User
participant UI
participant API
participant DB
participant NWC
User->>UI: Open subscription modal
UI->>UI: Show plan selector (monthly/yearly)
User->>UI: Select plan and initiate payment
UI->>API: PUT /api/users/subscription { subscriptionType, ... }
API->>DB: updateUserSubscription(userId, isSubscribed, nwc, subscriptionType)
DB-->>API: Update Role with subscriptionType
API-->>UI: Success response
UI->>NWC: Initiate payment with amount based on subscriptionType
NWC-->>UI: Payment result
UI-->>User: Show confirmation and updated plan details
sequenceDiagram
participant Cron
participant DB
participant NWC
participant API
Cron->>DB: findExpiredSubscriptions()
DB-->>Cron: List of expired monthly/yearly subscriptions
loop For each expired subscription
Cron->>NWC: Attempt renewal (amount & period based on subscriptionType)
alt Payment success
Cron->>API: updateUserSubscription(userId, true, nwc, subscriptionType)
else Payment failed/missing NWC
Cron->>API: updateUserSubscription(userId, false, nwc, subscriptionType)
end
end
Cron-->>DB: Expire failed subscriptions
Poem
✨ 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.
Actionable comments posted: 2
🔭 Outside diff range comments (1)
src/db/models/userModels.js (1)
168-192:⚠️ Potential issueAdd runtime guard & standardise property assignment
subscriptionTypeis accepted as a free-form string. Persisting an unexpected value will break every piece of logic that relies on the"monthly"/"yearly"dichotomy (cron, pricing, UI toggles, etc.).
You can avoid subtle data corruption by validating the input up-front and by using a uniform “{ set: … }” syntax for update operations:export const updateUserSubscription = async (userId, isSubscribed, nwc, subscriptionType = 'monthly') => { + if (!['monthly', 'yearly'].includes(subscriptionType)) { + throw new Error(`Invalid subscriptionType "${subscriptionType}"`); + } try { const now = new Date(); return await prisma.user.update({ … - lastPaymentAt: isSubscribed ? now : { set: null }, + lastPaymentAt: { set: isSubscribed ? now : null },
🧹 Nitpick comments (13)
src/pages/api/users/subscription/cron.js (1)
58-58: Consider using optional chaining.The static analysis tool suggests using optional chaining for better code safety.
-if (response && response?.preimage) { +if (response?.preimage) {🧰 Tools
🪛 Biome (1.9.4)
[error] 58-58: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/components/profile/subscription/UserSubscription.js (2)
44-44: Consider using optional chaining.The static analysis tool suggests using optional chaining for better code safety.
-if (session && session?.user) { +if (session?.user) {🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
53-53: Consider using optional chaining.The static analysis tool suggests using optional chaining for better code safety.
-if (user && user.role) { +if (user?.role) {🧰 Tools
🪛 Biome (1.9.4)
[error] 53-53: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/db/models/userModels.js (2)
251-267: Dead code – retrieved subscription types are never used
expireUserSubscriptions()fetchessubscriptionTypefor eachuserId, builds thesubscriptionTypesmap, and then discards it. This adds an extra query and memory allocation without any benefit.If you intended to log / audit which type was expired, use the map; otherwise, delete the whole block:
- const subscriptions = await prisma.role.findMany({ … }); - const subscriptionTypes = {}; - subscriptions.forEach(sub => { - subscriptionTypes[sub.userId] = sub.subscriptionType || 'monthly'; - });
196-202: Connection management inconsistencyThis function (and two others below) calls
prisma.$disconnect()in afinallyblock, whereas all read/update helpers above keep the connection open.
Frequent connects/disconnects can exhaust connection pools in serverless environments and slow every request.Align the strategy: either always disconnect at the API-layer boundary or rely on a singleton Prisma client that manages its own lifecycle.
src/components/profile/subscription/SubscribeModal.js (2)
259-282: Hard-coded price strings hinder future pricing changesDisplaying
"50,000"/"500,000"directly couples UI to pricing logic. A single forgotten magic number could produce inconsistent pricing displays when marketing runs promotions.Expose a constant or utility function that derives the display amount from the same source used for invoice generation:
import { getAmountForPlan } from '@/utils/subscriptionPricing'; … <div className="price-display …"> {getAmountForPlan(subscriptionType).toLocaleString()} sats
177-187: Copy text still defaults to “monthly”The fallback
|| 'monthly'in the status messages will incorrectly label yearly subscribers ifsubscriptionTypeis accidentallynullbut the duration is 365 days.
Prefer showing an explicit “unknown” or—better—failing early whensubscriptionTypeis not set, so that data issues surface during QA.yearly_subscriptions.md (1)
193-195: Minor grammar – missing article“consider grandfathering existing subscribers or offering an upgrade path”
🧰 Tools
🪛 LanguageTool
[uncategorized] ~195-~195: You might be missing the article “an” here.
Context: ...hering existing subscribers or offering upgrade path(AI_EN_LECTOR_MISSING_DETERMINER_AN)
src/components/ui/Modal.js (4)
48-63: Width handling could be more robust.The width handling logic is functional, but lacks validation for custom width values. Consider adding validation to ensure that custom width values are valid CSS dimensions.
// Determine width class let widthClass = ''; if (width === 'fit') { widthClass = 'w-fit'; } else if (width === 'full') { widthClass = 'w-full'; } else { // Custom width will be handled via style + // Ensure width is a valid CSS dimension + const isValidDimension = /^\d+(%|px|rem|em|vh|vw)?$/.test(width); + if (isValidDimension) { style.width = width; + } else { + console.warn(`Invalid width value: ${width}. Using default width.`); + } }
77-84: Global CSS could cause style conflicts.Using global CSS with generic selectors could affect other PrimeReact Dialog components in the application. Consider using a more targeted approach or component-specific styles.
- // Apply tailwind CSS to modify dialog elements - const dialogClassNames = ` - .p-dialog-footer { - background-color: #1f2937 !important; - border-top: 1px solid #374151 !important; - } - `; + // Use a unique class for styling this specific modal type + const modalClassName = `dark-modal-${Math.random().toString(36).substr(2, 9)}`; + const dialogClassNames = ` + .${modalClassName} .p-dialog-footer { + background-color: #1f2937 !important; + border-top: 1px solid #374151 !important; + } + `;Then update the Dialog className to include this unique class:
<Dialog header={header} visible={visible} onHide={onHide} - className={`p-fluid pb-0 ${widthClass} ${className}`} + className={`p-fluid pb-0 ${widthClass} ${modalClassName} ${className}`} style={{ ...baseStyle, ...style }}
86-109: Consider enhancing accessibility attributes.While the underlying Dialog component may handle some accessibility features, explicitly adding ARIA attributes would improve the component's accessibility.
<Dialog header={header} visible={visible} onHide={onHide} className={`p-fluid pb-0 ${widthClass} ${className}`} style={{ ...baseStyle, ...style }} headerStyle={{ ...baseHeaderStyle, ...headerStyle }} contentStyle={{ ...baseContentStyle, ...contentStyle }} footerStyle={{ ...baseFooterStyle, ...footerStyle }} footer={footerContent} breakpoints={breakpoints} modal={modal} draggable={draggable} resizable={resizable} maximizable={maximizable} dismissableMask={dismissableMask} + aria-modal={true} + aria-labelledby={header ? 'modal-header' : undefined} {...otherProps} > + {header && <span id="modal-header" className="sr-only">{header}</span>} {children} </Dialog>
77-108: Performance optimization for global CSS.The global CSS is recreated on each render. Consider defining it once outside the component for better performance.
+// Define global styles outside the component to prevent recreation on every render +const dialogClassNames = ` + .p-dialog-footer { + background-color: #1f2937 !important; + border-top: 1px solid #374151 !important; + } +`; const Modal = ({ header, visible, onHide, children, className = '', style = {}, width = 'fit', footer, headerStyle = {}, contentStyle = {}, footerStyle = {}, breakpoints, modal, draggable, resizable, maximizable, dismissableMask, showCloseButton = false, ...otherProps }) => { // Base dark styling const baseStyle = { backgroundColor: '#1f2937' }; const baseHeaderStyle = { backgroundColor: '#1f2937', color: 'white' }; const baseContentStyle = { backgroundColor: '#1f2937' }; const baseFooterStyle = { backgroundColor: '#1f2937', borderTop: '1px solid #374151' }; // Determine width class let widthClass = ''; if (width === 'fit') { widthClass = 'w-fit'; } else if (width === 'full') { widthClass = 'w-full'; } else { // Custom width will be handled via style style.width = width; } // Create footer with close button if requested const footerContent = showCloseButton ? ( <div className="flex justify-end w-full"> <Button label="Close" icon="pi pi-times" onClick={onHide} className="p-button-text text-white" /> </div> ) : footer; - // Apply tailwind CSS to modify dialog elements - const dialogClassNames = ` - .p-dialog-footer { - background-color: #1f2937 !important; - border-top: 1px solid #374151 !important; - } - `; return ( <> <style jsx global>{dialogClassNames}</style>prisma/schema.prisma (1)
86-86: Consider definingsubscriptionTypeas a Prisma enum
Using a rawStringpermits invalid values at runtime. Introducing an enum enforces the allowed set (monthly,yearly) at the schema and client level:enum SubscriptionType { monthly yearly } model Role { subscriptionType SubscriptionType @default(monthly) // ... }
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (24)
prisma/migrations/20250514143724_add_subscription_type/migration.sql(1 hunks)prisma/migrations/migration_lock.toml(1 hunks)prisma/schema.prisma(1 hunks)src/components/MoreInfo.js(2 hunks)src/components/bitcoinConnect/CoursePaymentButton.js(3 hunks)src/components/bitcoinConnect/ResourcePaymentButton.js(3 hunks)src/components/bitcoinConnect/SubscriptionPaymentButton.js(12 hunks)src/components/forms/course/LessonSelector.js(2 hunks)src/components/onboarding/WelcomeModal.js(3 hunks)src/components/profile/UserBadges.js(3 hunks)src/components/profile/UserProfileCard.js(2 hunks)src/components/profile/subscription/CalendlyEmbed.js(3 hunks)src/components/profile/subscription/CancelSubscription.js(3 hunks)src/components/profile/subscription/LightningAddressForm.js(3 hunks)src/components/profile/subscription/Nip05Form.js(3 hunks)src/components/profile/subscription/RenewSubscription.js(3 hunks)src/components/profile/subscription/SubscribeModal.js(7 hunks)src/components/profile/subscription/UserSubscription.js(8 hunks)src/components/ui/Modal.js(1 hunks)src/db/models/roleModels.js(1 hunks)src/db/models/userModels.js(5 hunks)src/pages/api/users/subscription/cron.js(1 hunks)src/pages/api/users/subscription/index.js(1 hunks)yearly_subscriptions.md(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (4)
src/components/profile/subscription/LightningAddressForm.js (1)
src/components/ui/Modal.js (1)
Modal(27-110)
src/components/profile/subscription/RenewSubscription.js (1)
src/components/ui/Modal.js (1)
Modal(27-110)
src/components/forms/course/LessonSelector.js (4)
src/components/ui/Modal.js (1)
Modal(27-110)src/components/forms/course/embedded/EmbeddedDocumentForm.js (1)
EmbeddedDocumentForm(15-248)src/components/forms/course/CourseForm.js (1)
isPaidCourse(21-21)src/components/forms/course/embedded/EmbeddedVideoForm.js (1)
EmbeddedVideoForm(12-228)
src/components/profile/subscription/UserSubscription.js (12)
src/components/profile/subscription/SubscribeModal.js (3)
subscriptionType(37-37)subscriptionOptions(39-42)session(23-23)src/components/profile/subscription/Nip05Form.js (2)
session(18-18)windowWidth(17-17)src/components/profile/subscription/LightningAddressForm.js (2)
session(25-25)windowWidth(24-24)src/components/bitcoinConnect/SubscriptionPaymentButton.js (3)
session(34-34)getAmount(40-42)SubscriptionPaymentButtons(20-351)src/components/navbar/Navbar.js (2)
session(20-20)windowWidth(18-18)src/components/feeds/MessageInput.js (1)
session(17-17)src/pages/details/[slug]/index.js (1)
session(28-28)src/components/profile/UserProfile.js (3)
session(19-19)user(17-17)windowWidth(16-16)src/components/profile/UserContent.js (3)
session(27-27)user(28-28)windowWidth(26-26)src/pages/api/users/subscription/cron.js (1)
getAmount(12-15)src/components/profile/subscription/CalendlyEmbed.js (1)
windowWidth(7-7)src/components/profile/UserProfileCard.js (1)
windowWidth(20-20)
🪛 Biome (1.9.4)
src/components/profile/subscription/UserSubscription.js
[error] 44-44: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 53-53: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/pages/api/users/subscription/cron.js
[error] 58-58: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/components/profile/subscription/SubscribeModal.js
[error] 45-45: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🪛 LanguageTool
yearly_subscriptions.md
[grammar] ~22-~22: Using ‘plenty’ without ‘of’ is considered to be informal.
Context: ...dd subscription type parameter - Modify amount calculation based on subscription type - Update NWC...
(PLENTY_OF_NOUNS)
[style] ~131-~131: This adverb was used twice in the sentence. Consider removing one of them or replacing them with a synonym.
Context: ...Subscription UI** - Verify selecting yearly plan updates all UI elements - Check...
(ADVERB_REPETITION_PREMIUM)
[uncategorized] ~163-~163: You might be missing the article “a” here.
Context: ...ctive Yearly Subscription** - Set up test account with yearly subscription - V...
(AI_EN_LECTOR_MISSING_DETERMINER_A)
[uncategorized] ~167-~167: You might be missing the article “a” here.
Context: ...ired Monthly Subscription** - Create test account with monthly subscription - ...
(AI_EN_LECTOR_MISSING_DETERMINER_A)
[uncategorized] ~168-~168: You might be missing the article “the” here.
Context: ...nthly subscription - Manually adjust lastPaymentAt date to be >30 days ago - Run cron j...
(AI_EN_LECTOR_MISSING_DETERMINER_THE)
[uncategorized] ~169-~169: You might be missing the article “the” here.
Context: ...0 days ago - Run cron job and verify subscription is expired 4. **Expired Yearly Subscri...
(AI_EN_LECTOR_MISSING_DETERMINER_THE)
[uncategorized] ~172-~172: You might be missing the article “a” here.
Context: ...pired Yearly Subscription** - Create test account with yearly subscription - M...
(AI_EN_LECTOR_MISSING_DETERMINER_A)
[uncategorized] ~172-~172: You might be missing the article “a” here.
Context: ...ription** - Create test account with yearly subscription - Manually adjust lastP...
(AI_EN_LECTOR_MISSING_DETERMINER_A)
[uncategorized] ~173-~173: You might be missing the article “the” here.
Context: ...early subscription - Manually adjust lastPaymentAt date to be >365 days ago - Run cron ...
(AI_EN_LECTOR_MISSING_DETERMINER_THE)
[uncategorized] ~174-~174: You might be missing the article “the” here.
Context: ...5 days ago - Run cron job and verify subscription is expired 5. Auto-renewal Testing...
(AI_EN_LECTOR_MISSING_DETERMINER_THE)
[uncategorized] ~195-~195: You might be missing the article “an” here.
Context: ...hering existing subscribers or offering upgrade path
(AI_EN_LECTOR_MISSING_DETERMINER_AN)
🔇 Additional comments (73)
src/db/models/roleModels.js (1)
9-9: Good implementation ofsubscriptionTypein role creation.The added field correctly uses the provided value or falls back to 'monthly', maintaining backward compatibility with existing code that doesn't specify a subscription type.
src/pages/api/users/subscription/index.js (1)
15-16: API endpoint updated to support subscription types.The endpoint now extracts an optional
subscriptionTypeparameter from the request body with a default value of'monthly'and passes it to theupdateUserSubscriptionfunction. This implementation ensures backward compatibility with clients that don't specify a subscription type.src/components/profile/subscription/LightningAddressForm.js (2)
2-2: Modal component import updated.The Dialog component from PrimeReact has been replaced with a custom Modal component for a more consistent UI.
114-119: Modal usage and props updated.The Dialog component has been replaced with the custom Modal component and the width prop format has been updated to use the direct width property instead of a style object.
src/components/profile/UserProfileCard.js (2)
4-4: Modal component import updated.The Dialog component from PrimeReact has been replaced with a custom Modal component for a more consistent UI.
287-293: Modal props restructured.The Dialog component has been replaced with the custom Modal component, and the props have been restructured:
- Added
width="full"prop- Moved the max-width constraint to the className
- Removed the modal prop (now handled internally by the Modal component)
This change is consistent with the new Modal component's API.
src/components/profile/subscription/CancelSubscription.js (2)
2-2: Modal component import updated.The Dialog component from PrimeReact has been replaced with a custom Modal component for a more consistent UI.
34-38: Modal component usage updated.The Dialog component has been replaced with the custom Modal component while preserving the same functionality and props structure.
src/components/bitcoinConnect/CoursePaymentButton.js (2)
3-3: Import updated to use custom Modal componentThe import has been updated to use the custom Modal component from
@/components/ui/Modalinstead of the PrimeReact Dialog component.
230-246: Modal implementation looks goodThe Dialog component has been replaced with the custom Modal component, maintaining the same functionality while adopting the standardized modal UI. The width property is now directly passed as a prop rather than in a style object, which aligns with the Modal component's API.
src/components/profile/subscription/RenewSubscription.js (2)
2-2: Import updated to use custom Modal componentThe import has been updated to use the custom Modal component from
@/components/ui/Modalinstead of the PrimeReact Dialog component.
53-83: Modal implementation looks goodThe Dialog component has been replaced with the custom Modal component, maintaining the same functionality. The className prop that might have been present in the previous Dialog implementation has been removed, which is consistent with the Modal component's styling approach that applies base styles automatically.
src/components/profile/subscription/Nip05Form.js (2)
2-2: Import updated to use custom Modal componentThe import has been updated to use the custom Modal component from
@/components/ui/Modalinstead of the PrimeReact Dialog component.
85-90: Modal implementation looks goodThe Dialog component has been replaced with the custom Modal component. The width property is now directly passed as a prop with the same responsive logic based on windowWidth, which aligns with the Modal component's API.
src/components/MoreInfo.js (2)
2-2: Import updated to use custom Modal componentThe import has been updated to use the custom Modal component from
@/components/ui/Modalinstead of the PrimeReact Dialog component.
27-35: Modal implementation looks goodThe Dialog component has been replaced with the custom Modal component while preserving all props and children. This standardizes the modal UI across the application while maintaining the same functionality.
src/components/onboarding/WelcomeModal.js (3)
1-4: Import changes look goodThe change from using PrimeReact's Dialog directly to the custom Modal component keeps the code consistent with the application-wide refactoring.
28-35: Modal component implementation looks goodThe component successfully migrates from Dialog to Modal, appropriately splitting width handling into a dedicated width prop (90vw) and maxWidth style property. This aligns with the new Modal component's API design.
78-78: Closing tag change is correctProperly updated the closing tag to match the new Modal component.
src/components/forms/course/LessonSelector.js (3)
1-8: Import changes look goodThe change from using PrimeReact's Dialog directly to the custom Modal component keeps the code consistent with the application-wide refactoring of subscription-related UI.
236-243: Document form modal implementation looks goodThe component successfully migrates from Dialog to Modal for the document form modal while maintaining all the necessary props. The className for width control is preserved.
245-252: Video form modal implementation looks goodThe component successfully migrates from Dialog to Modal for the video form modal while maintaining all the necessary props. The className for width control is preserved.
src/components/profile/subscription/CalendlyEmbed.js (3)
1-5: Import changes look goodThe change from using PrimeReact's Dialog directly to the custom Modal component aligns with the application-wide UI standardization effort.
28-34: Modal implementation with responsive width looks goodThe component successfully migrates from Dialog to Modal and properly converts the width styling to use the new Modal's width prop. The responsive behavior based on window width is maintained correctly.
40-40: Closing tag change is correctProperly updated the closing tag to match the new Modal component.
src/components/profile/UserBadges.js (3)
1-8: Import changes look goodThe change from using PrimeReact's Dialog directly to the custom Modal component keeps the code consistent with the application-wide refactoring.
102-108: Modal implementation with width props looks goodThe component successfully migrates from Dialog to Modal, with an improved approach to width handling. The width CSS property has been moved to the dedicated width prop ("full"), while max-width remains in the className.
156-156: Closing tag change is correctProperly updated the closing tag to match the new Modal component.
src/components/bitcoinConnect/ResourcePaymentButton.js (2)
3-3: Dialog replaced with custom Modal component.The component now uses a custom Modal component from '@/components/ui/Modal' instead of the Dialog component from primereact.
125-130: Modal implementation updated.The Dialog component has been replaced with the custom Modal component, using a direct width prop instead of a style object. This is consistent with the application-wide refactoring to standardize modal UI.
src/pages/api/users/subscription/cron.js (9)
11-15: Subscription amount helper function added.New utility function to calculate subscription amounts based on subscription type, providing 500 sats for yearly and 50 sats for monthly subscriptions with appropriate comments.
20-31: Enhanced tracking for subscription types.Added detailed comments explaining the expiration periods and a structured stats object to track subscription statistics separately by subscription type.
35-38: Updated subscription processing to handle subscription types.Now destructuring the subscription type from expired subscriptions with a default of 'monthly', and tracking processed subscriptions by type. This ensures backward compatibility with existing data.
41-43: Dynamic subscription amount calculation.Added logging for subscription type and dynamic amount calculation based on the subscription type.
50-56: Enhanced invoice generation with subscription type.Updated invoice generation to include the subscription type in the comment and dynamic amount, with improved logging.
58-64: Updated subscription renewal with type preservation.The subscription renewal now preserves the subscription type when updating the user's subscription status, with typed success tracking.
🧰 Tools
🪛 Biome (1.9.4)
[error] 58-58: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
75-77: Added subscription type tracking for failed renewals.Now tracking failed renewals by subscription type, improving reporting accuracy.
83-86: Enhanced logging with subscription type details.Updated console logging to include detailed breakdowns by subscription type, making it easier to monitor the system's performance.
88-93: Updated API response with detailed subscription statistics.The response now includes a comprehensive breakdown of processed, renewed, and expired subscriptions by type, and returns the full stats object for potential further processing.
src/components/profile/subscription/UserSubscription.js (12)
19-19: Added SelectButton component import.Import for the SelectButton component which will be used to choose between subscription types.
36-41: Added subscription type state and options.New state variable to track the selected subscription type and defined options for the selection UI.
44-49: Initialize subscription type from user data.The subscription type is now initialized from the user's role if available, ensuring consistency between the UI and the user's current subscription.
🧰 Tools
🪛 Biome (1.9.4)
[error] 44-44: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
57-60: Dynamic subscription end date calculation.Updated to calculate the subscription end date based on the subscription type, using 365 days for yearly and 31 days for monthly subscriptions.
74-76: Include subscription type in API updates.Now passing the subscription type to the API when updating the user's subscription status.
114-117: Added subscription amount helper function.New function to calculate the subscription amount based on the subscription type, consistent with the cron job implementation.
183-208: Added subscription plan selection UI.New UI section with a SelectButton for choosing between monthly and yearly plans, displaying pricing and savings information. The UI is well-structured with appropriate styling and responsive design.
216-217: Pass subscription type to payment component.The selected subscription type is now passed to the SubscriptionPaymentButtons component, ensuring consistent payment processing.
228-231: Updated card styling.Minor style adjustments to the subscription benefits card.
241-248: Added current plan display.New UI elements to show the user's current subscription plan type and renewal date, improving user information.
322-327: Updated FAQ for subscription types.Added new FAQ entry explaining the difference between monthly and yearly subscription plans, highlighting the savings benefit.
332-334: Updated subscription duration explanation.Clarified the pay-as-you-go subscription duration to reflect the selected plan type.
src/components/bitcoinConnect/SubscriptionPaymentButton.js (16)
28-29: Added subscription type prop.Added a new prop to support different subscription types with a default of 'monthly'.
39-45: Dynamic amount calculation based on subscription type.Added a utility function to calculate the payment amount based on subscription type and initialized a constant with the calculated amount.
84-85: Updated invoice comment with subscription type.The invoice comment now includes the capitalized subscription type, providing better context for the payment.
96-97: Enhanced payment tracking with subscription type.Updated analytics tracking to include the subscription type for better data insights.
127-130: Updated NWC client configuration.The NWC client now uses the dynamic amount and sets the budget renewal period based on the subscription type.
174-175: Include subscription type in API update.Now passing the subscription type to the API when updating the user's subscription status with a recurring payment.
178-179: Enhanced recurring payment tracking.Updated analytics tracking for recurring subscriptions to include the subscription type.
240-241: Include subscription type in manual NWC setup.Now passing the subscription type to the API when updating the user's subscription status with a manual NWC setup.
244-245: Enhanced manual payment tracking.Updated analytics tracking for manual recurring subscriptions to include the subscription type.
268-269: Improved layout classes.Updated the flex classes to support both row and column layouts based on the layout prop.
272-273: Dynamic amount in button label.Updated the "Pay as you go" button label to show the dynamic amount based on subscription type.
284-285: Updated button styling.Modified button class names to support responsive layouts based on the layout prop.
289-290: Dynamic subscription type in button label.Updated the recurring subscription button label to include the capitalized subscription type.
300-301: Updated button styling.Modified recurring subscription button class names to support responsive layouts.
321-322: Updated NWC instructions.The NWC setup instructions now include the dynamic amount and subscription type for clarity.
345-346: Dynamic amount in payment modal title.Updated the payment modal title to show the dynamic amount based on subscription type.
src/components/ui/Modal.js (5)
1-4: Clean imports with necessary dependencies.The imports are appropriate for creating a modal component that wraps PrimeReact's Dialog and uses Button for the optional close button.
5-26: Well-documented component with comprehensive JSDoc.The JSDoc comments provide excellent documentation for the component, clearly explaining each prop and its purpose.
27-47: Props destructuring with appropriate defaults.The component definition is clean with good default values for optional props.
65-75: Clean conditional footer implementation.The footer logic elegantly handles both custom footers and the optional close button case.
1-112: Overall excellent modal implementation.This Modal component is well-designed, flexible, and provides a clean abstraction over PrimeReact's Dialog with consistent dark styling. It will standardize modal usage across the application while supporting the new subscription UI requirements.
prisma/migrations/20250514143724_add_subscription_type/migration.sql (1)
1-2: Migration addssubscriptionTypecolumn with default value
The script correctly alters theRoletable to include a non-nullablesubscriptionTypecolumn of typeTEXTand backfills existing rows with the'monthly'default. SQL syntax and quoting align with the Prisma schema.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (5)
src/constants/subscriptionPeriods.js (2)
7-9: Consider leap year for YEARLY periodThe yearly period is set to exactly 365 days, which doesn't account for leap years. While the buffer will likely make this a non-issue in most cases, you might consider adding a comment or using a more precise calculation for sensitive timing requirements.
23-24: Consider adding type validation for subscriptionType parameterWhile the conditional checks handle 'yearly' vs non-yearly subscriptions, consider adding explicit validation for the subscriptionType parameter to catch potential bugs if an invalid value is passed.
export const hasSubscriptionExpired = (lastPaymentDate, subscriptionType) => { if (!lastPaymentDate) return true; + + if (subscriptionType !== 'monthly' && subscriptionType !== 'yearly') { + console.warn(`Invalid subscriptionType: ${subscriptionType}, defaulting to 'monthly'`); + subscriptionType = 'monthly'; + } const now = new Date();src/db/models/userModels.js (1)
255-272: Subscription type map is created but not usedYou're fetching subscription types and creating a map (subscriptionTypes), but it doesn't appear to be used in the subsequent update operations. This could be cleaned up if not needed.
- // First, get the subscription types for each userId - const subscriptions = await prisma.role.findMany({ - where: { - userId: { in: userIds }, - }, - select: { - userId: true, - subscriptionType: true, - }, - }); - - // Create a map of userId to subscription type - const subscriptionTypes = {}; - subscriptions.forEach(sub => { - subscriptionTypes[sub.userId] = sub.subscriptionType || 'monthly'; - });Or if there's a specific reason to preserve this code, consider adding a more descriptive comment explaining its purpose for future maintainers.
src/components/profile/subscription/SubscribeModal.js (2)
45-68: Improve optional chaining in user property accessThe user object access should use optional chaining to avoid potential runtime errors if the user or user.role is undefined.
useEffect(() => { - if (user && user.role) { + if (user?.role) { setSubscribed(user.role.subscribed); setSubscriptionType(user.role.subscriptionType || 'monthly');🧰 Tools
🪛 Biome (1.9.4)
[error] 46-46: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
280-282: Calculate savings percentage dynamicallyThe savings message for yearly subscriptions shows "~17%" as a hardcoded value. Consider calculating this dynamically based on the actual prices to ensure it remains accurate if prices change.
{subscriptionType === 'yearly' && ( <div className="savings-message text-sm text-green-500 font-semibold mt-2"> - Save ~17% with yearly subscription! + Save {Math.round((1 - (500000 / (50000 * 12))) * 100)}% with yearly subscription! </div> )}
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
src/components/profile/subscription/SubscribeModal.js(7 hunks)src/constants/subscriptionPeriods.js(1 hunks)src/db/models/userModels.js(6 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
src/constants/subscriptionPeriods.js (1)
src/components/profile/subscription/SubscribeModal.js (1)
subscriptionType(38-38)
src/db/models/userModels.js (1)
src/constants/subscriptionPeriods.js (2)
SUBSCRIPTION_PERIODS(2-11)SUBSCRIPTION_PERIODS(2-11)
🪛 Biome (1.9.4)
src/components/profile/subscription/SubscribeModal.js
[error] 46-46: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (18)
src/constants/subscriptionPeriods.js (3)
1-11: SUBSCRIPTION_PERIODS constants are well-defined.The structure defined here provides a clear, centralized location for subscription-related constants, which will help maintain consistency across the application. This is a good approach for ensuring that subscription periods are uniform throughout the codebase.
13-20: Well-designed helper function for expiration date calculationThe
calculateExpirationDatefunction properly encapsulates the logic for determining when a subscription expires, making it reusable across the application. This improves consistency and maintainability.
22-36: Robust subscription expiration check with proper null handlingThe
hasSubscriptionExpiredfunction includes appropriate null checking for thelastPaymentDateand cleanly handles the subscription type selection. The buffer time inclusion is a good practice for avoiding edge cases around expiration timing.src/db/models/userModels.js (7)
1-2: Good import of shared subscription constantsImporting the centralized subscription period constants ensures consistency across the application and follows the DRY principle. This is the right approach for maintaining these values.
169-179: Appropriate default for optional subscriptionType parameterThe
updateUserSubscriptionfunction now accepts an optionalsubscriptionTypeparameter defaulting to 'monthly', which is a good backward compatible approach for supporting the new subscription types.
179-180: Properly handling subscriptionType in both create and update operationsThe function correctly applies the subscription type to both the create and update operations within the upsert, ensuring consistency in how the subscriptionType is stored.
Also applies to: 187-188
205-219: Well-implemented expiration calculation using shared constantsThe function now uses the shared
SUBSCRIPTION_PERIODSconstants to calculate expiration dates for both monthly and yearly subscriptions, which ensures consistency with other parts of the application.
221-235: Proper query structure for handling multiple subscription typesThe query structure using
ORconditions is the right approach for handling different subscription types with different expiration periods. This implementation will correctly identify expired subscriptions of both types.
239-240: Good inclusion of subscriptionType in the returned dataIncluding the
subscriptionTypein the returned data allows downstream functions to handle different subscription types appropriately.
282-284: Good practice to preserve subscription typePreserving the subscription type when a subscription expires is a good practice for historical data and makes renewal easier. The comment clearly explains this design decision.
src/components/profile/subscription/SubscribeModal.js (8)
1-2: Good module imports for subscription functionalityThe imports of the Modal component and subscription-related utilities (SelectButton and calculateExpirationDate) are appropriate for implementing the subscription type selection feature.
Also applies to: 20-21
38-43: Well-structured subscription type state and optionsThe initialization of subscription type state and definition of subscription options is clean and follows React best practices. The options structure works well with PrimeReact's SelectButton component.
51-61: Good null handling for lastPaymentAtThe code properly checks for the existence of
lastPaymentAtbefore using it to calculate dates, which prevents errors when dealing with new or expired subscriptions.
54-56: Proper use of shared helper functionUsing the imported
calculateExpirationDatehelper function ensures consistency in how expiration dates are calculated across the application.
70-77: Subscription type correctly included in API requestThe function properly includes the selected subscription type when making the API request to update the subscription, ensuring the backend receives this information.
185-186: Dynamic subscription renewal messagingThe renewal messages now dynamically show the subscription type and handle the case where no dates are available with 'N/A'. This improves the user experience by providing accurate information.
Also applies to: 194-195
266-291: Clearly structured subscription plan selector UIThe subscription plan selector UI is well-organized and provides clear feedback to the user about their selection, including pricing and savings information for yearly plans.
293-302: Subscription type correctly passed to payment componentThe
subscriptionTypeprop is correctly passed to theSubscriptionPaymentButtonscomponent, allowing it to handle different payment amounts and behavior based on the selected plan.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
src/components/profile/subscription/UserSubscription.js (2)
45-51: Consider using optional chainingThe conditional check can be simplified using optional chaining.
- if (session && session?.user) { + if (session?.user) {🧰 Tools
🪛 Biome (1.9.4)
[error] 45-45: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
54-67: Consider using optional chaining here tooSimilar to the previous suggestion, this conditional can be simplified.
- if (user && user.role) { + if (user?.role) {Additionally, the code correctly uses the shared
calculateExpirationDatehelper function, which improves code consistency across the application.🧰 Tools
🪛 Biome (1.9.4)
[error] 54-54: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
src/components/profile/subscription/UserSubscription.js(8 hunks)src/pages/about.js(2 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (1)
src/components/profile/subscription/UserSubscription.js (3)
src/components/profile/subscription/SubscribeModal.js (1)
subscriptionType(38-38)src/components/bitcoinConnect/SubscriptionPaymentButton.js (2)
getAmount(40-42)SubscriptionPaymentButtons(20-351)src/pages/api/users/subscription/cron.js (1)
getAmount(12-15)
🪛 Biome (1.9.4)
src/components/profile/subscription/UserSubscription.js
[error] 45-45: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 54-54: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (14)
src/pages/about.js (2)
21-21: Good addition of centralized subscription period constants.Adding the import for
SUBSCRIPTION_PERIODSaligns with good software engineering practices by centralizing configuration values.
121-121: Well-implemented refactoring to support multiple subscription types.Replacing the hardcoded 31 days with
SUBSCRIPTION_PERIODS.MONTHLY.DAYSimproves maintainability and consistency across the application. This change properly supports the new functionality for different subscription types (monthly and yearly).src/components/profile/subscription/UserSubscription.js (12)
19-20: Good addition of necessary importsThe imports for
SelectButtonand the subscription period constants are correctly added to support the new subscription type functionality.
37-42: Well-structured subscription plan implementationGood implementation of subscription type state and options. The default 'monthly' value ensures backward compatibility with existing users.
47-49: Good initialization from user dataThis correctly initializes the subscription type from the user's existing subscription, maintaining state consistency.
81-81: API update to include subscription typeGood update to include the subscription type in the API call, ensuring the backend can properly handle the different subscription durations.
120-123: Consistent price calculationThe
getAmountfunction correctly returns 500 for yearly and 50 for monthly subscriptions, which is consistent with the values used in the relevant code snippets from other files.
168-187: Improved iconography and spacingNice adjustments to icon margins to improve visual consistency and readability of the subscription benefits list.
189-214: Well-designed subscription plan selectorThe subscription plan selector is well-implemented with:
- Clear UI for toggling between monthly and yearly plans
- Visual indication of savings for yearly subscriptions (~17%)
- Clear price display showing the amount and plan duration
This makes it easy for users to understand their options and the value proposition of each plan.
222-222: Proper integration with payment componentCorrectly passing the
subscriptionTypeto theSubscriptionPaymentButtonscomponent ensures that payments are processed with the correct amount and duration.
247-254: Improved subscription information displayGood addition of the current plan type and renewal date information. This helps users understand their subscription status more clearly.
318-333: Clear subscription plan differentiation in FAQThe added FAQ item about the difference between monthly and yearly plans is helpful for users to understand the value proposition of each option.
338-339: Updated subscription duration informationGood update to clarify that the subscription duration depends on the selected plan (monthly or yearly).
346-346: Clear recurring payment schedule informationGood update to clarify that recurring payments follow the chosen schedule (monthly or yearly).
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
src/pages/api/users/subscription/cron.js (1)
58-58: Fix inconsistent optional chaining.There's inconsistent use of optional chaining in the condition check.
-if (response && response?.preimage) { +if (response?.preimage) {🧰 Tools
🪛 Biome (1.9.4)
[error] 58-58: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/components/profile/subscription/UserSubscription.js (2)
45-50: Fix inconsistent optional chaining.There's inconsistent use of optional chaining in this condition.
-if (session && session?.user) { +if (session?.user) {🧰 Tools
🪛 Biome (1.9.4)
[error] 45-45: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
54-67: Consider using optional chaining consistently.The condition checks could be more consistent with optional chaining patterns.
-if (user && user.role) { +if (user?.role) {The implementation of dynamic expiration date calculation is good, properly using the shared helper function based on subscription type.
🧰 Tools
🪛 Biome (1.9.4)
[error] 54-54: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
📜 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)
src/components/bitcoinConnect/SubscriptionPaymentButton.js(12 hunks)src/components/profile/subscription/UserSubscription.js(7 hunks)src/pages/api/users/subscription/cron.js(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- src/components/bitcoinConnect/SubscriptionPaymentButton.js
🧰 Additional context used
🪛 Biome (1.9.4)
src/pages/api/users/subscription/cron.js
[error] 58-58: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
src/components/profile/subscription/UserSubscription.js
[error] 45-45: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
[error] 54-54: Change to an optional chain.
Unsafe fix: Change to an optional chain.
(lint/complexity/useOptionalChain)
🔇 Additional comments (12)
src/pages/api/users/subscription/cron.js (6)
11-15: Good implementation of the subscription type-based pricing.The helper function is well-designed, providing clear pricing differentiation between subscription types with appropriate comments about the savings percentage.
20-31: Well-structured statistics tracking.The implementation properly handles both subscription types with clear comments about different expiration periods and a well-organized statistics object for tracking metrics.
35-38: LGTM! Defaulting to monthly subscription is a good failsafe.The destructuring with default value for subscriptionType provides backward compatibility for existing users without a specified type.
50-53: Nice dynamic invoice comment formatting.The code properly formats the subscription type with the first letter capitalized, creating professional-looking invoice comments.
59-64: Good implementation of subscription renewal with type preservation.The code correctly passes the subscription type to the updateUserSubscription function, maintaining the user's selected plan.
83-92: Well-detailed statistics reporting.The logging and response JSON provide clear insights into subscription performance metrics broken down by type, which will be valuable for monitoring the effectiveness of the yearly subscription option.
src/components/profile/subscription/UserSubscription.js (6)
19-20: Good implementation of required imports.The addition of SelectButton and subscription period utilities provides the necessary components for the new subscription type functionality.
37-42: Well-structured subscription options.The state initialization and options definition for subscription types is clear and follows React best practices.
75-95: Properly updated API call with subscription type.The subscription success handler correctly includes the selected subscription type in the API request, ensuring proper persisting of the user's choice.
184-209: Excellent subscription plan selector UI.The subscription plan selector is well-implemented with:
- Clear visual distinction between plans
- Informative savings message for yearly subscriptions
- Price display with appropriate context
- Good responsive design considerations
The 17% savings message aligns with the actual pricing (500,000 vs 12 × 50,000).
242-249: Good display of current subscription details.The current plan section clearly shows the subscription type and renewal date, providing users with essential information about their subscription status.
322-328: Clear FAQ about subscription differences.The FAQ explanation about the difference between monthly and yearly plans is informative and accurately reflects the pricing structure.
Summary by CodeRabbit
New Features
Enhancements
Documentation