send message to new recipient
This commit is contained in:
@ -8,7 +8,8 @@ import NewMessageArea from "./new-message-area";
|
||||
|
||||
export default function Conversation() {
|
||||
const router = useRouter();
|
||||
const conversation = useConversation(router.params.recipient)[0];
|
||||
const recipient = decodeURIComponent(router.params.recipient);
|
||||
const conversation = useConversation(recipient)[0];
|
||||
const messagesListRef = useRef<HTMLUListElement>(null);
|
||||
|
||||
useEffect(() => {
|
||||
@ -75,7 +76,7 @@ export default function Conversation() {
|
||||
</ul>
|
||||
</div>
|
||||
<Suspense fallback={null}>
|
||||
<NewMessageArea />
|
||||
<NewMessageArea recipient={recipient} />
|
||||
</Suspense>
|
||||
</>
|
||||
);
|
||||
|
@ -8,14 +8,18 @@ import { Direction, Message, MessageStatus } from "../../../db";
|
||||
import getConversationsQuery from "../queries/get-conversations";
|
||||
import useCurrentCustomer from "../../core/hooks/use-current-customer";
|
||||
import useCustomerPhoneNumber from "../../core/hooks/use-customer-phone-number";
|
||||
import { FunctionComponent } from "react";
|
||||
|
||||
type Form = {
|
||||
content: string;
|
||||
};
|
||||
|
||||
export default function NewMessageArea() {
|
||||
const router = useRouter();
|
||||
const recipient = router.params.recipient;
|
||||
type Props = {
|
||||
recipient: string;
|
||||
onSend?: () => void;
|
||||
};
|
||||
|
||||
const NewMessageArea: FunctionComponent<Props> = ({ recipient, onSend }) => {
|
||||
const { customer } = useCurrentCustomer();
|
||||
const phoneNumber = useCustomerPhoneNumber();
|
||||
const sendMessageMutation = useMutation(sendMessage)[0];
|
||||
@ -30,6 +34,10 @@ export default function NewMessageArea() {
|
||||
formState: { isSubmitting },
|
||||
} = useForm<Form>();
|
||||
const onSubmit = handleSubmit(async ({ content }) => {
|
||||
if (!recipient) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isSubmitting) {
|
||||
return;
|
||||
}
|
||||
@ -60,6 +68,7 @@ export default function NewMessageArea() {
|
||||
{ refetch: false }
|
||||
);
|
||||
setValue("content", "");
|
||||
onSend?.();
|
||||
await sendMessageMutation({ to: recipient, content });
|
||||
await refetchConversations({ cancelRefetch: true });
|
||||
});
|
||||
@ -83,7 +92,9 @@ export default function NewMessageArea() {
|
||||
</button>
|
||||
</form>
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
export default NewMessageArea;
|
||||
|
||||
function uuidv4() {
|
||||
return "xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g, function (c) {
|
||||
|
53
app/messages/components/new-message-bottom-sheet.tsx
Normal file
53
app/messages/components/new-message-bottom-sheet.tsx
Normal file
@ -0,0 +1,53 @@
|
||||
import { Suspense, useState } from "react";
|
||||
import { BottomSheet } from "react-spring-bottom-sheet";
|
||||
import { atom, useAtom } from "jotai";
|
||||
import { useRouter, Routes } from "blitz";
|
||||
|
||||
import "react-spring-bottom-sheet/dist/style.css";
|
||||
|
||||
import NewMessageArea from "./new-message-area";
|
||||
|
||||
export const bottomSheetOpenAtom = atom(false);
|
||||
|
||||
export default function NewMessageBottomSheet() {
|
||||
const router = useRouter();
|
||||
const [isOpen, setIsOpen] = useAtom(bottomSheetOpenAtom);
|
||||
const [recipient, setRecipient] = useState("");
|
||||
|
||||
return (
|
||||
<BottomSheet
|
||||
open={isOpen}
|
||||
onDismiss={() => setIsOpen(false)}
|
||||
snapPoints={({ maxHeight }) => maxHeight / 2}
|
||||
header={
|
||||
<div className="w-full flex items-center justify-center p-4 text-black relative">
|
||||
<span className="font-semibold text-base">New Message</span>
|
||||
|
||||
<button onClick={() => setIsOpen(false)} className="absolute right-4">
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
>
|
||||
<main className="flex flex-col h-full overflow-hidden">
|
||||
<div className="flex items-center p-4 border-t border-b">
|
||||
<span className="mr-4 text-[#333]">To:</span>
|
||||
<input
|
||||
onChange={(event) => setRecipient(event.target.value)}
|
||||
className="bg-none border-none outline-none flex-1 text-black"
|
||||
/>
|
||||
</div>
|
||||
<Suspense fallback={null}>
|
||||
<NewMessageArea
|
||||
recipient={recipient}
|
||||
onSend={() => {
|
||||
router
|
||||
.push(Routes.ConversationPage({ recipient }))
|
||||
.then(() => setIsOpen(false));
|
||||
}}
|
||||
/>
|
||||
</Suspense>
|
||||
</main>
|
||||
</BottomSheet>
|
||||
);
|
||||
}
|
21
app/messages/components/new-message-button.tsx
Normal file
21
app/messages/components/new-message-button.tsx
Normal file
@ -0,0 +1,21 @@
|
||||
import type { FunctionComponent, MouseEventHandler } from "react";
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
|
||||
import { faEdit } from "@fortawesome/pro-regular-svg-icons";
|
||||
|
||||
type Props = {
|
||||
onClick: MouseEventHandler<HTMLButtonElement>;
|
||||
};
|
||||
|
||||
const NewMessageButton: FunctionComponent<Props> = ({ onClick }) => {
|
||||
return (
|
||||
<button
|
||||
onClick={onClick}
|
||||
className="absolute bottom-20 right-6
|
||||
w-14 h-14 bg-gray-800 rounded-full hover:bg-gray-900 active:shadow-lg shadow transition ease-in duration-200 focus:outline-none"
|
||||
>
|
||||
<FontAwesomeIcon size="lg" className="m-auto pl-1.5 text-white w-8 h-8" icon={faEdit} />
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
export default NewMessageButton;
|
@ -14,6 +14,7 @@ export default function useConversation(recipient: string) {
|
||||
|
||||
return conversations[recipient]!;
|
||||
},
|
||||
keepPreviousData: true,
|
||||
}
|
||||
);
|
||||
}
|
||||
|
14
app/messages/hooks/use-known-recipients.ts
Normal file
14
app/messages/hooks/use-known-recipients.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import { useQuery } from "blitz";
|
||||
import getConversationsQuery from "../queries/get-conversations";
|
||||
|
||||
export default function useKnownRecipients() {
|
||||
return useQuery(
|
||||
getConversationsQuery,
|
||||
{},
|
||||
{
|
||||
select(conversations) {
|
||||
return Object.keys(conversations);
|
||||
},
|
||||
}
|
||||
);
|
||||
}
|
@ -1,11 +1,15 @@
|
||||
import { resolver } from "blitz";
|
||||
import { z } from "zod";
|
||||
import twilio from "twilio";
|
||||
|
||||
import db, { Direction, MessageStatus } from "../../../db";
|
||||
import getCurrentCustomer from "../../customers/queries/get-current-customer";
|
||||
import getCustomerPhoneNumber from "../../phone-numbers/queries/get-customer-phone-number";
|
||||
import { encrypt } from "../../../db/_encryption";
|
||||
import sendMessageQueue from "../../api/queue/send-message";
|
||||
import appLogger from "../../../integrations/logger";
|
||||
|
||||
const logger = appLogger.child({ mutation: "send-message" });
|
||||
|
||||
const Body = z.object({
|
||||
content: z.string(),
|
||||
@ -17,6 +21,15 @@ export default resolver.pipe(
|
||||
resolver.authorize(),
|
||||
async ({ content, to }, context) => {
|
||||
const customer = await getCurrentCustomer(null, context);
|
||||
try {
|
||||
await twilio(customer!.accountSid!, customer!.authToken!)
|
||||
.lookups.v1.phoneNumbers(to)
|
||||
.fetch();
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
return;
|
||||
}
|
||||
|
||||
const customerId = customer!.id;
|
||||
const customerPhoneNumber = await getCustomerPhoneNumber({ customerId }, context);
|
||||
|
||||
|
@ -1,22 +1,28 @@
|
||||
import { Suspense } from "react";
|
||||
import { Suspense, useState } from "react";
|
||||
import type { BlitzPage } from "blitz";
|
||||
import { Routes } from "blitz";
|
||||
import { useAtom } from "jotai";
|
||||
|
||||
import Layout from "../../core/layouts/layout";
|
||||
import ConversationsList from "../components/conversations-list";
|
||||
import NewMessageButton from "../components/new-message-button";
|
||||
import NewMessageBottomSheet, { bottomSheetOpenAtom } from "../components/new-message-bottom-sheet";
|
||||
import useRequireOnboarding from "../../core/hooks/use-require-onboarding";
|
||||
|
||||
const Messages: BlitzPage = () => {
|
||||
useRequireOnboarding();
|
||||
const setIsOpen = useAtom(bottomSheetOpenAtom)[1];
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className="flex flex-col space-y-6 p-6">
|
||||
<p>Messages page</p>
|
||||
<h2 className="text-3xl font-bold">Messages</h2>
|
||||
</div>
|
||||
<Suspense fallback="Loading...">
|
||||
<ConversationsList />
|
||||
</Suspense>
|
||||
<NewMessageButton onClick={() => setIsOpen(true)} />
|
||||
<NewMessageBottomSheet />
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
@ -16,7 +16,7 @@ const ConversationPage: BlitzPage = () => {
|
||||
useRequireOnboarding();
|
||||
|
||||
const router = useRouter();
|
||||
const recipient = router.params.recipient;
|
||||
const recipient = decodeURIComponent(router.params.recipient);
|
||||
|
||||
return (
|
||||
<>
|
||||
@ -39,7 +39,7 @@ const ConversationPage: BlitzPage = () => {
|
||||
|
||||
ConversationPage.getLayout = function ConversationLayout(page) {
|
||||
const router = useRouter();
|
||||
const recipient = router.params.recipient;
|
||||
const recipient = decodeURIComponent(router.params.recipient);
|
||||
const pageTitle = `Messages with ${recipient}`;
|
||||
|
||||
return (
|
||||
|
Reference in New Issue
Block a user