list phone calls
This commit is contained in:
parent
f55f1c5359
commit
a262f61823
@ -1,16 +0,0 @@
|
||||
export enum SmsType {
|
||||
SENT = "sent",
|
||||
RECEIVED = "received",
|
||||
}
|
||||
|
||||
export type Sms = {
|
||||
id: string;
|
||||
customerId: string;
|
||||
content: string;
|
||||
from: string;
|
||||
to: string;
|
||||
type: SmsType;
|
||||
twilioSid?: string;
|
||||
// status: sent/delivered/received
|
||||
sentAt: string; // timestampz
|
||||
};
|
@ -1,31 +1,44 @@
|
||||
import { MessageStatus } from "twilio/lib/rest/api/v2010/account/message";
|
||||
|
||||
import appLogger from "../../lib/logger";
|
||||
import supabase from "../supabase/server";
|
||||
import type { Sms } from "./_types";
|
||||
import { findCustomer } from "./customer";
|
||||
import { decrypt } from "./_encryption";
|
||||
|
||||
const logger = appLogger.child({ module: "sms" });
|
||||
const logger = appLogger.child({ module: "message" });
|
||||
|
||||
export async function insertSms(messages: Omit<Sms, "id" | "twilioSid">): Promise<Sms> {
|
||||
export type Message = {
|
||||
id: string;
|
||||
customerId: string;
|
||||
content: string;
|
||||
from: string;
|
||||
to: string;
|
||||
direction: "inbound" | "outbound";
|
||||
status: MessageStatus;
|
||||
twilioSid?: string;
|
||||
sentAt: string; // timestampz
|
||||
};
|
||||
|
||||
export async function insertMessage(message: Omit<Message, "id" | "twilioSid">): Promise<Message> {
|
||||
const { error, data } = await supabase
|
||||
.from<Sms>("sms")
|
||||
.insert(messages);
|
||||
.from<Message>("message")
|
||||
.insert(message);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data![0];
|
||||
}
|
||||
|
||||
export async function insertManySms(messages: Omit<Sms, "id">[]) {
|
||||
export async function insertManyMessage(messages: Omit<Message, "id">[]) {
|
||||
await supabase
|
||||
.from<Sms>("sms")
|
||||
.from<Message>("message")
|
||||
.insert(messages)
|
||||
.throwOnError();
|
||||
}
|
||||
|
||||
export async function findCustomerMessages(customerId: Sms["customerId"]): Promise<Sms[]> {
|
||||
export async function findCustomerMessages(customerId: Message["customerId"]): Promise<Message[]> {
|
||||
const { error, data } = await supabase
|
||||
.from<Sms>("sms")
|
||||
.from<Message>("message")
|
||||
.select("*")
|
||||
.eq("customerId", customerId);
|
||||
|
||||
@ -34,9 +47,9 @@ export async function findCustomerMessages(customerId: Sms["customerId"]): Promi
|
||||
return data!;
|
||||
}
|
||||
|
||||
export async function findCustomerMessageBySid({ customerId, twilioSid }: Pick<Sms, "customerId" | "twilioSid">): Promise<Sms> {
|
||||
export async function findCustomerMessageBySid({ customerId, twilioSid }: Pick<Message, "customerId" | "twilioSid">): Promise<Message> {
|
||||
const { error, data } = await supabase
|
||||
.from<Sms>("sms")
|
||||
.from<Message>("message")
|
||||
.select("*")
|
||||
.eq("customerId", customerId)
|
||||
.eq("twilioSid", twilioSid)
|
||||
@ -47,17 +60,17 @@ export async function findCustomerMessageBySid({ customerId, twilioSid }: Pick<S
|
||||
return data!;
|
||||
}
|
||||
|
||||
export async function setTwilioSid({ id, twilioSid }: Pick<Sms, "id" | "twilioSid">) {
|
||||
await supabase.from<Sms>("sms")
|
||||
export async function setTwilioSid({ id, twilioSid }: Pick<Message, "id" | "twilioSid">) {
|
||||
await supabase.from<Message>("message")
|
||||
.update({ twilioSid })
|
||||
.eq("id", id)
|
||||
.throwOnError();
|
||||
}
|
||||
|
||||
export async function findConversation(customerId: Sms["customerId"], recipient: Sms["to"]): Promise<Sms[]> {
|
||||
export async function findConversation(customerId: Message["customerId"], recipient: Message["to"]): Promise<Message[]> {
|
||||
const customer = await findCustomer(customerId);
|
||||
const { error, data } = await supabase
|
||||
.from<Sms>("sms")
|
||||
.from<Message>("message")
|
||||
.select("*")
|
||||
.eq("customerId", customerId)
|
||||
.or(`to.eq.${recipient},from.eq.${recipient}`);
|
53
src/database/phone-call.ts
Normal file
53
src/database/phone-call.ts
Normal file
@ -0,0 +1,53 @@
|
||||
import type { CallStatus } from "twilio/lib/rest/api/v2010/account/call";
|
||||
|
||||
import appLogger from "../../lib/logger";
|
||||
import supabase from "../supabase/server";
|
||||
|
||||
const logger = appLogger.child({ module: "phone-call" });
|
||||
|
||||
export type PhoneCall = {
|
||||
id: string;
|
||||
customerId: string;
|
||||
twilioSid: string;
|
||||
from: string;
|
||||
to: string;
|
||||
status: CallStatus;
|
||||
direction: "inbound" | "outbound";
|
||||
duration: string;
|
||||
createdAt: string; // timestampz
|
||||
}
|
||||
|
||||
export async function insertPhoneCall(phoneCall: Omit<PhoneCall, "id" | "twilioSid">): Promise<PhoneCall> {
|
||||
const { error, data } = await supabase
|
||||
.from<PhoneCall>("phone-call")
|
||||
.insert(phoneCall);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data![0];
|
||||
}
|
||||
|
||||
export async function insertManyPhoneCalls(phoneCalls: Omit<PhoneCall, "id">[]) {
|
||||
await supabase
|
||||
.from<PhoneCall>("phone-call")
|
||||
.insert(phoneCalls)
|
||||
.throwOnError();
|
||||
}
|
||||
|
||||
export async function findCustomerPhoneCalls(customerId: PhoneCall["customerId"]): Promise<PhoneCall[]> {
|
||||
const { error, data } = await supabase
|
||||
.from<PhoneCall>("phone-call")
|
||||
.select("*")
|
||||
.eq("customerId", customerId);
|
||||
|
||||
if (error) throw error;
|
||||
|
||||
return data!;
|
||||
}
|
||||
|
||||
export async function setTwilioSid({ id, twilioSid }: Pick<PhoneCall, "id" | "twilioSid">) {
|
||||
await supabase.from<PhoneCall>("phone-call")
|
||||
.update({ twilioSid })
|
||||
.eq("id", id)
|
||||
.throwOnError();
|
||||
}
|
@ -1,11 +1,10 @@
|
||||
import { useMutation, useQuery, useQueryClient } from "react-query";
|
||||
import axios from "axios";
|
||||
import type { Sms } from "../database/_types";
|
||||
import { SmsType } from "../database/_types";
|
||||
import type { Message } from "../database/message";
|
||||
import useUser from "./use-user";
|
||||
|
||||
type UseConversationParams = {
|
||||
initialData?: Sms[];
|
||||
initialData?: Message[];
|
||||
recipient: string;
|
||||
}
|
||||
|
||||
@ -16,11 +15,11 @@ export default function useConversation({
|
||||
const user = useUser();
|
||||
const getConversationUrl = `/api/conversation/${encodeURIComponent(recipient)}`;
|
||||
const fetcher = async () => {
|
||||
const { data } = await axios.get<Sms[]>(getConversationUrl);
|
||||
const { data } = await axios.get<Message[]>(getConversationUrl);
|
||||
return data;
|
||||
};
|
||||
const queryClient = useQueryClient();
|
||||
const getConversationQuery = useQuery<Sms[]>(
|
||||
const getConversationQuery = useQuery<Message[]>(
|
||||
getConversationUrl,
|
||||
fetcher,
|
||||
{
|
||||
@ -31,21 +30,22 @@ export default function useConversation({
|
||||
);
|
||||
|
||||
const sendMessage = useMutation(
|
||||
(sms: Pick<Sms, "to" | "content">) => axios.post(`/api/conversation/${sms.to}/send-message`, sms, { withCredentials: true }),
|
||||
(sms: Pick<Message, "to" | "content">) => axios.post(`/api/conversation/${sms.to}/send-message`, sms, { withCredentials: true }),
|
||||
{
|
||||
onMutate: async (sms: Pick<Sms, "to" | "content">) => {
|
||||
onMutate: async (sms: Pick<Message, "to" | "content">) => {
|
||||
await queryClient.cancelQueries(getConversationUrl);
|
||||
const previousMessages = queryClient.getQueryData<Sms[]>(getConversationUrl);
|
||||
const previousMessages = queryClient.getQueryData<Message[]>(getConversationUrl);
|
||||
|
||||
if (previousMessages) {
|
||||
queryClient.setQueryData<Sms[]>(getConversationUrl, [
|
||||
queryClient.setQueryData<Message[]>(getConversationUrl, [
|
||||
...previousMessages,
|
||||
{
|
||||
id: "", // TODO: somehow generate an id
|
||||
from: "", // TODO: get user's phone number
|
||||
customerId: user.userProfile!.id,
|
||||
sentAt: new Date().toISOString(),
|
||||
type: SmsType.SENT,
|
||||
direction: "outbound",
|
||||
status: "queued",
|
||||
content: sms.content,
|
||||
to: sms.to,
|
||||
},
|
||||
@ -56,7 +56,7 @@ export default function useConversation({
|
||||
},
|
||||
onError: (error, variables, context) => {
|
||||
if (context?.previousMessages) {
|
||||
queryClient.setQueryData<Sms[]>(getConversationUrl, context.previousMessages);
|
||||
queryClient.setQueryData<Message[]>(getConversationUrl, context.previousMessages);
|
||||
}
|
||||
},
|
||||
onSettled: () => queryClient.invalidateQueries(getConversationUrl),
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Joi from "joi";
|
||||
|
||||
import { withApiAuthRequired } from "../../../../../lib/session-helpers";
|
||||
import { findConversation } from "../../../../database/sms";
|
||||
import { findConversation } from "../../../../database/message";
|
||||
import type { ApiError } from "../../_types";
|
||||
import appLogger from "../../../../../lib/logger";
|
||||
|
||||
|
@ -1,14 +1,12 @@
|
||||
import Joi from "joi";
|
||||
|
||||
import { SmsType } from "../../../../database/_types";
|
||||
import { withApiAuthRequired } from "../../../../../lib/session-helpers";
|
||||
import { findConversation, insertSms } from "../../../../database/sms";
|
||||
import { insertMessage } from "../../../../database/message";
|
||||
import type { ApiError } from "../../_types";
|
||||
import appLogger from "../../../../../lib/logger";
|
||||
import { findCustomerPhoneNumber } from "../../../../database/phone-number";
|
||||
import { encrypt } from "../../../../database/_encryption";
|
||||
import { findCustomer } from "../../../../database/customer";
|
||||
import twilio from "twilio";
|
||||
import sendMessageQueue from "../../queue/send-message";
|
||||
|
||||
const logger = appLogger.child({ route: "/api/conversation" });
|
||||
@ -60,11 +58,12 @@ export default withApiAuthRequired(async function sendMessageHandler(
|
||||
const { phoneNumber } = await findCustomerPhoneNumber(customerId);
|
||||
const body: Body = validationResult.value;
|
||||
|
||||
const sms = await insertSms({
|
||||
const sms = await insertMessage({
|
||||
from: phoneNumber,
|
||||
customerId: customerId,
|
||||
sentAt: new Date().toISOString(),
|
||||
type: SmsType.SENT,
|
||||
direction: "outbound",
|
||||
status: "queued",
|
||||
content: encrypt(body.content, customer.encryptionKey),
|
||||
to: body.to,
|
||||
});
|
||||
|
@ -1,19 +1,19 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
import { insertSms } from "../../database/sms";
|
||||
import { SmsType } from "../../database/_types";
|
||||
import { insertMessage } from "../../database/message";
|
||||
import { encrypt } from "../../database/_encryption";
|
||||
import twilio from "twilio";
|
||||
import fetchCallsQueue from "./queue/fetch-calls";
|
||||
|
||||
export default async function ddd(req: NextApiRequest, res: NextApiResponse) {
|
||||
const accountSid = "ACa886d066be0832990d1cf43fb1d53362";
|
||||
const authToken = "8696a59a64b94bb4eba3548ed815953b";
|
||||
// const ddd = await twilio(accountSid, authToken).incomingPhoneNumbers.list();
|
||||
const phoneNumber = "+33757592025";
|
||||
const ddd = await twilio(accountSid, authToken)
|
||||
/*const ddd = await twilio(accountSid, authToken)
|
||||
.messages
|
||||
.list({
|
||||
to: phoneNumber,
|
||||
});
|
||||
});*/
|
||||
|
||||
/*const ddd = await insertSms({
|
||||
to: "+213",
|
||||
@ -43,6 +43,9 @@ export default async function ddd(req: NextApiRequest, res: NextApiResponse) {
|
||||
voiceApplicationSid: appSid,
|
||||
});*/
|
||||
|
||||
const customerId = "bcb723bc-9706-4811-a964-cc03018bd2ac";
|
||||
const ddd = fetchCallsQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` })
|
||||
|
||||
console.log("ddd", ddd);
|
||||
|
||||
return res.status(200).send(ddd);
|
||||
|
40
src/pages/api/queue/fetch-calls.ts
Normal file
40
src/pages/api/queue/fetch-calls.ts
Normal file
@ -0,0 +1,40 @@
|
||||
import { Queue } from "quirrel/next";
|
||||
import twilio from "twilio";
|
||||
|
||||
import { findCustomerPhoneNumber } from "../../../database/phone-number";
|
||||
import { findCustomer } from "../../../database/customer";
|
||||
import insertCallsQueue from "./insert-calls";
|
||||
|
||||
type Payload = {
|
||||
customerId: string;
|
||||
}
|
||||
|
||||
const fetchCallsQueue = Queue<Payload>(
|
||||
"api/queue/fetch-calls",
|
||||
async ({ customerId }) => {
|
||||
const customer = await findCustomer(customerId);
|
||||
const phoneNumber = await findCustomerPhoneNumber(customerId);
|
||||
|
||||
const [callsSent, callsReceived] = await Promise.all([
|
||||
twilio(customer.accountSid, customer.authToken)
|
||||
.calls
|
||||
.list({ from: phoneNumber.phoneNumber }),
|
||||
twilio(customer.accountSid, customer.authToken)
|
||||
.calls
|
||||
.list({ to: phoneNumber.phoneNumber })
|
||||
]);
|
||||
const calls = [
|
||||
...callsSent,
|
||||
...callsReceived,
|
||||
].sort((a, b) => a.dateCreated.getTime() - b.dateCreated.getTime());
|
||||
|
||||
await insertCallsQueue.enqueue({
|
||||
customerId,
|
||||
calls,
|
||||
}, {
|
||||
id: `insert-calls-${customerId}`,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
export default fetchCallsQueue;
|
@ -15,12 +15,14 @@ const fetchMessagesQueue = Queue<Payload>(
|
||||
const customer = await findCustomer(customerId);
|
||||
const phoneNumber = await findCustomerPhoneNumber(customerId);
|
||||
|
||||
const messagesSent = await twilio(customer.accountSid, customer.authToken)
|
||||
.messages
|
||||
.list({ from: phoneNumber.phoneNumber });
|
||||
const messagesReceived = await twilio(customer.accountSid, customer.authToken)
|
||||
.messages
|
||||
.list({ to: phoneNumber.phoneNumber });
|
||||
const [messagesSent, messagesReceived] = await Promise.all([
|
||||
twilio(customer.accountSid, customer.authToken)
|
||||
.messages
|
||||
.list({ from: phoneNumber.phoneNumber }),
|
||||
twilio(customer.accountSid, customer.authToken)
|
||||
.messages
|
||||
.list({ to: phoneNumber.phoneNumber }),
|
||||
]);
|
||||
const messages = [
|
||||
...messagesSent,
|
||||
...messagesReceived,
|
||||
|
32
src/pages/api/queue/insert-calls.ts
Normal file
32
src/pages/api/queue/insert-calls.ts
Normal file
@ -0,0 +1,32 @@
|
||||
import { Queue } from "quirrel/next";
|
||||
import type { CallInstance } from "twilio/lib/rest/api/v2010/account/call";
|
||||
|
||||
import type { PhoneCall } from "../../../database/phone-call";
|
||||
import { insertManyPhoneCalls } from "../../../database/phone-call";
|
||||
|
||||
type Payload = {
|
||||
customerId: string;
|
||||
calls: CallInstance[];
|
||||
}
|
||||
|
||||
const insertCallsQueue = Queue<Payload>(
|
||||
"api/queue/insert-calls",
|
||||
async ({ calls, customerId }) => {
|
||||
const phoneCalls = calls
|
||||
.map<Omit<PhoneCall, "id">>(call => ({
|
||||
customerId,
|
||||
twilioSid: call.sid,
|
||||
from: call.from,
|
||||
to: call.to,
|
||||
direction: call.direction === "inbound" ? "inbound" : "outbound",
|
||||
status: call.status,
|
||||
duration: call.duration,
|
||||
createdAt: new Date(call.dateCreated).toISOString(),
|
||||
}))
|
||||
.sort((a, b) => a.createdAt.localeCompare(b.createdAt));
|
||||
|
||||
await insertManyPhoneCalls(phoneCalls);
|
||||
},
|
||||
);
|
||||
|
||||
export default insertCallsQueue;
|
@ -2,9 +2,8 @@ import { Queue } from "quirrel/next";
|
||||
import type { MessageInstance } from "twilio/lib/rest/api/v2010/account/message";
|
||||
|
||||
import { findCustomer } from "../../../database/customer";
|
||||
import type { Sms } from "../../../database/_types";
|
||||
import { SmsType } from "../../../database/_types";
|
||||
import { insertManySms } from "../../../database/sms";
|
||||
import type { Message } from "../../../database/message";
|
||||
import { insertManyMessage } from "../../../database/message";
|
||||
import { encrypt } from "../../../database/_encryption";
|
||||
|
||||
type Payload = {
|
||||
@ -18,16 +17,20 @@ const insertMessagesQueue = Queue<Payload>(
|
||||
const customer = await findCustomer(customerId);
|
||||
const encryptionKey = customer.encryptionKey;
|
||||
|
||||
const sms = messages.map<Omit<Sms, "id">>(message => ({
|
||||
customerId,
|
||||
content: encrypt(message.body, encryptionKey),
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
type: ["received", "receiving"].includes(message.status) ? SmsType.RECEIVED : SmsType.SENT,
|
||||
messageSid: message.sid,
|
||||
sentAt: message.dateSent.toISOString(),
|
||||
}));
|
||||
await insertManySms(sms);
|
||||
const sms = messages
|
||||
.map<Omit<Message, "id">>(message => ({
|
||||
customerId,
|
||||
content: encrypt(message.body, encryptionKey),
|
||||
from: message.from,
|
||||
to: message.to,
|
||||
status: message.status,
|
||||
direction: message.direction === "inbound" ? "inbound" : "outbound",
|
||||
twilioSid: message.sid,
|
||||
sentAt: new Date(message.dateSent).toISOString(),
|
||||
}))
|
||||
.sort((a, b) => a.sentAt.localeCompare(b.sentAt));
|
||||
|
||||
await insertManyMessage(sms);
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -3,7 +3,7 @@ import twilio from "twilio";
|
||||
|
||||
import { findCustomer } from "../../../database/customer";
|
||||
import { findCustomerPhoneNumber } from "../../../database/phone-number";
|
||||
import { setTwilioSid } from "../../../database/sms";
|
||||
import { setTwilioSid } from "../../../database/message";
|
||||
|
||||
type Payload = {
|
||||
id: string;
|
||||
|
@ -9,14 +9,14 @@ type Payload = {
|
||||
}
|
||||
|
||||
const setTwilioWebhooks = Queue<Payload>(
|
||||
"api/queue/send-message",
|
||||
"api/queue/set-twilio-webhooks",
|
||||
async ({ customerId }) => {
|
||||
const customer = await findCustomer(customerId);
|
||||
const twimlApp = await twilio(customer.accountSid, customer.authToken)
|
||||
.applications
|
||||
.create({
|
||||
friendlyName: "Virtual Phone",
|
||||
smsUrl: "https://phone.mokhtar.dev/api/webhook/incoming-sms",
|
||||
smsUrl: "https://phone.mokhtar.dev/api/webhook/incoming-message",
|
||||
smsMethod: "POST",
|
||||
voiceUrl: "https://phone.mokhtar.dev/api/webhook/incoming-call",
|
||||
voiceMethod: "POST",
|
||||
|
@ -7,6 +7,7 @@ import appLogger from "../../../../lib/logger";
|
||||
import { createPhoneNumber } from "../../../database/phone-number";
|
||||
import { findCustomer } from "../../../database/customer";
|
||||
import fetchMessagesQueue from "../queue/fetch-messages";
|
||||
import fetchCallsQueue from "../queue/fetch-calls";
|
||||
import setTwilioWebhooks from "../queue/set-twilio-webhooks";
|
||||
|
||||
const logger = appLogger.child({ route: "/api/user/add-phone-number" });
|
||||
@ -49,6 +50,7 @@ export default withApiAuthRequired(async function addPhoneNumberHandler(req, res
|
||||
|
||||
await Promise.all([
|
||||
fetchMessagesQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` }),
|
||||
fetchCallsQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` }),
|
||||
setTwilioWebhooks.enqueue({ customerId }, { id: `set-twilio-webhooks-${customerId}` }),
|
||||
]);
|
||||
|
||||
|
@ -3,14 +3,13 @@ import twilio from "twilio";
|
||||
|
||||
import type { ApiError } from "../_types";
|
||||
import appLogger from "../../../../lib/logger";
|
||||
import { Customer, findCustomerByPhoneNumber } from "../../../database/customer";
|
||||
import { insertSms } from "../../../database/sms";
|
||||
import { SmsType } from "../../../database/_types";
|
||||
import { findCustomerByPhoneNumber } from "../../../database/customer";
|
||||
import { insertMessage } from "../../../database/message";
|
||||
import { encrypt } from "../../../database/_encryption";
|
||||
|
||||
const logger = appLogger.child({ route: "/api/webhook/incoming-sms" });
|
||||
const logger = appLogger.child({ route: "/api/webhook/incoming-message" });
|
||||
|
||||
export default async function incomingSmsHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
export default async function incomingMessageHandler(req: NextApiRequest, res: NextApiResponse) {
|
||||
if (req.method !== "POST") {
|
||||
const statusCode = 405;
|
||||
const apiError: ApiError = {
|
||||
@ -41,7 +40,7 @@ export default async function incomingSmsHandler(req: NextApiRequest, res: NextA
|
||||
try {
|
||||
const phoneNumber = req.body.To;
|
||||
const customer = await findCustomerByPhoneNumber(phoneNumber);
|
||||
const url = "https://phone.mokhtar.dev/api/webhook/incoming-sms";
|
||||
const url = "https://phone.mokhtar.dev/api/webhook/incoming-message";
|
||||
const isRequestValid = twilio.validateRequest(customer.authToken!, twilioSignature, url, req.body);
|
||||
if (!isRequestValid) {
|
||||
const statusCode = 400;
|
||||
@ -55,11 +54,12 @@ export default async function incomingSmsHandler(req: NextApiRequest, res: NextA
|
||||
return;
|
||||
}
|
||||
|
||||
await insertSms({
|
||||
await insertMessage({
|
||||
customerId: customer.id,
|
||||
to: req.body.To,
|
||||
from: req.body.From,
|
||||
type: SmsType.RECEIVED,
|
||||
status: "received",
|
||||
direction: "inbound",
|
||||
sentAt: req.body.DateSent,
|
||||
content: encrypt(req.body.Body, customer.encryptionKey),
|
||||
});
|
@ -1,14 +1,15 @@
|
||||
import type { InferGetServerSidePropsType, NextPage } from "next";
|
||||
|
||||
import { withPageOnboardingRequired } from "../../lib/session-helpers";
|
||||
import Layout from "../components/layout";
|
||||
import { findCustomerPhoneCalls } from "../database/phone-call";
|
||||
import useUser from "../hooks/use-user";
|
||||
import Layout from "../components/layout";
|
||||
|
||||
type Props = InferGetServerSidePropsType<typeof getServerSideProps>;
|
||||
|
||||
const pageTitle = "Calls";
|
||||
|
||||
const Calls: NextPage<Props> = (props) => {
|
||||
const Calls: NextPage<Props> = ({ phoneCalls }) => {
|
||||
const { userProfile } = useUser();
|
||||
|
||||
console.log("userProfile", userProfile);
|
||||
@ -21,19 +22,34 @@ const Calls: NextPage<Props> = (props) => {
|
||||
<Layout title={pageTitle}>
|
||||
<div className="flex flex-col space-y-6 p-6">
|
||||
<p>Calls page</p>
|
||||
<ul className="divide-y">
|
||||
{phoneCalls.map((phoneCall) => {
|
||||
const recipient = phoneCall.direction === "outbound" ? phoneCall.to : phoneCall.from;
|
||||
return (
|
||||
<li key={phoneCall.twilioSid} className="flex flex-row justify-between py-2">
|
||||
<div>{recipient}</div>
|
||||
<div>{new Date(phoneCall.createdAt).toLocaleString("fr-FR")}</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
</Layout>
|
||||
);
|
||||
};
|
||||
|
||||
export const getServerSideProps = withPageOnboardingRequired(
|
||||
async ({ res }) => {
|
||||
async ({ res }, user) => {
|
||||
res.setHeader(
|
||||
"Cache-Control",
|
||||
"private, s-maxage=15, stale-while-revalidate=59",
|
||||
);
|
||||
|
||||
return { props: {} };
|
||||
const phoneCalls = await findCustomerPhoneCalls(user.id);
|
||||
|
||||
return {
|
||||
props: { phoneCalls },
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
|
@ -7,9 +7,8 @@ import clsx from "clsx";
|
||||
import { useForm } from "react-hook-form";
|
||||
|
||||
import { withPageOnboardingRequired } from "../../../lib/session-helpers";
|
||||
import { findConversation } from "../../database/sms";
|
||||
import type { Sms } from "../../database/_types";
|
||||
import { SmsType } from "../../database/_types";
|
||||
import type { Message } from "../../database/message";
|
||||
import { findConversation } from "../../database/message";
|
||||
import supabase from "../../supabase/client";
|
||||
import useUser from "../../hooks/use-user";
|
||||
import useConversation from "../../hooks/use-conversation";
|
||||
@ -17,7 +16,7 @@ import Layout from "../../components/layout";
|
||||
|
||||
type Props = {
|
||||
recipient: string;
|
||||
conversation: Sms[];
|
||||
conversation: Message[];
|
||||
}
|
||||
|
||||
type Form = {
|
||||
@ -60,7 +59,7 @@ const Messages: NextPage<Props> = (props) => {
|
||||
}
|
||||
|
||||
const subscription = supabase
|
||||
.from<Sms>(`sms:customerId=eq.${userProfile.id}`)
|
||||
.from<Message>(`sms:customerId=eq.${userProfile.id}`)
|
||||
.on("INSERT", (payload) => {
|
||||
const message = payload.new;
|
||||
if ([message.from, message.to].includes(recipient)) {
|
||||
@ -98,12 +97,28 @@ const Messages: NextPage<Props> = (props) => {
|
||||
</header>
|
||||
<div className="flex flex-col space-y-6 p-6">
|
||||
<ul>
|
||||
{conversation!.map(message => {
|
||||
{conversation!.map((message, index) => {
|
||||
const isOutbound = message.direction === "outbound";
|
||||
const isSameSender = message.from === conversation![index + 1]?.from;
|
||||
const isLast = index === conversation!.length;
|
||||
return (
|
||||
<li key={message.id} className={clsx(message.type === SmsType.SENT ? "text-right" : "text-left")}>
|
||||
{message.content}
|
||||
<li
|
||||
key={message.id}
|
||||
className={clsx(
|
||||
isSameSender || isLast ? "pb-3" : "pb-4",
|
||||
isOutbound ? "text-right" : "text-left",
|
||||
)}
|
||||
>
|
||||
<span
|
||||
className={clsx(
|
||||
"p-2 rounded-lg text-white",
|
||||
isOutbound ? "bg-[#3194ff] rounded-br-none" : "bg-black rounded-bl-none",
|
||||
)}
|
||||
>
|
||||
{message.content}
|
||||
</span>
|
||||
</li>
|
||||
)
|
||||
);
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
|
@ -2,9 +2,8 @@ import type { InferGetServerSidePropsType, NextPage } from "next";
|
||||
import Link from "next/link";
|
||||
|
||||
import { withPageOnboardingRequired } from "../../../lib/session-helpers";
|
||||
import type { Sms } from "../../database/_types";
|
||||
import { SmsType } from "../../database/_types";
|
||||
import { findCustomerMessages } from "../../database/sms";
|
||||
import type { Message } from "../../database/message";
|
||||
import { findCustomerMessages } from "../../database/message";
|
||||
import { findCustomer } from "../../database/customer";
|
||||
import { decrypt } from "../../database/_encryption";
|
||||
import useUser from "../../hooks/use-user";
|
||||
@ -25,15 +24,17 @@ const Messages: NextPage<Props> = ({ conversations }) => {
|
||||
<Layout title={pageTitle}>
|
||||
<div className="flex flex-col space-y-6 p-6">
|
||||
<p>Messages page</p>
|
||||
<ul>
|
||||
<ul className="divide-y">
|
||||
{Object.entries(conversations).map(([recipient, message]) => {
|
||||
return (
|
||||
<li key={recipient}>
|
||||
<li key={recipient} className="py-2">
|
||||
<Link href={`/messages/${recipient}`}>
|
||||
<a>
|
||||
<div>{recipient}</div>
|
||||
<a className="flex flex-col">
|
||||
<div className="flex flex-row justify-between">
|
||||
<strong>{recipient}</strong>
|
||||
<div>{new Date(message.sentAt).toLocaleString("fr-FR")}</div>
|
||||
</div>
|
||||
<div>{message.content}</div>
|
||||
<div>{new Date(message.sentAt).toLocaleDateString()}</div>
|
||||
</a>
|
||||
</Link>
|
||||
</li>
|
||||
@ -59,10 +60,10 @@ export const getServerSideProps = withPageOnboardingRequired(
|
||||
findCustomerMessages(user.id),
|
||||
]);
|
||||
|
||||
let conversations: Record<Recipient, Sms> = {};
|
||||
let conversations: Record<Recipient, Message> = {};
|
||||
for (const message of messages) {
|
||||
let recipient: string;
|
||||
if (message.type === SmsType.SENT) {
|
||||
if (message.direction === "outbound") {
|
||||
recipient = message.to;
|
||||
} else {
|
||||
recipient = message.from;
|
||||
|
Loading…
Reference in New Issue
Block a user