diff --git a/app/auth/page.tsx b/app/auth/page.tsx index dfe81e0f..5b5bb58d 100644 --- a/app/auth/page.tsx +++ b/app/auth/page.tsx @@ -94,7 +94,7 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=your_key`} try { await signInWithGitHub(); } catch (error) { - errorLogger.error(ErrorCategory.AUTH, 'GitHub auth failed:', error); + errorLogger.error(ErrorCategory.AUTH, 'GitHub auth failed', error); setError('GitHub authentication failed. Please try again.'); setIsLoading(false); } diff --git a/app/layout.tsx b/app/layout.tsx index f8a1fdf4..339b56fe 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -2,7 +2,7 @@ import type { Metadata, Viewport } from 'next'; import { Inter } from 'next/font/google'; import { ThemeProvider } from '@/components/theme-provider'; -import { AuthProvider } from '@/providers/AuthProvider'; +import SupabaseProvider from '@/components/SupabaseProvider'; import { RealtimeProvider } from '@/providers/RealtimeProvider'; import { SentryProvider } from '@/components/sentry-provider'; import { VersionCheck } from '@/components/version-check'; @@ -124,7 +124,7 @@ export default function RootLayout({ disableTransitionOnChange > - +
{children}
@@ -132,7 +132,7 @@ export default function RootLayout({
-
+
diff --git a/app/page.tsx b/app/page.tsx index 39665f5e..8011b936 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -11,8 +11,6 @@ import PublicPlayground from '@/components/public-playground'; import ExampleGallery from '@/components/example-gallery'; import CompetitiveEdge from '@/components/competitive-edge'; import { useSupabase } from '@/components/SupabaseProvider'; -import { AuthButtons } from '@/components/auth-buttons'; -import { CrossBrowserButton } from '@/components/ui/cross-browser-button'; const FeaturesShowcase = dynamic(() => import('@/components/features-showcase'), { loading: () => ( @@ -59,6 +57,7 @@ export default function Home() { const [showFloatingCTA, setShowFloatingCTA] = useState(false); const [mounted, setMounted] = useState(false); const { scrollY } = useScroll(); + const { user, loading } = useSupabase(); // Ensure component is mounted before complex interactions useEffect(() => { @@ -92,6 +91,101 @@ export default function Home() { return () => unsubscribe(); }, [scrollY, showFloatingCTA, mounted]); + // Simple auth buttons that don't cause re-renders + const AuthButtons = () => { + if (loading) { + return ( +
+
+
+
+ ); + } + + if (user) { + return ( +
+ + Chat + + + Subscribe + +
+ ); + } + + return ( +
+ + Sign In + + + Sign Up + +
+ ); + }; + + // Simplified floating CTA + const FloatingCTA = () => { + const buttonText = user ? 'Go to Chat' : 'Sign In'; + const targetRoute = user ? '/chat' : '/auth'; + + return ( + router.push(targetRoute)} + className="flex items-center gap-2 rounded-full bg-gradient-to-r from-[#6C52A0] to-[#A0527C] px-6 py-3 font-medium text-white shadow-lg shadow-purple-900/20 hover:from-[#7C62B0] hover:to-[#B0627C]" + whileHover={{ scale: 1.05 }} + whileTap={{ scale: 0.95 }} + > + {buttonText} + + + + + + ); + }; + // Don't render complex interactions until mounted if (!mounted) { return ( @@ -102,51 +196,63 @@ export default function Home() { } return ( -
- + {/* Auth buttons */} + - - {/* Debug: Simple static buttons to confirm they show */} -
- - -
- {/* Uncomment this after we confirm the basic buttons work */} - {/* */} -
-
+ + - {/* Floating CTA */} + {/* Main CTA button */} -
+ {/* Floating CTA for subscribing */} + {showFloatingCTA && ( + + + {user ? 'Subscribe' : 'Sign Up'} + + + + + + )} + + {/* Main content */} +
@@ -155,53 +261,10 @@ export default function Home() { - + +
-
); } - -// Simplified floating CTA -const FloatingCTA = () => { - const router = useRouter(); - const { user } = useSupabase(); - const buttonText = user ? 'Go to Chat' : 'Sign In'; - const targetRoute = user ? '/chat' : '/auth'; - - return ( - router.push(targetRoute)} - className="cross-browser-button gradient-button-primary flex items-center gap-2 rounded-full px-6 py-3 font-medium text-white shadow-lg shadow-purple-900/20" - motionProps={{ - whileHover: { scale: 1.05 }, - whileTap: { scale: 0.95 }, - }} - > - {buttonText} - - - - - - ); -}; diff --git a/components/SupabaseProvider.tsx b/components/SupabaseProvider.tsx index a1599bc6..7c685a0d 100644 --- a/components/SupabaseProvider.tsx +++ b/components/SupabaseProvider.tsx @@ -34,19 +34,25 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo useEffect(() => { let isMounted = true; - // Set up a global timeout to prevent infinite loading + // Set up a shorter global timeout to prevent infinite loading const globalTimeout = setTimeout(() => { - if (isMounted && loading) { + if (isMounted) { errorLogger.warning(ErrorCategory.GENERAL, 'Auth initialization timeout - setting loading to false'); setLoading(false); setUser(null); } - }, 15000); // 15 second max timeout + }, 5000); // Reduced to 5 seconds for faster feedback // Check if Supabase is configured const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + console.log('🔧 Supabase Environment Check:', { + url: supabaseUrl ? 'Set' : 'Missing', + key: supabaseAnonKey ? 'Set' : 'Missing', + urlValue: supabaseUrl, + }); + if ( !supabaseUrl || !supabaseAnonKey || @@ -55,6 +61,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo supabaseUrl.includes('placeholder') || supabaseAnonKey.includes('placeholder') ) { + console.error('❌ Supabase environment variables are missing or using placeholders'); errorLogger.warning( ErrorCategory.GENERAL, 'Supabase environment variables are missing or using placeholders. Authentication will be disabled.' @@ -62,11 +69,14 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo if (isMounted) { setIsConfigured(false); setLoading(false); + setUser(null); } clearTimeout(globalTimeout); return; } + console.log('✅ Supabase environment variables configured'); + try { // Create Supabase client const client = createBrowserClient(supabaseUrl, supabaseAnonKey, { @@ -78,17 +88,23 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo }, }); - if (!isMounted) return; + if (!isMounted) { + clearTimeout(globalTimeout); + return; + } setSupabase(client); setIsConfigured(true); + console.log('✅ Supabase client created successfully'); - // Get initial session with timeout + // Get initial session with shorter timeout const getInitialSession = async () => { try { - // Create a timeout promise that rejects + console.log('🔍 Getting initial session...'); + + // Create a shorter timeout promise const timeoutPromise = new Promise((_, reject) => - setTimeout(() => reject(new Error('Session check timeout')), 10000) + setTimeout(() => reject(new Error('Session check timeout')), 3000) // Reduced to 3 seconds ); // Race between session check and timeout @@ -97,12 +113,17 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo const result = await Promise.race([sessionPromise, timeoutPromise]); if (result.error) { + console.error('❌ Error getting initial session:', result.error); errorLogger.error(ErrorCategory.GENERAL, 'Error getting initial session:', result.error); + } else { + console.log('✅ Initial session check completed', result.data?.session?.user ? 'User found' : 'No user'); } + if (isMounted) { setUser(result.data?.session?.user ?? null); } } catch (error) { + console.error('❌ Session check failed, continuing without auth:', error); errorLogger.error(ErrorCategory.GENERAL, 'Error getting session (falling back to no auth):', error); // On error, assume no user and continue if (isMounted) { @@ -111,6 +132,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo } finally { if (isMounted) { setLoading(false); + console.log('✅ Auth loading completed'); } clearTimeout(globalTimeout); } @@ -122,7 +144,8 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo const { data: { subscription }, } = client.auth.onAuthStateChange(async (event, session) => { - errorLogger.info(ErrorCategory.GENERAL, 'Auth state changed:', event, session?.user?.email); + console.log('🔄 Auth state changed:', event, session?.user?.email); + errorLogger.info(ErrorCategory.GENERAL, `Auth state changed: ${event} ${session?.user?.email || 'no user'}`); if (!isMounted) return; @@ -132,10 +155,12 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo // Handle different auth events if (event === 'SIGNED_OUT') { // Clear any cached data - but don't force redirect + console.log('👋 User signed out'); errorLogger.info(ErrorCategory.GENERAL, 'User signed out'); } else if (event === 'SIGNED_IN' && session?.user) { // Handle successful sign in - errorLogger.info(ErrorCategory.GENERAL, 'User signed in:', session.user.email); + console.log('👤 User signed in:', session.user.email); + errorLogger.info(ErrorCategory.GENERAL, `User signed in: ${session.user.email}`); // Try to sync user to database in background (non-blocking) try { @@ -145,7 +170,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo } catch (syncError) { errorLogger.warning( ErrorCategory.GENERAL, - 'Background user sync failed (auth still valid):', + 'Background user sync failed (auth still valid)', syncError ); // Don't fail the auth flow for this @@ -153,8 +178,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo } else if (event === 'TOKEN_REFRESHED') { errorLogger.info( ErrorCategory.GENERAL, - 'Token refreshed for user:', - session?.user?.email + `Token refreshed for user: ${session?.user?.email}` ); } }); @@ -165,10 +189,12 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo isMounted = false; }; } catch (error) { - errorLogger.error(ErrorCategory.GENERAL, 'Error initializing Supabase:', error); + console.error('❌ Error initializing Supabase:', error); + errorLogger.error(ErrorCategory.GENERAL, 'Error initializing Supabase', error); if (isMounted) { setIsConfigured(false); setLoading(false); + setUser(null); } clearTimeout(globalTimeout); } @@ -179,6 +205,18 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo }; }, []); + // Emergency fallback - force loading to false after 8 seconds no matter what + useEffect(() => { + const emergencyTimeout = setTimeout(() => { + if (loading) { + console.warn('âš ī¸ Emergency timeout: forcing loading to false'); + setLoading(false); + } + }, 8000); + + return () => clearTimeout(emergencyTimeout); + }, [loading]); + const signOut = async () => { if (!supabase || !isConfigured) { errorLogger.warning( @@ -192,12 +230,12 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo setLoading(true); const { error } = await supabase.auth.signOut(); if (error) { - errorLogger.error(ErrorCategory.GENERAL, 'Sign out error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'Sign out error', error); } else { setUser(null); } } catch (error) { - errorLogger.error(ErrorCategory.GENERAL, 'Sign out error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'Sign out error', error); } finally { setLoading(false); } @@ -211,7 +249,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo try { setLoading(true); const redirectTo = `${window.location.origin}/auth/callback?next=/chat`; - errorLogger.info(ErrorCategory.GENERAL, 'GitHub OAuth redirect URL:', redirectTo); + errorLogger.info(ErrorCategory.GENERAL, `GitHub OAuth redirect URL: ${redirectTo}`); const { error } = await supabase.auth.signInWithOAuth({ provider: 'github', @@ -220,12 +258,12 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo }, }); if (error) { - errorLogger.error(ErrorCategory.GENERAL, 'GitHub sign in error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'GitHub sign in error', error); setLoading(false); throw error; } } catch (error) { - errorLogger.error(ErrorCategory.GENERAL, 'GitHub sign in error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'GitHub sign in error', error); setLoading(false); throw error; } @@ -244,7 +282,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo }); return { error }; } catch (error) { - errorLogger.error(ErrorCategory.GENERAL, 'Email sign in error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'Email sign in error', error); return { error }; } finally { setLoading(false); @@ -270,7 +308,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo }); return { error }; } catch (error) { - errorLogger.error(ErrorCategory.GENERAL, 'Email sign up error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'Email sign up error', error); return { error }; } finally { setLoading(false); @@ -288,7 +326,7 @@ export default function SupabaseProvider({ children }: { children: React.ReactNo }); return { error }; } catch (error) { - errorLogger.error(ErrorCategory.GENERAL, 'Reset password error:', error); + errorLogger.error(ErrorCategory.GENERAL, 'Reset password error', error); return { error }; } }; diff --git a/components/hero.tsx b/components/hero.tsx index 8f589239..56f26d93 100644 --- a/components/hero.tsx +++ b/components/hero.tsx @@ -3,7 +3,6 @@ import { useState, useEffect, useRef } from 'react'; import { motion } from 'framer-motion'; import { Button } from './ui/button'; -import { CrossBrowserButton } from './ui/cross-browser-button'; import { useRouter } from 'next/navigation'; import useIsMobile from '@/hooks/useIsMobile'; @@ -142,17 +141,6 @@ export default function Hero() { transition={{ duration: 0.8, delay: 0.7 }} className="flex flex-col justify-center gap-4 sm:flex-row" > -<<<<<<< HEAD - - -======= Explore Examples ->>>>>>> f975414d0fc27fc5df0c6bfdee5d45995fa23c95 {/* Subscribe Button */} @@ -188,13 +166,9 @@ export default function Hero() { transition={{ duration: 0.8, delay: 0.9 }} className="mt-8 flex flex-col items-center" > - router.push('/auth')} - motionProps={{ - whileHover: { scale: 1.02 }, - whileTap: { scale: 0.98 } - }} > Get Full Access - +
Start building amazing websites today
diff --git a/package.json b/package.json index f9c1cd21..815fadcb 100644 --- a/package.json +++ b/package.json @@ -16,6 +16,7 @@ "format": "prettier --write \"**/*.{js,jsx,ts,tsx,json,css,md}\"", "format:check": "prettier --check \"**/*.{js,jsx,ts,tsx,json,css,md}\"", "check-env": "node scripts/check-env.js", + "verify-deployment": "node scripts/verify-deployment.js", "fix-chat": "node scripts/fix-chat-api.js", "fix-db": "node scripts/auto-fix-database.js", "optimize-bundle": "node scripts/optimize-bundle.js", diff --git a/scripts/verify-deployment.js b/scripts/verify-deployment.js new file mode 100644 index 00000000..979e6937 --- /dev/null +++ b/scripts/verify-deployment.js @@ -0,0 +1,210 @@ +#!/usr/bin/env node + +/** + * Deployment Verification Script + * + * This script verifies that all environment variables are properly configured + * for Vercel deployment and tests authentication flow. + */ + +const fs = require('fs'); +const path = require('path'); + +// Color codes for terminal output +const colors = { + reset: '\x1b[0m', + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + gray: '\x1b[90m', +}; + +console.log(`${colors.blue}🚀 ZapDev Deployment Verification${colors.reset}\n`); + +// Load environment variables from .env.local if it exists +const envPath = path.join(process.cwd(), '.env.local'); +if (fs.existsSync(envPath)) { + require('dotenv').config({ path: envPath }); + console.log(`${colors.green}✅ Found .env.local file${colors.reset}`); +} else { + console.log(`${colors.yellow}âš ī¸ No .env.local file found - checking system environment${colors.reset}`); +} + +// Critical environment variables for deployment +const criticalVars = [ + { + key: 'NEXT_PUBLIC_SUPABASE_URL', + description: 'Supabase project URL', + required: true, + validate: (value) => value && value.includes('.supabase.co') && !value.includes('placeholder') + }, + { + key: 'NEXT_PUBLIC_SUPABASE_ANON_KEY', + description: 'Supabase anonymous key', + required: true, + validate: (value) => value && value.length > 50 && !value.includes('placeholder') + }, + { + key: 'GROQ_API_KEY', + description: 'Groq AI API key', + required: true, + validate: (value) => value && value.startsWith('gsk_') + } +]; + +// Optional but recommended variables +const optionalVars = [ + { + key: 'SUPABASE_SERVICE_ROLE_KEY', + description: 'Supabase service role key (for admin operations)', + validate: (value) => !value || value.length > 50 + }, + { + key: 'NODE_ENV', + description: 'Node environment', + validate: (value) => !value || ['development', 'production', 'test'].includes(value) + } +]; + +let hasErrors = false; +let hasWarnings = false; + +console.log(`${colors.blue}Checking Critical Environment Variables:${colors.reset}`); +console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + +// Check critical variables +criticalVars.forEach(({ key, description, required, validate }) => { + const value = process.env[key]; + + if (!value) { + console.log(`${colors.red}❌ ${key}: Missing${colors.reset}`); + console.log(`${colors.gray} ${description}${colors.reset}`); + hasErrors = true; + } else if (validate && !validate(value)) { + console.log(`${colors.red}❌ ${key}: Invalid format${colors.reset}`); + console.log(`${colors.gray} ${description}${colors.reset}`); + hasErrors = true; + } else { + console.log(`${colors.green}✅ ${key}: Configured${colors.reset}`); + } +}); + +console.log(`\n${colors.blue}Checking Optional Environment Variables:${colors.reset}`); +console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + +// Check optional variables +optionalVars.forEach(({ key, description, validate }) => { + const value = process.env[key]; + + if (!value) { + console.log(`${colors.yellow}âš ī¸ ${key}: Not set${colors.reset}`); + console.log(`${colors.gray} ${description}${colors.reset}`); + hasWarnings = true; + } else if (validate && !validate(value)) { + console.log(`${colors.yellow}âš ī¸ ${key}: Invalid format${colors.reset}`); + console.log(`${colors.gray} ${description}${colors.reset}`); + hasWarnings = true; + } else { + console.log(`${colors.green}✅ ${key}: Configured${colors.reset}`); + } +}); + +// Environment-specific checks +console.log(`\n${colors.blue}Environment-Specific Checks:${colors.reset}`); +console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + +// Check if we're in production mode +const isProduction = process.env.NODE_ENV === 'production'; +if (isProduction) { + console.log(`${colors.green}✅ Running in production mode${colors.reset}`); + + // Additional production checks + const appUrl = process.env.NEXT_PUBLIC_APP_URL; + if (!appUrl || appUrl.includes('localhost')) { + console.log(`${colors.yellow}âš ī¸ NEXT_PUBLIC_APP_URL should be set to your domain in production${colors.reset}`); + hasWarnings = true; + } else { + console.log(`${colors.green}✅ App URL configured for production${colors.reset}`); + } +} else { + console.log(`${colors.blue}â„šī¸ Running in development mode${colors.reset}`); +} + +// Check Vercel-specific configuration +if (process.env.VERCEL) { + console.log(`${colors.green}✅ Running on Vercel${colors.reset}`); + console.log(`${colors.gray} Region: ${process.env.VERCEL_REGION || 'unknown'}${colors.reset}`); + console.log(`${colors.gray} Environment: ${process.env.VERCEL_ENV || 'unknown'}${colors.reset}`); +} else { + console.log(`${colors.blue}â„šī¸ Not running on Vercel (local development)${colors.reset}`); +} + +// Authentication test +console.log(`\n${colors.blue}Testing Authentication Configuration:${colors.reset}`); +console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + +const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL; +const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY; + +if (supabaseUrl && supabaseKey && + !supabaseUrl.includes('placeholder') && + !supabaseKey.includes('placeholder')) { + console.log(`${colors.green}✅ Supabase configuration looks valid${colors.reset}`); + console.log(`${colors.gray} URL: ${supabaseUrl}${colors.reset}`); + console.log(`${colors.gray} Key: ${supabaseKey.substring(0, 20)}...${colors.reset}`); + + // Try to validate the URL format + try { + const url = new URL(supabaseUrl); + if (url.hostname.endsWith('.supabase.co')) { + console.log(`${colors.green}✅ Supabase URL format is correct${colors.reset}`); + } else { + console.log(`${colors.yellow}âš ī¸ Supabase URL doesn't appear to be a valid Supabase URL${colors.reset}`); + hasWarnings = true; + } + } catch (error) { + console.log(`${colors.red}❌ Supabase URL is not a valid URL${colors.reset}`); + hasErrors = true; + } +} else { + console.log(`${colors.red}❌ Supabase configuration is missing or using placeholders${colors.reset}`); + hasErrors = true; +} + +// Final summary +console.log(`\n${colors.blue}Deployment Summary:${colors.reset}`); +console.log(`${colors.gray}${'─'.repeat(60)}${colors.reset}`); + +if (hasErrors) { + console.log(`${colors.red}❌ Deployment verification failed!${colors.reset}`); + console.log(`${colors.gray} Please fix the critical errors above before deploying.${colors.reset}`); + + console.log(`\n${colors.blue}Quick Fix Instructions:${colors.reset}`); + console.log(`${colors.gray}1. Go to your Vercel dashboard${colors.reset}`); + console.log(`${colors.gray}2. Navigate to your project settings${colors.reset}`); + console.log(`${colors.gray}3. Go to Environment Variables${colors.reset}`); + console.log(`${colors.gray}4. Add the missing variables listed above${colors.reset}`); + console.log(`${colors.gray}5. Redeploy your application${colors.reset}`); + + process.exit(1); +} else if (hasWarnings) { + console.log(`${colors.yellow}âš ī¸ Deployment verification passed with warnings${colors.reset}`); + console.log(`${colors.gray} Your app should work, but consider fixing the warnings for optimal performance.${colors.reset}`); +} else { + console.log(`${colors.green}✅ Deployment verification passed!${colors.reset}`); + console.log(`${colors.gray} Your app is ready for deployment.${colors.reset}`); +} + +console.log(`\n${colors.blue}Next Steps:${colors.reset}`); +if (process.env.VERCEL) { + console.log(`${colors.gray}â€ĸ Your app is running on Vercel - check the live URL${colors.reset}`); + console.log(`${colors.gray}â€ĸ Test authentication by signing in${colors.reset}`); + console.log(`${colors.gray}â€ĸ Check browser console for any errors${colors.reset}`); +} else { + console.log(`${colors.gray}â€ĸ Run 'bun run build' to test production build${colors.reset}`); + console.log(`${colors.gray}â€ĸ Deploy to Vercel when ready${colors.reset}`); + console.log(`${colors.gray}â€ĸ Test authentication after deployment${colors.reset}`); +} + +console.log(`\n${colors.gray}For more help, check the README.md file${colors.reset}`); \ No newline at end of file