Skip to content

Commit 73071ab

Browse files
tsahimatsliahclaude
andcommitted
feat(referral): gate the Plus reward behind referral_plus_reward
Review feedback (capJavert): the page promised and displayed a free Plus month with no backend granting it and a reward period inferred from referral timestamps rather than entitlement data. Put the promise back behind `referral_plus_reward` (default false): with it off the page reads "Grow the community" and promises nothing; flip it on only once fulfillment / authoritative entitlement data lands. Mark the two referral notification types as placeholders in code — no backend emits them yet, so they are not complete. Co-Authored-By: Claude Opus 4.8 <[email protected]>
1 parent e0aaf9e commit 73071ab

5 files changed

Lines changed: 110 additions & 26 deletions

File tree

packages/shared/src/components/notifications/utils.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,10 @@ export enum NotificationType {
9999
WarmIntro = 'warm_intro',
100100
ExperienceCompanyEnriched = 'experience_company_enriched',
101101
LiveRoomStarted = 'live_room_started',
102+
// Placeholder types for the invite-friends reward. No backend emits either
103+
// one yet, so nothing produces these notifications in production — they exist
104+
// for the client to render and for Storybook to demo. Not complete until the
105+
// backend can emit them (and the Plus reward is actually fulfilled).
102106
ReferralFriendJoined = 'referral_friend_joined',
103107
ReferralPlusUnlocked = 'referral_plus_unlocked',
104108
}

packages/shared/src/lib/featureManagement.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,17 @@ export const featureShortcutsHub = new Feature('shortcuts_hub_v2', false);
195195

196196
export const featureGiveback = new Feature('giveback', isDevelopment);
197197

