session thingy for onboarding checks

This commit is contained in:
m5r 2021-08-01 22:01:51 +08:00
parent 7acbca65ce
commit 7d34fcd48f
15 changed files with 85 additions and 102 deletions

View File

@ -4,14 +4,14 @@ import db from "db";
import twilio from "twilio"; import twilio from "twilio";
export default async function ddd(req: BlitzApiRequest, res: BlitzApiResponse) { export default async function ddd(req: BlitzApiRequest, res: BlitzApiResponse) {
await Promise.all([ /*await Promise.all([
db.message.deleteMany(), db.message.deleteMany(),
db.phoneCall.deleteMany(), db.phoneCall.deleteMany(),
db.phoneNumber.deleteMany(), db.phoneNumber.deleteMany(),
db.customer.deleteMany(),
]); ]);
await db.customer.deleteMany();
await db.user.deleteMany();*/
await db.user.deleteMany();
const accountSid = "ACa886d066be0832990d1cf43fb1d53362"; const accountSid = "ACa886d066be0832990d1cf43fb1d53362";
const authToken = "8696a59a64b94bb4eba3548ed815953b"; const authToken = "8696a59a64b94bb4eba3548ed815953b";
/*const ddd = await twilio(accountSid, authToken) /*const ddd = await twilio(accountSid, authToken)
@ -37,6 +37,17 @@ export default async function ddd(req: BlitzApiRequest, res: BlitzApiResponse) {
to: "+33757592025", to: "+33757592025",
from: "+33757592722", from: "+33757592722",
});*/ });*/
/*const [messagesSent, messagesReceived] = await Promise.all([
twilio(accountSid, authToken).messages.list({
from: "+33757592025",
}),
twilio(accountSid, authToken).messages.list({
to: "+33757592025",
}),
]);
console.log("messagesReceived", messagesReceived.sort((a, b) => a.dateCreated.getTime() - b.dateCreated.getTime()));
// console.log("messagesReceived", messagesReceived);*/
res.status(200).end(); res.status(200).end();
} }

View File

@ -1,11 +1,13 @@
import { useQuery } from "blitz"; import { useAuthenticatedSession, useQuery } from "blitz";
import getCurrentCustomer from "../../customers/queries/get-current-customer"; import getCurrentCustomer from "../../customers/queries/get-current-customer";
export default function useCurrentCustomer() { export default function useCurrentCustomer() {
const session = useAuthenticatedSession();
const [customer] = useQuery(getCurrentCustomer, null); const [customer] = useQuery(getCurrentCustomer, null);
return { return {
customer, customer,
hasCompletedOnboarding: Boolean(!!customer && customer.accountSid && customer.authToken), hasFilledTwilioCredentials: Boolean(customer && customer.accountSid && customer.authToken),
hasCompletedOnboarding: session.hasCompletedOnboarding,
}; };
} }

View File

@ -4,12 +4,9 @@ import getCurrentCustomerPhoneNumber from "../../phone-numbers/queries/get-curre
import useCurrentCustomer from "./use-current-customer"; import useCurrentCustomer from "./use-current-customer";
export default function useCustomerPhoneNumber() { export default function useCustomerPhoneNumber() {
const { hasCompletedOnboarding } = useCurrentCustomer(); const { customer } = useCurrentCustomer();
const [customerPhoneNumber] = useQuery( const hasFilledTwilioCredentials = Boolean(customer && customer.accountSid && customer.authToken);
getCurrentCustomerPhoneNumber, const [customerPhoneNumber] = useQuery(getCurrentCustomerPhoneNumber, {}, { enabled: hasFilledTwilioCredentials });
{},
{ enabled: hasCompletedOnboarding },
);
return customerPhoneNumber; return customerPhoneNumber;
} }

View File

