Skip to content

Added missing empty state for members#27463

Open
weylandswart wants to merge 2 commits intomainfrom
missing-empty-state-for-members
Open

Added missing empty state for members#27463
weylandswart wants to merge 2 commits intomainfrom
missing-empty-state-for-members

Conversation

@weylandswart
Copy link
Copy Markdown
Contributor

No ref

Adds back a (Shade-driven) empty state for Members to emulate the existing one in Ember.

Screenshot 2026-04-20 at 11 45 10

.

Screenshot 2026-04-20 at 11 45 26

No ref

Adds back a (Shade-driven) empty state for Members to emulate the existing one in Ember.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Apr 20, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: dbc6e66b-b06b-4964-8d58-86ae5db57885

📥 Commits

Reviewing files that changed from the base of the PR and between 8ff0c39 and cf769cd.

📒 Files selected for processing (1)
  • e2e/helpers/pages/admin/members/members-list-page.ts

Walkthrough

A new MembersEmptyState component was created to handle the empty members page display with conditional rendering based on members_signup_access settings. The component offers users options to add themselves as members or import via CSV when memberships are enabled, or displays a disabled state when memberships are restricted. The members page was updated to use this new component instead of a plain empty indicator. Additionally, the EmptyIndicator component's actions container had its top margin spacing removed. The members list empty-state text in tests was updated from "No members yet" to "Start building your audience," and the empty state messaging for filtered views was refined for clarity and layout.

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Added missing empty state for members' accurately describes the primary change: adding a new MembersEmptyState component to display an empty state UI when the members list is empty.
Description check ✅ Passed The description clearly relates to the changeset by stating it 'Adds back a (Shade-driven) empty state for Members to emulate the existing one in Ember' and includes screenshots showing the implementation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch missing-empty-state-for-members

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.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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/posts/src/views/members/components/members-empty-state.tsx`:
- Around line 11-18: The code treats memberships as enabled when settings are
unresolved because membersSignupAccess becomes undefined; update the
membershipsEnabled calculation to only evaluate after settings are loaded (e.g.,
check settingsData or settingsData.settings exists) — for example in the
component that calls useBrowseSettings, change the membershipsEnabled derivation
(currently using membersSignupAccess and membershipsEnabled) to first gate on
settingsData (or a loading flag) and only set membershipsEnabled =
getSettingValue<string>(settingsData.settings, 'members_signup_access') !==
'none' when settingsData is present so the CTA is not shown while settings are
still loading.
- Around line 27-39: The POST to `${apiRoot}/members/` in this component is
missing the framework's required fetch settings (e.g., credentials and
cache/version headers); update the call to include credentials: 'include' and
add headers like 'app-pragma': 'no-cache' and the app/version header matching
the pattern in import-members-modal.tsx, or better yet, extract this logic into
a new mutation hook (e.g., useCreateMembers) mirroring existing hooks such as
useAddTier/useAddNewsletter so API auth/headers are centralized and reusable;
modify the code that calls getGhostPaths() and currentUser to use that hook or
the corrected fetch options.
🪄 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: f5bcc814-af29-4a4b-b70b-6a98c1d556e3

📥 Commits

Reviewing files that changed from the base of the PR and between aaebe4f and 8ff0c39.

📒 Files selected for processing (3)
  • apps/posts/src/views/members/components/members-empty-state.tsx
  • apps/posts/src/views/members/members.tsx
  • apps/shade/src/components/ui/empty-indicator.tsx

Comment on lines +11 to +18
const {data: settingsData} = useBrowseSettings({});
const {data: currentUser} = useCurrentUser();
const navigate = useNavigate();
const [isAdding, setIsAdding] = useState(false);

const {assetRoot} = getGhostPaths();
const membersSignupAccess = getSettingValue<string>(settingsData?.settings ?? null, 'members_signup_access');
const membershipsEnabled = membersSignupAccess !== 'none';
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.

⚠️ Potential issue | 🟡 Minor

Don’t treat unresolved settings as memberships enabled.

While useBrowseSettings is still loading, membersSignupAccess is undefined, so undefined !== 'none' renders the enabled CTA even on sites where memberships are disabled. Gate the branch until settings are loaded.

Proposed fix
-import {Button, EmptyIndicator} from '@tryghost/shade/components';
+import {Button, EmptyIndicator, LoadingIndicator} from '@tryghost/shade/components';
@@
-    const {data: settingsData} = useBrowseSettings({});
+    const {data: settingsData, isLoading: isSettingsLoading} = useBrowseSettings({});
@@
     const {assetRoot} = getGhostPaths();
     const membersSignupAccess = getSettingValue<string>(settingsData?.settings ?? null, 'members_signup_access');
     const membershipsEnabled = membersSignupAccess !== 'none';
+
+    if (isSettingsLoading || !settingsData?.settings) {
+        return (
+            <div className="flex h-full items-center justify-center px-4">
+                <LoadingIndicator size="lg" />
+            </div>
+        );
+    }
📝 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
const {data: settingsData} = useBrowseSettings({});
const {data: currentUser} = useCurrentUser();
const navigate = useNavigate();
const [isAdding, setIsAdding] = useState(false);
const {assetRoot} = getGhostPaths();
const membersSignupAccess = getSettingValue<string>(settingsData?.settings ?? null, 'members_signup_access');
const membershipsEnabled = membersSignupAccess !== 'none';
const {data: settingsData, isLoading: isSettingsLoading} = useBrowseSettings({});
const {data: currentUser} = useCurrentUser();
const navigate = useNavigate();
const [isAdding, setIsAdding] = useState(false);
const {assetRoot} = getGhostPaths();
const membersSignupAccess = getSettingValue<string>(settingsData?.settings ?? null, 'members_signup_access');
const membershipsEnabled = membersSignupAccess !== 'none';
if (isSettingsLoading || !settingsData?.settings) {
return (
<div className="flex h-full items-center justify-center px-4">
<LoadingIndicator size="lg" />
</div>
);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/posts/src/views/members/components/members-empty-state.tsx` around lines
11 - 18, The code treats memberships as enabled when settings are unresolved
because membersSignupAccess becomes undefined; update the membershipsEnabled
calculation to only evaluate after settings are loaded (e.g., check settingsData
or settingsData.settings exists) — for example in the component that calls
useBrowseSettings, change the membershipsEnabled derivation (currently using
membersSignupAccess and membershipsEnabled) to first gate on settingsData (or a
loading flag) and only set membershipsEnabled =
getSettingValue<string>(settingsData.settings, 'members_signup_access') !==
'none' when settingsData is present so the CTA is not shown while settings are
still loading.

