f3f0d831d2
PADRAO PREVIEW 3-WAY (mobile/sidebar/floating)
- Replica o pattern do MelissaNegocio em MelissaAgendador e MelissaLinkExterno.
- Mobile: preview teleporta pro topo do main, acima de tudo (diferente do
Negocio que vai pro drawer).
- Mid-desktop (1024-1339): teleporta pro fim da sidebar inline.
- Wide-desktop (>=1340): painel flutuante glass fora do fake dialog,
ancorado a +14px do right edge da .X-page com width 320px.
MELISSAAGENDADOR (.mag-page)
- Importa AgendadorPreview (componente legacy do ConfiguracoesAgendadorPage).
- isWideDesktop ref + matchMedia('(min-width: 1340px)') + previewTarget computed.
- 3 placeholders + Teleport com card mag-w--side mag-w--preview.
- Adiciona right: max(6px, min(50%, calc(100% - 1006px))) em .mag-page no
@media >=1024px (necessario pra abrir espaco pro floating).
MELISSALINKEXTERNO (.ml-page)
- Restruturacao: sidebar (Como funciona / Boas praticas) movida da DIREITA
pra ESQUERDA + mobile drawer pattern (botao Menu, Teleport, transitions,
backdrop) espelhando MelissaAgendador.
- 3-way teleport do preview com placeholders nos 3 alvos.
- ml-side ganha width 320px + scroll proprio.
- Right rule + floating preview CSS.
COMPONENTE NOVO: src/components/cadastro/CadastroExternoPreview.vue (~350L)
- Phone-frame 260px estilo AgendadorPreview replicando o CadastroPacienteExterno
publico: nav (logo Psi + chip verificado), hero (avatar 38px + nome split
firstName/lastName em accent + work_description label + clinic name),
stepper 4 dots (1 active), card etapa 1 (numero decorativo + tag "Etapa
1 de 4" + title "Sobre voce" + 3 input bars + CTA "Continuar"), powered by.
- Recebe :token e busca info via mesma edge function que o publico
(get-intake-invite-info), watch refetcha quando token rotaciona.
- Sem token ou sem dados, fallback gracioso pra placeholders ("Profissional"
+ iniciais).
CHROME EM 6 PAGINAS TABULARES (sem preview)
- Apenas o right: max(6px, min(50%, calc(100% - 1006px))) no @media >=1024px,
fazendo a janela ficar do mesmo tamanho do MelissaAgendador.
- MelissaCadastrosRecebidos (.mcr), MelissaRecorrencias (.mr), MelissaGrupos
(.mg), MelissaTags (.mt), MelissaCompromissos (.mc), MelissaMedicos (.mm).
- +9 a 12 linhas por arquivo. Cada um nao tinha @media >=1024px ainda.
ESLint: 0 errors da minha mudanca. 2 errors pre-existentes em
MelissaRecorrencias.vue (totalDone unused L235, v-for/v-bind:key L584) -
nao toquei aquelas linhas.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
477 lines
14 KiB
Vue
477 lines
14 KiB
Vue
<!--
|
|
|--------------------------------------------------------------------------
|
|
| Agência PSI
|
|
|--------------------------------------------------------------------------
|
|
| Phone-frame preview da página pública de auto-cadastro do paciente
|
|
| (CadastroPacienteExterno). Mostra como o paciente vê o link gerado
|
|
| em MelissaLinkExterno.
|
|
|
|
|
| Recebe :token e busca info do convite (terapeuta + clínica) via mesma
|
|
| edge function que o público usa (get-intake-invite-info). Sem token
|
|
| ou sem dados, renderiza placeholders ilustrativos.
|
|
|--------------------------------------------------------------------------
|
|
| © 2026 — Agência Psi
|
|
|--------------------------------------------------------------------------
|
|
-->
|
|
<script setup>
|
|
import { ref, computed, watch, onMounted } from 'vue';
|
|
import { supabase } from '@/lib/supabase/client';
|
|
|
|
const props = defineProps({
|
|
token: { type: String, default: '' }
|
|
});
|
|
|
|
const TOKEN_RX = /^[0-9a-f]{32}$|^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
|
|
const info = ref(null);
|
|
const loading = ref(false);
|
|
const avatarFailed = ref(false);
|
|
|
|
const therapist = computed(() => info.value?.therapist || null);
|
|
const clinic = computed(() => info.value?.clinic || null);
|
|
|
|
const displayName = computed(() => therapist.value?.display_name || 'Profissional');
|
|
const firstName = computed(() => String(displayName.value || '').trim().split(/\s+/)[0] || '');
|
|
const lastName = computed(() => String(displayName.value || '').trim().split(/\s+/).slice(1).join(' '));
|
|
const avatar = computed(() => (!avatarFailed.value ? therapist.value?.avatar_url || '' : ''));
|
|
const initials = computed(() => {
|
|
const parts = String(displayName.value || '').trim().split(/\s+/).filter(Boolean);
|
|
if (!parts.length) return '·';
|
|
if (parts.length === 1) return parts[0].slice(0, 2).toUpperCase();
|
|
return (parts[0][0] + parts[parts.length - 1][0]).toUpperCase();
|
|
});
|
|
|
|
const WORK_DESCRIPTION_LABEL = {
|
|
psicologo_clinico: 'Psicólogo(a) Clínico(a)',
|
|
psicanalista: 'Psicanalista',
|
|
psiquiatra: 'Psiquiatra',
|
|
psicoterapeuta: 'Psicoterapeuta',
|
|
neuropsicologo: 'Neuropsicólogo(a)',
|
|
psicologo_organizacional: 'Psic. Organizacional',
|
|
psicologo_escolar: 'Psic. Escolar',
|
|
psicologo_hospitalar: 'Psic. Hospitalar',
|
|
coach_mentor: 'Coach / Mentor(a)',
|
|
terapeuta_holistico: 'Terapeuta Holístico(a)',
|
|
outro: 'Profissional da saúde mental'
|
|
};
|
|
const workLabel = computed(() => {
|
|
const key = therapist.value?.work_description;
|
|
if (!key) return '';
|
|
return WORK_DESCRIPTION_LABEL[key] || 'Profissional da saúde mental';
|
|
});
|
|
|
|
async function fetchInviteInfo() {
|
|
if (!props.token || !TOKEN_RX.test(props.token)) {
|
|
info.value = null;
|
|
return;
|
|
}
|
|
loading.value = true;
|
|
avatarFailed.value = false;
|
|
try {
|
|
const { data, error } = await supabase.functions.invoke('get-intake-invite-info', {
|
|
body: { token: props.token }
|
|
});
|
|
if (error) return;
|
|
if (data?.ok && data.info) info.value = data.info;
|
|
} catch {
|
|
/* silencioso — preview cai pros placeholders */
|
|
} finally {
|
|
loading.value = false;
|
|
}
|
|
}
|
|
|
|
watch(() => props.token, fetchInviteInfo);
|
|
onMounted(fetchInviteInfo);
|
|
</script>
|
|
|
|
<template>
|
|
<!-- Frame de celular -->
|
|
<div class="phone-frame">
|
|
<div class="phone-notch" />
|
|
|
|
<div class="cep-root">
|
|
<!-- Nav -->
|
|
<div class="cep-nav">
|
|
<div class="cep-nav__brand">
|
|
<span class="cep-nav__logo">Ψ</span>
|
|
<span class="cep-nav__name">Agência PSI</span>
|
|
</div>
|
|
<span class="cep-nav__chip">
|
|
<i class="pi pi-check" />
|
|
</span>
|
|
</div>
|
|
|
|
<!-- Hero -->
|
|
<div class="cep-hero">
|
|
<div class="cep-hero__eyebrow">Você foi convidado(a) por</div>
|
|
<div class="cep-hero__identity">
|
|
<div class="cep-hero__avatar-wrap">
|
|
<img
|
|
v-if="avatar"
|
|
:src="avatar"
|
|
:alt="displayName"
|
|
class="cep-hero__avatar"
|
|
@error="avatarFailed = true"
|
|
/>
|
|
<div v-else class="cep-hero__avatar cep-hero__avatar--initials">{{ initials }}</div>
|
|
</div>
|
|
<div class="cep-hero__content">
|
|
<div v-if="clinic?.name" class="cep-hero__clinic">{{ clinic.name }}</div>
|
|
<div class="cep-hero__title">
|
|
<span>{{ firstName }}</span>
|
|
<span v-if="lastName" class="cep-accent">{{ lastName }}</span>
|
|
</div>
|
|
<div v-if="workLabel" class="cep-hero__work">{{ workLabel }}</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Stepper -->
|
|
<div class="cep-stepper">
|
|
<div class="cep-stepper__item is-active">
|
|
<span class="cep-stepper__num">01</span>
|
|
</div>
|
|
<span class="cep-stepper__line" />
|
|
<div class="cep-stepper__item">
|
|
<span class="cep-stepper__num">02</span>
|
|
</div>
|
|
<span class="cep-stepper__line" />
|
|
<div class="cep-stepper__item">
|
|
<span class="cep-stepper__num">03</span>
|
|
</div>
|
|
<span class="cep-stepper__line" />
|
|
<div class="cep-stepper__item">
|
|
<span class="cep-stepper__num">04</span>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Step card -->
|
|
<article class="cep-card">
|
|
<div class="cep-card__num" aria-hidden="true">01</div>
|
|
<div class="cep-card__head">
|
|
<span class="cep-card__tag">Etapa 1 de 4</span>
|
|
<span class="cep-card__progress">25%</span>
|
|
</div>
|
|
<div class="cep-card__body">
|
|
<h2 class="cep-card__title">
|
|
<span>Sobre</span>
|
|
<span class="cep-accent">você</span>
|
|
</h2>
|
|
<p class="cep-card__desc">Preencha seus dados básicos.</p>
|
|
|
|
<div class="cep-fields">
|
|
<div class="cep-field">
|
|
<label class="cep-field__label">Nome completo</label>
|
|
<div class="cep-input"></div>
|
|
</div>
|
|
<div class="cep-field">
|
|
<label class="cep-field__label">E-mail</label>
|
|
<div class="cep-input"></div>
|
|
</div>
|
|
<div class="cep-field">
|
|
<label class="cep-field__label">Telefone</label>
|
|
<div class="cep-input"></div>
|
|
</div>
|
|
</div>
|
|
|
|
<button type="button" class="cep-cta" disabled>
|
|
<span>Continuar</span>
|
|
<i class="pi pi-arrow-right text-[0.55rem]" />
|
|
</button>
|
|
</div>
|
|
</article>
|
|
|
|
<p class="cep-powered">Powered by <strong>Agência Psi</strong></p>
|
|
</div>
|
|
</div>
|
|
</template>
|
|
|
|
<style scoped>
|
|
/* ── Frame de celular ──────────────────── */
|
|
.phone-frame {
|
|
position: relative;
|
|
width: 260px;
|
|
min-height: 500px;
|
|
margin: 0 auto;
|
|
border-radius: 2.5rem;
|
|
border: 8px solid #1e293b;
|
|
background: #1e293b;
|
|
box-shadow:
|
|
0 0 0 2px #334155,
|
|
0 32px 64px rgba(0, 0, 0, 0.35),
|
|
0 8px 24px rgba(0, 0, 0, 0.2);
|
|
overflow: hidden;
|
|
}
|
|
.phone-notch {
|
|
position: absolute;
|
|
top: 0;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
width: 72px;
|
|
height: 10px;
|
|
background: #1e293b;
|
|
border-radius: 0 0 10px 10px;
|
|
z-index: 10;
|
|
}
|
|
|
|
/* ── Root (mimica fundo do CadastroExterno light) ─── */
|
|
.cep-root {
|
|
background: linear-gradient(180deg, #fafafa 0%, #f1f5f9 100%);
|
|
min-height: 100%;
|
|
padding: 12px 10px 14px;
|
|
overflow-y: auto;
|
|
max-height: 560px;
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 10px;
|
|
}
|
|
|
|
/* ── Nav ──────────────────────────────── */
|
|
.cep-nav {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
padding: 4px 6px 0;
|
|
}
|
|
.cep-nav__brand {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
color: #111827;
|
|
font-weight: 800;
|
|
}
|
|
.cep-nav__logo {
|
|
font-size: 0.92rem;
|
|
color: #6366f1;
|
|
}
|
|
.cep-nav__name {
|
|
font-size: 0.62rem;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
.cep-nav__chip {
|
|
display: grid;
|
|
place-items: center;
|
|
width: 16px;
|
|
height: 16px;
|
|
border-radius: 50%;
|
|
background: #10b98122;
|
|
color: #10b981;
|
|
font-size: 0.5rem;
|
|
}
|
|
.cep-nav__chip > i { font-size: 0.5rem; }
|
|
|
|
/* ── Hero ──────────────────────────────── */
|
|
.cep-hero {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 4px;
|
|
padding: 4px 4px 0;
|
|
}
|
|
.cep-hero__eyebrow {
|
|
font-size: 0.52rem;
|
|
font-weight: 700;
|
|
color: #6366f1;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.06em;
|
|
}
|
|
.cep-hero__identity {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
}
|
|
.cep-hero__avatar-wrap {
|
|
flex-shrink: 0;
|
|
}
|
|
.cep-hero__avatar {
|
|
width: 38px;
|
|
height: 38px;
|
|
border-radius: 50%;
|
|
border: 2px solid #fff;
|
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.12);
|
|
background: #f1f5f9;
|
|
object-fit: cover;
|
|
display: grid;
|
|
place-items: center;
|
|
color: #6366f1;
|
|
font-weight: 800;
|
|
font-size: 0.62rem;
|
|
}
|
|
.cep-hero__avatar--initials { background: linear-gradient(135deg, #e0e7ff 0%, #c7d2fe 100%); }
|
|
.cep-hero__content {
|
|
flex: 1;
|
|
min-width: 0;
|
|
}
|
|
.cep-hero__clinic {
|
|
font-size: 0.5rem;
|
|
color: #6b7280;
|
|
font-weight: 600;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
}
|
|
.cep-hero__title {
|
|
font-size: 0.78rem;
|
|
font-weight: 800;
|
|
letter-spacing: -0.02em;
|
|
color: #111827;
|
|
line-height: 1.1;
|
|
}
|
|
.cep-hero__title .cep-accent {
|
|
color: #6366f1;
|
|
margin-left: 3px;
|
|
}
|
|
.cep-hero__work {
|
|
font-size: 0.55rem;
|
|
color: #6b7280;
|
|
margin-top: 1px;
|
|
}
|
|
|
|
/* ── Stepper ───────────────────────────── */
|
|
.cep-stepper {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 2px;
|
|
padding: 2px 0;
|
|
}
|
|
.cep-stepper__item {
|
|
width: 22px;
|
|
height: 22px;
|
|
border-radius: 50%;
|
|
background: #fff;
|
|
border: 1.5px solid #e5e7eb;
|
|
display: grid;
|
|
place-items: center;
|
|
color: #9ca3af;
|
|
transition: all 150ms ease;
|
|
}
|
|
.cep-stepper__item.is-active {
|
|
background: #6366f1;
|
|
border-color: #6366f1;
|
|
color: #fff;
|
|
box-shadow: 0 4px 10px rgba(99, 102, 241, 0.32);
|
|
}
|
|
.cep-stepper__num {
|
|
font-size: 0.5rem;
|
|
font-weight: 800;
|
|
}
|
|
.cep-stepper__line {
|
|
width: 8px;
|
|
height: 1.5px;
|
|
background: #e5e7eb;
|
|
}
|
|
|
|
/* ── Card de etapa ─────────────────────── */
|
|
.cep-card {
|
|
position: relative;
|
|
background: #fff;
|
|
border-radius: 0.875rem;
|
|
border: 1px solid rgba(0, 0, 0, 0.06);
|
|
box-shadow:
|
|
0 6px 18px rgba(0, 0, 0, 0.06),
|
|
0 2px 6px rgba(0, 0, 0, 0.04);
|
|
overflow: hidden;
|
|
padding: 10px 10px 12px;
|
|
}
|
|
.cep-card__num {
|
|
position: absolute;
|
|
top: 4px;
|
|
right: 8px;
|
|
font-size: 2rem;
|
|
font-weight: 900;
|
|
color: #6366f1;
|
|
opacity: 0.08;
|
|
line-height: 1;
|
|
pointer-events: none;
|
|
}
|
|
.cep-card__head {
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: space-between;
|
|
margin-bottom: 6px;
|
|
}
|
|
.cep-card__tag {
|
|
font-size: 0.5rem;
|
|
font-weight: 700;
|
|
color: #6366f1;
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.05em;
|
|
}
|
|
.cep-card__progress {
|
|
font-size: 0.5rem;
|
|
font-weight: 700;
|
|
color: #6b7280;
|
|
}
|
|
.cep-card__title {
|
|
font-size: 0.85rem;
|
|
font-weight: 800;
|
|
letter-spacing: -0.02em;
|
|
color: #111827;
|
|
line-height: 1.1;
|
|
margin-bottom: 3px;
|
|
}
|
|
.cep-card__title .cep-accent {
|
|
color: #6366f1;
|
|
margin-left: 3px;
|
|
}
|
|
.cep-card__desc {
|
|
font-size: 0.55rem;
|
|
color: #6b7280;
|
|
line-height: 1.4;
|
|
margin-bottom: 8px;
|
|
}
|
|
|
|
/* ── Fields ────────────────────────────── */
|
|
.cep-fields {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 6px;
|
|
margin-bottom: 10px;
|
|
}
|
|
.cep-field {
|
|
display: flex;
|
|
flex-direction: column;
|
|
gap: 2px;
|
|
}
|
|
.cep-field__label {
|
|
font-size: 0.5rem;
|
|
color: #374151;
|
|
font-weight: 600;
|
|
}
|
|
.cep-input {
|
|
height: 18px;
|
|
border: 1px solid #e5e7eb;
|
|
border-radius: 0.375rem;
|
|
background: #f9fafb;
|
|
}
|
|
|
|
/* ── CTA ───────────────────────────────── */
|
|
.cep-cta {
|
|
width: 100%;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
gap: 4px;
|
|
padding: 7px 10px;
|
|
background: #6366f1;
|
|
color: #fff;
|
|
border: none;
|
|
border-radius: 0.5rem;
|
|
font-size: 0.62rem;
|
|
font-weight: 700;
|
|
cursor: default;
|
|
box-shadow: 0 4px 12px rgba(99, 102, 241, 0.28);
|
|
}
|
|
|
|
/* ── Powered ───────────────────────────── */
|
|
.cep-powered {
|
|
text-align: center;
|
|
font-size: 0.5rem;
|
|
color: #9ca3af;
|
|
margin-top: 4px;
|
|
}
|
|
.cep-powered strong {
|
|
color: #6b7280;
|
|
font-weight: 700;
|
|
}
|
|
</style>
|