Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions src/components/trade/header/user-menu.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import {
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import { useCopyToClipboard } from "@/hooks/ui/use-copy-to-clipboard";
import { useAutoRegisterReferral } from "@/hooks/use-referral";
import { shortenAddress } from "@/lib/format";

function CopyAddressMenuItem({ address }: { address: string }) {
Expand Down Expand Up @@ -53,6 +54,8 @@ export function UserMenu() {
const { data: ensName } = useEnsName({ address });
const [mounted, setMounted] = useState(false);

useAutoRegisterReferral();

useEffect(() => {
setMounted(true);
}, []);
Expand Down
20 changes: 11 additions & 9 deletions src/components/trade/tradebox/points-modal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,14 @@ import {
WarningCircleIcon,
} from "@phosphor-icons/react";
import { usePrivy } from "@privy-io/react-auth";
import { useEffect, useId, useState } from "react";
import { useCallback, useEffect, useId, useState } from "react";
import { useConnection } from "wagmi";
import { Button } from "@/components/ui/button";
import { Dialog, DialogContent, DialogHeader, DialogTitle } from "@/components/ui/dialog";
import { InfoRow } from "@/components/ui/info-row";
import { Input } from "@/components/ui/input";
import { useCopyToClipboard } from "@/hooks/ui/use-copy-to-clipboard";
import { getStoredReferral } from "@/hooks/use-referral";
import { usePointsModalActions, usePointsModalOpen } from "@/stores/use-global-modal-store";

const API_URL = import.meta.env.VITE_HYPERMILES_API_URL;
Expand Down Expand Up @@ -97,12 +98,12 @@ export function PointsModal() {

const [view, setView] = useState<ModalView>("loading");
const [userPoints, setUserPoints] = useState<UserPoints | null>(null);
const [referralCode, setReferralCode] = useState("");
const [referralCode, setReferralCode] = useState(() => getStoredReferral() ?? "");
const [error, setError] = useState<string | null>(null);
const [submitting, setSubmitting] = useState(false);
const referralInputId = useId();

function fetchPoints() {
const fetchPoints = useCallback(() => {
if (!address) return;
setView("loading");
setError(null);
Expand All @@ -122,17 +123,17 @@ export function PointsModal() {
setView("signup");
}
})
.catch(() => {
.catch((error) => {
console.error("Failed to fetch points:", error);
setError("Unable to connect to points service");
setView("error");
});
}
}, [address]);

// biome-ignore lint/correctness/useExhaustiveDependencies: fetchPoints is intentionally excluded to avoid re-creating on every render
useEffect(() => {
if (!open || !address) return;
fetchPoints();
}, [open, address]);
}, [open, address, fetchPoints]);

function handleClose() {
close();
Expand Down Expand Up @@ -179,6 +180,7 @@ export function PointsModal() {
});
setView("summary");
} catch (err) {
console.error("Signup failed:", err);
setError(err instanceof Error ? err.message : "Signup failed");
} finally {
setSubmitting(false);
Expand Down Expand Up @@ -217,8 +219,8 @@ export function PointsModal() {
<InfoRow
className="p-0"
labelClassName="flex items-center gap-1.5 text-text-500"
label={<Trans>Your Referral Code</Trans>}
value={<CopyableCode code={userPoints.referralCode} />}
label={<Trans>Your Referral Link</Trans>}
value={<CopyableCode code={`${window.location.origin}/?referral=${userPoints.referralCode}`} />}
/>
</div>

Expand Down
78 changes: 78 additions & 0 deletions src/hooks/use-referral.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { usePrivy } from "@privy-io/react-auth";
import { useEffect, useRef } from "react";
import { useConnection } from "wagmi";

const API_URL = import.meta.env.VITE_HYPERMILES_API_URL;
const STORAGE_KEY = "hyperterminal:referral";

function getStoredReferral(): string | null {
if (typeof window === "undefined") return null;
return localStorage.getItem(STORAGE_KEY);
}

function clearStoredReferral() {
localStorage.removeItem(STORAGE_KEY);
}

export function useReferralCapture() {
useEffect(() => {
if (typeof window === "undefined") return;

const params = new URLSearchParams(window.location.search);
const referral = params.get("referral");
if (!referral) return;

localStorage.setItem(STORAGE_KEY, referral);

params.delete("referral");
const newSearch = params.toString();
const newUrl = window.location.pathname + (newSearch ? `?${newSearch}` : "") + window.location.hash;
window.history.replaceState({}, "", newUrl);
}, []);
}

export function useAutoRegisterReferral() {
const { address, isConnected } = useConnection();
const { user } = usePrivy();
const registeredRef = useRef(false);

useEffect(() => {
async function autoRegister() {
if (!isConnected || !address || registeredRef.current) return;

registeredRef.current = true;

try {
const res = await fetch(`${API_URL}/user_points?user_address=${address}`);
if (res.ok) {
clearStoredReferral();
return;
}

if (res.status === 404) {
const referral = getStoredReferral();
const body: Record<string, string> = { walletAddress: address };
if (referral) {
body.referralCode = referral;
}
if (user?.id) {
body.privyUserId = user.id;
}
await fetch(`${API_URL}/users`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(body),
});
clearStoredReferral();
}
} catch (error) {
console.error("Auto-registration failed:", error);
registeredRef.current = false;
}
}

autoRegister();
}, [isConnected, address, user?.id]);
Comment on lines +39 to +75
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The logic inside this useEffect can be simplified and made more readable by using async/await. This also provides an opportunity to handle errors more explicitly by logging them in a catch block. The current implementation swallows errors, which can make debugging difficult.

useEffect(() => {
	const autoRegister = async () => {
		if (!isConnected || !address || registeredRef.current) return;

		registeredRef.current = true;

		try {
			const res = await fetch(`${API_URL}/user_points?user_address=${address}`);
			if (res.ok) {
				clearStoredReferral();
				return;
			}

			if (res.status === 404) {
				const referral = getStoredReferral();
				const body: Record<string, string> = { walletAddress: address };
				if (referral) {
					body.referralCode = referral;
				}
				if (user?.id) {
					body.privyUserId = user.id;
				}
				await fetch(`${API_URL}/users`, {
					method: "POST",
					headers: { "Content-Type": "application/json" },
					body: JSON.stringify(body),
				});
				clearStoredReferral();
			}
		} catch (error) {
			console.error("Auto-registration failed:", error);
			registeredRef.current = false;
		}
	};

	autoRegister();
}, [isConnected, address, user?.id]);

}

export { getStoredReferral };
6 changes: 5 additions & 1 deletion src/locales/ar/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -2256,5 +2256,9 @@ msgstr ""
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Code"
#~ msgid "Your Referral Code"
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Link"
msgstr ""
8 changes: 6 additions & 2 deletions src/locales/en/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -2269,5 +2269,9 @@ msgstr "Yes"
#~ msgstr "You can revoke permissions at any time by registering a new approval with 0% fee rate. Builder codes are processed entirely onchain as part of the fee logic."

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Code"
msgstr "Your Referral Code"
#~ msgid "Your Referral Code"
#~ msgstr "Your Referral Code"

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Link"
msgstr "Your Referral Link"
6 changes: 5 additions & 1 deletion src/locales/es/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -2256,5 +2256,9 @@ msgstr ""
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Code"
#~ msgid "Your Referral Code"
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Link"
msgstr ""
6 changes: 5 additions & 1 deletion src/locales/fr/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -2256,5 +2256,9 @@ msgstr ""
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Code"
#~ msgid "Your Referral Code"
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Link"
msgstr ""
6 changes: 5 additions & 1 deletion src/locales/hi/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -2256,5 +2256,9 @@ msgstr ""
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Code"
#~ msgid "Your Referral Code"
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Link"
msgstr ""
6 changes: 5 additions & 1 deletion src/locales/zh/messages.po
Original file line number Diff line number Diff line change
Expand Up @@ -2256,5 +2256,9 @@ msgstr ""
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Code"
#~ msgid "Your Referral Code"
#~ msgstr ""

#: src/components/trade/tradebox/points-modal.tsx
msgid "Your Referral Link"
msgstr ""
3 changes: 3 additions & 0 deletions src/routes/__root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { ClientOnly, createRootRouteWithContext, HeadContent, Outlet, Scripts }
import { useEffect } from "react";
import { NotFoundPage } from "@/components/pages/not-found-page";
import { Toaster } from "@/components/ui/sonner";
import { useReferralCapture } from "@/hooks/use-referral";
import { MarketsInfoProvider } from "@/lib/hyperliquid/hooks/MarketsInfoProvider";
import { buildPageHead, mergeHead } from "@/lib/seo";
import { ExchangeScopeProvider } from "@/providers/exchange-scope";
Expand All @@ -25,6 +26,8 @@ export const Route = createRootRouteWithContext<MyRouterContext>()({
});

function RootComponent() {
useReferralCapture();

useEffect(() => {
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/sw.js", { scope: "/" });
Expand Down
Loading