got {messages,phone calls} {fetching,display} working

This commit is contained in:
m5r
2022-05-18 01:54:06 +02:00
parent 7dc9c403d8
commit 9c9b4a5af4
22 changed files with 543 additions and 133 deletions

View File

@ -1,6 +1,8 @@
import { Suspense } from "react";
import { type PhoneCall, Prisma } from "@prisma/client";
import { type LoaderFunction, json } from "@remix-run/node";
import { type LoaderFunction } from "@remix-run/node";
import { json, useLoaderData } from "superjson-remix";
import { parsePhoneNumber } from "awesome-phonenumber";
import MissingTwilioCredentials from "~/features/core/components/missing-twilio-credentials";
import PageTitle from "~/features/core/components/page-title";
@ -9,30 +11,46 @@ import InactiveSubscription from "~/features/core/components/inactive-subscripti
import PhoneCallsList from "~/features/phone-calls/components/phone-calls-list";
import { requireLoggedIn } from "~/utils/auth.server";
import db from "~/utils/db.server";
import { parsePhoneNumber } from "awesome-phonenumber";
type PhoneCallMeta = {
formattedPhoneNumber: string;
country: string | "unknown";
};
export type PhoneCallsLoaderData = { phoneCalls: (PhoneCall & { toMeta: PhoneCallMeta; fromMeta: PhoneCallMeta })[] };
export type PhoneCallsLoaderData = {
user: {
hasOngoingSubscription: boolean;
hasPhoneNumber: boolean;
};
phoneCalls: (PhoneCall & { toMeta: PhoneCallMeta; fromMeta: PhoneCallMeta })[] | undefined;
};
export const loader: LoaderFunction = async ({ request }) => {
const { organizations } = await requireLoggedIn(request);
const organizationId = organizations[0].id;
const phoneNumberId = "";
const phoneNumber = await db.phoneNumber.findFirst({ where: { organizationId, id: phoneNumberId } });
if (phoneNumber?.isFetchingCalls) {
return;
const phoneNumber = await db.phoneNumber.findUnique({
where: { organizationId_isCurrent: { organizationId, isCurrent: true } },
});
if (!phoneNumber || phoneNumber.isFetchingCalls) {
return json<PhoneCallsLoaderData>({
user: {
hasOngoingSubscription: true, // TODO
hasPhoneNumber: Boolean(phoneNumber),
},
phoneCalls: undefined,
});
}
const phoneCalls = await db.phoneCall.findMany({
where: { phoneNumberId },
where: { phoneNumberId: phoneNumber.id },
orderBy: { createdAt: Prisma.SortOrder.desc },
});
return json<PhoneCallsLoaderData>({
user: {
hasOngoingSubscription: true, // TODO
hasPhoneNumber: Boolean(phoneNumber),
},
phoneCalls: phoneCalls.map((phoneCall) => ({
...phoneCall,
fromMeta: getPhoneNumberMeta(phoneCall.from),
@ -299,13 +317,9 @@ export const loader: LoaderFunction = async ({ request }) => {
};
export default function PhoneCalls() {
const { hasFilledTwilioCredentials, hasPhoneNumber, hasOngoingSubscription } = {
hasFilledTwilioCredentials: false,
hasPhoneNumber: false,
hasOngoingSubscription: false,
};
const { hasPhoneNumber, hasOngoingSubscription } = useLoaderData<PhoneCallsLoaderData>().user;
if (!hasFilledTwilioCredentials || !hasPhoneNumber) {
if (!hasPhoneNumber) {
return (
<>
<MissingTwilioCredentials />
@ -334,10 +348,10 @@ export default function PhoneCalls() {
<>
<PageTitle className="pl-12" title="Calls" />
<section className="flex flex-grow flex-col">
<Suspense fallback={<Spinner />}>
{/* TODO: skeleton phone calls list */}
<PhoneCallsList />
</Suspense>
{/*<Suspense fallback={<Spinner />}>*/}
{/* TODO: skeleton phone calls list */}
<PhoneCallsList />
{/*</Suspense>*/}
</section>
</>
);

View File

@ -1,6 +1,7 @@
import { Suspense } from "react";
import { json, type LoaderFunction, type MetaFunction } from "@remix-run/node";
import { useLoaderData, useNavigate, useParams } from "@remix-run/react";
import type { LoaderFunction, MetaFunction } from "@remix-run/node";
import { useNavigate, useParams } from "@remix-run/react";
import { json, useLoaderData } from "superjson-remix";
import { IoCall, IoChevronBack, IoInformationCircle } from "react-icons/io5";
import { type Message, Prisma } from "@prisma/client";
@ -38,32 +39,21 @@ export const loader: LoaderFunction = async ({ request, params }) => {
return json<ConversationLoaderData>({ conversation });
async function getConversation(recipient: string): Promise<ConversationType> {
/*if (!hasFilledTwilioCredentials) {
return;
}*/
const organizationId = organizations[0].id;
const organization = await db.organization.findFirst({
where: { id: organizationId },
include: { phoneNumbers: true },
const phoneNumber = await db.phoneNumber.findUnique({
where: { organizationId_isCurrent: { organizationId, isCurrent: true } },
});
if (!organization || !organization.phoneNumbers[0]) {
throw new Error("Not found");
}
const phoneNumber = organization.phoneNumbers[0]; // TODO: use the active number, not the first one
const phoneNumberId = phoneNumber.id;
if (organization.phoneNumbers[0].isFetchingMessages) {
throw new Error("Not found");
if (!phoneNumber || phoneNumber.isFetchingMessages) {
throw new Error("unreachable");
}
const formattedPhoneNumber = parsePhoneNumber(recipient).getNumber("international");
const messages = await db.message.findMany({
where: {
phoneNumberId,
phoneNumberId: phoneNumber.id,
OR: [{ from: recipient }, { to: recipient }],
},
orderBy: { sentAt: Prisma.SortOrder.desc },
orderBy: { sentAt: Prisma.SortOrder.asc },
});
return {
recipient,
@ -91,9 +81,9 @@ export default function ConversationPage() {
<IoInformationCircle className="h-8 w-8" />
</span>
</header>
<Suspense fallback={<div className="pt-12">Loading messages with {recipient}</div>}>
<Conversation />
</Suspense>
{/*<Suspense fallback={<div className="pt-12">Loading messages with {recipient}</div>}>*/}
<Conversation />
{/*</Suspense>*/}
</>
);
}

View File

@ -1,6 +1,6 @@
import { type LoaderFunction, json } from "@remix-run/node";
import { useLoaderData } from "@remix-run/react";
import { type Message, Prisma, Direction } from "@prisma/client";
import { type LoaderFunction } from "@remix-run/node";
import { json, useLoaderData } from "superjson-remix";
import { type Message, Prisma, Direction, SubscriptionStatus } from "@prisma/client";
import { parsePhoneNumber } from "awesome-phonenumber";
import PageTitle from "~/features/core/components/page-title";
@ -11,7 +11,6 @@ import { requireLoggedIn } from "~/utils/auth.server";
export type MessagesLoaderData = {
user: {
hasFilledTwilioCredentials: boolean;
hasPhoneNumber: boolean;
};
conversations: Record<string, Conversation> | undefined;
@ -25,7 +24,7 @@ type Conversation = {
export const loader: LoaderFunction = async ({ request }) => {
const { id, organizations } = await requireLoggedIn(request);
/*const user = await db.user.findFirst({
const user = await db.user.findFirst({
where: { id },
select: {
id: true,
@ -54,12 +53,9 @@ export const loader: LoaderFunction = async ({ request }) => {
},
},
});
const organization = user!.memberships[0]!.organization;
const hasFilledTwilioCredentials = Boolean(organization?.twilioAccountSid && organization?.twilioAuthToken);*/
const hasFilledTwilioCredentials = false;
const phoneNumber = await db.phoneNumber.findFirst({
// TODO: use the active number, not the first one
where: { organizationId: organizations[0].id },
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,
@ -69,34 +65,21 @@ export const loader: LoaderFunction = async ({ request }) => {
const conversations = await getConversations();
return json<MessagesLoaderData>({
user: {
hasFilledTwilioCredentials,
hasPhoneNumber: Boolean(phoneNumber),
},
user: { hasPhoneNumber: Boolean(phoneNumber) },
conversations,
});
async function getConversations() {
if (!hasFilledTwilioCredentials) {
return;
}
const organizationId = organizations[0].id;
const organization = await db.organization.findFirst({
where: { id: organizationId },
include: { phoneNumbers: true },
const phoneNumber = await db.phoneNumber.findUnique({
where: { organizationId_isCurrent: { organizationId, isCurrent: true } },
});
if (!organization || !organization.phoneNumbers[0]) {
throw new Error("Not found");
}
const phoneNumberId = organization.phoneNumbers[0].id; // TODO: use the active number, not the first one
if (organization.phoneNumbers[0].isFetchingMessages) {
if (!phoneNumber || phoneNumber.isFetchingMessages) {
return;
}
const messages = await db.message.findMany({
where: { phoneNumberId },
where: { phoneNumberId: phoneNumber.id },
orderBy: { sentAt: Prisma.SortOrder.desc },
});
@ -118,7 +101,7 @@ export const loader: LoaderFunction = async ({ request }) => {
};
}
if (conversations[recipient].lastMessage.sentAt > message.sentAt) {
if (message.sentAt > conversations[recipient].lastMessage.sentAt) {
conversations[recipient].lastMessage = message;
}
/*conversations[recipient]!.messages.push({
@ -129,13 +112,12 @@ export const loader: LoaderFunction = async ({ request }) => {
return conversations;
}
};
export default function MessagesPage() {
const { user } = useLoaderData<MessagesLoaderData>();
if (!user.hasFilledTwilioCredentials || !user.hasPhoneNumber) {
if (!user.hasPhoneNumber) {
return (
<>
<MissingTwilioCredentials />

View File

@ -1,31 +1,36 @@
import { type LoaderFunction, json } from "@remix-run/node";
import { type PhoneNumber, Prisma } from "@prisma/client";
import { requireLoggedIn } from "~/utils/auth.server";
import settingsPhoneAction from "~/features/settings/actions/phone";
import TwilioConnect from "~/features/settings/components/phone/twilio-connect";
import PhoneNumberForm from "~/features/settings/components/phone/phone-number-form";
import { requireLoggedIn } from "~/utils/auth.server";
import getTwilioClient from "~/utils/twilio.server";
import logger from "~/utils/logger.server";
import db from "~/utils/db.server";
export type PhoneSettingsLoaderData = {
phoneNumbers: {
phoneNumber: string;
sid: string;
}[];
}
phoneNumbers: Pick<PhoneNumber, "id" | "number" | "isCurrent">[];
};
export const loader: LoaderFunction = async ({ request }) => {
const { organizations } = await requireLoggedIn(request);
const organization = organizations[0];
if (!organization.twilioAccountSid) {
throw new Error("Twilio account is not connected");
logger.warn("Twilio account is not connected");
return json<PhoneSettingsLoaderData>({ phoneNumbers: [] });
}
const twilioClient = getTwilioClient(organization);
const incomingPhoneNumbers = await twilioClient.incomingPhoneNumbers.list();
const phoneNumbers = incomingPhoneNumbers.map(({ phoneNumber, sid }) => ({ phoneNumber, sid }));
const phoneNumbers = await db.phoneNumber.findMany({
where: { organizationId: organization.id },
select: { id: true, number: true, isCurrent: true },
orderBy: { id: Prisma.SortOrder.desc },
});
return json<PhoneSettingsLoaderData>({ phoneNumbers });
};
export const action = settingsPhoneAction;
function PhoneSettings() {
return (
<div className="flex flex-col space-y-6">