') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ', 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })(); add pricing calculator by KMKoushik · Pull Request #214 · usesend/useSend · GitHub
Skip to content

add pricing calculator - #214

Merged
KMKoushik merged 3 commits into
mainfrom
km/2025-09-06-landing-page-update
Sep 6, 2025
Merged

add pricing calculator#214
KMKoushik merged 3 commits into
mainfrom
km/2025-09-06-landing-page-update

Conversation

@KMKoushik

@KMKoushikKMKoushik commented Sep 5, 2025

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added an interactive Pricing Calculator, SiteFooter, and live Status badge.
  • Enhancements
    • Replaced hero images with optimized light/dark WEBP variants.
    • Feature cards now show theme-aware light/dark images.
    • GitHub links updated to point to the usesend/usesend repo.
    • Refreshed pricing copy for clearer messaging.
  • Style
    • Adjusted card foreground colors for improved readability in both themes.

@vercel

vercelBot commented Sep 5, 2025

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

ProjectDeploymentPreviewCommentsUpdated (UTC)
unsend-marketingReadyReadyPreviewCommentSep 6, 2025 0:54am

@coderabbitai

coderabbitaiBot commented Sep 5, 2025

Copy link
Copy Markdown
Contributor

Caution

Review failed

The pull request is closed.

Walkthrough

Adds a PricingCalculator UI component, a SiteFooter, and a StatusBadge; updates FeatureCard to support light/dark images and switches hero/assets to webp; changes repository constants (unsend → usesend) in several components; adjusts a design token in globals.css and integrates the pricing calculator into the marketing page.

Changes

