prefetch stuff
This commit is contained in:
parent
8607e70007
commit
306572d947
@ -5,7 +5,7 @@ import { IoChevronForward } from "react-icons/io5";
|
|||||||
import { formatRelativeDate } from "~/features/core/helpers/date-formatter";
|
import { formatRelativeDate } from "~/features/core/helpers/date-formatter";
|
||||||
import PhoneInitLoader from "~/features/core/components/phone-init-loader";
|
import PhoneInitLoader from "~/features/core/components/phone-init-loader";
|
||||||
import EmptyMessages from "./empty-messages";
|
import EmptyMessages from "./empty-messages";
|
||||||
import type { MessagesLoaderData } from "~/routes/__app/messages";
|
import type { MessagesLoaderData } from "~/features/messages/loaders/messages";
|
||||||
|
|
||||||
export default function ConversationsList() {
|
export default function ConversationsList() {
|
||||||
const { conversations } = useLoaderData<MessagesLoaderData>();
|
const { conversations } = useLoaderData<MessagesLoaderData>();
|
||||||
@ -24,7 +24,7 @@ export default function ConversationsList() {
|
|||||||
{Object.values(conversations).map(({ recipient, formattedPhoneNumber, lastMessage }) => {
|
{Object.values(conversations).map(({ recipient, formattedPhoneNumber, lastMessage }) => {
|
||||||
return (
|
return (
|
||||||
<li key={`sms-conversation-${recipient}`} className="py-2 px-4">
|
<li key={`sms-conversation-${recipient}`} className="py-2 px-4">
|
||||||
<Link to={`/messages/${recipient}`} className="flex flex-col">
|
<Link prefetch="intent" to={`/messages/${recipient}`} className="flex flex-col">
|
||||||
<div className="flex flex-row justify-between">
|
<div className="flex flex-row justify-between">
|
||||||
<span className="font-medium">{formattedPhoneNumber ?? recipient}</span>
|
<span className="font-medium">{formattedPhoneNumber ?? recipient}</span>
|
||||||
<div className="text-gray-700 flex flex-row gap-x-1">
|
<div className="text-gray-700 flex flex-row gap-x-1">
|
||||||
|
@ -1,21 +1,20 @@
|
|||||||
import type { LoaderFunction } from "@remix-run/node";
|
import type { LoaderFunction } from "@remix-run/node";
|
||||||
|
import { json } from "superjson-remix";
|
||||||
|
import { parsePhoneNumber } from "awesome-phonenumber";
|
||||||
import { type Message, Prisma, Direction, SubscriptionStatus } from "@prisma/client";
|
import { type Message, Prisma, Direction, SubscriptionStatus } from "@prisma/client";
|
||||||
|
|
||||||
import db from "~/utils/db.server";
|
import db from "~/utils/db.server";
|
||||||
import { requireLoggedIn } from "~/utils/auth.server";
|
import { requireLoggedIn } from "~/utils/auth.server";
|
||||||
|
|
||||||
export type MessagesLoaderData = {
|
export type MessagesLoaderData = {
|
||||||
user: {
|
user: { hasPhoneNumber: boolean };
|
||||||
hasFilledTwilioCredentials: boolean;
|
|
||||||
hasPhoneNumber: boolean;
|
|
||||||
};
|
|
||||||
conversations: Record<string, Conversation> | undefined;
|
conversations: Record<string, Conversation> | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
type Conversation = {
|
type Conversation = {
|
||||||
recipient: string;
|
recipient: string;
|
||||||
formattedPhoneNumber: string;
|
formattedPhoneNumber: string;
|
||||||
messages: Message[];
|
lastMessage: Message;
|
||||||
};
|
};
|
||||||
|
|
||||||
const loader: LoaderFunction = async ({ request }) => {
|
const loader: LoaderFunction = async ({ request }) => {
|
||||||
@ -49,8 +48,65 @@ const loader: LoaderFunction = async ({ request }) => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const organization = user!.memberships[0]!.organization;
|
const organization = user!.memberships[0].organization;
|
||||||
// const hasFilledTwilioCredentials = Boolean(organization?.twilioAccountSid && organization?.twilioAuthToken);
|
const phoneNumber = await db.phoneNumber.findUnique({
|
||||||
|
where: { organizationId_isCurrent: { organizationId: organization.id, isCurrent: true } },
|
||||||
|
select: {
|
||||||
|
id: true,
|
||||||
|
organizationId: true,
|
||||||
|
number: true,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
const conversations = await getConversations();
|
||||||
|
|
||||||
|
return json<MessagesLoaderData>({
|
||||||
|
user: { hasPhoneNumber: Boolean(phoneNumber) },
|
||||||
|
conversations,
|
||||||
|
});
|
||||||
|
|
||||||
|
async function getConversations() {
|
||||||
|
const organizationId = organizations[0].id;
|
||||||
|
const phoneNumber = await db.phoneNumber.findUnique({
|
||||||
|
where: { organizationId_isCurrent: { organizationId, isCurrent: true } },
|
||||||
|
});
|
||||||
|
if (!phoneNumber || phoneNumber.isFetchingMessages) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const messages = await db.message.findMany({
|
||||||
|
where: { phoneNumberId: phoneNumber.id },
|
||||||
|
orderBy: { sentAt: Prisma.SortOrder.desc },
|
||||||
|
});
|
||||||
|
|
||||||
|
let conversations: Record<string, Conversation> = {};
|
||||||
|
for (const message of messages) {
|
||||||
|
let recipient: string;
|
||||||
|
if (message.direction === Direction.Outbound) {
|
||||||
|
recipient = message.to;
|
||||||
|
} else {
|
||||||
|
recipient = message.from;
|
||||||
|
}
|
||||||
|
const formattedPhoneNumber = parsePhoneNumber(recipient).getNumber("international");
|
||||||
|
|
||||||
|
if (!conversations[recipient]) {
|
||||||
|
conversations[recipient] = {
|
||||||
|
recipient,
|
||||||
|
formattedPhoneNumber,
|
||||||
|
lastMessage: message,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if (message.sentAt > conversations[recipient].lastMessage.sentAt) {
|
||||||
|
conversations[recipient].lastMessage = message;
|
||||||
|
}
|
||||||
|
/*conversations[recipient]!.messages.push({
|
||||||
|
...message,
|
||||||
|
content: decrypt(message.content, organization.encryptionKey),
|
||||||
|
});*/
|
||||||
|
}
|
||||||
|
|
||||||
|
return conversations;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
export default loader;
|
export default loader;
|
||||||
|
@ -81,13 +81,11 @@ export default function ConversationPage() {
|
|||||||
<strong className="absolute right-0 left-0 text-center pointer-events-none">
|
<strong className="absolute right-0 left-0 text-center pointer-events-none">
|
||||||
{conversation?.formattedPhoneNumber ?? recipient}
|
{conversation?.formattedPhoneNumber ?? recipient}
|
||||||
</strong>
|
</strong>
|
||||||
<Link to={`/outgoing-call/${encodeURI(recipient)}`} className="pr-2">
|
<Link prefetch="intent" to={`/outgoing-call/${encodeURI(recipient)}`} className="pr-2">
|
||||||
<IoCall className="h-6 w-6" />
|
<IoCall className="h-6 w-6" />
|
||||||
</Link>
|
</Link>
|
||||||
</header>
|
</header>
|
||||||
{/*<Suspense fallback={<div className="pt-12">Loading messages with {recipient}</div>}>*/}
|
|
||||||
<Conversation />
|
<Conversation />
|
||||||
{/*</Suspense>*/}
|
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -1,118 +1,12 @@
|
|||||||
import { type LoaderFunction } from "@remix-run/node";
|
import { useLoaderData } from "superjson-remix";
|
||||||
import { json, useLoaderData } from "superjson-remix";
|
|
||||||
import { type Message, Prisma, Direction, SubscriptionStatus } from "@prisma/client";
|
|
||||||
import { parsePhoneNumber } from "awesome-phonenumber";
|
|
||||||
|
|
||||||
|
import messagesLoader, { type MessagesLoaderData } from "~/features/messages/loaders/messages";
|
||||||
import PageTitle from "~/features/core/components/page-title";
|
import PageTitle from "~/features/core/components/page-title";
|
||||||
import MissingTwilioCredentials from "~/features/core/components/missing-twilio-credentials";
|
import MissingTwilioCredentials from "~/features/core/components/missing-twilio-credentials";
|
||||||
import ConversationsList from "~/features/messages/components/conversations-list";
|
import ConversationsList from "~/features/messages/components/conversations-list";
|
||||||
import db from "~/utils/db.server";
|
import NewMessageButton from "~/features/messages/components/new-message-button";
|
||||||
import { requireLoggedIn } from "~/utils/auth.server";
|
|
||||||
|
|
||||||
export type MessagesLoaderData = {
|
export const loader = messagesLoader;
|
||||||
user: {
|
|
||||||
hasPhoneNumber: boolean;
|
|
||||||
};
|
|
||||||
conversations: Record<string, Conversation> | undefined;
|
|
||||||
};
|
|
||||||
|
|
||||||
type Conversation = {
|
|
||||||
recipient: string;
|
|
||||||
formattedPhoneNumber: string;
|
|
||||||
lastMessage: Message;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const loader: LoaderFunction = async ({ request }) => {
|
|
||||||
const { id, organizations } = await requireLoggedIn(request);
|
|
||||||
const user = await db.user.findFirst({
|
|
||||||
where: { id },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
fullName: true,
|
|
||||||
email: true,
|
|
||||||
role: true,
|
|
||||||
memberships: {
|
|
||||||
include: {
|
|
||||||
organization: {
|
|
||||||
include: {
|
|
||||||
subscriptions: {
|
|
||||||
where: {
|
|
||||||
OR: [
|
|
||||||
{ status: { not: SubscriptionStatus.deleted } },
|
|
||||||
{
|
|
||||||
status: SubscriptionStatus.deleted,
|
|
||||||
cancellationEffectiveDate: { gt: new Date() },
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
orderBy: { lastEventTime: Prisma.SortOrder.desc },
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const organization = user!.memberships[0].organization;
|
|
||||||
const phoneNumber = await db.phoneNumber.findUnique({
|
|
||||||
where: { organizationId_isCurrent: { organizationId: organization.id, isCurrent: true } },
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
organizationId: true,
|
|
||||||
number: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
const conversations = await getConversations();
|
|
||||||
|
|
||||||
return json<MessagesLoaderData>({
|
|
||||||
user: { hasPhoneNumber: Boolean(phoneNumber) },
|
|
||||||
conversations,
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getConversations() {
|
|
||||||
const organizationId = organizations[0].id;
|
|
||||||
const phoneNumber = await db.phoneNumber.findUnique({
|
|
||||||
where: { organizationId_isCurrent: { organizationId, isCurrent: true } },
|
|
||||||
});
|
|
||||||
if (!phoneNumber || phoneNumber.isFetchingMessages) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const messages = await db.message.findMany({
|
|
||||||
where: { phoneNumberId: phoneNumber.id },
|
|
||||||
orderBy: { sentAt: Prisma.SortOrder.desc },
|
|
||||||
});
|
|
||||||
|
|
||||||
let conversations: Record<string, Conversation> = {};
|
|
||||||
for (const message of messages) {
|
|
||||||
let recipient: string;
|
|
||||||
if (message.direction === Direction.Outbound) {
|
|
||||||
recipient = message.to;
|
|
||||||
} else {
|
|
||||||
recipient = message.from;
|
|
||||||
}
|
|
||||||
const formattedPhoneNumber = parsePhoneNumber(recipient).getNumber("international");
|
|
||||||
|
|
||||||
if (!conversations[recipient]) {
|
|
||||||
conversations[recipient] = {
|
|
||||||
recipient,
|
|
||||||
formattedPhoneNumber,
|
|
||||||
lastMessage: message,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (message.sentAt > conversations[recipient].lastMessage.sentAt) {
|
|
||||||
conversations[recipient].lastMessage = message;
|
|
||||||
}
|
|
||||||
/*conversations[recipient]!.messages.push({
|
|
||||||
...message,
|
|
||||||
content: decrypt(message.content, organization.encryptionKey),
|
|
||||||
});*/
|
|
||||||
}
|
|
||||||
|
|
||||||
return conversations;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function MessagesPage() {
|
export default function MessagesPage() {
|
||||||
const { user } = useLoaderData<MessagesLoaderData>();
|
const { user } = useLoaderData<MessagesLoaderData>();
|
||||||
@ -130,7 +24,6 @@ export default function MessagesPage() {
|
|||||||
<>
|
<>
|
||||||
<PageTitle title="Messages" />
|
<PageTitle title="Messages" />
|
||||||
<section className="flex flex-grow flex-col">
|
<section className="flex flex-grow flex-col">
|
||||||
{/* TODO: skeleton conversations list */}
|
|
||||||
<ConversationsList />
|
<ConversationsList />
|
||||||
</section>
|
</section>
|
||||||
</>
|
</>
|
||||||
|
Loading…
Reference in New Issue
Block a user