-
-
Notifications
You must be signed in to change notification settings - Fork 11.5k
Added missing empty state for members #27463
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,160 @@ | ||
| import React, {useCallback, useState} from 'react'; | ||
| import {Button, EmptyIndicator} from '@tryghost/shade/components'; | ||
| import {LucideIcon} from '@tryghost/shade/utils'; | ||
| import {getGhostPaths} from '@tryghost/admin-x-framework/helpers'; | ||
| import {getSettingValue, useBrowseSettings} from '@tryghost/admin-x-framework/api/settings'; | ||
| import {toast} from 'sonner'; | ||
| import {useCurrentUser} from '@tryghost/admin-x-framework/api/current-user'; | ||
| import {useNavigate} from '@tryghost/admin-x-framework'; | ||
|
|
||
| const MembersEmptyState: React.FC<{onMemberCreated?: () => void}> = ({onMemberCreated}) => { | ||
| 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 handleAddYourself = useCallback(async () => { | ||
| if (!currentUser || isAdding) { | ||
| return; | ||
| } | ||
|
|
||
| setIsAdding(true); | ||
| try { | ||
| 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 | ||
| }] | ||
| }) | ||
| }); | ||
|
Comment on lines
+27
to
+39
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 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' || trueRepository: 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 || trueRepository: 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=10Repository: 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 || trueRepository: 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)' || trueRepository: 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=20Repository: 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.tsxRepository: 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=15Repository: TryGhost/Ghost Length of output: 334 Use the framework's API abstraction or match its authentication headers. This bare Reference: Current bare fetchconst response = await fetch(`${apiRoot}/members/`, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({...})
});🤖 Prompt for AI Agents |
||
|
|
||
| if (!response.ok) { | ||
| throw new Error('Failed to create member'); | ||
| } | ||
|
|
||
| toast.success('You\'ve been added as a member'); | ||
| onMemberCreated?.(); | ||
| } catch { | ||
| toast.error('Failed to add member', { | ||
| description: 'There was a problem adding you as a member. Please try again.' | ||
| }); | ||
| } finally { | ||
| setIsAdding(false); | ||
| } | ||
| }, [currentUser, isAdding, onMemberCreated]); | ||
|
|
||
| if (!membershipsEnabled) { | ||
| return ( | ||
| <div className="flex h-full flex-col items-center justify-center px-4"> | ||
| <EmptyIndicator | ||
| actions={ | ||
| <Button variant="outline" asChild> | ||
| <a href="#/settings/members">Membership settings</a> | ||
| </Button> | ||
| } | ||
| description="Adjust your membership settings to start adding members." | ||
| title="Memberships have been disabled" | ||
| > | ||
| <LucideIcon.Users /> | ||
| </EmptyIndicator> | ||
| </div> | ||
| ); | ||
| } | ||
|
|
||
| return ( | ||
| <div className="flex h-full flex-col items-center justify-center px-4"> | ||
| <div className="flex max-w-lg flex-col items-center gap-3"> | ||
| <EmptyIndicator | ||
| actions={ | ||
| <div className="flex flex-col items-center gap-3"> | ||
| <Button | ||
| disabled={isAdding} | ||
| onClick={handleAddYourself} | ||
| > | ||
| {isAdding ? 'Adding...' : 'Add yourself as a member to test'} | ||
| </Button> | ||
| <p className="text-sm text-muted-foreground"> | ||
| Have members already?{' '} | ||
| <a className="font-medium text-foreground hover:underline" href="#/members/new">Add them manually</a> | ||
| {' '}or{' '} | ||
| <button | ||
| className="font-medium text-foreground hover:underline" | ||
| type="button" | ||
| onClick={() => navigate('/members/import')} | ||
| > | ||
| import from CSV | ||
| </button> | ||
| </p> | ||
| </div> | ||
| } | ||
| description="Use memberships to allow your readers to sign up and subscribe to your content." | ||
| title="Start building your audience" | ||
| > | ||
| <LucideIcon.Users /> | ||
| </EmptyIndicator> | ||
|
|
||
| <div className="mt-4 grid w-full grid-cols-1 gap-4 sm:grid-cols-2"> | ||
| <a | ||
| className="group flex flex-col overflow-hidden rounded-xl border bg-card transition-all hover:shadow-sm" | ||
| href="https://ghost.org/resources/build-audience-subscriber-signups/" | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| <div | ||
| className="h-36 w-full bg-cover bg-center" | ||
| style={{backgroundImage: `url(${assetRoot}img/marketing/members-1.jpg)`}} | ||
| /> | ||
| <div className="flex grow flex-col p-5"> | ||
| <h4 className="text-sm font-semibold"> | ||
| Building your audience with subscriber signups | ||
| </h4> | ||
| <p className="mt-1.5 text-sm leading-relaxed text-muted-foreground"> | ||
| Learn how to turn anonymous visitors into logged-in members with memberships in Ghost. | ||
| </p> | ||
| <span className="mt-3 inline-flex items-center gap-1 text-sm font-medium text-foreground"> | ||
| Start building | ||
| <LucideIcon.ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" /> | ||
| </span> | ||
| </div> | ||
| </a> | ||
|
|
||
| <a | ||
| className="group flex flex-col overflow-hidden rounded-xl border bg-card transition-all hover:shadow-sm" | ||
| href="https://ghost.org/resources/first-100-email-subscribers/" | ||
| rel="noopener noreferrer" | ||
| target="_blank" | ||
| > | ||
| <div | ||
| className="h-36 w-full bg-cover bg-center" | ||
| style={{backgroundImage: `url(${assetRoot}img/marketing/members-2.jpg)`}} | ||
| /> | ||
| <div className="flex grow flex-col p-5"> | ||
| <h4 className="text-sm font-semibold"> | ||
| Get your first 100 email subscribers | ||
| </h4> | ||
| <p className="mt-1.5 text-sm leading-relaxed text-muted-foreground"> | ||
| Starting from zero? Use this guide to find your founding audience members. | ||
| </p> | ||
| <span className="mt-3 inline-flex items-center gap-1 text-sm font-medium text-foreground"> | ||
| Become an expert | ||
| <LucideIcon.ArrowRight className="size-3.5 transition-transform group-hover:translate-x-0.5" /> | ||
| </span> | ||
| </div> | ||
| </a> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default MembersEmptyState; | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t treat unresolved settings as memberships enabled.
While
useBrowseSettingsis still loading,membersSignupAccessisundefined, soundefined !== 'none'renders the enabled CTA even on sites where memberships are disabled. Gate the branch until settings are loaded.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents