Fix/duplicate events on onboarding (#716)

This commit is contained in:
Femi Odugbesan 2021-09-22 02:25:33 -05:00 committed by GitHub
parent d4f29464f2
commit a047177e72
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 115 additions and 16 deletions

View file

@ -0,0 +1,10 @@
import * as fetch from "@lib/core/http/fetch-wrapper";
import { EventType } from "@prisma/client";
type GetEventsResponse = { message: string; data: EventType[] };
const getEventTypes = async () => {
const response = await fetch.get<GetEventsResponse>("/api/event-type");
return response.data;
};
export default getEventTypes;

View file

@ -72,6 +72,7 @@
"devDependencies": { "devDependencies": {
"@types/bcryptjs": "^2.4.2", "@types/bcryptjs": "^2.4.2",
"@types/jest": "^27.0.1", "@types/jest": "^27.0.1",
"@types/lodash.debounce": "^4.0.6",
"@types/node": "^16.6.1", "@types/node": "^16.6.1",
"@types/nodemailer": "^6.4.4", "@types/nodemailer": "^6.4.4",
"@types/qrcode": "^1.4.1", "@types/qrcode": "^1.4.1",

View file

@ -0,0 +1,44 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "@lib/auth";
import prisma from "@lib/prisma";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req: req });
if (!session) {
res.status(401).json({ message: "Not authenticated" });
return;
}
if (!session.user?.id) {
console.error("Session is missing a user id");
return res.status(500).json({ message: "Something went wrong" });
}
if (req.method === "GET") {
const user = await prisma.user.findUnique({
where: {
id: session.user.id,
},
select: {
id: true,
eventTypes: {
where: {
team: null,
},
select: {
id: true,
title: true,
description: true,
length: true,
schedulingType: true,
slug: true,
hidden: true,
},
},
},
});
return res.status(200).json({ message: "Events.", data: user.eventTypes });
}
}

View file

