first commit
This commit is contained in:
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="p-4">
|
||||
<!-- HEADER -->
|
||||
<div class="flex flex-column md:flex-row md:align-items-center md:justify-content-between gap-3">
|
||||
<div>
|
||||
<div class="text-900 text-2xl font-semibold">Cadastro Externo</div>
|
||||
<div class="text-600 mt-1">
|
||||
Gere um link para o paciente preencher o pré-cadastro.
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex gap-2">
|
||||
<Button
|
||||
label="Gerar novo link"
|
||||
icon="pi pi-refresh"
|
||||
severity="secondary"
|
||||
outlined
|
||||
:loading="rotating"
|
||||
@click="rotateLink"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- CARD -->
|
||||
<Card class="mt-4">
|
||||
<template #title>Seu link</template>
|
||||
<template #subtitle>Envie este link ao paciente.</template>
|
||||
|
||||
<template #content>
|
||||
<div class="flex flex-column gap-3">
|
||||
<div class="p-inputgroup">
|
||||
<InputText
|
||||
readonly
|
||||
:value="publicUrl"
|
||||
placeholder="Gerando seu link…"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-copy"
|
||||
severity="secondary"
|
||||
outlined
|
||||
:disabled="!publicUrl"
|
||||
@click="copyLink"
|
||||
v-tooltip.bottom="'Copiar'"
|
||||
/>
|
||||
<Button
|
||||
icon="pi pi-external-link"
|
||||
severity="secondary"
|
||||
outlined
|
||||
:disabled="!publicUrl"
|
||||
@click="openLink"
|
||||
v-tooltip.bottom="'Abrir'"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<Message v-if="!inviteToken" severity="info" :closable="false">
|
||||
Gerando seu link...
|
||||
</Message>
|
||||
</div>
|
||||
</template>
|
||||
</Card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, onMounted, ref } from 'vue'
|
||||
import Button from 'primevue/button'
|
||||
import Card from 'primevue/card'
|
||||
import InputText from 'primevue/inputtext'
|
||||
import Message from 'primevue/message'
|
||||
import { useToast } from 'primevue/usetoast'
|
||||
|
||||
import { supabase } from '@/lib/supabase/client' // ajuste se seu caminho for diferente
|
||||
|
||||
const toast = useToast()
|
||||
|
||||
const inviteToken = ref('')
|
||||
const rotating = ref(false)
|
||||
|
||||
const origin = computed(() => window.location.origin)
|
||||
const publicUrl = computed(() => {
|
||||
if (!inviteToken.value) return ''
|
||||
return `${origin.value}/cadastro/paciente?t=${inviteToken.value}`
|
||||
})
|
||||
|
||||
function newToken () {
|
||||
// browsers modernos
|
||||
if (globalThis.crypto?.randomUUID) return globalThis.crypto.randomUUID()
|
||||
// fallback simples
|
||||
return 'tok_' + Math.random().toString(36).slice(2) + Date.now().toString(36)
|
||||
}
|
||||
|
||||
async function requireUserId () {
|
||||
const { data, error } = await supabase.auth.getUser()
|
||||
if (error) throw error
|
||||
const uid = data?.user?.id
|
||||
if (!uid) throw new Error('Usuário não autenticado')
|
||||
return uid
|
||||
}
|
||||
|
||||
async function loadOrCreateInvite () {
|
||||
const uid = await requireUserId()
|
||||
|
||||
const { data, error } = await supabase
|
||||
.from('patient_invites')
|
||||
.select('token, active')
|
||||
.eq('owner_id', uid)
|
||||
.eq('active', true)
|
||||
.order('created_at', { ascending: false })
|
||||
.limit(1)
|
||||
|
||||
if (error) throw error
|
||||
|
||||
const token = data?.[0]?.token
|
||||
if (token) {
|
||||
inviteToken.value = token
|
||||
return
|
||||
}
|
||||
|
||||
const t = newToken()
|
||||
const { error: insErr } = await supabase
|
||||
.from('patient_invites')
|
||||
.insert({ owner_id: uid, token: t, active: true })
|
||||
|
||||
if (insErr) throw insErr
|
||||
inviteToken.value = t
|
||||
}
|
||||
|
||||
async function rotateLink () {
|
||||
rotating.value = true
|
||||
try {
|
||||
const t = newToken()
|
||||
const { error } = await supabase.rpc('rotate_patient_invite_token', { p_new_token: t })
|
||||
if (error) throw error
|
||||
|
||||
inviteToken.value = t
|
||||
toast.add({ severity: 'success', summary: 'Pronto', detail: 'Novo link gerado.', life: 2000 })
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: err?.message || 'Falha ao gerar novo link.', life: 3500 })
|
||||
} finally {
|
||||
rotating.value = false
|
||||
}
|
||||
}
|
||||
|
||||
async function copyLink () {
|
||||
try {
|
||||
if (!publicUrl.value) return
|
||||
await navigator.clipboard.writeText(publicUrl.value)
|
||||
toast.add({ severity: 'success', summary: 'Copiado', detail: 'Link copiado.', life: 1500 })
|
||||
} catch {
|
||||
toast.add({ severity: 'warn', summary: 'Atenção', detail: 'Não foi possível copiar automaticamente.', life: 2500 })
|
||||
}
|
||||
}
|
||||
|
||||
function openLink () {
|
||||
if (!publicUrl.value) return
|
||||
window.open(publicUrl.value, '_blank', 'noopener')
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await loadOrCreateInvite()
|
||||
} catch (err) {
|
||||
toast.add({ severity: 'error', summary: 'Erro', detail: err?.message || 'Falha ao carregar link.', life: 3500 })
|
||||
}
|
||||
})
|
||||
</script>
|
||||
Reference in New Issue
Block a user