changed portrait shape
Deploy / deploy-dev (push) Successful in 2m23s
Deploy / deploy-prod (push) Skipped

This commit is contained in:
blaisadmin
2026-07-21 22:14:27 -04:00
parent 737a84a8b8
commit a6eac3009d
4 changed files with 49 additions and 21 deletions
+6
View File
@@ -2992,6 +2992,12 @@ app.get('/api/admin/summary', requireAuth, requireSessionAuth, requireAdmin, asy
rescueBirds: Number(summary?.rescue_birds ?? 0), rescueBirds: Number(summary?.rescue_birds ?? 0),
pendingRescues: Number(summary?.pending_rescues ?? 0), pendingRescues: Number(summary?.pending_rescues ?? 0),
dailyUsers: Number(summary?.daily_users ?? 0), dailyUsers: Number(summary?.daily_users ?? 0),
subscriptionsByPlan: {
household_basic: Number(summary?.household_basic_subscriptions ?? 0),
household_plus: Number(summary?.household_plus_subscriptions ?? 0),
household_macaw: Number(summary?.household_macaw_subscriptions ?? 0),
household_hyacinth_macaw: Number(summary?.household_hyacinth_macaw_subscriptions ?? 0),
},
}, },
}); });
} catch (error) { } catch (error) {
@@ -531,6 +531,10 @@ test('getPlatformAdminSummary counts memorialized birds separately', async () =>
rescue_birds: 5, rescue_birds: 5,
pending_rescues: 1, pending_rescues: 1,
daily_users: 2, daily_users: 2,
household_basic_subscriptions: 2,
household_plus_subscriptions: 1,
household_macaw_subscriptions: 0,
household_hyacinth_macaw_subscriptions: 1,
}, },
], ],
}); });
@@ -543,4 +547,7 @@ test('getPlatformAdminSummary counts memorialized birds separately', async () =>
assert.match(calls[0].text, /memorialized_at IS NOT NULL/); assert.match(calls[0].text, /memorialized_at IS NOT NULL/);
assert.match(calls[0].text, /rescue_birds/); assert.match(calls[0].text, /rescue_birds/);
assert.match(calls[0].text, /workspaces\.workspace_type = 'rescue'/); assert.match(calls[0].text, /workspaces\.workspace_type = 'rescue'/);
assert.equal(summary?.household_basic_subscriptions, 2);
assert.match(calls[0].text, /billing_plan = 'household_basic'/);
assert.match(calls[0].text, /subscription_status IN \('active', 'trialing'\)/);
}); });
@@ -605,6 +605,10 @@ export const getPlatformAdminSummary = async () => {
rescue_birds: number; rescue_birds: number;
pending_rescues: number; pending_rescues: number;
daily_users: number; daily_users: number;
household_basic_subscriptions: number;
household_plus_subscriptions: number;
household_macaw_subscriptions: number;
household_hyacinth_macaw_subscriptions: number;
}>( }>(
`SELECT `SELECT
(SELECT COUNT(*)::int FROM birds) AS total_birds, (SELECT COUNT(*)::int FROM birds) AS total_birds,
@@ -614,7 +618,11 @@ export const getPlatformAdminSummary = async () => {
(SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue') AS rescue_workspaces, (SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue') AS rescue_workspaces,
(SELECT COUNT(*)::int FROM birds INNER JOIN workspaces ON workspaces.id = birds.workspace_id WHERE workspaces.workspace_type = 'rescue') AS rescue_birds, (SELECT COUNT(*)::int FROM birds INNER JOIN workspaces ON workspaces.id = birds.workspace_id WHERE workspaces.workspace_type = 'rescue') AS rescue_birds,
(SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue' AND rescue_verification_status = 'pending') AS pending_rescues, (SELECT COUNT(*)::int FROM workspaces WHERE workspace_type = 'rescue' AND rescue_verification_status = 'pending') AS pending_rescues,
(SELECT COUNT(DISTINCT user_id)::int FROM auth_sessions WHERE created_at >= CURRENT_DATE) AS daily_users`, (SELECT COUNT(DISTINCT user_id)::int FROM auth_sessions WHERE created_at >= CURRENT_DATE) AS daily_users,
(SELECT COUNT(*)::int FROM workspaces WHERE billing_plan = 'household_basic' AND subscription_status IN ('active', 'trialing')) AS household_basic_subscriptions,
(SELECT COUNT(*)::int FROM workspaces WHERE billing_plan = 'household_plus' AND subscription_status IN ('active', 'trialing')) AS household_plus_subscriptions,
(SELECT COUNT(*)::int FROM workspaces WHERE billing_plan = 'household_macaw' AND subscription_status IN ('active', 'trialing')) AS household_macaw_subscriptions,
(SELECT COUNT(*)::int FROM workspaces WHERE billing_plan = 'household_hyacinth_macaw' AND subscription_status IN ('active', 'trialing')) AS household_hyacinth_macaw_subscriptions`,
); );
return result.rows[0]; return result.rows[0];
+27 -20
View File
@@ -184,6 +184,7 @@ type AdminSummary = {
rescueBirds: number; rescueBirds: number;
pendingRescues: number; pendingRescues: number;
dailyUsers: number; dailyUsers: number;
subscriptionsByPlan: Record<HouseholdBillingPlan, number>;
}; };
type AdminRescueWorkspace = { type AdminRescueWorkspace = {
@@ -1540,7 +1541,7 @@ const OVERVIEW_WINDOW_DAYS = 30;
const OVERVIEW_HISTORY_DAYS = 425; const OVERVIEW_HISTORY_DAYS = 425;
const OVERVIEW_WIDTH = 520; const OVERVIEW_WIDTH = 520;
const OVERVIEW_HEIGHT = 220; const OVERVIEW_HEIGHT = 220;
const OVERVIEW_PADDING = { top: 20, right: 18, bottom: 36, left: 52 }; const OVERVIEW_PADDING = { top: 48, right: 18, bottom: 36, left: 52 };
const PHOTO_MAX_BYTES = 900_000; const PHOTO_MAX_BYTES = 900_000;
const PHOTO_EXPORT_SIZES = [720, 600, 480]; const PHOTO_EXPORT_SIZES = [720, 600, 480];
const PHOTO_EXPORT_QUALITIES = [0.9, 0.82, 0.74, 0.66]; const PHOTO_EXPORT_QUALITIES = [0.9, 0.82, 0.74, 0.66];
@@ -6341,42 +6342,42 @@ function App() {
))} ))}
{points.length > 0 ? (() => { {points.length > 0 ? (() => {
const latestPoint = points[points.length - 1]; const latestPoint = points[points.length - 1];
const portraitSize = 24; const pointerRadius = 13;
const portraitGap = 8; const pointerCenterY = latestPoint.y - 21;
const portraitX = latestPoint.x + portraitGap + portraitSize <= OVERVIEW_WIDTH - 2 const pointerTop = pointerCenterY - pointerRadius;
? latestPoint.x + portraitGap const pointerPath = [
: latestPoint.x - portraitGap - portraitSize; `M ${latestPoint.x} ${latestPoint.y}`,
const portraitY = Math.min( `C ${latestPoint.x - 2.5} ${latestPoint.y - 5}, ${latestPoint.x - pointerRadius} ${latestPoint.y - 13}, ${latestPoint.x - pointerRadius} ${pointerCenterY}`,
Math.max(latestPoint.y - portraitSize / 2, 2), `A ${pointerRadius} ${pointerRadius} 0 1 1 ${latestPoint.x + pointerRadius} ${pointerCenterY}`,
OVERVIEW_HEIGHT - OVERVIEW_PADDING.bottom - portraitSize - 2, `C ${latestPoint.x + pointerRadius} ${latestPoint.y - 13}, ${latestPoint.x + 2.5} ${latestPoint.y - 5}, ${latestPoint.x} ${latestPoint.y}`,
); 'Z',
].join(' ');
const clipPathId = `overview-bird-portrait-${bird.id}`; const clipPathId = `overview-bird-portrait-${bird.id}`;
return ( return (
<g className="overview-latest-portrait"> <g className="overview-latest-portrait">
<defs> <defs>
<clipPath id={clipPathId}> <clipPath id={clipPathId}>
<circle cx={portraitX + portraitSize / 2} cy={portraitY + portraitSize / 2} r={portraitSize / 2} /> <path d={pointerPath} />
</clipPath> </clipPath>
</defs> </defs>
<image <image
href={bird.photoDataUrl || defaultBirdPhoto} href={bird.photoDataUrl || defaultBirdPhoto}
x={portraitX} x={latestPoint.x - pointerRadius}
y={portraitY} y={pointerTop}
width={portraitSize} width={pointerRadius * 2}
height={portraitSize} height={latestPoint.y - pointerTop}
preserveAspectRatio="xMidYMid slice" preserveAspectRatio="xMidYMid slice"
clipPath={`url(#${clipPathId})`} clipPath={`url(#${clipPathId})`}
> >
<title>{`${bird.name}'s latest weight: ${latestPoint.label}`}</title> <title>{`${bird.name}'s latest weight: ${latestPoint.label}`}</title>
</image> </image>
<circle <path
cx={portraitX + portraitSize / 2} d={pointerPath}
cy={portraitY + portraitSize / 2}
r={portraitSize / 2}
fill="none" fill="none"
stroke={bird.chartColor} stroke={bird.chartColor}
strokeWidth="2.5" strokeWidth="2"
strokeLinejoin="round"
aria-hidden="true" aria-hidden="true"
/> />
</g> </g>
@@ -6589,6 +6590,12 @@ function App() {
<span>Pending rescues</span> <span>Pending rescues</span>
<strong>{adminSummary?.pendingRescues ?? '-'}</strong> <strong>{adminSummary?.pendingRescues ?? '-'}</strong>
</article> </article>
{(['household_basic', 'household_plus', 'household_macaw', 'household_hyacinth_macaw'] as const).map((billingPlan) => (
<article className="summary-card" key={billingPlan}>
<span>{formatBillingPlanName(billingPlan)} subscriptions</span>
<strong>{adminSummary?.subscriptionsByPlan?.[billingPlan] ?? '-'}</strong>
</article>
))}
</div> </div>
</article> </article>