M5: tenantship + admin members + accept_invite RPC
Modulo 5 da Fase 1 + quick wins fechados. features/tenantship/ com 2 services + 2 composables (members + invites). MembersPage.vue nova em views/pages/admin/ + rota /admin/members em routes.clinic. Migration 20260520000005 cria RPC accept_tenant_invite (SECURITY DEFINER + lock FOR UPDATE) — tenantInvitesRepository.acceptInvite agora chama RPC real (nao mais stub). SaasTenantFeaturesPage refatorada pra usar novo tenantFeatureAdminService. SetupWizardPage 2648 linhas deferido pra sessao dedicada. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
/*
|
||||
|--------------------------------------------------------------------------
|
||||
| Agência PSI
|
||||
|--------------------------------------------------------------------------
|
||||
| Arquivo: src/features/tenantship/composables/useTenantInvites.js
|
||||
|
|
||||
| Thin wrapper sobre tenantInvitesRepository. Segue
|
||||
| blueprints/composable-blueprint.md (Tipo A — thin wrapper default).
|
||||
|--------------------------------------------------------------------------
|
||||
*/
|
||||
import { ref } from 'vue';
|
||||
import { listForTenant, getByToken, sendInvite, revokeInvite, acceptInvite } from '@/features/tenantship/services/tenantInvitesRepository';
|
||||
|
||||
export function useTenantInvites() {
|
||||
const rows = ref([]);
|
||||
const loading = ref(false);
|
||||
const error = ref('');
|
||||
|
||||
async function loadForTenant({ tenantId, includeInactive } = {}) {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
rows.value = await listForTenant({ tenantId, includeInactive });
|
||||
} catch (e) {
|
||||
error.value = e?.message || 'Falha ao carregar convites.';
|
||||
rows.value = [];
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function send(payload) {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const created = await sendInvite(payload);
|
||||
// Inserir/replace na lista local sem re-fetch
|
||||
const idx = rows.value.findIndex((r) => r.id === created.id);
|
||||
if (idx >= 0) rows.value[idx] = created;
|
||||
else rows.value = [created, ...rows.value];
|
||||
return created;
|
||||
} catch (e) {
|
||||
error.value = e?.message || 'Falha ao enviar convite.';
|
||||
throw e;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
async function revoke(inviteId, opts) {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
const updated = await revokeInvite(inviteId, opts);
|
||||
const idx = rows.value.findIndex((r) => r.id === updated.id);
|
||||
if (idx >= 0) rows.value[idx] = updated;
|
||||
return updated;
|
||||
} catch (e) {
|
||||
error.value = e?.message || 'Falha ao revogar convite.';
|
||||
throw e;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* STUB — depende de RPC ainda não criada. Joga erro PT-BR explicando.
|
||||
* Ver tenantInvitesRepository.acceptInvite.
|
||||
*/
|
||||
async function accept(token) {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
return await acceptInvite(token);
|
||||
} catch (e) {
|
||||
error.value = e?.message || 'Falha ao aceitar convite.';
|
||||
throw e;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read público pelo token (anonymous). Não atualiza `rows` —
|
||||
* usado no fluxo de aceitar (link externo).
|
||||
*/
|
||||
async function fetchByToken(token) {
|
||||
loading.value = true;
|
||||
error.value = '';
|
||||
try {
|
||||
return await getByToken(token);
|
||||
} catch (e) {
|
||||
error.value = e?.message || 'Falha ao carregar convite.';
|
||||
return null;
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
}
|
||||
|
||||
return { rows, loading, error, loadForTenant, send, revoke, accept, fetchByToken };
|
||||
}
|
||||
Reference in New Issue
Block a user