198+
// Gates the "invite 3 friends, get 1 month of Plus" promo on the invite
199+
// settings page. MUST stay `false`: no backend grants the free month on the
200+
// third referral yet and the displayed reward period is inferred from referral
201+
// timestamps rather than entitlement data, so the promise cannot ship on. Flip
202+
// it on only once fulfillment / authoritative entitlement data lands. With it
203+
// off the page reads "Grow the community" and promises nothing.
204+
export const featureReferralPlusReward = new Feature(
205+
'referral_plus_reward',
206+
false,
207+
);
208+
198209
export const featureGivebackSuggestCause = new Feature(
199210
'giveback_suggest_cause',
200211
false,

packages/storybook/stories/features/referral/InviteFriends.stories.tsx

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -78,13 +78,20 @@ interface InvitePageProps {
7878
joinedCount?: number;
7979
isPlus?: boolean;
8080
showGiveback?: boolean;
81+
// Mirrors the `referral_plus_reward` flag on the real page.
82+
showReward?: boolean;
8183
}
8284

8385
// Mirrors packages/webapp/pages/settings/invite.tsx section for section.
84-
const rewardDescription = (isUnlocked: boolean): string =>
85-
isUnlocked
86+
const rewardDescription = (showReward: boolean, isUnlocked: boolean): string => {
87+
if (!showReward) {
88+
return "Share daily.dev with developers you know. When they join through your link, they'll show up in your referrals below.";
89+
}
90+
91+
return isUnlocked
8692
? 'Keep inviting. Every developer you bring makes the feed sharper.'
8793
: `When ${INVITE_GOAL} developers join daily.dev through your link, your free month of Plus starts.`;
94+
};
8895

8996
// Same derivation as the page: the free month runs from the join date of the
9097
// INVITE_GOALth developer.
@@ -106,6 +113,7 @@ const InvitePage = ({
106113
joinedCount = 0,
107114
isPlus = false,
108115
showGiveback = true,
116+
showReward = true,
109117
}: InvitePageProps): ReactElement => {
110118
const referredUsers = mockReferredUsers().slice(0, joinedCount);
111119
const isUnlocked = joinedCount >= INVITE_GOAL;
@@ -114,10 +122,14 @@ const InvitePage = ({
114122
<SettingsCard>
115123
<Section
116124
isFirst
117-
title={`Invite ${INVITE_GOAL} friends, get 1 month of Plus`}
118-
description={rewardDescription(isUnlocked)}
125+
title={
126+
showReward
127+
? `Invite ${INVITE_GOAL} friends, get 1 month of Plus`
128+
: 'Grow the community'
129+
}
130+
description={rewardDescription(showReward, isUnlocked)}
119131
>
120-
{isPlus && !isUnlocked && (
132+
{showReward && isPlus && !isUnlocked && (
121133
<Typography
122134
className="mt-1"
123135
type={TypographyType.Footnote}
@@ -126,12 +138,14 @@ const InvitePage = ({
126138
Already on Plus? The free month is added to your subscription.
127139
</Typography>
128140
)}
129-
<InviteRewardProgress
130-
className="mt-4"
131-
joinedCount={joinedCount}
132-
referredUsers={referredUsers}
133-
rewardPeriod={rewardPeriodFor(referredUsers)}
134-
/>
141+
{showReward && (
142+
<InviteRewardProgress
143+
className="mt-4"
144+
joinedCount={joinedCount}
145+
referredUsers={referredUsers}
146+
rewardPeriod={rewardPeriodFor(referredUsers)}
147+
/>
148+
)}
135149
</Section>
136150
<Section title="Your invitation link">
137151
<InviteLinkInput
@@ -284,6 +298,20 @@ export const ExistingPlusMember: Story = {
284298
},
285299
};
286300

301+
export const RewardFlagOff: Story = {
302+
name: 'Reward flag off (default)',
303+
args: { joinedCount: 1, showReward: false },
304+
decorators: [withInvite()],
305+
parameters: {
306+
docs: {
307+
description: {
308+
story:
309+
'What ships until `referral_plus_reward` is switched on: no reward promise and no tracker, just the referral tooling. The flag defaults off because no backend grants the free month yet.',
310+
},
311+
},
312+
},
313+
};
314+
287315
export const GivebackDisabled: Story = {
288316
name: 'Giveback flag off',
289317
args: { joinedCount: 1, showGiveback: false },

packages/webapp/__tests__/AccountInvitePage.tsx

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,10 @@ import {
1919
REFERRED_USERS_QUERY,
2020
} from '@dailydotdev/shared/src/graphql/users';
2121
import type { Feature } from '@dailydotdev/shared/src/lib/featureManagement';
22-
import { featureGiveback } from '@dailydotdev/shared/src/lib/featureManagement';
22+
import {
23+
featureGiveback,
24+
featureReferralPlusReward,
25+
} from '@dailydotdev/shared/src/lib/featureManagement';
2326
import { webappUrl } from '@dailydotdev/shared/src/lib/constants';
2427
import { LogEvent, TargetId } from '@dailydotdev/shared/src/lib/log';
2528
import { getLogContextStatic } from '@dailydotdev/shared/src/contexts/LogContext';
@@ -145,7 +148,20 @@ const renderComponent = (
145148
);
146149
};
147150

151+
it('should not promise the Plus reward while the flag is off', async () => {
152+
mockedFeatures.set(featureReferralPlusReward, false);
153+
mockReferralCampaign(2);
154+
renderComponent();
155+
156+
expect(await screen.findByText('Grow the community')).toBeInTheDocument();
157+
expect(
158+
screen.queryByText('Invite 3 friends, get 1 month of Plus'),
159+
).not.toBeInTheDocument();
160+
expect(screen.queryByText('2 of 3 friends joined')).not.toBeInTheDocument();
161+
});
162+
148163
it('should render the 3-invites promo with the progress at zero', async () => {
164+
mockedFeatures.set(featureReferralPlusReward, true);
149165
mockReferralCampaign(0);
150166
renderComponent();
151167

@@ -157,13 +173,15 @@ it('should render the 3-invites promo with the progress at zero', async () => {
157173
});
158174

159175
it('should reflect partial progress from the referral campaign', async () => {
176+
mockedFeatures.set(featureReferralPlusReward, true);
160177
mockReferralCampaign(2);
161178
renderComponent();
162179

163180
expect(await screen.findByText('2 of 3 friends joined')).toBeInTheDocument();
164181
});
165182

166183
it('should show the unlocked state once three friends joined', async () => {
184+
mockedFeatures.set(featureReferralPlusReward, true);
167185
mockReferralCampaign(3);
168186
renderComponent();
169187

@@ -174,6 +192,7 @@ it('should show the unlocked state once three friends joined', async () => {
174192
});
175193

176194
it('should keep the unlocked copy count-agnostic beyond the goal', async () => {
195+
mockedFeatures.set(featureReferralPlusReward, true);
177196
mockReferralCampaign(12);
178197
renderComponent();
179198

@@ -185,6 +204,7 @@ it('should keep the unlocked copy count-agnostic beyond the goal', async () => {
185204
});
186205

187206
it('should run the free month from the join date of the third referral', async () => {
207+
mockedFeatures.set(featureReferralPlusReward, true);
188208
mockReferralCampaign(3);
189209
renderComponent(loggedUser, [
190210
// Deliberately out of order — the period is derived from the third
@@ -199,6 +219,7 @@ it('should run the free month from the join date of the third referral', async (
199219
});
200220

201221
it('should fill the progress slots with the referred users avatars', async () => {
222+
mockedFeatures.set(featureReferralPlusReward, true);
202223
mockReferralCampaign(2);
203224
renderComponent(loggedUser, [
204225
referredUser('ref-1', 'idakern'),

packages/webapp/pages/settings/invite.tsx

Lines changed: 34 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ import {
4545
import { useLogContext } from '@dailydotdev/shared/src/contexts/LogContext';
4646
import { usePlusSubscription } from '@dailydotdev/shared/src/hooks/usePlusSubscription';
4747
import { useConditionalFeature } from '@dailydotdev/shared/src/hooks/useConditionalFeature';
48-
import { featureGiveback } from '@dailydotdev/shared/src/lib/featureManagement';
48+
import {
49+
featureGiveback,
50+
featureReferralPlusReward,
51+
} from '@dailydotdev/shared/src/lib/featureManagement';
4952
import {
5053
INVITE_GOAL,
5154
InviteRewardProgress,
@@ -76,6 +79,10 @@ const AccountInvitePage = (): ReactElement => {
7679
feature: featureGiveback,
7780
shouldEvaluate: !!user,
7881
});
82+
const { value: isRewardEnabled } = useConditionalFeature({
83+
feature: featureReferralPlusReward,
84+
shouldEvaluate: !!user,
85+
});
7986
const usersResult = useInfiniteQuery<ReferredUsersData>({
8087
queryKey: referredKey,
8188
queryFn: ({ pageParam }) =>
@@ -147,11 +154,18 @@ const AccountInvitePage = (): ReactElement => {
147154
});
148155
};
149156

150-
// The card below already announces the reward, so the unlocked copy only has
151-
// to give a reason to keep going.
152-
const rewardDescription = isRewardUnlocked
153-
? 'Keep inviting. Every developer you bring makes the feed sharper.'
154-
: `When ${INVITE_GOAL} developers join daily.dev through your link, your free month of Plus starts.`;
157+
const getRewardDescription = () => {
158+
if (!isRewardEnabled) {
159+
return "Share daily.dev with developers you know. When they join through your link, they'll show up in your referrals below.";
160+
}
161+
162+
// The card below already announces the reward, so the unlocked copy only
163+
// has to give a reason to keep going.
164+
return isRewardUnlocked
165+
? 'Keep inviting. Every developer you bring makes the feed sharper.'
166+
: `When ${INVITE_GOAL} developers join daily.dev through your link, your free month of Plus starts.`;
167+
};
168+
const rewardDescription = getRewardDescription();
155169

156170
const onGivebackClick = () => {
157171
logEvent({
@@ -164,10 +178,14 @@ const AccountInvitePage = (): ReactElement => {
164178
<AccountPageContainer title="Invite friends">
165179
<AccountContentSection
166180
className={{ heading: 'mt-0' }}
167-
title={`Invite ${INVITE_GOAL} friends, get 1 month of Plus`}
181+
title={
182+
isRewardEnabled
183+
? `Invite ${INVITE_GOAL} friends, get 1 month of Plus`
184+
: 'Grow the community'
185+
}
168186
description={rewardDescription}
169187
>
170-
{isPlus && !isRewardUnlocked && (
188+
{isRewardEnabled && isPlus && !isRewardUnlocked && (
171189
<Typography
172190
className="mt-1"
173191
type={TypographyType.Footnote}
@@ -176,12 +194,14 @@ const AccountInvitePage = (): ReactElement => {
176194
Already on Plus? The free month is added to your subscription.
177195
</Typography>
178196
)}
179-
<InviteRewardProgress
180-
className="mt-4"
181-
joinedCount={referredUsersCount}
182-
referredUsers={users}
183-
rewardPeriod={rewardPeriod}
184-
/>
197+
{isRewardEnabled && (
198+
<InviteRewardProgress
199+
className="mt-4"
200+
joinedCount={referredUsersCount}
201+
referredUsers={users}
202+
rewardPeriod={rewardPeriod}
203+
/>
204+
)}
185205
</AccountContentSection>
186206
<AccountContentSection title="Your invitation link">
187207
<InviteLinkInput

0 commit comments

Comments
 (0)