-
-
-
Devices
+
+
+
+
+ {[
+ { label: 'Browsers', value: 'browser' },
+ { label: 'Operating systems', value: 'os' },
+ { label: 'Devices', value: 'size' }
+ ].map(({ label, value }) => (
+ switchTab(value)}
+ >
+ {label}
+
+ ))}
+
-
- {[
- { label: 'Browser', value: 'browser' },
- { label: 'OS', value: 'os' },
- { label: 'Size', value: 'size' }
- ].map(({ label, value }) => (
- switchTab(value)}
- >
- {label}
-
- ))}
-
-
+
+
{renderContent()}
-
+
)
}
diff --git a/assets/js/dashboard/stats/graph/graph-util.js b/assets/js/dashboard/stats/graph/graph-util.js
index 2846457fc3a8..cf33ed8dfe13 100644
--- a/assets/js/dashboard/stats/graph/graph-util.js
+++ b/assets/js/dashboard/stats/graph/graph-util.js
@@ -31,45 +31,39 @@ const buildComparisonDataset = function (comparisonPlot) {
return [
{
data: plottable(comparisonPlot),
- borderColor: 'rgba(60,70,110,0.2)',
- pointBackgroundColor: 'rgba(60,70,110,0.2)',
- pointHoverBackgroundColor: 'rgba(60, 70, 110)',
+ borderColor: 'rgb(199, 210, 254)',
+ pointBackgroundColor: 'rgb(199, 210, 254)',
+ pointHoverBackgroundColor: 'rgb(199, 210, 254)',
yAxisID: 'yComparison'
}
]
}
-
const buildDashedDataset = function (plot, presentIndex) {
if (!presentIndex) return []
-
const dashedPart = plot.slice(presentIndex - 1, presentIndex + 1)
const dashedPlot = new Array(presentIndex - 1).concat(dashedPart)
-
return [
{
data: plottable(dashedPlot),
borderDash: [3, 3],
- borderColor: 'rgba(101,116,205)',
- pointHoverBackgroundColor: 'rgba(71, 87, 193)',
+ borderColor: 'rgb(99, 102, 241)',
+ pointHoverBackgroundColor: 'rgb(99, 102, 241)',
yAxisID: 'y'
}
]
}
-
const buildMainPlotDataset = function (plot, presentIndex) {
const data = presentIndex ? plot.slice(0, presentIndex) : plot
-
return [
{
data: plottable(data),
- borderColor: 'rgba(101,116,205)',
- pointBackgroundColor: 'rgba(101,116,205)',
- pointHoverBackgroundColor: 'rgba(71, 87, 193)',
+ borderColor: 'rgb(99, 102, 241)',
+ pointBackgroundColor: 'rgb(99, 102, 241)',
+ pointHoverBackgroundColor: 'rgb(99, 102, 241)',
yAxisID: 'y'
}
]
}
-
export const buildDataSet = (
plot,
comparisonPlot,
@@ -79,10 +73,10 @@ export const buildDataSet = (
) => {
var gradient = ctx.createLinearGradient(0, 0, 0, 300)
var prev_gradient = ctx.createLinearGradient(0, 0, 0, 300)
- gradient.addColorStop(0, 'rgba(101,116,205, 0.2)')
- gradient.addColorStop(1, 'rgba(101,116,205, 0)')
- prev_gradient.addColorStop(0, 'rgba(101,116,205, 0.075)')
- prev_gradient.addColorStop(1, 'rgba(101,116,205, 0)')
+ gradient.addColorStop(0, 'rgba(79, 70, 229, 0.15)')
+ gradient.addColorStop(1, 'rgba(79, 70, 229, 0)')
+ prev_gradient.addColorStop(0, 'rgba(79, 70, 229, 0.05)')
+ prev_gradient.addColorStop(1, 'rgba(79, 70, 229, 0)')
const defaultOptions = {
label,
diff --git a/assets/js/dashboard/stats/graph/top-stats.js b/assets/js/dashboard/stats/graph/top-stats.js
index db4f06a2a9be..e370dca7c50e 100644
--- a/assets/js/dashboard/stats/graph/top-stats.js
+++ b/assets/js/dashboard/stats/graph/top-stats.js
@@ -126,11 +126,11 @@ export default function TopStats({
const [statDisplayName, statExtraName] = stat.name.split(/(\(.+\))/g)
const statDisplayNameClass = classNames(
- 'text-xs font-bold tracking-wide text-gray-500 uppercase dark:text-gray-400 whitespace-nowrap flex w-fit border-b',
+ 'text-xs text-gray-500 uppercase dark:text-gray-400 whitespace-nowrap flex w-fit border-b',
{
- 'text-indigo-600 dark:text-indigo-500 border-indigo-600 dark:border-indigo-500':
+ 'text-indigo-600 dark:text-indigo-500 font-bold tracking-[-.01em] border-indigo-600 dark:border-indigo-500':
isSelected,
- 'group-hover:text-indigo-700 dark:group-hover:text-indigo-500 border-transparent':
+ 'font-semibold group-hover:text-indigo-700 dark:group-hover:text-indigo-500 border-transparent':
!isSelected
}
)
diff --git a/assets/js/dashboard/stats/graph/visitor-graph.js b/assets/js/dashboard/stats/graph/visitor-graph.js
index 2db7b61a20c7..c9e19a538ce2 100644
--- a/assets/js/dashboard/stats/graph/visitor-graph.js
+++ b/assets/js/dashboard/stats/graph/visitor-graph.js
@@ -166,7 +166,7 @@ export default function VisitorGraph({ updateImportedDataInView }) {
return (
{(topStatsLoading || graphLoading) && renderLoader()}
diff --git a/assets/js/dashboard/stats/imported-query-unsupported-warning.js b/assets/js/dashboard/stats/imported-query-unsupported-warning.js
index a6aab6128c80..3f442fb45939 100644
--- a/assets/js/dashboard/stats/imported-query-unsupported-warning.js
+++ b/assets/js/dashboard/stats/imported-query-unsupported-warning.js
@@ -1,7 +1,8 @@
-import React from 'react'
+import React, { useRef, useEffect } from 'react'
import { ExclamationCircleIcon } from '@heroicons/react/24/outline'
import FadeIn from '../fade-in'
import { useQueryContext } from '../query-context'
+import { Tooltip } from '../util/tooltip'
export default function ImportedQueryUnsupportedWarning({
loading,
@@ -10,6 +11,7 @@ export default function ImportedQueryUnsupportedWarning({
message
}) {
const { query } = useQueryContext()
+ const portalRef = useRef(null)
const tooltipMessage =
message || 'Imported data is excluded due to applied filters'
const show =
@@ -18,12 +20,18 @@ export default function ImportedQueryUnsupportedWarning({
skipImportedReason === 'unsupported_query' &&
query.period !== 'realtime'
+ useEffect(() => {
+ if (typeof document !== 'undefined') {
+ portalRef.current = document.body
+ }
+ }, [])
+
if (show || altCondition) {
return (
-
-
-
-
+
+
+
+
)
} else {
diff --git a/assets/js/dashboard/stats/locations/index.js b/assets/js/dashboard/stats/locations/index.js
index 699d354e5d06..073b1518097f 100644
--- a/assets/js/dashboard/stats/locations/index.js
+++ b/assets/js/dashboard/stats/locations/index.js
@@ -15,7 +15,11 @@ import ImportedQueryUnsupportedWarning from '../imported-query-unsupported-warni
import { citiesRoute, countriesRoute, regionsRoute } from '../../router'
import { useQueryContext } from '../../query-context'
import { useSiteContext } from '../../site-context'
+import { ReportLayout } from '../reports/report-layout'
+import { ReportHeader } from '../reports/report-header'
import { TabButton, TabWrapper } from '../../components/tabs'
+import MoreLink from '../more-link'
+import { MoreLinkState } from '../more-link-state'
function Countries({ query, site, onClick, afterFetchData }) {
function fetchData() {
@@ -51,10 +55,6 @@ function Countries({ query, site, onClick, afterFetchData }) {
onClick={onClick}
keyLabel="Country"
metrics={chooseMetrics()}
- detailsLinkProps={{
- path: countriesRoute.path,
- search: (search) => search
- }}
renderIcon={renderIcon}
color="bg-orange-50 group-hover/row:bg-orange-100"
/>
@@ -95,7 +95,6 @@ function Regions({ query, site, onClick, afterFetchData }) {
onClick={onClick}
keyLabel="Region"
metrics={chooseMetrics()}
- detailsLinkProps={{ path: regionsRoute.path, search: (search) => search }}
renderIcon={renderIcon}
color="bg-orange-50 group-hover/row:bg-orange-100"
/>
@@ -135,19 +134,12 @@ function Cities({ query, site, afterFetchData }) {
getFilterInfo={getFilterInfo}
keyLabel="City"
metrics={chooseMetrics()}
- detailsLinkProps={{ path: citiesRoute.path, search: (search) => search }}
renderIcon={renderIcon}
color="bg-orange-50 group-hover/row:bg-orange-100"
/>
)
}
-const labelFor = {
- countries: 'Countries',
- regions: 'Regions',
- cities: 'Cities'
-}
-
class Locations extends React.Component {
constructor(props) {
super(props)
@@ -159,7 +151,8 @@ class Locations extends React.Component {
this.state = {
mode: storedTab || 'map',
loading: true,
- skipImportedReason: null
+ skipImportedReason: null,
+ moreLinkState: MoreLinkState.LOADING
}
}
@@ -183,7 +176,7 @@ class Locations extends React.Component {
this.props.query !== prevProps.query ||
this.state.mode !== prevState.mode
) {
- this.setState({ loading: true })
+ this.setState({ loading: true, moreLinkState: MoreLinkState.LOADING })
}
}
@@ -206,8 +199,16 @@ class Locations extends React.Component {
}
afterFetchData(apiResponse) {
+ let newMoreLinkState
+
+ if (apiResponse.results && apiResponse.results.length > 0) {
+ newMoreLinkState = MoreLinkState.READY
+ } else {
+ newMoreLinkState = MoreLinkState.HIDDEN
+ }
this.setState({
loading: false,
+ moreLinkState: newMoreLinkState,
skipImportedReason: apiResponse.skip_imported_reason
})
}
@@ -251,38 +252,55 @@ class Locations extends React.Component {
}
}
+ getMoreLinkProps() {
+ let path
+
+ if (this.state.mode === 'regions') {
+ path = regionsRoute.path
+ } else if (this.state.mode === 'cities') {
+ path = citiesRoute.path
+ } else {
+ path = countriesRoute.path
+ }
+
+ return { path: path, search: (search) => search }
+ }
+
render() {
return (
-
-
-
-
- {labelFor[this.state.mode] || 'Locations'}
-
+
+
+
+
+ {[
+ { label: 'Map', value: 'map' },
+ { label: 'Countries', value: 'countries' },
+ { label: 'Regions', value: 'regions' },
+ { label: 'Cities', value: 'cities' }
+ ].map(({ value, label }) => (
+
+ {label}
+
+ ))}
+
-
- {[
- { label: 'Map', value: 'map' },
- { label: 'Countries', value: 'countries' },
- { label: 'Regions', value: 'regions' },
- { label: 'Cities', value: 'cities' }
- ].map(({ value, label }) => (
-
- {label}
-
- ))}
-
-
+
+
{this.renderContent()}
-
+
)
}
}
diff --git a/assets/js/dashboard/stats/locations/map-tooltip.tsx b/assets/js/dashboard/stats/locations/map-tooltip.tsx
index 7a36a9a8f85a..9e28c5c3f947 100644
--- a/assets/js/dashboard/stats/locations/map-tooltip.tsx
+++ b/assets/js/dashboard/stats/locations/map-tooltip.tsx
@@ -32,7 +32,10 @@ export const MapTooltip = ({ name, value, label, x, y }: MapTooltipProps) => (
top: y
}}
>
-
{name}
-
{value} {label}
+
{name}
+
+ {value}
+ {label}
+
)
diff --git a/assets/js/dashboard/stats/locations/map.tsx b/assets/js/dashboard/stats/locations/map.tsx
index 593a5747d8f0..8a0a53668955 100644
--- a/assets/js/dashboard/stats/locations/map.tsx
+++ b/assets/js/dashboard/stats/locations/map.tsx
@@ -2,7 +2,12 @@ import React, { useCallback, useEffect, useMemo, useRef, useState } from 'react'
import * as d3 from 'd3'
import classNames from 'classnames'
import * as api from '../../api'
-import { replaceFilterByPrefix, cleanLabels } from '../../util/filters'
+import {
+ replaceFilterByPrefix,
+ cleanLabels,
+ hasConversionGoalFilter,
+ isRealTimeDashboard
+} from '../../util/filters'
import { useAppNavigate } from '../../navigation/use-app-navigate'
import { numberShortFormatter } from '../../util/number-formatter'
import * as topojson from 'topojson-client'
@@ -12,8 +17,6 @@ import { useQueryContext } from '../../query-context'
import worldJson from 'visionscarto-world-atlas/world/110m.json'
import { UIMode, useTheme } from '../../theme-context'
import { apiPath } from '../../util/url'
-import MoreLink from '../more-link'
-import { countriesRoute } from '../../router'
import { MIN_HEIGHT } from '../reports/list'
import { MapTooltip } from './map-tooltip'
import { GeolocationNotice } from './geolocation-notice'
@@ -47,10 +50,15 @@ const WorldMap = ({
hoveredCountryAlpha3Code: string | null
}>({ x: 0, y: 0, hoveredCountryAlpha3Code: null })
- const labels =
- query.period === 'realtime'
- ? { singular: 'Current visitor', plural: 'Current visitors' }
- : { singular: 'Visitor', plural: 'Visitors' }
+ const labels = (() => {
+ if (hasConversionGoalFilter(query)) {
+ return { singular: 'Conversion', plural: 'Conversions' }
+ }
+ if (isRealTimeDashboard(query)) {
+ return { singular: 'Current visitor', plural: 'Current visitors' }
+ }
+ return { singular: 'Visitor', plural: 'Visitors' }
+ })()
const { data, refetch, isFetching, isError } = useQuery({
queryKey: ['countries', 'map', query],
@@ -78,7 +86,7 @@ const WorldMap = ({
if (data) {
afterFetchData(data)
}
- }, [afterFetchData, data])
+ }, [afterFetchData, data, isFetching])
const { maxValue, dataByCountryCode } = useMemo(() => {
const dataByCountryCode: Map = new Map()
@@ -148,10 +156,12 @@ const WorldMap = ({
: undefined
return (
-
-
+
-
) => search
- }}
- className="mt-3"
- onClick={undefined}
- />
{site.isDbip && }
)
}
const colorScales = {
- [UIMode.dark]: ['#2e3954', '#6366f1'],
- [UIMode.light]: ['#f5f3ff', '#a78bfa']
+ [UIMode.dark]: ['#2a276d', '#6366f1'], // custom color between indigo-900 and indigo-950, indigo-500
+ [UIMode.light]: ['#e0e7ff', '#818cf8'] // indigo-100, indigo-400
}
const sharedCountryClass = classNames('transition-colors')
@@ -203,19 +204,19 @@ const sharedCountryClass = classNames('transition-colors')
const countryClass = classNames(
sharedCountryClass,
'stroke-1',
- 'fill-[#fafafa]',
- 'stroke-[#dae1e7]',
- 'dark:fill-[#323236]',
- 'dark:stroke-[#18181b]'
+ 'fill-gray-150',
+ 'stroke-white',
+ 'dark:fill-gray-750',
+ 'dark:stroke-gray-900'
)
const highlightedCountryClass = classNames(
sharedCountryClass,
- 'stroke-2',
- 'fill-[#f4f4f5]',
- 'stroke-[#a78bfa]',
- 'dark:fill-[#3f3f46]',
- 'dark:stroke-[#6366f1]'
+ 'stroke-[1.5px]',
+ 'fill-gray-150',
+ 'stroke-indigo-400',
+ 'dark:fill-gray-750',
+ 'dark:stroke-indigo-500'
)
/**
diff --git a/assets/js/dashboard/stats/modals/devices/choose-metrics.js b/assets/js/dashboard/stats/modals/devices/choose-metrics.js
index bf647fcdb7cf..34959eddcb1d 100644
--- a/assets/js/dashboard/stats/modals/devices/choose-metrics.js
+++ b/assets/js/dashboard/stats/modals/devices/choose-metrics.js
@@ -22,7 +22,7 @@ export default function chooseMetrics(query, site) {
].filter((metric) => !!metric)
}
- if (isRealTimeDashboard(query)) {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/modals/devices/screen-sizes.js b/assets/js/dashboard/stats/modals/devices/screen-sizes.js
index 5b86d8a56b37..637ea4bcfd3b 100644
--- a/assets/js/dashboard/stats/modals/devices/screen-sizes.js
+++ b/assets/js/dashboard/stats/modals/devices/screen-sizes.js
@@ -13,10 +13,10 @@ function ScreenSizesModal() {
const site = useSiteContext()
const reportInfo = {
- title: 'Screen sizes',
+ title: 'Devices',
dimension: 'screen',
endpoint: url.apiPath(site, '/screen-sizes'),
- dimensionLabel: 'Screen size',
+ dimensionLabel: 'Device',
defaultOrder: ['visitors', SortDirection.desc]
}
diff --git a/assets/js/dashboard/stats/modals/entry-pages.js b/assets/js/dashboard/stats/modals/entry-pages.js
index 61f09ea37fa5..51840cd36e2c 100644
--- a/assets/js/dashboard/stats/modals/entry-pages.js
+++ b/assets/js/dashboard/stats/modals/entry-pages.js
@@ -63,7 +63,7 @@ function EntryPagesModal() {
].filter((metric) => !!metric)
}
- if (isRealTimeDashboard(query)) {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/modals/exit-pages.js b/assets/js/dashboard/stats/modals/exit-pages.js
index 433f8f9b293d..0cb072a7642c 100644
--- a/assets/js/dashboard/stats/modals/exit-pages.js
+++ b/assets/js/dashboard/stats/modals/exit-pages.js
@@ -1,6 +1,9 @@
import React, { useCallback } from 'react'
import Modal from './modal'
-import { hasConversionGoalFilter } from '../../util/filters'
+import {
+ hasConversionGoalFilter,
+ isRealTimeDashboard
+} from '../../util/filters'
import { addFilter, revenueAvailable } from '../../query'
import BreakdownModal from './breakdown-modal'
import * as metrics from '../reports/metrics'
@@ -60,7 +63,7 @@ function ExitPagesModal() {
].filter((metric) => !!metric)
}
- if (query.period === 'realtime') {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/modals/google-keywords.tsx b/assets/js/dashboard/stats/modals/google-keywords.tsx
index 31979bac4776..01bd2c65fcfb 100644
--- a/assets/js/dashboard/stats/modals/google-keywords.tsx
+++ b/assets/js/dashboard/stats/modals/google-keywords.tsx
@@ -91,7 +91,7 @@ function GoogleKeywordsModal() {
return (
!!metric)
}
- if (query.period === 'realtime') {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/modals/pages.js b/assets/js/dashboard/stats/modals/pages.js
index b4c05d17676e..3445480abf28 100644
--- a/assets/js/dashboard/stats/modals/pages.js
+++ b/assets/js/dashboard/stats/modals/pages.js
@@ -63,7 +63,7 @@ function PagesModal() {
].filter((metric) => !!metric)
}
- if (isRealTimeDashboard(query)) {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/modals/referrer-drilldown.js b/assets/js/dashboard/stats/modals/referrer-drilldown.js
index 324aafd2e87e..21b1b9bf38b8 100644
--- a/assets/js/dashboard/stats/modals/referrer-drilldown.js
+++ b/assets/js/dashboard/stats/modals/referrer-drilldown.js
@@ -70,7 +70,7 @@ function ReferrerDrilldownModal() {
].filter((metric) => !!metric)
}
- if (isRealTimeDashboard(query)) {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/modals/sources.js b/assets/js/dashboard/stats/modals/sources.js
index 43c83b2cfcdc..383c4937a356 100644
--- a/assets/js/dashboard/stats/modals/sources.js
+++ b/assets/js/dashboard/stats/modals/sources.js
@@ -136,7 +136,7 @@ function SourcesModal({ currentView }) {
].filter((metric) => !!metric)
}
- if (isRealTimeDashboard(query)) {
+ if (isRealTimeDashboard(query) && !hasConversionGoalFilter(query)) {
return [
metrics.createVisitors({
renderLabel: (_query) => 'Current visitors',
diff --git a/assets/js/dashboard/stats/more-link-state.js b/assets/js/dashboard/stats/more-link-state.js
new file mode 100644
index 000000000000..45052d31d909
--- /dev/null
+++ b/assets/js/dashboard/stats/more-link-state.js
@@ -0,0 +1,5 @@
+export const MoreLinkState = {
+ HIDDEN: 'hidden',
+ LOADING: 'loading',
+ READY: 'ready'
+}
diff --git a/assets/js/dashboard/stats/more-link.js b/assets/js/dashboard/stats/more-link.js
index 13c91782aec3..99da748ef8ed 100644
--- a/assets/js/dashboard/stats/more-link.js
+++ b/assets/js/dashboard/stats/more-link.js
@@ -1,11 +1,12 @@
-import React from 'react'
+import React, { useRef, useEffect } from 'react'
import { AppNavigationLink } from '../navigation/use-app-navigate'
+import { Tooltip } from '../util/tooltip'
+import { MoreLinkState } from './more-link-state'
function detailsIcon() {
return (
)
}
@@ -273,7 +259,7 @@ export default function ListReport<
})
return (
-
+
{keyLabel}
{metricLabels}
diff --git a/assets/js/dashboard/stats/reports/metrics.js b/assets/js/dashboard/stats/reports/metrics.js
index d3089714c59d..107b373f2ef6 100644
--- a/assets/js/dashboard/stats/reports/metrics.js
+++ b/assets/js/dashboard/stats/reports/metrics.js
@@ -77,12 +77,12 @@ export const createVisitors = (props) => {
const realtimeLabel = props.realtimeLabel || 'Current visitors'
const goalFilterLabel = props.goalFilterLabel || 'Conversions'
- if (query.period === 'realtime') {
- return realtimeLabel
- }
if (query && hasConversionGoalFilter(query)) {
return goalFilterLabel
}
+ if (query.period === 'realtime') {
+ return realtimeLabel
+ }
return defaultLabel
}
}
diff --git a/assets/js/dashboard/stats/reports/report-header.js b/assets/js/dashboard/stats/reports/report-header.js
new file mode 100644
index 000000000000..c5b8c381abdb
--- /dev/null
+++ b/assets/js/dashboard/stats/reports/report-header.js
@@ -0,0 +1,9 @@
+import React from 'react'
+
+export function ReportHeader({ children }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/assets/js/dashboard/stats/reports/report-layout.js b/assets/js/dashboard/stats/reports/report-layout.js
new file mode 100644
index 000000000000..acbd83ce9a71
--- /dev/null
+++ b/assets/js/dashboard/stats/reports/report-layout.js
@@ -0,0 +1,15 @@
+import React from 'react'
+import classNames from 'classnames'
+
+export function ReportLayout({ children, className = undefined }) {
+ return (
+
+ {children}
+
+ )
+}
diff --git a/assets/js/dashboard/stats/sources/referrer-list.js b/assets/js/dashboard/stats/sources/referrer-list.js
index 8f7269d88673..42ce1d3e2865 100644
--- a/assets/js/dashboard/stats/sources/referrer-list.js
+++ b/assets/js/dashboard/stats/sources/referrer-list.js
@@ -9,6 +9,11 @@ import { useQueryContext } from '../../query-context'
import { useSiteContext } from '../../site-context'
import { referrersDrilldownRoute } from '../../router'
import { SourceFavicon } from './source-favicon'
+import { ReportLayout } from '../reports/report-layout'
+import { ReportHeader } from '../reports/report-header'
+import { TabButton, TabWrapper } from '../../components/tabs'
+import MoreLink from '../more-link'
+import { MoreLinkState } from '../more-link-state'
const NO_REFERRER = 'Direct / None'
@@ -17,8 +22,12 @@ export default function Referrers({ source }) {
const site = useSiteContext()
const [skipImportedReason, setSkipImportedReason] = useState(null)
const [loading, setLoading] = useState(true)
+ const [moreLinkState, setMoreLinkState] = useState(MoreLinkState.LOADING)
- useEffect(() => setLoading(true), [query])
+ useEffect(() => {
+ setLoading(true)
+ setMoreLinkState(MoreLinkState.LOADING)
+ }, [query])
function fetchReferrers() {
return api.get(
@@ -31,6 +40,11 @@ export default function Referrers({ source }) {
function afterFetchReferrers(apiResponse) {
setLoading(false)
setSkipImportedReason(apiResponse.skip_imported_reason)
+ if (apiResponse.results && apiResponse.results.length > 0) {
+ setMoreLinkState(MoreLinkState.READY)
+ } else {
+ setMoreLinkState(MoreLinkState.HIDDEN)
+ }
}
function getExternalLinkUrl(referrer) {
@@ -68,29 +82,38 @@ export default function Referrers({ source }) {
}
return (
-
-
-
Top Referrers
-
+
+
+
+ {}}>
+ Top referrers
+
+
+
+
+ search
+ }}
/>
-
+
search
- }}
getExternalLinkUrl={getExternalLinkUrl}
renderIcon={renderIcon}
color="bg-blue-50"
/>
-
+
)
}
diff --git a/assets/js/dashboard/stats/sources/search-terms.tsx b/assets/js/dashboard/stats/sources/search-terms.tsx
index bab9205f1e34..7cb15c2d5565 100644
--- a/assets/js/dashboard/stats/sources/search-terms.tsx
+++ b/assets/js/dashboard/stats/sources/search-terms.tsx
@@ -1,14 +1,18 @@
import React, { useEffect, useCallback } from 'react'
import FadeIn from '../../fade-in'
import Bar from '../bar'
-import MoreLink from '../more-link'
import { numberShortFormatter } from '../../util/number-formatter'
import RocketIcon from '../modals/rocket-icon'
import * as api from '../../api'
import LazyLoader from '../../components/lazy-loader'
-import { referrersGoogleRoute } from '../../router'
import { useQueryContext } from '../../query-context'
import { PlausibleSite, useSiteContext } from '../../site-context'
+import { ReportLayout } from '../reports/report-layout'
+import { ReportHeader } from '../reports/report-header'
+import { TabButton, TabWrapper } from '../../components/tabs'
+import MoreLink from '../more-link'
+import { MoreLinkState } from '../more-link-state'
+import { referrersGoogleRoute } from '../../router'
interface SearchTerm {
name: string
@@ -74,6 +78,9 @@ function ConfigureSearchTermsCTA({
export function SearchTerms() {
const site = useSiteContext()
const { query } = useQueryContext()
+ const [moreLinkState, setMoreLinkState] = React.useState(
+ MoreLinkState.LOADING
+ )
const [loading, setLoading] = React.useState(true)
const [errorPayload, setErrorPayload] = React.useState
(
@@ -94,11 +101,17 @@ export function SearchTerms() {
setLoading(false)
setSearchTerms(res.results)
setErrorPayload(null)
+ if (res.results && res.results.length > 0) {
+ setMoreLinkState(MoreLinkState.READY)
+ } else {
+ setMoreLinkState(MoreLinkState.HIDDEN)
+ }
})
.catch((error) => {
setLoading(false)
setSearchTerms(null)
setErrorPayload(error.payload)
+ setMoreLinkState(MoreLinkState.HIDDEN)
})
}, [query, site.domain])
@@ -106,6 +119,7 @@ export function SearchTerms() {
if (visible) {
setLoading(true)
setSearchTerms([])
+ setMoreLinkState(MoreLinkState.LOADING)
fetchSearchTerms()
}
}, [query, fetchSearchTerms, visible])
@@ -143,15 +157,6 @@ export function SearchTerms() {
))}
- ) => search
- }}
- className="w-full mt-3"
- onClick={undefined}
- />
)
}
@@ -186,8 +191,23 @@ export function SearchTerms() {
}
return (
-
-
Search Terms
+
+
+
+
+ {}}>
+ Search terms
+
+
+
+ search
+ }}
+ />
+
{loading && (
@@ -204,6 +224,6 @@ export function SearchTerms() {
-
+
)
}
diff --git a/assets/js/dashboard/stats/sources/source-list.js b/assets/js/dashboard/stats/sources/source-list.js
index 1984101c9a8a..44d3c9f2e85b 100644
--- a/assets/js/dashboard/stats/sources/source-list.js
+++ b/assets/js/dashboard/stats/sources/source-list.js
@@ -23,7 +23,11 @@ import {
utmSourcesRoute,
utmTermsRoute
} from '../../router'
+import { ReportLayout } from '../reports/report-layout'
+import { ReportHeader } from '../reports/report-header'
import { DropdownTabButton, TabButton, TabWrapper } from '../../components/tabs'
+import MoreLink from '../more-link'
+import { MoreLinkState } from '../more-link-state'
const UTM_TAGS = {
utm_medium: {
@@ -83,7 +87,6 @@ function AllSources({ afterFetchData }) {
getFilterInfo={getFilterInfo}
keyLabel="Source"
metrics={chooseMetrics()}
- detailsLinkProps={{ path: sourcesRoute.path, search: (search) => search }}
renderIcon={renderIcon}
color="bg-blue-50 group-hover/row:bg-blue-100"
/>
@@ -122,10 +125,6 @@ function Channels({ onClick, afterFetchData }) {
keyLabel="Channel"
onClick={onClick}
metrics={chooseMetrics()}
- detailsLinkProps={{
- path: channelsRoute.path,
- search: (search) => search
- }}
color="bg-blue-50 group-hover/row:bg-blue-100"
/>
)
@@ -136,14 +135,6 @@ function UTMSources({ tab, afterFetchData }) {
const site = useSiteContext()
const utmTag = UTM_TAGS[tab]
- const route = {
- utm_medium: utmMediumsRoute,
- utm_source: utmSourcesRoute,
- utm_campaign: utmCampaignsRoute,
- utm_content: utmContentsRoute,
- utm_term: utmTermsRoute
- }[tab]
-
function fetchData() {
return api.get(url.apiPath(site, utmTag.endpoint), query, { limit: 9 })
}
@@ -171,21 +162,11 @@ function UTMSources({ tab, afterFetchData }) {
getFilterInfo={getFilterInfo}
keyLabel={utmTag.label}
metrics={chooseMetrics()}
- detailsLinkProps={{ path: route?.path, search: (search) => search }}
color="bg-blue-50 group-hover/row:bg-blue-100"
/>
)
}
-const labelFor = {
- channels: 'Top channels',
- all: 'Top sources'
-}
-
-for (const [key, utm_tag] of Object.entries(UTM_TAGS)) {
- labelFor[key] = utm_tag.title
-}
-
export default function SourceList() {
const site = useSiteContext()
const { query } = useQueryContext()
@@ -194,9 +175,13 @@ export default function SourceList() {
const [currentTab, setCurrentTab] = useState(storedTab || 'all')
const [loading, setLoading] = useState(true)
const [skipImportedReason, setSkipImportedReason] = useState(null)
+ const [moreLinkState, setMoreLinkState] = useState(MoreLinkState.LOADING)
const previousQuery = usePrevious(query)
- useEffect(() => setLoading(true), [query, currentTab])
+ useEffect(() => {
+ setLoading(true)
+ setMoreLinkState(MoreLinkState.LOADING)
+ }, [query, currentTab])
useEffect(() => {
const isRemovingFilter = (filterName) => {
@@ -225,6 +210,48 @@ export default function SourceList() {
setTab('all')()
}
+ function afterFetchData(apiResponse) {
+ setLoading(false)
+ setSkipImportedReason(apiResponse.skip_imported_reason)
+ if (apiResponse.results && apiResponse.results.length > 0) {
+ setMoreLinkState(MoreLinkState.READY)
+ } else {
+ setMoreLinkState(MoreLinkState.HIDDEN)
+ }
+ }
+
+ function moreLinkProps() {
+ if (Object.keys(UTM_TAGS).includes(currentTab)) {
+ const route = {
+ utm_medium: utmMediumsRoute,
+ utm_source: utmSourcesRoute,
+ utm_campaign: utmCampaignsRoute,
+ utm_content: utmContentsRoute,
+ utm_term: utmTermsRoute
+ }[currentTab]
+ return route
+ ? {
+ path: route.path,
+ search: (search) => search
+ }
+ : null
+ }
+
+ switch (currentTab) {
+ case 'channels':
+ return {
+ path: channelsRoute.path,
+ search: (search) => search
+ }
+ case 'all':
+ default:
+ return {
+ path: sourcesRoute.path,
+ search: (search) => search
+ }
+ }
+ }
+
function renderContent() {
if (Object.keys(UTM_TAGS).includes(currentTab)) {
return
@@ -241,54 +268,45 @@ export default function SourceList() {
}
}
- function afterFetchData(apiResponse) {
- setLoading(false)
- setSkipImportedReason(apiResponse.skip_imported_reason)
- }
-
return (
-
- {/* Header Container */}
-
-
-
- {labelFor[currentTab]}
-
+
+
+
+
+ {[
+ { value: 'channels', label: 'Channels' },
+ { value: 'all', label: 'Sources' }
+ ].map(({ value, label }) => (
+
+ {label}
+
+ ))}
+ ({
+ value,
+ label: title,
+ onClick: setTab(value),
+ selected: currentTab === value
+ }))}
+ >
+ {UTM_TAGS[currentTab] ? UTM_TAGS[currentTab].title : 'Campaigns'}
+
+
-
- {[
- { value: 'channels', label: 'Channels' },
- { value: 'all', label: 'Sources' }
- ].map(({ value, label }) => (
-
- {label}
-
- ))}
- ({
- value,
- label: title,
- onClick: setTab(value),
- selected: currentTab === value
- }))}
- >
- {UTM_TAGS[currentTab] ? UTM_TAGS[currentTab].title : 'Campaigns'}
-
-
-
- {/* Main Contents */}
+
+
{renderContent()}
-
+
)
}
diff --git a/assets/js/dashboard/util/tooltip.tsx b/assets/js/dashboard/util/tooltip.tsx
index fbce1a08133f..0ec0010427c6 100644
--- a/assets/js/dashboard/util/tooltip.tsx
+++ b/assets/js/dashboard/util/tooltip.tsx
@@ -91,7 +91,7 @@ function TooltipMessage({
ref={setPopperElement}
style={popperStyle}
{...popperAttributes}
- className="z-[999] px-2 py-1 rounded-sm text-sm text-gray-100 font-medium bg-gray-800 dark:bg-gray-700"
+ className="z-[99] px-2 py-1 rounded-sm text-sm text-gray-100 font-medium bg-gray-800 dark:bg-gray-700"
role="tooltip"
>
{children}