@ -5,10 +5,14 @@ import useCustomerPhoneNumber from "./use-customer-phone-number";
export default function useRequireOnboarding() { export default function useRequireOnboarding() {
const router = useRouter(); const router = useRouter();
const { customer, hasCompletedOnboarding } = useCurrentCustomer(); const { hasFilledTwilioCredentials, hasCompletedOnboarding } = useCurrentCustomer();
const customerPhoneNumber = useCustomerPhoneNumber(); const customerPhoneNumber = useCustomerPhoneNumber();
if (!hasCompletedOnboarding) { if (hasCompletedOnboarding) {
return;
}
if (!hasFilledTwilioCredentials) {
throw router.push(Routes.StepTwo()); throw router.push(Routes.StepTwo());
} }
@ -17,7 +21,6 @@ export default function useRequireOnboarding() {
return; return;
}*/ }*/
console.log("customerPhoneNumber", customerPhoneNumber);
if (!customerPhoneNumber) { if (!customerPhoneNumber) {
throw router.push(Routes.StepThree()); throw router.push(Routes.StepThree());
} }

View File

@ -60,10 +60,10 @@ const Keypad: BlitzPage = () => {
</Row> </Row>
<Row> <Row>
<div className="cursor-pointer select-none col-start-2 h-12 w-12 flex justify-center items-center mx-auto bg-green-800 rounded-full"> <div className="cursor-pointer select-none col-start-2 h-12 w-12 flex justify-center items-center mx-auto bg-green-800 rounded-full">
<FontAwesomeIcon icon={faPhone} color="white" size="lg" /> <FontAwesomeIcon className="w-6 h-6" icon={faPhone} color="white" size="lg" />
</div> </div>
<div className="cursor-pointer select-none my-auto" onClick={pressBackspace}> <div className="cursor-pointer select-none m-auto" onClick={pressBackspace}>
<FontAwesomeIcon icon={faBackspace} size="lg" /> <FontAwesomeIcon className="w-6 h-6" icon={faBackspace} size="lg" />
</div> </div>
</Row> </Row>
</section> </section>
@ -112,9 +112,7 @@ const Digit: FunctionComponent<{ digit: string }> = ({ children, digit }) => {
); );
}; };
const DigitLetters: FunctionComponent = ({ children }) => ( const DigitLetters: FunctionComponent = ({ children }) => <div className="text-xs text-gray-600">{children}</div>;
<div className="text-xs text-gray-600">{children}</div>
);
const phoneNumberAtom = atom(""); const phoneNumberAtom = atom("");
const pressDigitAtom = atom(null, (get, set, digit: string) => { const pressDigitAtom = atom(null, (get, set, digit: string) => {

View File

@ -12,14 +12,12 @@ const fetchMessagesQueue = Queue<Payload>("api/queue/fetch-messages", async ({ c
const customer = await db.customer.findFirst({ where: { id: customerId } }); const customer = await db.customer.findFirst({ where: { id: customerId } });
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId } }); const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId } });
const [messagesSent, messagesReceived] = await Promise.all([ const [sent, received] = await Promise.all([
twilio(customer!.accountSid!, customer!.authToken!).messages.list({ twilio(customer!.accountSid!, customer!.authToken!).messages.list({ from: phoneNumber!.phoneNumber }),
from: phoneNumber!.phoneNumber, twilio(customer!.accountSid!, customer!.authToken!).messages.list({ to: phoneNumber!.phoneNumber }),
}),
twilio(customer!.accountSid!, customer!.authToken!).messages.list({
to: phoneNumber!.phoneNumber,
}),
]); ]);
const messagesSent = sent.filter((message) => message.direction.startsWith("outbound"));
const messagesReceived = received.filter((message) => message.direction === "inbound");
const messages = [...messagesSent, ...messagesReceived].sort( const messages = [...messagesSent, ...messagesReceived].sort(
(a, b) => a.dateCreated.getTime() - b.dateCreated.getTime(), (a, b) => a.dateCreated.getTime() - b.dateCreated.getTime(),
); );

View File

@ -1,4 +1,4 @@
import { Suspense, useState } from "react"; import { Suspense } from "react";
import type { BlitzPage } from "blitz"; import type { BlitzPage } from "blitz";
import { Routes } from "blitz"; import { Routes } from "blitz";
import { useAtom } from "jotai"; import { useAtom } from "jotai";

View File

@ -1,9 +1,7 @@
import type { FunctionComponent } from "react"; import type { FunctionComponent } from "react";
import { Link } from "blitz";
import { CheckIcon } from "@heroicons/react/solid"; import { CheckIcon } from "@heroicons/react/solid";
import clsx from "clsx"; import clsx from "clsx";
import { Link, Routes, useRouter } from "blitz";
import useCustomerPhoneNumber from "../../core/hooks/use-customer-phone-number";
type StepLink = { type StepLink = {
href: string; href: string;
@ -19,13 +17,6 @@ type Props = {
const steps = ["Welcome", "Twilio Credentials", "Pick a plan"] as const; const steps = ["Welcome", "Twilio Credentials", "Pick a plan"] as const;
const OnboardingLayout: FunctionComponent<Props> = ({ children, currentStep, previous, next }) => { const OnboardingLayout: FunctionComponent<Props> = ({ children, currentStep, previous, next }) => {
const router = useRouter();
const customerPhoneNumber = useCustomerPhoneNumber();
if (customerPhoneNumber) {
throw router.push(Routes.Messages());
}
return ( return (
<div className="bg-gray-800 fixed z-10 inset-0 overflow-y-auto"> <div className="bg-gray-800 fixed z-10 inset-0 overflow-y-auto">
<div className="min-h-screen text-center block p-0"> <div className="min-h-screen text-center block p-0">

View File

@ -12,29 +12,23 @@ const Body = z.object({
phoneNumberSid: z.string(), phoneNumberSid: z.string(),
}); });
export default resolver.pipe( export default resolver.pipe(resolver.zod(Body), resolver.authorize(), async ({ phoneNumberSid }, context) => {
resolver.zod(Body), const customer = await getCurrentCustomer(null, context);
resolver.authorize(), const customerId = customer!.id;
async ({ phoneNumberSid }, context) => { const phoneNumbers = await twilio(customer!.accountSid!, customer!.authToken!).incomingPhoneNumbers.list();
const customer = await getCurrentCustomer(null, context); const phoneNumber = phoneNumbers.find((phoneNumber) => phoneNumber.sid === phoneNumberSid)!;
const customerId = customer!.id; await db.phoneNumber.create({
const phoneNumbers = await twilio( data: {
customer!.accountSid!, customerId,
customer!.authToken!, phoneNumberSid,
).incomingPhoneNumbers.list(); phoneNumber: phoneNumber.phoneNumber,
const phoneNumber = phoneNumbers.find((phoneNumber) => phoneNumber.sid === phoneNumberSid)!; },
await db.phoneNumber.create({ });
data: { context.session.$setPrivateData({ hasCompletedOnboarding: true });
customerId,
phoneNumberSid,
phoneNumber: phoneNumber.phoneNumber,
},
});
await Promise.all([ await Promise.all([
fetchMessagesQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` }), fetchMessagesQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` }),
fetchCallsQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` }), fetchCallsQueue.enqueue({ customerId }, { id: `fetch-messages-${customerId}` }),
setTwilioWebhooks.enqueue({ customerId }, { id: `set-twilio-webhooks-${customerId}` }), setTwilioWebhooks.enqueue({ customerId }, { id: `set-twilio-webhooks-${customerId}` }),
]); ]);
}, });
);

View File

@ -16,10 +16,7 @@ const StepOne: BlitzPage = () => {
}; };
StepOne.getLayout = (page) => ( StepOne.getLayout = (page) => (
<OnboardingLayout <OnboardingLayout currentStep={1} next={{ href: Routes.StepTwo().pathname, label: "Set up your phone number" }}>
currentStep={1}
next={{ href: Routes.StepTwo().pathname, label: "Set up your phone number" }}
>
{page} {page}
</OnboardingLayout> </OnboardingLayout>
); );
@ -39,16 +36,17 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
} }
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId: session.userId } }); const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId: session.userId } });
if (!phoneNumber) { if (phoneNumber) {
return { props: {} }; await session.$setPublicData({ hasCompletedOnboarding: true });
return {
redirect: {
destination: Routes.Messages().pathname,
permanent: false,
},
};
} }
return { return { props: {} };
redirect: {
destination: Routes.Messages().pathname,
permanent: false,
},
};
}; };
export default StepOne; export default StepOne;

View File

@ -102,6 +102,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, res }
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId: session.userId } }); const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId: session.userId } });
if (phoneNumber) { if (phoneNumber) {
await session.$setPublicData({ hasCompletedOnboarding: true });
return { return {
redirect: { redirect: {
destination: Routes.Messages().pathname, destination: Routes.Messages().pathname,
@ -129,10 +130,7 @@ export const getServerSideProps: GetServerSideProps<Props> = async ({ req, res }
}; };
} }
const incomingPhoneNumbers = await twilio( const incomingPhoneNumbers = await twilio(customer.accountSid, customer.authToken).incomingPhoneNumbers.list();
customer.accountSid,
customer.authToken,
).incomingPhoneNumbers.list();
const phoneNumbers = incomingPhoneNumbers.map(({ phoneNumber, sid }) => ({ phoneNumber, sid })); const phoneNumbers = incomingPhoneNumbers.map(({ phoneNumber, sid }) => ({ phoneNumber, sid }));
return { return {

View File

@ -48,10 +48,7 @@ const StepTwo: BlitzPage = () => {
<div className="flex flex-col space-y-4 items-center"> <div className="flex flex-col space-y-4 items-center">
<form onSubmit={onSubmit} className="flex flex-col gap-6"> <form onSubmit={onSubmit} className="flex flex-col gap-6">
<div className="w-full"> <div className="w-full">
<label <label htmlFor="twilioAccountSid" className="block text-sm font-medium text-gray-700">
htmlFor="twilioAccountSid"
className="block text-sm font-medium text-gray-700"
>
Twilio Account SID Twilio Account SID
</label> </label>
<input <input
@ -62,10 +59,7 @@ const StepTwo: BlitzPage = () => {
/> />
</div> </div>
<div className="w-full"> <div className="w-full">
<label <label htmlFor="twilioAuthToken" className="block text-sm font-medium text-gray-700">
htmlFor="twilioAuthToken"
className="block text-sm font-medium text-gray-700"
>
Twilio Auth Token Twilio Auth Token
</label> </label>
<input <input
@ -108,11 +102,7 @@ const StepTwoLayout: FunctionComponent = ({ children }) => {
return ( return (
<OnboardingLayout <OnboardingLayout
currentStep={2} currentStep={2}
next={ next={hasTwilioCredentials ? { href: Routes.StepThree().pathname, label: "Next" } : undefined}
hasTwilioCredentials
? { href: Routes.StepThree().pathname, label: "Next" }
: undefined
}
previous={{ href: Routes.StepOne().pathname, label: "Back" }} previous={{ href: Routes.StepOne().pathname, label: "Back" }}
> >
{children} {children}
@ -135,16 +125,17 @@ export const getServerSideProps: GetServerSideProps = async ({ req, res }) => {
} }
const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId: session.userId } }); const phoneNumber = await db.phoneNumber.findFirst({ where: { customerId: session.userId } });
if (!phoneNumber) { if (phoneNumber) {
return { props: {} }; await session.$setPublicData({ hasCompletedOnboarding: true });
return {
redirect: {
destination: Routes.Messages().pathname,
permanent: false,
},
};
} }
return { return { props: {} };
redirect: {
destination: Routes.Messages().pathname,
permanent: false,
},
};
}; };
export default StepTwo; export default StepTwo;

View File

@ -22,7 +22,8 @@ test.skip("renders blitz documentation link", () => {
paddleSubscriptionId: null, paddleSubscriptionId: null,
user: {} as any, user: {} as any,
}, },
hasCompletedOnboarding: false, hasFilledTwilioCredentials: false,
hasCompletedOnboarding: undefined,
}); });
const { getByText } = render(<Home />); const { getByText } = render(<Home />);

View File

@ -21,7 +21,7 @@
"semi": true, "semi": true,
"useTabs": true, "useTabs": true,
"tabWidth": 4, "tabWidth": 4,
"printWidth": 100, "printWidth": 120,
"trailingComma": "all", "trailingComma": "all",
"jsxBracketSameLine": false, "jsxBracketSameLine": false,
"quoteProps": "as-needed", "quoteProps": "as-needed",

View File

@ -12,6 +12,7 @@ declare module "blitz" {
PublicData: { PublicData: {
userId: User["id"]; userId: User["id"];
role: Role; role: Role;
hasCompletedOnboarding?: true;
}; };
} }
} }