Improve SSR auth bootstrap and align GraphQL communication fields
This commit is contained in:
@@ -486,8 +486,10 @@ const renderedPilotMessages = computed<PilotMessage[]>(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
async function gqlFetch<TData>(query: string, variables?: Record<string, unknown>) {
|
async function gqlFetch<TData>(query: string, variables?: Record<string, unknown>) {
|
||||||
|
const headers = process.server ? useRequestHeaders(["cookie"]) : undefined;
|
||||||
const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", {
|
const result = await $fetch<{ data?: TData; errors?: Array<{ message: string }> }>("/api/graphql", {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
|
headers,
|
||||||
body: { query, variables },
|
body: { query, variables },
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -531,6 +533,26 @@ async function loadMe() {
|
|||||||
authMe.value = data.me;
|
authMe.value = data.me;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const authResolved = ref(false);
|
||||||
|
|
||||||
|
async function bootstrapSession() {
|
||||||
|
try {
|
||||||
|
await loadMe();
|
||||||
|
if (!authMe.value) {
|
||||||
|
pilotMessages.value = [];
|
||||||
|
chatConversations.value = [];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
await Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
|
||||||
|
} catch {
|
||||||
|
authMe.value = null;
|
||||||
|
pilotMessages.value = [];
|
||||||
|
chatConversations.value = [];
|
||||||
|
} finally {
|
||||||
|
authResolved.value = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function createNewChatConversation() {
|
async function createNewChatConversation() {
|
||||||
if (chatCreating.value) return;
|
if (chatCreating.value) return;
|
||||||
chatThreadPickerOpen.value = false;
|
chatThreadPickerOpen.value = false;
|
||||||
@@ -1118,6 +1140,10 @@ async function rollbackLatestChangeSet() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.server) {
|
||||||
|
await bootstrapSession();
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
pilotHeaderText.value = pilotHeaderPhrases[Math.floor(Math.random() * pilotHeaderPhrases.length)] ?? "Every step moves you forward";
|
pilotHeaderText.value = pilotHeaderPhrases[Math.floor(Math.random() * pilotHeaderPhrases.length)] ?? "Every step moves you forward";
|
||||||
pilotMicSupported.value =
|
pilotMicSupported.value =
|
||||||
@@ -1125,12 +1151,14 @@ onMounted(() => {
|
|||||||
typeof MediaRecorder !== "undefined" &&
|
typeof MediaRecorder !== "undefined" &&
|
||||||
Boolean(navigator.mediaDevices?.getUserMedia);
|
Boolean(navigator.mediaDevices?.getUserMedia);
|
||||||
|
|
||||||
loadMe()
|
if (!authResolved.value) {
|
||||||
.then(() => {
|
void bootstrapSession().finally(() => {
|
||||||
startPilotBackgroundPolling();
|
if (authMe.value) startPilotBackgroundPolling();
|
||||||
return Promise.all([loadPilotMessages(), loadChatConversations(), refreshCrmData()]);
|
});
|
||||||
})
|
return;
|
||||||
.catch(() => {});
|
}
|
||||||
|
|
||||||
|
if (authMe.value) startPilotBackgroundPolling();
|
||||||
});
|
});
|
||||||
|
|
||||||
onBeforeUnmount(() => {
|
onBeforeUnmount(() => {
|
||||||
@@ -1764,7 +1792,7 @@ const latestPinnedItem = computed(() => selectedCommPinnedStream.value[0] ?? nul
|
|||||||
|
|
||||||
const latestPinnedLabel = computed(() => {
|
const latestPinnedLabel = computed(() => {
|
||||||
if (!latestPinnedItem.value) return "No pinned items yet";
|
if (!latestPinnedItem.value) return "No pinned items yet";
|
||||||
if (latestPinnedItem.value.kind === "pin") return latestPinnedItem.value.text;
|
if (latestPinnedItem.value.kind === "pin") return stripPinnedPrefix(latestPinnedItem.value.text);
|
||||||
return `${latestPinnedItem.value.event.title} · ${formatDay(latestPinnedItem.value.event.start)}`;
|
return `${latestPinnedItem.value.event.title} · ${formatDay(latestPinnedItem.value.event.start)}`;
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1772,6 +1800,10 @@ function normalizePinText(value: string) {
|
|||||||
return String(value ?? "").replace(/\s+/g, " ").trim();
|
return String(value ?? "").replace(/\s+/g, " ").trim();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function stripPinnedPrefix(value: string) {
|
||||||
|
return String(value ?? "").replace(/^\s*(закреплено|pinned)\s*:\s*/i, "").trim();
|
||||||
|
}
|
||||||
|
|
||||||
function isPinnedText(contact: string, value: string) {
|
function isPinnedText(contact: string, value: string) {
|
||||||
const contactName = String(contact ?? "").trim();
|
const contactName = String(contact ?? "").trim();
|
||||||
const text = normalizePinText(value);
|
const text = normalizePinText(value);
|
||||||
@@ -1781,7 +1813,7 @@ function isPinnedText(contact: string, value: string) {
|
|||||||
|
|
||||||
function entryPinText(entry: any): string {
|
function entryPinText(entry: any): string {
|
||||||
if (!entry) return "";
|
if (!entry) return "";
|
||||||
if (entry.kind === "pin") return normalizePinText(entry.text ?? "");
|
if (entry.kind === "pin") return normalizePinText(stripPinnedPrefix(entry.text ?? ""));
|
||||||
if (entry.kind === "recommendation") return normalizePinText(entry.card?.text ?? "");
|
if (entry.kind === "recommendation") return normalizePinText(entry.card?.text ?? "");
|
||||||
if (entry.kind === "event" || entry.kind === "eventAlert" || entry.kind === "eventLog") {
|
if (entry.kind === "event" || entry.kind === "eventAlert" || entry.kind === "eventLog") {
|
||||||
return normalizePinText(entry.event?.note || entry.event?.title || "");
|
return normalizePinText(entry.event?.note || entry.event?.title || "");
|
||||||
@@ -2353,7 +2385,11 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="h-[100dvh] overflow-hidden bg-base-200/35">
|
<div class="h-[100dvh] overflow-hidden bg-base-200/35">
|
||||||
<div v-if="!authMe" class="flex h-full items-center justify-center px-3">
|
<div v-if="!authResolved" class="flex h-full items-center justify-center">
|
||||||
|
<span class="loading loading-spinner loading-md text-base-content/70" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-else-if="!authMe" class="flex h-full items-center justify-center px-3">
|
||||||
<div class="card w-full max-w-sm border border-base-300 bg-base-100 shadow-sm">
|
<div class="card w-full max-w-sm border border-base-300 bg-base-100 shadow-sm">
|
||||||
<div class="card-body p-5">
|
<div class="card-body p-5">
|
||||||
<h1 class="text-lg font-semibold">Login</h1>
|
<h1 class="text-lg font-semibold">Login</h1>
|
||||||
@@ -3246,7 +3282,7 @@ async function decideFeedCard(card: FeedCard, decision: "accepted" | "rejected")
|
|||||||
>
|
>
|
||||||
<div v-if="entry.kind === 'pin'" class="flex justify-center">
|
<div v-if="entry.kind === 'pin'" class="flex justify-center">
|
||||||
<article class="w-full max-w-[460px] rounded-xl border border-base-300 bg-base-100 p-3">
|
<article class="w-full max-w-[460px] rounded-xl border border-base-300 bg-base-100 p-3">
|
||||||
<p class="text-sm text-base-content/85">{{ entry.text }}</p>
|
<p class="text-sm text-base-content/85">{{ stripPinnedPrefix(entry.text) }}</p>
|
||||||
</article>
|
</article>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -314,8 +314,8 @@ async function main() {
|
|||||||
contactId: c.id,
|
contactId: c.id,
|
||||||
text:
|
text:
|
||||||
idx % 3 === 0
|
idx % 3 === 0
|
||||||
? "Закреплено: уточнить владельца ERP, владельца данных и целевой квартал запуска."
|
? "Уточнить владельца ERP, владельца данных и целевой квартал запуска."
|
||||||
: "Закреплено: держать коммуникацию вокруг одного KPI и следующего шага.",
|
: "Держать коммуникацию вокруг одного KPI и следующего шага.",
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -388,7 +388,7 @@ async function getDashboard(auth: AuthContext | null) {
|
|||||||
kind: m.kind === "CALL" ? "call" : "message",
|
kind: m.kind === "CALL" ? "call" : "message",
|
||||||
direction: m.direction === "IN" ? "in" : "out",
|
direction: m.direction === "IN" ? "in" : "out",
|
||||||
text: m.content,
|
text: m.content,
|
||||||
audioUrl: m.audioUrl ?? "",
|
audioUrl: "",
|
||||||
duration: m.durationSec ? new Date(m.durationSec * 1000).toISOString().slice(14, 19) : "",
|
duration: m.durationSec ? new Date(m.durationSec * 1000).toISOString().slice(14, 19) : "",
|
||||||
transcript: Array.isArray(m.transcriptJson) ? ((m.transcriptJson as any) as string[]) : [],
|
transcript: Array.isArray(m.transcriptJson) ? ((m.transcriptJson as any) as string[]) : [],
|
||||||
}));
|
}));
|
||||||
@@ -542,7 +542,6 @@ async function createCommunication(auth: AuthContext | null, input: {
|
|||||||
direction: input?.direction === "in" ? "IN" : "OUT",
|
direction: input?.direction === "in" ? "IN" : "OUT",
|
||||||
channel: toDbChannel(input?.channel ?? "Phone") as any,
|
channel: toDbChannel(input?.channel ?? "Phone") as any,
|
||||||
content: (input?.text ?? "").trim(),
|
content: (input?.text ?? "").trim(),
|
||||||
audioUrl: (input?.audioUrl ?? "").trim() || null,
|
|
||||||
durationSec: typeof input?.durationSec === "number" ? input.durationSec : null,
|
durationSec: typeof input?.durationSec === "number" ? input.durationSec : null,
|
||||||
transcriptJson: Array.isArray(input?.transcript) ? input.transcript : undefined,
|
transcriptJson: Array.isArray(input?.transcript) ? input.transcript : undefined,
|
||||||
occurredAt,
|
occurredAt,
|
||||||
|
|||||||
Reference in New Issue
Block a user