Dashboard-Automatizase/docs/stories/story-3-1-criar-card-google-calendar.md
Luis Erlacher 0152a2fda0 feat: add n8n API testing script for Google OAuth2 schema and existing credentials
- Implemented a bash script to test n8n API and retrieve credential schemas.
- Added types for API responses, Google Calendar, and WhatsApp instances.
- Configured Vitest for testing with React and added setup for testing-library.
2025-10-10 14:29:02 -03:00

7.9 KiB

Story 3.1: Criar Card/Botão OAuth do Google Calendar

Story Metadata

  • Epic: Epic 3 - Google Calendar OAuth Integration
  • Story ID: 3.1
  • Priority: P1 (High)
  • Effort Estimate: 1-2 hours
  • Status: Not Started
  • Assignee: TBD

User Story

Como usuário, Eu quero ver um card/seção para Google Calendar com botão de autorização, Para que eu possa iniciar o processo de OAuth.

Acceptance Criteria

  1. Componente GoogleCalendarCard.tsx criado
  2. Card exibido no dashboard abaixo/ao lado dos cards WhatsApp
  3. Card contém: Título "Google Calendar", Botão "Conectar Google Calendar"
  4. Botão envia requisição POST para webhook n8n (N8N_WEBHOOK_GOOGLE_CALENDAR)
  5. n8n retorna URL OAuth e portal redireciona usuário
  6. Card usa tema escuro e cores azuis consistentes
  7. Card é responsivo

Technical Implementation Notes

Google Calendar Card Component

Criar arquivo /components/GoogleCalendarCard.tsx:

'use client'

interface GoogleCalendarCardProps {
  isConnected: boolean
  connectedEmail?: string | null
  onConnect: () => void
  loading?: boolean
}