Comment on lines +27 to +39
const {apiRoot} = getGhostPaths();
const response = await fetch(`${apiRoot}/members/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
members: [{
email: currentUser.email,
name: currentUser.name
}]
})
});
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Description: Inspect existing member-create/Admin API request conventions without modifying the repo.

set -euo pipefail

echo "Existing member API helpers or mutations:"
rg -n -C3 --type=ts --type=tsx '\b(use(Add|Create|Edit|Update)Member|addMember|createMember|createMembers|membersApi)\b' || true

echo
echo "Existing direct calls to the members endpoint:"
rg -n -C4 --type=ts --type=tsx 'fetch\s*\([^)]*/members/|apiRoot[^;\n]*/members/' || true

echo
echo "Admin API version/header conventions:"
rg -n -C3 --type=ts --type=tsx 'Accept-Version|Content-Type.*application/json|apiRoot' || true

Repository: TryGhost/Ghost

Length of output: 287


🏁 Script executed:

#!/bin/bash
# Search for member-related API helpers and mutations with corrected syntax

echo "=== Member API helpers/mutations ==="
rg -n -C2 '(useAddMember|useCreateMember|useEditMember|useUpdateMember|addMember|createMember|createMembers|membersApi)' --max-count=20 || true

echo
echo "=== Existing fetch calls to members endpoint ==="
rg -n -C3 '(fetch.*members|members.*fetch)' --max-count=20 || true

echo
echo "=== Admin API client usage patterns ==="
rg -n -C3 '(GhostAdminAPI|adminAPI|apiClient|useAdminAPI|useGhost)' apps/posts/src --max-count=30 || true

echo
echo "=== Looking at the specific file ==="
head -50 apps/posts/src/views/members/components/members-empty-state.tsx || true

Repository: TryGhost/Ghost

Length of output: 50373


🏁 Script executed:

#!/bin/bash
# Look at the specific file mentioned in the review
echo "=== File: members-empty-state.tsx ==="
cat -n apps/posts/src/views/members/components/members-empty-state.tsx

echo
echo "=== Search for Admin API/fetch patterns in apps/posts ==="
rg -n 'fetch|apiRoot|useGhost|adminAPI' apps/posts/src --max-count=30 -A 2

echo
echo "=== GhostAdminApiAdapter definition ==="
find . -name '*adapter*' -o -name '*Adapter*' | head -20 | xargs grep -l 'GhostAdminApiAdapter' 2>/dev/null || echo "No direct hits"
rg -n 'class GhostAdminApiAdapter|export.*GhostAdminApiAdapter' --max-count=10

Repository: TryGhost/Ghost

Length of output: 25068


🏁 Script executed:

#!/bin/bash
# Search for member API patterns in the framework
echo "=== Member-related API code in admin-x-framework ==="
find . -path "*/admin-x-framework*" -name "*.ts" -o -name "*.tsx" | xargs rg -l "member" 2>/dev/null | head -20

echo
echo "=== Check what the framework provides for API calls ==="
rg -n 'fetch|apiRoot' packages/@tryghost/admin-x-framework --max-count=30 -A 2 2>/dev/null || echo "Searching in framework..."

echo
echo "=== Look for API client patterns in admin-x-framework ==="
rg -n 'class.*Api|function.*Api|export.*api' packages/@tryghost/admin-x-framework/api --max-count=20 2>/dev/null || true

echo
echo "=== Check existing Admin API calls for headers/versioning ==="
rg -n 'Accept-Version|X-Ghost|authorization' packages/@tryghost/admin-x-framework --max-count=20 || true

Repository: TryGhost/Ghost

Length of output: 1643


🏁 Script executed:

#!/bin/bash
# Search for useBrowseMembersInfinite and other member hooks
echo "=== Member browse hooks ==="
rg -n 'useBrowseMembersInfinite|useMember|useEditMember|useCreateMember' --max-count=20

echo
echo "=== Check the hook implementation ==="
rg -n 'export.*useBrowseMembersInfinite' -A 10 --max-count=5

echo
echo "=== Compare: settings API usage in members-empty-state ==="
grep -n 'useBrowseSettings\|useCurrentUser' apps/posts/src/views/members/components/members-empty-state.tsx

echo
echo "=== Check framework package.json to understand dependencies ==="
cat package.json | grep -A 5 '@tryghost/admin-x-framework' || echo "Check if it's in workspace"

echo
echo "=== Look for member mutation/add operations in the posts app ==="
rg -n 'members' apps/posts/src --type=ts -g '*.ts' --max-count=20 -B 2 | grep -E '(hook|function|const.*=.*\(|mutation|add|create)' || true

Repository: TryGhost/Ghost

Length of output: 12417


🏁 Script executed:

#!/bin/bash
# Look at the full members.ts API file to see all exports
echo "=== Full members.ts API file ==="
cat apps/admin-x-framework/src/api/members.ts | head -200

echo
echo "=== Check for any create/add/mutation functions ==="
rg -n 'export.*create|export.*add|export.*mutation' apps/admin-x-framework/src/api/members.ts

echo
echo "=== Check how other mutations are done in the framework ==="
rg -n 'useMutation|createMutation' apps/admin-x-framework/src/api --max-count=20

Repository: TryGhost/Ghost

Length of output: 16926


🏁 Script executed:

#!/bin/bash
# Look at createMutation implementation
echo "=== createMutation implementation ==="
cat apps/admin-x-framework/src/utils/api/hooks.ts | head -300

echo
echo "=== Check for authentication/header handling ==="
rg -n 'Authorization|Accept-Version|headers' apps/admin-x-framework/src/utils/api --max-count=30 -A 2

echo
echo "=== Check if bare fetch in import-members-modal includes auth ==="
grep -A 20 'fetch.*members/upload' apps/posts/src/views/members/components/bulk-action-modals/import-members-modal.tsx

Repository: TryGhost/Ghost

Length of output: 14149


🏁 Script executed:

#!/bin/bash
# Check if members endpoint requires auth
echo "=== Check Admin API documentation/tests for members endpoint auth ==="
rg -n 'POST.*members' ghost/core/tests/api --max-count=10 -B 2 -A 2 | head -50

echo
echo "=== Verify if there are any tests for creating members ==="
rg -n 'create.*member\|add.*member' ghost/core/tests/api/members --max-count=15

Repository: TryGhost/Ghost

Length of output: 334


Use the framework's API abstraction or match its authentication headers.

This bare fetch call is missing critical headers that the framework normally provides. Specifically, it lacks credentials: 'include' (required for authentication), 'app-pragma': 'no-cache', and potential version headers. See import-members-modal.tsx for the correct bare-fetch pattern, or better yet, create a useCreateMembers mutation hook in the framework (similar to useAddTier, useAddNewsletter, etc.) to keep API concerns centralized.

Reference: Current bare fetch
const response = await fetch(`${apiRoot}/members/`, {
    method: 'POST',
    headers: {
        'Content-Type': 'application/json'
    },
    body: JSON.stringify({...})
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/posts/src/views/members/components/members-empty-state.tsx` around lines
27 - 39, The POST to `${apiRoot}/members/` in this component is missing the
framework's required fetch settings (e.g., credentials and cache/version
headers); update the call to include credentials: 'include' and add headers like
'app-pragma': 'no-cache' and the app/version header matching the pattern in
import-members-modal.tsx, or better yet, extract this logic into a new mutation
hook (e.g., useCreateMembers) mirroring existing hooks such as
useAddTier/useAddNewsletter so API auth/headers are centralized and reusable;
modify the code that calls getGhostPaths() and currentUser to use that hook or
the corrected fetch options.

@github-actions
Copy link
Copy Markdown
Contributor

E2E Tests Failed

To view the Playwright test report locally, run:

REPORT_DIR=$(mktemp -d) && gh run download 24662282217 -n playwright-report -D "$REPORT_DIR" && npx playwright show-report "$REPORT_DIR"

@weylandswart weylandswart requested a review from 9larsons as a code owner April 20, 2026 11:13
@sonarqubecloud
Copy link
Copy Markdown

Sign up for free to 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