Ends the war between tRPC and next-i18next (#939)

* Ends the war between tRPC and next-i18next

* Locale fixes

* Linting

* Linting

* trpc i18n (not working) (#942)

* simplify i18n handler and remove redundant(?) fn check

* split up viewer to a "logged in only" and "public"

* wip -- skip first render

Co-authored-by: Omar López <zomars@me.com>

* Linting

* I18n fixes

* We don't need serverSideTranslations in every page anymore

Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Alex Johansson <alexander@n1s.se>
This commit is contained in:
Omar López 2021-10-14 04:57:49 -06:00 committed by GitHub
parent 26f20e2397
commit 0861d7cc61
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
28 changed files with 243 additions and 401 deletions

View file

@ -9,8 +9,7 @@ export default function AddToHomescreen() {
return null; return null;
} }
} }
return ( return !closeBanner ? (
!closeBanner && (
<div className="fixed sm:hidden bottom-0 inset-x-0 pb-2 sm:pb-5"> <div className="fixed sm:hidden bottom-0 inset-x-0 pb-2 sm:pb-5">
<div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8"> <div className="max-w-7xl mx-auto px-2 sm:px-6 lg:px-8">
<div className="p-2 rounded-lg shadow-lg sm:p-3" style={{ background: "#2F333D" }}> <div className="p-2 rounded-lg shadow-lg sm:p-3" style={{ background: "#2F333D" }}>
@ -47,6 +46,5 @@ export default function AddToHomescreen() {
</div> </div>
</div> </div>
</div> </div>
) ) : null;
);
} }

View file

@ -1,21 +1,18 @@
import { useTranslation } from "next-i18next"; import { useTranslation } from "next-i18next";
import { useRouter } from "next/router";
interface Props { import { trpc } from "@lib/trpc";
localeProp: string;
}
const I18nLanguageHandler = ({ localeProp }: Props): null => { /**
* Auto-switches locale client-side to the logged in user's preference
*/
const I18nLanguageHandler = (): null => {
const { i18n } = useTranslation("common"); const { i18n } = useTranslation("common");
const router = useRouter(); const locale = trpc.useQuery(["viewer.i18n"]).data?.locale;
const { pathname } = router;
if (!localeProp) if (locale && i18n.language && i18n.language !== locale) {
console.warn( if (typeof i18n.changeLanguage === "function") i18n.changeLanguage(locale);
`You may forgot to return 'localeProp' from 'getServerSideProps' or 'getStaticProps' in ${pathname}`
);
if (i18n.language !== localeProp) {
i18n.changeLanguage(localeProp);
} }
return null; return null;
}; };

View file

