diff --git a/app/core/components/phone-init-loader.tsx b/app/core/components/phone-init-loader.tsx
new file mode 100644
index 0000000..e837069
--- /dev/null
+++ b/app/core/components/phone-init-loader.tsx
@@ -0,0 +1,23 @@
+export default function PhoneInitLoader() {
+ return (
+
+
+
We're finalizing your cloud phone initialization.
+
+ You don't have to refresh this page, we will do it automatically for you when your phone is ready.
+
+
+ );
+}
diff --git a/app/core/layouts/layout/index.tsx b/app/core/layouts/layout/index.tsx
index 7cc6e4b..a3b166c 100644
--- a/app/core/layouts/layout/index.tsx
+++ b/app/core/layouts/layout/index.tsx
@@ -35,7 +35,7 @@ const Layout: FunctionComponent = ({ children, title, pageTitle = title,
-
+
{children}
diff --git a/app/messages/api/queue/insert-messages.ts b/app/messages/api/queue/insert-messages.ts
index b71ad63..5759b8b 100644
--- a/app/messages/api/queue/insert-messages.ts
+++ b/app/messages/api/queue/insert-messages.ts
@@ -37,6 +37,22 @@ const insertMessagesQueue = Queue
(
.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 },
+ });
+ }
},
);
diff --git a/app/messages/components/conversations-list.tsx b/app/messages/components/conversations-list.tsx
index 64c0d3d..82757a8 100644
--- a/app/messages/components/conversations-list.tsx
+++ b/app/messages/components/conversations-list.tsx
@@ -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 ;
+ }
if (Object.keys(conversations).length === 0) {
return empty state
;
diff --git a/app/messages/pages/messages.tsx b/app/messages/pages/messages.tsx
index 4a94e12..d5fc592 100644
--- a/app/messages/pages/messages.tsx
+++ b/app/messages/pages/messages.tsx
@@ -26,9 +26,11 @@ const Messages: BlitzPage = () => {
Messages
-
-
-
+
setIsOpen(true)} />
>
diff --git a/app/messages/queries/get-conversations.ts b/app/messages/queries/get-conversations.ts
index 8958999..de9f8c7 100644
--- a/app/messages/queries/get-conversations.ts
+++ b/app/messages/queries/get-conversations.ts
@@ -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 },
diff --git a/app/onboarding/mutations/set-phone-number.ts b/app/onboarding/mutations/set-phone-number.ts
index 3aab977..d6ece29 100644
--- a/app/onboarding/mutations/set-phone-number.ts
+++ b/app/onboarding/mutations/set-phone-number.ts
@@ -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}` },
diff --git a/app/phone-calls/api/queue/insert-calls.ts b/app/phone-calls/api/queue/insert-calls.ts
index e3b9e0e..e3425f9 100644
--- a/app/phone-calls/api/queue/insert-calls.ts
+++ b/app/phone-calls/api/queue/insert-calls.ts
@@ -34,6 +34,22 @@ const insertCallsQueue = Queue("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;
diff --git a/app/phone-calls/components/phone-calls-list.tsx b/app/phone-calls/components/phone-calls-list.tsx
index a7dd08c..99b18f9 100644
--- a/app/phone-calls/components/phone-calls-list.tsx
+++ b/app/phone-calls/components/phone-calls-list.tsx
@@ -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 ;
+ }
if (phoneCalls.length === 0) {
return empty state
;
diff --git a/app/phone-calls/pages/calls.tsx b/app/phone-calls/pages/calls.tsx
index 6abd83c..adbf8d0 100644
--- a/app/phone-calls/pages/calls.tsx
+++ b/app/phone-calls/pages/calls.tsx
@@ -14,9 +14,11 @@ const PhoneCalls: BlitzPage = () => {
Calls
-
-
-
+
>
);
};
diff --git a/app/phone-calls/queries/get-phone-calls.ts b/app/phone-calls/queries/get-phone-calls.ts
index 22b303d..9498203 100644
--- a/app/phone-calls/queries/get-phone-calls.ts
+++ b/app/phone-calls/queries/get-phone-calls.ts
@@ -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 },
diff --git a/app/public-area/components/cta-form.tsx b/app/public-area/components/cta-form.tsx
index e5cd361..29aea63 100644
--- a/app/public-area/components/cta-form.tsx
+++ b/app/public-area/components/cta-form.tsx
@@ -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";
diff --git a/db/migrations/20210924173647_add_processing_phone_number_table/migration.sql b/db/migrations/20210924173647_add_processing_phone_number_table/migration.sql
new file mode 100644
index 0000000..c15c8a9
--- /dev/null
+++ b/db/migrations/20210924173647_add_processing_phone_number_table/migration.sql
@@ -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;
diff --git a/db/schema.prisma b/db/schema.prisma
index b04c773..f219336 100644
--- a/db/schema.prisma
+++ b/db/schema.prisma
@@ -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