Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 4 additions & 3 deletions .env.example
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
# Polar.sh Integration
POLAR_ACCESS_TOKEN=your_polar_access_token_here
POLAR_30SEC_PRODUCT_ID=your_30sec_product_id_here
# The Distiller's Cut — single 60s host-read episode
POLAR_60SEC_PRODUCT_ID=your_60sec_product_id_here
POLAR_BOTTLEDROP_PRODUCT_ID=your_bottledrop_product_id_here
# The Crate — monthly package
POLAR_CRATE_PRODUCT_ID=your_crate_product_id_here
# The Full Barrel — flagship monthly package
POLAR_FULLBARREL_PRODUCT_ID=your_fullbarrel_product_id_here
POLAR_LABEL_PRODUCT_ID=your_label_product_id_here
# The Single Barrel is a bespoke tier handled via /contact (no Polar product)
POLAR_SUCCESS_URL=https://whiskey.fm/sponsor/success

# Optional: Set to "sandbox" for testing, "production" for live (defaults to production)
Expand Down
7 changes: 2 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -177,21 +177,18 @@ This site uses Polar.sh for sponsor checkout. To set it up:
1. **Get your Polar credentials:**
- Log in to your [Polar dashboard](https://polar.sh)
- Go to Settings → API to get your access token
- Create two products for your sponsorship packages (30-second and 60-second
ads)
- Create one product per paid sponsorship package (The Distiller's Cut, The
Crate, and The Full Barrel)
- Note the product IDs from each product's page

2. **Configure environment variables:** Create a `.env` file in the root
directory with:

```env
POLAR_ACCESS_TOKEN=your_polar_access_token_here
POLAR_30SEC_PRODUCT_ID=your_30sec_product_id_here
POLAR_60SEC_PRODUCT_ID=your_60sec_product_id_here
POLAR_BOTTLEDROP_PRODUCT_ID=your_bottledrop_product_id_here
POLAR_CRATE_PRODUCT_ID=your_crate_product_id_here
POLAR_FULLBARREL_PRODUCT_ID=your_fullbarrel_product_id_here
POLAR_LABEL_PRODUCT_ID=your_label_product_id_here
POLAR_SUCCESS_URL=https://whiskey.fm/sponsor/success
```

Expand Down
38 changes: 26 additions & 12 deletions src/components/AdPackageCard.astro
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,21 @@ export interface Props {
bullets: Array<string>;
heading: string;
price: string;
productId: string;
productId?: string;
period?: string;
ctaLabel?: string;
ctaHref?: string;
}

const { bullets, heading, price, productId, period = 'per episode' } = Astro.props;
const {
bullets,
heading,
price,
productId,
period = 'per episode',
ctaLabel = 'Become a sponsor',
ctaHref = `/api/checkout?products=${productId}`
} = Astro.props;
Comment on lines +6 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Default ctaHref will produce an invalid URL when productId is undefined.

Line 19 constructs the default ctaHref as `/api/checkout?products=${productId}`. Since productId is now optional (line 6), when it's undefined the URL becomes /api/checkout?products=undefined, which will fail at the Polar checkout endpoint. The Single Barrel tier (which omits productId) works because it provides its own ctaHref, but this default is fragile and could break future cards that omit productId without providing ctaHref.

Guard the template literal or make the logic explicit.

🛡️ Proposed fix to guard against undefined productId
 const {
   bullets,
   heading,
   price,
   productId,
   period = 'per episode',
   ctaLabel = 'Become a sponsor',
-  ctaHref = `/api/checkout?products=${productId}`
+  ctaHref = productId ? `/api/checkout?products=${productId}` : '#'
 } = Astro.props;

Alternatively, if a ctaHref is always expected when productId is omitted, add a runtime check or TypeScript constraint to enforce the mutual dependency.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
productId?: string;
period?: string;
ctaLabel?: string;
ctaHref?: string;
}
const { bullets, heading, price, productId, period = 'per episode' } = Astro.props;
const {
bullets,
heading,
price,
productId,
period = 'per episode',
ctaLabel = 'Become a sponsor',
ctaHref = `/api/checkout?products=${productId}`
} = Astro.props;
productId?: string;
period?: string;
ctaLabel?: string;
ctaHref?: string;
}
const {
bullets,
heading,
price,
productId,
period = 'per episode',
ctaLabel = 'Become a sponsor',
ctaHref = productId ? `/api/checkout?products=${productId}` : '#'
} = Astro.props;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/AdPackageCard.astro` around lines 6 - 20, The default ctaHref
currently uses a template literal with productId (const ... ctaHref =
`/api/checkout?products=${productId}`) which yields an invalid URL when
productId is undefined; update the logic that sets ctaHref (in the Astro.props
destructure or immediately after) to guard productId—e.g., compute ctaHref
conditionally so that when productId is missing you either omit the products
query param or fallback to a safe URL (or throw/require ctaHref), and ensure the
component uses the guarded value; reference the ctaHref and productId symbols
when making the change.

---

<div
Expand All @@ -34,22 +44,26 @@ const { bullets, heading, price, productId, period = 'per episode' } = Astro.pro
class="text-light-text-heading mt-8 mb-12 flex-1 content-end px-5 text-4xl font-bold tabular-nums dark:text-white"
>
{price}
<span
class="text-light-text-body dark:text-dark-text-body text-3xl font-light"
>
/
</span>
<span class="text-light-text-body dark:text-dark-text-body text-lg">
{period}
</span>
{
period && (
<>
<span class="text-light-text-body dark:text-dark-text-body text-3xl font-light">
/
</span>
<span class="text-light-text-body dark:text-dark-text-body text-lg">
{period}
</span>
</>
)
}
</h3>

<div class="flex w-full">
<a class="btn mb-8 w-full justify-center" href={`/api/checkout?products=${productId}`}>
<a class="btn mb-8 w-full justify-center" href={ctaHref}>
<span
class="text-light-text-heading rounded-full px-8 py-3 text-center text-sm dark:text-white"
>
Become a sponsor
{ctaLabel}
</span>
</a>
</div>
Expand Down
50 changes: 21 additions & 29 deletions src/pages/sponsor.astro
Original file line number Diff line number Diff line change
Expand Up @@ -219,43 +219,22 @@ const currentYear = new Date().getFullYear();
<div class="grid grid-cols-1 gap-6 lg:grid-cols-2">
<AdPackageCard
bullets={[
'Have a bottle of whiskey or software product reviewed',
'Honest, positive mention during Whiskey and/or Whatnot segments'
'A baked-in, high-trust host-read ad',
'60-seconds, pre- or mid-roll',
'Logo & link in the show notes and on the site'
]}
heading="The Drop"
price="$250"
productId=`${import.meta.env.POLAR_BOTTLEDROP_PRODUCT_ID || 'YOUR_BOTTLEDROP_PRODUCT_ID'}`
/>
<AdPackageCard
bullets={[
'Ad on our website or in the show notes',
'Placed prominently on main page and show notes',
'Consistent exposure outside of the audio feed'
]}
heading="The Label"
price="$250"
productId=`${import.meta.env.POLAR_LABEL_PRODUCT_ID || 'YOUR_LABEL_PRODUCT_ID'}`
/>
<AdPackageCard
bullets={['A baked in, high-trust host read pre-roll ad', '30-seconds']}
heading="The Distiller's Cut"
price="$500"
productId=`${import.meta.env.POLAR_30SEC_PRODUCT_ID || 'YOUR_30SEC_PRODUCT_ID'}`
/>
<AdPackageCard
bullets={['A baked in, high-trust host read pre-roll ad', '60-seconds']}
heading="The Spirit Maker's Cut"
price="$1k"
price="$1,750"
productId=`${import.meta.env.POLAR_60SEC_PRODUCT_ID || 'YOUR_60SEC_PRODUCT_ID'}`
/>
<AdPackageCard
bullets={[
'4x 30-second host-read ads',
'4x 60-second host-read ads',
'4x website & show notes placements',
'Perfect for consistent monthly presence'
'Perfect for a consistent monthly presence'
]}
heading="The Crate"
price="$2.4k"
price="$4,200"
period="per month"
productId=`${import.meta.env.POLAR_CRATE_PRODUCT_ID || 'YOUR_CRATE_PRODUCT_ID'}`
/>
Expand All @@ -268,10 +247,23 @@ const currentYear = new Date().getFullYear();
'Social media mentions for each episode'
]}
heading="The Full Barrel"
price="$4k"
price="$7,000"
period="per month"
productId=`${import.meta.env.POLAR_FULLBARREL_PRODUCT_ID || 'YOUR_FULLBARREL_PRODUCT_ID'}`
/>
<AdPackageCard
bullets={[
'A co-branded single-barrel whiskey pick, selected on-air',
'Custom bottles for your team, your customers, or a listener giveaway',
'Audio, video & social rolled around the selection',
'Our most unique, most memorable sponsorship'
]}
heading="The Single Barrel"
price="Custom"
period=""
ctaLabel="Let's talk"
ctaHref="/contact"
/>
</div>

<div class="mt-20 mb-12 lg:mt-32">
Expand Down
Loading