diff --git a/src/api/leaderboard.ts b/src/api/leaderboard.ts
index 5e87b120f..5589ab174 100644
--- a/src/api/leaderboard.ts
+++ b/src/api/leaderboard.ts
@@ -20,6 +20,10 @@ export type ListLeaderboardStatsResponse = {
githubContributors: {
currentMonth: LeaderboardUserDetails[];
};
+ referrals: {
+ currentMonth: LeaderboardUserDetails[];
+ lifetime: LeaderboardUserDetails[];
+ };
};
export function leaderboardApi(context: APIContext) {
diff --git a/src/components/AccountStreak/AccountStreak.tsx b/src/components/AccountStreak/AccountStreak.tsx
index 750d966c4..306d660ec 100644
--- a/src/components/AccountStreak/AccountStreak.tsx
+++ b/src/components/AccountStreak/AccountStreak.tsx
@@ -2,7 +2,7 @@ import { useEffect, useRef, useState } from 'react';
import { isLoggedIn } from '../../lib/jwt';
import { httpGet } from '../../lib/http';
import { useToast } from '../../hooks/use-toast';
-import { Flame, X, Zap, ZapOff } from 'lucide-react';
+import { Zap, ZapOff } from 'lucide-react';
import { useOutsideClick } from '../../hooks/use-outside-click';
import { StreakDay } from './StreakDay';
import {
@@ -11,15 +11,8 @@ import {
} from '../../stores/page.ts';
import { useStore } from '@nanostores/react';
import { cn } from '../../lib/classname.ts';
-import { $accountStreak } from '../../stores/streak.ts';
-
-type StreakResponse = {
- count: number;
- longestCount: number;
- previousCount?: number | null;
- firstVisitAt: Date;
- lastVisitAt: Date;
-};
+import { $accountStreak, type StreakResponse } from '../../stores/streak.ts';
+import { InviteFriends } from './InviteFriends.tsx';
type AccountStreakProps = {};
@@ -184,11 +177,10 @@ export function AccountStreak(props: AccountStreakProps) {
Visit every day to keep your streak going!
-
-
- See how you compare to others
-
-
+
+
)}
diff --git a/src/components/AccountStreak/InviteFriends.tsx b/src/components/AccountStreak/InviteFriends.tsx
new file mode 100644
index 000000000..ff3885818
--- /dev/null
+++ b/src/components/AccountStreak/InviteFriends.tsx
@@ -0,0 +1,92 @@
+import { Copy, Heart } from 'lucide-react';
+import { useAuth } from '../../hooks/use-auth';
+import { useCopyText } from '../../hooks/use-copy-text';
+import { cn } from '../../lib/classname';
+import { CheckIcon } from '../ReactIcons/CheckIcon';
+import {TrophyEmoji} from "../ReactIcons/TrophyEmoji.tsx";
+
+type InviteFriendsProps = {
+ refByUserCount: number;
+};
+
+export function InviteFriends(props: InviteFriendsProps) {
+ const { refByUserCount } = props;
+
+ const user = useAuth();
+ const { copyText, isCopied } = useCopyText();
+
+ const referralLink = new URL(
+ `/signup?rc=${user?.id}`,
+ import.meta.env.DEV ? 'http://localhost:3000' : 'https://roadmap.sh',
+ ).toString();
+
+ return (
+
+
+ Invite people to join roadmap.sh
+
+
+
+ {Array.from({ length: 10 }).map((_, index) => (
+
+ ))}
+
+ {refByUserCount === 0 && (
+
You haven't invited anyone yet.
+ )}
+
+ {refByUserCount > 0 && refByUserCount < 10 && (
+
{refByUserCount} of 10 users joined
+ )}
+
+ {refByUserCount >= 10 && (
+
+ 🎉 You've invited {refByUserCount} users
+
+ )}
+
+
+ Share{' '}
+ {' '}
+ with anyone you think would benefit from roadmap.sh
+
+
+
+
+ See how you rank on the leaderboard
+
+
+
+ );
+}
diff --git a/src/components/AuthenticationFlow/EmailSignupForm.tsx b/src/components/AuthenticationFlow/EmailSignupForm.tsx
index f98857be6..f1c0f86f6 100644
--- a/src/components/AuthenticationFlow/EmailSignupForm.tsx
+++ b/src/components/AuthenticationFlow/EmailSignupForm.tsx
@@ -1,5 +1,7 @@
-import { type FormEvent, useState } from 'react';
+import { type FormEvent, useEffect, useState } from 'react';
import { httpPost } from '../../lib/http';
+import { deleteUrlParam, getUrlParams } from '../../lib/browser';
+import { isLoggedIn, setAIReferralCode } from '../../lib/jwt';
type EmailSignupFormProps = {
isDisabled?: boolean;
@@ -9,6 +11,9 @@ type EmailSignupFormProps = {
export function EmailSignupForm(props: EmailSignupFormProps) {
const { isDisabled, setIsDisabled } = props;
+ const { rc: referralCode } = getUrlParams() as {
+ rc?: string;
+ };
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const [name, setName] = useState('');
@@ -47,6 +52,16 @@ export function EmailSignupForm(props: EmailSignupFormProps) {
)}`;
};
+ useEffect(() => {
+ if (!referralCode || isLoggedIn()) {
+ deleteUrlParam('rc');
+ return;
+ }
+
+ setAIReferralCode(referralCode);
+ deleteUrlParam('rc');
+ }, []);
+
return (