prefetch stuff

This commit is contained in:
m5r 2022-05-20 01:16:38 +02:00
parent 8607e70007
commit 306572d947
4 changed files with 70 additions and 123 deletions

View File

@ -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">

View File

@ -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;

View File

@ -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>
); );
} }

View File

@ -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>
</> </>