'use client' import { useEffect, useRef, useState } from 'react' import { Loader, Modal, ModalClose, ModalContent, ModalDescription, ModalTitle, ModalTrigger, } from '@sim/emcn' import { createLogger } from '@sim/logger' import { X } from 'lucide-react' import Image from 'next/image' import { useRouter } from 'next/navigation' import { GithubIcon, GoogleIcon, MicrosoftIcon } from '@/components/icons' import { requestJson } from '@/lib/api/client/request' import { type AuthProviderStatusResponse, getAuthProvidersContract } from '@/lib/api/contracts/auth' import { client } from '@/lib/auth/auth-client' import { getEnv, isFalsy, isTruthy } from '@/lib/core/config/env' import { captureClientEvent } from '@/lib/posthog/client' import type { PostHogEventMap } from '@/lib/posthog/events' import { getBrandConfig } from '@/ee/whitelabeling' const logger = createLogger('AuthModal') type AuthView = 'login' | 'signup' interface AuthModalProps { children: React.ReactNode defaultView?: AuthView source: PostHogEventMap['auth_modal_opened']['source'] } type ProviderStatus = AuthProviderStatusResponse let fetchPromise: Promise | null = null const FALLBACK_STATUS: ProviderStatus = { githubAvailable: false, googleAvailable: false, microsoftAvailable: false, registrationDisabled: false, } const SOCIAL_BTN = 'relative flex h-[32px] w-full items-center justify-center rounded-[5px] border border-[var(--border-1)] text-[13.5px] text-[var(--text-primary)] transition-colors hover:bg-[var(--surface-hover)] disabled:cursor-not-allowed disabled:opacity-50' function fetchProviderStatus(): Promise { if (fetchPromise) return fetchPromise fetchPromise = requestJson(getAuthProvidersContract, {}) .then(({ githubAvailable, googleAvailable, microsoftAvailable, registrationDisabled }) => ({ githubAvailable, googleAvailable, microsoftAvailable, registrationDisabled, })) .catch(() => { fetchPromise = null return FALLBACK_STATUS }) return fetchPromise } export function AuthModal({ children, defaultView = 'login', source }: AuthModalProps) { const router = useRouter() const [open, setOpen] = useState(false) const [view, setView] = useState(defaultView) const [providerStatus, setProviderStatus] = useState(null) const [socialLoading, setSocialLoading] = useState<'github' | 'google' | 'microsoft' | null>(null) const brand = getBrandConfig() useEffect(() => { fetchProviderStatus().then(setProviderStatus) }, []) const ssoEnabled = isTruthy(getEnv('NEXT_PUBLIC_SSO_ENABLED')) const emailEnabled = !isFalsy(getEnv('NEXT_PUBLIC_EMAIL_PASSWORD_SIGNUP_ENABLED')) /** * Signup is unavailable when registration is disabled, so the shown view is * clamped to login rather than mirrored into state through an effect. */ const effectiveView: AuthView = view === 'signup' && providerStatus?.registrationDisabled ? 'login' : view /** * Tracks whether the visitor still wants the modal open. Cleared on dismiss so a * provider-status fetch that resolves afterwards can't reopen it or re-fire the * opened event. */ const openRequestedRef = useRef(false) function openWithStatus(status: ProviderStatus) { const hasModalContent = status.githubAvailable || status.googleAvailable || status.microsoftAvailable || ssoEnabled if (!hasModalContent) { /** Close the loader (no-op if never opened) and route out; disabled registration sends signup to login. */ setOpen(false) router.push(status.registrationDisabled || defaultView === 'login' ? '/login' : '/signup') return } const initialView: AuthView = defaultView === 'signup' && status.registrationDisabled ? 'login' : defaultView setOpen(true) setView(initialView) captureClientEvent('auth_modal_opened', { view: initialView, source }) } function handleOpenChange(nextOpen: boolean) { if (!nextOpen) { openRequestedRef.current = false setOpen(false) return } if (providerStatus) { openWithStatus(providerStatus) return } /** Status not loaded yet: open the loader immediately for responsiveness, then resolve. */ openRequestedRef.current = true setOpen(true) fetchProviderStatus().then((status) => { setProviderStatus(status) if (!openRequestedRef.current) return /** Consume the request so a queued double-click can't open twice (no duplicate event). */ openRequestedRef.current = false openWithStatus(status) }) } async function handleSocialLogin(provider: 'github' | 'google' | 'microsoft') { setSocialLoading(provider) try { await client.signIn.social({ provider, callbackURL: '/workspace' }) } catch (error) { logger.warn('Social sign-in did not complete', { provider, error }) } finally { setSocialLoading(null) } } function handleSSOLogin() { setOpen(false) router.push('/sso') } function handleEmailContinue() { setOpen(false) router.push(effectiveView === 'login' ? '/login' : '/signup') } return ( {children} {effectiveView === 'login' ? 'Log in' : 'Create account'} {effectiveView === 'login' ? 'Sign in to your account' : 'Create a new account'}
Close {!providerStatus ? (
) : ( <>
{brand.name}

Start building.

{effectiveView === 'login' ? 'Log in to continue' : 'Create free account'}

{providerStatus.googleAvailable && ( )} {providerStatus.microsoftAvailable && ( )} {providerStatus.githubAvailable && ( )} {ssoEnabled && ( )}
{emailEnabled && ( <>
Or
)}
{effectiveView === 'login' ? "Don't have an account? " : 'Already have an account? '} {effectiveView === 'login' && providerStatus.registrationDisabled ? ( Registration is disabled ) : ( )}
)}
) }