Added gift links admin UI for analytics, posts list, and settings#28897
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughThis PR adds gift-link management across admin and posts. It introduces gift-link API client mutations, a settings action to reset all gift links, an Ember-to-React bridge for opening a React gift-link modal from posts and pages lists, and new posts-app hooks, utilities, modal UI, and sharing entry points. It also adds related tests, package exports, and route updates. Possibly related PRs
Suggested reviewers
🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ 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 |
|
| Command | Status | Duration | Result |
|---|---|---|---|
nx run @tryghost/admin-x-settings:test:acceptance |
✅ Succeeded | 9m 58s | View ↗ |
nx run-many --target=build --projects=tag:publi... |
✅ Succeeded | 1s | View ↗ |
nx run-many -t test:unit -p @tryghost/admin-x-f... |
✅ Succeeded | 6m 50s | View ↗ |
nx run @tryghost/admin:build |
✅ Succeeded | 4m | View ↗ |
nx run ghost-admin:test |
✅ Succeeded | 2m 44s | View ↗ |
nx run @tryghost/activitypub:test:acceptance |
✅ Succeeded | 37s | View ↗ |
nx run-many -t lint -p @tryghost/admin-x-framew... |
✅ Succeeded | 19s | View ↗ |
nx run ghost:build:assets |
✅ Succeeded | 2s | View ↗ |
nx run ghost:build:tsc |
✅ Succeeded | 6s | View ↗ |
💡 Verify your cache is correct by running tasks in a sandbox. Read docs ↗
☁️ Nx Cloud last updated this comment at 2026-06-30 07:17:55 UTC
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
apps/posts/src/utils/gift-link.ts (1)
7-12: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueEdge case: hard-coded
?breaks URLs that already carry a query string.
buildGiftLinkUrlalways prefixes with?, so apostUrlalready containing a query string would yield a malformed URL with two?. Canonical post URLs are normally clean, so this is just defensive hardening.♻️ Use the correct separator
- return `${postUrl}?gift=${encodeURIComponent(token)}`; + const separator = postUrl.includes('?') ? '&' : '?'; + return `${postUrl}${separator}gift=${encodeURIComponent(token)}`;🤖 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 `@apps/posts/src/utils/gift-link.ts` around lines 7 - 12, The buildGiftLinkUrl helper always appends the gift token with a hard-coded query separator, which breaks when postUrl already contains existing query parameters. Update buildGiftLinkUrl to choose the correct separator based on whether postUrl already includes a query string, and keep the token encoding behavior unchanged.
🤖 Prompt for all review comments with 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.
Inline comments:
In `@apps/posts/src/views/PostAnalytics/modals/gift-link-modal.tsx`:
- Around line 135-143: Disable the ShareModal.CopyButton in gift-link-modal
while the link is being generated so it cannot copy an empty string. Update the
copy button in the gift-link CopyURLBox to use the existing ensuring state from
the modal logic, and keep the disabled state aligned with the same
giftLinkUrl/ensuring flow used in this component.
---
Nitpick comments:
In `@apps/posts/src/utils/gift-link.ts`:
- Around line 7-12: The buildGiftLinkUrl helper always appends the gift token
with a hard-coded query separator, which breaks when postUrl already contains
existing query parameters. Update buildGiftLinkUrl to choose the correct
separator based on whether postUrl already includes a query string, and keep the
token encoding behavior unchanged.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: 9397fe1c-217a-4d38-88dc-8ce6dd3e7870
📒 Files selected for processing (24)
apps/admin-x-framework/src/api/gift-links.tsapps/admin-x-settings/src/components/settings/advanced/danger-zone.tsxapps/admin-x-settings/test/acceptance/advanced/dangerzone.test.tsapps/admin/src/ember-bridge/ember-bridge.tsxapps/admin/src/ember-bridge/index.tsapps/admin/src/gift-link-modal-host.tsxapps/admin/src/routes.tsxapps/posts/package.jsonapps/posts/src/hooks/use-can-manage-gift-link.tsapps/posts/src/hooks/use-gift-link-usage.tsapps/posts/src/hooks/use-post-details.tsapps/posts/src/providers/post-analytics-context.tsxapps/posts/src/utils/constants.tsapps/posts/src/utils/gift-link.tsapps/posts/src/views/PostAnalytics/components/post-analytics-header.tsxapps/posts/src/views/PostAnalytics/modals/gift-link-modal.tsxapps/posts/test/unit/utils/gift-link.test.tsapps/shade/src/components/posts-stats/post-share-modal.tsxghost/admin/app/components/posts-list/context-menu.hbsghost/admin/app/components/posts-list/context-menu.jsghost/admin/app/services/feature.jsghost/admin/app/services/state-bridge.jsghost/admin/app/utils/gift-link.jsghost/admin/tests/unit/utils/gift-link-test.js
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e4e3aba258
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
cd8049c to
0f9b693
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0f9b6932f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with 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.
Inline comments:
In `@apps/posts/src/views/PostAnalytics/Overview/overview.tsx`:
- Around line 218-225: The “Share” button in PostAnalytics/Overview is only
revealed on hover, so it stays hidden for keyboard and touch users. Update the
Button in the relevant render path to also reveal on focus-visible (mirroring
the existing Growth “View more” behavior if applicable), while keeping the
current hover animation intact. Use the Button element and its existing
className/variant setup as the reference point, and apply the same accessibility
fix wherever the same hover-only pattern appears.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Repository UI
Review profile: CHILL
Plan: Pro
Run ID: 718f5ac2-0d5e-458f-b497-8aa983b49480
📒 Files selected for processing (25)
apps/admin-x-framework/src/api/gift-links.tsapps/admin-x-settings/src/components/settings/advanced/danger-zone.tsxapps/admin-x-settings/test/acceptance/advanced/dangerzone.test.tsapps/admin/src/ember-bridge/ember-bridge.tsxapps/admin/src/ember-bridge/index.tsapps/admin/src/gift-link-modal-host.tsxapps/admin/src/routes.tsxapps/posts/package.jsonapps/posts/src/hooks/use-can-manage-gift-link.tsapps/posts/src/hooks/use-gift-link-usage.tsapps/posts/src/hooks/use-post-details.tsapps/posts/src/providers/post-analytics-context.tsxapps/posts/src/utils/constants.tsapps/posts/src/utils/gift-link.tsapps/posts/src/views/PostAnalytics/Overview/overview.tsxapps/posts/src/views/PostAnalytics/components/post-analytics-header.tsxapps/posts/src/views/PostAnalytics/modals/gift-link-modal.tsxapps/posts/test/unit/utils/gift-link.test.tsapps/shade/src/components/posts-stats/post-share-modal.tsxghost/admin/app/components/posts-list/context-menu.hbsghost/admin/app/components/posts-list/context-menu.jsghost/admin/app/services/feature.jsghost/admin/app/services/state-bridge.jsghost/admin/app/utils/gift-link.jsghost/admin/tests/unit/utils/gift-link-test.js
✅ Files skipped from review due to trivial changes (3)
- apps/admin/src/ember-bridge/index.ts
- apps/posts/src/providers/post-analytics-context.tsx
- apps/posts/test/unit/utils/gift-link.test.ts
🚧 Files skipped from review as they are similar to previous changes (21)
- apps/admin-x-settings/test/acceptance/advanced/dangerzone.test.ts
- apps/posts/src/utils/gift-link.ts
- apps/posts/package.json
- apps/posts/src/hooks/use-can-manage-gift-link.ts
- ghost/admin/app/components/posts-list/context-menu.hbs
- ghost/admin/app/services/feature.js
- apps/admin/src/routes.tsx
- apps/admin/src/ember-bridge/ember-bridge.tsx
- apps/admin-x-framework/src/api/gift-links.ts
- apps/posts/src/hooks/use-post-details.ts
- apps/posts/src/views/PostAnalytics/components/post-analytics-header.tsx
- apps/posts/src/utils/constants.ts
- ghost/admin/app/utils/gift-link.js
- ghost/admin/app/components/posts-list/context-menu.js
- apps/shade/src/components/posts-stats/post-share-modal.tsx
- ghost/admin/app/services/state-bridge.js
- ghost/admin/tests/unit/utils/gift-link-test.js
- apps/posts/src/hooks/use-gift-link-usage.ts
- apps/admin-x-settings/src/components/settings/advanced/danger-zone.tsx
- apps/admin/src/gift-link-modal-host.tsx
- apps/posts/src/views/PostAnalytics/modals/gift-link-modal.tsx
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 143b521979
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const handleRemoveAllGiftLinks = () => { | ||
| NiceModal.show(ConfirmationModal, { | ||
| title: 'Reset all gift links?', | ||
| prompt: 'This immediately invalidates every active gift link across your site. Anyone holding one will lose access. New gift links can still be created afterwards.', |
There was a problem hiding this comment.
@weylandswart some wordsmithing required here?
| <ListItem | ||
| action={<Button aria-label='Reset all gift links' color='red' label='Reset' onClick={handleRemoveAllGiftLinks} />} | ||
| bgOnHover={false} | ||
| detail='Invalidate every active gift link across your site. Anyone holding one will lose access.' |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 39b501f49f
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
ref https://linear.app/ghost/issue/BER-3729 - one shared React gift-link modal is reused across the post-analytics screen and the Ember posts/pages list, rather than maintaining a separate Ember modal: the list's right-click menu fires an `openGiftLinkModal` event over the state bridge and a host mounted alongside the Ember fallback opens the React modal in place - the modal fetches link details (admin API) and usage (the same Tinybird analytics path as everything else) separately, degrading to no visitor count when analytics is off or the usage pipe isn't deployed yet; the share URL is the canonical post URL + `?gift=<token>` - adds the danger-zone "reset all gift links" action and a "share as a gift" entry in the post share modal - all gated behind the existing private `giftLinks` flag
ref https://linear.app/ghost/issue/BER-3729 - dropped the extra flex wrapper around ShareModal.Footer + the gift link - ShareModal.Content (a DialogContent) already lays its children out in a grid with gap-6, so the wrapper only duplicated that spacing (with an inconsistent gap-5) and broke the pattern where each ShareModal.* section is a direct child of Content
- Narrowed gift-link reads via useActiveGiftLink so callers get the token directly instead of indexing the response array - Routed useReadGiftLink through the giftLinkPath helper - Colocated the role check as canManageGiftLinks in the users API and dropped Author from gift-link eligibility (React + Ember) - Added visibility/uuid to the Page type and dropped the widening cast - Replaced the binary access-label ternary with a visibility-keyed map - Keyed the gift-link modal for reset instead of an on-close effect, and memoized its handlers with useCallback - Renamed the modal host's target state to entry; trimmed stale comments
39b501f to
e027100
Compare
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e0271005ba
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
When analytics is off or the usage query errors, useGiftLinkUsage returns undefined to distinguish 'unavailable' from a real zero. Rather than coercing that to 0 (or showing a meaningless placeholder), hide the visitor metric until the count is known, matching how the modal hides its badge.
316fc37 to
18e6135
Compare
The Growth 'View more' and Gift link 'Share' buttons were opacity-0 until hover, leaving them focusable but invisible for keyboard users and unreachable on touch. Reveal them on focus-visible too.

ref https://linear.app/ghost/issue/BER-3729
Summary
Adds the publisher-facing gift-links admin UI on top of the service + admin API already on
main. A single React gift-link modal is reused across surfaces instead of maintaining a separate Ember modal:openGiftLinkModalevent over the state bridge; a host mounted alongside the Ember fallback (at the/postsand/pagesroutes) opens the React modal in place.All gated behind the existing private
giftLinksflag.Notes
gift_links) and usage (visits/views via the same Tinybird analytics path as every other analytics surface). Usage degrades gracefully — when analytics is off, or the per-link usage pipe (BER-3746/BER-3728) isn't deployed yet, the visitor count is simply hidden and everything else still works.?gift=<token>(noutm).POST_ANALYTICS_INCLUDE); pages fetch on their own route. API wrapper names follow the backend controllers (ensure/create/removeAll).Testing
apps/postsunit suite (482) green; new URL-builder unit test; Ember eligibility-util unit test.admin-x-settingsdanger-zone acceptance test for reset-all (passing locally).posts,admin,shade,admin-x-framework,admin-x-settings, andghost/admin.