Compare commits

..

No commits in common. "094650dba77037ddfc992bcc5a816d91cc752f21" and "2e7b3561b64033e2b4c4b535e8fa8a07d85def2a" have entirely different histories.

4 changed files with 175 additions and 15 deletions

View File

@ -3,6 +3,7 @@ import { useState } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { useAuth } from '@/hooks/useAuth'
import { sendMagicLink } from '@/lib/api'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
@ -12,7 +13,9 @@ import { AuthenticationError } from '@/lib/auth'
export default function LoginAdminPage() {
const [credentials, setCredentials] = useState({ email: '', password: '' })
const [error, setError] = useState('')
const [magicMessage, setMagicMessage] = useState('')
const [magicError, setMagicError] = useState('')
const [magicLoading, setMagicLoading] = useState(false)
const [loading, setLoading] = useState(false)
const router = useRouter()
const { login } = useAuth()
@ -45,7 +48,26 @@ export default function LoginAdminPage() {
}
}
const handleSendMagicLink = async () => {
if (!credentials.email) {
setMagicError('Por favor, preencha o email antes de solicitar o magic link.')
return
}
setMagicLoading(true)
setMagicError('')
setMagicMessage('')
try {
const res = await sendMagicLink(credentials.email, { target: 'admin' })
setMagicMessage(res?.message ?? 'Magic link enviado. Verifique seu email.')
} catch (err: any) {
console.error('[MAGIC-LINK ADMIN] erro ao enviar:', err)
setMagicError(err?.message ?? String(err))
} finally {
setMagicLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8">
@ -111,7 +133,25 @@ export default function LoginAdminPage() {
{loading ? 'Entrando...' : 'Entrar no Sistema Administrativo'}
</Button>
</form>
<div className="mt-4 space-y-2">
<div className="text-sm text-muted-foreground mb-2">Ou entre usando um magic link (sem senha)</div>
{magicError && (
<Alert variant="destructive">
<AlertDescription>{magicError}</AlertDescription>
</Alert>
)}
{magicMessage && (
<Alert>
<AlertDescription>{magicMessage}</AlertDescription>
</Alert>
)}
<Button className="w-full" onClick={handleSendMagicLink} disabled={magicLoading}>
{magicLoading ? 'Enviando magic link...' : 'Enviar magic link'}
</Button>
</div>
<div className="mt-4 text-center">
<Button variant="outline" asChild className="w-full hover:bg-primary! hover:text-white! hover:border-primary! transition-all duration-200">

View File

@ -3,6 +3,7 @@ import { useState } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { useAuth } from '@/hooks/useAuth'
import { sendMagicLink } from '@/lib/api'
import { ENV_CONFIG } from '@/lib/env-config'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
@ -13,7 +14,9 @@ import { AuthenticationError } from '@/lib/auth'
export default function LoginPacientePage() {
const [credentials, setCredentials] = useState({ email: '', password: '' })
const [error, setError] = useState('')
const [magicMessage, setMagicMessage] = useState('')
const [magicError, setMagicError] = useState('')
const [magicLoading, setMagicLoading] = useState(false)
const [loading, setLoading] = useState(false)
const router = useRouter()
const { login } = useAuth()
@ -53,7 +56,26 @@ export default function LoginPacientePage() {
}
}
const handleSendMagicLink = async () => {
if (!credentials.email) {
setMagicError('Por favor, preencha o email antes de solicitar o magic link.')
return
}
setMagicLoading(true)
setMagicError('')
setMagicMessage('')
try {
const res = await sendMagicLink(credentials.email, { target: 'paciente' })
setMagicMessage(res?.message ?? 'Magic link enviado. Verifique seu email.')
} catch (err: any) {
console.error('[MAGIC-LINK PACIENTE] erro ao enviar:', err)
setMagicError(err?.message ?? String(err))
} finally {
setMagicLoading(false)
}
}
// --- Auto-cadastro (client-side) ---
const [showRegister, setShowRegister] = useState(false)
@ -197,7 +219,25 @@ export default function LoginPacientePage() {
{loading ? 'Entrando...' : 'Entrar na Minha Área'}
</Button>
</form>
<div className="mt-4 space-y-2">
<div className="text-sm text-muted-foreground mb-2">Ou entre usando um magic link (sem senha)</div>
{magicError && (
<Alert variant="destructive">
<AlertDescription>{magicError}</AlertDescription>
</Alert>
)}
{magicMessage && (
<Alert>
<AlertDescription>{magicMessage}</AlertDescription>
</Alert>
)}
<Button className="w-full" onClick={handleSendMagicLink} disabled={magicLoading}>
{magicLoading ? 'Enviando magic link...' : 'Enviar magic link'}
</Button>
</div>
<div className="mt-4 text-center">
<Button variant="outline" asChild className="w-full hover:bg-primary! hover:text-white! hover:border-primary! transition-all duration-200">

View File

@ -3,6 +3,7 @@ import { useState } from 'react'
import { useRouter } from 'next/navigation'
import Link from 'next/link'
import { useAuth } from '@/hooks/useAuth'
import { sendMagicLink } from '@/lib/api'
import { Button } from '@/components/ui/button'
import { Input } from '@/components/ui/input'
import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
@ -12,7 +13,9 @@ import { AuthenticationError } from '@/lib/auth'
export default function LoginPage() {
const [credentials, setCredentials] = useState({ email: '', password: '' })
const [error, setError] = useState('')
const [magicMessage, setMagicMessage] = useState('')
const [magicError, setMagicError] = useState('')
const [magicLoading, setMagicLoading] = useState(false)
const [loading, setLoading] = useState(false)
const router = useRouter()
const { login } = useAuth()
@ -54,7 +57,27 @@ export default function LoginPage() {
}
}
const handleSendMagicLink = async () => {
// basic client-side validation
if (!credentials.email) {
setMagicError('Por favor, preencha o email antes de solicitar o magic link.')
return
}
setMagicLoading(true)
setMagicError('')
setMagicMessage('')
try {
const res = await sendMagicLink(credentials.email, { target: 'medico' })
setMagicMessage(res?.message ?? 'Magic link enviado. Verifique seu email.')
} catch (err: any) {
console.error('[MAGIC-LINK] erro ao enviar:', err)
setMagicError(err?.message ?? String(err))
} finally {
setMagicLoading(false)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8">
@ -120,7 +143,25 @@ export default function LoginPage() {
{loading ? 'Entrando...' : 'Entrar'}
</Button>
</form>
<div className="mt-4 space-y-2">
<div className="text-sm text-muted-foreground mb-2">Ou entre usando um magic link (sem senha)</div>
{magicError && (
<Alert variant="destructive">
<AlertDescription>{magicError}</AlertDescription>
</Alert>
)}
{magicMessage && (
<Alert>
<AlertDescription>{magicMessage}</AlertDescription>
</Alert>
)}
<Button className="w-full" onClick={handleSendMagicLink} disabled={magicLoading}>
{magicLoading ? 'Enviando magic link...' : 'Enviar magic link'}
</Button>
</div>
<div className="mt-4 text-center">
<Button variant="outline" asChild className="w-full hover:bg-primary! hover:text-white! hover:border-primary! transition-all duration-200">

View File

@ -2474,7 +2474,7 @@ export type CreateUserInput = {
full_name: string;
phone?: string | null;
role: UserRoleEnum;
// Optional: when provided, backend can use this to perform a redirect
// Optional: when provided, backend can use this to send magic links that redirect
// to the given URL or interpret `target` to build a role-specific redirect.
emailRedirectTo?: string;
// Compatibility: some integrations expect `redirect_url` as the parameter name
@ -2616,11 +2616,11 @@ export async function criarUsuarioDirectAuth(input: {
// Try several common locations for the returned user id depending on Supabase configuration
const userId = responseData?.user?.id || responseData?.id || responseData?.data?.user?.id || responseData?.data?.id;
// If no user id was returned, treat this as a failure. Some Supabase setups (for example invite-only flows)
// may not return the user id immediately. In that case we cannot safely link the profile to a user.
// If no user id was returned, treat this as a failure. Some Supabase setups (e.g. magic link / invite)
// may not return the user id immediately. In that case we cannot safely link the profile to a user.
if (!userId) {
console.warn('[DIRECT AUTH] signup response did not include a user id; response:', responseData);
throw new Error('Signup did not return a user id (provider may be configured for invite or pending-confirmation flows). Fallback cannot determine created user id.');
throw new Error('Signup did not return a user id (provider may be configured for magic links or pending confirmation). Fallback cannot determine created user id.');
}
console.log('[DIRECT AUTH] Usuário criado:', userId);
@ -2657,8 +2657,8 @@ export async function criarUsuarioDirectAuth(input: {
export async function criarUsuarioMedico(medico: { email: string; full_name: string; phone_mobile: string; }): Promise<any> {
const redirectBase = DEFAULT_LANDING;
const emailRedirectTo = `${redirectBase.replace(/\/$/, '')}/profissional`;
// Use the role-specific landing as the redirect_url so the server-side
// flow redirects users directly to the app path (e.g. /profissional).
// Use the role-specific landing as the redirect_url so the magic link
// redirects users directly to the app path (e.g. /profissional).
const redirect_url = emailRedirectTo;
// generate a secure-ish random password on the client so the caller can receive it
const password = gerarSenhaAleatoria();
@ -2711,7 +2711,46 @@ export async function criarUsuarioPaciente(paciente: { email: string; full_name:
}
export async function sendMagicLink(
email: string,
options?: { emailRedirectTo?: string; target?: 'paciente' | 'medico' | 'admin' | 'default'; redirectBase?: string }
): Promise<{ success: boolean; message?: string }> {
if (!email) throw new Error('Email obrigatório para enviar magic link');
const url = `${API_BASE}/auth/v1/otp`;
const payload: any = { email };
const redirectUrl = buildRedirectUrl(options?.target, options?.emailRedirectTo, options?.redirectBase);
if (redirectUrl) {
// include both keys for broader compatibility across different Supabase setups
payload.options = { emailRedirectTo: redirectUrl, redirect_to: redirectUrl, redirect_url: redirectUrl };
}
try {
const res = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Accept: 'application/json',
apikey: ENV_CONFIG.SUPABASE_ANON_KEY,
},
body: JSON.stringify(payload),
});
const text = await res.text();
let json: any = null;
try { json = text ? JSON.parse(text) : null; } catch { json = null; }
if (!res.ok) {
const msg = (json && (json.error || json.msg || json.message)) ?? text ?? res.statusText;
throw new Error(String(msg));
}
return { success: true, message: (json && (json.message || json.msg)) ?? 'Magic link enviado. Verifique seu email.' };
} catch (err: any) {
console.error('[sendMagicLink] erro ao enviar magic link', err);
throw new Error(err?.message ?? 'Falha ao enviar magic link');
}
}
// ===== CEP (usado nos formulários) =====
export async function buscarCepAPI(cep: string): Promise<{