Skip to content

Commit f101919

Browse files
sirjamesgrayclaude
andcommitted
feat: Simplify UsernameBadge to tier-only and add activity feed backfill
## UsernameBadge Simplification - Simplified UsernameBadge to only require `tier` prop instead of subscriptionStatus + subscriptionAmount + tier - APIs now pre-compute effective tier using getEffectiveTier() - UsernameBadge auto-fetches tier from /api/users/full-profile when not provided - Updated SubscriptionTierBadge to use tier directly (deprecated old props) - Updated all 29 call sites across the codebase Tier values: - 'inactive' = no subscription ($0/mo) - shows ban icon - 'tier1' = Supporter ($10/mo) - 1 star - 'tier2' = Advocate ($20/mo) - 2 stars - 'tier3' = Champion ($30+/mo) - 3 stars with glow ## Activity Feed Backfill - When spam filters remove recent content, API now expands time window automatically (30 → 60 → 90 → 180 days) to find legitimate content - Users see older but real activity instead of empty "No recent activity" - Added helpful empty state message with "Adjust Filters" button when spam filters cause no results ## Filter Toggles - Added "show author" filter toggle to WhatLinksHere card - Added "show author" filter toggle to Related Pages by Others card - Both use animated filter row expansion with localStorage persistence - Added `subheader` prop to PageLinksCard for filter rows 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent 7764351 commit f101919

29 files changed

Lines changed: 568 additions & 357 deletions

app/[id]/versions/page.tsx

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,7 @@ interface PageVersion {
2525
diff?: { added: number; removed: number; hasChanges: boolean };
2626
versionId?: string;
2727
username?: string;
28-
subscriptionTier?: string;
29-
subscriptionStatus?: string;
30-
subscriptionAmount?: number;
28+
tier?: string; // Pre-computed effective tier from API
3129
hasActiveSubscription?: boolean;
3230
title?: string;
3331
}
@@ -246,20 +244,16 @@ export default function PageVersionsPage({ params }: PageVersionsPageProps) {
246244
isCurrentVersion: index === 0, // First item is most recent
247245
hasPreviousVersion: index < pageVersions.length - 1, // Has previous version if not the last item
248246
// Remove subscription data since we're not showing usernames
249-
subscriptionTier: null,
250-
subscriptionStatus: null,
251-
hasActiveSubscription: false,
252-
subscriptionAmount: null
247+
tier: null,
248+
hasActiveSubscription: false
253249
};
254250
}));
255251

256252
console.log('📊 [VERSIONS PAGE] Activity items created:', {
257253
count: activityItems.length,
258254
firstItem: activityItems[0] ? {
259255
username: activityItems[0].username,
260-
subscriptionTier: activityItems[0].subscriptionTier,
261-
subscriptionStatus: activityItems[0].subscriptionStatus,
262-
subscriptionAmount: activityItems[0].subscriptionAmount,
256+
tier: activityItems[0].tier,
263257
hasActiveSubscription: activityItems[0].hasActiveSubscription
264258
} : null
265259
});

