I18n's the i18n language dropdown & weekday using Intl (#955)

* I18n's the i18n language dropdown & weekday using Intl

* Some type fixes

* Trigger locale changes instantly (#958)

* Trigger locale changes instantly

* Restored types

* Capitalize languages across the board

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
pull/938/head^2
Alex van Andel 2021-10-15 12:32:09 +01:00 committed by GitHub
parent b5e176a87e
commit ce8e9c126b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 279 additions and 319 deletions

View File

@ -44,31 +44,3 @@ export const getOrSetUserLocaleFromHeaders = async (req: IncomingMessage): Promi
return preferredLocale;
};
interface localeType {
[locale: string]: string;
}
export const localeLabels: localeType = {
en: "English",
fr: "French",
it: "Italian",
ru: "Russian",
es: "Spanish",
de: "German",
pt: "Portuguese",
ro: "Romanian",
nl: "Dutch",
"pt-BR": "Portuguese (Brazilian)",
"es-419": "Spanish, Latin America",
ko: "Korean",
};
export type OptionType = {
value: string;
label: string;
};
export const localeOptions: OptionType[] = i18n.locales.map((locale) => {
return { value: locale, label: localeLabels[locale] };
});

8
lib/core/i18n/weekday.ts Normal file
View File

@ -0,0 +1,8 @@
// By default starts on Sunday (Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday)
export function weekdayNames(locale: string | string[], weekStart = 0, type: "short" | "long" = "long") {
return Array.from(Array(7).keys()).map((d) => nameOfDay(locale, d + weekStart, type));
}
export function nameOfDay(locale: string | string[], day: number, type: "short" | "long" = "long") {
return new Intl.DateTimeFormat(locale, { weekday: type }).format(new Date(1970, 0, day + 4));
}

View File

