fix #582: send user back to onboarding after adding integration (#635)

* fix #582: send user back to onboarding after adding integration if incomplete

* use more accurate, descriptive typings

Co-authored-by: Alex van Andel <me@alexvanandel.com>
This commit is contained in:
Femi Odugbesan 2021-10-08 08:10:57 -05:00 committed by GitHub
parent 2c9b301b77
commit 015b7c18af
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 73 additions and 55 deletions

View file

@ -1,3 +1,15 @@
import { User } from "@prisma/client";
import dayjs from "dayjs";
export const ONBOARDING_INTRODUCED_AT = dayjs("September 1 2021").toISOString();
export const ONBOARDING_NEXT_REDIRECT = {
redirect: {
permanent: false,
destination: "/getting-started",
},
} as const;
export const shouldShowOnboarding = (user: Pick<User, "createdDate" | "completedOnboarding">) => {
return !user.completedOnboarding && dayjs(user.createdDate).isAfter(ONBOARDING_INTRODUCED_AT);
};

View file

@ -8,9 +8,8 @@ import {
PlusIcon,
UsersIcon,
} from "@heroicons/react/solid";
import { SchedulingType } from "@prisma/client";
import { Prisma } from "@prisma/client";
import dayjs from "dayjs";
import { SchedulingType, Prisma } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Head from "next/head";
import Link from "next/link";
@ -23,7 +22,7 @@ import { getSession } from "@lib/auth";
import classNames from "@lib/classNames";
import { HttpError } from "@lib/core/http/error";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { ONBOARDING_INTRODUCED_AT } from "@lib/getting-started";
import { shouldShowOnboarding, ONBOARDING_NEXT_REDIRECT } from "@lib/getting-started";
import { useLocale } from "@lib/hooks/useLocale";
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
import createEventType from "@lib/mutations/event-types/create-event-type";
@ -145,7 +144,7 @@ const EventTypesPage = (props: PageProps) => {
"hover:bg-neutral-50 flex justify-between items-center ",
type.$disabled && "pointer-events-none"
)}>
<div className="flex items-center w-full justify-between px-4 py-4 sm:px-6 hover:bg-neutral-50">
<div className="flex items-center justify-between w-full px-4 py-4 sm:px-6 hover:bg-neutral-50">
<Link href={"/event-types/" + type.id}>
<a className="flex-grow text-sm truncate">
<div>
@ -224,7 +223,7 @@ const EventTypesPage = (props: PageProps) => {
leaveTo="transform opacity-0 scale-95">
<Menu.Items
static
className="absolute z-10 right-0 w-56 mt-2 origin-top-right bg-white divide-y rounded-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none divide-neutral-100">
className="absolute right-0 z-10 w-56 mt-2 origin-top-right bg-white divide-y rounded-sm shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none divide-neutral-100">
<div className="py-1">
<Menu.Item>
{({ active }) => (
@ -558,7 +557,7 @@ const CreateNewEventDialog = ({
);
};
export async function getServerSideProps(context) {
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
@ -647,13 +646,10 @@ export async function getServerSideProps(context) {
};
}
if (!user.completedOnboarding && dayjs(user.createdDate).isAfter(ONBOARDING_INTRODUCED_AT)) {
return {
redirect: {
permanent: false,
destination: "/getting-started",
},
};
if (
shouldShowOnboarding({ completedOnboarding: user.completedOnboarding, createdDate: user.createdDate })
) {
return ONBOARDING_NEXT_REDIRECT;
}
// backwards compatibility, TMP:

View file

@ -6,6 +6,7 @@ import Link from "next/link";
import { useCallback, useEffect, useRef, useState } from "react";
import { getSession } from "@lib/auth";
import { ONBOARDING_NEXT_REDIRECT, shouldShowOnboarding } from "@lib/getting-started";
import AddAppleIntegration, {
ADD_APPLE_INTEGRATION_FORM_TITLE,
} from "@lib/integrations/Apple/components/AddAppleIntegration";
@ -138,8 +139,8 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
const ConnectNewAppDialog = () => (
<Dialog>
<DialogTrigger className="py-2 px-4 mt-6 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="w-5 h-5 mr-1 inline" />
<DialogTrigger className="px-4 py-2 mt-6 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<PlusIcon className="inline w-5 h-5 mr-1" />
Connect a new App
</DialogTrigger>
@ -152,14 +153,14 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
.map((integration) => {
return (
<li key={integration.type} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<img className="h-8 w-8 mr-2" src={integration.imageSrc} alt={integration.title} />
<div className="w-1/12 pt-2 mr-4">
<img className="w-8 h-8 mr-2" src={integration.imageSrc} alt={integration.title} />
</div>
<div className="w-10/12">
<h2 className="font-cal text-gray-800 font-medium">{integration.title}</h2>
<p className="text-gray-400 text-sm">{integration.description}</p>
<h2 className="font-medium text-gray-800 font-cal">{integration.title}</h2>
<p className="text-sm text-gray-400">{integration.description}</p>
</div>
<div className="w-2/12 text-right pt-2">
<div className="w-2/12 pt-2 text-right">
<button
onClick={() => integrationHandler(integration.type)}
className="font-medium text-neutral-900 hover:text-neutral-500">
@ -171,7 +172,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
})}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-2">
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose asChild>
<Button color="secondary">Cancel</Button>
</DialogClose>
@ -182,7 +183,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
const SelectCalendarDialog = () => (
<Dialog onOpenChange={(open) => !open && onCloseSelectCalendar()}>
<DialogTrigger className="py-2 px-4 mt-6 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
<DialogTrigger className="px-4 py-2 mt-6 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
Select calendars
</DialogTrigger>
@ -192,20 +193,20 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
subtitle="If no entry is selected, all calendars will be checked"
/>
<div className="my-4">
<ul className="divide-y divide-gray-200 max-h-96 overflow-y-auto">
<ul className="overflow-y-auto divide-y divide-gray-200 max-h-96">
{selectableCalendars.map((calendar) => (
<li key={calendar.name} className="flex py-4">
<div className="w-1/12 mr-4 pt-2">
<div className="w-1/12 pt-2 mr-4">
<img
className="h-8 w-8 mr-2"
className="w-8 h-8 mr-2"
src={getCalendarIntegrationImage(calendar.integration)}
alt={calendar.integration}
/>
</div>
<div className="w-10/12 pt-3">
<h2 className="text-gray-800 font-medium">{calendar.name}</h2>
<h2 className="font-medium text-gray-800">{calendar.name}</h2>
</div>
<div className="w-2/12 text-right pt-3">
<div className="w-2/12 pt-3 text-right">
<Switch
defaultChecked={calendar.selected}
onCheckedChange={calendarSelectionHandler(calendar)}
@ -215,7 +216,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
))}
</ul>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-2">
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<DialogClose asChild>
<Button color="secondary">Confirm</Button>
</DialogClose>
@ -275,7 +276,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
/>
<div className="my-4">
{addCalDavError && (
<p className="text-red-700 text-sm">
<p className="text-sm text-red-700">
<span className="font-bold">Error: </span>
{addCalDavError.message}
</p>
@ -285,11 +286,11 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
onSubmit={handleAddCalDavIntegrationSaveButtonPress}
/>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-2">
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<Button
type="submit"
form={ADD_CALDAV_INTEGRATION_FORM_TITLE}
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
className="flex justify-center px-4 py-2 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
Save
</Button>
<DialogClose
@ -329,7 +330,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
/>
<div className="my-4">
{addAppleError && (
<p className="text-red-700 text-sm">
<p className="text-sm text-red-700">
<span className="font-bold">Error: </span>
{addAppleError.message}
</p>
@ -339,11 +340,11 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
onSubmit={handleAddAppleIntegrationSaveButtonPress}
/>
</div>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-2">
<div className="gap-2 mt-5 sm:mt-4 sm:flex sm:flex-row-reverse">
<button
type="submit"
form={ADD_APPLE_INTEGRATION_FORM_TITLE}
className="flex justify-center py-2 px-4 border border-transparent rounded-sm shadow-sm text-sm font-medium text-white bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
className="flex justify-center px-4 py-2 text-sm font-medium text-white border border-transparent rounded-sm shadow-sm bg-neutral-900 hover:bg-neutral-700 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-neutral-900">
Save
</button>
<DialogClose
@ -366,7 +367,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
return (
<div>
<Shell heading="Integrations" subtitle="Connect your favourite apps." CTA={<ConnectNewAppDialog />}>
<div className="bg-white border border-gray-200 overflow-hidden rounded-sm mb-8">
<div className="mb-8 overflow-hidden bg-white border border-gray-200 rounded-sm">
{integrations.filter((ig) => ig.credential).length !== 0 ? (
<ul className="divide-y divide-gray-200">
{integrations
@ -376,13 +377,13 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
<Link href={"/integrations/" + ig.credential.id}>
<a className="block hover:bg-gray-50">
<div className="flex items-center px-4 py-4 sm:px-6">
<div className="min-w-0 flex-1 flex items-center">
<div className="flex items-center flex-1 min-w-0">
<div className="flex-shrink-0">
<img className="h-10 w-10 mr-2" src={ig.imageSrc} alt={ig.title} />
<img className="w-10 h-10 mr-2" src={ig.imageSrc} alt={ig.title} />
</div>
<div className="min-w-0 flex-1 px-4 md:grid md:grid-cols-2 md:gap-4">
<div className="flex-1 min-w-0 px-4 md:grid md:grid-cols-2 md:gap-4">
<div>
<p className="text-sm font-medium text-neutral-900 truncate">{ig.title}</p>
<p className="text-sm font-medium truncate text-neutral-900">{ig.title}</p>
<p className="flex items-center text-sm text-gray-500">
{ig.type.endsWith("_calendar") && (
<span className="truncate">Calendar Integration</span>
@ -394,13 +395,13 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
</div>
<div className="hidden md:block">
{ig.credential.key && (
<p className="mt-2 flex items-center text text-gray-500">
<p className="flex items-center mt-2 text-gray-500 text">
<CheckCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-green-400" />
Connected
</p>
)}
{!ig.credential.key && (
<p className="mt-3 flex items-center text text-gray-500">
<p className="flex items-center mt-3 text-gray-500 text">
<XCircleIcon className="flex-shrink-0 mr-1.5 h-5 w-5 text-yellow-400" />
Not connected
</p>
@ -408,7 +409,7 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
</div>
</div>
<div>
<ChevronRightIcon className="h-5 w-5 text-gray-400" />
<ChevronRightIcon className="w-5 h-5 text-gray-400" />
</div>
</div>
</div>
@ -418,13 +419,13 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
))}
</ul>
) : (
<div className="bg-white shadow rounded-sm">
<div className="bg-white rounded-sm shadow">
<div className="flex">
<div className="py-9 pl-8">
<InformationCircleIcon className="text-neutral-900 w-16" />
<div className="pl-8 py-9">
<InformationCircleIcon className="w-16 text-neutral-900" />
</div>
<div className="py-5 sm:p-6">
<h3 className="text-lg leading-6 font-medium text-gray-900">
<h3 className="text-lg font-medium leading-6 text-gray-900">
You don&apos;t have any apps connected.
</h3>
<div className="mt-2 text-sm text-gray-500">
@ -438,10 +439,10 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
</div>
)}
</div>
<div className="bg-white border border-gray-200 rounded-sm mb-8">
<div className="mb-8 bg-white border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="font-cal text-lg leading-6 font-medium text-gray-900">Select calendars</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<h3 className="text-lg font-medium leading-6 text-gray-900 font-cal">Select calendars</h3>
<div className="max-w-xl mt-2 text-sm text-gray-500">
<p>Select which calendars are checked for availability to prevent double bookings.</p>
</div>
<SelectCalendarDialog />
@ -449,8 +450,8 @@ export default function Home({ integrations }: inferSSRProps<typeof getServerSid
</div>
<div className="border border-gray-200 rounded-sm">
<div className="px-4 py-5 sm:p-6">
<h3 className="font-cal text-lg leading-6 font-medium text-gray-900">Launch your own App</h3>
<div className="mt-2 max-w-xl text-sm text-gray-500">
<h3 className="text-lg font-medium leading-6 text-gray-900 font-cal">Launch your own App</h3>
<div className="max-w-xl mt-2 text-sm text-gray-500">
<p>If you want to add your own App here, get in touch with us.</p>
</div>
<div className="mt-5">
@ -485,14 +486,23 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
key: true,
},
},
completedOnboarding: true,
createdDate: true,
},
});
if (!user) return { redirect: { permanent: false, destination: "/auth/login" } };
if (!user)
return {
redirect: { permanent: false, destination: "/auth/login" },
};
const { credentials } = user;
if (
shouldShowOnboarding({ completedOnboarding: user.completedOnboarding, createdDate: user.createdDate })
) {
return ONBOARDING_NEXT_REDIRECT;
}
const integrations = getIntegrations(credentials);
const integrations = getIntegrations(user.credentials);
return {
props: { session, integrations },