add import messages/calls ui feedback
This commit is contained in:
parent
8f0a6f7060
commit
2f45e1d9a8
23
app/core/components/phone-init-loader.tsx
Normal file
23
app/core/components/phone-init-loader.tsx
Normal file
@ -0,0 +1,23 @@
|
||||
export default function PhoneInitLoader() {
|
||||
return (
|
||||
<div className="px-4 my-auto text-center space-y-2">
|
||||
<svg
|
||||
className="animate-spin mx-auto h-5 w-5 text-primary-700"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
fill="none"
|
||||
viewBox="0 0 24 24"
|
||||
>
|
||||
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" />
|
||||
<path
|
||||
className="opacity-75"
|
||||
fill="currentColor"
|
||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
||||
/>
|
||||
</svg>
|
||||
<p>We're finalizing your cloud phone initialization.</p>
|
||||
<p>
|
||||
You don't have to refresh this page, we will do it automatically for you when your phone is ready.
|
||||
</p>
|
||||
</div>
|
||||
);
|
||||
}
|
@ -35,7 +35,7 @@ const Layout: FunctionComponent<Props> = ({ children, title, pageTitle = title,
|
||||
<div className="h-full w-full overflow-hidden fixed bg-gray-50">
|
||||
<div className="flex flex-col w-full h-full">
|
||||
<div className="flex flex-col flex-1 w-full overflow-y-auto">
|
||||
<main className="flex-1 my-0 h-full">
|
||||
<main className="flex flex-col flex-1 my-0 h-full">
|
||||
<ErrorBoundary>{children}</ErrorBoundary>
|
||||
</main>
|
||||
</div>
|
||||
|
@ -37,6 +37,22 @@ const insertMessagesQueue = Queue<Payload>(
|
||||
.sort((a, b) => a.sentAt.getTime() - b.sentAt.getTime());
|
||||
|
||||
await db.message.createMany({ data: sms, skipDuplicates: true });
|
||||
|
||||
const processingState = await db.processingPhoneNumber.findFirst({ where: { organizationId, phoneNumberId } });
|
||||
if (!processingState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (processingState.hasFetchedCalls) {
|
||||
await db.processingPhoneNumber.delete({
|
||||
where: { organizationId_phoneNumberId: { organizationId, phoneNumberId } },
|
||||
});
|
||||
} else {
|
||||
await db.processingPhoneNumber.update({
|
||||
where: { organizationId_phoneNumberId: { organizationId, phoneNumberId } },
|
||||
data: { hasFetchedMessages: true },
|
||||
});
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -3,9 +3,22 @@ import { IoChevronForward } from "react-icons/io5";
|
||||
|
||||
import getConversationsQuery from "../queries/get-conversations";
|
||||
import { formatRelativeDate } from "../../core/helpers/date-formatter";
|
||||
import { useEffect } from "react";
|
||||
import PhoneInitLoader from "../../core/components/phone-init-loader";
|
||||
|
||||
export default function ConversationsList() {
|
||||
const conversations = useQuery(getConversationsQuery, {})[0];
|
||||
const [conversations, query] = useQuery(getConversationsQuery, {});
|
||||
|
||||
useEffect(() => {
|
||||
if (!conversations) {
|
||||
const pollInterval = setInterval(() => query.refetch(), 1500);
|
||||
return () => clearInterval(pollInterval);
|
||||
}
|
||||
}, [conversations, query]);
|
||||
|
||||
if (!conversations) {
|
||||
return <PhoneInitLoader />;
|
||||
}
|
||||
|
||||
if (Object.keys(conversations).length === 0) {
|
||||
return <div>empty state</div>;
|
||||
|
@ -26,9 +26,11 @@ const Messages: BlitzPage = () => {
|
||||
<div className="flex flex-col space-y-6 p-3">
|
||||
<h2 className="text-3xl font-bold">Messages</h2>
|
||||
</div>
|
||||
<Suspense fallback="Loading...">
|
||||
<ConversationsList />
|
||||
</Suspense>
|
||||
<section className="flex flex-grow flex-col">
|
||||
<Suspense fallback="Loading...">
|
||||
<ConversationsList />
|
||||
</Suspense>
|
||||
</section>
|
||||
<NewMessageButton onClick={() => setIsOpen(true)} />
|
||||
<NewMessageBottomSheet />
|
||||
</>
|
||||
|
@ -27,6 +27,12 @@ export default resolver.pipe(
|
||||
}
|
||||
|
||||
const phoneNumberId = organization.phoneNumbers[0]!.id;
|
||||
|
||||
const processingState = await db.processingPhoneNumber.findFirst({ where: { organizationId, phoneNumberId } });
|
||||
if (processingState && !processingState.hasFetchedMessages) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messages = await db.message.findMany({
|
||||
where: { organizationId, phoneNumberId },
|
||||
orderBy: { sentAt: Prisma.SortOrder.desc },
|
||||
|
@ -46,6 +46,14 @@ export default resolver.pipe(resolver.zod(Body), resolver.authorize(), async ({
|
||||
|
||||
const phoneNumberId = phoneNumberSid;
|
||||
await Promise.all([
|
||||
db.processingPhoneNumber.create({
|
||||
data: {
|
||||
organizationId,
|
||||
phoneNumberId,
|
||||
hasFetchedMessages: false,
|
||||
hasFetchedCalls: false,
|
||||
},
|
||||
}),
|
||||
fetchMessagesQueue.enqueue(
|
||||
{ organizationId, phoneNumberId },
|
||||
{ id: `fetch-messages-${organizationId}-${phoneNumberId}` },
|
||||
|
@ -34,6 +34,22 @@ const insertCallsQueue = Queue<Payload>("api/queue/insert-calls", async ({ calls
|
||||
.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime());
|
||||
|
||||
await db.phoneCall.createMany({ data: phoneCalls, skipDuplicates: true });
|
||||
|
||||
const processingState = await db.processingPhoneNumber.findFirst({ where: { organizationId, phoneNumberId } });
|
||||
if (!processingState) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (processingState.hasFetchedMessages) {
|
||||
await db.processingPhoneNumber.delete({
|
||||
where: { organizationId_phoneNumberId: { organizationId, phoneNumberId } },
|
||||
});
|
||||
} else {
|
||||
await db.processingPhoneNumber.update({
|
||||
where: { organizationId_phoneNumberId: { organizationId, phoneNumberId } },
|
||||
data: { hasFetchedCalls: true },
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
export default insertCallsQueue;
|
||||
|
@ -1,12 +1,25 @@
|
||||
import { useEffect } from "react";
|
||||
import { HiPhoneMissedCall, HiPhoneOutgoing } from "react-icons/hi";
|
||||
|
||||
import { Direction } from "../../../db";
|
||||
import usePhoneCalls from "../hooks/use-phone-calls";
|
||||
import { formatRelativeDate } from "../../core/helpers/date-formatter";
|
||||
import clsx from "clsx";
|
||||
|
||||
import { Direction } from "../../../db";
|
||||
import PhoneInitLoader from "../../core/components/phone-init-loader";
|
||||
import usePhoneCalls from "../hooks/use-phone-calls";
|
||||
import { formatRelativeDate } from "../../core/helpers/date-formatter";
|
||||
|
||||
export default function PhoneCallsList() {
|
||||
const phoneCalls = usePhoneCalls()[0];
|
||||
const [phoneCalls, query] = usePhoneCalls();
|
||||
|
||||
useEffect(() => {
|
||||
if (!phoneCalls) {
|
||||
const pollInterval = setInterval(() => query.refetch(), 1500);
|
||||
return () => clearInterval(pollInterval);
|
||||
}
|
||||
}, [phoneCalls, query]);
|
||||
|
||||
if (!phoneCalls) {
|
||||
return <PhoneInitLoader />;
|
||||
}
|
||||
|
||||
if (phoneCalls.length === 0) {
|
||||
return <div>empty state</div>;
|
||||
|
@ -14,9 +14,11 @@ const PhoneCalls: BlitzPage = () => {
|
||||
<div className="flex flex-col space-y-6 py-3 pl-12">
|
||||
<h2 className="text-3xl font-bold">Calls</h2>
|
||||
</div>
|
||||
<Suspense fallback="Loading...">
|
||||
<PhoneCallsList />
|
||||
</Suspense>
|
||||
<section className="flex flex-grow flex-col">
|
||||
<Suspense fallback="Loading...">
|
||||
<PhoneCallsList />
|
||||
</Suspense>
|
||||
</section>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -10,6 +10,11 @@ const Body = z.object({
|
||||
|
||||
export default resolver.pipe(resolver.zod(Body), resolver.authorize(), async ({ phoneNumberId }, context) => {
|
||||
const organizationId = context.session.orgId;
|
||||
const processingState = await db.processingPhoneNumber.findFirst({ where: { organizationId, phoneNumberId } });
|
||||
if (processingState && !processingState.hasFetchedCalls) {
|
||||
return;
|
||||
}
|
||||
|
||||
const phoneCalls = await db.phoneCall.findMany({
|
||||
where: { organizationId, phoneNumberId },
|
||||
orderBy: { createdAt: Prisma.SortOrder.desc },
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useEffect } from "react";
|
||||
import { useMutation, useRouter } from "blitz";
|
||||
import { useForm } from "react-hook-form";
|
||||
import * as Panelbear from "@panelbear/panelbear-js";
|
||||
|
@ -0,0 +1,19 @@
|
||||
-- CreateTable
|
||||
CREATE TABLE "ProcessingPhoneNumber" (
|
||||
"createdAt" TIMESTAMPTZ NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
"hasFetchedMessages" BOOLEAN NOT NULL,
|
||||
"hasFetchedCalls" BOOLEAN NOT NULL,
|
||||
"phoneNumberId" TEXT NOT NULL,
|
||||
"organizationId" TEXT NOT NULL,
|
||||
|
||||
PRIMARY KEY ("organizationId","phoneNumberId")
|
||||
);
|
||||
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "ProcessingPhoneNumber_phoneNumberId_unique" ON "ProcessingPhoneNumber"("phoneNumberId");
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProcessingPhoneNumber" ADD FOREIGN KEY ("phoneNumberId") REFERENCES "PhoneNumber"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
||||
|
||||
-- AddForeignKey
|
||||
ALTER TABLE "ProcessingPhoneNumber" ADD FOREIGN KEY ("organizationId") REFERENCES "Organization"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
@ -31,6 +31,7 @@ model Organization {
|
||||
notificationSubscriptions NotificationSubscription[]
|
||||
messages Message[]
|
||||
phoneCalls PhoneCall[]
|
||||
processingPhoneNumbers ProcessingPhoneNumber[]
|
||||
|
||||
@@unique([id, twilioAccountSid])
|
||||
}
|
||||
@ -194,12 +195,25 @@ model PhoneNumber {
|
||||
phoneCalls PhoneCall[]
|
||||
notificationSubscriptions NotificationSubscription[]
|
||||
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
organizationId String
|
||||
processingPhoneNumber ProcessingPhoneNumber?
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
organizationId String
|
||||
|
||||
@@unique([organizationId, id])
|
||||
}
|
||||
|
||||
model ProcessingPhoneNumber {
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
hasFetchedMessages Boolean
|
||||
hasFetchedCalls Boolean
|
||||
phoneNumber PhoneNumber @relation(fields: [phoneNumberId], references: [id])
|
||||
phoneNumberId String
|
||||
organization Organization @relation(fields: [organizationId], references: [id])
|
||||
organizationId String
|
||||
|
||||
@@id([organizationId, phoneNumberId])
|
||||
}
|
||||
|
||||
model NotificationSubscription {
|
||||
id String @id @default(uuid())
|
||||
createdAt DateTime @default(now()) @db.Timestamptz
|
||||
|
Loading…
Reference in New Issue
Block a user