Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion apps/portal/src/pages/Dashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -389,7 +389,7 @@ function PartnerCard({
</div>
{partner.stripeConnected && <StatusPill status="connected" />}
</div>
<div className="op-grid-collapse" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 12 }}>
<MiniStat label="Revenue" value={money(partner.revenue)} />
<MiniStat
label="Payouts"
Expand Down
2 changes: 1 addition & 1 deletion apps/portal/src/pages/admin/NetworkCreators.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -232,7 +232,7 @@ function CreatorCard({ creator }: { creator: DirectoryRow }) {
)}
</div>
)}
<div className="op-grid-collapse" style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 12 }}>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: 8, marginBottom: 12 }}>
<Stat label="90d revenue" value={revenue > 0 ? `$${revenue.toLocaleString(undefined, { maximumFractionDigits: 0 })}` : '—'} accent={revenue > 0} />
<Stat label="90d clicks" value={creator.clicks90d > 0 ? creator.clicks90d.toLocaleString() : '—'} />
</div>
Expand Down
18 changes: 14 additions & 4 deletions apps/portal/src/pages/admin/Webhooks.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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'] }),
Expand Down Expand Up @@ -203,11 +205,19 @@ function EndpointCard({

return (
<Card>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-start', gap: 12 }}>
<div
style={{
display: 'flex',
flexDirection: isMobile ? 'column' : 'row',
justifyContent: 'space-between',
alignItems: isMobile ? 'stretch' : 'flex-start',
gap: 12,
}}
>
<div style={{ minWidth: 0, flex: 1 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 4, flexWrap: 'wrap' }}>
<WebhookIcon size={14} color={endpoint.active ? theme.accent : theme.textDim} />
<code style={{ fontSize: 13, color: theme.text, fontWeight: 500 }}>{endpoint.url}</code>
<code style={{ fontSize: 13, color: theme.text, fontWeight: 500, minWidth: 0, overflowWrap: 'anywhere', wordBreak: 'break-all' }}>{endpoint.url}</code>
<StatusPill status={endpoint.active ? 'connected' : 'pending'} />
</div>
<div style={{ fontSize: 12, color: theme.textMuted }}>
Expand Down Expand Up @@ -239,7 +249,7 @@ function EndpointCard({
))}
</div>
</div>
<div style={{ display: 'flex', gap: 6 }}>
<div style={{ display: 'flex', gap: 6, flexWrap: 'wrap', flexShrink: 0 }}>
<Button size="sm" variant="secondary" onClick={onToggle}>
{expanded ? 'Hide' : 'Deliveries'}
</Button>
Expand Down
77 changes: 74 additions & 3 deletions apps/portal/src/ui.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
<Card style={{ textAlign: 'center', color: theme.textDim, fontSize: 14 }}>{empty}</Card>
);
}
return (
<Card padded={false} style={{ overflow: 'hidden' }}>
{rows.map((row, i) => (
<div
key={i}
style={{
padding: '14px 16px',
borderBottom: i < rows.length - 1 ? `1px solid ${theme.borderSubtle}` : 'none',
display: 'flex',
flexDirection: 'column',
gap: 8,
}}
>
{row.map((cell, j) => {
const isTitle = j === 0;
return (
<div
key={j}
style={
isTitle
? { fontSize: 15, fontWeight: 600, fontVariantNumeric: 'tabular-nums' }
: {
display: 'flex',
justifyContent: 'space-between',
alignItems: 'baseline',
gap: 12,
}
}
>
{!isTitle && (
<span
style={{
flexShrink: 0,
color: theme.textMuted,
fontSize: 11,
textTransform: 'uppercase',
letterSpacing: '0.05em',
}}
>
{cols[j]?.label}
</span>
)}
<span
style={{
minWidth: 0,
textAlign: isTitle ? 'left' : 'right',
fontVariantNumeric: 'tabular-nums',
}}
>
{cell}
</span>
</div>
);
})}
</div>
))}
</Card>
);
}

return (
<Card padded={false} style={{ overflow: 'hidden' }}>
{/* 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. */}
<div style={{ overflowX: 'auto', WebkitOverflowScrolling: 'touch' }}>
<table style={{ width: '100%', borderCollapse: 'collapse', fontSize: 14, minWidth: 'max-content' }}>
<thead>
Expand Down
Loading