feat(auth): migração final do UI frontend para Logto OIDC via GoTrue
This commit is contained in:
@@ -7,7 +7,7 @@ interface AuthContextType {
|
|||||||
user: User | null;
|
user: User | null;
|
||||||
session: Session | null;
|
session: Session | null;
|
||||||
loading: boolean;
|
loading: boolean;
|
||||||
signIn: (email: string, password: string) => Promise<{ error: unknown }>;
|
signIn: () => Promise<{ error: unknown }>;
|
||||||
signUp: (email: string, password: string) => Promise<{ error: unknown }>;
|
signUp: (email: string, password: string) => Promise<{ error: unknown }>;
|
||||||
signOut: () => Promise<void>;
|
signOut: () => Promise<void>;
|
||||||
updatePassword: (password: string) => Promise<{ error: unknown }>;
|
updatePassword: (password: string) => Promise<{ error: unknown }>;
|
||||||
@@ -334,15 +334,17 @@ export function AuthProvider({ children }: { children: ReactNode }) {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [user?.id, authInitialized]);
|
}, [user?.id, authInitialized]);
|
||||||
|
|
||||||
const signIn = async (email: string, password: string) => {
|
const signIn = async () => {
|
||||||
try {
|
try {
|
||||||
const { error } = await supabase.auth.signInWithPassword({
|
const { error } = await supabase.auth.signInWithOAuth({
|
||||||
email,
|
provider: 'keycloak',
|
||||||
password,
|
options: {
|
||||||
|
scopes: 'openid email profile',
|
||||||
|
}
|
||||||
});
|
});
|
||||||
return { error };
|
return { error };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
logger.error('Erro crítico no login:', error);
|
logger.error('Erro crítico no login SSO:', error);
|
||||||
return { error };
|
return { error };
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { AuthProvider } from './hooks/useAuth'
|
|||||||
import { IconStyleProvider } from './hooks/useIconStyle'
|
import { IconStyleProvider } from './hooks/useIconStyle'
|
||||||
import { ErrorBoundary } from './components/ErrorBoundary'
|
import { ErrorBoundary } from './components/ErrorBoundary'
|
||||||
import { AppRoutes } from './components/AppRoutes'
|
import { AppRoutes } from './components/AppRoutes'
|
||||||
|
import { LogtoProvider, LogtoConfig } from '@logto/react'
|
||||||
|
|
||||||
import './index.css'
|
import './index.css'
|
||||||
|
|
||||||
@@ -28,6 +29,11 @@ const queryClient = new QueryClient({
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const config: LogtoConfig = {
|
||||||
|
endpoint: 'https://logto-bzlued1boxl3t8ewsyn99an9.187.77.227.172.sslip.io',
|
||||||
|
appId: '4qun0u1tfce1fdn3pxrgq',
|
||||||
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Render da aplicação
|
// Render da aplicação
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -36,6 +42,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||||||
<React.StrictMode>
|
<React.StrictMode>
|
||||||
<ErrorBoundary>
|
<ErrorBoundary>
|
||||||
<QueryClientProvider client={queryClient}>
|
<QueryClientProvider client={queryClient}>
|
||||||
|
<LogtoProvider config={config}>
|
||||||
<AuthProvider>
|
<AuthProvider>
|
||||||
<IconStyleProvider>
|
<IconStyleProvider>
|
||||||
<ThemeProvider defaultTheme="system">
|
<ThemeProvider defaultTheme="system">
|
||||||
@@ -46,6 +53,7 @@ ReactDOM.createRoot(document.getElementById('root')!).render(
|
|||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</IconStyleProvider>
|
</IconStyleProvider>
|
||||||
</AuthProvider>
|
</AuthProvider>
|
||||||
|
</LogtoProvider>
|
||||||
</QueryClientProvider>
|
</QueryClientProvider>
|
||||||
</ErrorBoundary>
|
</ErrorBoundary>
|
||||||
</React.StrictMode>,
|
</React.StrictMode>,
|
||||||
|
|||||||
+24
-320
@@ -16,57 +16,35 @@ import { Eye, EyeOff, Mail, Lock } from 'lucide-react';
|
|||||||
|
|
||||||
const Auth = () => {
|
const Auth = () => {
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
const [showPassword, setShowPassword] = useState(false);
|
const { signIn, user, loading, isRecoveryFlow, session } = useAuth();
|
||||||
const [showForgotPassword, setShowForgotPassword] = useState(false);
|
|
||||||
const [loginData, setLoginData] = useState({ email: '', password: '' });
|
|
||||||
const [signupData, setSignupData] = useState({ email: '', password: '', confirmPassword: '' });
|
|
||||||
const { signIn, signUp, user, loading, isRecoveryFlow, session } = useAuth();
|
|
||||||
const { brandSettings } = useBrandSettings();
|
const { brandSettings } = useBrandSettings();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const [searchParams] = useSearchParams();
|
const [searchParams] = useSearchParams();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
|
|
||||||
// Estado para controlar se está em modo de recuperação
|
|
||||||
const [showPasswordReset, setShowPasswordReset] = useState(false);
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
console.log('🔍 Auth.tsx - Verificando fluxo de recuperação...');
|
|
||||||
console.log('Location:', {
|
|
||||||
pathname: location.pathname,
|
|
||||||
search: location.search,
|
|
||||||
hash: location.hash
|
|
||||||
});
|
|
||||||
console.log('Auth states:', {
|
|
||||||
user: !!user,
|
|
||||||
session: !!session,
|
|
||||||
isRecoveryFlow,
|
|
||||||
loading
|
|
||||||
});
|
|
||||||
|
|
||||||
// Verificar se é fluxo de recuperação
|
|
||||||
const recoveryType = searchParams.get('type');
|
const recoveryType = searchParams.get('type');
|
||||||
const hashParams = new URLSearchParams(location.hash.substring(1));
|
const hashParams = new URLSearchParams(location.hash.substring(1));
|
||||||
const hashType = hashParams.get('type');
|
const hashType = hashParams.get('type');
|
||||||
const accessToken = hashParams.get('access_token');
|
const accessToken = hashParams.get('access_token');
|
||||||
const refreshToken = hashParams.get('refresh_token');
|
const refreshToken = hashParams.get('refresh_token');
|
||||||
|
|
||||||
console.log('🔍 Parâmetros de recuperação:', {
|
const shouldShowReset = Boolean(
|
||||||
recoveryType,
|
recoveryType === 'recovery' ||
|
||||||
hashType,
|
|
||||||
accessToken: !!accessToken,
|
|
||||||
refreshToken: !!refreshToken,
|
|
||||||
isRecoveryFlow
|
|
||||||
});
|
|
||||||
|
|
||||||
// Se há type=recovery OU isRecoveryFlow OU tokens, mostrar tela de reset
|
|
||||||
const shouldShowReset = recoveryType === 'recovery' ||
|
|
||||||
hashType === 'recovery' ||
|
hashType === 'recovery' ||
|
||||||
isRecoveryFlow ||
|
isRecoveryFlow ||
|
||||||
(accessToken && refreshToken);
|
(accessToken && refreshToken)
|
||||||
|
);
|
||||||
|
|
||||||
console.log('🔑 Deve mostrar reset de senha:', shouldShowReset);
|
// Estado para controlar se está em modo de recuperação
|
||||||
setShowPasswordReset(Boolean(shouldShowReset));
|
const [showPasswordReset, setShowPasswordReset] = useState(shouldShowReset);
|
||||||
}, [searchParams, location.hash, location.search, isRecoveryFlow, user, session]);
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (shouldShowReset !== showPasswordReset) {
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
setShowPasswordReset(shouldShowReset);
|
||||||
|
}
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [shouldShowReset, showPasswordReset, loading, location.pathname]);
|
||||||
|
|
||||||
// Redirecionar usuários autenticados para página principal (exceto em fluxo de recuperação)
|
// Redirecionar usuários autenticados para página principal (exceto em fluxo de recuperação)
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -139,112 +117,18 @@ const Auth = () => {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatePassword = (password: string) => {
|
const handleLogin = async () => {
|
||||||
const minLength = password.length >= 8;
|
|
||||||
const hasUpperCase = /[A-Z]/.test(password);
|
|
||||||
const hasLowerCase = /[a-z]/.test(password);
|
|
||||||
const hasNumbers = /\d/.test(password);
|
|
||||||
const hasSpecialChar = /[!@#$%^&*(),.?":{}|<>]/.test(password);
|
|
||||||
|
|
||||||
const score = [minLength, hasUpperCase, hasLowerCase, hasNumbers, hasSpecialChar].filter(Boolean).length;
|
|
||||||
|
|
||||||
return {
|
|
||||||
score,
|
|
||||||
minLength,
|
|
||||||
hasUpperCase,
|
|
||||||
hasLowerCase,
|
|
||||||
hasNumbers,
|
|
||||||
hasSpecialChar,
|
|
||||||
isValid: score >= 4 && minLength
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const passwordStrength = validatePassword(signupData.password);
|
|
||||||
|
|
||||||
// Email validation
|
|
||||||
const isValidEmail = (email: string) => {
|
|
||||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
||||||
return emailRegex.test(email);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleLogin = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!loginData.email || !loginData.password) {
|
|
||||||
toast.error('Por favor, preencha todos os campos');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidEmail(loginData.email)) {
|
|
||||||
toast.error('Por favor, insira um email válido');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
const { error } = await signIn(loginData.email, loginData.password);
|
const { error } = await signIn();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
if (error.message.includes('Invalid login credentials')) {
|
toast.error('Erro ao redirecionar para o login: ' + (error as Error).message);
|
||||||
toast.error('Email ou senha incorretos');
|
|
||||||
} else {
|
|
||||||
toast.error('Erro ao fazer login: ' + error.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.success('Login realizado com sucesso!');
|
|
||||||
navigate('/');
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
|
}
|
||||||
|
// No else block needed as it redirects to external OIDC provider
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSignup = async (e: React.FormEvent) => {
|
|
||||||
e.preventDefault();
|
|
||||||
|
|
||||||
if (!signupData.email || !signupData.password || !signupData.confirmPassword) {
|
|
||||||
toast.error('Por favor, preencha todos os campos');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isValidEmail(signupData.email)) {
|
|
||||||
toast.error('Por favor, insira um email válido');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!passwordStrength.isValid) {
|
|
||||||
toast.error('A senha deve ter pelo menos 8 caracteres e incluir maiúsculas, minúsculas, números e símbolos');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (signupData.password !== signupData.confirmPassword) {
|
|
||||||
toast.error('As senhas não coincidem');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsLoading(true);
|
|
||||||
const { error } = await signUp(signupData.email, signupData.password);
|
|
||||||
|
|
||||||
if (error) {
|
|
||||||
if (error.message.includes('User already registered')) {
|
|
||||||
toast.error('Este email já está cadastrado');
|
|
||||||
} else {
|
|
||||||
toast.error('Erro ao criar conta: ' + error.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
toast.success('Conta criada com sucesso! Verifique seu email.');
|
|
||||||
}
|
|
||||||
setIsLoading(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPasswordStrengthColor = (score: number) => {
|
|
||||||
if (score <= 2) return 'bg-red-500';
|
|
||||||
if (score <= 3) return 'bg-yellow-500';
|
|
||||||
return 'bg-green-500';
|
|
||||||
};
|
|
||||||
|
|
||||||
const getPasswordStrengthText = (score: number) => {
|
|
||||||
if (score <= 2) return 'Fraca';
|
|
||||||
if (score <= 3) return 'Média';
|
|
||||||
return 'Forte';
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('📱 Renderizando página de login/cadastro');
|
console.log('📱 Renderizando página de login/cadastro');
|
||||||
return (
|
return (
|
||||||
@@ -281,188 +165,14 @@ const Auth = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent className="pt-6">
|
||||||
<Tabs defaultValue="login" className="w-full">
|
|
||||||
<TabsList className="grid w-full grid-cols-2 mb-6">
|
|
||||||
<TabsTrigger value="login" className="text-sm">Entrar</TabsTrigger>
|
|
||||||
<TabsTrigger value="signup" className="text-sm">Cadastrar</TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
|
|
||||||
<TabsContent value="login" className="space-y-4">
|
|
||||||
<form onSubmit={handleLogin} className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="login-email" className="text-sm font-medium">
|
|
||||||
Email
|
|
||||||
</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
id="login-email"
|
|
||||||
type="email"
|
|
||||||
placeholder="seu@email.com"
|
|
||||||
value={loginData.email}
|
|
||||||
onChange={(e) => setLoginData({ ...loginData, email: e.target.value })}
|
|
||||||
className="pl-10 h-12"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="login-password" className="text-sm font-medium">
|
|
||||||
Senha
|
|
||||||
</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
id="login-password"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Sua senha"
|
|
||||||
value={loginData.password}
|
|
||||||
onChange={(e) => setLoginData({ ...loginData, password: e.target.value })}
|
|
||||||
className="pl-10 pr-10 h-12"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-3 text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
type="submit"
|
onClick={handleLogin}
|
||||||
className="w-full h-12 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium text-sm"
|
className="w-full h-14 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium text-lg rounded-xl transition-all hover:scale-[1.02] shadow-md"
|
||||||
disabled={isLoading}
|
disabled={isLoading}
|
||||||
>
|
>
|
||||||
{isLoading ? 'Entrando...' : 'Entrar'}
|
{isLoading ? 'Redirecionando...' : 'Acessar Sistema Seguramente'}
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{/* Botão Esqueci a Senha */}
|
|
||||||
<div className="text-center">
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowForgotPassword(true)}
|
|
||||||
className="text-sm text-muted-foreground hover:text-foreground underline"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
Esqueci a senha
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</TabsContent>
|
|
||||||
|
|
||||||
<TabsContent value="signup" className="space-y-4">
|
|
||||||
<form onSubmit={handleSignup} className="space-y-4">
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="signup-email" className="text-sm font-medium">
|
|
||||||
Email
|
|
||||||
</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Mail className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
id="signup-email"
|
|
||||||
type="email"
|
|
||||||
placeholder="seu@email.com"
|
|
||||||
value={signupData.email}
|
|
||||||
onChange={(e) => setSignupData({ ...signupData, email: e.target.value })}
|
|
||||||
className="pl-10 h-12"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="signup-password" className="text-sm font-medium">
|
|
||||||
Senha
|
|
||||||
</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
id="signup-password"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Mínimo 8 caracteres"
|
|
||||||
value={signupData.password}
|
|
||||||
onChange={(e) => setSignupData({ ...signupData, password: e.target.value })}
|
|
||||||
className="pl-10 pr-10 h-12"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
onClick={() => setShowPassword(!showPassword)}
|
|
||||||
className="absolute right-3 top-3 text-muted-foreground hover:text-foreground"
|
|
||||||
>
|
|
||||||
{showPassword ? <EyeOff className="h-4 w-4" /> : <Eye className="h-4 w-4" />}
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Password Strength Indicator */}
|
|
||||||
{signupData.password && (
|
|
||||||
<div className="space-y-2">
|
|
||||||
<div className="flex items-center gap-2">
|
|
||||||
<div className="flex-1 bg-muted rounded-full h-2">
|
|
||||||
<div
|
|
||||||
className={`h-2 rounded-full transition-all duration-300 ${getPasswordStrengthColor(passwordStrength.score)}`}
|
|
||||||
style={{ width: `${(passwordStrength.score / 5) * 100}%` }}
|
|
||||||
></div>
|
|
||||||
</div>
|
|
||||||
<span className="text-xs text-muted-foreground">
|
|
||||||
{getPasswordStrengthText(passwordStrength.score)}
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
<div className="text-xs space-y-1">
|
|
||||||
<div className={`flex items-center gap-1 ${passwordStrength.minLength ? 'text-green-600' : 'text-red-600'}`}>
|
|
||||||
<span className="w-2 h-2 rounded-full bg-current"></span>
|
|
||||||
Mínimo 8 caracteres
|
|
||||||
</div>
|
|
||||||
<div className={`flex items-center gap-1 ${passwordStrength.hasUpperCase ? 'text-green-600' : 'text-red-600'}`}>
|
|
||||||
<span className="w-2 h-2 rounded-full bg-current"></span>
|
|
||||||
Letra maiúscula
|
|
||||||
</div>
|
|
||||||
<div className={`flex items-center gap-1 ${passwordStrength.hasNumbers ? 'text-green-600' : 'text-red-600'}`}>
|
|
||||||
<span className="w-2 h-2 rounded-full bg-current"></span>
|
|
||||||
Número
|
|
||||||
</div>
|
|
||||||
<div className={`flex items-center gap-1 ${passwordStrength.hasSpecialChar ? 'text-green-600' : 'text-red-600'}`}>
|
|
||||||
<span className="w-2 h-2 rounded-full bg-current"></span>
|
|
||||||
Símbolo especial
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="signup-confirm" className="text-sm font-medium">
|
|
||||||
Confirmar Senha
|
|
||||||
</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<Lock className="absolute left-3 top-3 h-4 w-4 text-muted-foreground" />
|
|
||||||
<Input
|
|
||||||
id="signup-confirm"
|
|
||||||
type={showPassword ? "text" : "password"}
|
|
||||||
placeholder="Confirme sua senha"
|
|
||||||
value={signupData.confirmPassword}
|
|
||||||
onChange={(e) => setSignupData({ ...signupData, confirmPassword: e.target.value })}
|
|
||||||
className="pl-10 h-12"
|
|
||||||
disabled={isLoading}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<Button
|
|
||||||
type="submit"
|
|
||||||
className="w-full h-12 bg-gradient-to-r from-blue-500 to-purple-600 hover:from-blue-600 hover:to-purple-700 text-white font-medium text-sm"
|
|
||||||
disabled={isLoading}
|
|
||||||
>
|
|
||||||
{isLoading ? 'Criando conta...' : 'Criar conta'}
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</TabsContent>
|
|
||||||
</Tabs>
|
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
@@ -471,12 +181,6 @@ const Auth = () => {
|
|||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Modal Esqueci a Senha */}
|
|
||||||
<ForgotPasswordModal
|
|
||||||
isOpen={showForgotPassword}
|
|
||||||
onClose={() => setShowForgotPassword(false)}
|
|
||||||
/>
|
|
||||||
</BeamsBackground>
|
</BeamsBackground>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user