Compare commits
2 Commits
5b8643a9e7
...
136f41020f
| Author | SHA1 | Date | |
|---|---|---|---|
| 136f41020f | |||
| 081862e1d7 |
@ -211,6 +211,13 @@ function LoginForm() {
|
|||||||
<p className={`text-sm font-medium text-center ${isError ? "text-red-600 dark:text-red-400" : "text-foreground"}`}>{status}</p>
|
<p className={`text-sm font-medium text-center ${isError ? "text-red-600 dark:text-red-400" : "text-foreground"}`}>{status}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
<p className="text-center text-sm text-muted-foreground">
|
||||||
|
Don’t have an account?{" "}
|
||||||
|
<Link href="/register" className="font-semibold text-primary hover:underline">
|
||||||
|
Sign up
|
||||||
|
</Link>
|
||||||
|
</p>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -253,7 +260,8 @@ export default function LoginPage() {
|
|||||||
Sign In
|
Sign In
|
||||||
</h2>
|
</h2>
|
||||||
<p className="mt-2 text-center text-sm text-muted-foreground">
|
<p className="mt-2 text-center text-sm text-muted-foreground">
|
||||||
<Link href="/register" className="font-medium text-primary hover:text-primary/80 transition-colors">
|
New to Aarthalabs?{" "}
|
||||||
|
<Link href="/register" className="font-semibold text-primary hover:underline">
|
||||||
Start your 14-day free trial
|
Start your 14-day free trial
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
50
lib/api.ts
50
lib/api.ts
@ -34,25 +34,41 @@ export function clearAuth(): void {
|
|||||||
localStorage.removeItem(USER_KEY);
|
localStorage.removeItem(USER_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Shared in-flight refresh promise so concurrent 401s (e.g. several widgets
|
||||||
|
// loading at once) all await the same refresh call instead of each firing
|
||||||
|
// their own — refresh tokens are single-use, so racing calls would otherwise
|
||||||
|
// invalidate each other and force a false session-expired logout.
|
||||||
|
let refreshInFlight: Promise<boolean> | null = null;
|
||||||
|
|
||||||
async function tryRefresh(): Promise<boolean> {
|
async function tryRefresh(): Promise<boolean> {
|
||||||
|
if (refreshInFlight) return refreshInFlight;
|
||||||
|
|
||||||
|
refreshInFlight = (async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch("/api/auth/refresh", {
|
||||||
|
method: "POST",
|
||||||
|
headers: { "Content-Type": "application/json" },
|
||||||
|
});
|
||||||
|
if (!res.ok) {
|
||||||
|
clearAuth();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const payload = (await res.json()) as ApiResponse<unknown>;
|
||||||
|
if (payload.error) {
|
||||||
|
clearAuth();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
clearAuth();
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await fetch("/api/auth/refresh", {
|
return await refreshInFlight;
|
||||||
method: "POST",
|
} finally {
|
||||||
headers: { "Content-Type": "application/json" },
|
refreshInFlight = null;
|
||||||
});
|
|
||||||
if (!res.ok) {
|
|
||||||
clearAuth();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const payload = (await res.json()) as ApiResponse<unknown>;
|
|
||||||
if (payload.error) {
|
|
||||||
clearAuth();
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
} catch {
|
|
||||||
clearAuth();
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user