send message to recipient
This commit is contained in:
61
app/features/messages/actions/messages.$recipient.tsx
Normal file
61
app/features/messages/actions/messages.$recipient.tsx
Normal file
@ -0,0 +1,61 @@
|
||||
import { type ActionFunction } from "@remix-run/node";
|
||||
import { json } from "superjson-remix";
|
||||
|
||||
import db from "~/utils/db.server";
|
||||
import { requireLoggedIn } from "~/utils/auth.server";
|
||||
import getTwilioClient, { translateMessageDirection, translateMessageStatus } from "~/utils/twilio.server";
|
||||
|
||||
export type NewMessageActionData = {};
|
||||
|
||||
const action: ActionFunction = async ({ params, request }) => {
|
||||
const user = await requireLoggedIn(request);
|
||||
const organization = user.organizations[0];
|
||||
const phoneNumber = await db.phoneNumber.findUnique({
|
||||
where: { organizationId_isCurrent: { organizationId: user.organizations[0].id, isCurrent: true } },
|
||||
});
|
||||
const recipient = decodeURIComponent(params.recipient ?? "");
|
||||
const formData = Object.fromEntries(await request.formData());
|
||||
|
||||
const { twilioAccountSid, twilioSubAccountSid } = organization;
|
||||
// const twilioClient = getTwilioClient({ twilioSubAccountSid, twilioAccountSid });
|
||||
const twilioClient = getTwilioClient({ twilioSubAccountSid: twilioAccountSid, twilioAccountSid });
|
||||
try {
|
||||
console.log({ twilioAccountSid, twilioSubAccountSid });
|
||||
console.log({
|
||||
body: formData.content.toString(),
|
||||
to: recipient,
|
||||
from: phoneNumber!.number,
|
||||
});
|
||||
const message = await twilioClient.messages.create({
|
||||
body: formData.content.toString(),
|
||||
to: recipient,
|
||||
from: phoneNumber!.number,
|
||||
});
|
||||
await db.message.create({
|
||||
data: {
|
||||
phoneNumberId: phoneNumber!.id,
|
||||
id: message.sid,
|
||||
to: message.to,
|
||||
from: message.from,
|
||||
status: translateMessageStatus(message.status),
|
||||
direction: translateMessageDirection(message.direction),
|
||||
sentAt: new Date(message.dateCreated),
|
||||
content: message.body,
|
||||
},
|
||||
});
|
||||
} catch (error: any) {
|
||||
// TODO: handle twilio error
|
||||
console.log(error.code); // 21211
|
||||
console.log(error.moreInfo); // https://www.twilio.com/docs/errors/21211
|
||||
console.log(JSON.stringify(error));
|
||||
throw error;
|
||||
/*await db.message.update({
|
||||
where: { id },
|
||||
data: { status: MessageStatus.Error /!*errorMessage: "Reason: failed because of"*!/ },
|
||||
});*/
|
||||
}
|
||||
|
||||
return json<NewMessageActionData>({});
|
||||
};
|
||||
|
||||
export default action;
|
@ -1,5 +1,5 @@
|
||||
import { Suspense, useEffect, useMemo, useRef } from "react";
|
||||
import { useParams } from "@remix-run/react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { useParams, useTransition } from "@remix-run/react";
|
||||
import { useLoaderData } from "superjson-remix";
|
||||
import clsx from "clsx";
|
||||
import { Direction } from "@prisma/client";
|
||||
@ -7,14 +7,31 @@ import { Direction } from "@prisma/client";
|
||||
import NewMessageArea from "./new-message-area";
|
||||
import { formatDate, formatTime } from "~/features/core/helpers/date-formatter";
|
||||
import { type ConversationLoaderData } from "~/routes/__app/messages.$recipient";
|
||||
import useSession from "~/features/core/hooks/use-session";
|
||||
|
||||
export default function Conversation() {
|
||||
const { currentPhoneNumber } = useSession();
|
||||
const params = useParams<{ recipient: string }>();
|
||||
const recipient = decodeURIComponent(params.recipient ?? "");
|
||||
const { conversation } = useLoaderData<ConversationLoaderData>();
|
||||
const messages = useMemo(() => conversation?.messages ?? [], [conversation?.messages]);
|
||||
const transition = useTransition();
|
||||
const messagesListRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
const messages = conversation.messages;
|
||||
if (transition.submission) {
|
||||
messages.push({
|
||||
id: "temp",
|
||||
phoneNumberId: currentPhoneNumber.id,
|
||||
from: currentPhoneNumber.number,
|
||||
to: recipient,
|
||||
sentAt: new Date(),
|
||||
direction: Direction.Outbound,
|
||||
|
||||
status: "Queued",
|
||||
content: transition.submission.formData.get("content")!.toString()
|
||||
})
|
||||
}
|
||||
|
||||
useEffect(() => {
|
||||
messagesListRef.current?.querySelector("li:last-child")?.scrollIntoView();
|
||||
}, [messages, messagesListRef]);
|
||||
@ -74,7 +91,7 @@ export default function Conversation() {
|
||||
})}
|
||||
</ul>
|
||||
</div>
|
||||
<NewMessageArea recipient={recipient} />
|
||||
<NewMessageArea />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
@ -1,68 +1,34 @@
|
||||
import type { FunctionComponent } from "react";
|
||||
import { useEffect, useRef } from "react";
|
||||
import { Form, useTransition } from "@remix-run/react";
|
||||
import { IoSend } from "react-icons/io5";
|
||||
import { type Message, Direction, MessageStatus } from "@prisma/client";
|
||||
import useSession from "~/features/core/hooks/use-session";
|
||||
|
||||
type Props = {
|
||||
recipient: string;
|
||||
onSend?: () => void;
|
||||
};
|
||||
function NewMessageArea() {
|
||||
const transition = useTransition();
|
||||
const formRef = useRef<HTMLFormElement>(null);
|
||||
const textFieldRef = useRef<HTMLTextAreaElement>(null);
|
||||
const isSendingMessage = transition.state === "submitting";
|
||||
|
||||
const NewMessageArea: FunctionComponent<Props> = ({ recipient, onSend }) => {
|
||||
const { currentOrganization, /*hasOngoingSubscription*/ } = useSession();
|
||||
// const phoneNumber = useCurrentPhoneNumber();
|
||||
// const sendMessageMutation = useMutation(sendMessage)[0];
|
||||
const onSubmit = async () => {
|
||||
/*const id = uuidv4();
|
||||
const message: Message = {
|
||||
id,
|
||||
organizationId: organization!.id,
|
||||
phoneNumberId: phoneNumber!.id,
|
||||
from: phoneNumber!.number,
|
||||
to: recipient,
|
||||
content: hasOngoingSubscription
|
||||
? content
|
||||
: content + "\n\nSent from Shellphone (https://www.shellphone.app)",
|
||||
direction: Direction.Outbound,
|
||||
status: MessageStatus.Queued,
|
||||
sentAt: new Date(),
|
||||
};*/
|
||||
|
||||
/*await setConversationsQueryData(
|
||||
(conversations) => {
|
||||
const nextConversations = { ...conversations };
|
||||
if (!nextConversations[recipient]) {
|
||||
nextConversations[recipient] = {
|
||||
recipient,
|
||||
formattedPhoneNumber: recipient,
|
||||
messages: [],
|
||||
};
|
||||
}
|
||||
|
||||
nextConversations[recipient]!.messages = [...nextConversations[recipient]!.messages, message];
|
||||
|
||||
return Object.fromEntries(
|
||||
Object.entries(nextConversations).sort(
|
||||
([, a], [, b]) =>
|
||||
b.messages[b.messages.length - 1]!.sentAt.getTime() -
|
||||
a.messages[a.messages.length - 1]!.sentAt.getTime(),
|
||||
),
|
||||
);
|
||||
},
|
||||
{ refetch: false },
|
||||
);*/
|
||||
// setValue("content", "");
|
||||
// onSend?.();
|
||||
};
|
||||
useEffect(() => {
|
||||
if (isSendingMessage) {
|
||||
formRef.current?.reset();
|
||||
textFieldRef.current?.focus();
|
||||
}
|
||||
}, [isSendingMessage]);
|
||||
|
||||
return (
|
||||
<form
|
||||
onSubmit={onSubmit}
|
||||
className="absolute bottom-0 w-screen backdrop-filter backdrop-blur-xl bg-white bg-opacity-75 border-t flex flex-row h-16 p-2 pr-0"
|
||||
<Form
|
||||
ref={formRef}
|
||||
method="post"
|
||||
className="absolute bottom-0 w-screen backdrop-filter backdrop-blur-xl bg-white bg-opacity-75 border-t flex flex-row h-14 mb-16 p-2 pr-0"
|
||||
replace
|
||||
>
|
||||
<textarea
|
||||
ref={textFieldRef}
|
||||
name="content"
|
||||
className="resize-none flex-1"
|
||||
className="resize-none rounded-full flex-1"
|
||||
style={{
|
||||
scrollbarWidth: "none",
|
||||
}}
|
||||
autoCapitalize="sentences"
|
||||
autoCorrect="on"
|
||||
placeholder="Text message"
|
||||
@ -73,16 +39,8 @@ const NewMessageArea: FunctionComponent<Props> = ({ recipient, onSend }) => {
|
||||
<button type="submit">
|
||||
<IoSend className="h-8 w-8 pl-1 pr-2" />
|
||||
</button>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
export default NewMessageArea;
|
||||
|
||||
function uuidv4() {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
const r = (Math.random() * 16) | 0,
|
||||
v = c == "x" ? r : (r & 0x3) | 0x8;
|
||||
return v.toString(16);
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user