got {messages,phone calls} {fetching,display} working
This commit is contained in:
@ -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>
|
||||
</>
|
||||
);
|
||||
|
@ -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>*/}
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -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 />
|
||||
|
@ -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">
|
||||
|
Reference in New Issue
Block a user