Skip to content
Closed
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
66 changes: 66 additions & 0 deletions src/app/dashboard/referrals/page.copy-loading.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// @vitest-environment jsdom

import { render, screen, waitFor } from "@testing-library/react";
import { afterEach, describe, expect, it, vi } from "vitest";
import ReferralsPage from "./page";

function deferred<T>() {
let resolve!: (value: T) => void;
const promise = new Promise<T>((res) => {
resolve = res;
});
return { promise, resolve };
}

describe("ReferralsPage copy loading state", () => {
afterEach(() => {
vi.unstubAllGlobals();
});

it("disables referral link copying until the link is loaded", async () => {
const codeResponse = deferred<{
ok: boolean;
json: () => Promise<{ code: string; link: string }>;
}>();

vi.stubGlobal(
"fetch",
vi.fn(async (input: RequestInfo | URL) => {
const url = input.toString();
if (url === "/api/referrals/code") {
return codeResponse.promise;
}

return {
ok: true,
json: async () => ({
data: [],
stats: {
total_invited: 0,
total_registered: 0,
conversion_rate: 0,
},
}),
};
})
);

render(<ReferralsPage />);

const copyButton = screen.getByRole("button", { name: /copy/i });
expect(copyButton).toBeDisabled();

codeResponse.resolve({
ok: true,
json: async () => ({
code: "alice",
link: "https://ugig.net/?ref=alice",
}),
});

await waitFor(() => {
expect(screen.getByDisplayValue("https://ugig.net/?ref=alice")).toBeTruthy();
});
expect(copyButton).not.toBeDisabled();
});
});
8 changes: 7 additions & 1 deletion src/app/dashboard/referrals/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ export default function ReferralsPage() {
}, []);

const copyLink = async () => {
if (!referralLink) {
setError("Referral link is still loading. Please try again.");
return;
}
Comment on lines 65 to +69

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Dead guard — unreachable via UI

Because the Copy button is now disabled={!referralLink}, a disabled <button> won't fire onClick, so copyLink can never be called while referralLink is empty. The if (!referralLink) block and the setError(...) call inside it are dead code; the error message will never be displayed to the user through normal interaction. The guard can be removed without changing behavior.


await navigator.clipboard.writeText(referralLink);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
Expand Down Expand Up @@ -166,7 +171,8 @@ export default function ReferralsPage() {
/>
<button
onClick={copyLink}
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors text-sm"
disabled={!referralLink}
className="flex items-center gap-2 px-4 py-2 bg-primary text-primary-foreground rounded-md hover:bg-primary/90 transition-colors text-sm disabled:opacity-50"
>
<Copy className="h-4 w-4" />
{copied ? "Copied!" : "Copy"}
Expand Down
Loading