feat(telemetry): collections, universal server-fn coverage, beacon, client tracker

- activities indexes + client_events/client_errors/rollups/alerts collections
- global function middleware via createStart; reads skipped on success
- actor stash, denial dedup, control-flow error guards
- api route logging, auth event overrides, beacon endpoint, web vitals
- centralized admin role checks, shared skeletons, award-badges fix
- extracted deriveRunState with tests
This commit is contained in:
2026-08-25 21:33:25 -07:00
parent 15aeb64b19
commit e2863c1f6c
70 changed files with 2545 additions and 275 deletions
+2
View File
@@ -22,6 +22,7 @@ import { useDisclosure } from '@mantine/hooks'
import { useEffect } from 'react'
import toast from '@/lib/sonner'
import { logger } from '@/lib/logger'
import { captureBoundaryError } from '@/lib/telemetry/client'
import { XCircleIcon, WarningIcon } from '@phosphor-icons/react'
import { Trans, useLingui } from '@lingui/react/macro'
import Button from './button'
@@ -41,6 +42,7 @@ export function DefaultCatchBoundary({ error }: ErrorComponentProps) {
useEffect(() => {
logger.error('DefaultCatchBoundary | ', error)
captureBoundaryError(error)
if (errorMessage.toLowerCase().includes('unauthenticated')) {
toast.error(t`You've been logged out`)
+6 -1
View File
@@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import { track } from '@/lib/telemetry/client'
import { Box, Paper, Group, Text, ActionIcon } from '@mantine/core'
import { DownloadIcon, XIcon } from '@phosphor-icons/react'
import { Trans, useLingui } from '@lingui/react/macro'
@@ -26,13 +27,17 @@ export function IOSInstallPrompt() {
if ((isIOS || isAndroid) && !isInStandaloneMode && !hasBeenDismissed) {
setPlatform(isIOS ? 'ios' : 'android')
const timer = setTimeout(() => setShow(true), 3000)
const timer = setTimeout(() => {
setShow(true)
track('pwa_ios_prompt_shown')
}, 3000)
return () => clearTimeout(timer)
}
}, [])
const handleDismiss = () => {
localStorage.setItem('pwa-install-prompt-dismissed', 'true')
track('pwa_ios_prompt_dismissed')
setShow(false)
}
@@ -0,0 +1,35 @@
import { Box, Group, Paper, Skeleton } from "@mantine/core";
import type { MantineRadius } from "@mantine/core";
export interface CardSkeletonProps {
withAvatar?: boolean;
avatarSize?: number;
withBadge?: boolean;
radius?: MantineRadius;
opacity?: number;
}
export const CardSkeleton = ({
withAvatar = true,
avatarSize = 32,
withBadge = true,
radius = "md",
opacity = 1,
}: CardSkeletonProps) => {
return (
<Paper p="sm" withBorder radius={radius} style={{ opacity }}>
<Group gap="sm" align="center" wrap="nowrap">
{withAvatar && (
<Skeleton height={avatarSize} width={avatarSize} radius="sm" />
)}
<Box style={{ flex: 1 }}>
<Skeleton height={16} width="70%" mb={4} />
<Skeleton height={12} width="40%" />
</Box>
{withBadge && <Skeleton height={20} width={60} radius="xl" />}
</Group>
</Paper>
);
};
export default CardSkeleton;
+3
View File
@@ -0,0 +1,3 @@
export * from "./list-skeleton";
export * from "./card-skeleton";
export * from "./stat-tile-skeleton";
@@ -0,0 +1,76 @@
import { Box, Divider, Group, Skeleton, Stack } from "@mantine/core";
import type { MantineSpacing } from "@mantine/core";
export interface ListSkeletonProps {
rows?: number;
withSearch?: boolean;
withAvatar?: boolean;
avatarSize?: number;
withSubtitle?: boolean;
withTrailing?: boolean | "double" | "text";
titleWidth?: string | number;
padding?: MantineSpacing;
fadeStep?: number;
fadeFloor?: number;
}
export const ListSkeleton = ({
rows = 6,
withSearch = false,
withAvatar = false,
avatarSize = 40,
withSubtitle = false,
withTrailing = false,
titleWidth = "45%",
padding = "md",
fadeStep = 0.1,
fadeFloor = 0.35,
}: ListSkeletonProps) => {
return (
<Stack gap={0}>
{withSearch && (
<Box px={padding} pb="sm">
<Skeleton height={42} radius="sm" />
</Box>
)}
{Array.from({ length: rows }).map((_, index) => (
<div
key={index}
style={{ opacity: Math.max(1 - index * fadeStep, fadeFloor) }}
>
<Group
p={padding}
wrap="nowrap"
w="100%"
justify={withTrailing === "text" ? "space-between" : undefined}
>
{withAvatar && (
<Skeleton height={avatarSize} width={avatarSize} radius="sm" />
)}
{withSubtitle ? (
<Stack gap={6}>
<Skeleton height={14} width={160} radius="sm" />
<Skeleton height={10} width={100} radius="sm" />
</Stack>
) : (
<Skeleton height={16} width={titleWidth} radius="sm" />
)}
{withTrailing === "double" ? (
<Stack ml="auto" gap={6} align="flex-end">
<Skeleton height={10} width={90} radius="sm" />
<Skeleton height={10} width={70} radius="sm" />
</Stack>
) : withTrailing === "text" ? (
<Skeleton height={10} width={60} radius="sm" />
) : withTrailing ? (
<Skeleton ml="auto" height={20} width={20} radius="sm" />
) : null}
</Group>
<Divider />
</div>
))}
</Stack>
);
};
export default ListSkeleton;
@@ -0,0 +1,42 @@
import { Card, Group, Skeleton, Stack } from "@mantine/core";
export interface StatTileSkeletonProps {
withCard?: boolean;
align?: "row" | "column";
numberWidth?: number;
numberHeight?: number;
labelWidth?: number;
labelHeight?: number;
}
export const StatTileSkeleton = ({
withCard = false,
align = "column",
numberWidth = align === "row" ? 36 : 48,
numberHeight = 28,
labelWidth = align === "row" ? 110 : 80,
labelHeight = align === "row" ? 14 : 12,
}: StatTileSkeletonProps) => {
const content =
align === "row" ? (
<Group gap="xs" align="baseline">
<Skeleton height={numberHeight} width={numberWidth} radius="sm" />
<Skeleton height={labelHeight} width={labelWidth} radius="sm" />
</Group>
) : (
<Stack gap={4} align="center">
<Skeleton height={numberHeight} width={numberWidth} radius="sm" />
<Skeleton height={labelHeight} width={labelWidth} radius="sm" />
</Stack>
);
if (!withCard) return content;
return (
<Card withBorder radius="md" p="md">
{content}
</Card>
);
};
export default StatTileSkeleton;