diff --git a/app/complete-profile/page.tsx b/app/complete-profile/page.tsx index c99d48eb..4478f289 100644 --- a/app/complete-profile/page.tsx +++ b/app/complete-profile/page.tsx @@ -5,6 +5,10 @@ import { useRouter } from 'next/navigation'; import Link from 'next/link'; import { createClient } from '@/lib/supabase/client'; import { toast } from 'sonner'; +import CodeuniaLogo from '@/components/codeunia-logo'; +import { InputValidator } from '@/lib/security/input-validation'; +import { CheckCircle, XCircle, AlertCircle, Loader2, Sparkles } from 'lucide-react'; +import { profileService } from '@/lib/services/profile'; interface User { id: string; @@ -25,6 +29,7 @@ export default function CompleteProfile() { const [isLoading, setIsLoading] = useState(false); const [isCheckingUsername, setIsCheckingUsername] = useState(false); const [usernameAvailable, setUsernameAvailable] = useState(null); + const [usernameError, setUsernameError] = useState(''); const [user, setUser] = useState(null); const [isValidating, setIsValidating] = useState(true); const router = useRouter(); @@ -43,29 +48,30 @@ export default function CompleteProfile() { } setUser(user); - // Check if profile already exists and is complete - const { data: profile } = await getSupabaseClient() - .from('profiles') - .select('first_name, last_name, username, profile_complete') - .eq('id', user.id) - .single(); - - if (profile) { - const isProfileComplete = profile.first_name && - profile.last_name && - profile.username && - profile.profile_complete; + // Check if profile already exists and is complete using profileService + try { + const profile = await profileService.getProfile(user.id); - if (isProfileComplete) { - // Profile is already complete, redirect to dashboard - router.push('/protected/dashboard'); - return; + if (profile) { + const isProfileComplete = profile.first_name && + profile.last_name && + profile.username && + profile.profile_complete; + + if (isProfileComplete) { + // Profile is already complete, redirect to dashboard + router.push('/protected/dashboard'); + return; + } + + // Pre-fill existing data + if (profile.first_name) setFirstName(profile.first_name); + if (profile.last_name) setLastName(profile.last_name); + if (profile.username) setUsername(profile.username); } - - // Pre-fill existing data - if (profile.first_name) setFirstName(profile.first_name); - if (profile.last_name) setLastName(profile.last_name); - if (profile.username) setUsername(profile.username); + } catch (profileError) { + console.error('Error checking profile:', profileError); + // Continue with the form - profileService will handle creation if needed } // Pre-fill from OAuth provider data if available @@ -101,21 +107,42 @@ export default function CompleteProfile() { }, []); const checkUsernameAvailability = async (usernameToCheck: string) => { - if (!usernameToCheck || usernameToCheck.length < 3) { + const clean = usernameToCheck.trim(); + + // First validate the username format + const validation = InputValidator.validateUsername(clean); + if (!validation.isValid) { + setUsernameError(validation.error || 'Invalid username'); + setUsernameAvailable(false); + return; + } + + setUsernameError(''); + + if (!clean || clean.length < 3) { setUsernameAvailable(null); return; } setIsCheckingUsername(true); try { - const clean = usernameToCheck.trim(); - const { data: isAvailable } = await getSupabaseClient().rpc('check_username_availability', { - username_param: clean - }); - setUsernameAvailable(isAvailable); + // Use direct query instead of RPC function (same approach as UsernameField component) + const { data, error } = await getSupabaseClient() + .from('profiles') + .select('username') + .eq('username', clean) + .single(); + + if (error && error.code !== 'PGRST116') { + throw error; + } + + // If no data found, username is available + setUsernameAvailable(!data); } catch (error) { console.error('Error checking username:', error); setUsernameAvailable(null); + setUsernameError('Unable to check username availability'); } finally { setIsCheckingUsername(false); } @@ -123,9 +150,10 @@ export default function CompleteProfile() { const handleUsernameChange = (value: string) => { setUsername(value); + setUsernameError(''); // Clear previous errors if (usernameCheckTimeout.current) clearTimeout(usernameCheckTimeout.current); usernameCheckTimeout.current = setTimeout(() => { - checkUsernameAvailability(value.trim()); + checkUsernameAvailability(value); }, 500); }; @@ -160,30 +188,32 @@ export default function CompleteProfile() { setIsLoading(true); try { - // Update profile with the provided information using upsert to handle missing profiles - const { data: upserted, error } = await getSupabaseClient() + // First update the basic profile information using profileService + const updatedProfile = await profileService.updateProfile(user.id, { + first_name: firstName.trim(), + last_name: lastName.trim(), + username: username.trim() + }); + + if (!updatedProfile) { + console.error('Profile update failed: No data returned'); + toast.error('Failed to update profile. Please try again.'); + return; + } + + // Then update the completion status fields directly + const { error: completionError } = await getSupabaseClient() .from('profiles') - .upsert([{ - id: user.id, - first_name: firstName.trim(), - last_name: lastName.trim(), - username: username.trim(), + .update({ profile_complete: true, username_set: true, username_editable: false - }], { onConflict: 'id' }) - .select('id') - .single(); + }) + .eq('id', user.id); - if (error) { - console.error('Profile update error:', error); - toast.error(error.message || 'Failed to update profile. Please try again.'); - return; - } - - if (!upserted) { - console.error('Profile update failed: No data returned'); - toast.error('Failed to update profile. Please try again.'); + if (completionError) { + console.error('Error updating completion status:', completionError); + toast.error('Profile updated but completion status failed. Please try again.'); return; } @@ -199,11 +229,17 @@ export default function CompleteProfile() { const generateRandomUsername = async () => { try { - const { data: randomUsername } = await getSupabaseClient().rpc('generate_safe_username'); - if (randomUsername) { - setUsername(randomUsername); - checkUsernameAvailability(randomUsername); - } + // Generate a simple random username since RPC might not be available + const adjectives = ['cool', 'smart', 'bright', 'quick', 'bold', 'wise', 'keen', 'sharp']; + const nouns = ['coder', 'dev', 'builder', 'creator', 'maker', 'hacker', 'ninja', 'wizard']; + const numbers = Math.floor(Math.random() * 9999); + + const adjective = adjectives[Math.floor(Math.random() * adjectives.length)]; + const noun = nouns[Math.floor(Math.random() * nouns.length)]; + const randomUsername = `${adjective}${noun}${numbers}`; + + setUsername(randomUsername); + checkUsernameAvailability(randomUsername); } catch (error) { console.error('Error generating username:', error); toast.error('Error generating username'); @@ -223,17 +259,17 @@ export default function CompleteProfile() { } return ( -
-
+
+
{/* Header */}
-
- CU +
+
-

- Welcome! Let's set up your CodeUnia profile. +

+ Welcome! Let's set up your profile

-

+

Complete your profile to get started with CodeUnia. This will only take a moment.

@@ -241,119 +277,171 @@ export default function CompleteProfile() { {/* Setup Form */}
{/* First Name */} -
-