@ -50,7 +50,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
"theme", "theme",
"completedOnboarding", "completedOnboarding",
]), ]),
bio: req.body.description, bio: req.body.description ?? req.body.data?.bio,
}, },
select: { select: {
id: true, id: true,

View file

@ -30,6 +30,9 @@ import classnames from "classnames";
import { ArrowRightIcon } from "@heroicons/react/outline"; import { ArrowRightIcon } from "@heroicons/react/outline";
import { getSession } from "@lib/auth"; import { getSession } from "@lib/auth";
import Button from "@components/ui/Button"; import Button from "@components/ui/Button";
import debounce from "lodash.debounce";
import Loader from "@components/Loader";
import getEventTypes from "../lib/queries/event-types/get-event-types";
dayjs.extend(utc); dayjs.extend(utc);
dayjs.extend(timezone); dayjs.extend(timezone);
@ -63,6 +66,7 @@ type OnboardingProps = {
export default function Onboarding(props: OnboardingProps) { export default function Onboarding(props: OnboardingProps) {
const router = useRouter(); const router = useRouter();
const [isSubmitting, setSubmitting] = React.useState(false);
const [enteredName, setEnteredName] = React.useState(); const [enteredName, setEnteredName] = React.useState();
const Sess = useSession(); const Sess = useSession();
const [ready, setReady] = useState(false); const [ready, setReady] = useState(false);
@ -116,7 +120,7 @@ export default function Onboarding(props: OnboardingProps) {
return responseData.data; return responseData.data;
}; };
const integrationHandler = (type: string) => { const handleAddIntegration = (type: string) => {
if (type === "caldav_calendar") { if (type === "caldav_calendar") {
setAddCalDavError(null); setAddCalDavError(null);
setIsAddCalDavIntegrationDialogOpen(true); setIsAddCalDavIntegrationDialogOpen(true);
@ -137,7 +141,7 @@ export default function Onboarding(props: OnboardingProps) {
} }
return ( return (
<li onClick={() => integrationHandler(integration.type)} key={integration.type} className="flex py-4"> <li onClick={() => handleAddIntegration(integration.type)} key={integration.type} className="flex py-4">
<div className="w-1/12 mr-4 pt-2"> <div className="w-1/12 mr-4 pt-2">
<img className="h-8 w-8 mr-2" src={integration.imageSrc} alt={integration.title} /> <img className="h-8 w-8 mr-2" src={integration.imageSrc} alt={integration.title} />
</div> </div>
@ -148,7 +152,7 @@ export default function Onboarding(props: OnboardingProps) {
</Text> </Text>
</div> </div>
<div className="w-2/12 text-right pt-2"> <div className="w-2/12 text-right pt-2">
<Button color="secondary" onClick={() => integrationHandler(integration.type)}> <Button color="secondary" onClick={() => handleAddIntegration(integration.type)}>
Connect Connect
</Button> </Button>
</div> </div>
@ -280,6 +284,7 @@ export default function Onboarding(props: OnboardingProps) {
const handleConfirmStep = async () => { const handleConfirmStep = async () => {
try { try {
setSubmitting(true);
if ( if (
steps[currentStep] && steps[currentStep] &&
steps[currentStep]?.onComplete && steps[currentStep]?.onComplete &&
@ -288,12 +293,16 @@ export default function Onboarding(props: OnboardingProps) {
await steps[currentStep].onComplete(); await steps[currentStep].onComplete();
} }
incrementStep(); incrementStep();
setSubmitting(false);
} catch (error) { } catch (error) {
console.log("handleConfirmStep", error); console.log("handleConfirmStep", error);
setSubmitting(false);
setError(error); setError(error);
} }
}; };
const debouncedHandleConfirmStep = debounce(handleConfirmStep, 850);
const handleSkipStep = () => { const handleSkipStep = () => {
incrementStep(); incrementStep();
}; };
@ -331,17 +340,22 @@ export default function Onboarding(props: OnboardingProps) {
* then the default availability is applied. * then the default availability is applied.
*/ */
const completeOnboarding = async () => { const completeOnboarding = async () => {
setSubmitting(true);
if (!props.eventTypes || props.eventTypes.length === 0) { if (!props.eventTypes || props.eventTypes.length === 0) {
Promise.all( const eventTypes = await getEventTypes();
DEFAULT_EVENT_TYPES.map(async (event) => { if (eventTypes.length === 0) {
return await createEventType(event); Promise.all(
}) DEFAULT_EVENT_TYPES.map(async (event) => {
); return await createEventType(event);
})
);
}
} }
await updateUser({ await updateUser({
completedOnboarding: true, completedOnboarding: true,
}); });
setSubmitting(false);
router.push("/event-types"); router.push("/event-types");
}; };
@ -365,7 +379,7 @@ export default function Onboarding(props: OnboardingProps) {
id="name" id="name"
autoComplete="given-name" autoComplete="given-name"
placeholder="Your name" placeholder="Your name"
defaultValue={props.user.name} defaultValue={props.user.name ?? enteredName}
required required
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm" className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
/> />
@ -397,13 +411,16 @@ export default function Onboarding(props: OnboardingProps) {
cancelText: "Set up later", cancelText: "Set up later",
onComplete: async () => { onComplete: async () => {
try { try {
setSubmitting(true);
await updateUser({ await updateUser({
name: nameRef.current.value, name: nameRef.current.value,
timeZone: selectedTimeZone.value, timeZone: selectedTimeZone.value,
}); });
setEnteredName(nameRef.current.value); setEnteredName(nameRef.current.value);
setSubmitting(true);
} catch (error) { } catch (error) {
setError(error); setError(error);
setSubmitting(false);
} }
}, },
}, },
@ -435,10 +452,12 @@ export default function Onboarding(props: OnboardingProps) {
<SchedulerForm <SchedulerForm
onSubmit={async (data) => { onSubmit={async (data) => {
try { try {
setSubmitting(true);
await createSchedule({ await createSchedule({
freeBusyTimes: data, freeBusyTimes: data,
}); });
handleConfirmStep(); debouncedHandleConfirmStep();
setSubmitting(false);
} catch (error) { } catch (error) {
setError(error); setError(error);
} }
@ -505,11 +524,15 @@ export default function Onboarding(props: OnboardingProps) {
cancelText: "Set up later", cancelText: "Set up later",
onComplete: async () => { onComplete: async () => {
try { try {
setSubmitting(true);
console.log("updating");
await updateUser({ await updateUser({
bio: bioRef.current.value, description: bioRef.current.value,
}); });
setSubmitting(false);
} catch (error) { } catch (error) {
setError(error); setError(error);
setSubmitting(false);
} }
}, },
}, },
@ -532,8 +555,13 @@ export default function Onboarding(props: OnboardingProps) {
<link rel="icon" href="/favicon.ico" /> <link rel="icon" href="/favicon.ico" />
</Head> </Head>
{isSubmitting && (
<div className="fixed w-full h-full bg-white bg-opacity-25 flex flex-col justify-center items-center content-center z-10">
<Loader />
</div>
)}
<div className="mx-auto py-24 px-4"> <div className="mx-auto py-24 px-4">
<article> <article className="relative">
<section className="sm:mx-auto sm:w-full sm:max-w-md space-y-4"> <section className="sm:mx-auto sm:w-full sm:max-w-md space-y-4">
<header className=""> <header className="">
<Text className="text-white" variant="largetitle"> <Text className="text-white" variant="largetitle">
@ -572,7 +600,11 @@ export default function Onboarding(props: OnboardingProps) {
{!steps[currentStep].hideConfirm && ( {!steps[currentStep].hideConfirm && (
<footer className="py-6 sm:mx-auto sm:w-full sm:max-w-md flex flex-col space-y-6 mt-8"> <footer className="py-6 sm:mx-auto sm:w-full sm:max-w-md flex flex-col space-y-6 mt-8">
<Button className="justify-center" onClick={handleConfirmStep} EndIcon={ArrowRightIcon}> <Button
className="justify-center"
disabled={isSubmitting}
onClick={debouncedHandleConfirmStep}
EndIcon={ArrowRightIcon}>
{steps[currentStep].confirmText} {steps[currentStep].confirmText}
</Button> </Button>
</footer> </footer>
@ -580,11 +612,11 @@ export default function Onboarding(props: OnboardingProps) {
</section> </section>
<section className="py-6 mt-8 mx-auto max-w-xl"> <section className="py-6 mt-8 mx-auto max-w-xl">
<div className="flex justify-between flex-row-reverse"> <div className="flex justify-between flex-row-reverse">
<button onClick={handleSkipStep}> <button disabled={isSubmitting} onClick={handleSkipStep}>
<Text variant="caption">Skip Step</Text> <Text variant="caption">Skip Step</Text>
</button> </button>
{currentStep !== 0 && ( {currentStep !== 0 && (
<button onClick={decrementStep}> <button disabled={isSubmitting} onClick={decrementStep}>
<Text variant="caption">Prev Step</Text> <Text variant="caption">Prev Step</Text>
</button> </button>
)} )}

View file

@ -1371,6 +1371,18 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.9.tgz#97edc9037ea0c38585320b28964dde3b39e4660d"
integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ== integrity sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==
"@types/lodash.debounce@^4.0.6":
version "4.0.6"
resolved "https://registry.yarnpkg.com/@types/lodash.debounce/-/lodash.debounce-4.0.6.tgz#c5a2326cd3efc46566c47e4c0aa248dc0ee57d60"
integrity sha512-4WTmnnhCfDvvuLMaF3KV4Qfki93KebocUF45msxhYyjMttZDQYzHkO639ohhk8+oco2cluAFL3t5+Jn4mleylQ==
dependencies:
"@types/lodash" "*"
"@types/lodash@*":
version "4.14.173"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.173.tgz#9d3b674c67a26cf673756f6aca7b429f237f91ed"
integrity sha512-vv0CAYoaEjCw/mLy96GBTnRoZrSxkGE0BKzKimdR8P3OzrNYNvBgtW7p055A+E8C31vXNUhWKoFCbhq7gbyhFg==
"@types/lodash@^4.14.165": "@types/lodash@^4.14.165":
version "4.14.172" version "4.14.172"
resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a" resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.172.tgz#aad774c28e7bfd7a67de25408e03ee5a8c3d028a"