- 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.
7.9 KiB
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
- ✅ Componente
GoogleCalendarCard.tsxcriado - ✅ Card exibido no dashboard abaixo/ao lado dos cards WhatsApp
- ✅ Card contém: Título "Google Calendar", Botão "Conectar Google Calendar"
- ✅ Botão envia requisição POST para webhook n8n (
N8N_WEBHOOK_GOOGLE_CALENDAR) - ✅ n8n retorna URL OAuth e portal redireciona usuário
- ✅ Card usa tema escuro e cores azuis consistentes
- ✅ 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
GoogleCalendarCardcriado - 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_urldo 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