import type { FormEvent } from 'react'; import { useState, useEffect } from 'react'; import { toast } from 'sonner'; import { ChevronDown } from 'lucide-react'; import { Input } from '@/components/ui/input'; import { Label } from '@/components/ui/label'; import { Button } from '@/components/ui/button'; import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '@/components/ui/collapsible'; import { useClient } from 'hooks/useClient'; type GoogleOAuthSettings = { clientId: string; clientSecret: string; }; const SCOPES = [ { scope: 'gmail.readonly', description: 'Read emails' }, { scope: 'calendar.readonly', description: 'Read calendar events' }, ]; const SetupGuide = ({ redirectUri }: { redirectUri: string }) => { const [open, setOpen] = useState(false); return ( Step-by-step setup guide
  1. Create a Google Cloud project

    Go to the{' '} New Project {' '} page. Give it a name (e.g. "Officer") and click Create.

  2. Enable the APIs

    Go to{' '} API Library . Search for and enable each of these:

    • Gmail API
    • Google Calendar API

    Click each one, then click Enable.

  3. Configure the OAuth consent screen

    Go to{' '} OAuth Branding .

    • Set App name to your organization name or "Officer"
    • Set User support email to your admin email
    • Add your admin email under Developer contact information
    • Click Save
  4. Set the audience

    Go to{' '} OAuth Audience .

    • If your team uses Google Workspace, select Internal — no verification needed
    • Otherwise, select External and add your team's emails under Test users{' '} (required while the app is unverified; limit of 100 test users)
  5. Add scopes

    In the left sidebar, click{' '} Data Access , then click Add or remove scopes. Search for and add:

      {SCOPES.map((s) => (
    • {s.scope} —{' '} {s.description}
    • ))}

    Click Update, then Save.

    Note: calendar.readonly{' '} is classified as sensitive and{' '} gmail.readonly as{' '} restricted by Google. This is fine for Internal apps (Google Workspace) and External apps in testing mode. Publishing to production with restricted scopes requires Google verification.

  6. Create OAuth credentials

    In the left sidebar, click{' '} Clients , then click Create OAuth client.

    • Application type: Web application
    • Name: anything (e.g. "Officer")
    • Authorized redirect URIs: add{' '} {redirectUri}
    • Click Create
  7. Copy the credentials

    A dialog will show your Client ID and Client Secret. Copy both and paste them into the fields below.

); }; type VerifyStatus = { valid: boolean; error: string | null } | null; const CredentialStatus = ({ status, isVerifying }: { status: VerifyStatus; isVerifying: boolean }) => { if (isVerifying) { return (
Verifying credentials...
); } if (!status) return null; return (
{status.valid ? 'Credentials valid' : (status.error ?? 'Invalid credentials')}
); }; export const GoogleOAuthConfig = () => { const client = useClient(); const [isLoading, setIsLoading] = useState(true); const [isSaving, setIsSaving] = useState(false); const [clientId, setClientId] = useState(''); const [clientSecret, setClientSecret] = useState(''); const [verifyStatus, setVerifyStatus] = useState(null); const [isVerifying, setIsVerifying] = useState(false); const verify = () => { setIsVerifying(true); client .get<{ valid: boolean; error: string | null }>('/integrations/google/verify') .then(setVerifyStatus) .catch(() => setVerifyStatus({ valid: false, error: 'Verification request failed' })) .finally(() => setIsVerifying(false)); }; useEffect(() => { client .get('/integrations/google/config') .then((data) => { if (data) { setClientId(data.clientId); setClientSecret(data.clientSecret); } }) .catch(() => {}) .finally(() => { setIsLoading(false); }); }, []); // Verify on load if credentials exist useEffect(() => { if (!isLoading && clientId && clientSecret) verify(); }, [isLoading]); const handleSave = async (ev?: FormEvent) => { ev?.preventDefault(); if (isSaving) return; setIsSaving(true); try { await client.put('/integrations/google/config', { clientId: clientId.trim(), clientSecret: clientSecret.trim() }); toast.success('Google OAuth configuration saved'); verify(); } catch { toast.error('Failed to save Google OAuth configuration'); } finally { setIsSaving(false); } }; if (isLoading) return null; const redirectUri = `${window.location.origin}/api/integrations/google/callback`; return (
); };