Skip to content
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

Subscribers: Add subscribers-dataviews flag and component #97946

Merged
merged 10 commits into from
Jan 14, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { default as SubscriberDataViews } from './subscriber-data-views';
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
@import '@wordpress/base-styles/breakpoints';
@import '@wordpress/dataviews/build-style/style.css';

.subscribers.subscribers--dataviews.main {
.navigation-header {
padding: 0 48px;

@media ( max-width: $break-small ) {
padding: 0 24px;
}
}
}

.subscriber-data-views {
.subscriber-profile.subscriber-profile--compact {
@media ( max-width: $break-xlarge ) {
max-width: 225px;
}

.subscriber-profile__user-details {
.subscriber-profile__name {
text-align: left;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,201 @@
import { useBreakpoint } from '@automattic/viewport-react';
import { DataViews } from '@wordpress/dataviews';
import { useTranslate } from 'i18n-calypso';
import { useMemo } from 'react';
import TimeSince from 'calypso/components/time-since';
import { EmptyListView } from 'calypso/my-sites/subscribers/components/empty-list-view';
import { SubscriberLaunchpad } from 'calypso/my-sites/subscribers/components/subscriber-launchpad';
import { SubscriberProfile } from 'calypso/my-sites/subscribers/components/subscriber-profile';
import { useSubscribersPage } from 'calypso/my-sites/subscribers/components/subscribers-page/subscribers-page-context';
import { useSubscriptionPlans } from 'calypso/my-sites/subscribers/hooks';
import { Subscriber } from 'calypso/my-sites/subscribers/types';
import { useSelector } from 'calypso/state';
import isAtomicSite from 'calypso/state/selectors/is-site-automated-transfer';
import { isSimpleSite } from 'calypso/state/sites/selectors';
import { SubscribersSortBy } from '../../constants';
import type { View, Field, Action } from '@wordpress/dataviews';
import './style.scss';

const SubscriptionTypeCell = ( { subscriber }: { subscriber: Subscriber } ) => {
const plans = useSubscriptionPlans( subscriber );
Copy link
Member Author

Choose a reason for hiding this comment

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

I found that we're not returning plans data from the endpoint used here. So this is how it works in the individual subscriber page, but it's not actually working yet.

return (
<>
{ plans.map( ( plan, index ) => (
<div key={ index }>{ plan.plan }</div>
) ) }
</>
);
};

type SubscriberDataViewsProps = {
siteId: number | null;
onClickView: ( subscriber: Subscriber ) => void;
onClickUnsubscribe: ( subscriber: Subscriber ) => void;
onGiftSubscription: ( subscriber: Subscriber ) => void;
};

const SubscriberDataViews = ( {
siteId,
onClickView,
onClickUnsubscribe,
}: SubscriberDataViewsProps ) => {
const translate = useTranslate();
const isMobile = useBreakpoint( '<1040px' );
const {
grandTotal,
page,
pageChangeCallback,
searchTerm,
isLoading,
subscribers,
pages,
isOwnerSubscribed,
perPage,
setPerPage,
handleSearch,
sortTerm,
setSortTerm,
} = useSubscribersPage();

const isSimple = useSelector( isSimpleSite );
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm preserving this empty view logic, but we could refactor or replace it.

const isAtomic = useSelector( ( state ) => isAtomicSite( state, siteId ) );
const EmptyComponent = isSimple || isAtomic ? SubscriberLaunchpad : EmptyListView;
const shouldShowLaunchpad =
! isLoading && ! searchTerm && ( ! grandTotal || ( grandTotal === 1 && isOwnerSubscribed ) );

const fields = useMemo< Field< Subscriber >[] >( () => {
const baseFields = [
{
id: 'name',
label: translate( 'Name' ),
getValue: ( { item }: { item: Subscriber } ) => item.display_name,
render: ( { item }: { item: Subscriber } ) => (
<button onClick={ () => onClickView( item ) }>
<SubscriberProfile
avatar={ item.avatar }
displayName={ item.display_name }
email={ item.email_address }
url={ item.url }
/>
</button>
),
enableHiding: false,
enableSorting: true,
},
];

if ( ! isMobile ) {
baseFields.push(
{
id: 'subscription_type',
label: translate( 'Subscription type' ),
getValue: ( { item }: { item: Subscriber } ) => ( item.plans?.length ? 'Paid' : 'Free' ),
render: ( { item }: { item: Subscriber } ) => (
<SubscriptionTypeCell subscriber={ item } />
),
enableHiding: false,
enableSorting: true,
},
{
id: 'date_subscribed',
label: translate( 'Since' ),
getValue: ( { item }: { item: Subscriber } ) => item.date_subscribed,
render: ( { item }: { item: Subscriber } ) => <TimeSince date={ item.date_subscribed } />,
enableHiding: false,
enableSorting: true,
}
);
}

return baseFields;
}, [ translate, onClickView, isMobile ] );

const actions = useMemo< Action< Subscriber >[] >(
() => [
{
id: 'view',
label: translate( 'View' ),
callback: ( items: Subscriber[] ) => onClickView( items[ 0 ] ),
isPrimary: true,
},
{
id: 'remove',
label: translate( 'Remove' ),
callback: ( items: Subscriber[] ) => onClickUnsubscribe( items[ 0 ] ),
},
],
[ translate, onClickView, onClickUnsubscribe ]
);

const handleViewChange = ( newView: View ) => {
if ( typeof newView.page === 'number' && newView.page !== page ) {
pageChangeCallback( newView.page );
}

if ( typeof newView.perPage === 'number' && newView.perPage !== perPage ) {
setPerPage( newView.perPage );
pageChangeCallback( 1 );
}

if ( typeof newView.search === 'string' && newView.search !== searchTerm ) {
handleSearch( newView.search );
}

if ( newView.sort?.field ) {
const newSortTerm =
newView.sort.field === 'name' ? SubscribersSortBy.Name : SubscribersSortBy.DateSubscribed;
if ( newSortTerm !== sortTerm ) {
setSortTerm( newSortTerm );
}
}
};

const currentView = useMemo< View >(
() => ( {
type: 'table',
layout: {},
search: searchTerm,
page,
perPage,
sort: {
field: sortTerm === SubscribersSortBy.Name ? 'name' : 'date_subscribed',
direction: 'desc',
},
} ),
[ searchTerm, page, perPage, sortTerm ]
);

const { data, paginationInfo } = useMemo( () => {
return {
data: subscribers,
paginationInfo: {
totalItems: grandTotal,
totalPages: pages ?? 0,
},
};
}, [ subscribers, grandTotal, pages ] );

return (
<section className="subscriber-data-views">
{ shouldShowLaunchpad ? (
<EmptyComponent />
) : (
<DataViews< Subscriber >
data={ data }
fields={ fields }
view={ currentView }
onChangeView={ handleViewChange }
isLoading={ isLoading }
paginationInfo={ paginationInfo }
getItemId={ ( item: Subscriber ) => item.subscription_id.toString() }
defaultLayouts={ { table: {} } }
actions={ actions }
search
searchLabel={ translate( 'Search by name, username or email…' ) }
/>
) }
</section>
);
};

export default SubscriberDataViews;
32 changes: 25 additions & 7 deletions client/my-sites/subscribers/main.tsx
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { isEnabled } from '@automattic/calypso-config';
import page from '@automattic/calypso-router';
import { Button, Gridicon } from '@automattic/components';
import { HelpCenter, Subscriber as SubscriberDataStore } from '@automattic/data-stores';
Expand All @@ -14,6 +15,7 @@ import NavigationHeader from 'calypso/components/navigation-header';
import SubscriberValidationGate from 'calypso/components/subscribers-validation-gate';
import isJetpackCloud from 'calypso/lib/jetpack/is-jetpack-cloud';
import GiftSubscriptionModal from 'calypso/my-sites/subscribers/components/gift-modal/gift-modal';
import { SubscriberDataViews } from 'calypso/my-sites/subscribers/components/subscriber-data-views';
import { SubscriberListContainer } from 'calypso/my-sites/subscribers/components/subscriber-list-container';
import {
SubscribersPageProvider,
Expand Down Expand Up @@ -179,20 +181,36 @@ const SubscribersPage = ( {
sortTermChanged={ sortTermChanged }
>
<QueryMembershipsSettings siteId={ siteId ?? 0 } source="calypso" />
<Main wideLayout className="subscribers">
<Main
wideLayout
className={ `subscribers${
isEnabled( 'subscribers-dataviews' ) ? ' subscribers--dataviews' : ''
}` }
>
<DocumentHead title={ translate( 'Subscribers' ) } />

<SubscribersHeader
selectedSiteId={ selectedSite?.ID }
disableCta={ isUnverified || isStagingSite }
/>
<SubscriberValidationGate siteId={ siteId }>
<SubscriberListContainer
siteId={ siteId }
onClickView={ onClickView }
onGiftSubscription={ onGiftSubscription }
onClickUnsubscribe={ onClickUnsubscribe }
/>
{ isEnabled( 'subscribers-dataviews' ) ? (
Copy link
Member Author

Choose a reason for hiding this comment

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

I'm showing the new DataViews component when the flag is enabled. I repurposed a lot from the existing SubscriberListContainer component.

// Your new dataviews component
<SubscriberDataViews
siteId={ siteId }
onClickView={ onClickView }
onGiftSubscription={ onGiftSubscription }
onClickUnsubscribe={ onClickUnsubscribe }
/>
) : (
// Existing subscriber list
<SubscriberListContainer
siteId={ siteId }
onClickView={ onClickView }
onGiftSubscription={ onGiftSubscription }
onClickUnsubscribe={ onClickUnsubscribe }
/>
) }

<UnsubscribeModal
subscriber={ currentSubscriber }
Expand Down
Loading