@ -1,18 +1,15 @@
import { InformationCircleIcon } from "@heroicons/react/outline";
import crypto from "crypto";
import { GetServerSidePropsContext } from "next";
import { RefObject, useEffect, useRef, useState } from "react";
import Select from "react-select";
import { i18n } from "next-i18next.config";
import { ComponentProps, RefObject, useEffect, useRef, useState } from "react";
import Select, { OptionTypeBase } from "react-select";
import TimezoneSelect from "react-timezone-select";
import { QueryCell } from "@lib/QueryCell";
import { asStringOrNull, asStringOrUndefined } from "@lib/asStringOrNull";
import { getSession } from "@lib/auth";
import {
getOrSetUserLocaleFromHeaders,
localeLabels,
localeOptions,
OptionType,
} from "@lib/core/i18n/i18n.utils";
import { nameOfDay } from "@lib/core/i18n/weekday";
import { useLocale } from "@lib/hooks/useLocale";
import { isBrandingHidden } from "@lib/isBrandingHidden";
import showToast from "@lib/notification";
@ -20,7 +17,7 @@ import prisma from "@lib/prisma";
import { trpc } from "@lib/trpc";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import { DialogClose, Dialog, DialogContent } from "@components/Dialog";
import { Dialog, DialogClose, DialogContent } from "@components/Dialog";
import ImageUploader from "@components/ImageUploader";
import SettingsShell from "@components/SettingsShell";
import Shell from "@components/Shell";
@ -31,6 +28,14 @@ import Button from "@components/ui/Button";
import { UsernameInput } from "@components/ui/UsernameInput";
type Props = inferSSRProps<typeof getServerSideProps>;
const getLocaleOptions = (displayLocale: string | string[]): OptionTypeBase[] => {
return i18n.locales.map((locale) => ({
value: locale,
label: new Intl.DisplayNames(displayLocale, { type: "language" }).of(locale),
}));
};
function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>; user: Props["user"] }) {
const { t } = useLocale();
const [modelOpen, setModalOpen] = useState(false);
@ -58,12 +63,12 @@ function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>
/>
<Dialog open={modelOpen}>
<DialogContent>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-yellow-100 mb-4">
<InformationCircleIcon className="h-6 w-6 text-yellow-400" aria-hidden="true" />
<div className="flex items-center justify-center w-12 h-12 mx-auto mb-4 bg-yellow-100 rounded-full">
<InformationCircleIcon className="w-6 h-6 text-yellow-400" aria-hidden="true" />
</div>
<div className="sm:flex sm:items-start mb-4">
<div className="mb-4 sm:flex sm:items-start">
<div className="mt-3 sm:mt-0 sm:text-left">
<h3 className="font-cal text-lg leading-6 font-bold text-gray-900" id="modal-title">
<h3 className="text-lg font-bold leading-6 text-gray-900 font-cal" id="modal-title">
{t("only_available_on_pro_plan")}
</h3>
</div>
@ -83,7 +88,7 @@ function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>
<div className="mt-5 sm:mt-4 sm:flex sm:flex-row-reverse gap-x-2">
<DialogClose asChild>
<Button
className="btn-wide btn-primary text-center table-cell"
className="table-cell text-center btn-wide btn-primary"
onClick={() => setModalOpen(false)}>
{t("dismiss")}
</Button>
@ -95,9 +100,25 @@ function HideBrandingInput(props: { hideBrandingRef: RefObject<HTMLInputElement>
);
}
export default function Settings(props: Props) {
function SettingsView(props: ComponentProps<typeof Settings> & { localeProp: string }) {
const utils = trpc.useContext();
const { t } = useLocale();
const mutation = trpc.useMutation("viewer.updateProfile");
const mutation = trpc.useMutation("viewer.updateProfile", {
onSuccess: () => {
showToast(t("your_user_profile_updated_successfully"), "success");
setHasErrors(false); // dismiss any open errors
},
onError: (err) => {
setHasErrors(true);
setErrorMessage(err.message);
document?.getElementsByTagName("main")[0]?.scrollTo({ top: 0, behavior: "smooth" });
},
async onSettled() {
await utils.invalidateQueries(["viewer.i18n"]);
},
});
const localeOptions = getLocaleOptions(props.localeProp);
const themeOptions = [
{ value: "light", label: t("light") },
@ -109,15 +130,16 @@ export default function Settings(props: Props) {
const descriptionRef = useRef<HTMLTextAreaElement>(null!);
const avatarRef = useRef<HTMLInputElement>(null!);
const hideBrandingRef = useRef<HTMLInputElement>(null!);
const [selectedTheme, setSelectedTheme] = useState<undefined | { value: string; label: string }>(undefined);
const [selectedTheme, setSelectedTheme] = useState<OptionTypeBase>();
const [selectedTimeZone, setSelectedTimeZone] = useState({ value: props.user.timeZone });
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState({
const [selectedWeekStartDay, setSelectedWeekStartDay] = useState<OptionTypeBase>({
value: props.user.weekStart,
label: "",
label: nameOfDay(props.localeProp, props.user.weekStart === "Sunday" ? 0 : 1),
});
const [selectedLanguage, setSelectedLanguage] = useState<OptionType>({
const [selectedLanguage, setSelectedLanguage] = useState<OptionTypeBase>({
value: props.localeProp,
label: props.localeLabels[props.localeProp],
label: localeOptions.find((option) => option.value === props.localeProp)?.label,
});
const [imageSrc, setImageSrc] = useState<string>(props.user.avatar || "");
const [hasErrors, setHasErrors] = useState(false);
@ -127,8 +149,6 @@ export default function Settings(props: Props) {
setSelectedTheme(
props.user.theme ? themeOptions.find((theme) => theme.value === props.user.theme) : undefined
);
setSelectedWeekStartDay({ value: props.user.weekStart, label: props.user.weekStart });
setSelectedLanguage({ value: props.localeProp, label: props.localeLabels[props.localeProp] });
}, []);
async function updateProfileHandler(event) {
@ -145,274 +165,239 @@ export default function Settings(props: Props) {
// TODO: Add validation
await mutation
.mutateAsync({
username: enteredUsername,
name: enteredName,
bio: enteredDescription,
avatar: enteredAvatar,
timeZone: enteredTimeZone,
weekStart: asStringOrUndefined(enteredWeekStartDay),
hideBranding: enteredHideBranding,
theme: asStringOrNull(selectedTheme?.value),
locale: enteredLanguage,
})
.then(() => {
showToast(t("your_user_profile_updated_successfully"), "success");
setHasErrors(false); // dismiss any open errors
})
.catch((err) => {
setHasErrors(true);
setErrorMessage(err.message);
document?.getElementsByTagName("main")[0]?.scrollTo({ top: 0, behavior: "smooth" });
});
mutation.mutate({
username: enteredUsername,
name: enteredName,
bio: enteredDescription,
avatar: enteredAvatar,
timeZone: enteredTimeZone,
weekStart: asStringOrUndefined(enteredWeekStartDay),
hideBranding: enteredHideBranding,
theme: asStringOrNull(selectedTheme?.value),
locale: enteredLanguage,
});
}
return (
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
{hasErrors && <Alert severity="error" title={errorMessage} />}
<div className="py-6 lg:pb-8">
<div className="flex flex-col lg:flex-row">
<div className="flex-grow space-y-6">
<div className="block sm:flex">
<div className="w-full mb-6 sm:w-1/2 sm:mr-2">
<UsernameInput ref={usernameRef} defaultValue={props.user.username} />
</div>
<div className="w-full sm:w-1/2 sm:ml-2">
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
{t("full_name")}
</label>
<input
ref={nameRef}
type="text"
name="name"
id="name"
autoComplete="given-name"
placeholder={t("your_name")}
required
className="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-sm shadow-sm focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
defaultValue={props.user.name}
/>
</div>
</div>
<div className="block sm:flex">
<div className="w-full mb-6 sm:w-1/2 sm:mr-2">
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
{t("email")}
</label>
<input
type="text"
name="email"
id="email"
placeholder={t("your_email")}
disabled
className="block w-full px-3 py-2 mt-1 text-gray-500 border border-gray-300 rounded-l-sm bg-gray-50 sm:text-sm"
defaultValue={props.user.email}
/>
<p className="mt-2 text-sm text-gray-500" id="email-description">
{t("change_email_contact")}{" "}
<a className="text-blue-500" href="mailto:help@cal.com">
help@cal.com
</a>
</p>
</div>
</div>
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700">
{t("about")}
</label>
<div className="mt-1">
<textarea
ref={descriptionRef}
id="about"
name="about"
placeholder={t("little_something_about")}
rows={3}
defaultValue={props.user.bio || undefined}
className="block w-full mt-1 border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"></textarea>
</div>
</div>
<div>
<div className="flex mt-1">
<Avatar
displayName={props.user.name}
className="relative w-10 h-10 rounded-full"
gravatarFallbackMd5={props.user.emailMd5}
imageSrc={imageSrc}
/>
<input
ref={avatarRef}
type="hidden"
name="avatar"
id="avatar"
placeholder="URL"
className="block w-full px-3 py-2 mt-1 border border-gray-300 rounded-sm shadow-sm focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
defaultValue={imageSrc}
/>
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("change_avatar")}
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}
/>
</div>
<hr className="mt-6" />
</div>
<div>
<label htmlFor="language" className="block text-sm font-medium text-gray-700">
{t("language")}
</label>
<div className="mt-1">
<Select
id="languageSelect"
value={selectedLanguage || props.localeProp}
onChange={setSelectedLanguage}
classNamePrefix="react-select"
className="block w-full mt-1 capitalize border border-gray-300 rounded-sm shadow-sm react-select-container focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
options={localeOptions}
/>
</div>
</div>
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<div className="mt-1">
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={setSelectedTimeZone}
classNamePrefix="react-select"
className="block w-full mt-1 border border-gray-300 rounded-sm shadow-sm react-select-container focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
/>
</div>
</div>
<div>
<label htmlFor="weekStart" className="block text-sm font-medium text-gray-700">
{t("first_day_of_week")}
</label>
<div className="mt-1">
<Select
id="weekStart"
value={selectedWeekStartDay}
onChange={setSelectedWeekStartDay}
classNamePrefix="react-select"
className="block w-full mt-1 capitalize border border-gray-300 rounded-sm shadow-sm react-select-container focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
options={[
{ value: "Sunday", label: nameOfDay(props.localeProp, 0) },
{ value: "Monday", label: nameOfDay(props.localeProp, 1) },
]}
/>
</div>
</div>
<div>
<label htmlFor="theme" className="block text-sm font-medium text-gray-700">
{t("single_theme")}
</label>
<div className="my-1">
<Select
id="theme"
isDisabled={!selectedTheme}
defaultValue={selectedTheme || themeOptions[0]}
value={selectedTheme || themeOptions[0]}
onChange={setSelectedTheme}
className="shadow-sm | { value: string } focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"
options={themeOptions}
/>
</div>
<div className="relative flex items-start mt-8">
<div className="flex items-center h-5">
<input
id="theme-adjust-os"
name="theme-adjust-os"
type="checkbox"
onChange={(e) => setSelectedTheme(e.target.checked ? null : themeOptions[0])}
checked={!selectedTheme}
className="w-4 h-4 border-gray-300 rounded-sm focus:ring-neutral-500 text-neutral-900"
/>
</div>
<div className="ml-3 text-sm">
<label htmlFor="theme-adjust-os" className="font-medium text-gray-700">
{t("automatically_adjust_theme")}
</label>
</div>
</div>
</div>
<div>
<div className="relative flex items-start">
<div className="flex items-center h-5">
<HideBrandingInput user={props.user} hideBrandingRef={hideBrandingRef} />
</div>
<div className="ml-3 text-sm">
<label htmlFor="hide-branding" className="font-medium text-gray-700">
{t("disable_cal_branding")}{" "}
{props.user.plan !== "PRO" && <Badge variant="default">PRO</Badge>}
</label>
<p className="text-gray-500">{t("disable_cal_branding_description")}</p>
</div>
</div>
</div>
</div>
</div>
<hr className="mt-8" />
<div className="flex justify-end py-4">
<Button type="submit">{t("save")}</Button>
</div>
</div>
</form>
);
}
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>
<form className="divide-y divide-gray-200 lg:col-span-9" onSubmit={updateProfileHandler}>
{hasErrors && <Alert severity="error" title={errorMessage} />}
<div className="py-6 lg:pb-8">
<div className="flex flex-col lg:flex-row">
<div className="flex-grow space-y-6">
<div className="block sm:flex">
<div className="w-full sm:w-1/2 sm:mr-2 mb-6">
<UsernameInput ref={usernameRef} defaultValue={props.user.username} />
</div>
<div className="w-full sm:w-1/2 sm:ml-2">
<label htmlFor="name" className="block text-sm font-medium text-gray-700">
{t("full_name")}
</label>
<input
ref={nameRef}
type="text"
name="name"
id="name"
autoComplete="given-name"
placeholder={t("your_name")}
required
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
defaultValue={props.user.name}
/>
</div>
</div>
<div className="block sm:flex">
<div className="w-full sm:w-1/2 sm:mr-2 mb-6">
<label htmlFor="email" className="block text-sm font-medium text-gray-700">
{t("email")}
</label>
<input
type="text"
name="email"
id="email"
placeholder={t("your_email")}
disabled
className="mt-1 block w-full py-2 px-3 text-gray-500 border border-gray-300 rounded-l-sm bg-gray-50 sm:text-sm"
defaultValue={props.user.email}
/>
<p className="mt-2 text-sm text-gray-500" id="email-description">
{t("change_email_contact")}{" "}
<a className="text-blue-500" href="mailto:help@cal.com">
help@cal.com
</a>
</p>
</div>
</div>
<div>
<label htmlFor="about" className="block text-sm font-medium text-gray-700">
{t("about")}
</label>
<div className="mt-1">
<textarea
ref={descriptionRef}
id="about"
name="about"
placeholder={t("little_something_about")}
rows={3}
defaultValue={props.user.bio}
className="shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"></textarea>
</div>
</div>
<div>
<div className="mt-1 flex">
<Avatar
displayName={props.user.name}
className="relative rounded-full w-10 h-10"
gravatarFallbackMd5={props.user.emailMd5}
imageSrc={imageSrc}
/>
<input
ref={avatarRef}
type="hidden"
name="avatar"
id="avatar"
placeholder="URL"
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
defaultValue={imageSrc}
/>
<ImageUploader
target="avatar"
id="avatar-upload"
buttonMsg={t("change_avatar")}
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}
/>
</div>
<hr className="mt-6" />
</div>
<div>
<label htmlFor="language" className="block text-sm font-medium text-gray-700">
{t("language")}
</label>
<div className="mt-1">
<Select
id="languageSelect"
value={selectedLanguage || props.localeProp}
onChange={setSelectedLanguage}
classNamePrefix="react-select"
className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm"
options={props.localeOptions}
/>
</div>
</div>
<div>
<label htmlFor="timeZone" className="block text-sm font-medium text-gray-700">
{t("timezone")}
</label>
<div className="mt-1">
<TimezoneSelect
id="timeZone"
value={selectedTimeZone}
onChange={setSelectedTimeZone}
classNamePrefix="react-select"
className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm"
/>
</div>
</div>
<div>
<label htmlFor="weekStart" className="block text-sm font-medium text-gray-700">
{t("first_day_of_week")}
</label>
<div className="mt-1">
<Select
id="weekStart"
value={selectedWeekStartDay}
onChange={setSelectedWeekStartDay}
classNamePrefix="react-select"
className="react-select-container border border-gray-300 rounded-sm shadow-sm focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm"
options={[
{ value: "Sunday", label: t("sunday") },
{ value: "Monday", label: t("monday") },
]}
/>
</div>
</div>
<div>
<label htmlFor="theme" className="block text-sm font-medium text-gray-700">
{t("single_theme")}
</label>
<div className="my-1">
<Select
id="theme"
isDisabled={!selectedTheme}
defaultValue={selectedTheme || themeOptions[0]}
value={selectedTheme || themeOptions[0]}
onChange={setSelectedTheme}
className="shadow-sm | { value: string } focus:ring-neutral-500 focus:border-neutral-500 mt-1 block w-full sm:text-sm border-gray-300 rounded-sm"
options={themeOptions}
/>
</div>
<div className="mt-8 relative flex items-start">
<div className="flex items-center h-5">
<input
id="theme-adjust-os"
name="theme-adjust-os"
type="checkbox"
onChange={(e) => setSelectedTheme(e.target.checked ? null : themeOptions[0])}
checked={!selectedTheme}
className="focus:ring-neutral-500 h-4 w-4 text-neutral-900 border-gray-300 rounded-sm"
/>
</div>
<div className="ml-3 text-sm">
<label htmlFor="theme-adjust-os" className="font-medium text-gray-700">
{t("automatically_adjust_theme")}
</label>
</div>
</div>
</div>
<div>
<div className="relative flex items-start">
<div className="flex items-center h-5">
<HideBrandingInput user={props.user} hideBrandingRef={hideBrandingRef} />
</div>
<div className="ml-3 text-sm">
<label htmlFor="hide-branding" className="font-medium text-gray-700">
{t("disable_cal_branding")}{" "}
{props.user.plan !== "PRO" && <Badge variant="default">PRO</Badge>}
</label>
<p className="text-gray-500">{t("disable_cal_branding_description")}</p>
</div>
</div>
</div>
</div>
{/*<div className="mt-6 flex-grow lg:mt-0 lg:ml-6 lg:flex-grow-0 lg:flex-shrink-0">
<p className="mb-2 text-sm font-medium text-gray-700" aria-hidden="true">
Photo
</p>
<div className="mt-1 lg:hidden">
<div className="flex items-center">
<div
className="flex-shrink-0 inline-block rounded-full overflow-hidden h-12 w-12"
aria-hidden="true">
<Avatar user={props.user} className="rounded-full h-full w-full" />
</div>
</div>
</div>
<div className="hidden relative rounded-full overflow-hidden lg:block">
<Avatar
user={props.user}
className="relative rounded-full w-40 h-40"
fallback={<div className="relative bg-neutral-900 rounded-full w-40 h-40"></div>}
/>
</div>
<div className="mt-4">
<label htmlFor="avatar" className="block text-sm font-medium text-gray-700">
Avatar URL
</label>
<input
ref={avatarRef}
type="text"
name="avatar"
id="avatar"
placeholder="URL"
className="mt-1 block w-full border border-gray-300 rounded-sm shadow-sm py-2 px-3 focus:outline-none focus:ring-neutral-500 focus:border-neutral-500 sm:text-sm"
defaultValue={props.user.avatar}
/>
</div>
</div>*/}
</div>
<hr className="mt-8" />
<div className="py-4 flex justify-end">
<Button type="submit">{t("save")}</Button>
</div>
</div>
</form>
<QueryCell
query={query}
success={({ data }) => <SettingsView {...props} localeProp={data.locale} />}
/>
</SettingsShell>
</Shell>
);
@ -420,7 +405,6 @@ export default function Settings(props: Props) {
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" } };
@ -451,10 +435,6 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
return {
props: {
session,
localeProp: locale,
localeOptions,
localeLabels,
user: {
...user,
emailMd5: crypto.createHash("md5").update(user.email).digest("hex"),