diff --git a/.gitignore b/.gitignore index 3aaf82c..c9d1cd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # Dependencies /node_modules/ - +/src/ # Environment variables .env .env.local diff --git a/app/blocks/login-page/magic-link-option.tsx b/app/blocks/login-page/magic-link-option.tsx index b734d23..55ca257 100644 --- a/app/blocks/login-page/magic-link-option.tsx +++ b/app/blocks/login-page/magic-link-option.tsx @@ -1,10 +1,86 @@ import { useState } from "react"; import styles from "./magic-link-option.module.css"; +import { createBrowserClient } from "@supabase/ssr"; -interface Props { className?: string; } +interface Props { + className?: string; +} + +function getSupabaseClient() { + let supabaseUrl: string | undefined; + let supabaseAnonKey: string | undefined; + + // 1. Safe extraction for Next.js / Webpack + try { + supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; + supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + } catch (e) { + // process is completely missing in the browser environment + } + + // 2. Safe fallback extraction for Vite / Turbopack + if (!supabaseUrl || !supabaseAnonKey) { + try { + // @ts-ignore - bypass compilation warnings if import.meta is missing + supabaseUrl = import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_URL || import.meta.env.VITE_SUPABASE_URL; + // @ts-ignore + supabaseAnonKey = import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_ANON_KEY || import.meta.env.VITE_SUPABASE_ANON_KEY; + } catch (e) { + // import.meta is completely missing in this environment + } + } + + // Final confirmation check + if (!supabaseUrl || !supabaseAnonKey) return null; + + return createBrowserClient(supabaseUrl, supabaseAnonKey); +} export function MagicLinkOption({ className }: Props) { + const [email, setEmail] = useState(""); + const [loading, setLoading] = useState(false); const [sent, setSent] = useState(false); + const [errorMsg, setErrorMsg] = useState(null); + + const handleSendLink = async () => { + setErrorMsg(null); + + const trimmed = email.trim(); + if (!trimmed) { + setErrorMsg("Please enter your email."); + return; + } + + const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; + if (!emailRegex.test(trimmed)) { + setErrorMsg("Please enter a valid email address."); + return; + } + + setLoading(true); + try { + const supabase = getSupabaseClient(); + if (!supabase) { + setErrorMsg("Supabase is not configured (missing URL/anon key)."); + return; + } + + const { error } = await supabase.auth.signInWithOtp({ + email: trimmed, + options: { + emailRedirectTo: window.location.origin, + }, + }); + + if (error) throw error; + setSent(true); + } catch (err: any) { + setErrorMsg(err?.message || "Failed to send magic link."); + } finally { + setLoading(false); + } + }; + return (
@@ -12,12 +88,32 @@ export function MagicLinkOption({ className }: Props) { or sign in with magic link
+ {sent ? (
Magic link sent! Check your email.
) : (
- - + setEmail(e.target.value)} + disabled={loading} + /> + +
+ )} + + {errorMsg && ( +
+ {errorMsg}
)}
diff --git a/app/utils/supabase.client.ts b/app/utils/supabase.client.ts index 3c0392b..3c89990 100644 --- a/app/utils/supabase.client.ts +++ b/app/utils/supabase.client.ts @@ -1,8 +1,16 @@ import { createBrowserClient } from "@supabase/ssr"; export function getSupabaseBrowserClient() { - const supabaseUrl = import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_URL || process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_ANON_KEY || process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; - + const supabaseUrl = + import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_URL || + process.env.NEXT_PUBLIC_SUPABASE_URL || + process.env.SUPABASE_URL!; + + const supabaseKey = + import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_ANON_KEY || + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || + process.env.SUPABASE_PUBLISHABLE_KEY!; + return createBrowserClient(supabaseUrl, supabaseKey); } + diff --git a/app/utils/supabase.server.ts b/app/utils/supabase.server.ts index 8e448da..a70c516 100644 --- a/app/utils/supabase.server.ts +++ b/app/utils/supabase.server.ts @@ -3,8 +3,19 @@ import { createServerClient, parseCookieHeader, serializeCookieHeader } from "@s export function getSupabaseServerClient(request: Request) { const headers = new Headers(); - const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!; - const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!; + const supabaseUrl = + import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_URL || + process.env.NEXT_PUBLIC_SUPABASE_URL || + // fallback to non-vite env keys currently present in your .env + process.env.SUPABASE_URL!; + + const supabaseKey = + import.meta.env.VITE_NEXT_PUBLIC_SUPABASE_ANON_KEY || + process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY || + // fallback to non-vite env keys currently present in your .env + process.env.SUPABASE_PUBLISHABLE_KEY!; + + const supabase = createServerClient(supabaseUrl, supabaseKey, { cookies: {