Skip to content
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
58 changes: 33 additions & 25 deletions client/dashboard/sites/site-launch-celebration-modal/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Button,
Modal,
} from '@wordpress/components';
import { useEvent } from '@wordpress/compose';
import { createInterpolateElement } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { copy, globe } from '@wordpress/icons';
Expand All @@ -21,50 +22,47 @@ interface SiteLaunchCelebrationModalProps {
site: Pick< Site, 'ID' | 'slug' | 'URL' | 'launch_status' > & {
plan?: Pick< Required< Site >[ 'plan' ], 'is_free' | 'product_slug' >;
};
onOpen?(): void;
onClose?(): void;
}

export default function SiteLaunchCelebrationModal( { site }: SiteLaunchCelebrationModalProps ) {
export default function SiteLaunchCelebrationModal( {
site,
onOpen: externalOnOpen,
onClose,
}: SiteLaunchCelebrationModalProps ) {
const [ isOpen, setIsOpen ] = useState( false );
const [ clipboardCopied, setClipboardCopied ] = useState( false );
const { recordTracksEvent } = useAnalytics();
const { queries } = useAppContext();
const { data: domains = [], isFetchedAfterMount: isDomainsFetched } = useQuery( {
const { data: domains = [], isFetchedAfterMount: isDomainsDataReady } = useQuery( {
...queries.domainsQuery(),
enabled: isOpen,
select: ( data ) => data.filter( ( domain ) => domain.blog_id === site.ID ),
} );
const copyButtonRef = useRef< HTMLButtonElement >( null );

const onOpen = useEvent( () => {
externalOnOpen?.();
setIsOpen( true );

// Track the modal view
recordTracksEvent( 'calypso_launchpad_celebration_modal_view', {
product_slug: site?.plan?.product_slug,
} );
} );

// Check if celebration modal should be shown based on URL param and site launch status
useEffect( () => {
const hasCelebrateLaunch = new URLSearchParams( window.location.search ).has(
'celebrateLaunch'
);
if ( site.launch_status === 'launched' && hasCelebrateLaunch ) {
setIsOpen( true );
}
}, [ site.launch_status ] );

useEffect( () => {
// Only run cleanup and analytics when modal is open
if ( ! isOpen ) {
return;
onOpen();
}
}, [ site.launch_status, onOpen ] );

// Remove the celebrateLaunch URL param without reloading the page
window.history.replaceState(
null,
'',
removeQueryArgs( window.location.href, 'celebrateLaunch' )
);

// Track the modal view
recordTracksEvent( 'calypso_launchpad_celebration_modal_view', {
product_slug: site?.plan?.product_slug,
} );
}, [ isOpen, site?.plan?.product_slug, recordTracksEvent ] );

if ( ! isOpen || ! isDomainsFetched ) {
if ( ! isOpen || ! isDomainsDataReady ) {
return null;
}

Expand Down Expand Up @@ -148,7 +146,17 @@ export default function SiteLaunchCelebrationModal( { site }: SiteLaunchCelebrat
className="celebration-modal"
title={ __( 'Congrats, your site is live!' ) }
size="medium"
onRequestClose={ () => setIsOpen( false ) }
onRequestClose={ () => {
setIsOpen( false );
onClose?.();

// Remove the celebrateLaunch URL param without reloading the page
window.history.replaceState(
null,
'',
removeQueryArgs( window.location.href, 'celebrateLaunch' )
);
} }
>
<ConfettiAnimation />
<VStack spacing={ 6 }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
* @jest-environment jsdom
*/

import { screen } from '@testing-library/react';
import { screen, waitFor } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import nock from 'nock';
import { render } from '../../../test-utils';
Expand Down Expand Up @@ -142,17 +142,22 @@ describe( '<SiteLaunchCelebrationModal>', () => {
} );

describe( 'Query Parameter Removal', () => {
test( 'removes celebrateLaunch query param when modal opens', async () => {
test( 'removes celebrateLaunch query param when modal closes', async () => {
setupCelebrateLaunchUrl();

const user = userEvent.setup();
const mockSite = createMockSite();
render( <SiteLaunchCelebrationModal site={ mockSite } /> );

// Wait for modal to render
await screen.findByRole( 'dialog' );

// Close the modal
await user.click( screen.getByRole( 'button', { name: 'Close' } ) );

// After component mounts and modal opens, the URL should not contain celebrateLaunch param
expect( window.location.href ).not.toContain( 'celebrateLaunch' );
await waitFor( () => {
expect( window.location.href ).not.toContain( 'celebrateLaunch' );
} );
} );
} );

Expand Down
40 changes: 39 additions & 1 deletion client/layout/index.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,19 @@ import { getSidebarType, SidebarType } from 'calypso/state/global-sidebar/select
import { isUserNewerThan, WEEK_IN_MILLISECONDS } from 'calypso/state/guided-tours/contexts';
import { getCurrentOAuth2Client } from 'calypso/state/oauth2-clients/ui/selectors';
import { isReaderMSDEnabled } from 'calypso/state/reader-ui/selectors';
import getInitialQueryArguments from 'calypso/state/selectors/get-initial-query-arguments';
import getIsBlazePro from 'calypso/state/selectors/get-is-blaze-pro';
import getPrimarySiteId from 'calypso/state/selectors/get-primary-site-id';
import hasGravatarDomainQueryParam from 'calypso/state/selectors/has-gravatar-domain-query-param';
import isAtomicSite from 'calypso/state/selectors/is-site-automated-transfer';
import isWooJPCFlow from 'calypso/state/selectors/is-woo-jpc-flow';
import { getIsOnboardingAffiliateFlow } from 'calypso/state/signup/flow/selectors';
import { isJetpackSite } from 'calypso/state/sites/selectors';
import getSite from 'calypso/state/sites/selectors/get-site';
import { isSupportSession } from 'calypso/state/support/selectors';
import { getCurrentLayoutFocus } from 'calypso/state/ui/layout-focus/selectors';
import {
getMostRecentlySelectedSiteId,
getSelectedSiteId,
getSidebarIsCollapsed,
masterbarIsVisible,
Expand Down Expand Up @@ -144,6 +148,7 @@ class Layout extends Component {
super( props );
this.state = {
isDesktop: isWithinBreakpoint( '>=782px' ),
initiallyUnlaunchedSite: false,
};
}

Expand All @@ -164,6 +169,15 @@ class Layout extends Component {
refreshColorScheme( prevProps.colorScheme, this.props.colorScheme );
}

static getDerivedStateFromProps( props ) {
if ( props.site?.launch_status === 'unlaunched' ) {
return {
initiallyUnlaunchedSite: true,
};
}
return null;
}

renderMasterbar( loadHelpCenterIcon ) {
if ( this.props.masterbarIsHidden ) {
return <EmptyMasterbar />;
Expand Down Expand Up @@ -202,6 +216,7 @@ class Layout extends Component {
/>
) }
<MasterbarComponent
siteId={ this.props.siteId }
section={ this.props.sectionGroup }
isCheckout={ this.props.sectionName === 'checkout' }
isCheckoutPending={ this.props.sectionName === 'checkout-pending' }
Expand All @@ -213,6 +228,20 @@ class Layout extends Component {
);
}

renderCelebrateSiteLaunchModal() {
if ( ! this.state.initiallyUnlaunchedSite && ! this.props.hasCelebrateLaunchQueryParam ) {
return null;
}

return (
<AsyncLoad
require="calypso/my-sites/customer-home/celebrate-site-launch-modal"
placeholder={ null }
siteId={ this.props.siteId }
/>
);
}

render() {
const sectionClass = clsx( 'layout', `focus-${ this.props.currentLayoutFocus }`, {
[ 'is-group-' + this.props.sectionGroup ]: this.props.sectionGroup,
Expand Down Expand Up @@ -360,6 +389,7 @@ class Layout extends Component {
{ ! this.props.isMSDEnabledForReader && (
<AsyncLoad require="calypso/layout/global-notifications" placeholder={ null } />
) }
{ this.renderCelebrateSiteLaunchModal() }
</div>
);
}
Expand All @@ -370,7 +400,13 @@ export default withCurrentRoute(
const dashboard = getDashboardFromHostname( window?.location?.hostname );
const sectionGroup = currentSection?.group ?? null;
const sectionName = currentSection?.name ?? null;
const siteId = getSelectedSiteId( state );

// Falls back to using the user's primary site if no site has been selected
// by the user yet
const siteId =
getSelectedSiteId( state ) ||
getMostRecentlySelectedSiteId( state ) ||
getPrimarySiteId( state );
const sectionJitmPath = getMessagePathForJITM( currentRoute );
const isJetpackLogin = currentRoute.startsWith( '/log-in/jetpack' );
const isJetpack =
Expand Down Expand Up @@ -484,11 +520,13 @@ export default withCurrentRoute(
sectionGroup,
sectionName,
sectionJitmPath,
hasCelebrateLaunchQueryParam: getInitialQueryArguments( state )?.celebrateLaunch === 'true',
currentLayoutFocus: getCurrentLayoutFocus( state ),
colorScheme,
needsColorScheme,
isFetchingColorScheme: isFetchingAdminColor( state, siteId ),
siteId,
site: getSite( state, siteId ),
// We avoid requesting sites in the Jetpack Connect authorization step, because this would
// request all sites before authorization has finished. That would cause the "all sites"
// request to lack the newly authorized site, and when the request finishes after
Expand Down
2 changes: 2 additions & 0 deletions client/layout/masterbar/item.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ interface MasterbarItemProps {
variant?: string;
ariaLabel?: string;
openSubMenuOnClick?: boolean;
isBusy?: boolean;
}

class MasterbarItem extends Component< MasterbarItemProps > {
Expand Down Expand Up @@ -244,6 +245,7 @@ class MasterbarItem extends Component< MasterbarItemProps > {
onMouseEnter: this.preload,
disabled: this.props.disabled,
'aria-label': this.props.ariaLabel,
isBusy: this.props.isBusy,
};

return (
Expand Down
8 changes: 2 additions & 6 deletions client/layout/masterbar/logged-in.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ import { getPreference } from 'calypso/state/preferences/selectors';
import getCurrentRoute from 'calypso/state/selectors/get-current-route';
import getEditorUrl from 'calypso/state/selectors/get-editor-url';
import getPreviousRoute from 'calypso/state/selectors/get-previous-route';
import getPrimarySiteId from 'calypso/state/selectors/get-primary-site-id';
import getSiteMigrationStatus from 'calypso/state/selectors/get-site-migration-status';
import hasGravatarDomainQueryParam from 'calypso/state/selectors/has-gravatar-domain-query-param';
import isDomainOnlySite from 'calypso/state/selectors/is-domain-only-site';
Expand Down Expand Up @@ -57,7 +56,7 @@ import isSimpleSite from 'calypso/state/sites/selectors/is-simple-site';
import { isSupportSession } from 'calypso/state/support/selectors';
import { activateNextLayoutFocus, setNextLayoutFocus } from 'calypso/state/ui/layout-focus/actions';
import { getCurrentLayoutFocus } from 'calypso/state/ui/layout-focus/selectors';
import { getMostRecentlySelectedSiteId, getSectionGroup } from 'calypso/state/ui/selectors';
import { getSectionGroup } from 'calypso/state/ui/selectors';
import Item from './item';
import Masterbar from './masterbar';
import { AgentsManagerIcon } from './masterbar-agents-manager/agents-manager-icon';
Expand Down Expand Up @@ -848,12 +847,9 @@ class MasterbarLoggedIn extends Component {
export { MasterbarLoggedIn };

export default connect(
( state ) => {
( state, { siteId } ) => {
const sectionGroup = getSectionGroup( state );

// Falls back to using the user's primary site if no site has been selected
// by the user yet
const siteId = getMostRecentlySelectedSiteId( state ) || getPrimarySiteId( state );
const sitePlanSlug = getSitePlanSlug( state, siteId );
const isMigrationInProgress =
isSiteMigrationInProgress( state, siteId ) || isSiteMigrationActiveRoute( state );
Expand Down
Loading
Loading