Setup Wizard
This commit is contained in:
@@ -123,8 +123,9 @@
|
||||
<div class="prof-card__sep" />
|
||||
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
<!-- Nome -->
|
||||
<div class="col-span-12 md:col-span-7">
|
||||
|
||||
<!-- Nome completo -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<InputText id="prof_name" v-model="form.full_name" class="w-full" autocomplete="name" @input="markDirty" />
|
||||
<label for="prof_name">Nome completo</label>
|
||||
@@ -132,8 +133,34 @@
|
||||
<small class="prof-hint">Aparece no menu, cabeçalhos e registros.</small>
|
||||
</div>
|
||||
|
||||
<!-- Como a Agência PSI deveria te chamar? -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<InputText id="prof_nickname" v-model="form.nickname" class="w-full" autocomplete="nickname" @input="markDirty" />
|
||||
<label for="prof_nickname">Como a Agência PSI deveria te chamar?</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Apelido ou nome preferido para comunicação.</small>
|
||||
</div>
|
||||
|
||||
<!-- O que melhor descreve seu trabalho? -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<Select
|
||||
id="prof_work_desc"
|
||||
v-model="form.work_description"
|
||||
:options="workDescriptionOptions"
|
||||
optionLabel="label"
|
||||
optionValue="value"
|
||||
class="w-full"
|
||||
@change="markDirty"
|
||||
/>
|
||||
<label for="prof_work_desc">O que melhor descreve seu trabalho?</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Exibido no seu perfil público.</small>
|
||||
</div>
|
||||
|
||||
<!-- E-mail -->
|
||||
<div class="col-span-12 md:col-span-5">
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<InputText id="prof_email" :modelValue="userEmail" class="w-full" disabled />
|
||||
<label for="prof_email">E-mail</label>
|
||||
@@ -141,27 +168,44 @@
|
||||
<small class="prof-hint">Gerenciado pelo Supabase Auth.</small>
|
||||
</div>
|
||||
|
||||
<!-- Informe seu trabalho (somente quando 'outro') -->
|
||||
<Transition name="prof-slide">
|
||||
<div v-if="form.work_description === 'outro'" class="col-span-12">
|
||||
<FloatLabel variant="on">
|
||||
<InputText
|
||||
id="prof_work_other"
|
||||
v-model="form.work_description_other"
|
||||
class="w-full"
|
||||
autocomplete="off"
|
||||
@input="markDirty"
|
||||
/>
|
||||
<label for="prof_work_other">Informe qual é o seu trabalho</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Descreva brevemente sua atuação profissional.</small>
|
||||
</div>
|
||||
</Transition>
|
||||
|
||||
<!-- Bio -->
|
||||
<div class="col-span-12 md:col-span-7">
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<Textarea
|
||||
id="prof_bio"
|
||||
v-model="form.bio"
|
||||
class="w-full"
|
||||
rows="5"
|
||||
maxlength="2000"
|
||||
maxlength="300"
|
||||
@input="markDirty"
|
||||
/>
|
||||
<label for="prof_bio">Bio</label>
|
||||
</FloatLabel>
|
||||
<div class="prof-hint flex justify-between">
|
||||
<span>Breve descrição sobre você.</span>
|
||||
<span>{{ (form.bio || '').length }}/2000</span>
|
||||
<span>{{ (form.bio || '').length }}/300</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Telefone -->
|
||||
<div class="col-span-12 md:col-span-5">
|
||||
<!-- Whatsapp -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<InputMask
|
||||
id="prof_phone"
|
||||
@@ -171,20 +215,195 @@
|
||||
:autoClear="false"
|
||||
@update:modelValue="markDirty"
|
||||
/>
|
||||
<label for="prof_phone">Telefone</label>
|
||||
<label for="prof_phone">Whatsapp</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Opcional.</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 02 AVATAR ──────────────────────────────────── -->
|
||||
<!-- ── 02 SITES E REDES SOCIAIS ─────────────────────── -->
|
||||
<div
|
||||
id="redes-sociais"
|
||||
class="prof-card scroll-mt-20"
|
||||
style="--c:#E879F9;--c-dim:rgba(232,121,249,0.08);--c-border:rgba(232,121,249,0.2)"
|
||||
>
|
||||
<div class="prof-card__num">02</div>
|
||||
<div class="prof-card__shine" />
|
||||
|
||||
<div class="prof-card__head">
|
||||
<div class="prof-card__icon"><i class="pi pi-share-alt" /></div>
|
||||
<span class="prof-card__tag">Sites e Redes Sociais</span>
|
||||
</div>
|
||||
|
||||
<div class="prof-card__title">Seus Sites e Redes Sociais</div>
|
||||
<div class="prof-card__subtitle">Links exibidos no seu perfil público.</div>
|
||||
|
||||
<div class="prof-card__sep" />
|
||||
|
||||
<div class="grid grid-cols-12 gap-4">
|
||||
|
||||
<!-- Site -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-globe" />
|
||||
<InputText
|
||||
id="prof_site"
|
||||
v-model="form.site_url"
|
||||
class="w-full"
|
||||
type="url"
|
||||
@input="markDirty"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="prof_site">Endereço do site</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Ex: https://seuperfil.com.br</small>
|
||||
</div>
|
||||
|
||||
<!-- Instagram -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-instagram" />
|
||||
<InputText
|
||||
id="prof_instagram"
|
||||
v-model="form.social_instagram"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="prof_instagram">Instagram</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Ex: @seuperfil</small>
|
||||
</div>
|
||||
|
||||
<!-- Youtube -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-youtube" />
|
||||
<InputText
|
||||
id="prof_youtube"
|
||||
v-model="form.social_youtube"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="prof_youtube">YouTube</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Ex: @seucanal</small>
|
||||
</div>
|
||||
|
||||
<!-- Facebook -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-facebook" />
|
||||
<InputText
|
||||
id="prof_facebook"
|
||||
v-model="form.social_facebook"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="prof_facebook">Facebook</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Ex: /suapagina</small>
|
||||
</div>
|
||||
|
||||
<!-- X -->
|
||||
<div class="col-span-12 md:col-span-6">
|
||||
<FloatLabel variant="on">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-twitter" />
|
||||
<InputText
|
||||
id="prof_x"
|
||||
v-model="form.social_x"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
</IconField>
|
||||
<label for="prof_x">X (Twitter)</label>
|
||||
</FloatLabel>
|
||||
<small class="prof-hint">Ex: @seuuser</small>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Outras redes -->
|
||||
<div class="mt-5">
|
||||
<div class="flex items-center justify-between mb-3">
|
||||
<div>
|
||||
<div class="text-sm font-semibold text-[var(--text-color)]">Outras redes ou links</div>
|
||||
<div class="text-xs text-[var(--text-color-secondary)]">Adicione qualquer outra rede social, podcast, link ou perfil.</div>
|
||||
</div>
|
||||
<Button
|
||||
icon="pi pi-plus"
|
||||
label="Adicionar"
|
||||
severity="secondary"
|
||||
size="small"
|
||||
outlined
|
||||
class="rounded-full"
|
||||
@click="addCustomSocial"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div v-if="customSocials.length" class="flex flex-col gap-3">
|
||||
<div
|
||||
v-for="(item, idx) in customSocials"
|
||||
:key="idx"
|
||||
class="flex items-center gap-2"
|
||||
>
|
||||
<FloatLabel variant="on" class="flex-1">
|
||||
<InputText
|
||||
:id="`prof_cs_name_${idx}`"
|
||||
v-model="item.name"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
<label :for="`prof_cs_name_${idx}`">Nome da rede</label>
|
||||
</FloatLabel>
|
||||
|
||||
<FloatLabel variant="on" class="flex-[2]">
|
||||
<IconField>
|
||||
<InputIcon class="pi pi-link" />
|
||||
<InputText
|
||||
:id="`prof_cs_url_${idx}`"
|
||||
v-model="item.url"
|
||||
class="w-full"
|
||||
@input="markDirty"
|
||||
/>
|
||||
</IconField>
|
||||
<label :for="`prof_cs_url_${idx}`">URL / usuário</label>
|
||||
</FloatLabel>
|
||||
|
||||
<Button
|
||||
icon="pi pi-trash"
|
||||
severity="danger"
|
||||
text
|
||||
rounded
|
||||
size="small"
|
||||
v-tooltip.top="'Remover'"
|
||||
@click="removeCustomSocial(idx)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p v-else class="text-xs text-[var(--text-color-secondary)] italic mt-1">
|
||||
Nenhuma rede adicional cadastrada ainda.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 03 AVATAR ──────────────────────────────────── -->
|
||||
<div
|
||||
id="avatar"
|
||||
class="prof-card scroll-mt-20"
|
||||
style="--c:#4ADE80;--c-dim:rgba(74,222,128,0.08);--c-border:rgba(74,222,128,0.2)"
|
||||
>
|
||||
<div class="prof-card__num">02</div>
|
||||
<div class="prof-card__num">03</div>
|
||||
<div class="prof-card__shine" />
|
||||
|
||||
<div class="prof-card__head">
|
||||
@@ -272,7 +491,7 @@
|
||||
class="prof-card scroll-mt-20"
|
||||
style="--c:#A78BFA;--c-dim:rgba(167,139,250,0.08);--c-border:rgba(167,139,250,0.2)"
|
||||
>
|
||||
<div class="prof-card__num">03</div>
|
||||
<div class="prof-card__num">04</div>
|
||||
<div class="prof-card__shine" />
|
||||
|
||||
<div class="prof-card__head">
|
||||
@@ -404,7 +623,7 @@
|
||||
class="prof-card scroll-mt-20"
|
||||
style="--c:#FB923C;--c-dim:rgba(251,146,60,0.08);--c-border:rgba(251,146,60,0.2)"
|
||||
>
|
||||
<div class="prof-card__num">04</div>
|
||||
<div class="prof-card__num">05</div>
|
||||
<div class="prof-card__shine" />
|
||||
|
||||
<div class="prof-card__head">
|
||||
@@ -526,13 +745,13 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ── 05 SEGURANÇA ───────────────────────────────── -->
|
||||
<!-- ── 07 SEGURANÇA ───────────────────────────────── -->
|
||||
<div
|
||||
id="seguranca"
|
||||
class="prof-card scroll-mt-20"
|
||||
style="--c:#F87171;--c-dim:rgba(248,113,113,0.08);--c-border:rgba(248,113,113,0.2)"
|
||||
>
|
||||
<div class="prof-card__num">05</div>
|
||||
<div class="prof-card__num">07</div>
|
||||
<div class="prof-card__shine" />
|
||||
|
||||
<div class="prof-card__head">
|
||||
@@ -623,6 +842,7 @@ const { setVariant } = _useLayout()
|
||||
import Textarea from 'primevue/textarea'
|
||||
import InputMask from 'primevue/inputmask'
|
||||
import Checkbox from 'primevue/checkbox'
|
||||
import Select from 'primevue/select'
|
||||
|
||||
import { supabase } from '@/lib/supabase/client'
|
||||
import { useLayout } from '@/layout/composables/layout'
|
||||
@@ -661,10 +881,19 @@ const ui = reactive({
|
||||
// Perfil
|
||||
const form = reactive({
|
||||
full_name: '',
|
||||
nickname: '',
|
||||
work_description: '',
|
||||
work_description_other: '',
|
||||
avatar_url: '',
|
||||
bio: '',
|
||||
phone: '',
|
||||
|
||||
site_url: '',
|
||||
social_instagram: '',
|
||||
social_youtube: '',
|
||||
social_facebook: '',
|
||||
social_x: '',
|
||||
|
||||
language: 'pt-BR',
|
||||
timezone: 'America/Sao_Paulo',
|
||||
|
||||
@@ -673,13 +902,41 @@ const form = reactive({
|
||||
notify_news: false
|
||||
})
|
||||
|
||||
const customSocials = ref([])
|
||||
|
||||
function addCustomSocial () {
|
||||
customSocials.value.push({ name: '', url: '' })
|
||||
markDirty()
|
||||
}
|
||||
|
||||
function removeCustomSocial (idx) {
|
||||
customSocials.value.splice(idx, 1)
|
||||
markDirty()
|
||||
}
|
||||
|
||||
const workDescriptionOptions = [
|
||||
{ label: 'Psicólogo(a) Clínico(a)', value: 'psicologo_clinico' },
|
||||
{ label: 'Psicanalista', value: 'psicanalista' },
|
||||
{ label: 'Psiquiatra', value: 'psiquiatra' },
|
||||
{ label: 'Psicoterapeuta', value: 'psicoterapeuta' },
|
||||
{ label: 'Neuropsicólogo(a)', value: 'neuropsicologo' },
|
||||
{ label: 'Psicólogo(a) Organizacional', value: 'psicologo_organizacional' },
|
||||
{ label: 'Psicólogo(a) Escolar / Educacional', value: 'psicologo_escolar' },
|
||||
{ label: 'Psicólogo(a) Hospitalar', value: 'psicologo_hospitalar' },
|
||||
{ label: 'Psicólogo(a) Jurídico(a)', value: 'psicologo_juridico' },
|
||||
{ label: 'Coach / Mentor(a)', value: 'coach_mentor' },
|
||||
{ label: 'Terapeuta Holístico(a)', value: 'terapeuta_holistico' },
|
||||
{ label: 'Outro', value: 'outro' },
|
||||
]
|
||||
|
||||
const sections = [
|
||||
{ id: 'conta', label: 'Conta', icon: 'pi pi-user' },
|
||||
{ id: 'avatar', label: 'Avatar', icon: 'pi pi-image' },
|
||||
{ id: 'layout', label: 'Aparência', icon: 'pi pi-palette' },
|
||||
{ id: 'preferencias', label: 'Preferências', icon: 'pi pi-sliders-h' },
|
||||
{ id: 'seguranca', label: 'Segurança', icon: 'pi pi-shield' },
|
||||
{ id: 'layout-variant', label: 'Layout', icon: 'pi pi-th-large' }
|
||||
{ id: 'conta', label: 'Conta', icon: 'pi pi-user' },
|
||||
{ id: 'redes-sociais', label: 'Sites e Redes', icon: 'pi pi-share-alt' },
|
||||
{ id: 'avatar', label: 'Avatar', icon: 'pi pi-image' },
|
||||
{ id: 'layout', label: 'Aparência', icon: 'pi pi-palette' },
|
||||
{ id: 'preferencias', label: 'Preferências', icon: 'pi pi-sliders-h' },
|
||||
{ id: 'layout-variant', label: 'Layout', icon: 'pi pi-th-large' },
|
||||
{ id: 'seguranca', label: 'Segurança', icon: 'pi pi-shield' },
|
||||
]
|
||||
|
||||
const activeSection = ref('conta')
|
||||
@@ -968,13 +1225,15 @@ async function loadUserSettings (uid) {
|
||||
if (settings.surface_color && !safeEq(settings.surface_color, layoutConfig.surface)) layoutConfig.surface = settings.surface_color
|
||||
if (settings.menu_mode && !safeEq(settings.menu_mode, layoutConfig.menuMode)) {
|
||||
layoutConfig.menuMode = settings.menu_mode
|
||||
try { changeMenuMode?.(settings.menu_mode) } catch {
|
||||
try { changeMenuMode?.({ value: settings.menu_mode }) } catch {}
|
||||
}
|
||||
// Não chama changeMenuMode() — ela reseta staticMenuInactive e outros estados,
|
||||
// fazendo a sidebar desaparecer ao entrar na página.
|
||||
}
|
||||
|
||||
// layout variant
|
||||
if (settings.layout_variant === 'rail' || settings.layout_variant === 'classic') {
|
||||
// layout variant — só aplica se mudou, para não resetar o estado do layout
|
||||
if (
|
||||
(settings.layout_variant === 'rail' || settings.layout_variant === 'classic') &&
|
||||
settings.layout_variant !== layoutConfig.variant
|
||||
) {
|
||||
setVariant(settings.layout_variant)
|
||||
}
|
||||
|
||||
@@ -1026,7 +1285,7 @@ async function loadProfile () {
|
||||
|
||||
const { data: prof, error: pErr } = await supabase
|
||||
.from('profiles')
|
||||
.select('full_name, avatar_url, phone, bio, language, timezone, notify_system_email, notify_reminders, notify_news')
|
||||
.select('full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news')
|
||||
.eq('id', user.id)
|
||||
.maybeSingle()
|
||||
|
||||
@@ -1035,6 +1294,18 @@ async function loadProfile () {
|
||||
form.avatar_url = prof.avatar_url ?? form.avatar_url
|
||||
form.phone = prof.phone ?? ''
|
||||
form.bio = prof.bio ?? ''
|
||||
form.nickname = prof.nickname ?? ''
|
||||
form.work_description = prof.work_description ?? ''
|
||||
form.work_description_other = prof.work_description_other ?? ''
|
||||
form.site_url = prof.site_url ?? ''
|
||||
form.social_instagram = prof.social_instagram ?? ''
|
||||
form.social_youtube = prof.social_youtube ?? ''
|
||||
form.social_facebook = prof.social_facebook ?? ''
|
||||
form.social_x = prof.social_x ?? ''
|
||||
|
||||
if (Array.isArray(prof.social_custom)) {
|
||||
customSocials.value = prof.social_custom
|
||||
}
|
||||
|
||||
form.language = prof.language ?? form.language
|
||||
form.timezone = prof.timezone ?? form.timezone
|
||||
@@ -1087,6 +1358,15 @@ async function saveAll () {
|
||||
avatar_url: metaPayload.avatar_url,
|
||||
phone: String(form.phone || '').trim() || null,
|
||||
bio: String(form.bio || '').trim() || null,
|
||||
nickname: String(form.nickname || '').trim() || null,
|
||||
work_description: String(form.work_description || '').trim() || null,
|
||||
work_description_other: form.work_description === 'outro' ? (String(form.work_description_other || '').trim() || null) : null,
|
||||
site_url: String(form.site_url || '').trim() || null,
|
||||
social_instagram: String(form.social_instagram || '').trim() || null,
|
||||
social_youtube: String(form.social_youtube || '').trim() || null,
|
||||
social_facebook: String(form.social_facebook || '').trim() || null,
|
||||
social_x: String(form.social_x || '').trim() || null,
|
||||
social_custom: customSocials.value.filter(s => s.name || s.url),
|
||||
|
||||
language: form.language || 'pt-BR',
|
||||
timezone: form.timezone || 'America/Sao_Paulo',
|
||||
@@ -1100,7 +1380,7 @@ async function saveAll () {
|
||||
.from('profiles')
|
||||
.update(profilePayload)
|
||||
.eq('id', userId.value)
|
||||
.select('id, role, full_name, avatar_url, phone, bio, language, timezone, notify_system_email, notify_reminders, notify_news, updated_at')
|
||||
.select('id, role, full_name, avatar_url, phone, bio, nickname, work_description, work_description_other, site_url, social_instagram, social_youtube, social_facebook, social_x, social_custom, language, timezone, notify_system_email, notify_reminders, notify_news, updated_at')
|
||||
.single()
|
||||
|
||||
if (pErr2) {
|
||||
@@ -1644,4 +1924,39 @@ onBeforeUnmount(() => {
|
||||
from { opacity: 0; transform: translateY(14px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ─── Social addons ─────────────────────────────────────── */
|
||||
.social-addon {
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
width: 2.75rem; flex-shrink: 0;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.social-addon--site { color: var(--text-color-secondary); }
|
||||
.social-addon--instagram { color: #E1306C; }
|
||||
.social-addon--youtube { color: #FF0000; }
|
||||
.social-addon--facebook { color: #1877F2; }
|
||||
.social-addon--x { color: var(--text-color); }
|
||||
|
||||
/* FloatLabel com inputgroup: label offset pelo addon */
|
||||
.social-float-label {
|
||||
left: 2.85rem !important;
|
||||
}
|
||||
|
||||
/* Fix FloatLabel wrapping inputgroup */
|
||||
.p-floatlabel:has(.p-inputgroup) { display: block; }
|
||||
.p-floatlabel .p-inputgroup { width: 100%; }
|
||||
.p-floatlabel .p-inputgroup .p-inputtext {
|
||||
border-radius: 0 var(--p-inputtext-border-radius, 8px) var(--p-inputtext-border-radius, 8px) 0 !important;
|
||||
}
|
||||
|
||||
/* ─── Transition "outro" ────────────────────────────────── */
|
||||
.prof-slide-enter-active,
|
||||
.prof-slide-leave-active {
|
||||
transition: opacity 0.2s ease, max-height 0.25s ease, margin 0.2s ease;
|
||||
max-height: 6rem; overflow: hidden;
|
||||
}
|
||||
.prof-slide-enter-from,
|
||||
.prof-slide-leave-to {
|
||||
opacity: 0; max-height: 0; margin: 0;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user