add-magic-link-endpoint

This commit is contained in:
João Gustavo 2025-10-16 22:20:03 -03:00
parent 37a87af28d
commit 199a3197be
3 changed files with 123 additions and 9 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,6 +13,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()
@ -51,6 +55,27 @@ 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, { emailRedirectTo: `${window.location.origin}/` })
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)
}
}
return (
<div className="min-h-screen flex items-center justify-center bg-background py-12 px-4 sm:px-6 lg:px-8">
<div className="max-w-md w-full space-y-8">
@ -115,6 +140,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,6 +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()
@ -53,6 +57,28 @@ 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, { emailRedirectTo: `${window.location.origin}/` })
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">
<div className="max-w-md w-full space-y-8">
@ -117,6 +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

@ -1623,10 +1623,11 @@ export async function criarUsuario(input: CreateUserInput): Promise<CreateUserRe
// The OpenAPI for the new endpoint exposes POST /create-user at the
// API root (API_BASE). Call that endpoint directly from the client.
const url = `${API_BASE}/create-user`;
const functionsUrl = `${API_BASE}/functions/v1/create-user`;
// Network/fetch errors (including CORS preflight failures) throw before we get a Response.
// Catch them and provide a clearer, actionable error message for developers/operators.
let res: Response;
let res: Response | null = null;
try {
res = await fetch(url, {
method: 'POST',
@ -1635,15 +1636,39 @@ export async function criarUsuario(input: CreateUserInput): Promise<CreateUserRe
});
} catch (err: any) {
console.error('[criarUsuario] fetch error for', url, err);
// Do not attempt client-side signup fallback. Role assignment and user creation
// must be performed by the backend endpoint `/create-user` which has the
// necessary privileges. Surface a clear error so operators can fix the
// backend (CORS / route availability) instead of silently creating an
// auth user without roles.
// Attempt functions fallback when primary endpoint can't be reached (network/CORS/route)
try {
console.warn('[criarUsuario] tentando fallback para', functionsUrl);
const res2 = await fetch(functionsUrl, {
method: 'POST',
headers: { ...baseHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
return await parse<CreateUserResponse>(res2 as Response);
} catch (err2: any) {
console.error('[criarUsuario] fallback functions also failed', err2);
throw new Error(
'Falha ao contatar o endpoint /create-user. Não será feito fallback via /auth/v1/signup. Verifique se o endpoint /create-user existe, está acessível e se o CORS/OPTIONS está configurado corretamente. Detalhes: ' + (err?.message ?? String(err))
'Falha ao contatar o endpoint /create-user e o fallback /functions/v1/create-user também falhou. Verifique disponibilidade e CORS. Detalhes: ' +
(err?.message ?? String(err)) + ' | fallback: ' + (err2?.message ?? String(err2))
);
}
}
// If we got a response but it's 404 (route not found), try the functions path too
if (res && !res.ok && res.status === 404) {
try {
console.warn('[criarUsuario] /create-user returned 404; trying functions path', functionsUrl);
const res2 = await fetch(functionsUrl, {
method: 'POST',
headers: { ...baseHeaders(), 'Content-Type': 'application/json' },
body: JSON.stringify(input),
});
return await parse<CreateUserResponse>(res2 as Response);
} catch (err2: any) {
console.error('[criarUsuario] fallback functions failed after 404', err2);
// Fall through to parse original response to provide friendly error
}
}
return await parse<CreateUserResponse>(res as Response);
}