app/admin/design-system/sections/PillLinkSection.tsx

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -49,8 +49,6 @@ export function PillLinkSection({ id }: { id: string }) {
4949
userId="user1"
5050
username="alex"
5151
tier={null}
52-
subscriptionStatus={null}
53-
subscriptionAmount={null}
5452
variant="pill"
5553
pillVariant="primary"
5654
/>
@@ -60,8 +58,6 @@ export function PillLinkSection({ id }: { id: string }) {
6058
userId="user2"
6159
username="sarah"
6260
tier="tier3"
63-
subscriptionStatus="active"
64-
subscriptionAmount={35}
6561
variant="pill"
6662
pillVariant="primary"
6763
/>
@@ -77,8 +73,6 @@ export function PillLinkSection({ id }: { id: string }) {
7773
userId="user3"
7874
username="jamie"
7975
tier="tier3"
80-
subscriptionStatus="active"
81-
subscriptionAmount={30}
8276
variant="pill"
8377
pillVariant="outline"
8478
/>

app/admin/design-system/sections/UsernameBadgeSection.tsx

Lines changed: 28 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -10,40 +10,32 @@ export function UsernameBadgeSection({ id }: { id: string }) {
1010
id={id}
1111
title="UsernameBadge"
1212
path="app/components/ui/UsernameBadge.tsx"
13-
description="Displays a user's username with subscription tier badge. Supports link and pill variants with different styling options."
13+
description="Displays a user's username with subscription tier badge. Only requires userId, username, and tier - the tier is pre-computed by APIs. Supports link and pill variants."
1414
>
1515
<StateDemo label="Link Variant (default)">
1616
<div className="flex flex-wrap gap-4 items-center">
1717
<UsernameBadge
1818
userId="user1"
1919
username="alex"
20-
tier={null}
21-
subscriptionStatus={null}
22-
subscriptionAmount={null}
20+
tier="inactive"
2321
variant="link"
2422
/>
2523
<UsernameBadge
2624
userId="user2"
2725
username="sarah"
2826
tier="tier1"
29-
subscriptionStatus="active"
30-
subscriptionAmount={10}
3127
variant="link"
3228
/>
3329
<UsernameBadge
3430
userId="user3"
3531
username="jamie"
3632
tier="tier2"
37-
subscriptionStatus="active"
38-
subscriptionAmount={20}
3933
variant="link"
4034
/>
4135
<UsernameBadge
4236
userId="user4"
4337
username="taylor"
4438
tier="tier3"
45-
subscriptionStatus="active"
46-
subscriptionAmount={35}
4739
variant="link"
4840
/>
4941
</div>
@@ -54,36 +46,28 @@ export function UsernameBadgeSection({ id }: { id: string }) {
5446
<UsernameBadge
5547
userId="user1"
5648
username="alex"
57-
tier={null}
58-
subscriptionStatus={null}
59-
subscriptionAmount={null}
49+
tier="inactive"
6050
variant="pill"
6151
pillVariant="primary"
6252
/>
6353
<UsernameBadge
6454
userId="user2"
6555
username="sarah"
6656
tier="tier1"
67-
subscriptionStatus="active"
68-
subscriptionAmount={10}
6957
variant="pill"
7058
pillVariant="primary"
7159
/>
7260
<UsernameBadge
7361
userId="user3"
7462
username="jamie"
7563
tier="tier2"
76-
subscriptionStatus="active"
77-
subscriptionAmount={20}
7864
variant="pill"
7965
pillVariant="primary"
8066
/>
8167
<UsernameBadge
8268
userId="user4"
8369
username="taylor"
8470
tier="tier3"
85-
subscriptionStatus="active"
86-
subscriptionAmount={35}
8771
variant="pill"
8872
pillVariant="primary"
8973
/>
@@ -96,18 +80,14 @@ export function UsernameBadgeSection({ id }: { id: string }) {
9680
<UsernameBadge
9781
userId="user1"
9882
username="alex"
99-
tier={null}
100-
subscriptionStatus={null}
101-
subscriptionAmount={null}
83+
tier="inactive"
10284
variant="pill"
10385
pillVariant="outline"
10486
/>
10587
<UsernameBadge
10688
userId="user2"
10789
username="sarah"
10890
tier="tier2"
109-
subscriptionStatus="active"
110-
subscriptionAmount={20}
11191
variant="pill"
11292
pillVariant="outline"
11393
/>
@@ -120,17 +100,13 @@ export function UsernameBadgeSection({ id }: { id: string }) {
120100
userId="user1"
121101
username="alex"
122102
tier="tier3"
123-
subscriptionStatus="active"
124-
subscriptionAmount={35}
125103
variant="link"
126104
showBadge={false}
127105
/>
128106
<UsernameBadge
129107
userId="user2"
130108
username="sarah"
131109
tier="tier3"
132-
subscriptionStatus="active"
133-
subscriptionAmount={35}
134110
variant="pill"
135111
pillVariant="primary"
136112
showBadge={false}
@@ -146,8 +122,6 @@ export function UsernameBadgeSection({ id }: { id: string }) {
146122
userId="user1"
147123
username="alex"
148124
tier="tier2"
149-
subscriptionStatus="active"
150-
subscriptionAmount={20}
151125
size="sm"
152126
variant="link"
153127
/>
@@ -158,8 +132,6 @@ export function UsernameBadgeSection({ id }: { id: string }) {
158132
userId="user2"
159133
username="sarah"
160134
tier="tier2"
161-
subscriptionStatus="active"
162-
subscriptionAmount={20}
163135
size="md"
164136
variant="link"
165137
/>
@@ -170,8 +142,6 @@ export function UsernameBadgeSection({ id }: { id: string }) {
170142
userId="user3"
171143
username="jamie"
172144
tier="tier2"
173-
subscriptionStatus="active"
174-
subscriptionAmount={20}
175145
size="lg"
176146
variant="link"
177147
/>
@@ -182,56 +152,66 @@ export function UsernameBadgeSection({ id }: { id: string }) {
182152
<StateDemo label="Subscription Tiers">
183153
<div className="wewrite-card p-4 bg-muted/30">
184154
<p className="text-sm text-muted-foreground mb-3">
185-
The badge shows stars based on subscription tier. Inactive users show a crossed-out circle.
155+
The badge shows stars based on subscription tier. The tier is pre-computed by APIs using getEffectiveTier().
186156
</p>
187157
<div className="flex flex-wrap gap-4 items-center">
188158
<div className="flex flex-col items-center gap-1">
189159
<UsernameBadge
190160
userId="inactive"
191161
username="inactive"
192-
tier={null}
193-
subscriptionStatus={null}
194-
subscriptionAmount={null}
162+
tier="inactive"
195163
variant="link"
196164
/>
197-
<span className="text-xs text-muted-foreground">No sub</span>
165+
<span className="text-xs text-muted-foreground">inactive</span>
198166
</div>
199167
<div className="flex flex-col items-center gap-1">
200168
<UsernameBadge
201169
userId="tier1"
202170
username="tier1"
203171
tier="tier1"
204-
subscriptionStatus="active"
205-
subscriptionAmount={10}
206172
variant="link"
207173
/>
208-
<span className="text-xs text-muted-foreground">$10/mo (1 star)</span>
174+
<span className="text-xs text-muted-foreground">tier1 ($10/mo)</span>
209175
</div>
210176
<div className="flex flex-col items-center gap-1">
211177
<UsernameBadge
212178
userId="tier2"
213179
username="tier2"
214180
tier="tier2"
215-
subscriptionStatus="active"
216-
subscriptionAmount={20}
217181
variant="link"
218182
/>
219-
<span className="text-xs text-muted-foreground">$20/mo (2 stars)</span>
183+
<span className="text-xs text-muted-foreground">tier2 ($20/mo)</span>
220184
</div>
221185
<div className="flex flex-col items-center gap-1">
222186
<UsernameBadge
223187
userId="tier3"
224188
username="tier3"
225189
tier="tier3"
226-
subscriptionStatus="active"
227-
subscriptionAmount={30}
228190
variant="link"
229191
/>
230-
<span className="text-xs text-muted-foreground">$30+/mo (3 stars)</span>
192+
<span className="text-xs text-muted-foreground">tier3 ($30+/mo)</span>
231193
</div>
232194
</div>
233195
</div>
234196
</StateDemo>
197+
198+
<StateDemo label="API Integration">
199+
<div className="wewrite-card p-4 bg-muted/30">
200+
<p className="text-sm text-muted-foreground mb-2">
201+
<strong>Simplified Props:</strong> Only <code className="bg-muted px-1 rounded">tier</code> is needed now. APIs pre-compute the effective tier.
202+
</p>
203+
<p className="text-sm text-muted-foreground mb-2">
204+
<strong>Auto-fetch:</strong> If tier is not provided, the component fetches it from <code className="bg-muted px-1 rounded">/api/users/full-profile</code>.
205+
</p>
206+
<pre className="text-xs bg-muted p-2 rounded mt-2 overflow-x-auto">
207+
{`// Minimal usage (tier auto-fetched)
208+
<UsernameBadge userId="abc" username="jamie" />
209+
210+
// With pre-fetched tier
211+
<UsernameBadge userId="abc" username="jamie" tier="tier2" />`}
212+
</pre>
213+
</div>
214+
</StateDemo>
235215
</ComponentShowcase>
236216
);
237217
}

app/api/admin/users/route.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { checkAdminPermissions, isUserRecordAdmin } from '../../admin-auth-helpe
88
import { getFirebaseAdmin } from '../../../firebase/admin';
99
import { getCollectionName, USD_COLLECTIONS } from '../../../utils/environmentConfig';
1010
import { withAdminContext } from '../../../utils/adminRequestContext';
11+
import { getEffectiveTier } from '../../../utils/subscriptionTiers';
1112

1213
interface UserData {
1314
uid: string;
@@ -26,6 +27,7 @@ interface UserData {
2627
pwaInstalled?: boolean;
2728
pwaVerified?: boolean; // True if user has actually used PWA in standalone mode (not just claimed to install)
2829
notificationSparkline?: number[];
30+
tier?: string; // Pre-computed effective tier for badge display
2931
financial?: {
3032
hasSubscription: boolean;
3133
subscriptionAmount?: number | null;
@@ -206,6 +208,13 @@ export async function GET(request: NextRequest) {
206208
subscriptionCancelReason = subscriptionData.cancelReason || null;
207209
}
208210

211+
// Pre-compute effective tier using centralized logic
212+
user.tier = getEffectiveTier(
213+
subscriptionAmount,
214+
subscriptionData?.tier ?? null,
215+
subscriptionStatus
216+
);
217+
209218
// Get USD balance data for allocation info
210219
const usdBalanceData = usdBalancesMap.get(uid);
211220

app/api/follows/suggestions/route.ts

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
import { NextRequest, NextResponse } from 'next/server';
22
import { getUserIdFromRequest, createApiResponse, createErrorResponse } from '../../auth-helper';
33
import { initAdmin } from '../../../firebase/admin';
4-
import { getCollectionName } from '../../../utils/environmentConfig';
4+
import { getCollectionName, getSubCollectionPath, PAYMENT_COLLECTIONS } from '../../../utils/environmentConfig';
5+
import { getEffectiveTier } from '../../../utils/subscriptionTiers';
56

67
/**
78
* Follow Suggestions API Route
@@ -111,6 +112,27 @@ export async function GET(request: NextRequest) {
111112
const userData = userDoc.data();
112113
const suggestionInfo = suggestionsMap.get(userId)!;
113114

115+
// Fetch subscription data for this user
116+
let subscriptionData = null;
117+
try {
118+
const { parentPath, subCollectionName } = getSubCollectionPath(
119+
PAYMENT_COLLECTIONS.USERS,
120+
userId,
121+
PAYMENT_COLLECTIONS.SUBSCRIPTIONS
122+
);
123+
const subDoc = await db.doc(parentPath).collection(subCollectionName).doc('current').get();
124+
subscriptionData = subDoc.exists ? subDoc.data() : null;
125+
} catch (error) {
126+
console.warn(`Error fetching subscription for user ${userId}:`, error);
127+
}
128+
129+
// Pre-compute effective tier using centralized logic
130+
const effectiveTier = getEffectiveTier(
131+
subscriptionData?.amount ?? null,
132+
subscriptionData?.tier ?? null,
133+
subscriptionData?.status ?? null
134+
);
135+
114136
// Parse bio if it's EditorContent format
115137
let bioText = '';
116138
if (userData?.bio) {
@@ -133,9 +155,7 @@ export async function GET(request: NextRequest) {
133155
username: userData?.username || `user_${userDoc.id.slice(0, 8)}`,
134156
photoURL: userData?.photoURL,
135157
bio: bioText,
136-
tier: userData?.tier,
137-
subscriptionStatus: userData?.subscriptionStatus,
138-
subscriptionAmount: userData?.subscriptionAmount,
158+
tier: effectiveTier,
139159
followerCount: userData?.followerCount || 0,
140160
source: suggestionInfo.source,
141161
score: suggestionInfo.score

0 commit comments

Comments
 (0)