diff --git a/apps/posts/src/views/members/components/members-empty-state.tsx b/apps/posts/src/views/members/components/members-empty-state.tsx new file mode 100644 index 00000000000..3334fdf3b13 --- /dev/null +++ b/apps/posts/src/views/members/components/members-empty-state.tsx @@ -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(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 + }] + }) + }); + + 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 ( +
+ + Membership settings + + } + description="Adjust your membership settings to start adding members." + title="Memberships have been disabled" + > + + +
+ ); + } + + return ( +
+
+ + +

+ Have members already?{' '} + Add them manually + {' '}or{' '} + +

+
+ } + description="Use memberships to allow your readers to sign up and subscribe to your content." + title="Start building your audience" + > + + + +
+ +
+
+

+ Building your audience with subscriber signups +

+

+ Learn how to turn anonymous visitors into logged-in members with memberships in Ghost. +

+ + Start building + + +
+
+ + + +
+
+ ); +}; + +export default MembersEmptyState; diff --git a/apps/posts/src/views/members/members.tsx b/apps/posts/src/views/members/members.tsx index eadd0ae5b1b..ae9d9e63fbe 100644 --- a/apps/posts/src/views/members/members.tsx +++ b/apps/posts/src/views/members/members.tsx @@ -1,5 +1,6 @@ import MembersActions from './components/members-actions'; import MembersContent from './components/members-content'; +import MembersEmptyState from './components/members-empty-state'; import MembersFilters from './components/members-filters'; import MembersHeader from './components/members-header'; import MembersHeaderSearch from './components/members-header-search'; @@ -187,26 +188,25 @@ const MembersPage: React.FC<{timezone: string}> = ({timezone}) => {
) : !data?.members.length ? ( -
- {hasFilterOrSearch ? ( - <> - - - - - - ) : ( - + hasFilterOrSearch ? ( +
+ clearAll({replace: false})} + > + Show all members + + } + title="No matching members found." + > - )} -
+
+ ) : ( + void refetch()} /> + ) ) : ( (({c } {actions && ( -
+
{actions}
)} diff --git a/e2e/helpers/pages/admin/members/members-list-page.ts b/e2e/helpers/pages/admin/members/members-list-page.ts index 0cc491cc6ab..169e47dc829 100644 --- a/e2e/helpers/pages/admin/members/members-list-page.ts +++ b/e2e/helpers/pages/admin/members/members-list-page.ts @@ -36,7 +36,7 @@ export class MembersListPage extends AdminPage implements MembersListSurface { this.newMemberButton = page.getByRole('link', {name: 'New member'}); this.filterButton = page.getByRole('button', {name: /^(Filter|Add filter)$/}); this.clearFiltersButton = page.getByRole('button', {name: 'Clear'}); - this.emptyState = page.getByText('No members yet'); + this.emptyState = page.getByText('Start building your audience'); this.noResults = page.getByText('No matching members found.'); this.showAllButton = page.getByRole('button', {name: 'Show all members'}); }