@ -11,7 +11,7 @@ import {
import { signOut, useSession } from "next-auth/client"; import { signOut, useSession } from "next-auth/client";
import Link from "next/link"; import Link from "next/link";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { Fragment, ReactNode, useEffect } from "react"; import React, { ReactNode, useEffect } from "react";
import { Toaster } from "react-hot-toast"; import { Toaster } from "react-hot-toast";
import LicenseBanner from "@ee/components/LicenseBanner"; import LicenseBanner from "@ee/components/LicenseBanner";
@ -49,6 +49,7 @@ function useMeQuery() {
function useRedirectToLoginIfUnauthenticated() { function useRedirectToLoginIfUnauthenticated() {
const [session, loading] = useSession(); const [session, loading] = useSession();
const router = useRouter(); const router = useRouter();
const query = useMeQuery();
useEffect(() => { useEffect(() => {
if (!loading && !session) { if (!loading && !session) {
@ -60,6 +61,27 @@ function useRedirectToLoginIfUnauthenticated() {
}); });
} }
}, [loading, session, router]); }, [loading, session, router]);
if (query.status !== "loading" && !query.data) {
router.replace("/auth/login");
}
}
function useRedirectToOnboardingIfNeeded() {
const [session, loading] = useSession();
const router = useRouter();
const query = useMeQuery();
const user = query.data;
useEffect(() => {
if (!loading && user) {
if (shouldShowOnboarding(user)) {
router.replace({
pathname: "/getting-started",
});
}
}
}, [loading, session, router, user]);
} }
export function ShellSubHeading(props: { export function ShellSubHeading(props: {
@ -90,20 +112,10 @@ export default function Shell(props: {
CTA?: ReactNode; CTA?: ReactNode;
}) { }) {
const router = useRouter(); const router = useRouter();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
useRedirectToLoginIfUnauthenticated(); useRedirectToLoginIfUnauthenticated();
useRedirectToOnboardingIfNeeded();
const telemetry = useTelemetry(); const telemetry = useTelemetry();
const query = useMeQuery();
useEffect(
function redirectToOnboardingIfNeeded() {
if (query.data && shouldShowOnboarding(query.data)) {
router.push("/getting-started");
}
},
[query.data, router]
);
const navigation = [ const navigation = [
{ {
@ -142,11 +154,7 @@ export default function Shell(props: {
telemetry.withJitsu((jitsu) => { telemetry.withJitsu((jitsu) => {
return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.asPath)); return jitsu.track(telemetryEventTypes.pageView, collectPageParameters(router.asPath));
}); });
}, [telemetry]); }, [telemetry, router.asPath]);
if (query.status !== "loading" && !query.data) {
router.replace("/auth/login");
}
const pageTitle = typeof props.heading === "string" ? props.heading : props.title; const pageTitle = typeof props.heading === "string" ? props.heading : props.title;

View file

@ -1,5 +1,4 @@
import { ArrowLeftIcon } from "@heroicons/react/solid"; import { ArrowLeftIcon } from "@heroicons/react/solid";
import { EventType } from "@prisma/client";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import showToast from "@lib/notification"; import showToast from "@lib/notification";
@ -8,11 +7,7 @@ import { Webhook } from "@lib/webhook";
import Button from "@components/ui/Button"; import Button from "@components/ui/Button";
import Switch from "@components/ui/Switch"; import Switch from "@components/ui/Switch";
export default function EditTeam(props: { export default function EditTeam(props: { webhook: Webhook; onCloseEdit: () => void }) {
webhook: Webhook;
eventTypes: EventType[];
onCloseEdit: () => void;
}) {
const [bookingCreated, setBookingCreated] = useState( const [bookingCreated, setBookingCreated] = useState(
props.webhook.eventTriggers.includes("booking_created") props.webhook.eventTriggers.includes("booking_created")
); );

View file

@ -1,18 +1,14 @@
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { PaymentData } from "@ee/lib/stripe/server"; import { PaymentData } from "@ee/lib/stripe/server";
import { asStringOrThrow } from "@lib/asStringOrNull"; import { asStringOrThrow } from "@lib/asStringOrNull";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
export type PaymentPageProps = inferSSRProps<typeof getServerSideProps>; export type PaymentPageProps = inferSSRProps<typeof getServerSideProps>;
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const rawPayment = await prisma.payment.findFirst({ const rawPayment = await prisma.payment.findFirst({
where: { where: {
uid: asStringOrThrow(context.query.uid), uid: asStringOrThrow(context.query.uid),
@ -103,8 +99,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
booking, booking,
payment, payment,
profile, profile,
localeProp: locale,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -66,7 +66,7 @@ export function QueryCell<TData, TError extends ErrorLike>(
return opts.loading?.(query) ?? <Loader />; return opts.loading?.(query) ?? <Loader />;
} }
if (query.status === "idle") { if (query.status === "idle") {
return null; return opts.idle?.(query) ?? <Loader />;
} }
// impossible state // impossible state
return null; return null;

View file

@ -1,18 +1,36 @@
import { IdProvider } from "@radix-ui/react-id"; import { IdProvider } from "@radix-ui/react-id";
import { Provider } from "next-auth/client"; import { Provider } from "next-auth/client";
import { appWithTranslation } from "next-i18next";
import { AppProps } from "next/dist/shared/lib/router/router"; import { AppProps } from "next/dist/shared/lib/router/router";
import React from "react"; import React, { ComponentProps, ReactNode } from "react";
import DynamicIntercomProvider from "@ee/lib/intercom/providerDynamic"; import DynamicIntercomProvider from "@ee/lib/intercom/providerDynamic";
import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry"; import { createTelemetryClient, TelemetryProvider } from "@lib/telemetry";
import { trpc } from "./trpc";
const I18nextAdapter = appWithTranslation(({ children }: { children?: ReactNode }) => <>{children}</>);
const CustomI18nextProvider = (props: { children: ReactNode }) => {
const { i18n, locale } = trpc.useQuery(["viewer.i18n"]).data ?? {};
const passedProps = {
...props,
pageProps: { ...i18n },
router: { locale },
} as unknown as ComponentProps<typeof I18nextAdapter>;
return <I18nextAdapter {...passedProps} />;
};
const AppProviders = (props: AppProps) => { const AppProviders = (props: AppProps) => {
const session = trpc.useQuery(["viewer.session"]).data;
return ( return (
<TelemetryProvider value={createTelemetryClient()}> <TelemetryProvider value={createTelemetryClient()}>
<IdProvider> <IdProvider>
<DynamicIntercomProvider> <DynamicIntercomProvider>
<Provider session={props.pageProps.session}>{props.children}</Provider> <Provider session={session || undefined}>
<CustomI18nextProvider>{props.children}</CustomI18nextProvider>
</Provider>
</DynamicIntercomProvider> </DynamicIntercomProvider>
</IdProvider> </IdProvider>
</TelemetryProvider> </TelemetryProvider>

View file

@ -1,10 +1,8 @@
import { ArrowRightIcon } from "@heroicons/react/outline"; import { ArrowRightIcon } from "@heroicons/react/outline";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import useTheme from "@lib/hooks/useTheme"; import useTheme from "@lib/hooks/useTheme";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
@ -75,7 +73,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps>) {
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const username = (context.query.user as string).toLowerCase(); const username = (context.query.user as string).toLowerCase();
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
@ -139,10 +136,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return { return {
props: { props: {
localeProp: locale,
user, user,
eventTypes, eventTypes,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -1,9 +1,7 @@
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { asStringOrNull } from "@lib/asStringOrNull"; import { asStringOrNull } from "@lib/asStringOrNull";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -16,7 +14,6 @@ export default function Type(props: AvailabilityPageProps) {
} }
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const locale = await getOrSetUserLocaleFromHeaders(context.req);
// get query params and typecast them to string // get query params and typecast them to string
// (would be even better to assert them instead of typecasting) // (would be even better to assert them instead of typecasting)
const userParam = asStringOrNull(context.query.user); const userParam = asStringOrNull(context.query.user);
@ -178,7 +175,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return { return {
props: { props: {
localeProp: locale,
profile: { profile: {
name: user.name, name: user.name,
image: user.avatar, image: user.avatar,
@ -189,7 +185,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
date: dateParam, date: dateParam,
eventType: eventTypeObject, eventType: eventTypeObject,
workingHours, workingHours,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -2,10 +2,8 @@ import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { asStringOrThrow } from "@lib/asStringOrNull"; import { asStringOrThrow } from "@lib/asStringOrNull";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -21,8 +19,6 @@ export default function Book(props: BookPageProps) {
} }
export async function getServerSideProps(context: GetServerSidePropsContext) { export async function getServerSideProps(context: GetServerSidePropsContext) {
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const user = await prisma.user.findUnique({ const user = await prisma.user.findUnique({
where: { where: {
username: asStringOrThrow(context.query.user), username: asStringOrThrow(context.query.user),
@ -103,7 +99,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
return { return {
props: { props: {
localeProp: locale,
profile: { profile: {
slug: user.username, slug: user.username,
name: user.name, name: user.name,
@ -112,7 +107,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
}, },
eventType: eventTypeObject, eventType: eventTypeObject,
booking, booking,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
} }

View file

@ -3,7 +3,6 @@ import { loggerLink } from "@trpc/client/links/loggerLink";
import { withTRPC } from "@trpc/next"; import { withTRPC } from "@trpc/next";
import type { TRPCClientErrorLike } from "@trpc/react"; import type { TRPCClientErrorLike } from "@trpc/react";
import { Maybe } from "@trpc/server"; import { Maybe } from "@trpc/server";
import { appWithTranslation } from "next-i18next";
import { DefaultSeo } from "next-seo"; import { DefaultSeo } from "next-seo";
import type { AppProps as NextAppProps } from "next/app"; import type { AppProps as NextAppProps } from "next/app";
import superjson from "superjson"; import superjson from "superjson";
@ -28,7 +27,7 @@ function MyApp(props: AppProps) {
return ( return (
<AppProviders {...props}> <AppProviders {...props}>
<DefaultSeo {...seoConfig.defaultNextSeo} /> <DefaultSeo {...seoConfig.defaultNextSeo} />
<I18nLanguageHandler localeProp={pageProps.localeProp} /> <I18nLanguageHandler />
<Component {...pageProps} err={err} /> <Component {...pageProps} err={err} />
</AppProviders> </AppProviders>
); );
@ -92,4 +91,4 @@ export default withTRPC<AppRouter>({
* @link https://trpc.io/docs/ssr * @link https://trpc.io/docs/ssr
*/ */
ssr: false, ssr: false,
})(appWithTranslation(MyApp)); })(MyApp);

View file

@ -1,21 +1,19 @@
import dayjs from "dayjs"; import dayjs, { Dayjs } from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useEffect, useState } from "react"; import { useEffect, useState } from "react";
import { getSession } from "@lib/auth"; import { QueryCell } from "@lib/QueryCell";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma"; import { inferQueryOutput, trpc } from "@lib/trpc";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import Loader from "@components/Loader"; import Loader from "@components/Loader";
import Shell from "@components/Shell"; import Shell from "@components/Shell";
dayjs.extend(utc); dayjs.extend(utc);
export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSideProps>) { type User = inferQueryOutput<"viewer.me">;
const AvailabilityView = ({ user }: { user: User }) => {
const { t } = useLocale(); const { t } = useLocale();
const [loading, setLoading] = useState(true); const [loading, setLoading] = useState(true);
const [availability, setAvailability] = useState([]); const [availability, setAvailability] = useState([]);
@ -29,9 +27,11 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
return `${h}:${m}`; return `${h}:${m}`;
} }
const fetchAvailability = (date) => { useEffect(() => {
const fetchAvailability = (date: Dayjs) => {
const dateFrom = date.startOf("day").utc().format(); const dateFrom = date.startOf("day").utc().format();
const dateTo = date.endOf("day").utc().format(); const dateTo = date.endOf("day").utc().format();
setLoading(true);
fetch(`/api/availability/${user.username}?dateFrom=${dateFrom}&dateTo=${dateTo}`) fetch(`/api/availability/${user.username}?dateFrom=${dateFrom}&dateTo=${dateTo}`)
.then((res) => { .then((res) => {
@ -39,24 +39,18 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
}) })
.then((availableIntervals) => { .then((availableIntervals) => {
setAvailability(availableIntervals.busy); setAvailability(availableIntervals.busy);
setLoading(false);
}) })
.catch((e) => { .catch((e) => {
console.error(e); console.error(e);
})
.finally(() => {
setLoading(false);
}); });
}; };
useEffect(() => {
fetchAvailability(selectedDate); fetchAvailability(selectedDate);
}, [selectedDate]); }, [selectedDate]);
if (loading) {
return <Loader />;
}
return ( return (
<div>
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
<div className="bg-white max-w-xl overflow-hidden shadow rounded-sm"> <div className="bg-white max-w-xl overflow-hidden shadow rounded-sm">
<div className="px-4 py-5 sm:p-6"> <div className="px-4 py-5 sm:p-6">
{t("overview_of_day")}{" "} {t("overview_of_day")}{" "}
@ -64,7 +58,7 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
type="date" type="date"
className="inline border-none h-8 p-0" className="inline border-none h-8 p-0"
defaultValue={selectedDate.format("YYYY-MM-DD")} defaultValue={selectedDate.format("YYYY-MM-DD")}
onBlur={(e) => { onChange={(e) => {
setSelectedDate(dayjs(e.target.value)); setSelectedDate(dayjs(e.target.value));
}} }}
/> />
@ -75,7 +69,10 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
{t("your_day_starts_at")} {convertMinsToHrsMins(user.startTime)} {t("your_day_starts_at")} {convertMinsToHrsMins(user.startTime)}
</div> </div>
</div> </div>
{availability.map((slot) => ( {loading ? (
<Loader />
) : availability.length > 0 ? (
availability.map((slot) => (
<div key={slot.start} className="bg-neutral-100 overflow-hidden rounded-sm"> <div key={slot.start} className="bg-neutral-100 overflow-hidden rounded-sm">
<div className="px-4 py-5 sm:p-6 text-black"> <div className="px-4 py-5 sm:p-6 text-black">
{t("calendar_shows_busy_between")}{" "} {t("calendar_shows_busy_between")}{" "}
@ -90,8 +87,13 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
{t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")} {t(dayjs(slot.start).format("MMMM").toLowerCase())} {dayjs(slot.start).format("YYYY")}
</div> </div>
</div> </div>
))} ))
{availability.length === 0 && <Loader />} ) : (
<div className="bg-neutral-100 overflow-hidden rounded-sm">
<div className="px-4 py-5 sm:p-6 text-black">{t("calendar_no_busy_slots")}</div>
</div>
)}
<div className="bg-black overflow-hidden rounded-sm"> <div className="bg-black overflow-hidden rounded-sm">
<div className="px-4 sm:px-6 py-2 text-white"> <div className="px-4 sm:px-6 py-2 text-white">
{t("your_day_ends_at")} {convertMinsToHrsMins(user.endTime)} {t("your_day_ends_at")} {convertMinsToHrsMins(user.endTime)}
@ -100,37 +102,17 @@ export default function Troubleshoot({ user }: inferSSRProps<typeof getServerSid
</div> </div>
</div> </div>
</div> </div>
);
};
export default function Troubleshoot() {
const query = trpc.useQuery(["viewer.me"]);
const { t } = useLocale();
return (
<div>
<Shell heading={t("troubleshoot")} subtitle={t("troubleshoot_description")}>
<QueryCell query={query} success={({ data }) => <AvailabilityView user={data} />} />
</Shell> </Shell>
</div> </div>
); );
} }
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session?.user?.id) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const user = await prisma.user.findFirst({
where: {
id: session.user.id,
},
select: {
startTime: true,
endTime: true,
username: true,
},
});
if (!user) return { redirect: { permanent: false, destination: "/auth/login" } };
return {
props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
};
};

View file

@ -23,7 +23,11 @@ export default function Bookings() {
const router = useRouter(); const router = useRouter();
const status = router.query?.status as BookingListingStatus; const status = router.query?.status as BookingListingStatus;
const query = trpc.useQuery(["viewer.bookings", { status }]);
const query = trpc.useQuery(["viewer.bookings", { status }], {
// first render has status `undefined`
enabled: !!status,
});
return ( return (
<Shell heading={t("bookings")} subtitle={t("bookings_description")}> <Shell heading={t("bookings")} subtitle={t("bookings_description")}>

View file

@ -2,11 +2,9 @@ import { CalendarIcon, XIcon } from "@heroicons/react/solid";
import dayjs from "dayjs"; import dayjs from "dayjs";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { getSession } from "next-auth/client"; import { getSession } from "next-auth/client";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import { useState } from "react"; import { useState } from "react";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry"; import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@lib/telemetry";
@ -145,7 +143,6 @@ export default function Type(props) {
export async function getServerSideProps(context) { export async function getServerSideProps(context) {
const session = await getSession(context); const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const booking = await prisma.booking.findUnique({ const booking = await prisma.booking.findUnique({
where: { where: {
uid: context.query.uid, uid: context.query.uid,
@ -202,7 +199,6 @@ export async function getServerSideProps(context) {
booking: bookingObj, booking: bookingObj,
cancellationAllowed: cancellationAllowed:
(!!session?.user && session.user.id == booking.user?.id) || booking.startTime >= new Date(), (!!session?.user && session.user.id == booking.user?.id) || booking.startTime >= new Date(),
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
} }

View file

@ -19,7 +19,6 @@ import dayjs from "dayjs";
import timezone from "dayjs/plugin/timezone"; import timezone from "dayjs/plugin/timezone";
import utc from "dayjs/plugin/utc"; import utc from "dayjs/plugin/utc";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useRouter } from "next/router"; import { useRouter } from "next/router";
import React, { useEffect, useRef, useState } from "react"; import React, { useEffect, useRef, useState } from "react";
import { FormattedNumber, IntlProvider } from "react-intl"; import { FormattedNumber, IntlProvider } from "react-intl";
@ -37,7 +36,6 @@ import {
import { getSession } from "@lib/auth"; import { getSession } from "@lib/auth";
import classNames from "@lib/classNames"; import classNames from "@lib/classNames";
import { HttpError } from "@lib/core/http/error"; import { HttpError } from "@lib/core/http/error";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import getIntegrations, { hasIntegration } from "@lib/integrations/getIntegrations"; import getIntegrations, { hasIntegration } from "@lib/integrations/getIntegrations";
import { LocationType } from "@lib/location"; import { LocationType } from "@lib/location";
@ -1097,8 +1095,6 @@ const EventTypePage = (props: inferSSRProps<typeof getServerSideProps>) => {
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, query } = context; const { req, query } = context;
const session = await getSession({ req }); const session = await getSession({ req });
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const typeParam = parseInt(asStringOrThrow(query.type)); const typeParam = parseInt(asStringOrThrow(query.type));
if (!session?.user?.id) { if (!session?.user?.id) {
@ -1278,7 +1274,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return { return {
props: { props: {
session, session,
localeProp: locale,
eventType: eventTypeObject, eventType: eventTypeObject,
locationOptions, locationOptions,
availability, availability,
@ -1286,7 +1281,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
teamMembers, teamMembers,
hasPaymentIntegration, hasPaymentIntegration,
currency, currency,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -1,11 +1,6 @@
import { ExternalLinkIcon } from "@heroicons/react/solid"; import { ExternalLinkIcon } from "@heroicons/react/solid";
import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getSession } from "@lib/auth";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma";
import SettingsShell from "@components/SettingsShell"; import SettingsShell from "@components/SettingsShell";
import Shell from "@components/Shell"; import Shell from "@components/Shell";
@ -55,36 +50,3 @@ export default function Billing() {
</Shell> </Shell>
); );
} }
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const user = await prisma.user.findFirst({
where: {
email: session.user.email,
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true,
timeZone: true,
weekStart: true,
},
});
return {
props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
};
}

View file

@ -1,14 +1,9 @@
import { PlusIcon } from "@heroicons/react/outline"; import { PlusIcon } from "@heroicons/react/outline";
import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/client"; import { useSession } from "next-auth/client";
import { serverSideTranslations } from "next-i18next/serverSideTranslations"; import { useEffect, useRef, useState } from "react";
import { useEffect, useState, useRef } from "react";
import { getSession } from "@lib/auth";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma"; import { trpc } from "@lib/trpc";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { Webhook } from "@lib/webhook"; import { Webhook } from "@lib/webhook";
import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTrigger } from "@components/Dialog"; import { Dialog, DialogClose, DialogContent, DialogHeader, DialogTrigger } from "@components/Dialog";
@ -20,7 +15,8 @@ import Switch from "@components/ui/Switch";
import EditWebhook from "@components/webhook/EditWebhook"; import EditWebhook from "@components/webhook/EditWebhook";
import WebhookList from "@components/webhook/WebhookList"; import WebhookList from "@components/webhook/WebhookList";
export default function Embed(props: inferSSRProps<typeof getServerSideProps>) { export default function Embed() {
const user = trpc.useQuery(["viewer.me"]).data;
const [, loading] = useSession(); const [, loading] = useSession();
const { t } = useLocale(); const { t } = useLocale();
@ -51,11 +47,7 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
getWebhooks(); getWebhooks();
}, []); }, []);
if (loading) { const iframeTemplate = `<iframe src="${process.env.NEXT_PUBLIC_BASE_URL}/${user?.username}" frameborder="0" allowfullscreen></iframe>`;
return <Loader />;
}
const iframeTemplate = `<iframe src="${process.env.NEXT_PUBLIC_APP_URL}/${props.user?.username}" frameborder="0" allowfullscreen></iframe>`;
const htmlTemplate = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${t( const htmlTemplate = `<!DOCTYPE html><html lang="en"><head><meta charset="UTF-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>${t(
"schedule_a_meeting" "schedule_a_meeting"
)}</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body>${iframeTemplate}</body></html>`; )}</title><style>body {margin: 0;}iframe {height: calc(100vh - 4px);width: calc(100vw - 4px);box-sizing: border-box;}</style></head><body>${iframeTemplate}</body></html>`;
@ -111,6 +103,10 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
setEditWebhookEnabled(false); setEditWebhookEnabled(false);
}; };
if (loading) {
return <Loader />;
}
return ( return (
<Shell heading={t("embed_and_webhooks")} subtitle={t("integrate_using_embed_or_webhooks")}> <Shell heading={t("embed_and_webhooks")} subtitle={t("integrate_using_embed_or_webhooks")}>
<SettingsShell> <SettingsShell>
@ -280,41 +276,10 @@ export default function Embed(props: inferSSRProps<typeof getServerSideProps>) {
</a> </a>
</div> </div>
)} )}
{!!editWebhookEnabled && <EditWebhook webhook={webhookToEdit} onCloseEdit={onCloseEdit} />} {!!editWebhookEnabled && webhookToEdit && (
<EditWebhook webhook={webhookToEdit} onCloseEdit={onCloseEdit} />
)}
</SettingsShell> </SettingsShell>
</Shell> </Shell>
); );
} }
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session?.user?.email) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const user = await prisma.user.findFirst({
where: {
email: session?.user?.email,
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true,
timeZone: true,
weekStart: true,
},
});
return {
props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
};
}

View file

@ -1,7 +1,6 @@
import { InformationCircleIcon } from "@heroicons/react/outline"; import { InformationCircleIcon } from "@heroicons/react/outline";
import crypto from "crypto"; import crypto from "crypto";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { RefObject, useEffect, useRef, useState } from "react"; import { RefObject, useEffect, useRef, useState } from "react";
import Select from "react-select"; import Select from "react-select";
import TimezoneSelect from "react-timezone-select"; import TimezoneSelect from "react-timezone-select";
@ -105,14 +104,12 @@ export default function Settings(props: Props) {
{ value: "dark", label: t("dark") }, { value: "dark", label: t("dark") },
]; ];
const usernameRef = useRef<HTMLInputElement>(null); const usernameRef = useRef<HTMLInputElement>(null!);
const nameRef = useRef<HTMLInputElement>(null); const nameRef = useRef<HTMLInputElement>(null!);
const descriptionRef = useRef<HTMLTextAreaElement>(); const descriptionRef = useRef<HTMLTextAreaElement>(null!);
const avatarRef = useRef<HTMLInputElement>(null); const avatarRef = useRef<HTMLInputElement>(null!);
const hideBrandingRef = useRef<HTMLInputElement>(null); const hideBrandingRef = useRef<HTMLInputElement>(null!);
const [selectedTheme, setSelectedTheme] = useState<null | { value: string | null }>({ const [selectedTheme, setSelectedTheme] = useState<undefined | { value: string; label: string }>(undefined);
value: props.user.theme,
});
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone }); const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({ const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({
value: props.user.weekStart, value: props.user.weekStart,
@ -128,25 +125,12 @@ export default function Settings(props: Props) {
useEffect(() => { useEffect(() => {
setSelectedTheme( setSelectedTheme(
props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : null props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : undefined
); );
setSelectedWeekStartDay({ value: props.user.weekStart, label: props.user.weekStart }); setSelectedWeekStartDay({ value: props.user.weekStart, label: props.user.weekStart });
setSelectedLanguage({ value: props.localeProp, label: props.localeLabels[props.localeProp] }); setSelectedLanguage({ value: props.localeProp, label: props.localeLabels[props.localeProp] });
}, []); }, []);
const handleAvatarChange = (newAvatar) => {
avatarRef.current.value = newAvatar;
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
).set;
nativeInputValueSetter.call(avatarRef.current, newAvatar);
const ev2 = new Event("input", { bubbles: true });
avatarRef.current.dispatchEvent(ev2);
updateProfileHandler(ev2);
setImageSrc(newAvatar);
};
async function updateProfileHandler(event) { async function updateProfileHandler(event) {
event.preventDefault(); event.preventDefault();
@ -273,7 +257,18 @@ export default function Settings(props: Props) {
target="avatar" target="avatar"
id="avatar-upload" id="avatar-upload"
buttonMsg={t("change_avatar")} buttonMsg={t("change_avatar")}
handleAvatarChange={handleAvatarChange} handleAvatarChange={(newAvatar) => {
avatarRef.current.value = newAvatar;
const nativeInputValueSetter = Object.getOwnPropertyDescriptor(
window.HTMLInputElement.prototype,
"value"
).set;
nativeInputValueSetter.call(avatarRef.current, newAvatar);
const ev2 = new Event("input", { bubbles: true });
avatarRef.current.dispatchEvent(ev2);
updateProfileHandler(ev2);
setImageSrc(newAvatar);
}}
imageSrc={imageSrc} imageSrc={imageSrc}
/> />
</div> </div>
@ -464,7 +459,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
...user, ...user,
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"), emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),
}, },
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -1,59 +1,22 @@
import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import React from "react"; import React from "react";
import { getSession } from "@lib/auth";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import prisma from "@lib/prisma"; import { trpc } from "@lib/trpc";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import SettingsShell from "@components/SettingsShell"; import SettingsShell from "@components/SettingsShell";
import Shell from "@components/Shell"; import Shell from "@components/Shell";
import ChangePasswordSection from "@components/security/ChangePasswordSection"; import ChangePasswordSection from "@components/security/ChangePasswordSection";
import TwoFactorAuthSection from "@components/security/TwoFactorAuthSection"; import TwoFactorAuthSection from "@components/security/TwoFactorAuthSection";
export default function Security({ user }: inferSSRProps<typeof getServerSideProps>) { export default function Security() {
const user = trpc.useQuery(["viewer.me"]).data;
const { t } = useLocale(); const { t } = useLocale();
return ( return (
<Shell heading={t("security")} subtitle={t("manage_account_security")}> <Shell heading={t("security")} subtitle={t("manage_account_security")}>
<SettingsShell> <SettingsShell>
<ChangePasswordSection /> <ChangePasswordSection />
<TwoFactorAuthSection twoFactorEnabled={user.twoFactorEnabled} /> <TwoFactorAuthSection twoFactorEnabled={user?.twoFactorEnabled} />
</SettingsShell> </SettingsShell>
</Shell> </Shell>
); );
} }
export async function getServerSideProps(context: GetServerSidePropsContext) {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session?.user?.id) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const user = await prisma.user.findFirst({
where: {
id: session.user.id,
},
select: {
id: true,
username: true,
name: true,
twoFactorEnabled: true,
},
});
if (!user) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
return {
props: {
session,
user,
...(await serverSideTranslations(locale, ["common"])),
},
};
}

View file

@ -1,13 +1,8 @@
import { UsersIcon } from "@heroicons/react/outline"; import { UsersIcon } from "@heroicons/react/outline";
import { PlusIcon } from "@heroicons/react/solid"; import { PlusIcon } from "@heroicons/react/solid";
import { GetServerSideProps } from "next";
import type { Session } from "next-auth";
import { useSession } from "next-auth/client"; import { useSession } from "next-auth/client";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { useEffect, useRef, useState } from "react"; import { useEffect, useRef, useState } from "react";
import { getSession } from "@lib/auth";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import { Member } from "@lib/member"; import { Member } from "@lib/member";
import { Team } from "@lib/team"; import { Team } from "@lib/team";
@ -200,20 +195,3 @@ export default function Teams() {
</Shell> </Shell>
); );
} }
// Export the `session` prop to use sessions with Server Side Rendering
export const getServerSideProps: GetServerSideProps<{ session: Session | null }> = async (context) => {
const session = await getSession(context);
const locale = await getOrSetUserLocaleFromHeaders(context.req);
if (!session) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
return {
props: {
session,
localeProp: locale,
...(await serverSideTranslations(locale, ["common"])),
},
};
};

View file

@ -1,11 +1,9 @@
import { ArrowRightIcon } from "@heroicons/react/solid"; import { ArrowRightIcon } from "@heroicons/react/solid";
import { Prisma } from "@prisma/client"; import { Prisma } from "@prisma/client";
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import Link from "next/link"; import Link from "next/link";
import React from "react"; import React from "react";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import { useLocale } from "@lib/hooks/useLocale"; import { useLocale } from "@lib/hooks/useLocale";
import useTheme from "@lib/hooks/useTheme"; import useTheme from "@lib/hooks/useTheme";
import { useToggleQuery } from "@lib/hooks/useToggleQuery"; import { useToggleQuery } from "@lib/hooks/useToggleQuery";
@ -102,7 +100,6 @@ function TeamPage({ team }: inferSSRProps<typeof getServerSideProps>) {
} }
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug; const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug;
const userSelect = Prisma.validator<Prisma.UserSelect>()({ const userSelect = Prisma.validator<Prisma.UserSelect>()({
@ -165,9 +162,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return { return {
props: { props: {
localeProp: locale,
team, team,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -1,8 +1,6 @@
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { asStringOrNull } from "@lib/asStringOrNull"; import { asStringOrNull } from "@lib/asStringOrNull";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -15,7 +13,6 @@ export default function TeamType(props: AvailabilityTeamPageProps) {
} }
export const getServerSideProps = async (context: GetServerSidePropsContext) => { export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const slugParam = asStringOrNull(context.query.slug); const slugParam = asStringOrNull(context.query.slug);
const typeParam = asStringOrNull(context.query.type); const typeParam = asStringOrNull(context.query.type);
const dateParam = asStringOrNull(context.query.date); const dateParam = asStringOrNull(context.query.date);
@ -81,7 +78,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return { return {
props: { props: {
localeProp: locale,
profile: { profile: {
name: team.name, name: team.name,
slug: team.slug, slug: team.slug,
@ -91,7 +87,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
date: dateParam, date: dateParam,
eventType: eventTypeObject, eventType: eventTypeObject,
workingHours, workingHours,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
}; };

View file

@ -1,9 +1,7 @@
import { GetServerSidePropsContext } from "next"; import { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import "react-phone-number-input/style.css"; import "react-phone-number-input/style.css";
import { asStringOrThrow } from "@lib/asStringOrNull"; import { asStringOrThrow } from "@lib/asStringOrNull";
import { getOrSetUserLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
import prisma from "@lib/prisma"; import prisma from "@lib/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps"; import { inferSSRProps } from "@lib/types/inferSSRProps";
@ -16,7 +14,6 @@ export default function TeamBookingPage(props: TeamBookingPageProps) {
} }
export async function getServerSideProps(context: GetServerSidePropsContext) { export async function getServerSideProps(context: GetServerSidePropsContext) {
const locale = await getOrSetUserLocaleFromHeaders(context.req);
const eventTypeId = parseInt(asStringOrThrow(context.query.type)); const eventTypeId = parseInt(asStringOrThrow(context.query.type));
if (typeof eventTypeId !== "number" || eventTypeId % 1 !== 0) { if (typeof eventTypeId !== "number" || eventTypeId % 1 !== 0) {
return { return {
@ -89,7 +86,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
return { return {
props: { props: {
localeProp: locale,
profile: { profile: {
...eventTypeObject.team, ...eventTypeObject.team,
slug: "team/" + eventTypeObject.slug, slug: "team/" + eventTypeObject.slug,
@ -98,7 +94,6 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
}, },
eventType: eventTypeObject, eventType: eventTypeObject,
booking, booking,
...(await serverSideTranslations(locale, ["common"])),
}, },
}; };
} }

View file

@ -22,6 +22,7 @@
"on": "on", "on": "on",
"and": "and", "and": "and",
"calendar_shows_busy_between": "Your calendar shows you as busy between", "calendar_shows_busy_between": "Your calendar shows you as busy between",
"calendar_no_busy_slots": "Your don't have busy slots in this date.",
"troubleshoot": "Troubleshoot", "troubleshoot": "Troubleshoot",
"troubleshoot_description": "Understand why certain times are available and others are blocked.", "troubleshoot_description": "Understand why certain times are available and others are blocked.",
"overview_of_day": "Here is an overview of your day on", "overview_of_day": "Here is an overview of your day on",

View file

@ -174,7 +174,7 @@
"profile_updated_successfully": "Perfil Actualizado con Éxito", "profile_updated_successfully": "Perfil Actualizado con Éxito",
"your_user_profile_updated_successfully": "Su perfil de usuario se ha actualizado correctamente.", "your_user_profile_updated_successfully": "Su perfil de usuario se ha actualizado correctamente.",
"user_cannot_found_db": "El usuario parece haber iniciado sesión pero no se puede encontrar en la base de datos", "user_cannot_found_db": "El usuario parece haber iniciado sesión pero no se puede encontrar en la base de datos",
"embed_and_webhooks": "Insertar &amp; Webhooks", "embed_and_webhooks": "Incrustrar y Webhooks",
"enabled": "Activado", "enabled": "Activado",
"disabled": "Desactivado", "disabled": "Desactivado",
"billing": "Facturación", "billing": "Facturación",

View file

@ -3,6 +3,7 @@ import * as trpc from "@trpc/server";
import { Maybe } from "@trpc/server"; import { Maybe } from "@trpc/server";
import * as trpcNext from "@trpc/server/adapters/next"; import * as trpcNext from "@trpc/server/adapters/next";
import { NextApiRequest } from "next"; import { NextApiRequest } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getSession, Session } from "@lib/auth"; import { getSession, Session } from "@lib/auth";
import { getLocaleFromHeaders } from "@lib/core/i18n/i18n.utils"; import { getLocaleFromHeaders } from "@lib/core/i18n/i18n.utils";
@ -83,7 +84,9 @@ export const createContext = async ({ req, res }: trpcNext.CreateNextContextOpti
const user = await getUserFromSession({ session, req }); const user = await getUserFromSession({ session, req });
const locale = user?.locale ?? getLocaleFromHeaders(req); const locale = user?.locale ?? getLocaleFromHeaders(req);
const i18n = await serverSideTranslations(locale, ["common"]);
return { return {
i18n,
prisma, prisma,
session, session,
user, user,

View file

@ -11,13 +11,14 @@ export function createRouter() {
export function createProtectedRouter() { export function createProtectedRouter() {
return createRouter().middleware(({ ctx, next }) => { return createRouter().middleware(({ ctx, next }) => {
if (!ctx.user) { if (!ctx.user || !ctx.session) {
throw new trpc.TRPCError({ code: "UNAUTHORIZED" }); throw new trpc.TRPCError({ code: "UNAUTHORIZED" });
} }
return next({ return next({
ctx: { ctx: {
...ctx, ...ctx,
// infers that `user` is non-nullable to downstream procedures // infers that `user` and `session` are non-nullable to downstream procedures
session: ctx.session,
user: ctx.user, user: ctx.user,
}, },
}); });

View file

@ -11,14 +11,31 @@ import { ALL_INTEGRATIONS } from "@lib/integrations/getIntegrations";
import slugify from "@lib/slugify"; import slugify from "@lib/slugify";
import { getCalendarAdapterOrNull } from "../../lib/calendarClient"; import { getCalendarAdapterOrNull } from "../../lib/calendarClient";
import { createProtectedRouter } from "../createRouter"; import { createProtectedRouter, createRouter } from "../createRouter";
import { resizeBase64Image } from "../lib/resizeBase64Image"; import { resizeBase64Image } from "../lib/resizeBase64Image";
const checkUsername = const checkUsername =
process.env.NEXT_PUBLIC_APP_URL === "https://cal.com" ? checkPremiumUsername : checkRegularUsername; process.env.NEXT_PUBLIC_APP_URL === "https://cal.com" ? checkPremiumUsername : checkRegularUsername;
// things that unauthenticated users can query about themselves
const publicViewerRouter = createRouter()
.query("session", {
resolve({ ctx }) {
return ctx.session;
},
})
.query("i18n", {
async resolve({ ctx }) {
const { locale, i18n } = ctx;
return {
i18n,
locale,
};
},
});
// routes only available to authenticated users // routes only available to authenticated users
export const viewerRouter = createProtectedRouter() const loggedInViewerRouter = createProtectedRouter()
.query("me", { .query("me", {
resolve({ ctx }) { resolve({ ctx }) {
const { const {
@ -34,6 +51,7 @@ export const viewerRouter = createProtectedRouter()
avatar, avatar,
createdDate, createdDate,
completedOnboarding, completedOnboarding,
twoFactorEnabled,
} = ctx.user; } = ctx.user;
const me = { const me = {
id, id,
@ -47,6 +65,7 @@ export const viewerRouter = createProtectedRouter()
avatar, avatar,
createdDate, createdDate,
completedOnboarding, completedOnboarding,
twoFactorEnabled,
}; };
return me; return me;
}, },
@ -251,3 +270,5 @@ export const viewerRouter = createProtectedRouter()
}); });
}, },
}); });
export const viewerRouter = createRouter().merge(publicViewerRouter).merge(loggedInViewerRouter);