+
0 ? `$${revenue.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : '—'} accent={revenue > 0} />
0 ? creator.clicks90d.toLocaleString() : '—'} />
diff --git a/apps/portal/src/pages/admin/Webhooks.tsx b/apps/portal/src/pages/admin/Webhooks.tsx
index c8c413c..199a7a7 100644
--- a/apps/portal/src/pages/admin/Webhooks.tsx
+++ b/apps/portal/src/pages/admin/Webhooks.tsx
@@ -3,6 +3,7 @@ import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { Plus, Webhook as WebhookIcon, Copy, Check, Trash2, Play } from 'lucide-react';
import { api } from '../../api.js';
import { theme } from '../../theme.js';
+import { useIsMobile } from '../../lib/useMediaQuery.js';
import {
Button,
Card,
@@ -172,6 +173,7 @@ function EndpointCard({
onToggle: () => void;
}) {
const qc = useQueryClient();
+ const isMobile = useIsMobile();
const toggleActive = useMutation({
mutationFn: () => api(`/webhooks/${endpoint.id}`, { method: 'PATCH', body: { active: !endpoint.active } }),
onSuccess: () => qc.invalidateQueries({ queryKey: ['webhooks'] }),
@@ -203,11 +205,19 @@ function EndpointCard({
return (
-
+
-
+
- {endpoint.url}
+ {endpoint.url}
@@ -239,7 +249,7 @@ function EndpointCard({
))}
-
+
{expanded ? 'Hide' : 'Deliveries'}
diff --git a/apps/portal/src/ui.tsx b/apps/portal/src/ui.tsx
index 7e4cab9..4bea56b 100644
--- a/apps/portal/src/ui.tsx
+++ b/apps/portal/src/ui.tsx
@@ -123,11 +123,82 @@ export function Table({
empty?: string;
}) {
const cols = columns.map((c) => (typeof c === 'string' ? { label: c, align: 'left' as const } : c));
+ const isMobile = useIsMobile();
+
+ // On phones, a multi-column table forces horizontal scrolling and hides
+ // data off-screen. Reflow each row into a stacked "label: value" card so
+ // every field is visible without scrolling sideways. The first column is
+ // treated as the row's title (rendered full-width, larger).
+ if (isMobile) {
+ if (rows.length === 0) {
+ return (
+
{empty}
+ );
+ }
+ return (
+
+ {rows.map((row, i) => (
+
+ {row.map((cell, j) => {
+ const isTitle = j === 0;
+ return (
+
+ {!isTitle && (
+
+ {cols[j]?.label}
+
+ )}
+
+ {cell}
+
+
+ );
+ })}
+
+ ))}
+
+ );
+ }
+
return (
- {/* Horizontal scroll on narrow viewports: data tables routinely have
- more columns than fit a phone, so let the table scroll inside the
- card instead of blowing out the page width. */}
+ {/* Horizontal scroll as a fallback for very wide content on tablet/
+ desktop widths; phones use the stacked card layout above. */}