Improve multilingualism and fix search routes (#5334)

* fixed search routes

* made static text multilingual and fixed german translations

# Conflicts:
#	apps/web/components/availability/Schedule.tsx

* delete empty file created by fixing merge conflicts

* fixing TextField placeholder by passing attendeeName and fixed json variable

* Using useLocal to modify static text to make it multilingual, and passing the correct variables for brand and mail

* seperated whitelabel and improved translations

* added missing translation

* added missing translation for webhooks

* Updated AdminAppsView with dynamic translations.
Added translations for german and english files only.

* changed back to one liner

* updated german and english translations for impersonation.tsx

* updated german and english translations for impersonation.tsx and users.tsx. added missing german translation for timeformat_profile_hint

* updated german and english translations for team-billing-view.tsx

* updated german and english translations for LicenseRequired.tsx

* updated routes for profile and avatar

* yarn.lock updated from newer changes

* Revert " yarn.lock updated from newer changes"

This reverts commit efd9a90bf7.

* sanitize dangerouslySetInnerHTML to prevent xss attacks

* tried to fix window title flicker

* changed ssdInit to ssrInit for getServerSideProps. Serverside translation works but current route still set as a window title

* flicker with route in window title is caused here. It is not necessary to check if isPublic and session is false because it already gets checked in useRedirectToLoginIfUnauthenticated hook.

* fixed window title translation flicker for availability page

* fixed window title translation flicker for teams page

* fixed window title translation flicker for workflow page

* fixed error that div may not be rendered within p element

* fixed window title translation flicker for booking page

* fixed window title translation flicker by adding getServerSideProps

* Only set HeadSeo if an page title exists. If window title is set by the Meta component, shell is causing a flicker because it overwrites the title which is set by Meta. It is a problem especially for settings pages.

* fixed window title translation flicker by adding the Meta component to the skeleton

* fixed condition

* removed condition and added withoutSeo for settings pages

* using translations for create team page further fixed flicker for window title

* fixed flicker for window title for event-type creation page

* fixed flicker for window title for availability creation page

* fixed flicker for window title for sso page

* updated conferencing en translation

* added meta.CTA back it was mistakenly removed

* fixed flicker for workflows page

* fixed missing variable

* Update packages/ui/v2/core/Shell.tsx

* Delete index.tsx

* Update sso.tsx

* Updates subdmoules

Co-authored-by: maxi <maximilian.oehrlein@clicksports.de>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
This commit is contained in:
Max Oehrlein 2022-12-07 21:53:44 +01:00 committed by GitHub
parent 02e9d8d45f
commit 4686d3ff48
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
74 changed files with 568 additions and 141 deletions

View File

@ -195,7 +195,7 @@ const Component = ({
<h4 className="mt-8 font-semibold text-gray-900 ">{t("pricing")}</h4>
<span>
{price === 0 ? (
"Free"
t("free_to_use_apps")
) : (
<>
{Intl.NumberFormat("en-US", {

View File

@ -20,12 +20,13 @@ type AvailabilityOption = {
const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.Option {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.Option>
@ -34,12 +35,13 @@ const Option = ({ ...props }: OptionProps<AvailabilityOption>) => {
const SingleValue = ({ ...props }: SingleValueProps<AvailabilityOption>) => {
const { label, isDefault } = props.data;
const { t } = useLocale();
return (
<components.SingleValue {...props}>
<span>{label}</span>
{isDefault && (
<Badge variant="blue" className="ml-2">
Default
{t("default")}
</Badge>
)}
</components.SingleValue>

View File

@ -103,7 +103,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
addOnSuffix={
@ -352,7 +352,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupInfered
<TextField
label={t("event_name")}
type="text"
placeholder={t("meeting_with_user")}
placeholder={t("meeting_with_user", { attendeeName: eventType.users[0]?.name })}
defaultValue={eventType.eventName || ""}
{...formMethods.register("eventName")}
/>

View File

@ -130,6 +130,7 @@ export const EventSetupTab = (
{validLocations.length === 0 && (
<div className="flex">
<Select
placeholder={t("select")}
options={locationOptions}
isSearchable={false}
className="block w-full min-w-0 flex-1 rounded-sm text-sm"
@ -212,7 +213,7 @@ export const EventSetupTab = (
<div className="space-y-8">
<TextField
required
label={t("Title")}
label={t("title")}
defaultValue={eventType.title}
{...formMethods.register("title")}
/>

View File

@ -67,7 +67,7 @@ function getNavigation(props: {
name: "availability",
href: `/event-types/${eventType.id}?tabName=availability`,
icon: Icon.FiCalendar,
info: `Working Hours`, // TODO: Get this from props
info: `default_schedule_name`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",

View File

@ -1,9 +1,12 @@
import Head from "next/head";
import { APP_NAME, WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button } from "@calcom/ui";
export default function Error500() {
const { t } = useLocale();
return (
<div className="flex h-screen">
<Head>
@ -24,7 +27,7 @@ export default function Error500() {
Something went wrong on our end. Get in touch with our support team, and well get it fixed right
away for you.
</p>
<Button href={`${WEBSITE_URL}/support`}>Contact support</Button>
<Button href={`${WEBSITE_URL}/support`}>{t("contact_support")}</Button>
<Button color="secondary" href="javascript:history.back()" className="ml-2">
Go back
</Button>

View File

@ -29,6 +29,8 @@ import { HttpError } from "@lib/core/http/error";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
import EditableHeading from "@components/ui/EditableHeading";
import { ssgInit } from "@server/lib/ssg";
const querySchema = z.object({
schedule: stringOrNumber,
});
@ -91,7 +93,7 @@ export default function Availability({ schedule }: { schedule: number }) {
return (
<Shell
backPath="/availability"
title={data?.schedule.name && data.schedule.name + " | " + t("availability")}
title={data?.schedule.name ? data.schedule.name + " | " + t("availability") : t("availability")}
heading={
<Controller
control={form.control}
@ -209,14 +211,16 @@ export default function Availability({ schedule }: { schedule: number }) {
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
if (!params.success) return { notFound: true };
return {
props: {
schedule: params.data.schedule,
trpcState: ssg.dehydrate(),
},
revalidate: 10, // seconds
};

View File

@ -1,4 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { GetServerSidePropsContext } from "next";
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -10,6 +11,8 @@ import { HttpError } from "@lib/core/http/error";
import SkeletonLoader from "@components/availability/SkeletonLoader";
import { ssrInit } from "@server/lib/ssr";
export function AvailabilityList({ schedules }: RouterOutputs["viewer"]["availability"]["list"]) {
const { t } = useLocale();
const utils = trpc.useContext();
@ -112,3 +115,13 @@ export default function AvailabilityPage() {
</div>
);
}
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -14,6 +14,8 @@ import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import BookingListItem from "@components/booking/BookingListItem";
import SkeletonLoader from "@components/booking/SkeletonLoader";
import { ssgInit } from "@server/lib/ssg";
type BookingListingStatus = RouterInputs["viewer"]["bookings"]["get"]["status"];
type BookingOutput = RouterOutputs["viewer"]["bookings"]["get"]["bookings"][0];
@ -179,14 +181,16 @@ export default function Bookings() {
);
}
export const getStaticProps: GetStaticProps = (ctx) => {
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const ssg = await ssgInit(ctx);
if (!params.success) return { notFound: true };
return {
props: {
status: params.data.status,
trpcState: ssg.dehydrate(),
},
};
};

View File

@ -39,6 +39,7 @@ import { EventTypeSingleLayout } from "@components/eventtype/EventTypeSingleLayo
import EventWorkflowsTab from "@components/eventtype/EventWorkfowsTab";
import { getTranslation } from "@server/lib/i18n";
import { ssrInit } from "@server/lib/ssr";
export type FormValues = {
title: string;
@ -316,6 +317,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const { req, query } = context;
const session = await getSession({ req });
const typeParam = parseInt(asStringOrThrow(query.type));
const ssr = await ssrInit(context);
if (Number.isNaN(typeParam)) {
return {
@ -553,6 +555,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
team: eventTypeObject.team || null,
teamMembers,
currentUserMembership,
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,5 +1,5 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import Head from "next/head";
import { GetServerSidePropsContext } from "next";
import Link from "next/link";
import { useRouter } from "next/router";
import React, { Fragment, useEffect, useState } from "react";
@ -38,6 +38,8 @@ import SkeletonLoader from "@components/eventtype/SkeletonLoader";
import Avatar from "@components/ui/Avatar";
import AvatarGroup from "@components/ui/AvatarGroup";
import { ssrInit } from "@server/lib/ssr";
type EventTypeGroups = RouterOutputs["viewer"]["eventTypes"]["getByViewer"]["eventTypeGroups"];
type EventTypeGroupProfile = EventTypeGroups[number]["profile"];
@ -579,6 +581,7 @@ const WithQuery = withQuery(trpc.viewer.eventTypes.getByViewer);
const EventTypesPage = () => {
const { t } = useLocale();
return (
<div>
<Shell
@ -618,4 +621,14 @@ const EventTypesPage = () => {
);
};
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default EventTypesPage;

View File

@ -1,14 +1,30 @@
import { GetServerSidePropsContext } from "next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminAppsView() {
const { t } = useLocale();
return (
<>
<Meta title="Apps" description="apps_description" />
<h1>App listing</h1>
<Meta title={t("apps")} description={t("apps_description")} />
<h1>{t("apps_listing")}</h1>
</>
);
}
AdminAppsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminAppsView;

View File

@ -1,16 +1,19 @@
import { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react";
import { useRef } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, getAdminLayout as getLayout, Meta, TextField } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminView() {
const { t } = useLocale();
const usernameRef = useRef<HTMLInputElement>(null);
return (
<>
<Meta title="Admin" description="Impersonation" />
<Meta title={t("admin")} description={t("impersonation")} />
<form
className="mb-6 w-full sm:w-1/2"
onSubmit={(e) => {
@ -21,7 +24,7 @@ function AdminView() {
<div className="flex items-center space-x-2">
<TextField
containerClassName="w-full"
name="Impersonate User"
name={t("user_impersonation_heading")}
addOnLeading={<>{process.env.NEXT_PUBLIC_WEBSITE_URL}/</>}
ref={usernameRef}
hint={t("impersonate_user_tip")}
@ -36,4 +39,14 @@ function AdminView() {
AdminView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminView;

View File

@ -1,5 +1,9 @@
import { GetServerSidePropsContext } from "next";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminAppsView() {
return (
<>
@ -11,4 +15,14 @@ function AdminAppsView() {
AdminAppsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminAppsView;

View File

@ -1,14 +1,30 @@
import { GetServerSidePropsContext } from "next";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getAdminLayout as getLayout, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function AdminUsersView() {
const { t } = useLocale();
return (
<>
<Meta title="Users" description="users_description" />
<h1>Users listing</h1>
<Meta title={t("users")} description={t("users_description")} />
<h1>{t("users_listing")}</h1>
</>
);
}
AdminUsersView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AdminUsersView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
import { HelpScout, useChat } from "react-live-chat-loader";
@ -8,6 +9,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, getSettingsLayout as getLayout, Icon, Meta } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
interface CtaRowProps {
title: string;
description: string;
@ -64,7 +67,7 @@ const BillingView = () => {
<CtaRow title={t("billing_help_title")} description={t("billing_help_description")}>
<Button color="secondary" onClick={onContactSupportClick}>
{t("billing_help_cta")}
{t("contact_support")}
</Button>
</CtaRow>
{showChat && <HelpScout color="#292929" icon="message" horizontalPosition="right" zIndex="1" />}
@ -75,4 +78,14 @@ const BillingView = () => {
BillingView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default BillingView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { TApiKeys } from "@calcom/ee/api-keys/components/ApiKeyListItem";
@ -18,6 +19,8 @@ import {
SkeletonLoader,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const ApiKeysView = () => {
const { t } = useLocale();
@ -93,4 +96,14 @@ const ApiKeysView = () => {
ApiKeysView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ApiKeysView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useSession } from "next-auth/react";
import { Controller, useForm } from "react-hook-form";
@ -18,9 +19,12 @@ import {
Switch,
} from "@calcom/ui";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonButton className="mr-6 h-32 w-48 rounded-md p-5" />
@ -69,7 +73,7 @@ const AppearanceView = () => {
},
});
if (isLoading) return <SkeletonLoader />;
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
if (!user) return null;
@ -86,7 +90,7 @@ const AppearanceView = () => {
theme: values.theme || null,
});
}}>
<Meta title="Appearance" description="Manage settings for your booking appearance" />
<Meta title={t("appearance")} description={t("appearance_description")} />
<div className="mb-6 flex items-center text-sm">
<div>
<p className="font-semibold">{t("theme")}</p>
@ -206,6 +210,16 @@ const AppearanceView = () => {
AppearanceView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default AppearanceView;
interface ThemeLabelProps {
variant: "light" | "dark" | "system";

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/router";
@ -29,6 +30,8 @@ import { QueryCell } from "@lib/QueryCell";
import { CalendarSwitch } from "@components/settings/CalendarSwitch";
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = () => {
return (
<SkeletonContainer>
@ -77,11 +80,7 @@ const CalendarsView = () => {
return (
<>
<Meta
title="Calendars"
description="Configure how your event types interact with your calendars"
CTA={<AddCalendarButton />}
/>
<Meta title={t("calendars")} description={t("calendars_description")} CTA={<AddCalendarButton />} />
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
@ -231,4 +230,14 @@ const CalendarsView = () => {
CalendarsView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default CalendarsView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -24,9 +25,12 @@ import {
SkeletonText,
} from "@calcom/ui";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@ -60,11 +64,12 @@ const ConferencingLayout = () => {
const [deleteAppModal, setDeleteAppModal] = useState(false);
const [deleteCredentialId, setDeleteCredentialId] = useState<number>(0);
if (isLoading) return <SkeletonLoader />;
if (isLoading)
return <SkeletonLoader title={t("conferencing")} description={t("conferencing_description")} />;
return (
<div className="w-full bg-white sm:mx-0 xl:mt-0">
<Meta title="Conferencing" description="Add your favourite video conferencing apps for your meetings" />
<Meta title={t("conferencing")} description={t("conferencing_description")} />
<List>
{apps?.items &&
apps.items
@ -128,4 +133,14 @@ const ConferencingLayout = () => {
ConferencingLayout.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ConferencingLayout;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useMemo } from "react";
import { Controller, useForm } from "react-hook-form";
@ -21,9 +22,12 @@ import {
import { withQuery } from "@lib/QueryCell";
import { nameOfDay } from "@lib/core/i18n/weekday";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<SkeletonText className="h-8 w-full" />
<SkeletonText className="h-8 w-full" />
@ -47,14 +51,14 @@ const GeneralQueryView = () => {
const { t } = useLocale();
const { data: user, isLoading } = trpc.viewer.me.useQuery();
if (isLoading) return <SkeletonLoader />;
if (isLoading) return <SkeletonLoader title={t("general")} description={t("general_description")} />;
if (!user) {
throw new Error(t("something_went_wrong"));
}
return (
<WithQuery
success={({ data }) => <GeneralView user={user} localeProp={data.locale} />}
customLoader={<SkeletonLoader />}
customLoader={<SkeletonLoader title={t("general")} description={t("general_description")} />}
/>
);
};
@ -127,7 +131,7 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
weekStart: values.weekStart.value,
});
}}>
<Meta title="General" description="Manage settings for your language and timezone" />
<Meta title={t("general")} description={t("general_description")} />
<Controller
name="locale"
render={({ field: { value, onChange } }) => (
@ -210,4 +214,14 @@ const GeneralView = ({ localeProp, user }: GeneralViewProps) => {
GeneralQueryView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default GeneralQueryView;

View File

@ -1,5 +1,6 @@
import { IdentityProvider } from "@prisma/client";
import crypto from "crypto";
import { GetServerSidePropsContext } from "next";
import { signOut } from "next-auth/react";
import { BaseSyntheticEvent, useEffect, useRef, useState } from "react";
import { Controller, useForm } from "react-hook-form";
@ -37,9 +38,12 @@ import {
import TwoFactor from "@components/auth/TwoFactor";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
const SkeletonLoader = () => {
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonAvatar className=" h-12 w-12 px-4" />
@ -208,11 +212,14 @@ const ProfileView = () => {
showToast(t("error_updating_settings"), "error");
};
if (isLoading || !user) return <SkeletonLoader />;
if (isLoading || !user)
return <SkeletonLoader title={t("profile")} description={t("profile_description")} />;
const isDisabled = isSubmitting || !isDirty;
return (
<>
<Meta title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
<Form
form={formMethods}
handleSubmit={(values) => {
@ -222,7 +229,6 @@ const ProfileView = () => {
mutation.mutate(values);
}
}}>
<Meta title="Profile" description={t("profile_description", { appName: APP_NAME })} />
<div className="flex items-center">
<Controller
control={formMethods.control}
@ -359,4 +365,14 @@ const ProfileView = () => {
ProfileView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ProfileView;

View File

@ -1,3 +1,4 @@
import { GetServerSidePropsContext } from "next";
import { useForm } from "react-hook-form";
import { useLocale } from "@calcom/lib/hooks/useLocale";
@ -13,6 +14,8 @@ import {
Switch,
} from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const ProfileImpersonationView = () => {
const { t } = useLocale();
const utils = trpc.useContext();
@ -39,7 +42,7 @@ const ProfileImpersonationView = () => {
return (
<>
<Meta title="Impersonation Settings" description="" />
<Meta title={t("impersonation")} description={t("impersonation_description")} />
<Form
form={formMethods}
handleSubmit={({ disableImpersonation }) => {
@ -74,4 +77,14 @@ const ProfileImpersonationView = () => {
ProfileImpersonationView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default ProfileImpersonationView;

View File

@ -1,4 +1,5 @@
import { IdentityProvider } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { Trans } from "next-i18next";
import { useForm } from "react-hook-form";
@ -7,6 +8,8 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, Form, getSettingsLayout as getLayout, Meta, PasswordField, showToast } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
type ChangePasswordFormValues = {
oldPassword: string;
newPassword: string;
@ -92,4 +95,14 @@ const PasswordView = () => {
PasswordView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default PasswordView;

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/sso/page/user-sso-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,12 +1,37 @@
import { GetServerSidePropsContext } from "next";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Badge, getSettingsLayout as getLayout, Loader, Meta, Switch } from "@calcom/ui";
import {
Badge,
getSettingsLayout as getLayout,
Meta,
Switch,
SkeletonButton,
SkeletonContainer,
SkeletonText,
} from "@calcom/ui";
import DisableTwoFactorModal from "@components/settings/DisableTwoFactorModal";
import EnableTwoFactorModal from "@components/settings/EnableTwoFactorModal";
import { ssrInit } from "@server/lib/ssr";
const SkeletonLoader = ({ title, description }: { title: string; description: string }) => {
return (
<SkeletonContainer>
<Meta title={title} description={description} />
<div className="mt-6 mb-8 space-y-6 divide-y">
<div className="flex items-center">
<SkeletonButton className="mr-6 h-8 w-20 rounded-md p-5" />
<SkeletonText className="h-8 w-full" />
</div>
</div>
</SkeletonContainer>
);
};
const TwoFactorAuthView = () => {
const utils = trpc.useContext();
@ -16,11 +41,11 @@ const TwoFactorAuthView = () => {
const [enableModalOpen, setEnableModalOpen] = useState(false);
const [disableModalOpen, setDisableModalOpen] = useState(false);
if (isLoading) return <Loader />;
if (isLoading) return <SkeletonLoader title={t("2fa")} description={t("2fa_description")} />;
return (
<>
<Meta title="Two-Factor Authentication" description="Manage settings for your account passwords" />
<Meta title={t("2fa")} description={t("2fa_description")} />
<div className="mt-6 flex items-start space-x-4">
<Switch
checked={user?.twoFactorEnabled}
@ -35,7 +60,7 @@ const TwoFactorAuthView = () => {
{user?.twoFactorEnabled ? t("enabled") : t("disabled")}
</Badge>
</div>
<p className="text-sm text-gray-600">Add an extra layer of security to your account.</p>
<p className="text-sm text-gray-600">{t("add_an_extra_layer_of_security")}</p>
</div>
</div>
@ -68,4 +93,14 @@ const TwoFactorAuthView = () => {
TwoFactorAuthView.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default TwoFactorAuthView;

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-appearance-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-billing-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-members-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/teams/pages/team-profile-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/sso/page/teams-sso-view";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -1,14 +1,19 @@
import { GetServerSidePropsContext } from "next";
import Head from "next/head";
import { CreateANewTeamForm } from "@calcom/features/ee/teams/components";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { getWizardLayout as getLayout } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
const CreateNewTeamPage = () => {
const { t } = useLocale();
return (
<>
<Head>
<title>Create a new Team</title>
<meta name="description" content="Create a new team to ease your organisational booking" />
<title>{t("create_new_team")}</title>
<meta name="description" content={t("create_new_team_description")} />
</Head>
<CreateANewTeamForm />
</>
@ -17,4 +22,14 @@ const CreateNewTeamPage = () => {
CreateNewTeamPage.getLayout = getLayout;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default CreateNewTeamPage;

View File

@ -1,8 +1,12 @@
import { GetServerSidePropsContext } from "next";
import { TeamsListing } from "@calcom/features/ee/teams/components";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Shell } from "@calcom/ui";
import { ssrInit } from "@server/lib/ssr";
function Teams() {
const { t } = useLocale();
return (
@ -22,4 +26,14 @@ function Teams() {
Teams.requiresLicense = false;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};
export default Teams;

View File

@ -1 +1,15 @@
import { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
export { default } from "@calcom/features/ee/workflows/pages/index";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
return {
props: {
trpcState: ssr.dehydrate(),
},
};
};

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "عرض الإعدادات المتقدمة",
"event_name": "اسم الحدث",
"event_name_tooltip": "الاسم الذي سيظهر في التقاويم",
"meeting_with_user": "الاجتماع مع {ATTENDEE}",
"meeting_with_user": "الاجتماع مع {{attendeeName}}",
"additional_inputs": "مدخلات إضافية",
"additional_input_description": "يتطلب من المجدول إدخال مدخلات إضافية قبل تأكيد الحجز",
"label": "العلامة",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Zobrazit pokročilá nastavení",
"event_name": "Název události",
"event_name_tooltip": "Název, který se zobrazí v kalendářích",
"meeting_with_user": "Schůzka s {ATTENDEE}",
"meeting_with_user": "Schůzka s {{attendeeName}}",
"additional_inputs": "Další pole",
"additional_input_description": "Před potvrzením rezervace vyžadovat od plánující osoby zadání dalších vstupů",
"label": "Popisek",

View File

@ -142,7 +142,7 @@
"install_another": "Weitere installieren",
"until": "bis",
"powered_by": "betrieben von",
"unavailable": "Unverfügbar",
"unavailable": "Nicht verfügbar",
"set_work_schedule": "Arbeitszeiten festlegen",
"change_bookings_availability": "Verfügbarkeit für Buchungen ändern",
"select": "Auswählen...",
@ -604,6 +604,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Abrechnung der Teams",
"team_billing_description": "Verwalte die Abrechungen deines Teams",
"upgrade_to_flexible_pro_title": "Wir haben die Abrechnung für Teams geändert",
"upgrade_to_flexible_pro_message": "Es gibt Mitglieder in Ihrem Team ohne Lizenz. Upgraden Sie Ihren Pro-Plan um fehlende Lizenzen abzudecken.",
"changed_team_billing_info": "Ab Januar 2022 berechnen wir pro Sitzplatz für Teammitglieder. Mitglieder deines Teams, die Pro kostenlos hatten, sind jetzt in einer 14 Tage Testphase. Sobald die Testversion abläuft, werden diese Mitglieder vor Ihrem Team verborgen, es sei denn, Sie upgraden jetzt.",
@ -637,7 +638,7 @@
"show_advanced_settings": "Erweiterte Einstellungen anzeigen",
"event_name": "Termin-Name",
"event_name_tooltip": "Der Name, der in Kalendern angezeigt wird",
"meeting_with_user": "Meeting mit {ATTENDEE}",
"meeting_with_user": "Meeting mit {{attendeeName}}",
"additional_inputs": "Zusätzliche Notizen",
"additional_input_description": "Erfordert die Eingabe zusätzlicher Eingaben durch den Planer vor der Buchungsbestätigung",
"label": "Bezeichnung",
@ -688,6 +689,8 @@
"confirm_delete_account": "Ja, Account löschen",
"integrations": "Integrationen",
"apps": "Apps",
"apps_description": "Hier findest du eine Auflistung deiner Apps",
"apps_listing": "App-Liste",
"category_apps": "{{category}} Apps",
"app_store": "App Store",
"app_store_description": "Verbindet Menschen, Technologie und den Arbeitsplatz.",
@ -1119,7 +1122,11 @@
"invoices": "Rechnungen",
"embeds": "Embeds",
"impersonation": "Benutzeridentitätswechsel",
"impersonation_description": "Einstellungen um Benutzeridentitätswechsel zu verwalten",
"users": "Benutzer",
"profile_description": "Einstellungen für Ihr {{appName}} Profil verwalten",
"users_description": "Hier findest du eine Auflistung aller Benutzer",
"users_listing": "Benutzerliste",
"general_description": "Einstellungen für Ihre Sprache und Zeitzone verwalten",
"calendars_description": "Konfigurieren Sie, wie Ihre Event-Typen mit Ihren Kalendern interagieren sollen",
"appearance_description": "Einstellungen für Ihre Buchungsdarstellung verwalten",
@ -1293,6 +1300,10 @@
"saml_sp_acs_url_copied": "ACS-URL kopiert!",
"saml_sp_entity_id_copied": "SP-Entitäts-ID kopiert!",
"saml_btn_configure": "Konfigurieren",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
"free_to_use_apps": "Kostenlos",
"enterprise_license": "Das ist eine Enterprise-Funktion",
"enterprise_license_description": "Um diese Funktion zu aktivieren, holen Sie sich einen Deployment-Schlüssel von der {{consoleUrl}}-Konsole und fügen Sie ihn als CALCOM_LICENSE_KEY zu Ihrer .env hinzu. Wenn Ihr Team bereits eine Lizenz hat, wenden Sie sich bitte an {{supportMail}} für Hilfe.",
"add_calendar": "Kalender hinzufügen",
"limit_future_bookings": "Zukünftige Termine begrenzen",
"limit_future_bookings_description": "Begrenzt, wie weit in die Zukunft dieser Termin gebucht werden kann",

View File

@ -629,6 +629,7 @@
"teams": "Teams",
"team": "Team",
"team_billing": "Team Billing",
"team_billing_description": "Manage billing for your team",
"upgrade_to_flexible_pro_title": "We've changed billing for teams",
"upgrade_to_flexible_pro_message": "There are members in your team without a seat. Upgrade your pro plan to cover missing seats.",
"changed_team_billing_info": "As of January 2022 we charge on a per-seat basis for team members. Members of your team who had PRO for free are now on a 14 day trial. Once their trial expires these members will be hidden from your team unless you upgrade now.",
@ -663,7 +664,7 @@
"show_advanced_settings": "Show advanced settings",
"event_name": "Event Name",
"event_name_tooltip": "The name that will appear in calendars",
"meeting_with_user": "Meeting with {ATTENDEE}",
"meeting_with_user": "Meeting with {{attendeeName}}",
"additional_inputs": "Additional Inputs",
"additional_input_description": "Require scheduler to input additional inputs prior the booking is confirmed",
"label": "Label",
@ -724,6 +725,8 @@
"delete_account_confirmation_message": "Are you sure you want to delete your {{appName}} account? Anyone who you've shared your account link with will no longer be able to book using it and any preferences you have saved will be lost.",
"integrations": "Integrations",
"apps": "Apps",
"apps_description": "Here you can find a list of your apps",
"apps_listing": "App listing",
"category_apps": "{{category}} apps",
"app_store": "App Store",
"app_store_description": "Connecting people, technology and the workplace.",
@ -1176,12 +1179,15 @@
"invoices": "Invoices",
"embeds": "Embeds",
"impersonation": "Impersonation",
"impersonation_description": "Settings to manage user impersonation",
"users": "Users",
"profile_description": "Manage settings for your {{appName}} profile",
"users_description": "Here you can find a list of all users",
"users_listing": "User listing",
"general_description": "Manage settings for your language and timezone",
"calendars_description": "Configure how your event types interact with your calendars",
"appearance_description": "Manage settings for your booking appearance",
"conferencing_description": "Manage your video conferencing apps for your meetings",
"conferencing_description": "Add your favourite video conferencing apps for your meetings",
"password_description": "Manage settings for your account passwords",
"2fa_description": "Manage settings for your account passwords",
"we_just_need_basic_info": "We just need some basic info to get your profile setup.",
@ -1382,6 +1388,10 @@
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email",
"attendee_email_info": "The person booking's email",
"kbar_search_placeholder": "Type a command or search...",
"free_to_use_apps": "Free",
"enterprise_license": "This is an enterprise feature",
"enterprise_license_description": "To enable this feature, get a deployment key at {{consoleUrl}} console and add it to your .env as CALCOM_LICENSE_KEY. If your team already has a license, please contact {{supportMail}} for help.",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
"choose_common_schedule_team_event": "Choose a common schedule",
"choose_common_schedule_team_event_description": "Enable this if you want to use a common schedule between hosts. When disabled, each host will be booked based on their default schedule.",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Mostrar Opciones Avanzadas",
"event_name": "Nombre del Evento",
"event_name_tooltip": "Nombre que aparecerá en los calendarios",
"meeting_with_user": "Reunión con {ATTENDEE}",
"meeting_with_user": "Reunión con {{attendeeName}}",
"additional_inputs": "Campos Adicionales",
"additional_input_description": "Requerir que el planificador introduzca entradas adicionales antes de confirmar la reserva",
"label": "Etiqueta",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Afficher les paramètres avancés",
"event_name": "Nom de l'événement",
"event_name_tooltip": "Le nom qui apparaîtra dans les calendriers",
"meeting_with_user": "Rendez-vous avec {ATTENDEE}",
"meeting_with_user": "Rendez-vous avec {{attendeeName}}",
"additional_inputs": "Entrées additionnelles",
"additional_input_description": "Exiger que le planificateur saisisse des entrées supplémentaires avant que la réservation soit confirmée",
"label": "Libellé",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "הצגת הגדרות מתקדמות",
"event_name": "שם האירוע",
"event_name_tooltip": "השם שיופיע בלוחות השנה",
"meeting_with_user": "פגישה עם {ATTENDEE}",
"meeting_with_user": "פגישה עם {{attendeeName}}",
"additional_inputs": "אפשרויות קלט נוספות",
"additional_input_description": "לדרוש מכלי התזמון להוסיף פרטים לפני אישור ההזמנה",
"label": "תווית",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Mostra impostazioni avanzate",
"event_name": "Nome Dell'Evento",
"event_name_tooltip": "Il nome che apparirà nei calendari",
"meeting_with_user": "Riunione con {ATTENDEE}",
"meeting_with_user": "Riunione con {{attendeeName}}",
"additional_inputs": "Input Aggiuntivi",
"additional_input_description": "Chiedi al creatore del programma di inserire dei campi di input aggiuntivi prima che la prenotazione sia confermata",
"label": "Etichetta",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "詳細設定を表示",
"event_name": "イベント名",
"event_name_tooltip": "カレンダーに表示される名前",
"meeting_with_user": "{ATTENDEE} とのミーティング",
"meeting_with_user": "{{attendeeName}} とのミーティング",
"additional_inputs": "追加入力",
"additional_input_description": "予約確認前にスケジュール設定者の追加入力が必要です",
"label": "ラベル",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "고급 설정 표시",
"event_name": "이벤트 이름",
"event_name_tooltip": "캘린더에 표시될 이름",
"meeting_with_user": "{ATTENDEE} 님과의 회의",
"meeting_with_user": "{{attendeeName}} 님과의 회의",
"additional_inputs": "추가 입력",
"additional_input_description": "예약이 확정되기 전 스케줄러가 추가 입력을 해야 합니다",
"label": "레이블",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Toon geavanceerde instellingen",
"event_name": "Naam van evenement",
"event_name_tooltip": "De naam die in kalenders wordt weergegeven",
"meeting_with_user": "Afspraak met {ATTENDEE}",
"meeting_with_user": "Afspraak met {{attendeeName}}",
"additional_inputs": "Extra invoer",
"additional_input_description": "Planner vereist om extra gegevens in te voeren voordat de boeking wordt bevestigd",
"label": "Omschrijving",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Pokaż ustawienia zaawansowane",
"event_name": "Nazwa wydarzenia",
"event_name_tooltip": "Nazwa, która pojawi się w kalendarzach",
"meeting_with_user": "Spotkanie z {ATTENDEE}",
"meeting_with_user": "Spotkanie z {{attendeeName}}",
"additional_inputs": "Dodatkowe Wejścia",
"additional_input_description": "Wymagaj od osoby układającej harmonogram wprowadzania dodatkowych danych przed potwierdzeniem rezerwacji",
"label": "Etykieta",

View File

@ -654,7 +654,7 @@
"show_advanced_settings": "Mostrar Configurações Avançadas",
"event_name": "Nome do Evento",
"event_name_tooltip": "O nome que aparecerá nos calendários",
"meeting_with_user": "Reunião com {ATTENDEE}",
"meeting_with_user": "Reunião com {{attendeeName}}",
"additional_inputs": "Campos adicionais",
"additional_input_description": "Solicitar que o agendador insira entradas adicionais antes que o pedido seja confirmado",
"label": "Etiqueta",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Mostrar Configurações Avançadas",
"event_name": "Nome do Evento",
"event_name_tooltip": "O nome que será mostrado nos calendários",
"meeting_with_user": "Reunião com {ATTENDEE}",
"meeting_with_user": "Reunião com {{attendeeName}}",
"additional_inputs": "Campos adicionais",
"additional_input_description": "Requerer que o responsável pelo agendamento especifique entradas complementares antes do pedido ser confirmado",
"label": "Etiqueta",

View File

@ -654,7 +654,7 @@
"show_advanced_settings": "Afișează setările avansate",
"event_name": "Denumirea evenimentului",
"event_name_tooltip": "Numele care va apărea în calendare",
"meeting_with_user": "Întâlnire cu {ATTENDEE}",
"meeting_with_user": "Întâlnire cu {{attendeeName}}",
"additional_inputs": "Date suplimentare",
"additional_input_description": "Solicitați planificatorului să introducă intrări suplimentare înainte ca rezervarea să fie confirmată",
"label": "Eticheta",

View File

@ -654,7 +654,7 @@
"show_advanced_settings": "Показать дополнительные параметры",
"event_name": "Название события",
"event_name_tooltip": "Название, которое будет отображаться в календарях",
"meeting_with_user": "Встреча с {ATTENDEE}",
"meeting_with_user": "Встреча с {{attendeeName}}",
"additional_inputs": "Дополнительные вопросы",
"additional_input_description": "Необходимо ввести в планировщик дополнительную информацию до подтверждения бронирования",
"label": "Название",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Pokaži napredna podešavanja",
"event_name": "Ime Događaja",
"event_name_tooltip": "Ime koje će se pojaviti u kalendarima",
"meeting_with_user": "Sastanak sa {ATTENDEE}",
"meeting_with_user": "Sastanak sa {{attendeeName}}",
"additional_inputs": "Dodatni unosi",
"additional_input_description": "Zahtevaj od planera da unese dodatne unose pre potvrde rezervacije",
"label": "Oznaka",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Visa avancerade inställningar",
"event_name": "Händelsenamn",
"event_name_tooltip": "Namnet som kommer visas i kalendrar",
"meeting_with_user": "Möte med {ATTENDEE}",
"meeting_with_user": "Möte med {{attendeeName}}",
"additional_inputs": "Ytterligare inmatningar",
"additional_input_description": "Kräv schemaläggaren att mata in ytterligare ingångar innan bokningen bekräftas",
"label": "Etikett",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Gelişmiş ayarları göster",
"event_name": "Etkinlik Adı",
"event_name_tooltip": "Takvimlerde görünecek isim",
"meeting_with_user": "{ATTENDEE} ile toplantı",
"meeting_with_user": "{{attendeeName}} ile toplantı",
"additional_inputs": "Ek Girişler",
"additional_input_description": "Rezervasyon onaylanmadan önce planlayıcının ek veri girişi yapması gereklidir",
"label": "Etiket",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Показати додаткові параметри",
"event_name": "Тип заходу",
"event_name_tooltip": "Ім’я, яке показуватиметься в календарях",
"meeting_with_user": "Нарада з користувачем {ATTENDEE}",
"meeting_with_user": "Нарада з користувачем {{attendeeName}}",
"additional_inputs": "Додаткові поля введення",
"additional_input_description": "Вимагати від особи, що хоче створити бронювання, надавати додаткові відомості",
"label": "Мітка",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "Hiển thị cài đặt nâng cao",
"event_name": "Tên sự kiện",
"event_name_tooltip": "Tên sẽ xuất hiện trong lịch",
"meeting_with_user": "Gặp gỡ với {ATTENDEE}",
"meeting_with_user": "Gặp gỡ với {{attendeeName}}",
"additional_inputs": "Trường bổ sung",
"additional_input_description": "Yêu cầu người đặt lịch hãy nhập thông tin đầu vào bổ sung trước khi lịch hẹn được xác nhận",
"label": "Label",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "显示高级设置",
"event_name": "活动名称",
"event_name_tooltip": "在日历中显示的名称",
"meeting_with_user": "与 {ATTENDEE} 的会议",
"meeting_with_user": "与 {{attendeeName}} 的会议",
"additional_inputs": "更多输入框",
"additional_input_description": "在确认预约之前,需要预约者输入额外的信息",
"label": "标签",

View File

@ -653,7 +653,7 @@
"show_advanced_settings": "顯示進階設定",
"event_name": "活動名稱",
"event_name_tooltip": "會顯示在行事曆上的名字",
"meeting_with_user": "與 {ATTENDEE} 的會議",
"meeting_with_user": "與 {{attendeeName}} 的會議",
"additional_inputs": "額外輸入欄",
"additional_input_description": "確認預約前,預約者需先輸入額外的內容",
"label": "標籤",

View File

@ -60,6 +60,7 @@
},
"devDependencies": {
"@snaplet/copycat": "^0.3.0",
"@types/dompurify": "^2.4.0",
"@types/jest": "^28.1.7",
"dotenv-checker": "^1.1.5",
"husky": "^8.0.1",

View File

@ -3,6 +3,7 @@
* All new changes should be made to the V2 file in
* `/packages/features/ee/common/components/v2/LicenseRequired.tsx`
*/
import DOMPurify from "dompurify";
import { useSession } from "next-auth/react";
import React, { AriaRole, ComponentType, Fragment } from "react";
@ -35,18 +36,20 @@ const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) =
) : (
<EmptyScreen
Icon={Icon.FiAlertTriangle}
headline="This is an enterprise feature"
headline={t("enterprise_license")}
description={
<div
dangerouslySetInnerHTML={{
__html: t("enterprise_license_description", {
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
__html: DOMPurify.sanitize(
t("enterprise_license_description", {
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
${APP_NAME}
</a>`,
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
${SUPPORT_MAIL_ADDRESS}
</a>`,
}),
})
),
}}
/>
}

View File

@ -1,3 +1,4 @@
import DOMPurify from "dompurify";
import { useSession } from "next-auth/react";
import React, { AriaRole, ComponentType, Fragment } from "react";
@ -28,18 +29,20 @@ const LicenseRequired = ({ children, as = "", ...rest }: LicenseRequiredProps) =
) : (
<EmptyScreen
Icon={Icon.FiAlertTriangle}
headline="This is an enterprise feature"
headline={t("enterprise_license")}
description={
<div
dangerouslySetInnerHTML={{
__html: t("enterprise_license_description", {
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
__html: DOMPurify.sanitize(
t("enterprise_license_description", {
consoleUrl: `<a href="${CONSOLE_URL}" target="_blank" rel="noopener noreferrer" class="underline">
${APP_NAME}
</a>`,
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
supportMail: `<a href="mailto:${SUPPORT_MAIL_ADDRESS}" class="underline">
${SUPPORT_MAIL_ADDRESS}
</a>`,
}),
})
),
}}
/>
}

View File

@ -60,7 +60,7 @@ export default function SAMLConfiguration({ teamId }: { teamId: number | null })
};
if (isLoading) {
return <SkeletonLoader />;
return <SkeletonLoader title={t("saml_config")} description={t("saml_description")} />;
}
if (hasError) {

View File

@ -47,7 +47,7 @@ const ProfileView = () => {
return (
<>
<Meta title="Booking Appearance" description="Manage settings for your team's booking appearance" />
<Meta title={t("booking_appearance")} description={t("appearance_team_description")} />
{!isLoading && (
<>
{isAdmin ? (

View File

@ -9,10 +9,9 @@ const BillingView = () => {
const router = useRouter();
const returnTo = router.asPath;
const billingHref = `/api/integrations/stripepayment/portal?returnTo=${WEBAPP_URL}${returnTo}`;
return (
<>
<Meta title="Team Billing" description="Manage billing for your team" />
<Meta title={t("team_billing")} description={t("team_billing_description")} />
<div className="flex flex-col text-sm sm:flex-row">
<div>
<h2 className="font-medium">{t("billing_manage_details_title")}</h2>

View File

@ -46,7 +46,7 @@ const MembersView = () => {
return (
<>
<Meta title="Team Members" description="Users that are in the group" />
<Meta title={t("team_members")} description={t("members_team_description")} />
{!isLoading && (
<>
<div>

View File

@ -132,7 +132,7 @@ const ProfileView = () => {
return (
<>
<Meta title="Profile" description="Manage settings for your team profile" />
<Meta title={t("profile")} description={t("profile_team_description")} />
{!isLoading && (
<>
{isAdmin ? (

View File

@ -35,11 +35,11 @@ function WorkflowsPage() {
},
});
return session.data ? (
return (
<Shell
heading={data?.workflows.length ? t("workflows") : undefined}
heading={t("workflows")}
title={t("workflows")}
subtitle={data?.workflows.length ? t("workflows_to_automate_notifications") : ""}
subtitle={t("workflows_to_automate_notifications")}
CTA={
session.data?.hasValidLicense && data?.workflows && data?.workflows.length > 0 ? (
<Button
@ -62,8 +62,6 @@ function WorkflowsPage() {
)}
</LicenseRequired>
</Shell>
) : (
<></>
);
}

View File

@ -54,7 +54,7 @@ const EditWebhook = () => {
return (
<>
<Meta
title="Edit Webhook"
title={t("edit_webhook")}
description={t("add_webhook_description", { appName: APP_NAME })}
backButton
/>

View File

@ -62,7 +62,7 @@ const NewWebhookView = () => {
return (
<>
<Meta
title="Add Webhook"
title={t("add_webhook")}
description={t("add_webhook_description", { appName: APP_NAME })}
backButton
/>

View File

@ -52,151 +52,151 @@ export const KBarRoot = ({ children }: { children: React.ReactNode }) => {
{
id: "workflows",
name: "Workflows",
section: "Workflows",
name: "workflows",
section: "workflows",
shortcut: ["w", "f"],
keywords: "workflows automation",
perform: () => router.push("/workflows"),
},
{
id: "event-types",
name: "Event Types",
section: "Event Types",
name: "event_types_page_title",
section: "event_types_page_title",
shortcut: ["e", "t"],
keywords: "event types",
perform: () => router.push("/event-types"),
},
{
id: "app-store",
name: "App Store",
section: "Apps",
name: "app_store",
section: "apps",
shortcut: ["a", "s"],
keywords: "app store",
perform: () => router.push("/apps"),
},
{
id: "upcoming-bookings",
name: "Upcoming Bookings",
section: "Bookings",
name: "upcoming",
section: "bookings",
shortcut: ["u", "b"],
keywords: "upcoming bookings",
perform: () => router.push("/bookings/upcoming"),
},
{
id: "recurring-bookings",
name: "Recurring Bookings",
section: "Bookings",
name: "recurring",
section: "bookings",
shortcut: ["r", "b"],
keywords: "recurring bookings",
perform: () => router.push("/bookings/recurring"),
},
{
id: "past-bookings",
name: "Past Bookings",
section: "Bookings",
name: "past",
section: "bookings",
shortcut: ["p", "b"],
keywords: "past bookings",
perform: () => router.push("/bookings/past"),
},
{
id: "cancelled-bookings",
name: "Cancelled Bookings",
section: "Bookings",
name: "cancelled",
section: "bookings",
shortcut: ["c", "b"],
keywords: "cancelled bookings",
perform: () => router.push("/bookings/cancelled"),
},
{
id: "schedule",
name: "Schedule",
section: "Availability",
name: "availability",
section: "availability",
shortcut: ["s", "a"],
keywords: "schedule availability",
perform: () => router.push("/availability"),
},
{
id: "profile",
name: "Profile",
section: "Profile Settings",
name: "profile",
section: "profile",
shortcut: ["p", "s"],
keywords: "setting profile",
perform: () => router.push("/settings/profile"),
perform: () => router.push("/settings/my-account/profile"),
},
{
id: "avatar",
name: "Change Avatar",
section: "Profile Settings",
name: "change_avatar",
section: "profile",
shortcut: ["c", "a"],
keywords: "remove change modify avatar",
perform: () => router.push("/settings/profile"),
perform: () => router.push("/settings/my-account/profile"),
},
{
id: "timezone",
name: "Change Timezone",
section: "Profile Settings",
name: "timezone",
section: "profile",
shortcut: ["c", "t"],
keywords: "change modify timezone",
perform: () => router.push("/settings/my-account/general"),
},
{
id: "brand-color",
name: "Change Brand Color",
section: "Profile Settings",
name: "brand_color",
section: "profile",
shortcut: ["b", "c"],
keywords: "change modify brand color",
perform: () => router.push("/settings/my-account/appearance"),
},
{
id: "teams",
name: "Teams",
name: "teams",
shortcut: ["t", "s"],
keywords: "add manage modify team",
perform: () => router.push("/settings/teams"),
},
{
id: "password",
name: "Change Password",
section: "Security Settings",
name: "change_password",
section: "security",
shortcut: ["c", "p"],
keywords: "change modify password",
perform: () => router.push("/settings/security"),
perform: () => router.push("/settings/security/password"),
},
{
id: "two-factor",
name: "Two Factor Authentication",
section: "Security Settings",
name: "two_factor_auth",
section: "security",
shortcut: ["t", "f", "a"],
keywords: "two factor authentication",
perform: () => router.push("/settings/security/two-factor-auth"),
},
{
id: "impersonation",
name: "User Impersonation",
section: "Security Settings",
name: "user_impersonation_heading",
section: "security",
shortcut: ["u", "i"],
keywords: "user impersonation",
perform: () => router.push("/settings/security/impersonation"),
},
{
id: "webhooks",
name: "Webhook",
section: "Developer Settings",
name: "Webhooks",
section: "developer",
shortcut: ["w", "h"],
keywords: "webhook automation",
perform: () => router.push("/settings/developer/webhooks"),
},
{
id: "api-keys",
name: "API Keys",
section: "Developer Settings",
name: "api_keys",
section: "developer",
shortcut: ["a", "p", "i"],
keywords: "api keys",
perform: () => router.push("/settings/developer/api-keys"),
},
{
id: "billing",
name: "View and Manage Billing",
section: "Billing",
name: "manage_billing",
section: "billing",
shortcut: ["m", "b"],
keywords: "billing view manage",
perform: () => router.push("/settings/billing"),
@ -272,13 +272,14 @@ const DisplayShortcuts = (item: shortcutArrayType) => {
function RenderResults() {
const { results } = useMatches();
const { t } = useLocale();
return (
<KBarResults
items={results}
onRender={({ item, active }) =>
typeof item === "string" ? (
<div className="bg-white p-4 text-xs uppercase text-gray-500">{item}</div>
<div className="bg-white p-4 text-xs uppercase text-gray-500">{t(item)}</div>
) : (
<div
// For seeing keyboard up & down navigation in action, we need visual feedback based on "active" prop
@ -287,7 +288,7 @@ function RenderResults() {
borderLeft: active ? "2px solid black" : "2px solid transparent",
}}
className="flex items-center justify-between px-4 py-2.5 text-sm hover:cursor-pointer">
<span>{item.name}</span>
<span>{t(item.name)}</span>
<DisplayShortcuts shortcuts={item.shortcut} />
</div>
)

View File

@ -31,9 +31,9 @@ export default function EmptyScreen({
</div>
<div className="flex max-w-[420px] flex-col items-center">
<h2 className="text-semibold font-cal mt-6 text-xl dark:text-gray-300">{headline}</h2>
<p className="mt-3 mb-8 text-center text-sm font-normal leading-6 text-gray-700 dark:text-gray-300">
<div className="mt-3 mb-8 text-center text-sm font-normal leading-6 text-gray-700 dark:text-gray-300">
{description}
</p>
</div>
{buttonOnClick && buttonText && <Button onClick={(e) => buttonOnClick(e)}>{buttonText}</Button>}
{buttonRaw}
</div>

View File

@ -203,8 +203,6 @@ export default function Shell(props: LayoutProps) {
useRedirectToLoginIfUnauthenticated(props.isPublic);
useRedirectToOnboardingIfNeeded();
useTheme("light");
const { session } = useRedirectToLoginIfUnauthenticated(props.isPublic);
if (!session && !props.isPublic) return null;
return (
<KBarRoot>

View File

@ -1,10 +1,21 @@
import Meta from "../Meta";
import { ShellSubHeading } from "../Shell";
import { SkeletonText } from "../skeleton";
function SkeletonLoader({ className }: { className?: string }) {
function SkeletonLoader({
className,
title,
description,
}: {
className?: string;
title?: string;
description?: string;
}) {
return (
<>
<ShellSubHeading title={<div className="h-6 w-32 bg-gray-100" />} {...{ className }} />
{title && description && <Meta title={title} description={description} />}
<ul className="-mx-4 animate-pulse divide-y divide-neutral-200 rounded-md border border-gray-200 bg-white sm:mx-0 sm:overflow-hidden">
<SkeletonItem />
<SkeletonItem />

View File

@ -122,7 +122,7 @@ const SettingsSidebarContainer = ({ className = "" }) => {
<>
<div className="desktop-only pt-4" />
<VerticalTabItem
name="Back"
name={t("back")}
href="/."
icon={Icon.FiArrowLeft}
textClassNames="text-md font-medium leading-none text-black"
@ -327,6 +327,7 @@ export default function SettingsLayout({
return (
<Shell
withoutSeo={true}
flexChildrenContainer
{...rest}
SidebarContainer={<SettingsSidebarContainer className="hidden lg:flex" />}

View File

@ -7861,6 +7861,13 @@
resolved "https://registry.yarnpkg.com/@types/detect-port/-/detect-port-1.3.2.tgz#8c06a975e472803b931ee73740aeebd0a2eb27ae"
integrity sha512-xxgAGA2SAU4111QefXPSp5eGbDm/hW6zhvYl9IeEPZEry9F4d66QAHm5qpUXjb6IsevZV/7emAEx5MhP6O192g==
"@types/dompurify@^2.4.0":
version "2.4.0"
resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.4.0.tgz#fd9706392a88e0e0e6d367f3588482d817df0ab9"
integrity sha512-IDBwO5IZhrKvHFUl+clZxgf3hn2b/lU6H1KaBShPkQyGJUQ0xwebezIPSuiyGwfz1UzJWQl4M7BDxtHtCCPlTg==
dependencies:
"@types/trusted-types" "*"
"@types/eslint-scope@^3.7.3":
version "3.7.4"
resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.4.tgz#37fc1223f0786c39627068a12e94d6e6fc61de16"
@ -8383,7 +8390,7 @@
resolved "https://registry.yarnpkg.com/@types/tough-cookie/-/tough-cookie-4.0.2.tgz#6286b4c7228d58ab7866d19716f3696e03a09397"
integrity sha512-Q5vtl1W5ue16D+nIaW8JWebSSraJVlK+EthKn7e7UcD4KWsaSJ8BqGPXNaPghgtcn/fhvrN17Tv8ksUsQpiplw==
"@types/trusted-types@^2.0.2":
"@types/trusted-types@*", "@types/trusted-types@^2.0.2":
version "2.0.2"
resolved "https://registry.yarnpkg.com/@types/trusted-types/-/trusted-types-2.0.2.tgz#fc25ad9943bcac11cceb8168db4f275e0e72e756"
integrity sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==