From 2f45e1d9a852115ddbb6a4507d9741433b5d9dbe Mon Sep 17 00:00:00 2001 From: m5r Date: Sat, 25 Sep 2021 07:07:40 +0800 Subject: [PATCH] add import messages/calls ui feedback --- app/core/components/phone-init-loader.tsx | 23 +++++++++++++++++++ app/core/layouts/layout/index.tsx | 2 +- app/messages/api/queue/insert-messages.ts | 16 +++++++++++++ .../components/conversations-list.tsx | 15 +++++++++++- app/messages/pages/messages.tsx | 8 ++++--- app/messages/queries/get-conversations.ts | 6 +++++ app/onboarding/mutations/set-phone-number.ts | 8 +++++++ app/phone-calls/api/queue/insert-calls.ts | 16 +++++++++++++ .../components/phone-calls-list.tsx | 23 +++++++++++++++---- app/phone-calls/pages/calls.tsx | 8 ++++--- app/phone-calls/queries/get-phone-calls.ts | 5 ++++ app/public-area/components/cta-form.tsx | 2 +- .../migration.sql | 19 +++++++++++++++ db/schema.prisma | 18 +++++++++++++-- 14 files changed, 153 insertions(+), 16 deletions(-) create mode 100644 app/core/components/phone-init-loader.tsx create mode 100644 db/migrations/20210924173647_add_processing_phone_number_table/migration.sql 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