Design improvements for retention offer#26712
Conversation
The retention offer screen rendered an empty gh-portal-retention-offer-price div when the discount type was free months, causing a large visual gap. Moved the conditional to wrap the entire div so it's not rendered at all for free month offers, and adjusted spacing accordingly.
WalkthroughUpdated UI layout and rendering in the account plan and offers index screens. On the account plan page: header padding was added for non-full-size layouts; retention-offer vertical spacing and acceptance-button spacing were reduced; the inner price block is no longer rendered for free-month offers. In the offers index: the filter trigger was simplified to a labeled button; table layout switched to a minimum width with adjusted column widths; the first column and its header were made sticky with background/z-index fixes. apps/portal package version bumped; no exported APIs changed. 🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
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. Comment |
🤖 Velo CI Failure AnalysisClassification: 🟠 SOFT FAIL
|
| return ( | ||
| <tr key={offer.id} className={`group relative scale-100 border-b border-b-grey-200 dark:border-grey-800 ${archived ? 'opacity-60' : ''}`} data-testid="offer-item"> | ||
| <td className='p-0'><a className='block cursor-pointer p-5 pl-0' onClick={() => handleOfferEdit(offer.id)}><span className='font-semibold'>{offer?.name}</span><br /><span className='text-sm text-grey-700'>{offerTier.name} {getOfferCadence(offer.cadence)}</span></a></td> | ||
| <td className='sticky left-0 z-10 bg-white p-0 dark:bg-black'><a className='block cursor-pointer p-5 pl-0' onClick={() => handleOfferEdit(offer.id)}><span className='font-semibold'>{offer?.name}</span><br /><span className='text-sm text-grey-700'>{offerTier.name} {getOfferCadence(offer.cadence)}</span></a></td> |
There was a problem hiding this comment.
Sticky column background bleeds through on archived rows
Low Severity
The newly added sticky Name column (bg-white dark:bg-black) on the <td> won't fully mask scrolled-under content for archived offer rows, because the parent <tr> applies opacity-60. CSS opacity on a parent makes all descendant backgrounds semi-transparent, so the sticky column's opaque background becomes see-through when the row is archived. This defeats the purpose of the sticky column during horizontal scrolling.
Additional Locations (1)
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In
`@apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx`:
- Line 90: The icon-only filter trigger Button (the Button with
label={<LucideIcon.ListFilter .../>} and unstyled={true}) needs an accessible
name; add an aria-label or aria-labelledby to the Button (for example
aria-label="Filter options" or similar descriptive text) so screen readers
announce it, or include visually-hidden text associated with the Button; ensure
the change is applied on the same Button component rendering the
LucideIcon.ListFilter.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: f703dc42-123e-414c-affa-714ba4534d05
📒 Files selected for processing (1)
apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx
apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx
Outdated
Show resolved
Hide resolved
Changelog for v2.65.1 -> 2.65.2:
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx
Show resolved
Hide resolved
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx (1)
371-377:⚠️ Potential issue | 🟠 MajorReplace click-only anchors with buttons for keyboard accessibility.
Lines 371–376 use
<a>elements withonClickhandlers but nohrefattribute. This pattern is not keyboard-accessible (missing focus management, Enter key support) and is semantically incorrect for in-app actions. Convert these to<button type="button">elements instead:
- Line 371: Offer name cell
- Line 372: Discount amount cell
- Line 373: Price cell
- Line 376: Status cell
All four cells in the row should use buttons for consistency and accessibility compliance.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx` around lines 371 - 377, Replace the click-only anchor elements used for in-row actions with semantic buttons: change the <a> tags that call handleOfferEdit(offer.id) in the offer name cell, discount cell, price cell and status cell to <button type="button"> so keyboard users can focus and activate via Enter/Space; ensure the redemption count link still uses an anchor when it has an href (keep createRedemptionFilterUrl(offer.id) for actual navigation) and when it falls back to invoking handleOfferEdit(offer.id) use a button instead, preserving existing className, onClick logic and conditional href behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Outside diff comments:
In
`@apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx`:
- Around line 371-377: Replace the click-only anchor elements used for in-row
actions with semantic buttons: change the <a> tags that call
handleOfferEdit(offer.id) in the offer name cell, discount cell, price cell and
status cell to <button type="button"> so keyboard users can focus and activate
via Enter/Space; ensure the redemption count link still uses an anchor when it
has an href (keep createRedemptionFilterUrl(offer.id) for actual navigation) and
when it falls back to invoking handleOfferEdit(offer.id) use a button instead,
preserving existing className, onClick logic and conditional href behavior.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: b5840bce-6e08-46ce-a036-42a953543562
📒 Files selected for processing (1)
apps/admin-x-settings/src/components/settings/growth/offers/offers-index-retention.tsx


ref https://linear.app/ghost/issue/BER-3388/design-bugsimprovements
Note
Low Risk
Purely presentational/layout and accessibility tweaks with no changes to business logic or data flows; primary risk is minor CSS/layout regressions in the offers list and retention-offer screen.
Overview
Improves retention-offer UI in both Admin and Portal.
In Admin (
offers-index-retention.tsx), updates the offers table for better horizontal scrolling: makes the Name column sticky, sets a minimum table width/column sizing, and tweaks the filter button styling and accessibility (aria-label).In Portal (
account-plan-page.js), adjusts retention-offer spacing and markup so the price block only renders when applicable, and tightens button/footnote margins; bumps@tryghost/portalversion to2.65.2.Written by Cursor Bugbot for commit e18bcd7. This will update automatically on new commits. Configure here.