Conversation
WalkthroughThis pull request introduces a new feature to check team plan availability across the billing system. The changes span three files in the frontend: Changes
Possibly related PRs
Poem
🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
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 (
|
Deploying maple with
|
| Latest commit: |
521a2fa
|
| Status: | ✅ Deploy successful! |
| Preview URL: | https://0decc4c5.maple-ca8.pages.dev |
| Branch Preview URL: | https://team-pricing.maple-ca8.pages.dev |
a78fcc0 to
799d55b
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
frontend/src/billing/billingApi.ts (1)
183-210: Enhance error handling specificity.While the error handling is consistent with other API functions, consider providing more specific error messages for different failure scenarios.
Apply this diff to improve error messages:
if (!response.ok) { const errorText = await response.text(); console.error("Team plan availability error response:", errorText); if (response.status === 401) { throw new Error("Unauthorized"); + } else if (response.status === 404) { + throw new Error("Team plan availability endpoint not found"); + } else if (response.status === 503) { + throw new Error("Team plan availability service is temporarily unavailable"); } throw new Error(`Failed to check team plan availability: ${errorText}`); }frontend/src/routes/pricing.tsx (2)
270-270: Extract email address to a constant.The support email address should be extracted to a constant to maintain consistency and ease updates.
Add this at the top of the file:
+const SUPPORT_EMAIL = "support@opensecret.cloud";Then update the usage:
- window.location.href = "mailto:support@opensecret.cloud"; + window.location.href = `mailto:${SUPPORT_EMAIL}`;
Line range hint
439-474: Extract price calculation logic.The yearly Bitcoin price calculation logic should be extracted into a separate function for better maintainability.
Consider adding this utility function:
const calculatePrices = (product: BillingProduct, useBitcoin: boolean) => { const monthlyOriginalPrice = (product.default_price.unit_amount / 100).toFixed(2); const monthlyDiscountedPrice = ( Math.floor(product.default_price.unit_amount / 2) / 100 ).toFixed(2); // Calculate yearly prices for Bitcoin (10% off) const yearlyDiscountedPrice = ( Math.floor(product.default_price.unit_amount * 12 * 0.9) / 100 ).toFixed(2); // Calculate monthly equivalent of yearly Bitcoin price const monthlyEquivalentPrice = (Number(yearlyDiscountedPrice) / 12).toFixed(2); return { displayOriginalPrice: monthlyOriginalPrice, displayDiscountedPrice: useBitcoin ? monthlyEquivalentPrice : monthlyDiscountedPrice, yearlyDiscountedPrice }; };
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/billing/billingApi.ts(1 hunks)frontend/src/billing/billingService.ts(2 hunks)frontend/src/routes/pricing.tsx(11 hunks)
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (4)
frontend/src/billing/billingService.ts (2)
9-10: LGTM: Clean import addition.The import statement for
fetchTeamPlanAvailableis properly added.
95-97: LGTM: Well-implemented service method.The
getTeamPlanAvailablemethod follows the established pattern of usingexecuteWithTokenfor authentication and proper error handling.frontend/src/routes/pricing.tsx (2)
164-178: LGTM: Well-implemented team plan availability check.The query is properly implemented with error handling and fallback to
falseon error.
558-565: LGTM: Proper button state handling.The button state properly handles both loading and Bitcoin payment restrictions for team plans.
799d55b to
dbcee4d
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (4)
frontend/src/routes/pricing.tsx (4)
265-273: Extract support email as a constant.The support email address is duplicated. Consider extracting it to a constant at the top of the file to maintain DRY principles and make updates easier.
+const SUPPORT_EMAIL = "support@opensecret.cloud"; + function PricingPage() { // ... existing code ... // For team plan, redirect to email if not available if (isTeamPlan && !isTeamPlanAvailable) { - window.location.href = "mailto:support@opensecret.cloud"; + window.location.href = `mailto:${SUPPORT_EMAIL}`; return; }
360-361: Extract grid layout class for reusability.The grid layout class is duplicated across loading, error, and main states. Consider extracting it to a constant or CSS class for better maintainability.
+const PRICING_GRID_CLASS = "w-full max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-4 lg:gap-6 px-4 sm:px-6 lg:px-8"; + // In loading state -<div className="pt-8 w-full max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-4 lg:gap-6 px-4 sm:px-6 lg:px-8"> +<div className={`pt-8 ${PRICING_GRID_CLASS}`}> // In error state -<div className="pt-8 w-full grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-4 lg:gap-6 px-4 sm:px-6 lg:px-8"> +<div className={`pt-8 ${PRICING_GRID_CLASS}`}> // In main state -<div className="pt-8 w-full max-w-7xl mx-auto grid grid-cols-1 md:grid-cols-4 gap-4 md:gap-4 lg:gap-6 px-4 sm:px-6 lg:px-8"> +<div className={`pt-8 ${PRICING_GRID_CLASS}`}>Also applies to: 385-385, 440-440
Line range hint
449-519: Improve readability of pricing display logic.The pricing display logic uses complex nested ternaries and has magic numbers. Consider these improvements:
- Extract discount percentages as constants.
- Split the complex conditional rendering into separate components or functions.
+const BITCOIN_DISCOUNT = 0.9; // 10% off +const REGULAR_DISCOUNT = 0.5; // 50% off + +const getPricingDisplay = (product, useBitcoin) => { + if (product.name === "Team" && useBitcoin) { + return "Team plan is not available with Bitcoin payment."; + } + return product.description; +}; + // In the component -{product.name === "Team" && useBitcoin - ? "Team plan is not available with Bitcoin payment." - : product.description} +{getPricingDisplay(product, useBitcoin)}
559-570: Simplify button state handling.The button state logic is complex with multiple conditions. Consider extracting the logic into helper functions for better readability and maintainability.
+const isButtonDisabled = (productId, loadingProductId, isTeamPlan, useBitcoin) => { + return loadingProductId === productId || (useBitcoin && isTeamPlan); +}; + +const getButtonClassName = (isTeamPlan, isTeamPlanAvailable, baseClassName) => { + return `${baseClassName} ${ + isTeamPlan && !isTeamPlanAvailable + ? "!opacity-100 !cursor-pointer hover:!bg-white/70" + : "" + }`; +}; + <button onClick={() => handleButtonClick(product)} - disabled={loadingProductId === product.id || (useBitcoin && product.name === "Team")} - className={`w-full bg-white/90 backdrop-blur-sm text-black hover:bg-white/70 active:bg-white/80 px-4 sm:px-8 py-3 sm:py-4 rounded-lg text-lg sm:text-xl font-light transition-all duration-200 shadow-[0_0_25px_rgba(255,255,255,0.25)] hover:shadow-[0_0_35px_rgba(255,255,255,0.35)] disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 group-hover:bg-white ${ - isTeamPlan && !isTeamPlanAvailable ? "!opacity-100 !cursor-pointer hover:!bg-white/70" : "" - }`} + disabled={isButtonDisabled(product.id, loadingProductId, isTeamPlan, useBitcoin)} + className={getButtonClassName( + isTeamPlan, + isTeamPlanAvailable, + "w-full bg-white/90 backdrop-blur-sm text-black hover:bg-white/70 active:bg-white/80 px-4 sm:px-8 py-3 sm:py-4 rounded-lg text-lg sm:text-xl font-light transition-all duration-200 shadow-[0_0_25px_rgba(255,255,255,0.25)] hover:shadow-[0_0_35px_rgba(255,255,255,0.35)] disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2 group-hover:bg-white" + )} >
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/billing/billingApi.ts(1 hunks)frontend/src/billing/billingService.ts(2 hunks)frontend/src/routes/pricing.tsx(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/billing/billingApi.ts
- frontend/src/billing/billingService.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
frontend/src/routes/pricing.tsx (1)
214-225: LGTM! Clear and consistent button text logic.The implementation correctly handles the team plan button text, maintaining consistency with the existing pattern.
dbcee4d to
521a2fa
Compare
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (3)
frontend/src/routes/pricing.tsx (3)
449-449: Refactor price calculations into a separate utility function.The price calculations are duplicated across the component. Consider extracting them into a reusable utility function.
+const calculatePrices = (product: any, useBitcoin: boolean) => { + const monthlyOriginalPrice = (product.default_price.unit_amount / 100).toFixed(2); + const monthlyDiscountedPrice = (Math.floor(product.default_price.unit_amount / 2) / 100).toFixed(2); + const yearlyDiscountedPrice = (Math.floor(product.default_price.unit_amount * 12 * 0.9) / 100).toFixed(2); + const monthlyEquivalentPrice = (Number(yearlyDiscountedPrice) / 12).toFixed(2); + + return { + displayOriginalPrice: monthlyOriginalPrice, + displayDiscountedPrice: useBitcoin ? monthlyEquivalentPrice : monthlyDiscountedPrice, + yearlyDiscountedPrice, + }; +};Also applies to: 451-458
559-566: Add ARIA attributes for better accessibility.The disabled state and pricing information should be properly conveyed to screen readers.
<button onClick={() => handleButtonClick(product)} disabled={loadingProductId === product.id || (useBitcoin && product.name === "Team")} + aria-disabled={loadingProductId === product.id || (useBitcoin && product.name === "Team")} + aria-label={`${product.name} plan - ${getButtonText(product)}`} className={`w-full bg-white/90 backdrop-blur-sm text-black hover:bg-white/70...`} >
214-214: Add type safety for product name checks.Consider using an enum or constant for product names to avoid typos and improve maintainability.
+const PRODUCT_NAMES = { + TEAM: 'team', + FREE: 'free', +} as const; + -const isTeamPlan = targetPlanName.includes("team"); +const isTeamPlan = targetPlanName.includes(PRODUCT_NAMES.TEAM);Also applies to: 265-266
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (3)
frontend/src/billing/billingApi.ts(1 hunks)frontend/src/billing/billingService.ts(2 hunks)frontend/src/routes/pricing.tsx(11 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- frontend/src/billing/billingApi.ts
- frontend/src/billing/billingService.ts
⏰ Context from checks skipped due to timeout of 90000ms (1)
- GitHub Check: Cloudflare Pages
🔇 Additional comments (1)
frontend/src/routes/pricing.tsx (1)
164-177: Enhance error handling for team plan availability check.The current implementation silently fails and defaults to false, which might hide actual issues.
Summary by CodeRabbit
New Features
Bug Fixes
Documentation