diff --git a/client/a8c-for-agencies/components/select-site-button/button.tsx b/client/a8c-for-agencies/components/select-site-button/button.tsx new file mode 100644 index 000000000000..903d33ec365b --- /dev/null +++ b/client/a8c-for-agencies/components/select-site-button/button.tsx @@ -0,0 +1,60 @@ +import { Button } from '@wordpress/components'; +import { useTranslate } from 'i18n-calypso'; +import { useCallback, useState } from 'react'; +import { useDispatch } from 'calypso/state'; +import { recordTracksEvent } from 'calypso/state/analytics/actions'; +import SelectSiteModal from './modal'; +import type { ReactNode } from 'react'; + +export type SelectSiteButtonProps = { + onSiteSelect: ( siteId: number, siteDomain: string ) => void; + buttonLabel?: string; + modalTitle?: string; + modalSubtitle?: string; + helpText?: ReactNode; + trackingEvent?: string; + className?: string; +}; + +const SelectSiteButton = ( { + onSiteSelect, + buttonLabel, + modalTitle, + modalSubtitle, + helpText, + trackingEvent = 'calypso_select_site_button_click', + className, +}: SelectSiteButtonProps ) => { + const translate = useTranslate(); + const dispatch = useDispatch(); + const [ isOpen, setIsOpen ] = useState( false ); + + const handleOpenModal = useCallback( () => { + setIsOpen( true ); + dispatch( recordTracksEvent( trackingEvent ) ); + }, [ dispatch, trackingEvent ] ); + + return ( + <> + + + { isOpen && ( + setIsOpen( false ) } + onSiteSelect={ onSiteSelect } + title={ modalTitle } + subtitle={ helpText || modalSubtitle } + /> + ) } + + ); +}; + +export default SelectSiteButton; diff --git a/client/a8c-for-agencies/components/select-site-button/index.tsx b/client/a8c-for-agencies/components/select-site-button/index.tsx new file mode 100644 index 000000000000..d9e728e9c7ea --- /dev/null +++ b/client/a8c-for-agencies/components/select-site-button/index.tsx @@ -0,0 +1,4 @@ +import './style.scss'; + +export { default } from './button'; +export type { SelectSiteButtonProps } from './button'; diff --git a/client/a8c-for-agencies/components/select-site-button/modal.tsx b/client/a8c-for-agencies/components/select-site-button/modal.tsx new file mode 100644 index 000000000000..6093ad98e1a5 --- /dev/null +++ b/client/a8c-for-agencies/components/select-site-button/modal.tsx @@ -0,0 +1,66 @@ +import { Button } from '@wordpress/components'; +import { useTranslate } from 'i18n-calypso'; +import { useState } from 'react'; +import A4AModal from 'calypso/a8c-for-agencies/components/a4a-modal'; +import { A4A_SITES_LINK } from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants'; +import { useDispatch } from 'calypso/state'; +import { recordTracksEvent } from 'calypso/state/analytics/actions'; +import SelectSiteTable, { type SiteItem } from './site-table'; +import type { ReactNode } from 'react'; + +type SelectSiteModalProps = { + onClose: () => void; + onSiteSelect: ( siteId: number, siteDomain: string ) => void; + title?: string; + subtitle?: ReactNode; +}; + +const SelectSiteModal = ( { onClose, onSiteSelect, title, subtitle }: SelectSiteModalProps ) => { + const translate = useTranslate(); + const dispatch = useDispatch(); + + const [ selectedSite, setSelectedSite ] = useState< SiteItem | null >( null ); + + const handleSelectSite = () => { + if ( selectedSite ) { + onSiteSelect( selectedSite.id, selectedSite.site ); + onClose(); + } + }; + + return ( + + dispatch( + recordTracksEvent( 'calypso_select_site_modal_sites_dashboard_click' ) + ) + } + /> + ), + }, + } + ) + } + onClose={ onClose } + extraActions={ + + } + > + + + ); +}; + +export default SelectSiteModal; diff --git a/client/a8c-for-agencies/components/select-site-button/site-table.tsx b/client/a8c-for-agencies/components/select-site-button/site-table.tsx new file mode 100644 index 000000000000..47902a01ae26 --- /dev/null +++ b/client/a8c-for-agencies/components/select-site-button/site-table.tsx @@ -0,0 +1,91 @@ +import { filterSortAndPaginate } from '@wordpress/dataviews'; +import { useTranslate } from 'i18n-calypso'; +import { useCallback, useMemo, useState } from 'react'; +import A4ATablePlaceholder from 'calypso/a8c-for-agencies/components/a4a-table-placeholder'; +import { initialDataViewsState } from 'calypso/a8c-for-agencies/components/items-dashboard/constants'; +import ItemsDataViews from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews'; +import { DataViewsState } from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews/interfaces'; +import { + useFetchAllManagedSites, + type SiteItem, +} from 'calypso/a8c-for-agencies/sections/migrations/hooks/use-fetch-all-managed-sites'; +import FormRadio from 'calypso/components/forms/form-radio'; +import { useDispatch } from 'calypso/state'; +import { recordTracksEvent } from 'calypso/state/analytics/actions'; + +type SelectSiteTableProps = { + selectedSite: SiteItem | null; + setSelectedSite: ( site: SiteItem | null ) => void; +}; + +const SelectSiteTable = ( { selectedSite, setSelectedSite }: SelectSiteTableProps ) => { + const translate = useTranslate(); + const dispatch = useDispatch(); + + const { items, isLoading } = useFetchAllManagedSites(); + + const [ dataViewsState, setDataViewsState ] = useState< DataViewsState >( { + ...initialDataViewsState, + fields: [ 'site' ], + } ); + + const onSelectSite = useCallback( + ( item: SiteItem ) => { + setSelectedSite( item ); + dispatch( recordTracksEvent( 'calypso_select_site_table_select_site_click' ) ); + }, + [ dispatch, setSelectedSite ] + ); + + const fields = useMemo( () => { + const siteColumn = { + id: 'site', + label: translate( 'Site' ), + getValue: ( { item }: { item: SiteItem } ) => item.site, + render: ( { item }: { item: SiteItem } ) => ( +
+ onSelectSite( item ) } + label={ item.site } + /> +
+ ), + enableGlobalSearch: true, + enableHiding: false, + enableSorting: false, + }; + + return [ siteColumn ]; + }, [ onSelectSite, selectedSite?.id, translate ] ); + + const { data: allSites, paginationInfo } = useMemo( () => { + return filterSortAndPaginate( items as SiteItem[], dataViewsState, fields ); + }, [ items, dataViewsState, fields ] ); + + return ( +
+ { isLoading ? ( + + ) : ( + `${ item.id }`, + pagination: paginationInfo, + enableSearch: true, + actions: [], + dataViewsState: dataViewsState, + setDataViewsState: setDataViewsState, + defaultLayouts: { table: {} }, + } } + /> + ) } +
+ ); +}; + +export default SelectSiteTable; diff --git a/client/a8c-for-agencies/components/select-site-button/style.scss b/client/a8c-for-agencies/components/select-site-button/style.scss new file mode 100644 index 000000000000..87ef08032788 --- /dev/null +++ b/client/a8c-for-agencies/components/select-site-button/style.scss @@ -0,0 +1,11 @@ +.redesigned-a8c-table { + &.show-overflow-overlay { + overflow: visible; + } + + &.search-enabled { + .dataviews-module__table-header { + padding-top: 16px; + } + } +} diff --git a/client/a8c-for-agencies/components/sidebar-menu/hooks/use-main-menu-items.tsx b/client/a8c-for-agencies/components/sidebar-menu/hooks/use-main-menu-items.tsx index 08ceeef314ef..07c2b0f5f95b 100644 --- a/client/a8c-for-agencies/components/sidebar-menu/hooks/use-main-menu-items.tsx +++ b/client/a8c-for-agencies/components/sidebar-menu/hooks/use-main-menu-items.tsx @@ -11,6 +11,7 @@ import { people, starEmpty, plugins, + chartBar, } from '@wordpress/icons'; import { useTranslate } from 'i18n-calypso'; import { useMemo } from 'react'; @@ -36,8 +37,12 @@ import { A4A_AGENCY_TIER_LINK, A4A_MIGRATIONS_OVERVIEW_LINK, A4A_WOOPAYMENTS_LINK, + A4A_REPORTS_LINK, + A4A_REPORTS_OVERVIEW_LINK, + A4A_REPORTS_DASHBOARD_LINK, } from '../lib/constants'; import { createItem } from '../lib/utils'; +import type { SidebarMenuItem } from '../lib/types'; const useMainMenuItems = ( path: string ) => { const translate = useTranslate(); @@ -45,7 +50,7 @@ const useMainMenuItems = ( path: string ) => { const agency = useSelector( getActiveAgency ); const menuItems = useMemo( () => { - let referralItems = [] as any[]; + let referralItems = [] as SidebarMenuItem[]; if ( isSectionNameEnabled( 'a8c-for-agencies-referrals' ) ) { referralItems = [ @@ -73,7 +78,22 @@ const useMainMenuItems = ( path: string ) => { }, withChevron: true, } - : {}; + : ( {} as SidebarMenuItem ); + + const reportsSubMenuItems: SidebarMenuItem[] = [ + { + icon: home, + link: A4A_REPORTS_OVERVIEW_LINK, + title: translate( 'Overview' ), + trackEventProps: { menu_item: 'Automattic for Agencies / Reports / Overview' }, + }, + { + icon: category, + link: A4A_REPORTS_DASHBOARD_LINK, + title: translate( 'Dashboard' ), + trackEventProps: { menu_item: 'Automattic for Agencies / Reports / Dashboard' }, + }, + ]; return [ { @@ -119,12 +139,13 @@ const useMainMenuItems = ( path: string ) => { migrationMenuItem, { icon: WooPayments, - path: '/', + path: A4A_WOOPAYMENTS_LINK, link: A4A_WOOPAYMENTS_LINK, title: translate( 'WooPayments' ), trackEventProps: { menu_item: 'Automattic for Agencies / WooPayments', }, + withChevron: true, }, ...( isSectionNameEnabled( 'a8c-for-agencies-plugins' ) ? [ @@ -139,6 +160,22 @@ const useMainMenuItems = ( path: string ) => { }, ] : [] ), + { + icon: chartBar, + path: A4A_REPORTS_LINK, + link: A4A_REPORTS_OVERVIEW_LINK, + title: ( + <> + { translate( 'Reports' ) } + { translate( 'Beta' ) } + + ), + trackEventProps: { + menu_item: 'Automattic for Agencies / Reports', + }, + withChevron: true, + subMenuItems: reportsSubMenuItems.map( ( item ) => createItem( item, path ) ), + }, ...( config.isEnabled( 'a4a-partner-directory' ) || config.isEnabled( 'a8c-for-agencies-agency-tier' ) ? [ @@ -194,7 +231,8 @@ const useMainMenuItems = ( path: string ) => { : [] ), ] .map( ( item ) => createItem( item, path ) ) - .filter( ( item ) => isPathAllowed( item.link, agency ) ); + .filter( ( item ) => isPathAllowed( item.link, agency ) ) + .filter( Boolean ) as SidebarMenuItem[]; }, [ agency, path, translate ] ); return menuItems; }; diff --git a/client/a8c-for-agencies/components/sidebar-menu/hooks/use-reports-menu-items.tsx b/client/a8c-for-agencies/components/sidebar-menu/hooks/use-reports-menu-items.tsx new file mode 100644 index 000000000000..1b43ffc4c750 --- /dev/null +++ b/client/a8c-for-agencies/components/sidebar-menu/hooks/use-reports-menu-items.tsx @@ -0,0 +1,50 @@ +import { category, home } from '@wordpress/icons'; +import { useTranslate } from 'i18n-calypso'; +import { useMemo } from 'react'; +import { + A4A_REPORTS_LINK, + A4A_REPORTS_DASHBOARD_LINK, + A4A_REPORTS_OVERVIEW_LINK, +} from '../lib/constants'; +import { createItem } from '../lib/utils'; + +const useReportsMenuItems = ( path: string ) => { + const translate = useTranslate(); + + const menuItems = useMemo( () => { + return [ + createItem( + { + icon: home, + path: A4A_REPORTS_LINK, + link: A4A_REPORTS_OVERVIEW_LINK, + title: translate( 'Overview' ), + trackEventProps: { + menu_item: 'Automattic for Agencies / Reports / Overview', + }, + }, + path + ), + createItem( + { + icon: category, + path: A4A_REPORTS_LINK, + link: A4A_REPORTS_DASHBOARD_LINK, + title: translate( 'Dashboard' ), + trackEventProps: { + menu_item: 'Automattic for Agencies / Reports / Dashboard', + }, + }, + path + ), + ] + .map( ( item ) => createItem( item, path ) ) + .map( ( item ) => ( { + ...item, + isSelected: item.link === path, + } ) ); + }, [ path, translate ] ); + return menuItems; +}; + +export default useReportsMenuItems; diff --git a/client/a8c-for-agencies/components/sidebar-menu/lib/constants.ts b/client/a8c-for-agencies/components/sidebar-menu/lib/constants.ts index c24941235873..2b07d58f549a 100644 --- a/client/a8c-for-agencies/components/sidebar-menu/lib/constants.ts +++ b/client/a8c-for-agencies/components/sidebar-menu/lib/constants.ts @@ -53,6 +53,11 @@ export const A4A_WOOPAYMENTS_DASHBOARD_LINK = `${ A4A_WOOPAYMENTS_LINK }/dashboa export const A4A_WOOPAYMENTS_PAYMENT_SETTINGS_LINK = `${ A4A_WOOPAYMENTS_LINK }/payment-settings`; export const A4A_WOOPAYMENTS_SITE_SETUP_LINK = `${ A4A_WOOPAYMENTS_LINK }/site-setup`; export const A4A_WOOPAYMENTS_OVERVIEW_LINK = `${ A4A_WOOPAYMENTS_LINK }/overview`; +export const A4A_REPORTS_LINK = '/reports'; +export const A4A_REPORTS_OVERVIEW_LINK = `${ A4A_REPORTS_LINK }/overview`; +export const A4A_REPORTS_DASHBOARD_LINK = `${ A4A_REPORTS_LINK }/dashboard`; +export const A4A_REPORTS_BUILD_LINK = `${ A4A_REPORTS_LINK }/build`; +export const A4A_REPORTS_EXAMPLE_LINK = `${ A4A_REPORTS_LINK }/example`; // Client export const A4A_CLIENT_LANDING_LINK = '/client/landing'; diff --git a/client/a8c-for-agencies/components/sidebar-menu/reports.tsx b/client/a8c-for-agencies/components/sidebar-menu/reports.tsx new file mode 100644 index 000000000000..e3339825bf04 --- /dev/null +++ b/client/a8c-for-agencies/components/sidebar-menu/reports.tsx @@ -0,0 +1,36 @@ +import page from '@automattic/calypso-router'; +import { chevronLeft } from '@wordpress/icons'; +import { useTranslate } from 'i18n-calypso'; +import Sidebar from '../sidebar'; +import useReportsMenuItems from './hooks/use-reports-menu-items'; +import { A4A_OVERVIEW_LINK, A4A_REPORTS_LINK } from './lib/constants'; + +type Props = { + path: string; +}; + +export default function ReportsSidebar( { path }: Props ) { + const translate = useTranslate(); + const menuItems = useReportsMenuItems( path ); + + return ( + + { translate( 'Reports' ) } + { translate( 'Beta' ) } + + } + backButtonProps={ { + label: translate( 'Back to overview' ), + icon: chevronLeft, + onClick: () => { + page( A4A_OVERVIEW_LINK ); + }, + } } + menuItems={ menuItems } + withUserProfileFooter + /> + ); +} diff --git a/client/a8c-for-agencies/components/sidebar/style.scss b/client/a8c-for-agencies/components/sidebar/style.scss index 8e7a395093b7..1b8bf6bd554f 100644 --- a/client/a8c-for-agencies/components/sidebar/style.scss +++ b/client/a8c-for-agencies/components/sidebar/style.scss @@ -65,3 +65,19 @@ list-style: none; margin: 0; } + +.sidebar-menu__beta-badge { + display: inline-flex; + align-items: center; + height: 16px; + line-height: 16px; + padding: 0 6px; + border-radius: 4px; + margin-left: 8px; + box-sizing: border-box; + font-size: 11px; + background-color: var(--color-primary-50); + color: var(--color-text-inverted); + font-weight: 500; + text-transform: uppercase; +} diff --git a/client/a8c-for-agencies/lib/permission.ts b/client/a8c-for-agencies/lib/permission.ts index 855dd421e67b..f8e49711cda1 100644 --- a/client/a8c-for-agencies/lib/permission.ts +++ b/client/a8c-for-agencies/lib/permission.ts @@ -47,6 +47,7 @@ import { A4A_WOOPAYMENTS_PAYMENT_SETTINGS_LINK, A4A_WOOPAYMENTS_SITE_SETUP_LINK, A4A_WOOPAYMENTS_OVERVIEW_LINK, + A4A_REPORTS_LINK, } from '../components/sidebar-menu/lib/constants'; import type { Agency } from 'calypso/state/a8c-for-agencies/types'; @@ -99,6 +100,7 @@ const MEMBER_ACCESSIBLE_PATHS: Record< string, string[] > = { [ A4A_WOOPAYMENTS_PAYMENT_SETTINGS_LINK ]: [ 'a4a_read_referrals' ], [ A4A_WOOPAYMENTS_SITE_SETUP_LINK ]: [ 'a4a_read_referrals' ], [ A4A_WOOPAYMENTS_OVERVIEW_LINK ]: [ 'a4a_read_referrals' ], + [ A4A_REPORTS_LINK ]: [ 'a4a_read_managed_sites' ], }; const MEMBER_ACCESSIBLE_DYNAMIC_PATHS: Record< string, string[] > = { @@ -108,6 +110,7 @@ const MEMBER_ACCESSIBLE_DYNAMIC_PATHS: Record< string, string[] > = { licenses: [ 'a4a_jetpack_licensing' ], plugins: [ 'a4a_read_managed_sites' ], referrals: [ 'a4a_read_referrals' ], + reports: [ 'a4a_read_managed_sites' ], }; const DYNAMIC_PATH_PATTERNS: Record< string, RegExp > = { @@ -117,6 +120,7 @@ const DYNAMIC_PATH_PATTERNS: Record< string, RegExp > = { team: /^\/team(\/.*)?$/, plugins: /^\/plugins(\/.*)?$/, referrals: /^\/referrals(\/.*)?$/, + reports: /^\/reports(\/.*)?$/, }; export const isPathAllowed = ( pathname: string, agency: Agency | null ) => { diff --git a/client/a8c-for-agencies/sections/reports/build-report/index.tsx b/client/a8c-for-agencies/sections/reports/build-report/index.tsx new file mode 100644 index 000000000000..43cbe2636d3b --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/build-report/index.tsx @@ -0,0 +1,380 @@ +import { + Button, + SelectControl, + TextareaControl, + CheckboxControl, + TextControl, + DatePicker, + Popover, +} from '@wordpress/components'; +import { useState, useEffect, useRef } from '@wordpress/element'; +import { useTranslate } from 'i18n-calypso'; +import { LayoutWithGuidedTour as Layout } from 'calypso/a8c-for-agencies/components/layout/layout-with-guided-tour'; +import LayoutTop from 'calypso/a8c-for-agencies/components/layout/layout-with-payment-notification'; +import SelectSiteButton from 'calypso/a8c-for-agencies/components/select-site-button'; +import MobileSidebarNavigation from 'calypso/a8c-for-agencies/components/sidebar/mobile-sidebar-navigation'; +import { A4A_REPORTS_LINK } from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants'; +import LayoutBody from 'calypso/layout/hosting-dashboard/body'; +import LayoutHeader, { + LayoutHeaderBreadcrumb as Breadcrumb, + LayoutHeaderActions as Actions, +} from 'calypso/layout/hosting-dashboard/header'; + +import './style.scss'; + +// Mock data - replace with actual data fetching as needed +const MOCK_TIMEFRAMES = [ + { label: 'Last 30 days', value: 'last_30_days' }, + { label: 'Last 7 days', value: 'last_7_days' }, + { label: 'Last 24 hours', value: 'last_24_hours' }, + { label: 'Custom range', value: 'custom' }, +]; + +// Checkbox groups for Step 2 +const STATS_OPTIONS = [ + { label: 'Visitors and Views in this timeframe', value: 'total_traffic' }, + { label: 'Top 5 posts', value: 'top_pages' }, + { label: 'Top 5 referrers', value: 'top_devices' }, + { label: 'Top 5 cities', value: 'top_locations' }, + { label: 'Device breakdown', value: 'top_locations' }, + { label: 'Total Visitors and Views since the site was created', value: 'total_traffic-all-time' }, + { label: 'Most popular time of day', value: 'most_popular_time_of_day' }, + { label: 'Most popular day of week', value: 'most_popular_day_of_week' }, +]; + +type CheckedItemsState = Record< string, boolean >; + +const BuildReport = () => { + const translate = useTranslate(); + const title = translate( 'Build Report' ); + const teammateEmailsRef = useRef< HTMLDivElement >( null ); + + // Step 1: Setup State + const [ selectedTimeframe, setSelectedTimeframe ] = useState( MOCK_TIMEFRAMES[ 0 ].value ); + const [ selectedSite, setSelectedSite ] = useState( '' ); + const [ clientEmail, setClientEmail ] = useState( '' ); + // const [ clientName, setClientName ] = useState( '' ); + const [ customIntroText, setCustomIntroText ] = useState( '' ); + const [ sendMeACopy, setSendMeACopy ] = useState( false ); + const [ teammateEmails, setTeammateEmails ] = useState( '' ); + const [ startDate, setStartDate ] = useState< string | undefined >( undefined ); + const [ endDate, setEndDate ] = useState< string | undefined >( undefined ); + const [ isStartDatePickerOpen, setIsStartDatePickerOpen ] = useState( false ); + const [ isEndDatePickerOpen, setIsEndDatePickerOpen ] = useState( false ); + + // Step 2: Pick Content State + const [ statsCheckedItems, setStatsCheckedItems ] = useState< CheckedItemsState >( + STATS_OPTIONS.reduce( ( acc, item ) => ( { ...acc, [ item.value ]: true } ), {} ) + ); + + // Step 3: Schedule and Send State + const [ currentStep, setCurrentStep ] = useState( 1 ); + const handleNextStep = () => setCurrentStep( ( prev ) => prev + 1 ); + const handlePrevStep = () => setCurrentStep( ( prev ) => prev - 1 ); + + const handleStep2CheckboxChange = ( groupKey: 'stats', itemName: string ) => { + const setterMap: Record< + string, + React.Dispatch< React.SetStateAction< CheckedItemsState > > + > = { + stats: setStatsCheckedItems, + }; + setterMap[ groupKey ]?.( ( prev: CheckedItemsState ) => ( { + ...prev, + [ itemName ]: ! prev[ itemName ], + } ) ); + }; + + // Auto-scroll to teammate emails field when checkbox is checked + useEffect( () => { + if ( sendMeACopy && teammateEmailsRef.current ) { + setTimeout( () => { + teammateEmailsRef.current?.scrollIntoView( { behavior: 'smooth', block: 'center' } ); + }, 100 ); // Small delay to ensure field is rendered + } + }, [ sendMeACopy ] ); + + // Set default custom date range to yesterday and today if no dates are set + useEffect( () => { + if ( selectedTimeframe === 'custom' && ! startDate && ! endDate ) { + const today = new Date(); + const yesterday = new Date( today ); + yesterday.setDate( today.getDate() - 1 ); + + setStartDate( yesterday.toISOString().split( 'T' )[ 0 ] ); + setEndDate( today.toISOString().split( 'T' )[ 0 ] ); + } + }, [ selectedTimeframe, startDate, endDate ] ); + + // Helper function to format date for display + const formatDateForDisplay = ( dateString: string | undefined ) => { + if ( ! dateString ) { + return ''; + } + const date = new Date( dateString ); + return date.toLocaleDateString( 'en-US', { + year: 'numeric', + month: 'long', + day: 'numeric', + } ); + }; + + const renderStepContent = () => { + switch ( currentStep ) { + case 1: + return ( + <> +
+

+ { translate( 'Step 1 of 3: Enter report details' ) } +

+
+ +
+ setSelectedSite( siteDomain ) } + buttonLabel={ selectedSite || translate( 'Choose a site to report on' ) } + helpText={ + <> + { translate( + "If you don't see the site in the list, connect it first via the " + ) } + + { translate( 'Sites Dashboard' ) } + + { translate( '.' ) } +
+ { translate( + 'Only live WordPress.com sites or Pressable sites with Jetpack installed are supported.' + ) } + + } + trackingEvent="calypso_a4a_reports_select_site_button_click" + /> +
+ + { selectedTimeframe === 'custom' && ( +
+
+
+ {} } + onClick={ () => setIsStartDatePickerOpen( true ) } + readOnly + className="build-report__date-input" + /> + { isStartDatePickerOpen && ( + setIsStartDatePickerOpen( false ) } + anchorRef={ undefined } + placement="bottom-start" + className="build-report__date-popover" + > + { + setStartDate( date ); + // If end date is set and is before or equal to the new start date, + // set end date to the day after start date + if ( endDate && new Date( date ) >= new Date( endDate ) ) { + const nextDay = new Date( date ); + nextDay.setDate( nextDay.getDate() + 1 ); + setEndDate( nextDay.toISOString().split( 'T' )[ 0 ] ); + } + setIsStartDatePickerOpen( false ); + } } + /> + + ) } +
+
+
+
+ {} } + onClick={ () => setIsEndDatePickerOpen( true ) } + readOnly + className="build-report__date-input" + /> + { isEndDatePickerOpen && ( + setIsEndDatePickerOpen( false ) } + anchorRef={ undefined } + placement="bottom-start" + className="build-report__date-popover" + > + { + setEndDate( date ); + setIsEndDatePickerOpen( false ); + } } + isInvalidDate={ ( date ) => { + // Disable dates before the start date + if ( ! startDate ) { + return false; + } + return new Date( date ) < new Date( startDate ); + } } + /> + + ) } +
+ { startDate && endDate && new Date( endDate ) < new Date( startDate ) && ( +

+ { translate( 'End date must be after start date' ) } +

+ ) } +
+
+ ) } + + + { sendMeACopy && ( +
+ +
+ ) } + + ); + case 2: + return ( + <> +
+

+ { translate( 'Step 2 of 3: Choose report content' ) } +

+
+ + + +

+ { translate( 'Stats' ) } +

+ { STATS_OPTIONS.map( ( item ) => ( + handleStep2CheckboxChange( 'stats', item.value ) } + className="build-report__checkbox-control" + /> + ) ) } + + ); + case 3: + return ( + <> +

+ { translate( 'Step 3 of 3: Send your report' ) } +

+

+ { translate( + 'Your report is ready for sending. Checkout the preview, then click "Send to client now".' + ) } +
+ { translate( "We'll take it from there!" ) } +

+ + + + ); + default: + return null; + } + }; + + const renderActions = () => ( +
+ { currentStep > 1 && ( + + ) } + { currentStep < 3 && ( + + ) } + { currentStep === 3 && ( + + ) } +
+ ); + + return ( + + + + + + + + + + +
+ { renderStepContent() } + { renderActions() } +
+
+
+ ); +}; + +export default BuildReport; diff --git a/client/a8c-for-agencies/sections/reports/build-report/style.scss b/client/a8c-for-agencies/sections/reports/build-report/style.scss new file mode 100644 index 000000000000..e6b5306f4d31 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/build-report/style.scss @@ -0,0 +1,152 @@ +@import '@wordpress/base-styles/mixins'; +@import '@wordpress/base-styles/variables'; + +.build-report { + .build-report__content { + flex-grow: 1; + max-width: 660px; + min-height: 0; + display: flex; + flex-direction: column; + gap: 16px; + padding: 16px 24px; + border: 1px solid var( --color-neutral-10 ); + border-radius: 4px; + margin-inline: auto; + margin-block-start: 16px; + } + + .build-report__step-header { + display: flex; + flex-direction: column; + gap: 8px; + padding-block: 8px 4px; + } + + .build-report__step-title { + @include heading-large; + margin-bottom: 0; + } + + .build-report__step-description { + @include body-medium; + color: var( --studio-gray-70 ); + margin-bottom: 0; + } + + .build-report__group-label { + @include heading-medium; + // border-top: 1px solid var( --color-neutral-10 ); + // padding-top: 16px; + margin-top: 8px; + } + + .build-report__group-label--first { + border-top: none; + padding-top: 0; + margin-top: 0; + } + + .build-report__checkbox-control:last-child { + margin-bottom: 16px; + } + + // Fixing margins from StyledHelp-deprecatedMarginHelp which is bieng inserted by the component + .components-base-control__help { + margin-top: 0; + margin-bottom: 0; + } + + .build-report__actions { + display: flex; + justify-content: flex-start; + gap: 8px; + background-color: var( --color-surface ); + margin-top: auto; + padding-block: 8px; + } + + .build-report__date-fields-container { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 16px; + + @media (max-width: 768px) { + grid-template-columns: 1fr; + } + } + + .build-report__date-field { + display: flex; + flex-direction: column; + gap: 8px; + + + .build-report__date-input-wrapper { + position: relative; + } + + .build-report__date-input { + .components-text-control__input { + cursor: pointer; + background-color: var( --color-surface ); + border: 1px solid var( --color-neutral-20 ); + border-radius: 4px; + padding: 8px 12px; + + &:focus, + &:hover { + border-color: var( --color-primary ); + box-shadow: 0 0 0 1px var( --color-primary ); + outline: none; + } + + &::placeholder { + color: var( --color-neutral-40 ); + } + } + } + + .components-datetime { + border: none; + background-color: var( --color-surface ); + + .components-datetime__date { + border: none; + background: transparent; + } + + .components-datetime__date-header { + padding: 12px 16px; + border-bottom: 1px solid var( --color-neutral-10 ); + background-color: var( --color-neutral-0 ); + } + + .components-datetime__calendar { + padding: 16px; + } + } + + .components-button.is-primary { + background-color: var( --color-primary ); + border-color: var( --color-primary ); + } + } + + .build-report__date-error { + @include body-small; + color: var( --color-error ); + margin-top: 4px; + margin-bottom: 0; + } + + .build-report__preview-button { + width: fit-content; + } +} + +.build-report__date-popover { + .components-popover__content { + padding: 16px; + } +} diff --git a/client/a8c-for-agencies/sections/reports/controller.tsx b/client/a8c-for-agencies/sections/reports/controller.tsx new file mode 100644 index 000000000000..d5af37564122 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/controller.tsx @@ -0,0 +1,39 @@ +import { type Callback } from '@automattic/calypso-router'; +import PageViewTracker from 'calypso/a8c-for-agencies/components/a4a-page-view-tracker'; +import ReportsSidebar from 'calypso/a8c-for-agencies/components/sidebar-menu/reports'; +import BuildReport from './build-report'; +import ReportsDashboard from './reports-dashboard'; +import ReportsOverview from './reports-overview'; + +export const reportsDashboardContext: Callback = ( context, next ) => { + context.secondary = ; + context.primary = ( + <> + + + + ); + next(); +}; + +export const reportsOverviewContext: Callback = ( context, next ) => { + context.secondary = ; + context.primary = ( + <> + + + + ); + next(); +}; + +export const buildReportContext: Callback = ( context, next ) => { + context.secondary = ; + context.primary = ( + <> + + + + ); + next(); +}; diff --git a/client/a8c-for-agencies/sections/reports/example-report/index.tsx b/client/a8c-for-agencies/sections/reports/example-report/index.tsx new file mode 100644 index 000000000000..0011b4f89833 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/example-report/index.tsx @@ -0,0 +1,353 @@ +import { useTranslate } from 'i18n-calypso'; +import { ReactNode } from 'react'; + +import './style.scss'; + +interface ReportCardProps { + title: string; + value?: string | number; + children?: ReactNode; + className?: string; + icon?: ReactNode; +} + +const VisitorsIcon = () => ( + + + +); + +const ViewsIcon = () => ( + + + +); + +const TopPostsIcon = () => ( + + + + + + + +); + +const TopReferrersIcon = () => ( + + + +); + +const TopCitiesIcon = () => ( + + + +); + +const DeviceBreakdownIcon = () => ( + + + +); + +const PopularTimeIcon = () => ( + + + +); + +const PopularDayIcon = () => ( + + + +); + +const ReportCard = ( { title, value, children, className, icon }: ReportCardProps ) => ( +
+

+ { icon } + { title } +

+ { value &&

{ value }

} + { children } +
+); + +const ExampleReport = () => { + const translate = useTranslate(); + const agencyName = 'Blue Peak Digital'; + + // Mock data + const reportData = { + siteName: 'Lauger Watch Co.', + siteUrl: 'laugerwatch.com', + dateRange: 'MAY 26 - JUNE 26, 2025', + stats: { + visitors: 580, + views: 3200, + topPosts: [ + { name: 'Book a showing', views: 1357 }, + { name: 'Repair services', views: 26 }, + { name: 'Bundled packages', views: 24 }, + { name: 'Membership plans', views: 19 }, + { name: 'Gallery', views: 14 }, + ], + topReferrers: [ + { name: 'Search engines', views: 1357 }, + { name: 'Reddit', views: 26 }, + { name: 'X', views: 24 }, + { name: 'WordPress Android App', views: 19 }, + { name: 'WordPress iOS App', views: 14 }, + ], + topCities: [ + { name: 'Boston', views: 423 }, + { name: 'Dallas', views: 213 }, + { name: 'San Francisco', views: 53 }, + { name: 'Los Angeles', views: 12 }, + { name: 'New York City', views: 5 }, + ], + deviceBreakdown: [ + { device: 'Mobile', percentage: 58 }, + { device: 'Desktop', percentage: 40 }, + { device: 'Tablet', percentage: 2 }, + ], + popularTime: '3:00 PM', + popularDay: 'Friday', + }, + }; + + return ( +
+
+
+
+

{ translate( 'SITE UPDATE FROM' ) }

+

{ agencyName }

+

{ reportData.dateRange }

+

{ reportData.siteName }

+

{ reportData.siteUrl }

+

+ { translate( + 'Hey Mary! Here\'s the monthly report for your site! Notice how the "Repair services" page is lower in traffic than "Book a showing". Let\'s jump on a call to disucss how we can boost that page.' + ) } +
+ + { translate( '—Steve' ) } +

+
+
+ +
+
+

{ translate( 'Last 30 days' ) }

+
+ +
+ } + /> + } + /> + } + > +
+ { translate( 'Name' ) } + { translate( 'Views' ) } +
+ { reportData.stats.topPosts.map( ( post ) => ( +
+ { post.name } + { post.views } +
+ ) ) } +
+ } + > +
+ { translate( 'Name' ) } + { translate( 'Views' ) } +
+ { reportData.stats.topReferrers.map( ( referrer ) => ( +
+ { referrer.name } + { referrer.views } +
+ ) ) } +
+ } + > +
+ { translate( 'Name' ) } + { translate( 'Views' ) } +
+ { reportData.stats.topCities.map( ( city ) => ( +
+ { city.name } + { city.views } +
+ ) ) } +
+ } + > +
+ { translate( 'Device' ) } + { translate( '%' ) } +
+ { reportData.stats.deviceBreakdown.map( ( item ) => ( +
+ { item.device } + { item.percentage } +
+ ) ) } +
+
+ +
+

+ { translate( 'All time' ) } +

+
+
+ } + /> + } + /> + } + /> + } + /> +
+
+
+ { translate( 'Brought to you by %s', { args: agencyName } ) } +
+
+
+ ); +}; + +export default ExampleReport; diff --git a/client/a8c-for-agencies/sections/reports/example-report/style.scss b/client/a8c-for-agencies/sections/reports/example-report/style.scss new file mode 100644 index 000000000000..de4c7ec5f15f --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/example-report/style.scss @@ -0,0 +1,190 @@ +@import '@wordpress/base-styles/breakpoints'; +@import '@wordpress/base-styles/mixins'; +@import '@wordpress/base-styles/variables'; + + +.example-report { + padding: 56px; + background: var( --a4a-brand-blue-80 ); + width: unset; +} + +.example-report__inner-body { + padding: 24px; + background: var( --color-surface ); +} + +.example-report__content { + background: var( --color-surface ); + max-width: 800px; + margin: 0 auto; + /* stylelint-disable-next-line scales/radii */ + border-radius: 12px; + overflow: hidden; +} + +.example-report__header { + background-color: #b9e4fe; + padding: 40px 24px; + position: relative; + overflow: hidden; + + // This will be used for the decorative background image + &::after { + content: ''; + position: absolute; + top: -120px; + right: 0; + width: 50%; + height: 100%; + background-image: url( ../../../../assets/images/a8c-for-agencies/reports/doodle.png ); + background-repeat: no-repeat; + background-position: right center; + background-size: contain; + pointer-events: none; + } + + .example-report__header-content { + max-width: 1200px; + margin: 0 auto; + position: relative; + z-index: 1; + } + + .example-report__date { + @include body-small; + text-transform: uppercase; + margin-bottom: 0; + color: var( --color-neutral-70 ); + } + + h1 { + @include heading-2x-large; + margin-bottom: 8px; + margin-top: 16px; + color: var( --color-neutral-90 ); + } + + .example-report__url { + @include body-small; + color: var( --color-neutral-70 ); + margin-bottom: 24px; + } + + .example-report__message { + @include body-large; + line-height: 1.6; + color: var( --color-neutral-90 ); + max-width: 600px; + margin-bottom: 0; + } +} + +.example-report__section-title-container { + margin-bottom: 24px; + + .example-report__section-title { + @include heading-x-large; + margin-bottom: 16px; + } + + .example-report__section-title--flush { + margin-bottom: 0; + } + + .example-report__date-range { + @include body-small; + color: var( --color-neutral-70 ); + text-transform: uppercase; + } +} + +.example-report__grid { + display: grid; + grid-template-columns: repeat( 1, 1fr ); + gap: 24px; + margin-bottom: 48px; + + @include break-medium { + grid-template-columns: repeat( 2, 1fr ); + } +} + +.example-report__grid:last-child { + margin-bottom: 24px; +} + +.example-report__card { + background-color: var( --color-surface ); + border: 1px solid var( --color-neutral-10 ); + border-radius: 4px; + padding: 24px; + + .example-report__card-title { + @include heading; + font-weight: 600; + color: var( --color-neutral-70 ); + display: flex; + align-items: center; + margin-block-start: 0; + margin-block-end: 16px; + } + + .example-report__card-value { + @include heading-2x-large; + margin: 0; + color: var( --color-neutral-90 ); + } +} + +.example-report__card--list { + .example-report__card-title { + margin-bottom: 24px; + } + + .example-report__table-header, + .example-report__table-row { + display: flex; + justify-content: space-between; + padding: 4px 0; + @include body-small; + + span:last-child { + text-align: end; + font-weight: 600; + } + } + + .example-report__table-header { + font-weight: normal; + margin-bottom: 8px; + + .example-report__table-header-text { + font-weight: 600; + } + } + + .example-report__table-row { + span:first-child { + color: var( --color-neutral-90 ); + } + + span:last-child { + color: var( --color-neutral-90 ); + } + } +} + +.example-report__blurb { + @include body-medium; + margin-bottom: 0; + margin-top: 8px; +} + +.example-report__footer { + background-color: var( --studio-gray-0 ); + @include body-small; + color: var( --studio-gray-70 ); + text-align: left; + padding: 24px; +} diff --git a/client/a8c-for-agencies/sections/reports/index.tsx b/client/a8c-for-agencies/sections/reports/index.tsx new file mode 100644 index 000000000000..477299c4c9d6 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/index.tsx @@ -0,0 +1,38 @@ +import page from '@automattic/calypso-router'; +import { + A4A_REPORTS_LINK, + A4A_REPORTS_OVERVIEW_LINK, + A4A_REPORTS_DASHBOARD_LINK, + A4A_REPORTS_BUILD_LINK, +} from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants'; +import { requireAccessContext } from 'calypso/a8c-for-agencies/controller'; +import { makeLayout, render as clientRender } from 'calypso/controller'; +import { reportsOverviewContext, reportsDashboardContext, buildReportContext } from './controller'; + +export default function () { + page( + A4A_REPORTS_OVERVIEW_LINK, + requireAccessContext, + reportsOverviewContext, + makeLayout, + clientRender + ); + page( + A4A_REPORTS_DASHBOARD_LINK, + requireAccessContext, + reportsDashboardContext, + makeLayout, + clientRender + ); + page( + A4A_REPORTS_BUILD_LINK, + requireAccessContext, + buildReportContext, + makeLayout, + clientRender + ); + page( A4A_REPORTS_LINK, () => page.redirect( A4A_REPORTS_OVERVIEW_LINK ) ); + + // Keep this route but redirect to overview for any direct links + page( `${ A4A_REPORTS_LINK }/build`, () => page.redirect( A4A_REPORTS_OVERVIEW_LINK ) ); +} diff --git a/client/a8c-for-agencies/sections/reports/reports-dashboard/index.tsx b/client/a8c-for-agencies/sections/reports/reports-dashboard/index.tsx new file mode 100644 index 000000000000..db9533b331bb --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/reports-dashboard/index.tsx @@ -0,0 +1,51 @@ +import { Button } from '@wordpress/components'; +import { useTranslate } from 'i18n-calypso'; +import { useState } from 'react'; +import { initialDataViewsState } from 'calypso/a8c-for-agencies/components/items-dashboard/constants'; +import { DataViewsState } from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews/interfaces'; +import { LayoutWithGuidedTour as Layout } from 'calypso/a8c-for-agencies/components/layout/layout-with-guided-tour'; +import LayoutTop from 'calypso/a8c-for-agencies/components/layout/layout-with-payment-notification'; +import MobileSidebarNavigation from 'calypso/a8c-for-agencies/components/sidebar/mobile-sidebar-navigation'; +import { A4A_REPORTS_BUILD_LINK } from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants'; +import LayoutBody from 'calypso/layout/hosting-dashboard/body'; +import LayoutColumn from 'calypso/layout/hosting-dashboard/column'; +import LayoutHeader, { + LayoutHeaderTitle as Title, + LayoutHeaderActions as Actions, +} from 'calypso/layout/hosting-dashboard/header'; +import ReportsList from '../reports-list'; +import './style.scss'; + +export default function ReportsDashboard() { + const translate = useTranslate(); + const pageTitle = translate( 'Reports Dashboard' ); + const [ dataViewsState, setDataViewsState ] = useState< DataViewsState >( { + ...initialDataViewsState, + fields: [ 'siteNameOrUrl', 'dateSent', 'status' ], + } ); + + return ( + + + + + { pageTitle } + + + + + + + + + + + + ); +} diff --git a/client/a8c-for-agencies/sections/reports/reports-dashboard/style.scss b/client/a8c-for-agencies/sections/reports/reports-dashboard/style.scss new file mode 100644 index 000000000000..4dd78bc0b3d3 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/reports-dashboard/style.scss @@ -0,0 +1,6 @@ +@import '@wordpress/base-styles/mixins'; +@import '@wordpress/base-styles/variables'; + +.reports-dashboard-content { + // Remove padding to let the dataview handle its own spacing +} diff --git a/client/a8c-for-agencies/sections/reports/reports-list/index.tsx b/client/a8c-for-agencies/sections/reports/reports-list/index.tsx new file mode 100644 index 000000000000..32e7f85c74b8 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/reports-list/index.tsx @@ -0,0 +1,145 @@ +import { filterSortAndPaginate } from '@wordpress/dataviews'; +import { useMemo, useCallback } from '@wordpress/element'; +import { chevronRight } from '@wordpress/icons'; +import { useTranslate } from 'i18n-calypso'; +import { DATAVIEWS_LIST } from 'calypso/a8c-for-agencies/components/items-dashboard/constants'; +import ItemsDataViews from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews'; +import { DataViewsState } from 'calypso/a8c-for-agencies/components/items-dashboard/items-dataviews/interfaces'; +import { useDispatch } from 'calypso/state'; +import { recordTracksEvent } from 'calypso/state/analytics/actions'; +import type { Field, Action } from '@wordpress/dataviews'; +import type { ReactNode } from 'react'; + +import './style.scss'; + +interface Report { + id: string; + siteNameOrUrl: string; + dateSent: string; + status: string; +} + +interface Props { + reports: Report[]; + dataViewsState: DataViewsState; + setDataViewsState: ( callback: ( prevState: DataViewsState ) => DataViewsState ) => void; +} + +export default function ReportsList( { reports, dataViewsState, setDataViewsState }: Props ) { + const translate = useTranslate(); + const dispatch = useDispatch(); + + const openReportPreviewPane = useCallback( + ( report: Report ) => { + setDataViewsState( ( prevState: DataViewsState ) => ( { + ...prevState, + selectedItem: report, + type: DATAVIEWS_LIST, + } ) ); + dispatch( recordTracksEvent( 'calypso_a4a_reports_list_view_details_click' ) ); + }, + [ dispatch, setDataViewsState ] + ); + + const fields: Field< Report >[] = useMemo( + () => [ + { + id: 'siteNameOrUrl', + label: translate( 'Site Name / URL' ).toUpperCase(), + getValue: () => '-', + render: ( { item }: { item: Report } ): ReactNode => ( + { item.siteNameOrUrl } + ), + enableHiding: false, + enableSorting: false, + }, + ], + [ translate ] + ); + + const actions: Action< Report >[] = useMemo( () => { + if ( dataViewsState.type === 'table' ) { + return [ + { + id: 'view-report', + label: translate( 'View Details' ), + isPrimary: true, + icon: chevronRight, + callback( items ) { + alert( `View Details clicked for report: ${ items[ 0 ].siteNameOrUrl }` ); + // openReportPreviewPane( items[ 0 ] ); + return false; + }, + }, + ]; + } + + return []; + }, [ translate, dataViewsState.type ] ); + + const placeholderReport: Report = useMemo( + () => ( { + id: 'placeholder-1', + siteNameOrUrl: translate( 'example.com' ), + dateSent: translate( 'October 26, 2025' ), + status: translate( 'Sent' ), + } ), + [ translate ] + ); + + const additionalPlaceholderReports: Report[] = useMemo( + () => [ + { + id: 'placeholder-2', + siteNameOrUrl: translate( 'mybusiness.com' ), + dateSent: translate( 'November 15, 2025' ), + status: translate( 'Sent' ), + }, + { + id: 'placeholder-3', + siteNameOrUrl: translate( 'clientsite.org' ), + dateSent: translate( 'December 3, 2025' ), + status: translate( 'Sent' ), + }, + { + id: 'placeholder-4', + siteNameOrUrl: translate( 'portfolio.net' ), + dateSent: translate( 'December 18, 2025' ), + status: translate( 'Sent' ), + }, + ], + [ translate ] + ); + + const { data: items, paginationInfo: pagination } = useMemo( () => { + const reportsToDisplay = + reports.length > 0 ? reports : [ placeholderReport, ...additionalPlaceholderReports ]; + return filterSortAndPaginate( reportsToDisplay, dataViewsState, fields ); + }, [ reports, placeholderReport, additionalPlaceholderReports, dataViewsState, fields ] ); + + return ( +
+ `${ item.id }`, + onSelectionChange: ( data ) => { + const allReports = + reports.length > 0 ? reports : [ placeholderReport, ...additionalPlaceholderReports ]; + const report = allReports.find( ( r ) => r.id === data[ 0 ] ); + if ( report ) { + openReportPreviewPane( report ); + } + }, + pagination, + enableSearch: false, + fields, + actions, + setDataViewsState, + dataViewsState, + defaultLayouts: { table: {} }, + } } + /> +
+ ); +} diff --git a/client/a8c-for-agencies/sections/reports/reports-list/style.scss b/client/a8c-for-agencies/sections/reports/reports-list/style.scss new file mode 100644 index 000000000000..7c6457d431bf --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/reports-list/style.scss @@ -0,0 +1,20 @@ +@import '@wordpress/base-styles/mixins'; +@import '@wordpress/base-styles/variables'; + +.redesigned-a8c-table { + // Fix search component spacing issues + .dataviews-search { + margin-block-end: 16px; + } + + // Ensure consistent spacing for the dataview container + .dataviews-wrapper { + margin-block-start: 0; + } + + // Remove extra padding from search container + .dataviews-filters__container { + padding-block: 0; + margin-block-end: 16px; + } +} diff --git a/client/a8c-for-agencies/sections/reports/reports-overview/index.tsx b/client/a8c-for-agencies/sections/reports/reports-overview/index.tsx new file mode 100644 index 000000000000..d1133f0248a4 --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/reports-overview/index.tsx @@ -0,0 +1,203 @@ +import page from '@automattic/calypso-router'; +import { Button, Modal } from '@wordpress/components'; +import { useTranslate } from 'i18n-calypso'; +import { useMemo, useCallback, useState } from 'react'; +import { LayoutWithGuidedTour as Layout } from 'calypso/a8c-for-agencies/components/layout/layout-with-guided-tour'; +import LayoutTop from 'calypso/a8c-for-agencies/components/layout/layout-with-payment-notification'; +import PageSectionColumns from 'calypso/a8c-for-agencies/components/page-section-columns'; +import MobileSidebarNavigation from 'calypso/a8c-for-agencies/components/sidebar/mobile-sidebar-navigation'; +import { A4A_REPORTS_BUILD_LINK } from 'calypso/a8c-for-agencies/components/sidebar-menu/lib/constants'; +import SimpleList from 'calypso/a8c-for-agencies/components/simple-list'; +import ExampleReport from 'calypso/a8c-for-agencies/sections/reports/example-report'; +import whyImage from 'calypso/assets/images/a8c-for-agencies/reports/report-mock-2.png'; +import readyImage from 'calypso/assets/images/a8c-for-agencies/reports/report-mock-3.png'; +import heroImage from 'calypso/assets/images/a8c-for-agencies/reports/report-mock.png'; +import LayoutBody from 'calypso/layout/hosting-dashboard/body'; +import LayoutHeader, { + LayoutHeaderTitle as Title, + LayoutHeaderActions as Actions, +} from 'calypso/layout/hosting-dashboard/header'; +import { useDispatch } from 'calypso/state'; +import { recordTracksEvent } from 'calypso/state/analytics/actions'; + +import './style.scss'; + +const ReportsOverview = () => { + const translate = useTranslate(); + const dispatch = useDispatch(); + const [ isExampleReportModalOpen, setIsExampleReportModalOpen ] = useState( false ); + + const title = translate( 'Client Reports' ); + + const listItems1 = useMemo( + () => [ + translate( + "Automated professional client reports that highlight key metrics from your clients' sites." + ), + translate( + 'Show clients the value of your ongoing work with beautiful, professional reports.' + ), + translate( + 'Include detailed statistics, security measures, and performance indicators that matter most.' + ), + translate( + 'Coming soon: Schedule regular report delivery to keep clients informed without extra work.' + ), + ], + [ translate ] + ); + + const listItems2 = useMemo( + () => [ + translate( + 'Choose exactly what data to include in each report based on client needs and priorities.' + ), + translate( + 'Demonstrate the ongoing value of your services with clear, easy-to-understand metrics.' + ), + translate( 'Strengthen client relationships with regular, professional communication.' ), + ], + [ translate ] + ); + + const handleBuildReport = useCallback( () => { + dispatch( recordTracksEvent( 'calypso_a4a_reports_build_report_button_click' ) ); + page( A4A_REPORTS_BUILD_LINK ); + }, [ dispatch ] ); + + const handleExampleReport = useCallback( () => { + dispatch( recordTracksEvent( 'calypso_a4a_reports_example_report_button_click' ) ); + setIsExampleReportModalOpen( true ); + }, [ dispatch ] ); + + const closeExampleReportModal = useCallback( () => { + setIsExampleReportModalOpen( false ); + }, [] ); + + const buildReportButton = useMemo( () => { + return ( +
+ + +
+ ); + }, [ translate, handleBuildReport, handleExampleReport ] ); + + return ( + + + + { title } + + + + + + + + + +
+
+
+ { translate( 'Create professional reports for your clients' ) } +
+
+ { translate( + "Prove your agency's impact with polished, easy-to-read reports. Pull in traffic stats, security checks, and performance metrics automatically, then send a snapshot that keeps clients informed, impressed, and confident in the work you do each month." + ) } +
+
+ { buildReportButton } +
+
+ + Reports & Analytics + +
+ + + +
+
+ { translate( + 'Reports turn raw data into clear stories. They highlight progress, justify fees, and create regular touchpoints that spark new goals. Each delivery invites a conversation, making upsells or scope expansion a natural, value-based next step for both sides.' + ) } +
+
+
+ + Client Reports + +
+ + + + + + + + + + + + + <> +
+ { translate( + "Our streamlined report builder makes it easy to create professional client reports in minutes. Simply select what information to include, and click send! We'll handle the rest." + ) } +
+
+ +
+ +
+ + Reports & Analytics + +
+
+ { isExampleReportModalOpen && ( + + + + ) } +
+ ); +}; + +export default ReportsOverview; diff --git a/client/a8c-for-agencies/sections/reports/reports-overview/style.scss b/client/a8c-for-agencies/sections/reports/reports-overview/style.scss new file mode 100644 index 000000000000..9c3b52d5534f --- /dev/null +++ b/client/a8c-for-agencies/sections/reports/reports-overview/style.scss @@ -0,0 +1,113 @@ +@import '@wordpress/base-styles/breakpoints'; +@import '@wordpress/base-styles/mixins'; +@import '@wordpress/base-styles/variables'; + +.reports-overview { + .hosting-dashboard-layout__header-actions { + display: flex; + flex-direction: column; + align-items: normal; + justify-content: normal; + } + + .hosting-dashboard-layout__body-wrapper { + max-width: unset; + padding-inline: 0; + margin: 0; + } + + .reports-overview__body { + padding-block-end: 0; + } +} + +.reports-overview__content { + container-type: inline-size; + display: flex; + flex-direction: column; + gap: 24px; + + img { + width: max-content; + } +} + +.reports-overview__heading { + @include heading-2x-large; + margin-block-end: 8px; + text-wrap: pretty; +} + +.reports-overview__description { + @include body-large; + + > div { + margin-block-end: 16px; + } + + a { + text-decoration: underline; + } +} + +a.components-button.reports-overview__button--secondary { + &, + &:hover, + &:focus, + &:focus-visible { + color: var( --studio-automattic-blue-60 ); + background-color: var( --color-surface ); + border: none; + outline: none; + box-shadow: none; + text-decoration: none; + } +} + +.reports-overview__list a { + text-decoration: underline; + color: var( --color-text ); + + &:hover, + &:focus, + &:focus-visible { + color: var( --color-text ); + } +} + +.reports-overview__buttons-container { + container-type: inline-size; + display: flex; + flex-direction: column; + gap: 8px; + + .components-button.reports-overview__button { + flex: 1; + width: 100%; + } +} + +@container (min-width: 375px) { + .reports-overview__buttons-container .components-button.reports-overview__button { + flex: none; + width: auto; + } +} + +@container (min-width: 350px) { + .reports-overview__buttons-container{ + flex-direction: row; + } +} + +.reports-overview__example-report-modal { + .components-modal__content { + background-color: var( --a4a-brand-blue-80 ); + } +} + +.reports-overview__example-report-modal-body { + overflow: hidden; +} + + diff --git a/client/a8c-for-agencies/style.scss b/client/a8c-for-agencies/style.scss index cc18ee04f41e..8d58e5bbb77a 100644 --- a/client/a8c-for-agencies/style.scss +++ b/client/a8c-for-agencies/style.scss @@ -75,6 +75,9 @@ --color-yellow-0: var(--studio-yellow-0); --color-yellow-20: var(--studio-yellow-20); --color-yellow-50: var(--studio-yellow-50); + + // A4A Brand Colors + --a4a-brand-blue-80: #12304a; } .theme-a8c-for-agencies { diff --git a/client/assets/images/a8c-for-agencies/reports/doodle.png b/client/assets/images/a8c-for-agencies/reports/doodle.png new file mode 100644 index 000000000000..ef698c5fc7cf Binary files /dev/null and b/client/assets/images/a8c-for-agencies/reports/doodle.png differ diff --git a/client/assets/images/a8c-for-agencies/reports/report-mock-2.png b/client/assets/images/a8c-for-agencies/reports/report-mock-2.png new file mode 100644 index 000000000000..2afbd8cd987b Binary files /dev/null and b/client/assets/images/a8c-for-agencies/reports/report-mock-2.png differ diff --git a/client/assets/images/a8c-for-agencies/reports/report-mock-3.png b/client/assets/images/a8c-for-agencies/reports/report-mock-3.png new file mode 100644 index 000000000000..cb6a09bc171e Binary files /dev/null and b/client/assets/images/a8c-for-agencies/reports/report-mock-3.png differ diff --git a/client/assets/images/a8c-for-agencies/reports/report-mock.png b/client/assets/images/a8c-for-agencies/reports/report-mock.png new file mode 100644 index 000000000000..0b98779382e0 Binary files /dev/null and b/client/assets/images/a8c-for-agencies/reports/report-mock.png differ diff --git a/client/sections.js b/client/sections.js index d102909ffa13..1ef02873a587 100644 --- a/client/sections.js +++ b/client/sections.js @@ -903,6 +903,12 @@ const sections = [ module: 'calypso/a8c-for-agencies/sections/woopayments', group: 'a8c-for-agencies', }, + { + name: 'a8c-for-agencies-reports', + paths: [ '/reports', '/reports/overview' ], + module: 'calypso/a8c-for-agencies/sections/reports', + group: 'a8c-for-agencies', + }, ]; module.exports = sections; diff --git a/config/a8c-for-agencies-development.json b/config/a8c-for-agencies-development.json index 005660f804e0..1c557f663ce7 100644 --- a/config/a8c-for-agencies-development.json +++ b/config/a8c-for-agencies-development.json @@ -67,7 +67,8 @@ "a8c-for-agencies-client": true, "a8c-for-agencies-team": true, "a8c-for-agencies-agency-tier": true, - "a8c-for-agencies-woopayments": true + "a8c-for-agencies-woopayments": true, + "a8c-for-agencies-reports": true }, "site_filter": [], "theme": "a8c-for-agencies", diff --git a/config/a8c-for-agencies-horizon.json b/config/a8c-for-agencies-horizon.json index c627431e86b6..064649a5b414 100644 --- a/config/a8c-for-agencies-horizon.json +++ b/config/a8c-for-agencies-horizon.json @@ -60,7 +60,8 @@ "a8c-for-agencies-client": true, "a8c-for-agencies-team": true, "a8c-for-agencies-agency-tier": true, - "a8c-for-agencies-woopayments": true + "a8c-for-agencies-woopayments": true, + "a8c-for-agencies-reports": true }, "site_filter": [], "theme": "a8c-for-agencies", diff --git a/config/a8c-for-agencies-production.json b/config/a8c-for-agencies-production.json index fd7871a23fb7..35a74ca88b48 100644 --- a/config/a8c-for-agencies-production.json +++ b/config/a8c-for-agencies-production.json @@ -60,7 +60,8 @@ "a8c-for-agencies-client": true, "a8c-for-agencies-team": true, "a8c-for-agencies-agency-tier": true, - "a8c-for-agencies-woopayments": true + "a8c-for-agencies-woopayments": true, + "a8c-for-agencies-reports": true }, "site_filter": [], "theme": "a8c-for-agencies", diff --git a/config/a8c-for-agencies-stage.json b/config/a8c-for-agencies-stage.json index e00c9d33f7f6..cc87b4db39fc 100644 --- a/config/a8c-for-agencies-stage.json +++ b/config/a8c-for-agencies-stage.json @@ -62,7 +62,8 @@ "a8c-for-agencies-client": true, "a8c-for-agencies-team": true, "a8c-for-agencies-agency-tier": true, - "a8c-for-agencies-woopayments": true + "a8c-for-agencies-woopayments": true, + "a8c-for-agencies-reports": true }, "site_filter": [], "theme": "a8c-for-agencies",