diff --git a/projects/packages/activity-log/changelog/default-table-view-and-fix-upsell-styles b/projects/packages/activity-log/changelog/default-table-view-and-fix-upsell-styles
new file mode 100644
index 000000000000..70197c7750fb
--- /dev/null
+++ b/projects/packages/activity-log/changelog/default-table-view-and-fix-upsell-styles
@@ -0,0 +1,4 @@
+Significance: patch
+Type: fixed
+
+Activity Log: default the page to the Table layout, load the upsell-callout stylesheet from the main entry, and surface the disabled toolbar + disabled date-range picker on the free tier with upgrade tooltips.
diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx
index 44bce8790691..0bf291fb9a29 100644
--- a/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx
+++ b/projects/packages/activity-log/src/js/components/ActivityLog/UpsellCallout.tsx
@@ -17,7 +17,8 @@ import { addQueryArgs } from '@wordpress/url';
import { useCallback } from 'react';
import { useAnalytics } from '../../hooks/use-analytics';
import illustrationUrl from './activity-logs-callout-illustration.svg';
-import './upsell-callout.scss';
+// Stylesheet is `@use`d from `src/js/style.scss` so the rules ride the
+// main entry chunk instead of relying on a side-effect JS import.
const PRODUCT_SLUG = 'jetpack_security_t1_yearly';
const UPSELL_SOURCE = 'activity-log-page-purchase';
@@ -75,40 +76,37 @@ export function UpsellCallout() {
return (
-
- { __( 'Track every action with Jetpack Activity Log', 'jetpack-activity-log' ) }
+ { __( 'Track every action with Activity logs', 'jetpack-activity-log' ) }
{ __(
- 'Debug issues faster with insights from a comprehensive audit log of all your admin activities.',
+ 'Debug issues faster with insights from a comprehensive audit log of all your site events.',
'jetpack-activity-log'
) }
{ __(
- 'With your free plan, you can see your 20 most recent events. Upgrade for 30 days of history, plus filtering and date range controls.',
+ 'Upgrade to get complete activity history for the last 30 days, advanced filtering and date range selection. Available on the Jetpack Security and Complete plans.',
'jetpack-activity-log'
) }
-
- { __( 'Available on the Jetpack Security and Complete plans.', 'jetpack-activity-log' ) }
-
+
);
}
diff --git a/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx b/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx
index bac3a9381b1a..6ea493cbfce4 100644
--- a/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx
+++ b/projects/packages/activity-log/src/js/components/ActivityLog/index.tsx
@@ -131,6 +131,55 @@ export default function ActivityLog() {
return () => observer.disconnect();
}, [] );
+ // On free tier, neutralize DataViews' real search + filter cluster
+ // (the `.dataviews__search` Stack rendered by `DataViews`'s default
+ // UI). We let DataViews ship its own toolbar so the page tracks
+ // upstream changes for free, then attach: `aria-disabled` for
+ // assistive tech, a `title` attribute that surfaces the upgrade
+ // nudge as a native browser tooltip on hover, and `tabindex="-1"`
+ // on every focusable descendant so the cluster is unreachable via
+ // keyboard. Pointer-event blocking on the children is handled in
+ // CSS via the `[aria-disabled="true"]` rule.
+ // `MutationObserver` re-applies after DataViews remounts the
+ // toolbar / re-renders the input (e.g., on initial fetch resolution
+ // or layout switch) so React's render doesn't strip the attributes.
+ useEffect( () => {
+ if ( hasActivityLogsAccess ) {
+ return;
+ }
+
+ const wrapper = wrapperRef.current;
+ if ( ! wrapper ) {
+ return;
+ }
+
+ const tooltipText = __( 'Upgrade your plan to use this feature.', 'jetpack-activity-log' );
+
+ const apply = ( root: ParentNode ) => {
+ const cluster = root.querySelector< HTMLElement >( '.dataviews__search' );
+ if ( ! cluster ) {
+ return;
+ }
+
+ if ( cluster.getAttribute( 'aria-disabled' ) !== 'true' ) {
+ cluster.setAttribute( 'aria-disabled', 'true' );
+ cluster.setAttribute( 'title', tooltipText );
+ }
+
+ cluster.querySelectorAll< HTMLElement >( 'input, button, [tabindex]' ).forEach( el => {
+ if ( el.getAttribute( 'tabindex' ) !== '-1' ) {
+ el.setAttribute( 'tabindex', '-1' );
+ }
+ } );
+ };
+
+ apply( wrapper );
+ const observer = new MutationObserver( () => apply( wrapper ) );
+ observer.observe( wrapper, { subtree: true, childList: true } );
+
+ return () => observer.disconnect();
+ }, [ hasActivityLogsAccess ] );
+
// Date-range defaults to "Last 7 days" anchored at the site's calendar
// today (not the browser's) — matches Calypso's `getDefaultDateRange`.
// The range is client-only state: refreshes reset to the default
@@ -328,8 +377,10 @@ export default function ActivityLog() {
// Mounting the picker as an admin-ui `actions` slot places it in the
// AdminPage header alongside the title/subtitle — matches MSD's
- // layout for the logs pages.
- const headerActions = hasActivityLogsAccess ? (
+ // layout for the logs pages. On free tier the picker renders as a
+ // disabled upgrade affordance (no popover, hover tooltip), keeping
+ // the page surface visually consistent with the paid version.
+ const headerActions = (
- ) : undefined;
+ );
return (
-
-
- data={ logData }
- isLoading={ isFetching || isLoadingList }
- paginationInfo={ paginationInfo }
- fields={ fields as Field< Activity >[] }
- view={ view }
- actions={ actions }
- getItemId={ getItemId }
- search
- // Advertise both DataViews' built-in Activity timeline
- // (the default) and a Table layout. Toggle lives in
- // the cog popover's layout switcher. Each layout maps
- // the event parts to the right slots:
- // - Activity: `event_icon` → mediaField (left
- // bullet slot), `event_title` → titleField,
- // `event_description` → descriptionField, plus
- // `groupBy: published_date` for day headers.
- // - Table: one composite `event` column alongside
- // Date / User.
- // See DEFAULT_LAYOUTS in ./views for the full shape —
- // it explicitly nulls slot/groupBy refs on Table so a
- // round-trip Activity → Table doesn't carry those
- // over and double-render as a primary column.
- defaultLayouts={ DEFAULT_LAYOUTS }
- onChangeView={ onChangeView }
- onReset={ isViewModified ? onResetView : false }
- // On the free tier, lock the perPage selector to the
- // capped size and hide search/filters/sort/view-config
- // by replacing the default UI with just the table (same
- // switches Calypso uses at logs-activity/dataviews/
- // index.tsx:201-208).
- config={
- hasActivityLogsAccess
- ? undefined
- : { perPageSizes: [ ACTIVITY_LOGS_DEFAULT_PAGE_SIZE ] }
- }
- empty={
-
+ { /*
+ * Single inner div soaks up `jetpack-admin-page-layout`'s
+ * `.admin-ui-page > :not(...):not(...) > *` rule (which
+ * force-applies `flex: 1 1 auto; flex-direction: column`
+ * to every direct child of the page's scroll column).
+ * With the chain landing here, DataViews and the
+ * free-tier UpsellCallout are grandchildren and stack
+ * without competing for flex space — no `!important`
+ * overrides needed.
+ */ }
+
+
+ data={ logData }
+ isLoading={ isFetching || isLoadingList }
+ paginationInfo={ paginationInfo }
+ fields={ fields as Field< Activity >[] }
+ view={ view }
+ actions={ actions }
+ getItemId={ getItemId }
+ search
+ // Advertise both DataViews' built-in Activity timeline
+ // (the default) and a Table layout. Toggle lives in
+ // the cog popover's layout switcher. Each layout maps
+ // the event parts to the right slots:
+ // - Activity: `event_icon` → mediaField (left
+ // bullet slot), `event_title` → titleField,
+ // `event_description` → descriptionField, plus
+ // `groupBy: published_date` for day headers.
+ // - Table: one composite `event` column alongside
+ // Date / User.
+ // See DEFAULT_LAYOUTS in ./views for the full shape —
+ // it explicitly nulls slot/groupBy refs on Table so a
+ // round-trip Activity → Table doesn't carry those
+ // over and double-render as a primary column.
+ defaultLayouts={ DEFAULT_LAYOUTS }
+ onChangeView={ onChangeView }
+ onReset={ isViewModified ? onResetView : false }
+ // On the free tier, lock the perPage selector to the
+ // capped size. DataViews keeps rendering its real
+ // toolbar (search + filter toggle + cog); the
+ // search/filter cluster is neutralized by the
+ // `aria-disabled` + `tabindex="-1"` overlay
+ // applied in the effect above — Calypso's
+ // equivalent switch at logs-activity/dataviews/
+ // index.tsx:201-208 hides them, but we want the
+ // upgrade affordance to be discoverable on hover.
+ config={
+ hasActivityLogsAccess
+ ? undefined
+ : { perPageSizes: [ ACTIVITY_LOGS_DEFAULT_PAGE_SIZE ] }
+ }
+ empty={
+