diff --git a/.github/workflows/cd-cloud.yml b/.github/workflows/cd-cloud.yml index b155624a7..90a09dab0 100644 --- a/.github/workflows/cd-cloud.yml +++ b/.github/workflows/cd-cloud.yml @@ -1,4 +1,4 @@ -name: Create docker images +name: Create docker images (cloud) on: push: diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml index cf275cb87..5a529a6c5 100644 --- a/.github/workflows/cd.yml +++ b/.github/workflows/cd.yml @@ -1,50 +1,101 @@ name: Create docker images -on: [create] +on: + push: + branches: + - master + - main + - dev + # Publish semver tags as releases. + tags: [ 'v*.*.*' ] + pull_request: + branches: + - master + - main + - dev + workflow_dispatch: jobs: build: name: Build, push, and deploy - if: ${{ startsWith(github.ref, 'refs/tags/v') }} runs-on: ubuntu-latest + permissions: + contents: read + packages: write + # This is used to complete the identity challenge + # with sigstore/fulcio when running outside of PRs. + id-token: write strategy: matrix: db-type: [postgresql] steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v5 - - name: Set env - run: | - echo "NOW=$(date -u +'%Y-%m-%dT%H:%M:%SZ')" >> $GITHUB_ENV - - - name: Generate tags - id: generate_tags - run: | - echo "tag_patch=$(echo ${{ matrix.db-type }})-${GITHUB_REF#refs/tags/}" >> $GITHUB_ENV - echo "tag_minor=$(echo ${{ matrix.db-type }})-$(echo ${GITHUB_REF#refs/tags/} | cut -d. -f1,2)" >> $GITHUB_ENV - echo "tag_major=$(echo ${{ matrix.db-type }})-$(echo ${GITHUB_REF#refs/tags/} | cut -d. -f1)" >> $GITHUB_ENV - echo "tag_latest=$(echo ${{ matrix.db-type }})-latest" >> $GITHUB_ENV + # Install the cosign tool except on PR + # https://github.com/sigstore/cosign-installer + - name: Install cosign + if: github.event_name != 'pull_request' + uses: sigstore/cosign-installer@v3 - - uses: mr-smithers-excellent/docker-build-push@v6 - name: Build & push Docker image to ghcr.io for ${{ matrix.db-type }} + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Log into registry docker.io + if: github.event_name != 'pull_request' && github.repository == 'umami-software/umami' + uses: docker/login-action@v3 with: - image: umami - tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }} - buildArgs: DATABASE_TYPE=${{ matrix.db-type }} - registry: ghcr.io - multiPlatform: true - platform: linux/amd64,linux/arm64 - username: ${{ github.actor }} - password: ${{ secrets.GITHUB_TOKEN }} - - - uses: mr-smithers-excellent/docker-build-push@v6 - name: Build & push Docker image to docker.io for ${{ matrix.db-type }} - with: - image: umamisoftware/umami - tags: ${{ env.tag_major }}, ${{ env.tag_minor }}, ${{ env.tag_patch }}, ${{ env.tag_latest }} - buildArgs: DATABASE_TYPE=${{ matrix.db-type }} registry: docker.io username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Log into ghcr registry + if: github.event_name != 'pull_request' + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Extract Docker metadata + id: meta + uses: docker/metadata-action@v5 + with: + images: | + umamisoftware/umami,enable=${{ github.repository == 'umami-software/umami' }} + ghcr.io/${{ github.repository }} + flavor: | + latest=auto + prefix=${{ matrix.db-type }}- + tags: | + type=ref,event=branch + type=ref,event=pr + + # output 1.1.2 + type=semver,pattern={{version}} + # output 1.1 + type=semver,pattern={{major}}.{{minor}} + # output 1 + type=semver,pattern={{major}} + + - name: Build and push Docker image + id: build-and-push + uses: docker/build-push-action@v6 + with: + context: . + platforms: linux/amd64,linux/arm64 + build-args: DATABASE_TYPE=${{ matrix.db-type }} + push: ${{ github.event_name != 'pull_request' }} + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha + cache-to: type=gha,mode=max + + # Sign the resulting Docker image digest except on PRs. + - name: Sign the published Docker image + if: ${{ github.event_name != 'pull_request' }} + env: + TAGS: ${{ steps.meta.outputs.tags }} + DIGEST: ${{ steps.build-and-push.outputs.digest }} + run: echo "${TAGS}" | xargs -I {} cosign sign --yes "{}@${DIGEST}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88a922e34..eee0a02d3 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -14,11 +14,16 @@ jobs: strategy: matrix: include: - - node-version: 18.18 - db-type: postgresql + - node-version: 18.18 + pnpm-version: 10 + db-type: postgresql steps: - uses: actions/checkout@v4 + - uses: pnpm/action-setup@v4 # required so that setup-node will work + with: + version: ${{ matrix.pnpm-version }} + run_install: false - name: Use Node.js ${{ matrix.node-version }} uses: actions/setup-node@v4 with: diff --git a/package.components.json b/package.components.json index ba951df99..927e06604 100644 --- a/package.components.json +++ b/package.components.json @@ -1,6 +1,6 @@ { "name": "@umami/components", - "version": "0.125.0", + "version": "0.127.0", "description": "Umami React components.", "author": "Mike Cao ", "license": "MIT", diff --git a/src/app/(main)/SideNav.tsx b/src/app/(main)/SideNav.tsx index c351ef47f..6e67465cd 100644 --- a/src/app/(main)/SideNav.tsx +++ b/src/app/(main)/SideNav.tsx @@ -69,7 +69,7 @@ export function SideNav(props: SidebarProps) { diff --git a/src/app/(main)/links/LinkDeleteButton.tsx b/src/app/(main)/links/LinkDeleteButton.tsx index 05a3217f7..6754074f2 100644 --- a/src/app/(main)/links/LinkDeleteButton.tsx +++ b/src/app/(main)/links/LinkDeleteButton.tsx @@ -15,7 +15,7 @@ export function LinkDeleteButton({ name: string; onSave?: () => void; }) { - const { formatMessage, labels, getErrorMessage } = useMessages(); + const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages(); const { mutateAsync, isPending, error, touch } = useDeleteQuery(`/links/${linkId}`); const handleConfirm = async (close: () => void) => { @@ -33,9 +33,14 @@ export function LinkDeleteButton({ {({ close }) => ( {name}, + }} + /> + } isLoading={isPending} error={getErrorMessage(error)} onConfirm={handleConfirm.bind(null, close)} diff --git a/src/app/(main)/links/[linkId]/LinkHeader.tsx b/src/app/(main)/links/[linkId]/LinkHeader.tsx index 0d325ebb4..ba1de3ee2 100644 --- a/src/app/(main)/links/[linkId]/LinkHeader.tsx +++ b/src/app/(main)/links/[linkId]/LinkHeader.tsx @@ -10,7 +10,7 @@ export function LinkHeader() { const link = useLink(); return ( - }> + } marginBottom="3"> diff --git a/src/app/(main)/pixels/PixelDeleteButton.tsx b/src/app/(main)/pixels/PixelDeleteButton.tsx index c90261e55..98e34c1f9 100644 --- a/src/app/(main)/pixels/PixelDeleteButton.tsx +++ b/src/app/(main)/pixels/PixelDeleteButton.tsx @@ -13,7 +13,7 @@ export function PixelDeleteButton({ name: string; onSave?: () => void; }) { - const { formatMessage, labels, getErrorMessage } = useMessages(); + const { formatMessage, labels, getErrorMessage, FormattedMessage } = useMessages(); const { mutateAsync, isPending, error } = useDeleteQuery(`/pixels/${pixelId}`); const { touch } = useModified(); @@ -32,9 +32,14 @@ export function PixelDeleteButton({ {({ close }) => ( {name}, + }} + /> + } isLoading={isPending} error={getErrorMessage(error)} onConfirm={handleConfirm.bind(null, close)} diff --git a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx index 991d807d5..7e1a62b87 100644 --- a/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx +++ b/src/app/(main)/pixels/[pixelId]/PixelHeader.tsx @@ -10,7 +10,7 @@ export function PixelHeader() { const pixel = usePixel(); return ( - }> + } marginBottom="3"> diff --git a/src/app/(main)/teams/TeamLeaveForm.tsx b/src/app/(main)/teams/TeamLeaveForm.tsx index c8063365b..1c7846b8d 100644 --- a/src/app/(main)/teams/TeamLeaveForm.tsx +++ b/src/app/(main)/teams/TeamLeaveForm.tsx @@ -14,7 +14,7 @@ export function TeamLeaveForm({ onSave: () => void; onClose: () => void; }) { - const { formatMessage, labels, messages, getErrorMessage } = useMessages(); + const { formatMessage, labels, messages, getErrorMessage, FormattedMessage } = useMessages(); const { mutateAsync, error, isPending } = useDeleteQuery(`/teams/${teamId}/users/${userId}`); const { touch } = useModified(); @@ -31,9 +31,14 @@ export function TeamLeaveForm({ return ( {teamName}, + }} + /> + } onConfirm={handleConfirm} onClose={onClose} isLoading={isPending} diff --git a/src/app/(main)/teams/TeamsJoinButton.tsx b/src/app/(main)/teams/TeamsJoinButton.tsx index e30ff1204..742954970 100644 --- a/src/app/(main)/teams/TeamsJoinButton.tsx +++ b/src/app/(main)/teams/TeamsJoinButton.tsx @@ -1,5 +1,5 @@ import { Button, Icon, Modal, DialogTrigger, Dialog, Text, useToast } from '@umami/react-zen'; -import { AddUserSvg } from '@/components/icons'; +import { UserPlus } from '@/components/icons'; import { useMessages, useModified } from '@/components/hooks'; import { TeamJoinForm } from './TeamJoinForm'; @@ -17,7 +17,7 @@ export function TeamsJoinButton() { diff --git a/src/app/(main)/teams/[teamId]/TeamMemberRemoveButton.tsx b/src/app/(main)/teams/[teamId]/TeamMemberRemoveButton.tsx index 22c871a30..065f637bd 100644 --- a/src/app/(main)/teams/[teamId]/TeamMemberRemoveButton.tsx +++ b/src/app/(main)/teams/[teamId]/TeamMemberRemoveButton.tsx @@ -17,7 +17,7 @@ export function TeamMemberRemoveButton({ disabled?: boolean; onSave?: () => void; }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, FormattedMessage } = useMessages(); const { mutateAsync, isPending, error } = useDeleteQuery(`/teams/${teamId}/users/${userId}`); const { touch } = useModified(); @@ -36,9 +36,14 @@ export function TeamMemberRemoveButton({ {({ close }) => ( {userName}, + }} + /> + } isLoading={isPending} error={error} onConfirm={handleConfirm.bind(null, close)} diff --git a/src/app/(main)/teams/[teamId]/TeamSettings.tsx b/src/app/(main)/teams/[teamId]/TeamSettings.tsx index c186a1a0a..9649c4f7c 100644 --- a/src/app/(main)/teams/[teamId]/TeamSettings.tsx +++ b/src/app/(main)/teams/[teamId]/TeamSettings.tsx @@ -2,7 +2,7 @@ import Link from 'next/link'; import { Column, Icon, Text, Row } from '@umami/react-zen'; import { useLoginQuery, useMessages, useNavigation, useTeam } from '@/components/hooks'; import { ROLES } from '@/lib/constants'; -import { Users, ArrowLeft } from '@/components/icons'; +import { Users, ArrowRight } from '@/components/icons'; import { TeamLeaveButton } from '@/app/(main)/teams/TeamLeaveButton'; import { TeamManage } from './TeamManage'; import { TeamEditForm } from './TeamEditForm'; @@ -34,8 +34,8 @@ export function TeamSettings({ teamId }: { teamId: string }) { <> - - + + {formatMessage(labels.teams)} diff --git a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx index a4a0c1897..723c0249b 100644 --- a/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx +++ b/src/app/(main)/websites/[websiteId]/WebsiteFilterButton.tsx @@ -14,12 +14,12 @@ export function WebsiteFilterButton({ showText?: boolean; }) { const { formatMessage, labels } = useMessages(); - const { replaceParams, router } = useNavigation(); + const { updateParams, router } = useNavigation(); const handleChange = ({ filters, segment, cohort }: any) => { const params = filtersArrayToObject(filters); - const url = replaceParams({ ...params, segment, cohort }); + const url = updateParams({ ...params, segment, cohort }); router.push(url); }; diff --git a/src/app/(main)/websites/[websiteId]/cohorts/CohortDeleteButton.tsx b/src/app/(main)/websites/[websiteId]/cohorts/CohortDeleteButton.tsx index 239193b0d..32a9180db 100644 --- a/src/app/(main)/websites/[websiteId]/cohorts/CohortDeleteButton.tsx +++ b/src/app/(main)/websites/[websiteId]/cohorts/CohortDeleteButton.tsx @@ -16,7 +16,7 @@ export function CohortDeleteButton({ name: string; onSave?: () => void; }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, FormattedMessage } = useMessages(); const { mutateAsync, isPending, error, touch } = useDeleteQuery( `/websites/${websiteId}/segments/${cohortId}`, ); @@ -36,9 +36,14 @@ export function CohortDeleteButton({ {({ close }) => ( {name}, + }} + /> + } isLoading={isPending} error={error} onConfirm={handleConfirm.bind(null, close)} diff --git a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx index df4329012..7150f43f8 100644 --- a/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx +++ b/src/app/(main)/websites/[websiteId]/events/EventsTable.tsx @@ -3,7 +3,7 @@ import { useFormat, useMessages, useNavigation } from '@/components/hooks'; import { Empty } from '@/components/common/Empty'; import { Avatar } from '@/components/common/Avatar'; import Link from 'next/link'; -import { Bolt, Eye } from '@/components/icons'; +import { LightningSvg, Eye } from '@/components/icons'; import { DateDistance } from '@/components/common/DateDistance'; import { TypeIcon } from '@/components/common/TypeIcon'; @@ -25,7 +25,7 @@ export function EventsTable({ data = [] }) { - {row.eventName ? : } + {row.eventName ? : } {formatMessage(row.eventName ? labels.triggeredEvent : labels.viewedPage)} diff --git a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx index c7bd13476..e994831d6 100644 --- a/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx +++ b/src/app/(main)/websites/[websiteId]/realtime/RealtimeLog.tsx @@ -8,7 +8,7 @@ import { useTimezone, useWebsite, } from '@/components/hooks'; -import { Eye, Visitor, Bolt } from '@/components/icons'; +import { Eye, User, LightningSvg } from '@/components/icons'; import { BROWSERS, OS_NAMES } from '@/lib/constants'; import { stringToColor } from '@/lib/format'; import { useMemo, useState } from 'react'; @@ -23,14 +23,14 @@ const TYPE_EVENT = 'event'; const icons = { [TYPE_PAGEVIEW]: , - [TYPE_SESSION]: , - [TYPE_EVENT]: , + [TYPE_SESSION]: , + [TYPE_EVENT]: , }; export function RealtimeLog({ data }: { data: any }) { const website = useWebsite(); const [search, setSearch] = useState(''); - const { formatMessage, labels, messages } = useMessages(); + const { formatMessage, labels, messages, FormattedMessage } = useMessages(); const { formatValue } = useFormat(); const { locale } = useLocale(); const { formatTimezoneDate } = useTimezone(); @@ -74,20 +74,25 @@ export function RealtimeLog({ data }: { data: any }) { const { __type, eventName, urlPath, browser, os, country, device } = log; if (__type === TYPE_EVENT) { - return formatMessage(messages.eventLog, { - event: {eventName || formatMessage(labels.unknown)}, - url: ( - - {urlPath} - - ), - }); + return ( + {eventName || formatMessage(labels.unknown)}, + url: ( + + {urlPath} + + ), + }} + /> + ); } if (__type === TYPE_PAGEVIEW) { @@ -104,12 +109,17 @@ export function RealtimeLog({ data }: { data: any }) { } if (__type === TYPE_SESSION) { - return formatMessage(messages.visitorLog, { - country: {countryNames[country] || formatMessage(labels.unknown)}, - browser: {BROWSERS[browser]}, - os: {OS_NAMES[os] || os}, - device: {formatMessage(labels[device] || labels.unknown)}, - }); + return ( + {countryNames[country] || formatMessage(labels.unknown)}, + browser: {BROWSERS[browser]}, + os: {OS_NAMES[os] || os}, + device: {formatMessage(labels[device] || labels.unknown)}, + }} + /> + ); } }; diff --git a/src/app/(main)/websites/[websiteId]/segments/SegmentDeleteButton.tsx b/src/app/(main)/websites/[websiteId]/segments/SegmentDeleteButton.tsx index a588d11a3..6cbc24bd8 100644 --- a/src/app/(main)/websites/[websiteId]/segments/SegmentDeleteButton.tsx +++ b/src/app/(main)/websites/[websiteId]/segments/SegmentDeleteButton.tsx @@ -16,7 +16,7 @@ export function SegmentDeleteButton({ name: string; onSave?: () => void; }) { - const { formatMessage, labels } = useMessages(); + const { formatMessage, labels, FormattedMessage } = useMessages(); const { mutateAsync, isPending, error, touch } = useDeleteQuery( `/websites/${websiteId}/segments/${segmentId}`, ); @@ -36,9 +36,14 @@ export function SegmentDeleteButton({ {({ close }) => ( {name}, + }} + /> + } isLoading={isPending} error={error} onConfirm={handleConfirm.bind(null, close)} diff --git a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx index d88fff7ab..4ef120f71 100644 --- a/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx +++ b/src/app/(main)/websites/[websiteId]/sessions/[sessionId]/SessionActivity.tsx @@ -12,7 +12,7 @@ import { Dialog, } from '@umami/react-zen'; import { LoadingPanel } from '@/components/common/LoadingPanel'; -import { Bolt, Eye, FileText } from '@/components/icons'; +import { LightningSvg, Eye, FileText } from '@/components/icons'; import { useMessages, useSessionActivityQuery, useTimezone } from '@/components/hooks'; import { EventData } from '@/components/metrics/EventData'; @@ -52,7 +52,7 @@ export function SessionActivity({ {formatTimezoneDate(createdAt, 'pp')} - {eventName ? : } + {eventName ? : } {eventName ? formatMessage(labels.triggeredEvent) diff --git a/src/components/common/PageHeader.tsx b/src/components/common/PageHeader.tsx index 4d0dac5c2..4049e0c6e 100644 --- a/src/components/common/PageHeader.tsx +++ b/src/components/common/PageHeader.tsx @@ -4,6 +4,7 @@ import { Heading, Icon, Row, RowProps, Text, Column } from '@umami/react-zen'; export function PageHeader({ title, description, + label, icon, showBorder = true, children, @@ -11,6 +12,7 @@ export function PageHeader({ }: { title: string; description?: string; + label?: ReactNode; icon?: ReactNode; showBorder?: boolean; allowEdit?: boolean; @@ -26,7 +28,8 @@ export function PageHeader({ width="100%" {...props} > - + + {label} {icon && ( @@ -35,7 +38,11 @@ export function PageHeader({ )} {title && {title}} - {description && {description}} + {description && ( + + {description} + + )} {children} diff --git a/src/components/hooks/useMessages.ts b/src/components/hooks/useMessages.ts index 48b1ba60f..42bbeb01e 100644 --- a/src/components/hooks/useMessages.ts +++ b/src/components/hooks/useMessages.ts @@ -1,7 +1,22 @@ -import { useIntl } from 'react-intl'; +import { useIntl, FormattedMessage, type MessageDescriptor } from 'react-intl'; import { messages, labels } from '@/components/messages'; -export function useMessages() { +type FormatMessage = ( + descriptor: MessageDescriptor, + values?: Record, + opts?: any, +) => string | null; + +interface UseMessages { + formatMessage: FormatMessage; + messages: typeof messages; + labels: typeof labels; + getMessage: (id: string) => string; + getErrorMessage: (error: unknown) => string | undefined; + FormattedMessage: typeof FormattedMessage; +} + +export function useMessages(): UseMessages { const intl = useIntl(); const getMessage = (id: string) => { @@ -21,15 +36,12 @@ export function useMessages() { }; const formatMessage = ( - descriptor: { - id: string; - defaultMessage: string; - }, - values?: Record, + descriptor: MessageDescriptor, + values?: Record, opts?: any, ) => { return descriptor ? intl.formatMessage(descriptor, values, opts) : null; }; - return { formatMessage, messages, labels, getMessage, getErrorMessage }; + return { formatMessage, messages, labels, getMessage, getErrorMessage, FormattedMessage }; } diff --git a/src/components/icons.ts b/src/components/icons.ts index a4c180ff8..ddcda3b66 100644 --- a/src/components/icons.ts +++ b/src/components/icons.ts @@ -1,18 +1,14 @@ export * from 'lucide-react'; export { Logo as LogoSvg, - Bolt as BoltSvg, - Change as ChangeSvg, Compare as CompareSvg, Funnel as FunnelSvg, - Lightbulb as LightbulbSvg, Lightning as LightningSvg, Location as LocationSvg, Magnet as MagnetSvg, Money as MoneySvg, Network as NetworkSvg, Path as PathSvg, - Tag as TagSvg, Target as TargetSvg, AddUser as AddUserSvg, } from '@/components/svg'; diff --git a/src/components/input/SettingsButton.tsx b/src/components/input/SettingsButton.tsx index 275fab07f..23ad91d31 100644 --- a/src/components/input/SettingsButton.tsx +++ b/src/components/input/SettingsButton.tsx @@ -10,7 +10,15 @@ import { MenuSection, } from '@umami/react-zen'; import { useMessages, useLoginQuery, useNavigation, useConfig } from '@/components/hooks'; -import { LogOut, LockKeyhole, Settings, UserCircle, LifeBuoy, BookText } from '@/components/icons'; +import { + LogOut, + LockKeyhole, + Settings, + UserCircle, + LifeBuoy, + BookText, + ExternalLink, +} from '@/components/icons'; import { DOCS_URL } from '@/lib/constants'; export function SettingsButton() { @@ -54,7 +62,11 @@ export function SettingsButton() { id="/docs" icon={} label={formatMessage(labels.documentation)} - /> + > + + + + } diff --git a/src/components/input/WebsiteDateFilter.tsx b/src/components/input/WebsiteDateFilter.tsx index 475fce44a..1045de211 100644 --- a/src/components/input/WebsiteDateFilter.tsx +++ b/src/components/input/WebsiteDateFilter.tsx @@ -18,7 +18,7 @@ export function WebsiteDateFilter({ showButtons = true, allowCompare, }: WebsiteDateFilterProps) { - const { dateRange } = useDateRange(websiteId); + const { dateRange, saveDateRange } = useDateRange(websiteId); const { value, endDate } = dateRange; const { formatMessage, labels } = useMessages(); const { @@ -32,6 +32,7 @@ export function WebsiteDateFilter({ const disableForward = value === 'all' || isAfter(endDate, new Date()); const handleChange = (date: string) => { + saveDateRange(date); router.push(updateParams({ date, offset: undefined })); }; diff --git a/src/components/metrics/WeeklyTraffic.tsx b/src/components/metrics/WeeklyTraffic.tsx index ff6147392..82d1c1c1f 100644 --- a/src/components/metrics/WeeklyTraffic.tsx +++ b/src/components/metrics/WeeklyTraffic.tsx @@ -85,7 +85,7 @@ export function WeeklyTraffic({ websiteId }: { websiteId: string }) { height="16px" borderRadius="full" style={{ margin: '0 auto' }} - role="cell" + role="button" >