Cohort / File(s)Summary
Marketing page & integration
apps/marketing/src/app/page.tsx
Replaced hero PNGs with WEBP, updated features to pass imageLightSrc/imageDarkSrc, swapped Footer for new SiteFooter, updated pricing copy, and renders PricingCalculator.
Feature card theming API
apps/marketing/src/components/FeatureCard.tsx
Introduces FeatureCardProps with imageLightSrc/imageDarkSrc (keeps deprecated imageSrc), renders theme-aware images (light/dark) with overflow clipping and a bottom gradient overlay.
New pricing UI
apps/marketing/src/components/PricingCalculator.tsx
Adds client-side slider-based pricing estimator (marketing + transactional), computes line items and min-monthly spend, accessible labels, exported as named and default.
Footer & branding
apps/marketing/src/components/SiteFooter.tsx
Adds new SiteFooter component rendering logo/links/status and current year; internal links via Next.js Link, external open in new tab.
Status & uptime
apps/marketing/src/components/StatusBadge.tsx
Adds StatusBadge client component that fetches uptime data, derives `operational
Repo constant updates
apps/marketing/src/components/GitHubStarsButton.tsx, apps/marketing/src/components/TopNav.tsx
Updated REPO constant from unsend-dev/unsend to usesend/usesend (affects REPO_URL / API_URL usage).
Design tokens
packages/ui/styles/globals.css
Adjusted --card-foreground values for light and dark themes; no other token changes.
Misc (exports/types)
apps/marketing/src/components/FeatureCard.tsx
Public API change: FeatureCard prop signature updated to accept imageLightSrc/imageDarkSrc; imageSrc marked deprecated.

Sequence Diagram(s)

sequenceDiagram
autonumber
actor User
participant Page as Marketing Page
participant PC as PricingCalculator
participant UI as Pricing UI
User->>Page: Open /pricing section
Page->>PC: Mount PricingCalculator
PC->>UI: Render sliders + initial calculations
User->>PC: Drag Marketing slider
PC->>PC: Recompute marketingCost, subtotal, totalDue
PC->>UI: Update displayed values
User->>PC: Drag Transactional slider
PC->>PC: Recompute transactionalCost, subtotal, totalDue
PC->>UI: Update displayed values
Loading
sequenceDiagram
autonumber
participant Badge as StatusBadge
participant API as Uptime API
participant UI as Badge UI
Badge->>API: fetch(baseUrl + /api/status-page/heartbeat/...)
API-->>Badge: JSON response
Badge->>Badge: Heuristics -> (operational|degraded|down|unknown)
Badge->>UI: Render color dot + label + link
Note right of Badge: Polls every 60s (useEffect + cleanup)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

  • add rebrand landing page #211 — Overlapping marketing UI changes: page, FeatureCard, TopNav, and GitHubStarsButton modifications (shared file targets and repo constant edits).
  • add pricing calculator #214 — Adds PricingCalculator and FeatureCard theme/image updates; closely matches the new pricing UI and image format changes.
  • rebrand to useSend #210 — Repository-wide rebrand edits from unsendusesend; related to REPO constant updates in this PR.

Poem

I nibble code beneath the moonlit stack,
Two sliders hum and images switch track.
A footer planted, status dot aglow,
WebP sprites hop where PNGs used to show.
My whiskers twitch — deploy, then snack! 🐇✨


📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 63b054a and b2e89c1.

📒 Files selected for processing (6)
  • apps/marketing/src/app/page.tsx (9 hunks)
  • apps/marketing/src/components/FeatureCard.tsx (2 hunks)
  • apps/marketing/src/components/GitHubStarsButton.tsx (1 hunks)
  • apps/marketing/src/components/SiteFooter.tsx (1 hunks)
  • apps/marketing/src/components/StatusBadge.tsx (1 hunks)
  • apps/marketing/src/components/TopNav.tsx (1 hunks)
✨ Finishing Touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch km/2025-09-06-landing-page-update

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@cloudflare-workers-and-pages

Copy link
Copy Markdown

Deploying unsend with Cloudflare Pages Cloudflare Pages

Latest commit:63b054a
Status:🚫 Build failed.

View logs

@coderabbitaicoderabbitaiBot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 0

🧹 Nitpick comments (6)
apps/marketing/src/components/FeatureCard.tsx (2)

30-43: Add sizes for next/image with fill to avoid layout warnings and improve responsiveness.

Apply:

 <Image
src={(imageLightSrc || imageDarkSrc)!}
alt={title || "Feature image"}
fill
+ sizes="(min-width: 640px) 50vw, 100vw"
className="object-cover dark:hidden rounded-t-xl"
priority={false}
/>
<Image
src={(imageDarkSrc || imageLightSrc)!}
alt={title || "Feature image"}
fill
+ sizes="(min-width: 640px) 50vw, 100vw"
className="object-cover hidden dark:block rounded-t-xl"
priority={false}
/>

56-69: Use webp fallbacks (to match page hero) and add sizes.

Aligns with the new assets and keeps performance consistent.

- <Image- src="/hero-light.png"+ <Image+ src="/hero-light.webp"
alt="Feature image"
fill
+ sizes="(min-width: 640px) 50vw, 100vw"
className="object-cover dark:hidden rounded-t-xl"
priority={false}
/>
- <Image- src="/hero-dark.png"+ <Image+ src="/hero-dark.webp"
alt="Feature image"
fill
+ sizes="(min-width: 640px) 50vw, 100vw"
className="object-cover hidden dark:block rounded-t-xl"
priority={false}
/>
apps/marketing/src/components/PricingCalculator.tsx (3)

91-94: Fix comment to match the actual defaults.

- // Defaults chosen to total $10: 8000*$0.001 + 5000*$0.0004 = 10+ // Defaults chosen to total $10: 5000*$0.001 + 12500*$0.0004 = 10

135-168: Optional: Use Intl.NumberFormat for currency for locale-safe formatting.

+ const fmtUSD = React.useMemo(+ () => new Intl.NumberFormat("en-US", { style: "currency", currency: "USD" }),+ []+ );
...
- ${marketingCost.toFixed(2)}+ {fmtUSD.format(marketingCost)}
...
- @ ${MARKETING_RATE.toFixed(4)} each+ @ {MARKETING_RATE.toFixed(4)} each
...
- ${transactionalCost.toFixed(2)}+ {fmtUSD.format(transactionalCost)}
...
- @ ${TRANSACTIONAL_RATE.toFixed(4)} each+ @ {TRANSACTIONAL_RATE.toFixed(4)} each
...
- ${totalDue.toFixed(2)}+ {fmtUSD.format(totalDue)}

70-79: Optional: Prevent tooltip clipping at the slider edges.

Clamp the badge position a bit inside the track.

- {dragging && (+ {dragging && (
<div
- className="pointer-events-none absolute -top-9 left-0 -translate-x-1/2"- style={{ left: `${percent}%` }}+ className="pointer-events-none absolute -top-9 left-0"+ style={{ left: `${Math.min(97, Math.max(3, percent))}%`, transform: "translateX(-50%)" }}
>
apps/marketing/src/app/page.tsx (1)

377-377: Polish the pricing subheading copy.

Capitalize and consider an em dash for readability.

- pay for what you use, the most affordable email platform+ Pay for what you use — the most affordable email platform
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

💡 Knowledge Base configuration:

  • MCP integration is disabled by default for public repositories
  • Jira integration is disabled by default for public repositories
  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 79d1eba and 63b054a.

⛔ Files ignored due to path filters (2)
  • apps/marketing/public/hero-dark.png is excluded by !**/*.png
  • apps/marketing/public/hero-light.png is excluded by !**/*.png
📒 Files selected for processing (4)
  • apps/marketing/src/app/page.tsx (7 hunks)
  • apps/marketing/src/components/FeatureCard.tsx (1 hunks)
  • apps/marketing/src/components/PricingCalculator.tsx (1 hunks)
  • packages/ui/styles/globals.css (2 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*.{js,jsx,ts,tsx}

📄 CodeRabbit inference engine (.cursor/rules/general.mdc)

Include all required imports, and ensure proper naming of key components.

Files:

  • apps/marketing/src/components/PricingCalculator.tsx
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/components/FeatureCard.tsx
**/*.{ts,tsx}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx}: Prefer TypeScript for implementation files (TypeScript-first)
ESLint must pass with zero warnings using @usesend/eslint-config before PRs

Files:

  • apps/marketing/src/components/PricingCalculator.tsx
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/components/FeatureCard.tsx
**/*.{ts,tsx,md}

📄 CodeRabbit inference engine (AGENTS.md)

**/*.{ts,tsx,md}: Use 2-space indentation and keep semicolons as enforced by Prettier 3
Run Prettier 3 formatting (pnpm format) over TypeScript and Markdown files

Files:

  • apps/marketing/src/components/PricingCalculator.tsx
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/components/FeatureCard.tsx
**/*.{tsx,jsx}

📄 CodeRabbit inference engine (AGENTS.md)

Name React component files in PascalCase (e.g., AppSideBar.tsx)

Files:

  • apps/marketing/src/components/PricingCalculator.tsx
  • apps/marketing/src/app/page.tsx
  • apps/marketing/src/components/FeatureCard.tsx
🧬 Code graph analysis (1)
apps/marketing/src/app/page.tsx (1)
apps/marketing/src/components/PricingCalculator.tsx (1)
  • PricingCalculator (85-175)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Cloudflare Pages
🔇 Additional comments (7)
packages/ui/styles/globals.css (1)

12-12: Contrast passes WCAG standards
Computed contrast ratios are ~8.1:1 against the light background and ~13.8:1 against the dark background—both exceed the 4.5:1 minimum.

apps/marketing/src/components/FeatureCard.tsx (1)

5-15: API extension looks good.

Prop typing and deprecation strategy are clear and backward-compatible.

Also applies to: 19-23

apps/marketing/src/app/page.tsx (5)

8-8: LGTM: PricingCalculator import.


65-65: LGTM: Switch to webp hero assets.

Good perf improvement and consistency with other imagery.

Also applies to: 75-75


226-228: LGTM: Migration to light/dark image variants for features.

Prop usage matches the updated FeatureCard API.

Also applies to: 234-236, 277-279


396-398: LGTM: PricingCalculator placement under the pricing cards.


157-173: Domains whitelist not needed with unoptimized static export
apps/marketing/next.config.js already sets images.unoptimized: true for output: "export", so Next.js will render plain <img> tags and bypass the image‐optimization pipeline (including domain checks) (nextjs.org, vercel.com)

Likely an incorrect or invalid review comment.

@KMKoushik
KMKoushik merged commit 69470a4 into mainSep 6, 2025
3 of 5 checks passed
@KMKoushik
KMKoushik deleted the km/2025-09-06-landing-page-update branch September 6, 2025 00:53
KMKoushik added a commit that referenced this pull request Sep 7, 2025
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@KMKoushik