Fix: Remove WhatsApp instance dependency.
Removed WhatsApp instance connection requirement for group creation. Removed "Reconectar WhatsApp" button. Fixed Google sign-in redirect issue to complete profile and then dashboard. Added password recovery option.
This commit is contained in:
parent
05a8186a19
commit
3a1bc74f34
@ -1,4 +1,3 @@
|
|||||||
|
|
||||||
import { Toaster } from "@/components/ui/sonner";
|
import { Toaster } from "@/components/ui/sonner";
|
||||||
import { TooltipProvider } from "@/components/ui/tooltip";
|
import { TooltipProvider } from "@/components/ui/tooltip";
|
||||||
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
|
||||||
@ -8,6 +7,7 @@ import { supabase } from "@/integrations/supabase/client";
|
|||||||
import { useAuthStore } from "@/stores/authStore";
|
import { useAuthStore } from "@/stores/authStore";
|
||||||
import ProtectedRoute from "@/components/auth/ProtectedRoute";
|
import ProtectedRoute from "@/components/auth/ProtectedRoute";
|
||||||
import Layout from "@/components/layout/Layout";
|
import Layout from "@/components/layout/Layout";
|
||||||
|
import ResetPassword from '@/pages/ResetPassword';
|
||||||
|
|
||||||
// Pages
|
// Pages
|
||||||
import Index from "./pages/Index";
|
import Index from "./pages/Index";
|
||||||
@ -61,6 +61,7 @@ function App() {
|
|||||||
<Route path="/" element={<Landing />} />
|
<Route path="/" element={<Landing />} />
|
||||||
<Route path="/auth" element={<Auth />} />
|
<Route path="/auth" element={<Auth />} />
|
||||||
<Route path="/email-confirmation" element={<EmailConfirmation />} />
|
<Route path="/email-confirmation" element={<EmailConfirmation />} />
|
||||||
|
<Route path="/reset-password" element={<ResetPassword />} />
|
||||||
|
|
||||||
{/* Rotas protegidas */}
|
{/* Rotas protegidas */}
|
||||||
<Route path="/" element={<ProtectedRoute><Layout><Outlet /></Layout></ProtectedRoute>}>
|
<Route path="/" element={<ProtectedRoute><Layout><Outlet /></Layout></ProtectedRoute>}>
|
||||||
|
|||||||
121
src/components/auth/ForgotPasswordForm.tsx
Normal file
121
src/components/auth/ForgotPasswordForm.tsx
Normal file
@ -0,0 +1,121 @@
|
|||||||
|
|
||||||
|
import { useState } from 'react';
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { ArrowLeft, Mail, CheckCircle2 } from "lucide-react";
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
|
||||||
|
interface ForgotPasswordFormProps {
|
||||||
|
onBackToLogin: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ForgotPasswordForm = ({ onBackToLogin }: ForgotPasswordFormProps) => {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [emailSent, setEmailSent] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { error } = await supabase.auth.resetPasswordForEmail(email, {
|
||||||
|
redirectTo: `${window.location.origin}/reset-password`,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError(error.message);
|
||||||
|
} else {
|
||||||
|
setEmailSent(true);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError('Erro inesperado. Tente novamente.');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (emailSent) {
|
||||||
|
return (
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="space-y-1 text-center">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<CheckCircle2 className="h-12 w-12 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-2xl">Email enviado!</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Enviamos um link para redefinir sua senha para <strong>{email}</strong>
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent className="space-y-4">
|
||||||
|
<div className="bg-blue-50 p-4 rounded-lg">
|
||||||
|
<p className="text-sm text-blue-800">
|
||||||
|
Verifique sua caixa de entrada e spam. O link expira em 1 hora.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<Button onClick={onBackToLogin} variant="outline" className="w-full">
|
||||||
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
|
Voltar ao login
|
||||||
|
</Button>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="space-y-1 text-center">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<Mail className="h-12 w-12 text-blue-500" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-2xl">Esqueceu sua senha?</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Digite seu email e enviaremos um link para redefinir sua senha
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
|
<Input
|
||||||
|
id="email"
|
||||||
|
type="email"
|
||||||
|
placeholder="seu@email.com"
|
||||||
|
value={email}
|
||||||
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||||
|
{isLoading ? "Enviando..." : "Enviar link de recuperação"}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="ghost"
|
||||||
|
onClick={onBackToLogin}
|
||||||
|
className="w-full"
|
||||||
|
>
|
||||||
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
|
Voltar ao login
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ForgotPasswordForm;
|
||||||
@ -1,73 +1,117 @@
|
|||||||
|
|
||||||
import { useState } from 'react';
|
import { useState } from 'react';
|
||||||
import { useNavigate } from 'react-router-dom';
|
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { supabase } from "@/integrations/supabase/client";
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { AlertCircle } from "lucide-react";
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface LoginFormProps {
|
interface LoginFormProps {
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
setIsLoading: (isLoading: boolean) => void;
|
setIsLoading: (loading: boolean) => void;
|
||||||
|
onForgotPassword: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LoginForm = ({ isLoading, setIsLoading }: LoginFormProps) => {
|
const LoginForm = ({ isLoading, setIsLoading, onForgotPassword }: LoginFormProps) => {
|
||||||
|
const [email, setEmail] = useState('');
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [error, setError] = useState('');
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const [loginEmail, setLoginEmail] = useState('');
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
const [loginSenha, setLoginSenha] = useState('');
|
|
||||||
|
|
||||||
const handleLogin = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
const { error } = await supabase.auth.signInWithPassword({
|
try {
|
||||||
email: loginEmail,
|
const { data, error } = await supabase.auth.signInWithPassword({
|
||||||
password: loginSenha,
|
email: email.trim().toLowerCase(),
|
||||||
});
|
password: password,
|
||||||
|
|
||||||
setIsLoading(false);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
toast.error("Erro no login", {
|
|
||||||
description: "Email ou senha incorretos. Verifique seus dados."
|
|
||||||
});
|
});
|
||||||
} else {
|
|
||||||
toast.success("Login realizado com sucesso", {
|
if (error) {
|
||||||
description: "Bem-vindo de volta!"
|
console.error('Erro no login:', error);
|
||||||
});
|
|
||||||
// Redirecionar para o dashboard após login bem-sucedido
|
if (error.message.includes('Invalid login credentials')) {
|
||||||
navigate('/dashboard');
|
setError('Email ou senha incorretos. Verifique suas credenciais.');
|
||||||
|
} else if (error.message.includes('Email not confirmed')) {
|
||||||
|
setError('Email não confirmado. Verifique sua caixa de entrada.');
|
||||||
|
} else {
|
||||||
|
setError(error.message);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.user) {
|
||||||
|
console.log('✅ Login realizado com sucesso:', data.user.email);
|
||||||
|
|
||||||
|
toast.success("Login realizado com sucesso!", {
|
||||||
|
description: `Bem-vindo, ${data.user.email}!`,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Redirecionar para dashboard
|
||||||
|
navigate('/');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Erro inesperado no login:', error);
|
||||||
|
setError('Erro inesperado. Tente novamente.');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<form onSubmit={handleLogin} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="email">Email</Label>
|
||||||
<Input
|
<Input
|
||||||
id="login-email"
|
id="email"
|
||||||
placeholder="Email"
|
|
||||||
type="email"
|
type="email"
|
||||||
value={loginEmail}
|
placeholder="seu@email.com"
|
||||||
onChange={(e) => setLoginEmail(e.target.value)}
|
value={email}
|
||||||
disabled={isLoading}
|
onChange={(e) => setEmail(e.target.value)}
|
||||||
required
|
required
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password">Senha</Label>
|
||||||
<Input
|
<Input
|
||||||
id="login-senha"
|
id="password"
|
||||||
placeholder="Senha"
|
|
||||||
type="password"
|
type="password"
|
||||||
value={loginSenha}
|
placeholder="Sua senha"
|
||||||
onChange={(e) => setLoginSenha(e.target.value)}
|
value={password}
|
||||||
disabled={isLoading}
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
required
|
required
|
||||||
|
disabled={isLoading}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertCircle className="h-4 w-4" />
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
<Button type="submit" className="w-full" disabled={isLoading}>
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||||
{isLoading ? "Entrando..." : "Entrar"}
|
{isLoading ? "Entrando..." : "Entrar"}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
|
<div className="text-center">
|
||||||
|
<Button
|
||||||
|
type="button"
|
||||||
|
variant="link"
|
||||||
|
onClick={onForgotPassword}
|
||||||
|
className="text-sm text-muted-foreground hover:text-primary"
|
||||||
|
>
|
||||||
|
Esqueceu sua senha?
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
</form>
|
</form>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@ -18,65 +18,96 @@ const ProtectedRoute = ({ children }: ProtectedRouteProps) => {
|
|||||||
|
|
||||||
const setLoggedIn = useAuthStore((state) => state.setLoggedIn);
|
const setLoggedIn = useAuthStore((state) => state.setLoggedIn);
|
||||||
const setUser = useAuthStore((state) => state.setUser);
|
const setUser = useAuthStore((state) => state.setUser);
|
||||||
|
const setSession = useAuthStore((state) => state.setSession);
|
||||||
const isProfileComplete = useAuthStore((state) => state.isProfileComplete);
|
const isProfileComplete = useAuthStore((state) => state.isProfileComplete);
|
||||||
|
|
||||||
// Hook para verificar completude do perfil
|
// Hook para verificar completude do perfil
|
||||||
const { isChecking } = useProfileCompletion(session?.user?.email || '');
|
const { isChecking } = useProfileCompletion(session?.user?.email || '');
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
|
let mounted = true;
|
||||||
|
|
||||||
// Verificar sessão inicial
|
// Verificar sessão inicial
|
||||||
supabase.auth.getSession().then(({ data: { session } }) => {
|
const getInitialSession = async () => {
|
||||||
console.log('🔐 [PROTECTED_ROUTE] Sessão inicial:', session?.user?.email);
|
try {
|
||||||
setSession(session);
|
const { data: { session }, error } = await supabase.auth.getSession();
|
||||||
setLoggedIn(!!session);
|
|
||||||
setUser(session?.user ? { id: session.user.id } : null);
|
|
||||||
|
|
||||||
if (session?.user?.email) {
|
|
||||||
localStorage.setItem('userEmail', session.user.email);
|
|
||||||
console.log('👤 [PROTECTED_ROUTE] Email salvo no localStorage:', session.user.email);
|
|
||||||
|
|
||||||
// Disparar evento customizado para notificar outros componentes sobre o login
|
if (error) {
|
||||||
window.dispatchEvent(new CustomEvent('userLoggedIn', {
|
console.error('🔐 [PROTECTED_ROUTE] Erro ao obter sessão:', error);
|
||||||
detail: { email: session.user.email }
|
}
|
||||||
}));
|
|
||||||
} else {
|
if (mounted) {
|
||||||
localStorage.removeItem('userEmail');
|
console.log('🔐 [PROTECTED_ROUTE] Sessão inicial:', session?.user?.email || 'Nenhuma sessão');
|
||||||
}
|
setSession(session);
|
||||||
|
setLoggedIn(!!session);
|
||||||
setIsLoading(false);
|
setUser(session?.user ? { id: session.user.id } : null);
|
||||||
});
|
|
||||||
|
|
||||||
// Listener para mudanças de auth
|
|
||||||
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
|
||||||
(_event, session) => {
|
|
||||||
console.log('🔄 [PROTECTED_ROUTE] Mudança de auth:', _event, session?.user?.email);
|
|
||||||
setSession(session);
|
|
||||||
setLoggedIn(!!session);
|
|
||||||
setUser(session?.user ? { id: session.user.id } : null);
|
|
||||||
|
|
||||||
if (session?.user?.email) {
|
|
||||||
localStorage.setItem('userEmail', session.user.email);
|
|
||||||
console.log('👤 [PROTECTED_ROUTE] Email atualizado no localStorage:', session.user.email);
|
|
||||||
|
|
||||||
// Disparar evento customizado para notificar sobre o login
|
if (session?.user?.email) {
|
||||||
if (_event === 'SIGNED_IN') {
|
localStorage.setItem('userEmail', session.user.email);
|
||||||
console.log('🎉 [PROTECTED_ROUTE] Login detectado, disparando evento');
|
console.log('👤 [PROTECTED_ROUTE] Email salvo no localStorage:', session.user.email);
|
||||||
|
|
||||||
|
// Disparar evento customizado para notificar outros componentes sobre o login
|
||||||
window.dispatchEvent(new CustomEvent('userLoggedIn', {
|
window.dispatchEvent(new CustomEvent('userLoggedIn', {
|
||||||
detail: { email: session.user.email }
|
detail: { email: session.user.email }
|
||||||
}));
|
}));
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('userEmail');
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
localStorage.removeItem('userEmail');
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('🔐 [PROTECTED_ROUTE] Erro inesperado ao obter sessão:', error);
|
||||||
|
if (mounted) {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
setIsLoading(false);
|
getInitialSession();
|
||||||
|
|
||||||
|
// Listener para mudanças de auth com debounce
|
||||||
|
let timeoutId: NodeJS.Timeout;
|
||||||
|
|
||||||
|
const { data: { subscription } } = supabase.auth.onAuthStateChange(
|
||||||
|
(event, session) => {
|
||||||
|
console.log('🔄 [PROTECTED_ROUTE] Mudança de auth:', event, session?.user?.email || 'Sem sessão');
|
||||||
|
|
||||||
|
// Debounce para evitar múltiplas atualizações
|
||||||
|
clearTimeout(timeoutId);
|
||||||
|
timeoutId = setTimeout(() => {
|
||||||
|
if (mounted) {
|
||||||
|
setSession(session);
|
||||||
|
setLoggedIn(!!session);
|
||||||
|
setUser(session?.user ? { id: session.user.id } : null);
|
||||||
|
|
||||||
|
if (session?.user?.email) {
|
||||||
|
localStorage.setItem('userEmail', session.user.email);
|
||||||
|
console.log('👤 [PROTECTED_ROUTE] Email atualizado no localStorage:', session.user.email);
|
||||||
|
|
||||||
|
// Disparar evento customizado para notificar sobre o login
|
||||||
|
if (event === 'SIGNED_IN') {
|
||||||
|
console.log('🎉 [PROTECTED_ROUTE] Login detectado, disparando evento');
|
||||||
|
window.dispatchEvent(new CustomEvent('userLoggedIn', {
|
||||||
|
detail: { email: session.user.email }
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
localStorage.removeItem('userEmail');
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
}, 100); // Debounce de 100ms
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
mounted = false;
|
||||||
|
clearTimeout(timeoutId);
|
||||||
subscription.unsubscribe();
|
subscription.unsubscribe();
|
||||||
};
|
};
|
||||||
}, [setLoggedIn, setUser]);
|
}, [setLoggedIn, setUser, setSession]);
|
||||||
|
|
||||||
// Mostrar loading enquanto verifica auth ou perfil
|
// Mostrar loading enquanto verifica auth ou perfil
|
||||||
if (isLoading || isChecking) {
|
if (isLoading || isChecking) {
|
||||||
|
|||||||
@ -14,22 +14,22 @@ const SocialLoginButtons = () => {
|
|||||||
console.log('🔐 Iniciando login com Google...');
|
console.log('🔐 Iniciando login com Google...');
|
||||||
console.log('🌐 URL atual:', window.location.origin);
|
console.log('🌐 URL atual:', window.location.origin);
|
||||||
|
|
||||||
// Determinar a URL de redirecionamento correta baseada no hostname - SEMPRE para /dashboard
|
// Determinar a URL de redirecionamento correta baseada no hostname
|
||||||
let redirectUrl;
|
let redirectUrl;
|
||||||
const hostname = window.location.hostname;
|
const hostname = window.location.hostname;
|
||||||
|
|
||||||
if (hostname === 'localhost') {
|
if (hostname === 'localhost') {
|
||||||
redirectUrl = 'http://localhost:3000/dashboard';
|
redirectUrl = 'http://localhost:3000/';
|
||||||
} else if (hostname.includes('financehome.innova1001.com.br')) {
|
} else if (hostname.includes('financehome.innova1001.com.br')) {
|
||||||
redirectUrl = 'https://financehome.innova1001.com.br/dashboard';
|
redirectUrl = 'https://financehome.innova1001.com.br/';
|
||||||
} else if (hostname.includes('lovableproject.com')) {
|
} else if (hostname.includes('lovableproject.com')) {
|
||||||
redirectUrl = `${window.location.origin}/dashboard`;
|
redirectUrl = `${window.location.origin}/`;
|
||||||
} else {
|
} else {
|
||||||
// Fallback para qualquer outro domínio - sempre para dashboard
|
// Fallback para qualquer outro domínio
|
||||||
redirectUrl = `${window.location.origin}/dashboard`;
|
redirectUrl = `${window.location.origin}/`;
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('🔗 URL de redirecionamento (DASHBOARD):', redirectUrl);
|
console.log('🔗 URL de redirecionamento:', redirectUrl);
|
||||||
|
|
||||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||||
provider: 'google',
|
provider: 'google',
|
||||||
@ -37,7 +37,7 @@ const SocialLoginButtons = () => {
|
|||||||
redirectTo: redirectUrl,
|
redirectTo: redirectUrl,
|
||||||
queryParams: {
|
queryParams: {
|
||||||
access_type: 'offline',
|
access_type: 'offline',
|
||||||
prompt: 'consent',
|
prompt: 'select_account',
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -51,6 +51,7 @@ const SocialLoginButtons = () => {
|
|||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
console.log('✅ Redirecionamento iniciado...');
|
console.log('✅ Redirecionamento iniciado...');
|
||||||
|
// Não fazer nada aqui, deixar o redirecionamento acontecer naturalmente
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('❌ Erro geral no login com Google:', error);
|
console.error('❌ Erro geral no login com Google:', error);
|
||||||
|
|||||||
@ -1,14 +1,12 @@
|
|||||||
|
|
||||||
import { useEffect, useState } from 'react';
|
import { useEffect, useState } from 'react';
|
||||||
import { useExistingInstanceCheck } from '@/hooks/whatsapp/useExistingInstanceCheck';
|
|
||||||
import { useGroupCreation } from '@/hooks/whatsappGroups/useGroupCreation';
|
|
||||||
import { listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
|
import { listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
|
||||||
import LoadingState from './LoadingState';
|
import LoadingState from './LoadingState';
|
||||||
import NoInstanceState from './NoInstanceState';
|
|
||||||
import GroupCreationForm from './GroupCreationForm';
|
import GroupCreationForm from './GroupCreationForm';
|
||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
||||||
import { Info, Loader2 } from 'lucide-react';
|
import { Info } from 'lucide-react';
|
||||||
|
import { useGroupCreation } from '@/hooks/whatsappGroups/useGroupCreation';
|
||||||
|
|
||||||
interface CreateGroupFormProps {
|
interface CreateGroupFormProps {
|
||||||
userEmail: string;
|
userEmail: string;
|
||||||
@ -20,13 +18,6 @@ const CreateGroupForm = ({ userEmail, onSuccess }: CreateGroupFormProps) => {
|
|||||||
const [jaTemGrupo, setJaTemGrupo] = useState(false);
|
const [jaTemGrupo, setJaTemGrupo] = useState(false);
|
||||||
const [grupoExistente, setGrupoExistente] = useState<any>(null);
|
const [grupoExistente, setGrupoExistente] = useState<any>(null);
|
||||||
|
|
||||||
const {
|
|
||||||
hasExistingInstance,
|
|
||||||
checkingExistingInstance,
|
|
||||||
existingInstanceData,
|
|
||||||
recheckInstance
|
|
||||||
} = useExistingInstanceCheck(userEmail);
|
|
||||||
|
|
||||||
const { cadastrando, handleCadastrarGrupo } = useGroupCreation(userEmail, onSuccess);
|
const { cadastrando, handleCadastrarGrupo } = useGroupCreation(userEmail, onSuccess);
|
||||||
|
|
||||||
// Verificar se o usuário já tem grupos cadastrados
|
// Verificar se o usuário já tem grupos cadastrados
|
||||||
@ -56,12 +47,11 @@ const CreateGroupForm = ({ userEmail, onSuccess }: CreateGroupFormProps) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
recheckInstance();
|
|
||||||
verificarGruposExistentes();
|
verificarGruposExistentes();
|
||||||
}, [userEmail]);
|
}, [userEmail]);
|
||||||
|
|
||||||
if (checkingExistingInstance || verificandoGrupos) {
|
if (verificandoGrupos) {
|
||||||
return <LoadingState message="Verificando instância WhatsApp e grupos existentes..." />;
|
return <LoadingState message="Verificando grupos existentes..." />;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se já tem grupo, mostrar informação
|
// Se já tem grupo, mostrar informação
|
||||||
@ -93,18 +83,14 @@ const CreateGroupForm = ({ userEmail, onSuccess }: CreateGroupFormProps) => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se não tem instância conectada, mostrar estado de sem instância
|
|
||||||
if (!hasExistingInstance) {
|
|
||||||
return <NoInstanceState userInstance={existingInstanceData} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
const handleSubmit = (nomeGrupo: string) => {
|
const handleSubmit = (nomeGrupo: string) => {
|
||||||
handleCadastrarGrupo(nomeGrupo, existingInstanceData);
|
// Agora pode criar grupo sem verificar instância
|
||||||
|
handleCadastrarGrupo(nomeGrupo, { whatsapp: userEmail });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<GroupCreationForm
|
<GroupCreationForm
|
||||||
userInstance={existingInstanceData!}
|
userInstance={{ instancia_zap: userEmail, status_instancia: 'ativo', whatsapp: userEmail }}
|
||||||
userEmail={userEmail}
|
userEmail={userEmail}
|
||||||
cadastrando={cadastrando}
|
cadastrando={cadastrando}
|
||||||
onSubmit={handleSubmit}
|
onSubmit={handleSubmit}
|
||||||
|
|||||||
@ -7,7 +7,7 @@ import { Label } from '@/components/ui/label';
|
|||||||
import { Alert, AlertDescription } from '@/components/ui/alert';
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
||||||
import { Loader2, AlertCircle, CheckCircle2, Info } from 'lucide-react';
|
import { Loader2, AlertCircle, CheckCircle2, Info } from 'lucide-react';
|
||||||
import { useToast } from '@/hooks/use-toast';
|
import { useToast } from '@/hooks/use-toast';
|
||||||
import { verificarInstanciaWhatsApp, listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
|
import { listarGruposWhatsApp } from '@/services/gruposWhatsAppService';
|
||||||
import { findOrCreateWhatsAppGroup } from '@/services/whatsAppGroupsService';
|
import { findOrCreateWhatsAppGroup } from '@/services/whatsAppGroupsService';
|
||||||
import { createWorkflowInN8n } from '@/services/n8nWorkflowService';
|
import { createWorkflowInN8n } from '@/services/n8nWorkflowService';
|
||||||
import { createEvolutionWebhook } from '@/services/whatsApp/webhookService';
|
import { createEvolutionWebhook } from '@/services/whatsApp/webhookService';
|
||||||
@ -25,8 +25,6 @@ const CreateGroupFormSimple = ({ userEmail, onSuccess }: CreateGroupFormProps) =
|
|||||||
const [verificandoGrupos, setVerificandoGrupos] = useState(true);
|
const [verificandoGrupos, setVerificandoGrupos] = useState(true);
|
||||||
const [jaTemGrupo, setJaTemGrupo] = useState(false);
|
const [jaTemGrupo, setJaTemGrupo] = useState(false);
|
||||||
const [grupoExistente, setGrupoExistente] = useState<any>(null);
|
const [grupoExistente, setGrupoExistente] = useState<any>(null);
|
||||||
const [webhookEnviado, setWebhookEnviado] = useState(false);
|
|
||||||
const [workflowAtivado, setWorkflowAtivado] = useState(false);
|
|
||||||
const [mensagemStatus, setMensagemStatus] = useState<{
|
const [mensagemStatus, setMensagemStatus] = useState<{
|
||||||
tipo: 'info' | 'success' | 'error';
|
tipo: 'info' | 'success' | 'error';
|
||||||
texto: string;
|
texto: string;
|
||||||
@ -85,23 +83,8 @@ const CreateGroupFormSimple = ({ userEmail, onSuccess }: CreateGroupFormProps) =
|
|||||||
|
|
||||||
setCarregando(true);
|
setCarregando(true);
|
||||||
setMensagemStatus(null);
|
setMensagemStatus(null);
|
||||||
setWebhookEnviado(false);
|
|
||||||
setWorkflowAtivado(false);
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Verificar se o usuário tem instância WhatsApp
|
|
||||||
setMensagemStatus({ tipo: 'info', texto: 'Verificando sua instância do WhatsApp...' });
|
|
||||||
|
|
||||||
const instanciaInfo = await verificarInstanciaWhatsApp();
|
|
||||||
|
|
||||||
if (!instanciaInfo.hasInstance) {
|
|
||||||
setMensagemStatus({
|
|
||||||
tipo: 'error',
|
|
||||||
texto: 'Você precisa ter uma instância do WhatsApp conectada. Acesse o menu "WhatsApp" primeiro.'
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Criar ou encontrar grupo
|
// Criar ou encontrar grupo
|
||||||
setMensagemStatus({ tipo: 'info', texto: 'Cadastrando grupo...' });
|
setMensagemStatus({ tipo: 'info', texto: 'Cadastrando grupo...' });
|
||||||
|
|
||||||
@ -122,7 +105,6 @@ const CreateGroupFormSimple = ({ userEmail, onSuccess }: CreateGroupFormProps) =
|
|||||||
try {
|
try {
|
||||||
console.log(`🔔 Enviando webhook ativarworkflow para usuário: ${userEmail}`);
|
console.log(`🔔 Enviando webhook ativarworkflow para usuário: ${userEmail}`);
|
||||||
await activateUserWorkflow(userEmail);
|
await activateUserWorkflow(userEmail);
|
||||||
setWorkflowAtivado(true);
|
|
||||||
console.log('✅ [GRUPO] Webhook ativarworkflow enviado com sucesso no cadastro do grupo');
|
console.log('✅ [GRUPO] Webhook ativarworkflow enviado com sucesso no cadastro do grupo');
|
||||||
} catch (workflowError) {
|
} catch (workflowError) {
|
||||||
console.error('❌ Erro ao enviar webhook ativarworkflow:', workflowError);
|
console.error('❌ Erro ao enviar webhook ativarworkflow:', workflowError);
|
||||||
@ -136,7 +118,6 @@ const CreateGroupFormSimple = ({ userEmail, onSuccess }: CreateGroupFormProps) =
|
|||||||
try {
|
try {
|
||||||
console.log(`🔔 Enviando email para N8N configurar webhook: ${userEmail}`);
|
console.log(`🔔 Enviando email para N8N configurar webhook: ${userEmail}`);
|
||||||
await createEvolutionWebhook(userEmail);
|
await createEvolutionWebhook(userEmail);
|
||||||
setWebhookEnviado(true);
|
|
||||||
console.log('✅ [GRUPO] Email enviado com sucesso para N8N no cadastro do grupo');
|
console.log('✅ [GRUPO] Email enviado com sucesso para N8N no cadastro do grupo');
|
||||||
} catch (webhookError) {
|
} catch (webhookError) {
|
||||||
console.error('❌ Erro ao enviar email para N8N:', webhookError);
|
console.error('❌ Erro ao enviar email para N8N:', webhookError);
|
||||||
|
|||||||
@ -6,16 +6,17 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from "@/components/ui/tabs";
|
|||||||
import { Boxes } from "@/components/ui/background-boxes";
|
import { Boxes } from "@/components/ui/background-boxes";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowLeft } from "lucide-react";
|
import { ArrowLeft } from "lucide-react";
|
||||||
import { cn } from "@/lib/utils";
|
|
||||||
import LoginForm from '@/components/auth/LoginForm';
|
import LoginForm from '@/components/auth/LoginForm';
|
||||||
import RegisterForm from '@/components/auth/RegisterForm';
|
import RegisterForm from '@/components/auth/RegisterForm';
|
||||||
import SocialLoginButtons from '@/components/auth/SocialLoginButtons';
|
import SocialLoginButtons from '@/components/auth/SocialLoginButtons';
|
||||||
import AuthSecurityFeatures from '@/components/auth/AuthSecurityFeatures';
|
import AuthSecurityFeatures from '@/components/auth/AuthSecurityFeatures';
|
||||||
|
import ForgotPasswordForm from '@/components/auth/ForgotPasswordForm';
|
||||||
import { toast } from "sonner";
|
import { toast } from "sonner";
|
||||||
|
|
||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [activeTab, setActiveTab] = useState("login");
|
const [activeTab, setActiveTab] = useState("login");
|
||||||
|
const [showForgotPassword, setShowForgotPassword] = useState(false);
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Show success message when redirected from registration or email confirmation
|
// Show success message when redirected from registration or email confirmation
|
||||||
@ -32,6 +33,35 @@ const Auth = () => {
|
|||||||
}
|
}
|
||||||
}, [location.state]);
|
}, [location.state]);
|
||||||
|
|
||||||
|
const handleForgotPassword = () => {
|
||||||
|
setShowForgotPassword(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleBackToLogin = () => {
|
||||||
|
setShowForgotPassword(false);
|
||||||
|
setActiveTab("login");
|
||||||
|
};
|
||||||
|
|
||||||
|
if (showForgotPassword) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen relative w-full overflow-hidden bg-slate-900 flex items-center justify-center px-4">
|
||||||
|
<div className="absolute inset-0 w-full h-full bg-slate-900 z-10 [mask-image:radial-gradient(transparent,white)] pointer-events-none" />
|
||||||
|
<Boxes />
|
||||||
|
|
||||||
|
<Link to="/" className="absolute top-4 left-4 z-30">
|
||||||
|
<Button variant="ghost" size="sm" className="text-white hover:bg-white/10">
|
||||||
|
<ArrowLeft className="h-4 w-4 mr-2" />
|
||||||
|
Voltar
|
||||||
|
</Button>
|
||||||
|
</Link>
|
||||||
|
|
||||||
|
<div className="relative z-20 bg-white/95 backdrop-blur-sm border-white/20 rounded-lg">
|
||||||
|
<ForgotPasswordForm onBackToLogin={handleBackToLogin} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen relative w-full overflow-hidden bg-slate-900 flex items-center justify-center px-4">
|
<div className="min-h-screen relative w-full overflow-hidden bg-slate-900 flex items-center justify-center px-4">
|
||||||
<div className="absolute inset-0 w-full h-full bg-slate-900 z-10 [mask-image:radial-gradient(transparent,white)] pointer-events-none" />
|
<div className="absolute inset-0 w-full h-full bg-slate-900 z-10 [mask-image:radial-gradient(transparent,white)] pointer-events-none" />
|
||||||
@ -66,7 +96,11 @@ const Auth = () => {
|
|||||||
</TabsList>
|
</TabsList>
|
||||||
|
|
||||||
<TabsContent value="login" className="space-y-4">
|
<TabsContent value="login" className="space-y-4">
|
||||||
<LoginForm isLoading={isLoading} setIsLoading={setIsLoading} />
|
<LoginForm
|
||||||
|
isLoading={isLoading}
|
||||||
|
setIsLoading={setIsLoading}
|
||||||
|
onForgotPassword={handleForgotPassword}
|
||||||
|
/>
|
||||||
<SocialLoginButtons />
|
<SocialLoginButtons />
|
||||||
</TabsContent>
|
</TabsContent>
|
||||||
|
|
||||||
|
|||||||
147
src/pages/ResetPassword.tsx
Normal file
147
src/pages/ResetPassword.tsx
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
|
||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
|
||||||
|
import { Alert, AlertDescription } from "@/components/ui/alert";
|
||||||
|
import { CheckCircle2, Lock } from "lucide-react";
|
||||||
|
import { supabase } from '@/integrations/supabase/client';
|
||||||
|
import { toast } from "sonner";
|
||||||
|
|
||||||
|
const ResetPassword = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [password, setPassword] = useState('');
|
||||||
|
const [confirmPassword, setConfirmPassword] = useState('');
|
||||||
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState('');
|
||||||
|
const [success, setSuccess] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
// Verificar se há um token de recuperação na URL
|
||||||
|
const hashParams = new URLSearchParams(window.location.hash.substring(1));
|
||||||
|
const accessToken = hashParams.get('access_token');
|
||||||
|
const refreshToken = hashParams.get('refresh_token');
|
||||||
|
|
||||||
|
if (!accessToken || !refreshToken) {
|
||||||
|
toast.error("Link inválido ou expirado");
|
||||||
|
navigate('/auth');
|
||||||
|
}
|
||||||
|
}, [navigate]);
|
||||||
|
|
||||||
|
const handleSubmit = async (e: React.FormEvent) => {
|
||||||
|
e.preventDefault();
|
||||||
|
setIsLoading(true);
|
||||||
|
setError('');
|
||||||
|
|
||||||
|
if (password !== confirmPassword) {
|
||||||
|
setError('As senhas não coincidem');
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (password.length < 6) {
|
||||||
|
setError('A senha deve ter pelo menos 6 caracteres');
|
||||||
|
setIsLoading(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const { error } = await supabase.auth.updateUser({
|
||||||
|
password: password
|
||||||
|
});
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
setError(error.message);
|
||||||
|
} else {
|
||||||
|
setSuccess(true);
|
||||||
|
toast.success("Senha redefinida com sucesso!");
|
||||||
|
setTimeout(() => {
|
||||||
|
navigate('/auth');
|
||||||
|
}, 3000);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
setError('Erro inesperado. Tente novamente.');
|
||||||
|
} finally {
|
||||||
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (success) {
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="space-y-1 text-center">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<CheckCircle2 className="h-12 w-12 text-green-500" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-2xl">Senha redefinida!</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Sua senha foi alterada com sucesso. Você será redirecionado para o login.
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="min-h-screen flex items-center justify-center bg-gray-50 px-4">
|
||||||
|
<Card className="w-full max-w-md">
|
||||||
|
<CardHeader className="space-y-1 text-center">
|
||||||
|
<div className="flex justify-center mb-4">
|
||||||
|
<Lock className="h-12 w-12 text-blue-500" />
|
||||||
|
</div>
|
||||||
|
<CardTitle className="text-2xl">Nova senha</CardTitle>
|
||||||
|
<CardDescription>
|
||||||
|
Digite sua nova senha
|
||||||
|
</CardDescription>
|
||||||
|
</CardHeader>
|
||||||
|
<CardContent>
|
||||||
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="password">Nova senha</Label>
|
||||||
|
<Input
|
||||||
|
id="password"
|
||||||
|
type="password"
|
||||||
|
placeholder="Digite sua nova senha"
|
||||||
|
value={password}
|
||||||
|
onChange={(e) => setPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
minLength={6}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="confirmPassword">Confirmar senha</Label>
|
||||||
|
<Input
|
||||||
|
id="confirmPassword"
|
||||||
|
type="password"
|
||||||
|
placeholder="Confirme sua nova senha"
|
||||||
|
value={confirmPassword}
|
||||||
|
onChange={(e) => setConfirmPassword(e.target.value)}
|
||||||
|
required
|
||||||
|
disabled={isLoading}
|
||||||
|
minLength={6}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button type="submit" className="w-full" disabled={isLoading}>
|
||||||
|
{isLoading ? "Salvando..." : "Redefinir senha"}
|
||||||
|
</Button>
|
||||||
|
</form>
|
||||||
|
</CardContent>
|
||||||
|
</Card>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ResetPassword;
|
||||||
Loading…
Reference in New Issue
Block a user