From 599c0b486d188081555b35888611a7799370ad51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?th=E1=BB=8Bnh?= Date: Wed, 13 Aug 2025 21:36:49 +0700 Subject: [PATCH] Require login with real users before using integration --- web/personas-open-source/src/app/page.tsx | 187 +++++++++++++++++++--- 1 file changed, 166 insertions(+), 21 deletions(-) diff --git a/web/personas-open-source/src/app/page.tsx b/web/personas-open-source/src/app/page.tsx index ea4ee200709..0899067d00e 100644 --- a/web/personas-open-source/src/app/page.tsx +++ b/web/personas-open-source/src/app/page.tsx @@ -22,19 +22,26 @@ import { doc, setDoc, or, + getDoc, } from 'firebase/firestore'; import { toast } from 'sonner'; import { Mixpanel } from '@/lib/mixpanel'; import { useInView } from 'react-intersection-observer'; import { ulid } from 'ulid'; -import { auth } from '@/lib/firebase'; +import { auth, googleProvider } from '@/lib/firebase'; import { Header } from '@/components/Header'; import { InputArea } from '@/components/InputArea'; import { ChatbotList } from '@/components/ChatbotList'; import { Footer } from '@/components/Footer'; import { Chatbot, TwitterProfile, LinkedinProfile } from '@/types/profiles'; import { PreorderBanner } from '@/components/shared/PreorderBanner'; -import { signInAnonymously, onAuthStateChanged, User } from 'firebase/auth'; +import { Button } from '@/components/ui/button'; +import { + signInAnonymously, + onAuthStateChanged, + User, + signInWithPopup, +} from 'firebase/auth'; // Helper function to detect mobile devices (basic check) const isMobileDevice = (): boolean => { @@ -170,8 +177,12 @@ export default function HomePage() { const [availablePlatforms] = useState({ twitter: true, linkedin: true }); const [platformSelectionMode] = useState<'create' | 'add'>('create'); const [currentUserUid, setCurrentUserUid] = useState(null); + const [isAnonymous, setIsAnonymous] = useState(true); const [authInitialized, setAuthInitialized] = useState(false); const [isIntegrating, setIsIntegrating] = useState(false); + const [showLoginPrompt, setShowLoginPrompt] = useState(false); + const [pendingIntegration, setPendingIntegration] = useState(null); + const [loginUidMismatch, setLoginUidMismatch] = useState(null); // ---------------------------------------------------------------------------------- // Helper: open ChatGPT workspace - NOW WITH MOBILE HANDLING @@ -227,11 +238,18 @@ export default function HomePage() { useEffect(() => { const unsubscribe = onAuthStateChanged(auth, (user: User | null) => { if (user) { - console.log('[Auth State] User found:', user.uid); + console.log( + '[Auth State] User found:', + user.uid, + 'isAnonymous:', + user.isAnonymous, + ); setCurrentUserUid(user.uid); + setIsAnonymous(user.isAnonymous); } else { console.log('[Auth State] No user found.'); setCurrentUserUid(null); + setIsAnonymous(true); } setAuthInitialized(true); // Mark auth as initialized }); @@ -898,12 +916,42 @@ Recent activity on Linkedin:\n"${enhancedDesc}" which you can use for your perso openChatGPTWithUid(uid); }; + const handleShowAllIntegrationsClick = (e: React.MouseEvent) => { + Mixpanel.track('Show All Integrations Clicked', { + timestamp: new Date().toISOString(), + }); + + const params = new URLSearchParams(window.location.search); + const uidParam = params.get('uid'); + if (uidParam && uidParam !== currentUserUid) { + e.preventDefault(); + setLoginUidMismatch(uidParam); + setShowLoginPrompt(true); + return; + } + + if (isAnonymous) { + e.preventDefault(); + setPendingIntegration('show_all_integrations'); + setShowLoginPrompt(true); + } + }; + const handleIntegrationClick = async (provider: string) => { if (isIntegrating) return; setIsIntegrating(true); console.log(`[handleIntegrationClick] Clicked provider: ${provider}`); + const params = new URLSearchParams(window.location.search); + const uidParam = params.get('uid'); + if (uidParam && uidParam !== currentUserUid) { + setLoginUidMismatch(uidParam); + setShowLoginPrompt(true); + setIsIntegrating(false); + return; + } + // Track the click event in Mixpanel Mixpanel.track('Integration Clicked', { provider: provider, @@ -929,6 +977,16 @@ Recent activity on Linkedin:\n"${enhancedDesc}" which you can use for your perso setIsIntegrating(false); // Reset state on failure return; } + + // Check if the user is anonymous + if (isAnonymous) { + if (loadingToastId) toast.dismiss(loadingToastId); + setPendingIntegration(provider); + setShowLoginPrompt(true); + setIsIntegrating(false); + return; + } + console.log(`[handleIntegrationClick] Obtained UID: ${uid}`); // 2. Trigger API Call (Fire-and-Forget - before clipboard/redirect logic) @@ -1025,11 +1083,103 @@ Recent activity on Linkedin:\n"${enhancedDesc}" which you can use for your perso } }; + useEffect(() => { + const triggerPendingIntegration = async () => { + // Only proceed if there's a pending integration and the user is fully authenticated + if (pendingIntegration && !isAnonymous && currentUserUid) { + // Important: copy the value and clear the state *before* the async operation + // to prevent re-triggering if the component re-renders. + const integrationToRun = pendingIntegration; + setPendingIntegration(null); + + if (integrationToRun === 'show_all_integrations') { + const url = `https://veyrax.com/omi/auth?omi_user_id=${encodeURIComponent( + currentUserUid, + )}`; + window.open(url, '_blank', 'noopener,noreferrer'); + } else { + // Now we can safely call handleIntegrationClick, as the state is up-to-date + await handleIntegrationClick(integrationToRun); + } + } + }; + triggerPendingIntegration(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [pendingIntegration, isAnonymous, currentUserUid]); + // URL for the Veyrax page to add more tools - Updated path const addToolsUrl = currentUserUid ? `https://veyrax.com/omi/auth?omi_user_id=${encodeURIComponent(currentUserUid)}` : '#'; + const LoginPrompt = () => { + const handleGoogleSignIn = async () => { + try { + const result = await signInWithPopup(auth, googleProvider); + const user = result.user; + setCurrentUserUid(user.uid); // Set UID after login + setIsAnonymous(false); // A Google-signed-in user is never anonymous + + // Save user data if first time + const userRef = doc(db, 'users', user.uid); + const userSnap = await getDoc(userRef); + + if (!userSnap.exists()) { + const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone; + await setDoc(userRef, { + time_zone: timeZone, + created_at: new Date(), + }); + } + + setShowLoginPrompt(false); + setLoginUidMismatch(null); + } catch (error) { + console.error('Error signing in or saving user data:', error); + toast.error('Error signing in. Please try again.'); + } + }; + + return ( +
+
+
+
+

omi

+ {loginUidMismatch ? ( + <> +

+ Please sign in to the correct account. +

+

+ This action requires the account with ID starting with:{' '} + + {loginUidMismatch.substring(0, 8)}... + +

+ + ) : ( + <> +

Sign in to start integration

+

+ Create a free account to unlock all integrations +

+ + )} +
+ + +
+
+
+ ); + }; + return (
{/* */} @@ -1044,24 +1194,18 @@ Recent activity on Linkedin:\n"${enhancedDesc}" which you can use for your perso isIntegrating={isIntegrating} /> - {/* Add more tools link (conditionally rendered) */} - {currentUserUid && ( - - )} + {/* Add more tools link */} + {/* Before/After Comparison */}
@@ -1126,6 +1270,7 @@ Recent activity on Linkedin:\n"${enhancedDesc}" which you can use for your perso onSelect={handlePlatformSelect} mode={platformSelectionMode} /> + {showLoginPrompt && }
); }