diff --git a/EnvExample.txt b/EnvExample.txt new file mode 100644 index 0000000..4cafc9f --- /dev/null +++ b/EnvExample.txt @@ -0,0 +1,10 @@ +EMAIL_USER=Your App Email +EMAIL_PASSWORD=Your Google App Password +NEXT_PUBLIC_GA_ID=Your Google Analytics ID + +NEXT_PUBLIC_SUPABASE_URL=Your supabase Url +NEXT_PUBLIC_SUPABASE_ANON_KEY=Your Anon Key +NEXT_PUBLIC_TURNSTILE_SITE_KEY=Your Cloudfare Captcha Key + +TURNSTILE_SECRET_KEY=Your Cloudfare backend route api key +SUPABASE_SERVICE_KEY=Your supabase service key \ No newline at end of file diff --git a/app/api/auth/route.js b/app/api/auth/route.js new file mode 100644 index 0000000..6633ff2 --- /dev/null +++ b/app/api/auth/route.js @@ -0,0 +1,52 @@ +import { createClient } from '@supabase/supabase-js' + +const supabase = createClient( + process.env.NEXT_PUBLIC_SUPABASE_URL, + process.env.SUPABASE_SERVICE_KEY +) + +export async function POST(req) { + try { + const { email, password, captchaToken, action } = await req.json() + + if (!email || !password) + return new Response(JSON.stringify({ message: 'Email and password required' }), { status: 400 }) + + if (action === 'signup') { + if (!captchaToken) + return new Response(JSON.stringify({ message: 'Captcha token missing' }), { status: 400 }) + + // Verify Turnstile token + const verifyRes = await fetch('https://challenges.cloudflare.com/turnstile/v0/siteverify', { + method: 'POST', + headers: { 'Content-Type': 'application/x-www-form-urlencoded' }, + body: new URLSearchParams({ + secret: process.env.TURNSTILE_SECRET_KEY, + response: captchaToken, + }), + }) + + const data = await verifyRes.json() + if (!data.success) + return new Response(JSON.stringify({ message: 'Captcha verification failed' }), { status: 400 }) + + // Create Supabase user + const { user, error } = await supabase.auth.admin.createUser({ email, password }) + if (error) + return new Response(JSON.stringify({ message: error.message }), { status: 400 }) + + return new Response(JSON.stringify({ message: 'Signup successful! Check your email.' }), { status: 200 }) + } + + else if (action === 'login') { + return new Response(JSON.stringify({ message: 'Use frontend login with anon key' }), { status: 400 }) + } + + else { + return new Response(JSON.stringify({ message: 'Invalid action' }), { status: 400 }) + } + } catch (err) { + console.error(err) + return new Response(JSON.stringify({ message: 'Internal server error' }), { status: 500 }) + } +} \ No newline at end of file diff --git a/app/login/page.jsx b/app/login/page.jsx index 76213ce..ac91dde 100644 --- a/app/login/page.jsx +++ b/app/login/page.jsx @@ -5,12 +5,12 @@ import { useRouter } from 'next/navigation' import { FiMail, FiLock, FiUser, FiLogIn, FiUserPlus, FiSun, FiMoon } from 'react-icons/fi' import { motion } from 'framer-motion' import Link from 'next/link' -import dynamic from "next/dynamic"; +import dynamic from 'next/dynamic' const Turnstile = dynamic( - () => import("@marsidev/react-turnstile").then((mod) => mod.Turnstile), + () => import('@marsidev/react-turnstile').then((mod) => mod.Turnstile), { ssr: false } -); +) export default function LoginPage() { const [email, setEmail] = useState('') @@ -38,17 +38,28 @@ export default function LoginPage() { const handleAuth = async () => { setLoading(true) setError('') - + try { if (isLogin) { + // Login with frontend anon key const { error } = await supabase.auth.signInWithPassword({ email, password }) if (error) throw error router.push('/dashboard') } else { + // Signup via API route with Turnstile if (!captchaToken) throw new Error('Please complete captcha') - const { error } = await supabase.auth.signUp({ email, password, options: { captchaToken } }) - if (error) throw error - alert('Check your email for confirmation!') + + const res = await fetch('/auth', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password, captchaToken, action: 'signup' }), + }) + + const data = await res.json() + if (!res.ok) throw new Error(data.message) + + alert(data.message) + setIsLogin(true) // switch to login after signup } } catch (err) { setError(err.message) @@ -57,21 +68,19 @@ export default function LoginPage() { } } - const handleGoogleSignIn = async () => { - const { error } = await supabase.auth.signInWithOAuth({ - provider: 'google', - }) - if (error) console.error('Error signing in with Google:', error.message) + const handleGoogleSignIn = async () => { + const { error } = await supabase.auth.signInWithOAuth({ provider: 'google' }) + if (error) console.error('Google sign-in error:', error.message) } return ( -
- + - {/* Header with theme toggle */} + {/* Header */}

{isLogin ? 'Welcome Back' : 'Create Account'}

@@ -79,30 +88,19 @@ export default function LoginPage() { {isLogin ? 'Sign in to access your dashboard' : 'Join us to get started'}

-
{error && ( - -

{error}

+ + {error} )} + {/* Form */}
@@ -110,7 +108,7 @@ export default function LoginPage() {
setEmail(e.target.value)} @@ -123,7 +121,7 @@ export default function LoginPage() {
setPassword(e.target.value)} @@ -137,7 +135,7 @@ export default function LoginPage() {
@@ -156,53 +154,45 @@ export default function LoginPage() { onClick={handleAuth} disabled={loading} className={`w-full flex items-center justify-center py-3 px-4 rounded-lg text-white font-medium transition-all ${ - loading - ? 'bg-gray-400 dark:bg-gray-600 cursor-not-allowed' - : 'bg-blue-600 hover:from-blue-700 hover:to-purple-700 dark:hover:from-blue-800 dark:hover:to-purple-800 shadow-md hover:shadow-lg' + loading + ? 'bg-gray-400 dark:bg-gray-600 cursor-not-allowed' + : 'bg-blue-600 hover:bg-blue-700 dark:hover:bg-blue-800 shadow-md hover:shadow-lg' }`} > {loading ? ( 'Processing...' + ) : isLogin ? ( + <> + Sign In + ) : ( <> - {isLogin ? ( - <> - Sign In - - ) : ( - <> - Sign Up - - )} + Sign Up )}
+ {/* Switch forms */}
{isLogin ? (

Don't have an account?{' '} -

) : (

Already have an account?{' '} -

)}
+ {/* Google OAuth */}
or @@ -210,15 +200,9 @@ export default function LoginPage() {