Perf: Optimize event-types page (#2436)
* Perf: Optimize event-types page * Memoize layout in Shell * setQueryState without awaiting mutate for optimistic update * Update Shell.tsx * Fix types * Update auth-index.test.ts Co-authored-by: zomars <zomars@me.com> Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
This commit is contained in:
parent
3c6ac395cc
commit
699d910ab4
6 changed files with 203 additions and 129 deletions
|
@ -1,23 +1,25 @@
|
|||
import { SelectorIcon } from "@heroicons/react/outline";
|
||||
import {
|
||||
ArrowLeftIcon,
|
||||
CalendarIcon,
|
||||
ClockIcon,
|
||||
CogIcon,
|
||||
ExternalLinkIcon,
|
||||
LinkIcon,
|
||||
LogoutIcon,
|
||||
ViewGridIcon,
|
||||
MoonIcon,
|
||||
MapIcon,
|
||||
ArrowLeftIcon,
|
||||
MoonIcon,
|
||||
ViewGridIcon,
|
||||
} from "@heroicons/react/solid";
|
||||
import { signOut, useSession } from "next-auth/react";
|
||||
import { SessionContextValue, signOut, useSession } from "next-auth/react";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
import React, { Fragment, ReactNode, useEffect } from "react";
|
||||
import { Toaster } from "react-hot-toast";
|
||||
|
||||
import { useIsEmbed } from "@calcom/embed-core";
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { UserPlan } from "@calcom/prisma/client";
|
||||
import Button from "@calcom/ui/Button";
|
||||
import Dropdown, {
|
||||
DropdownMenuContent,
|
||||
|
@ -30,9 +32,8 @@ import TrialBanner from "@ee/components/TrialBanner";
|
|||
import HelpMenuItem from "@ee/components/support/HelpMenuItem";
|
||||
|
||||
import classNames from "@lib/classNames";
|
||||
import { NEXT_PUBLIC_BASE_URL } from "@lib/config/constants";
|
||||
import { WEBAPP_URL } from "@lib/config/constants";
|
||||
import { shouldShowOnboarding } from "@lib/getting-started";
|
||||
import { useLocale } from "@lib/hooks/useLocale";
|
||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
|
||||
import { trpc } from "@lib/trpc";
|
||||
|
||||
|
@ -58,7 +59,6 @@ function useRedirectToLoginIfUnauthenticated(isPublic = false) {
|
|||
const { data: session, status } = useSession();
|
||||
const loading = status === "loading";
|
||||
const router = useRouter();
|
||||
const shouldDisplayUnauthed = router.pathname.startsWith("/apps");
|
||||
|
||||
useEffect(() => {
|
||||
if (isPublic) {
|
||||
|
@ -69,7 +69,7 @@ function useRedirectToLoginIfUnauthenticated(isPublic = false) {
|
|||
router.replace({
|
||||
pathname: "/auth/login",
|
||||
query: {
|
||||
callbackUrl: `${NEXT_PUBLIC_BASE_URL}/${location.pathname}${location.search}`,
|
||||
callbackUrl: `${WEBAPP_URL}/${location.pathname}${location.search}`,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
@ -78,7 +78,6 @@ function useRedirectToLoginIfUnauthenticated(isPublic = false) {
|
|||
|
||||
return {
|
||||
loading: loading && !session,
|
||||
shouldDisplayUnauthed,
|
||||
session,
|
||||
};
|
||||
}
|
||||
|
@ -122,28 +121,14 @@ export function ShellSubHeading(props: {
|
|||
);
|
||||
}
|
||||
|
||||
export default function Shell(props: {
|
||||
centered?: boolean;
|
||||
title?: string;
|
||||
heading?: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
children: ReactNode;
|
||||
CTA?: ReactNode;
|
||||
large?: boolean;
|
||||
HeadingLeftIcon?: ReactNode;
|
||||
backPath?: string; // renders back button to specified path
|
||||
// use when content needs to expand with flex
|
||||
flexChildrenContainer?: boolean;
|
||||
isPublic?: boolean;
|
||||
}) {
|
||||
const Layout = ({
|
||||
status,
|
||||
plan,
|
||||
...props
|
||||
}: LayoutProps & { status: SessionContextValue["status"]; plan?: UserPlan }) => {
|
||||
const isEmbed = useIsEmbed();
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
const { loading, session } = useRedirectToLoginIfUnauthenticated(props.isPublic);
|
||||
const { isRedirectingToOnboarding } = useRedirectToOnboardingIfNeeded();
|
||||
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
const { t } = useLocale();
|
||||
const navigation = [
|
||||
{
|
||||
name: t("event_types_page_title"),
|
||||
|
@ -188,35 +173,10 @@ export default function Shell(props: {
|
|||
current: router.asPath.startsWith("/settings"),
|
||||
},
|
||||
];
|
||||
|
||||
useEffect(() => {
|
||||
telemetry.withJitsu((jitsu) => {
|
||||
return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.asPath));
|
||||
});
|
||||
}, [telemetry, router.asPath]);
|
||||
|
||||
const pageTitle = typeof props.heading === "string" ? props.heading : props.title;
|
||||
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
|
||||
const i18n = useViewerI18n();
|
||||
const { status } = useSession();
|
||||
|
||||
if (i18n.status === "loading" || query.status === "loading" || isRedirectingToOnboarding || loading) {
|
||||
// show spinner whilst i18n is loading to avoid language flicker
|
||||
return (
|
||||
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!session && !props.isPublic) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomBranding lightVal={user?.brandColor} darkVal={user?.darkBrandColor} />
|
||||
<HeadSeo
|
||||
title={pageTitle ?? "Cal.com"}
|
||||
description={props.subtitle ? props.subtitle?.toString() : ""}
|
||||
|
@ -306,8 +266,8 @@ export default function Shell(props: {
|
|||
<small style={{ fontSize: "0.5rem" }} className="mx-3 mt-1 mb-2 hidden opacity-50 lg:block">
|
||||
© {new Date().getFullYear()} Cal.com, Inc. v.{pkg.version + "-"}
|
||||
{process.env.NEXT_PUBLIC_WEBSITE_URL === "https://cal.com" ? "h" : "sh"}
|
||||
<span className="lowercase" data-testid={`plan-${user?.plan.toLowerCase()}`}>
|
||||
-{user && user.plan}
|
||||
<span className="lowercase" data-testid={`plan-${plan?.toLowerCase()}`}>
|
||||
-{plan}
|
||||
</span>
|
||||
</small>
|
||||
</div>
|
||||
|
@ -426,6 +386,60 @@ export default function Shell(props: {
|
|||
</div>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoizedLayout = React.memo(Layout);
|
||||
|
||||
type LayoutProps = {
|
||||
centered?: boolean;
|
||||
title?: string;
|
||||
heading?: ReactNode;
|
||||
subtitle?: ReactNode;
|
||||
children: ReactNode;
|
||||
CTA?: ReactNode;
|
||||
large?: boolean;
|
||||
HeadingLeftIcon?: ReactNode;
|
||||
backPath?: string; // renders back button to specified path
|
||||
// use when content needs to expand with flex
|
||||
flexChildrenContainer?: boolean;
|
||||
isPublic?: boolean;
|
||||
};
|
||||
|
||||
export default function Shell(props: LayoutProps) {
|
||||
const router = useRouter();
|
||||
const { loading, session } = useRedirectToLoginIfUnauthenticated(props.isPublic);
|
||||
const { isRedirectingToOnboarding } = useRedirectToOnboardingIfNeeded();
|
||||
const telemetry = useTelemetry();
|
||||
|
||||
useEffect(() => {
|
||||
telemetry.withJitsu((jitsu) => {
|
||||
return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.asPath));
|
||||
});
|
||||
}, [telemetry, router.asPath]);
|
||||
|
||||
const query = useMeQuery();
|
||||
const user = query.data;
|
||||
|
||||
const i18n = useViewerI18n();
|
||||
const { status } = useSession();
|
||||
|
||||
if (i18n.status === "loading" || query.status === "loading" || isRedirectingToOnboarding || loading) {
|
||||
// show spinner whilst i18n is loading to avoid language flicker
|
||||
return (
|
||||
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
|
||||
<Loader />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!session && !props.isPublic) return null;
|
||||
|
||||
return (
|
||||
<>
|
||||
<CustomBranding lightVal={user?.brandColor} darkVal={user?.darkBrandColor} />
|
||||
<MemoizedLayout plan={user?.plan} status={status} {...props} />
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
function UserDropdown({ small }: { small?: boolean }) {
|
||||
|
|
|
@ -9,8 +9,21 @@ import {
|
|||
|
||||
import { Alert } from "@calcom/ui/Alert";
|
||||
|
||||
import { trpc } from "@lib/trpc";
|
||||
|
||||
import Loader from "@components/Loader";
|
||||
|
||||
import type { AppRouter } from "@server/routers/_app";
|
||||
import type { TRPCClientErrorLike } from "@trpc/client";
|
||||
import type { UseTRPCQueryOptions } from "@trpc/react";
|
||||
// import type { inferProcedures } from "@trpc/react/src/createReactQueryHooks";
|
||||
import type {
|
||||
inferHandlerInput,
|
||||
inferProcedureInput,
|
||||
inferProcedureOutput,
|
||||
ProcedureRecord,
|
||||
} from "@trpc/server";
|
||||
|
||||
type ErrorLike = {
|
||||
message: string;
|
||||
};
|
||||
|
@ -72,3 +85,31 @@ export function QueryCell<TData, TError extends ErrorLike>(
|
|||
// impossible state
|
||||
return null;
|
||||
}
|
||||
|
||||
type inferProcedures<TObj extends ProcedureRecord<any, any, any, any, any, any>> = {
|
||||
[TPath in keyof TObj]: {
|
||||
input: inferProcedureInput<TObj[TPath]>;
|
||||
output: inferProcedureOutput<TObj[TPath]>;
|
||||
};
|
||||
};
|
||||
type TQueryValues = inferProcedures<AppRouter["_def"]["queries"]>;
|
||||
type TQueries = AppRouter["_def"]["queries"];
|
||||
type TError = TRPCClientErrorLike<AppRouter>;
|
||||
|
||||
const withQuery = <TPath extends keyof TQueryValues & string>(
|
||||
pathAndInput: [path: TPath, ...args: inferHandlerInput<TQueries[TPath]>],
|
||||
params?: UseTRPCQueryOptions<TPath, TQueryValues[TPath]["input"], TQueryValues[TPath]["output"], TError>
|
||||
) => {
|
||||
return function WithQuery(
|
||||
opts: Omit<
|
||||
Partial<QueryCellOptionsWithEmpty<TQueryValues[TPath]["output"], TError>> &
|
||||
QueryCellOptionsNoEmpty<TQueryValues[TPath]["output"], TError>,
|
||||
"query"
|
||||
>
|
||||
) {
|
||||
const query = trpc.useQuery(pathAndInput, params);
|
||||
return <QueryCell query={query} {...opts} />;
|
||||
};
|
||||
};
|
||||
|
||||
export { withQuery };
|
||||
|
|
|
@ -9,7 +9,7 @@ import showToast from "@calcom/lib/notification";
|
|||
import { Button } from "@calcom/ui";
|
||||
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "@calcom/ui/Dropdown";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
import { withQuery } from "@lib/QueryCell";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||
|
||||
|
@ -99,13 +99,14 @@ export function AvailabilityList({ schedules }: inferQueryOutput<"viewer.availab
|
|||
);
|
||||
}
|
||||
|
||||
const WithQuery = withQuery(["viewer.availability.list"]);
|
||||
|
||||
export default function AvailabilityPage() {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.useQuery(["viewer.availability.list"]);
|
||||
return (
|
||||
<div>
|
||||
<Shell heading={t("availability")} subtitle={t("configure_availability")} CTA={<NewScheduleButton />}>
|
||||
<QueryCell query={query} success={({ data }) => <AvailabilityList {...data} />} />
|
||||
<WithQuery success={({ data }) => <AvailabilityList {...data} />} />
|
||||
</Shell>
|
||||
</div>
|
||||
);
|
||||
|
|
|
@ -30,7 +30,7 @@ import Dropdown, {
|
|||
DropdownMenuSeparator,
|
||||
} from "@calcom/ui/Dropdown";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
import { withQuery } from "@lib/QueryCell";
|
||||
import classNames from "@lib/classNames";
|
||||
import { HttpError } from "@lib/core/http/error";
|
||||
import { inferQueryOutput, trpc } from "@lib/trpc";
|
||||
|
@ -63,38 +63,83 @@ type EventTypeGroup = inferQueryOutput<"viewer.eventTypes">["eventTypeGroups"][n
|
|||
type EventType = EventTypeGroup["eventTypes"][number];
|
||||
interface EventTypeListProps {
|
||||
group: EventTypeGroup;
|
||||
groupIndex: number;
|
||||
readOnly: boolean;
|
||||
types: EventType[];
|
||||
}
|
||||
|
||||
export const EventTypeList = ({ group, readOnly, types }: EventTypeListProps): JSX.Element => {
|
||||
const Item = ({ type, group, readOnly }: any) => {
|
||||
const { t } = useLocale();
|
||||
|
||||
return (
|
||||
<Link href={"/event-types/" + type.id}>
|
||||
<a
|
||||
className="flex-grow truncate text-sm"
|
||||
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
||||
<div>
|
||||
<span
|
||||
className="truncate font-medium text-neutral-900 ltr:mr-1 rtl:ml-1"
|
||||
data-testid={"event-type-title-" + type.id}>
|
||||
{type.title}
|
||||
</span>
|
||||
<small
|
||||
className="hidden text-neutral-500 sm:inline"
|
||||
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
|
||||
{type.hidden && (
|
||||
<span className="rtl:mr-2inline items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800 ltr:ml-2">
|
||||
{t("hidden")}
|
||||
</span>
|
||||
)}
|
||||
{readOnly && (
|
||||
<span className="rtl:mr-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2">
|
||||
{t("readonly")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EventTypeDescription eventType={type} />
|
||||
</a>
|
||||
</Link>
|
||||
);
|
||||
};
|
||||
|
||||
const MemoizedItem = React.memo(Item);
|
||||
|
||||
export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeListProps): JSX.Element => {
|
||||
const { t } = useLocale();
|
||||
const router = useRouter();
|
||||
|
||||
const utils = trpc.useContext();
|
||||
const mutation = trpc.useMutation("viewer.eventTypeOrder", {
|
||||
onError: (err) => {
|
||||
onError: async (err) => {
|
||||
console.error(err.message);
|
||||
},
|
||||
async onSettled() {
|
||||
await utils.cancelQuery(["viewer.eventTypes"]);
|
||||
await utils.invalidateQueries(["viewer.eventTypes"]);
|
||||
},
|
||||
});
|
||||
const [sortableTypes, setSortableTypes] = useState(types);
|
||||
useEffect(() => {
|
||||
setSortableTypes(types);
|
||||
}, [types]);
|
||||
function moveEventType(index: number, increment: 1 | -1) {
|
||||
const newList = [...sortableTypes];
|
||||
|
||||
const type = sortableTypes[index];
|
||||
const tmp = sortableTypes[index + increment];
|
||||
function moveEventType(index: number, increment: 1 | -1) {
|
||||
const newList = [...types];
|
||||
|
||||
const type = types[index];
|
||||
const tmp = types[index + increment];
|
||||
if (tmp) {
|
||||
newList[index] = tmp;
|
||||
newList[index + increment] = type;
|
||||
}
|
||||
setSortableTypes(newList);
|
||||
|
||||
utils.cancelQuery(["viewer.eventTypes"]);
|
||||
utils.setQueryData(["viewer.eventTypes"], (data) =>
|
||||
Object.assign(data, {
|
||||
eventTypesGroups: [
|
||||
data?.eventTypeGroups.slice(0, groupIndex),
|
||||
Object.assign(group, {
|
||||
eventTypes: newList,
|
||||
}),
|
||||
data?.eventTypeGroups.slice(groupIndex + 1),
|
||||
],
|
||||
})
|
||||
);
|
||||
|
||||
mutation.mutate({
|
||||
ids: newList.map((type) => type.id),
|
||||
});
|
||||
|
@ -155,7 +200,7 @@ export const EventTypeList = ({ group, readOnly, types }: EventTypeListProps): J
|
|||
return (
|
||||
<div className="-mx-4 mb-16 overflow-hidden rounded-sm border border-gray-200 bg-white sm:mx-0">
|
||||
<ul className="divide-y divide-neutral-200" data-testid="event-types">
|
||||
{sortableTypes.map((type, index) => (
|
||||
{types.map((type, index) => (
|
||||
<li
|
||||
key={type.id}
|
||||
className={classNames(
|
||||
|
@ -168,7 +213,7 @@ export const EventTypeList = ({ group, readOnly, types }: EventTypeListProps): J
|
|||
type.$disabled && "pointer-events-none"
|
||||
)}>
|
||||
<div className="group flex w-full items-center justify-between px-4 py-4 hover:bg-neutral-50 sm:px-6">
|
||||
{sortableTypes.length > 1 && (
|
||||
{types.length > 1 && (
|
||||
<>
|
||||
<button
|
||||
className="invisible absolute left-1/2 -mt-4 mb-4 -ml-4 hidden h-7 w-7 scale-0 rounded-full border bg-white p-1 text-gray-400 transition-all hover:border-transparent hover:text-black hover:shadow group-hover:visible group-hover:scale-100 sm:left-[19px] sm:ml-0 sm:block"
|
||||
|
@ -183,36 +228,7 @@ export const EventTypeList = ({ group, readOnly, types }: EventTypeListProps): J
|
|||
</button>
|
||||
</>
|
||||
)}
|
||||
<Link href={"/event-types/" + type.id}>
|
||||
<a
|
||||
className="flex-grow truncate text-sm"
|
||||
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
||||
<div>
|
||||
<span
|
||||
className="truncate font-medium text-neutral-900 ltr:mr-1 rtl:ml-1"
|
||||
data-testid={"event-type-title-" + type.id}>
|
||||
{type.title}
|
||||
</span>
|
||||
<small
|
||||
className="hidden text-neutral-500 sm:inline"
|
||||
data-testid={
|
||||
"event-type-slug-" + type.id
|
||||
}>{`/${group.profile.slug}/${type.slug}`}</small>
|
||||
{type.hidden && (
|
||||
<span className="rtl:mr-2inline items-center rounded-sm bg-yellow-100 px-1.5 py-0.5 text-xs font-medium text-yellow-800 ltr:ml-2">
|
||||
{t("hidden")}
|
||||
</span>
|
||||
)}
|
||||
{readOnly && (
|
||||
<span className="rtl:mr-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2">
|
||||
{t("readonly")}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
<EventTypeDescription eventType={type} />
|
||||
</a>
|
||||
</Link>
|
||||
|
||||
<MemoizedItem type={type} group={group} readOnly={readOnly} />
|
||||
<div className="mt-4 hidden flex-shrink-0 sm:mt-0 sm:ml-5 sm:flex">
|
||||
<div className="flex justify-between rtl:space-x-reverse">
|
||||
{type.users?.length > 1 && (
|
||||
|
@ -487,9 +503,19 @@ const CreateFirstEventTypeView = ({ canAddEvents, profiles }: CreateEventTypePro
|
|||
);
|
||||
};
|
||||
|
||||
const CTA = () => {
|
||||
const query = trpc.useQuery(["viewer.eventTypes"]);
|
||||
|
||||
if (!query.data) return null;
|
||||
|
||||
return (
|
||||
<CreateEventTypeButton canAddEvents={query.data.viewer.canAddEvents} options={query.data.profiles} />
|
||||
);
|
||||
};
|
||||
|
||||
const WithQuery = withQuery(["viewer.eventTypes"]);
|
||||
const EventTypesPage = () => {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.useQuery(["viewer.eventTypes"]);
|
||||
|
||||
return (
|
||||
<div>
|
||||
|
@ -497,19 +523,8 @@ const EventTypesPage = () => {
|
|||
<title>Home | Cal.com</title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</Head>
|
||||
<Shell
|
||||
heading={t("event_types_page_title")}
|
||||
subtitle={t("event_types_page_subtitle")}
|
||||
CTA={
|
||||
query.data && (
|
||||
<CreateEventTypeButton
|
||||
canAddEvents={query.data.viewer.canAddEvents}
|
||||
options={query.data.profiles}
|
||||
/>
|
||||
)
|
||||
}>
|
||||
<QueryCell
|
||||
query={query}
|
||||
<Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
|
||||
<WithQuery
|
||||
success={({ data }) => (
|
||||
<>
|
||||
{data.viewer.plan === "FREE" && !data.viewer.canAddEvents && (
|
||||
|
@ -528,7 +543,7 @@ const EventTypesPage = () => {
|
|||
className="mb-4"
|
||||
/>
|
||||
)}
|
||||
{data.eventTypeGroups.map((group) => (
|
||||
{data.eventTypeGroups.map((group, index) => (
|
||||
<Fragment key={group.profile.slug}>
|
||||
{/* hide list heading when there is only one (current user) */}
|
||||
{(data.eventTypeGroups.length !== 1 || group.teamId) && (
|
||||
|
@ -537,7 +552,12 @@ const EventTypesPage = () => {
|
|||
membershipCount={group.metadata.membershipCount}
|
||||
/>
|
||||
)}
|
||||
<EventTypeList types={group.eventTypes} group={group} readOnly={group.metadata.readOnly} />
|
||||
<EventTypeList
|
||||
types={group.eventTypes}
|
||||
group={group}
|
||||
groupIndex={index}
|
||||
readOnly={group.metadata.readOnly}
|
||||
/>
|
||||
</Fragment>
|
||||
))}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import Button from "@calcom/ui/Button";
|
|||
import { Dialog, DialogTrigger } from "@calcom/ui/Dialog";
|
||||
import { TextField } from "@calcom/ui/form/fields";
|
||||
|
||||
import { QueryCell } from "@lib/QueryCell";
|
||||
import { withQuery } from "@lib/QueryCell";
|
||||
import { asStringOrNull, asStringOrUndefined } from "@lib/asStringOrNull";
|
||||
import { getSession } from "@lib/auth";
|
||||
import { nameOfDay } from "@lib/core/i18n/weekday";
|
||||
|
@ -482,17 +482,15 @@ function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: str
|
|||
);
|
||||
}
|
||||
|
||||
const WithQuery = withQuery(["viewer.i18n"]);
|
||||
|
||||
export default function Settings(props: Props) {
|
||||
const { t } = useLocale();
|
||||
const query = trpc.useQuery(["viewer.i18n"]);
|
||||
|
||||
return (
|
||||
<Shell heading={t("profile")} subtitle={t("edit_profile_info_description")}>
|
||||
<SettingsShell>
|
||||
<QueryCell
|
||||
query={query}
|
||||
success={({ data }) => <SettingsView {...props} localeProp={data.locale} />}
|
||||
/>
|
||||
<WithQuery success={({ data }) => <SettingsView {...props} localeProp={data.locale} />} />
|
||||
</SettingsShell>
|
||||
</Shell>
|
||||
);
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { expect, test } from "@playwright/test";
|
||||
|
||||
import { BASE_URL } from "@lib/config/constants";
|
||||
import { WEBAPP_URL } from "@lib/config/constants";
|
||||
import prisma from "@lib/prisma";
|
||||
|
||||
import { todo } from "../lib/testUtils";
|
||||
|
@ -40,7 +40,7 @@ test.describe("Can signup from a team invite", async () => {
|
|||
select: { token: true },
|
||||
});
|
||||
token = tokenObj?.token;
|
||||
signupFromInviteURL = `/auth/signup?token=${token}&callbackUrl=${BASE_URL}/settings/teams`;
|
||||
signupFromInviteURL = `/auth/signup?token=${token}&callbackUrl=${WEBAPP_URL}/settings/teams`;
|
||||
});
|
||||
|
||||
test.afterAll(async () => {
|
||||
|
|
Loading…
Reference in a new issue