Files
scoreodonto.com/frontend/components/GoogleConnectButton.tsx
T
VPS 4 Builder ba062e92d0 fix(login): render standalone views edge-to-edge e esconde scrollbar do carrossel de onboarding
- App.tsx: wrappers de padding (p-4/md:p-8) e max-w-7xl/mx-auto agora só se aplicam quando não é tela standalone; login/landing/public renderizam full-bleed
- OnboardingChat.tsx: classe ob-noscroll esconde a barra de rolagem do slide intro (mantém scroll), substituindo a custom-scrollbar indefinida no fluxo de login

Inclui também demais alterações pendentes do working tree (snapshot do estado atual de dev).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-13 13:23:19 +02:00

122 lines
5.3 KiB
TypeScript

import React, { useState } from 'react';
import { Calendar, Link, CheckCircle2, AlertCircle, Loader2 } from 'lucide-react';
interface GoogleConnectButtonProps {
ownerId: string;
clinicaId?: string;
isConnected: boolean;
onStatusChange?: () => void;
}
export const GoogleConnectButton: React.FC<GoogleConnectButtonProps> = ({ ownerId, clinicaId, isConnected, onStatusChange }) => {
const [isLoading, setIsLoading] = useState(false);
const handleConnect = async () => {
setIsLoading(true);
try {
const params = new URLSearchParams({ ownerId });
if (clinicaId) params.set('clinicaId', clinicaId);
const response = await fetch(`/api/auth/google/url?${params}`);
const data = await response.json();
if (!response.ok || data.error) {
alert(`Erro: ${data.error || 'Falha ao obter URL de autenticação'}`);
setIsLoading(false);
return;
}
const { url } = data;
// Abrir em popup para manter o contexto
const width = 600;
const height = 700;
const left = window.screen.width / 2 - width / 2;
const top = window.screen.height / 2 - height / 2;
const popup = window.open(
url,
'google-auth',
`width=${width},height=${height},left=${left},top=${top}`
);
// Detecção robusta de fechamento. As páginas de login do Google enviam
// Cross-Origin-Opener-Policy, o que BLOQUEIA ler popup.closed (enche o
// console de warning e não detecta o fechamento). Por isso usamos o
// retorno de FOCO à janela principal como sinal de que o popup foi fechado;
// o polling de popup.closed fica só como fallback protegido por try/catch.
let done = false;
const finish = () => {
if (done) return;
done = true;
window.removeEventListener('focus', onFocus);
clearInterval(timer);
setIsLoading(false);
if (onStatusChange) onStatusChange();
};
const onFocus = () => { setTimeout(finish, 600); };
window.addEventListener('focus', onFocus);
const timer = setInterval(() => {
try { if (!popup || popup.closed) finish(); } catch { /* COOP bloqueia: ignora */ }
}, 1000);
} catch (error) {
console.error('Erro ao conectar Google Agenda:', error);
setIsLoading(false);
}
};
const handleDisconnect = async () => {
if (!confirm('Deseja realmente desconectar esta agenda?')) return;
setIsLoading(true);
try {
await fetch(`/api/auth/google/${ownerId}`, { method: 'DELETE' });
if (onStatusChange) onStatusChange();
} catch (error) {
console.error('Erro ao desconectar:', error);
} finally {
setIsLoading(false);
}
};
if (isConnected) {
return (
<div className="flex items-center gap-3 bg-emerald-50 border border-emerald-100 p-4 rounded-2xl">
<div className="p-2 bg-emerald-500 rounded-xl">
<CheckCircle2 size={18} className="text-white" />
</div>
<div className="flex-1">
<p className="text-[10px] font-black text-emerald-800 uppercase tracking-tight">Google Agenda Conectada</p>
<p className="text-[9px] text-emerald-600 font-bold uppercase tracking-widest mt-0.5">Sincronização Ativa</p>
</div>
<button
onClick={handleDisconnect}
disabled={isLoading}
className="text-[9px] font-black text-red-500 hover:text-red-700 uppercase tracking-widest px-3 py-1 bg-white border border-red-100 rounded-lg shadow-sm transition-all"
>
{isLoading ? <Loader2 size={12} className="animate-spin" /> : 'Desconectar'}
</button>
</div>
);
}
return (
<button
onClick={handleConnect}
disabled={isLoading}
className="w-full group relative overflow-hidden flex items-center gap-4 bg-white border-2 border-gray-100 p-4 rounded-2xl hover:border-blue-500 hover:shadow-xl hover:shadow-blue-50/50 transition-all duration-300"
>
<div className="p-3 bg-gray-50 group-hover:bg-blue-600 rounded-xl transition-colors duration-300">
<Calendar size={20} className="text-gray-400 group-hover:text-white transition-colors duration-300" />
</div>
<div className="text-left">
<p className="text-xs font-black text-gray-800 uppercase tracking-tight group-hover:text-blue-600">Conectar Google Agenda</p>
<p className="text-[10px] text-gray-400 font-bold uppercase tracking-widest">Sincronize horários externos</p>
</div>
{isLoading && (
<div className="absolute right-4">
<Loader2 size={18} className="text-blue-600 animate-spin" />
</div>
)}
</button>
);
};