import { useEffect } from 'react'; import { useForm, Controller, useFormState } from 'react-hook-form'; import { zodResolver } from '@hookform/resolvers/zod'; import { z } from 'zod'; import { ExternalLink } from 'lucide-react'; import { Button, Input, Switch, CopyButton, Dialog, DialogContent, DialogHeader, DialogTitle, DialogFooter, } from '@/components'; import WarningIcon from '@/assets/icons/warning.svg'; import { oAuthConfigSchema, OAuthConfigSchema, OAuthProvidersSchema, } from '@insforge/shared-schemas'; import { type OAuthProviderInfo } from '../helpers'; import { useOAuthConfig } from '@/features/auth/hooks/useOAuthConfig'; import { getBackendUrl, isInsForgeCloudProject } from '@/lib/utils/utils'; const getCallbackUrl = (provider?: string) => { // Use backend API URL for OAuth callback let backendUrl = getBackendUrl(); // Check if backend URL contains "localhost" and provider is "x" if (provider === 'x' && backendUrl.includes('localhost')) { backendUrl = backendUrl.replace('://localhost', '://www.localhost'); } return `${backendUrl}/api/auth/oauth/${provider}/callback`; }; interface OAuthConfigDialogProps { provider?: OAuthProviderInfo; isOpen: boolean; onClose: () => void; onSuccess?: () => void; } export function OAuthConfigDialog({ provider, isOpen, onClose, onSuccess, }: OAuthConfigDialogProps) { const { configs, providerConfig, createConfig, updateConfig, isCreating, isUpdating, setSelectedProvider, isLoadingProvider, } = useOAuthConfig(); const form = useForm({ resolver: zodResolver(oAuthConfigSchema.extend({ clientSecret: z.string().optional() })), defaultValues: { provider: provider?.id || 'google', clientId: '', clientSecret: '', useSharedKey: false, }, }); const useSharedKey = form.watch('useSharedKey'); const clientId = form.watch('clientId'); const clientSecret = form.watch('clientSecret'); // Our Cloud only support shared keys of these OAuth Providers for now const sharedKeyProviders: readonly OAuthProvidersSchema[] = [ 'google', 'github', 'discord', 'linkedin', 'facebook', ] satisfies readonly OAuthProvidersSchema[]; const isSharedKeysAvailable = isInsForgeCloudProject() && provider?.id && sharedKeyProviders.includes(provider.id); // Use useFormState hook for better reactivity const { isDirty } = useFormState({ control: form.control, }); // Set selected provider and refetch when dialog opens useEffect(() => { if (isOpen && provider) { setSelectedProvider(provider.id); } }, [configs, isOpen, provider, setSelectedProvider]); // Load OAuth configuration after fetching useEffect(() => { if (isOpen && provider && !isLoadingProvider) { if (providerConfig) { form.reset({ provider: provider.id, clientId: providerConfig.clientId || '', clientSecret: providerConfig.clientSecret || '', useSharedKey: providerConfig.useSharedKey || false, }); } else { form.reset({ provider: provider.id, clientId: '', clientSecret: '', useSharedKey: isSharedKeysAvailable, }); } } }, [form, isLoadingProvider, isOpen, isSharedKeysAvailable, provider, providerConfig]); const handleSubmitData = (data: OAuthConfigSchema & { clientSecret?: string }) => { if (!provider) { return; } try { if (providerConfig) { // Update existing config updateConfig({ provider: provider.id, config: data.useSharedKey ? { useSharedKey: true } : { clientId: data.clientId, clientSecret: data.clientSecret, useSharedKey: false, }, }); } else { // Create new config createConfig({ provider: provider.id, clientId: data.useSharedKey ? undefined : data.clientId, clientSecret: data.useSharedKey ? undefined : clientSecret, useSharedKey: data.useSharedKey, }); } // Call success callback if provided if (onSuccess) { onSuccess(); } // Close dialog onClose(); } catch (error) { console.error('Error saving OAuth config:', error); } }; const handleSubmit = () => { void handleSubmitData(form.getValues()); }; const saving = isCreating || isUpdating; // Use RHF's built-in validation and dirty state const isDisabled = () => { if (saving) { return true; } // In update mode, require dirty state if (providerConfig && !isDirty) { return true; } // If using shared keys, always allow (no credential validation needed) if (useSharedKey) { return false; } // If NOT using shared keys, require both clientId and clientSecret return !clientId || !clientSecret; }; return ( !open && onClose()}> {provider?.name} {isLoadingProvider ? (
Loading OAuth configuration...
) : ( <>
e.preventDefault()} className="flex flex-col">
{/* Shared Keys Toggle */} {isSharedKeysAvailable && (
{ return ( { field.onChange(value); }} /> ); }} /> Shared Keys
)} {useSharedKey ? ( /* Shared Keys Enabled */

Shared keys are created by the InsForge team for development. It helps you get started, but will show a InsForge logo and name on the OAuth screen.

Warning Shared keys should never be used in production
) : ( /* Shared Keys Disabled */
Create a {provider?.name.split(' ')[0]} OAuth App {' '} and set the callback url to:
{getCallbackUrl(provider?.id)}
)}
{!useSharedKey && (
)}
)}
); }