* 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:
parent
2c9b301b77
commit
015b7c18af
3 changed files with 73 additions and 55 deletions
|
@ -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);
|
||||
};
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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'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 },
|
||||
|
|
Loading…
Reference in a new issue