diff --git a/apps/portal/src/pages/Dashboard.tsx b/apps/portal/src/pages/Dashboard.tsx index c311fcd..56ebb45 100644 --- a/apps/portal/src/pages/Dashboard.tsx +++ b/apps/portal/src/pages/Dashboard.tsx @@ -389,7 +389,7 @@ function PartnerCard({ {partner.stripeConnected && } -
+
)} -
+
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({ ))}
-
+
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. */}