Spaces:
Sleeping
Sleeping
Fix auth redirect URLs: use NEXT_PUBLIC_SITE_URL instead of window.location.origin (fixes localhost in verification emails)
Browse files- web/app/auth/forgot-password/page.tsx +3 -2
- web/app/auth/login/page.tsx +5 -2
- web/app/auth/signup/page.tsx +20 -9
- web/lib/auth-url.ts +13 -0
web/app/auth/forgot-password/page.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
import { createClient } from "@/lib/supabase/client";
|
|
|
|
| 5 |
import Link from "next/link";
|
| 6 |
import { ArrowLeft, Mail, Check } from "lucide-react";
|
| 7 |
|
|
@@ -18,7 +19,7 @@ export default function ForgotPasswordPage() {
|
|
| 18 |
setError("");
|
| 19 |
|
| 20 |
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
| 21 |
-
redirectTo: `${
|
| 22 |
});
|
| 23 |
|
| 24 |
if (error) { setError(error.message); }
|
|
@@ -33,7 +34,7 @@ export default function ForgotPasswordPage() {
|
|
| 33 |
|
| 34 |
const { error } = await supabase.auth.signInWithOtp({
|
| 35 |
email,
|
| 36 |
-
options: { emailRedirectTo: `${
|
| 37 |
});
|
| 38 |
|
| 39 |
if (error) { setError(error.message); }
|
|
|
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
import { createClient } from "@/lib/supabase/client";
|
| 5 |
+
import { getBaseUrl } from "@/lib/auth-url";
|
| 6 |
import Link from "next/link";
|
| 7 |
import { ArrowLeft, Mail, Check } from "lucide-react";
|
| 8 |
|
|
|
|
| 19 |
setError("");
|
| 20 |
|
| 21 |
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
| 22 |
+
redirectTo: `${getBaseUrl()}/auth/reset-password`,
|
| 23 |
});
|
| 24 |
|
| 25 |
if (error) { setError(error.message); }
|
|
|
|
| 34 |
|
| 35 |
const { error } = await supabase.auth.signInWithOtp({
|
| 36 |
email,
|
| 37 |
+
options: { emailRedirectTo: `${getBaseUrl()}/auth/callback` },
|
| 38 |
});
|
| 39 |
|
| 40 |
if (error) { setError(error.message); }
|
web/app/auth/login/page.tsx
CHANGED
|
@@ -2,6 +2,7 @@
|
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
import { createClient } from "@/lib/supabase/client";
|
|
|
|
| 5 |
import Link from "next/link";
|
| 6 |
import { ArrowLeft, Mail } from "lucide-react";
|
| 7 |
|
|
@@ -24,7 +25,8 @@ export default function LoginPage() {
|
|
| 24 |
if (!email) { setError("Enter your email first."); return; }
|
| 25 |
setLoading(true); setError("");
|
| 26 |
const { error } = await supabase.auth.signInWithOtp({
|
| 27 |
-
email,
|
|
|
|
| 28 |
});
|
| 29 |
if (error) { setError(error.message); }
|
| 30 |
else { setMagicSent(true); }
|
|
@@ -33,7 +35,8 @@ export default function LoginPage() {
|
|
| 33 |
|
| 34 |
async function handleOAuth(provider: "google" | "github") {
|
| 35 |
await supabase.auth.signInWithOAuth({
|
| 36 |
-
provider,
|
|
|
|
| 37 |
});
|
| 38 |
}
|
| 39 |
|
|
|
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
import { createClient } from "@/lib/supabase/client";
|
| 5 |
+
import { getBaseUrl } from "@/lib/auth-url";
|
| 6 |
import Link from "next/link";
|
| 7 |
import { ArrowLeft, Mail } from "lucide-react";
|
| 8 |
|
|
|
|
| 25 |
if (!email) { setError("Enter your email first."); return; }
|
| 26 |
setLoading(true); setError("");
|
| 27 |
const { error } = await supabase.auth.signInWithOtp({
|
| 28 |
+
email,
|
| 29 |
+
options: { emailRedirectTo: `${getBaseUrl()}/auth/callback` },
|
| 30 |
});
|
| 31 |
if (error) { setError(error.message); }
|
| 32 |
else { setMagicSent(true); }
|
|
|
|
| 35 |
|
| 36 |
async function handleOAuth(provider: "google" | "github") {
|
| 37 |
await supabase.auth.signInWithOAuth({
|
| 38 |
+
provider,
|
| 39 |
+
options: { redirectTo: `${getBaseUrl()}/auth/callback` },
|
| 40 |
});
|
| 41 |
}
|
| 42 |
|
web/app/auth/signup/page.tsx
CHANGED
|
@@ -2,7 +2,9 @@
|
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
import { createClient } from "@/lib/supabase/client";
|
|
|
|
| 5 |
import Link from "next/link";
|
|
|
|
| 6 |
|
| 7 |
export default function SignupPage() {
|
| 8 |
const [email, setEmail] = useState("");
|
|
@@ -14,14 +16,21 @@ export default function SignupPage() {
|
|
| 14 |
|
| 15 |
async function handleSignup(e: React.FormEvent) {
|
| 16 |
e.preventDefault(); setLoading(true); setError("");
|
| 17 |
-
const { error } = await supabase.auth.signUp({
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 18 |
if (error) { setError(error.message); } else { setDone(true); }
|
| 19 |
setLoading(false);
|
| 20 |
}
|
| 21 |
|
| 22 |
async function handleOAuth(provider: "google" | "github") {
|
| 23 |
await supabase.auth.signInWithOAuth({
|
| 24 |
-
provider,
|
|
|
|
| 25 |
});
|
| 26 |
}
|
| 27 |
|
|
@@ -30,7 +39,7 @@ export default function SignupPage() {
|
|
| 30 |
<div className="min-h-screen flex items-center justify-center bg-white px-4">
|
| 31 |
<div className="text-center max-w-xs">
|
| 32 |
<h2 className="text-xl font-semibold">Check your email</h2>
|
| 33 |
-
<p className="mt-2 text-sm text-zinc-500">We sent a confirmation link to {email}.</p>
|
| 34 |
<Link href="/auth/login" className="mt-4 inline-block text-sm text-zinc-600 hover:underline">Back to login</Link>
|
| 35 |
</div>
|
| 36 |
</div>
|
|
@@ -41,15 +50,17 @@ export default function SignupPage() {
|
|
| 41 |
<div className="min-h-screen flex items-center justify-center bg-white px-4">
|
| 42 |
<div className="w-full max-w-sm">
|
| 43 |
<div className="mb-8">
|
| 44 |
-
<Link href="/" className="text-sm text-zinc-400 hover:text-zinc-600">
|
|
|
|
|
|
|
| 45 |
<h1 className="mt-4 text-xl font-semibold">Create an account</h1>
|
| 46 |
</div>
|
| 47 |
|
| 48 |
<div className="space-y-2.5">
|
| 49 |
<button onClick={() => handleOAuth("google")}
|
| 50 |
-
className="w-full px-4 py-2.5 border border-zinc-200 rounded-
|
| 51 |
<button onClick={() => handleOAuth("github")}
|
| 52 |
-
className="w-full px-4 py-2.5 border border-zinc-200 rounded-
|
| 53 |
</div>
|
| 54 |
|
| 55 |
<div className="flex items-center gap-3 my-6">
|
|
@@ -58,12 +69,12 @@ export default function SignupPage() {
|
|
| 58 |
|
| 59 |
<form onSubmit={handleSignup} className="space-y-3">
|
| 60 |
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required
|
| 61 |
-
placeholder="Email" className="w-full px-3 py-2.5 border border-zinc-200 rounded-
|
| 62 |
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required minLength={8}
|
| 63 |
-
placeholder="Password (8+ characters)" className="w-full px-3 py-2.5 border border-zinc-200 rounded-
|
| 64 |
{error && <p className="text-xs text-red-600">{error}</p>}
|
| 65 |
<button type="submit" disabled={loading}
|
| 66 |
-
className="w-full bg-zinc-900 text-white py-2.5 rounded-
|
| 67 |
{loading ? "Creating..." : "Create account"}
|
| 68 |
</button>
|
| 69 |
</form>
|
|
|
|
| 2 |
|
| 3 |
import { useState } from "react";
|
| 4 |
import { createClient } from "@/lib/supabase/client";
|
| 5 |
+
import { getBaseUrl } from "@/lib/auth-url";
|
| 6 |
import Link from "next/link";
|
| 7 |
+
import { ArrowLeft } from "lucide-react";
|
| 8 |
|
| 9 |
export default function SignupPage() {
|
| 10 |
const [email, setEmail] = useState("");
|
|
|
|
| 16 |
|
| 17 |
async function handleSignup(e: React.FormEvent) {
|
| 18 |
e.preventDefault(); setLoading(true); setError("");
|
| 19 |
+
const { error } = await supabase.auth.signUp({
|
| 20 |
+
email,
|
| 21 |
+
password,
|
| 22 |
+
options: {
|
| 23 |
+
emailRedirectTo: `${getBaseUrl()}/auth/callback`,
|
| 24 |
+
},
|
| 25 |
+
});
|
| 26 |
if (error) { setError(error.message); } else { setDone(true); }
|
| 27 |
setLoading(false);
|
| 28 |
}
|
| 29 |
|
| 30 |
async function handleOAuth(provider: "google" | "github") {
|
| 31 |
await supabase.auth.signInWithOAuth({
|
| 32 |
+
provider,
|
| 33 |
+
options: { redirectTo: `${getBaseUrl()}/auth/callback` },
|
| 34 |
});
|
| 35 |
}
|
| 36 |
|
|
|
|
| 39 |
<div className="min-h-screen flex items-center justify-center bg-white px-4">
|
| 40 |
<div className="text-center max-w-xs">
|
| 41 |
<h2 className="text-xl font-semibold">Check your email</h2>
|
| 42 |
+
<p className="mt-2 text-sm text-zinc-500">We sent a confirmation link to <span className="font-medium text-zinc-700">{email}</span>.</p>
|
| 43 |
<Link href="/auth/login" className="mt-4 inline-block text-sm text-zinc-600 hover:underline">Back to login</Link>
|
| 44 |
</div>
|
| 45 |
</div>
|
|
|
|
| 50 |
<div className="min-h-screen flex items-center justify-center bg-white px-4">
|
| 51 |
<div className="w-full max-w-sm">
|
| 52 |
<div className="mb-8">
|
| 53 |
+
<Link href="/" className="inline-flex items-center gap-1.5 text-sm text-zinc-400 hover:text-zinc-600">
|
| 54 |
+
<ArrowLeft className="w-3.5 h-3.5" /> Back
|
| 55 |
+
</Link>
|
| 56 |
<h1 className="mt-4 text-xl font-semibold">Create an account</h1>
|
| 57 |
</div>
|
| 58 |
|
| 59 |
<div className="space-y-2.5">
|
| 60 |
<button onClick={() => handleOAuth("google")}
|
| 61 |
+
className="w-full px-4 py-2.5 border border-zinc-200 rounded-lg text-sm hover:bg-zinc-50 transition-colors">Continue with Google</button>
|
| 62 |
<button onClick={() => handleOAuth("github")}
|
| 63 |
+
className="w-full px-4 py-2.5 border border-zinc-200 rounded-lg text-sm hover:bg-zinc-50 transition-colors">Continue with GitHub</button>
|
| 64 |
</div>
|
| 65 |
|
| 66 |
<div className="flex items-center gap-3 my-6">
|
|
|
|
| 69 |
|
| 70 |
<form onSubmit={handleSignup} className="space-y-3">
|
| 71 |
<input type="email" value={email} onChange={(e) => setEmail(e.target.value)} required
|
| 72 |
+
placeholder="Email" className="w-full px-3 py-2.5 border border-zinc-200 rounded-lg text-sm focus:outline-none focus:border-zinc-400" />
|
| 73 |
<input type="password" value={password} onChange={(e) => setPassword(e.target.value)} required minLength={8}
|
| 74 |
+
placeholder="Password (8+ characters)" className="w-full px-3 py-2.5 border border-zinc-200 rounded-lg text-sm focus:outline-none focus:border-zinc-400" />
|
| 75 |
{error && <p className="text-xs text-red-600">{error}</p>}
|
| 76 |
<button type="submit" disabled={loading}
|
| 77 |
+
className="w-full bg-zinc-900 text-white py-2.5 rounded-lg text-sm font-medium hover:bg-zinc-800 disabled:opacity-40 transition-colors">
|
| 78 |
{loading ? "Creating..." : "Create account"}
|
| 79 |
</button>
|
| 80 |
</form>
|
web/lib/auth-url.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
/**
|
| 2 |
+
* Get the base URL for auth redirects.
|
| 3 |
+
* Uses NEXT_PUBLIC_SITE_URL in production, falls back to window.location.origin for local dev.
|
| 4 |
+
*/
|
| 5 |
+
export function getBaseUrl(): string {
|
| 6 |
+
if (process.env.NEXT_PUBLIC_SITE_URL) {
|
| 7 |
+
return process.env.NEXT_PUBLIC_SITE_URL;
|
| 8 |
+
}
|
| 9 |
+
if (typeof window !== "undefined") {
|
| 10 |
+
return window.location.origin;
|
| 11 |
+
}
|
| 12 |
+
return "http://localhost:3000";
|
| 13 |
+
}
|