+ {/* Star row β shows outline stars initially, filled gold stars in positive step */}
+
+ {STAR_INDICES.map((i) => (
+
+ ))}
+
+
+ {/* Text */}
+
+
+ {isPositive
+ ? 'Would you leave a quick review?'
+ : 'Are you enjoying daily.dev on your new tab?'}
+
+
+ {isPositive
+ ? 'Honest reviews help other developers discover daily.dev β it takes 30 seconds.'
+ : 'Let us know if this is a good moment to ask.'}
+
+
+ );
+}
diff --git a/packages/shared/src/components/referral/ReferralGrowthTestingPanel.tsx b/packages/shared/src/components/referral/ReferralGrowthTestingPanel.tsx
new file mode 100644
index 00000000000..89f151b52f6
--- /dev/null
+++ b/packages/shared/src/components/referral/ReferralGrowthTestingPanel.tsx
@@ -0,0 +1,416 @@
+import type { ReactElement } from 'react';
+import React, { useState } from 'react';
+import classNames from 'classnames';
+import { Button, ButtonSize, ButtonVariant } from '../buttons/Button';
+import {
+ Typography,
+ TypographyColor,
+ TypographyType,
+} from '../typography/Typography';
+import { useAuthContext } from '../../contexts/AuthContext';
+import { useLazyModal } from '../../hooks/useLazyModal';
+import { LazyModal } from '../modals/common/types';
+import {
+ chromeWebStoreReviewUrl,
+ isTesting,
+ settingsUrl,
+} from '../../lib/constants';
+import { useGrowthBookContext } from '../GrowthBookProvider';
+import { MiniCloseIcon, ReadingStreakIcon, StarIcon } from '../icons';
+import { IconSize } from '../Icon';
+import { StreakShareFlowDemo } from './StreakShareFlowDemo';
+
+const STAR_INDICES = [0, 1, 2, 3, 4] as const;
+
+/*
+ * TEMPORARY QA PANEL
+ * Remove this component and its registration in MainLayout entirely
+ * after the referral/review growth experiment has been reviewed and approved.
+ */
+
+export function ReferralGrowthTestingPanel(): ReactElement | null {
+ const { user } = useAuthContext();
+ const { growthbook } = useGrowthBookContext();
+ const { openModal } = useLazyModal();
+ const [isCollapsed, setIsCollapsed] = useState(false);
+ const [flagsForced, setFlagsForced] = useState(false);
+ const [reviewOpen, setReviewOpen] = useState(false);
+ const [reviewPositive, setReviewPositive] = useState(false);
+ const [demoStreak, setDemoStreak] = useState(7);
+ const [demoOpen, setDemoOpen] = useState(false);
+
+ if (isTesting) {
+ return null;
+ }
+
+ const setFlagsForcedOn = (on: boolean) => {
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
+ const gb = growthbook as any;
+ if (on) {
+ gb?.setForcedFeatures?.(
+ new Map([
+ ['referral_growth_loops', true],
+ ['extension_store_review_prompt', true],
+ ]),
+ );
+ } else {
+ gb?.setForcedFeatures?.(new Map());
+ }
+ setFlagsForced(on);
+ };
+
+ const launchStreakInvite = (currentStreak: number, maxStreak: number) => {
+ setFlagsForcedOn(true);
+ setDemoStreak(currentStreak);
+ openModal({
+ type: LazyModal.NewStreak,
+ props: { currentStreak, maxStreak },
+ });
+ };
+
+ const buildDemoMessage = (streak: number) => {
+ const firstName = user?.name?.split(' ')[0] ?? 'I';
+ if (streak >= 100) {
+ return `π₯ ${firstName} read dev content every day for ${streak} days straight on daily.dev. Worth a look:`;
+ }
+ if (streak >= 30) {
+ return `π₯ ${streak} days reading dev news on daily.dev β no skips. Want to give it a try?`;
+ }
+ return `π₯ ${streak}-day reading streak on daily.dev. Read with me:`;
+ };
+
+ const demoUsername = user?.username ?? 'you';
+ const demoLink = user?.username
+ ? `daily.dev/${user.username}`
+ : 'daily.dev/your-profile';
+
+ const openFeedbackModal = () => openModal({ type: LazyModal.Feedback });
+
+ const toggleReview = () => {
+ setReviewOpen((prev) => !prev);
+ setReviewPositive(false);
+ };
+
+ const reviewBanner = reviewOpen ? (
+
+
+
+ {STAR_INDICES.map((i) => (
+
+ ))}
+
+
+
+ {reviewPositive
+ ? 'Would you leave a quick review?'
+ : 'Are you enjoying daily.dev on your new tab?'}
+
+
+ {reviewPositive
+ ? 'Honest reviews help other developers discover daily.dev β it takes 30 seconds.'
+ : 'Let us know if this is a good moment to ask.'}
+
+
+
+ {step === 'send' && '1 of 3 β You hit βSendβ'}
+ {step === 'receive' && '2 of 3 β Your friend opens WhatsApp'}
+ {step === 'land' && '3 of 3 β They tap the link'}
+
+