export default function GoogleCalendarCard({
  isConnected,
  connectedEmail,
  onConnect,
  loading = false,
}: GoogleCalendarCardProps) {
  return (
    <div className="bg-gray-800 border border-gray-700 rounded-lg p-6 hover:border-primary-500 transition-colors">
      {/* Header */}
      <div className="flex items-center justify-between mb-4">
        <div className="flex items-center gap-3">
          {/* Google Calendar Icon */}
          <svg className="w-8 h-8 text-primary-400" fill="currentColor" viewBox="0 0 24 24">
            <path d="M19 4h-1V2h-2v2H8V2H6v2H5c-1.11 0-1.99.9-1.99 2L3 20c0 1.1.89 2 2 2h14c1.1 0 2-.9 2-2V6c0-1.1-.9-2-2-2zm0 16H5V10h14v10zm0-12H5V6h14v2z"/>
          </svg>
          <h3 className="text-lg font-semibold text-white">Google Calendar</h3>
        </div>
        <span
          className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
            isConnected ? 'bg-green-500 text-white' : 'bg-gray-600 text-gray-300'
          }`}
        >
          {isConnected ? '✅ Conectado' : '❌ Não conectado'}
        </span>
      </div>

      {/* Connected Email (if connected) */}
      {isConnected && connectedEmail && (
        <div className="mb-4 p-3 bg-gray-700/50 rounded border border-gray-600">
          <p className="text-sm text-gray-400">Conta conectada:</p>
          <p className="text-sm text-white font-medium">{connectedEmail}</p>
        </div>
      )}

      {/* Description */}
      <p className="text-gray-400 text-sm mb-4">
        {isConnected
          ? 'Sua integração com Google Calendar está ativa. Você pode reconectar com uma conta diferente se necessário.'
          : 'Conecte sua conta do Google Calendar para habilitar integrações de agenda.'}
      </p>

      {/* Action Button */}
      <button
        onClick={onConnect}
        disabled={loading}
        className="w-full px-4 py-2 bg-primary-600 hover:bg-primary-700 text-white text-sm font-medium rounded-md transition-colors disabled:opacity-50 flex items-center justify-center gap-2"
      >
        {loading && (
          <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
        )}
        {isConnected ? 'Reconectar' : 'Conectar Google Calendar'}
      </button>
    </div>
  )
}

Update Dashboard to Include Google Calendar Card

Atualizar /app/dashboard/page.tsx (adicionar seção Google Calendar):

// ... (imports existentes)
import GoogleCalendarCard from '@/components/GoogleCalendarCard'

export default function DashboardPage() {
  // ... (state existente)

  // Google Calendar State
  const [calendarConnected, setCalendarConnected] = useState(false)
  const [calendarEmail, setCalendarEmail] = useState<string | null>(null)

  useEffect(() => {
    // ... (código existente)

    // Load Google Calendar status (will be implemented in Story 3.3)
    loadGoogleCalendarStatus()
  }, [])

  const loadGoogleCalendarStatus = async () => {
    // Placeholder - will be implemented in Story 3.3
    // Check portal.integrations table for google_calendar status
    try {
      const { data: { user } } = await supabase.auth.getUser()
      if (user) {
        const { data } = await supabase
          .from('portal.integrations')
          .select('status')
          .eq('user_id', user.id)
          .eq('provider', 'google_calendar')
          .single()

        if (data) {
          setCalendarConnected(data.status === 'connected')
        }
      }
    } catch (error) {
      console.error('Error loading Google Calendar status:', error)
    }
  }

  const handleConnectGoogleCalendar = async () => {
    try {
      setCalendarLoading(true)

      const { data: { user } } = await supabase.auth.getUser()
      if (!user) {
        throw new Error('User not authenticated')
      }

      const n8nWebhookUrl = process.env.NEXT_PUBLIC_N8N_WEBHOOK_GOOGLE_CALENDAR
      if (!n8nWebhookUrl) {
        console.error('N8N_WEBHOOK_GOOGLE_CALENDAR not configured')
        setToastMessage('Erro de configuração. Contate o administrador.')
        setToastType('error')
        setToastVisible(true)
        return
      }

      // Send webhook to n8n
      const response = await fetch(n8nWebhookUrl, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          user_id: user.id,
          user_email: user.email,
          redirect_uri: `${window.location.origin}/dashboard`
        })
      })

      const data = await response.json()

      if (data.oauth_url) {
        // Redirect to Google OAuth
        window.location.href = data.oauth_url
      } else {
        throw new Error('Failed to initiate OAuth flow')
      }
    } catch (error) {
      console.error('Error connecting Google Calendar:', error)
      setToastMessage('Erro ao conectar Google Calendar. Tente novamente.')
      setToastType('error')
      setToastVisible(true)
    } finally {
      setCalendarLoading(false)
    }
  }

  return (
    <>
      <div className="space-y-6">
        {/* Welcome Section */}
        {/* ... (código existente) */}

        {/* WhatsApp Instances Section */}
        {/* ... (código existente) */}

        {/* Google Calendar Section */}
        <div>
          <h3 className="text-xl font-semibold text-white mb-4">Integrações</h3>
          <GoogleCalendarCard
            isConnected={calendarConnected}
            connectedEmail={calendarEmail}
            onConnect={handleConnectGoogleCalendar}
          />
        </div>
      </div>

      {/* Modals and Toasts */}
      {/* ... (código existente) */}
    </>
  )
}

Environment Variables

Adicionar ao .env.local:

NEXT_PUBLIC_N8N_WEBHOOK_GOOGLE_CALENDAR=https://n8n.automatizase.com.br/webhook/google-calendar-sync

Importante: Referir-se a docs/n8n-webhook-google-calendar.md para detalhes completos de implementação do webhook n8n.

Testing Checklist

  • Componente GoogleCalendarCard criado
  • Card exibido no dashboard
  • Título e ícone do Google Calendar visíveis
  • Status badge exibe "Não conectado" inicialmente
  • Botão "Conectar Google Calendar" funciona
  • Clicar no botão envia POST para webhook n8n
  • n8n retorna JSON com oauth_url
  • Portal redireciona para oauth_url do Google
  • Card usa tema escuro e cores azuis
  • Card é responsivo

Dependencies

  • Blocks: Story 3.2, 3.3
  • Blocked By: Epic 1 (precisa de dashboard)

Notes

  • Status de conexão será implementado na Story 3.3
  • OAuth flow completo será gerenciado pelo n8n (Story 3.2)
  • n8n webhook deve retornar JSON: { "status": "processing", "oauth_url": "https://accounts.google.com/..." }
  • Portal envia via POST: { "user_id": "uuid", "user_email": "email@example.com", "redirect_uri": "https://portal.../dashboard" }
  • Documentação completa do webhook: docs/n8n-webhook-google-calendar.md