diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx index 948ba5313b..b8f77ac56f 100644 --- a/apps/web/components/booking/BookingListItem.tsx +++ b/apps/web/components/booking/BookingListItem.tsx @@ -248,7 +248,7 @@ function BookingListItem(booking: BookingItemProps) { - + @@ -275,11 +275,6 @@ function BookingListItem(booking: BookingItemProps) { {booking.eventType.team.name} )} - {!!booking?.eventType?.price && !booking.paid && ( - - {t("pending_payment")} - - )} {booking.paid && ( {t("paid")} @@ -343,7 +338,9 @@ function BookingListItem(booking: BookingItemProps) { {!!booking?.eventType?.price && !booking.paid && ( - Pending payment + + {t("pending_payment")} + )} {booking.description && ( @@ -531,13 +528,4 @@ const DisplayAttendees = ({ ); }; -const Tag = ({ children, className = "" }: React.PropsWithChildren<{ className?: string }>) => { - return ( - - {children} - - ); -}; - export default BookingListItem; diff --git a/apps/web/pages/auth/setup/index.tsx b/apps/web/pages/auth/setup/index.tsx index c85d53544f..dfc472042d 100644 --- a/apps/web/pages/auth/setup/index.tsx +++ b/apps/web/pages/auth/setup/index.tsx @@ -1,6 +1,9 @@ +import { UserPermissionRole } from "@prisma/client"; +import { GetServerSidePropsContext } from "next"; import { useState } from "react"; import AdminAppsList from "@calcom/features/apps/AdminAppsList"; +import { getSession } from "@calcom/lib/auth"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import prisma from "@calcom/prisma"; import { inferSSRProps } from "@calcom/types/inferSSRProps"; @@ -32,14 +35,33 @@ export default function Setup(props: inferSSRProps) { return ( <>
- + t("current_step_of_total", { currentStep, maxSteps })} + />
); } -export const getServerSideProps = async () => { +export const getServerSideProps = async (context: GetServerSidePropsContext) => { const userCount = await prisma.user.count(); + const { req } = context; + const session = await getSession({ req }); + + if (session?.user.role && session?.user.role !== UserPermissionRole.ADMIN) { + return { + redirect: { + destination: `/404`, + permanent: false, + }, + }; + } + return { props: { userCount, diff --git a/apps/web/pages/event-types/index.tsx b/apps/web/pages/event-types/index.tsx index d8545e1fc8..b8557c93c8 100644 --- a/apps/web/pages/event-types/index.tsx +++ b/apps/web/pages/event-types/index.tsx @@ -258,7 +258,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL const calLink = `${CAL_URL}/${embedLink}`; return (
  • -
    +
    {!(firstItem && firstItem.id === type.id) && ( diff --git a/apps/web/public/static/locales/en/common.json b/apps/web/public/static/locales/en/common.json index e2304630ed..5cee39e7a4 100644 --- a/apps/web/public/static/locales/en/common.json +++ b/apps/web/public/static/locales/en/common.json @@ -1461,6 +1461,8 @@ "event_type_duplicate_copy_text": "{{slug}}-copy", "set_as_default": "Set as default", "hide_eventtype_details": "Hide EventType Details", + "show_navigation": "Show navigation", + "hide_navigation": "Hide navigation", "verification_code_sent": "Verification code sent", "verified_successfully": "Verified successfully", "wrong_code": "Wong verification code", diff --git a/packages/features/ee/teams/components/CreateANewTeamForm.tsx b/packages/features/ee/teams/components/CreateANewTeamForm.tsx index a3479de4ae..e8f6353d61 100644 --- a/packages/features/ee/teams/components/CreateANewTeamForm.tsx +++ b/packages/features/ee/teams/components/CreateANewTeamForm.tsx @@ -9,17 +9,17 @@ import { Avatar, Button, Form, Icon, ImageUploader, TextField } from "@calcom/ui import { NewTeamFormValues } from "../lib/types"; -const querySchema = z.optional(z.string()); +const querySchema = z.object({ + returnTo: z.string(), +}); export const CreateANewTeamForm = () => { const { t } = useLocale(); const router = useRouter(); - const { - query: { returnTo }, - } = router; - const returnToParsed = querySchema.safeParse(returnTo); - const returnToParam = returnToParsed.success ? returnToParsed.data : "/settings/teams"; + const returnToParsed = querySchema.safeParse(router.query); + + const returnToParam = returnToParsed.success ? returnToParsed.data.returnTo : "/settings/teams"; const newTeamFormMethods = useForm(); diff --git a/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx b/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx index 2142dbad04..fff6dd339a 100644 --- a/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx +++ b/packages/features/ee/teams/components/TeamsUpgradeBanner.tsx @@ -16,9 +16,11 @@ export function TeamsUpgradeBanner() { showToast(error.message, "error"); }, }); + if (!data) return null; const [membership] = data; if (!membership) return null; + return ( { return ( <> - + setShowMemberInvitationModal(true)} + data-testid="new-member-button"> + {t("add")} + + ) : ( + <> + ) + } + /> {!isLoading && ( <>
    @@ -68,19 +86,6 @@ const MembersView = () => { )} )} - {isAdmin && ( -
    - -
    - )}
      {team?.members.map((member) => { diff --git a/packages/features/ee/workflows/lib/getOptions.ts b/packages/features/ee/workflows/lib/getOptions.ts index c3059e74e1..557bbf93ab 100644 --- a/packages/features/ee/workflows/lib/getOptions.ts +++ b/packages/features/ee/workflows/lib/getOptions.ts @@ -4,17 +4,18 @@ import { TFunction } from "next-i18next"; import { TIME_UNIT, WORKFLOW_ACTIONS, WORKFLOW_TEMPLATES, WORKFLOW_TRIGGER_EVENTS } from "./constants"; export function getWorkflowActionOptions(t: TFunction, isTeamsPlan?: boolean) { - return WORKFLOW_ACTIONS.map((action) => { - const actionString = t(`${action.toLowerCase()}_action`); + return WORKFLOW_ACTIONS.filter((action) => action !== WorkflowActions.EMAIL_ADDRESS) //removing EMAIL_ADDRESS for now due to abuse episode + .map((action) => { + const actionString = t(`${action.toLowerCase()}_action`); - const isSMSAction = action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER; + const isSMSAction = action === WorkflowActions.SMS_ATTENDEE || action === WorkflowActions.SMS_NUMBER; - return { - label: actionString.charAt(0).toUpperCase() + actionString.slice(1), - value: action, - disabled: isSMSAction && !isTeamsPlan, - }; - }); + return { + label: actionString.charAt(0).toUpperCase() + actionString.slice(1), + value: action, + disabled: isSMSAction && !isTeamsPlan, + }; + }); } export function getWorkflowTriggerOptions(t: TFunction) { diff --git a/packages/lib/hooks/useTypedQuery.ts b/packages/lib/hooks/useTypedQuery.ts index 595ec37d32..97246781fc 100644 --- a/packages/lib/hooks/useTypedQuery.ts +++ b/packages/lib/hooks/useTypedQuery.ts @@ -2,6 +2,10 @@ import { useRouter } from "next/router"; import { useCallback, useMemo } from "react"; import { z } from "zod"; +type OptionalKeys = { [K in keyof T]-?: Record extends Pick ? K : never }[keyof T]; + +type FilteredKeys = { [K in keyof T as T[K] extends U ? K : never]: T[K] }; + // Take array as a string and return zod array export const queryNumberArray = z .string() @@ -20,22 +24,19 @@ export const queryStringArray = z .preprocess((a) => z.string().parse(a).split(","), z.string().array()) .or(z.string().array()); -export function useTypedQuery(schema: T) { - type InferedSchema = z.infer; - type SchemaKeys = keyof InferedSchema; - type OptionalKeys = { - [K in keyof InferedSchema]: undefined extends InferedSchema[K] ? K : never; - }[keyof InferedSchema]; - - type ArrayOnlyKeys = { - [K in keyof InferedSchema]: InferedSchema[K] extends Array & undefined ? K : never; - }; +export function useTypedQuery(schema: T) { + type Output = z.infer; + type FullOutput = Required; + type OutputKeys = Required; + type OutputOptionalKeys = OptionalKeys; + type ArrayOutput = FilteredKeys>; + type ArrayOutputKeys = keyof ArrayOutput; const { query: unparsedQuery, ...router } = useRouter(); const parsedQuerySchema = schema.safeParse(unparsedQuery); - let parsedQuery: InferedSchema = useMemo(() => { - return {} as InferedSchema; + let parsedQuery: Output = useMemo(() => { + return {} as Output; }, []); if (parsedQuerySchema.success) parsedQuery = parsedQuerySchema.data; @@ -43,29 +44,24 @@ export function useTypedQuery(schema: T) { // Set the query based on schema values const setQuery = useCallback( - function setQuery(key: J, value: Partial) { + function setQuery(key: J, value: Output[J]) { // Remove old value by key so we can merge new value const { [key]: _, ...newQuery } = parsedQuery; const newValue = { ...newQuery, [key]: value }; - const search = new URLSearchParams(newValue).toString(); + const search = new URLSearchParams(newValue as any).toString(); router.replace({ query: search }, undefined, { shallow: true }); }, [parsedQuery, router] ); // Delete a key from the query - function removeByKey(key: OptionalKeys) { + function removeByKey(key: OutputOptionalKeys) { const { [key]: _, ...newQuery } = parsedQuery; - router.replace({ query: newQuery }, undefined, { shallow: true }); + router.replace({ query: newQuery as Output }, undefined, { shallow: true }); } // push item to existing key - function pushItemToKey( - key: J, - value: InferedSchema[J] extends Array | undefined - ? NonNullable[number] - : NonNullable - ) { + function pushItemToKey(key: J, value: ArrayOutput[J][number]) { const existingValue = parsedQuery[key]; if (Array.isArray(existingValue)) { if (existingValue.includes(value)) return; // prevent adding the same value to the array @@ -76,21 +72,14 @@ export function useTypedQuery(schema: T) { } // Remove item by key and value - function removeItemByKeyAndValue( - key: J, - value: InferedSchema[J] extends Array | undefined - ? NonNullable[number] - : NonNullable - ) { + function removeItemByKeyAndValue(key: J, value: ArrayOutput[J][number]) { const existingValue = parsedQuery[key]; - console.log(existingValue); - const newValue = existingValue.filter((item: InferedSchema[J][number]) => item !== value); - if (Array.isArray(existingValue) && newValue.length > 0) { - setQuery(key, value); + if (Array.isArray(existingValue)) { + // @ts-expect-error this is too much for TS it seems + const newValue = existingValue.filter((item) => item !== value); + setQuery(key, newValue); } else { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - we know the key is optional but i can't figure out for the life of me - // how to support it in the type + // @ts-expect-error this is too much for TS it seems removeByKey(key); } } diff --git a/packages/prisma/migrations/20221214210020_set_seats_show_attendees_to_default_false/migration.sql b/packages/prisma/migrations/20221214210020_set_seats_show_attendees_to_default_false/migration.sql new file mode 100644 index 0000000000..59a31715d9 --- /dev/null +++ b/packages/prisma/migrations/20221214210020_set_seats_show_attendees_to_default_false/migration.sql @@ -0,0 +1,2 @@ +-- AlterTable +ALTER TABLE "EventType" ALTER COLUMN "seatsShowAttendees" SET DEFAULT false; diff --git a/packages/prisma/schema.prisma b/packages/prisma/schema.prisma index 97b8b147e8..16629d23b4 100644 --- a/packages/prisma/schema.prisma +++ b/packages/prisma/schema.prisma @@ -68,7 +68,7 @@ model EventType { beforeEventBuffer Int @default(0) afterEventBuffer Int @default(0) seatsPerTimeSlot Int? - seatsShowAttendees Boolean? + seatsShowAttendees Boolean? @default(false) schedulingType SchedulingType? schedule Schedule? @relation(fields: [scheduleId], references: [id]) scheduleId Int? diff --git a/packages/ui/components/badge/Badge.tsx b/packages/ui/components/badge/Badge.tsx index d6c35bcc57..6d4ba7762e 100644 --- a/packages/ui/components/badge/Badge.tsx +++ b/packages/ui/components/badge/Badge.tsx @@ -9,7 +9,7 @@ const badgeClassNameByVariant = { orange: "bg-orange-100 text-orange-800", success: "bg-green-100 text-green-800", green: "bg-green-100 text-green-800", - gray: "bg-gray-100 text-gray-800 dark:bg-transparent dark:text-darkgray-800", + gray: "bg-gray-100 text-gray-800 dark:bg-transparent dark:text-darkgray-800 group-hover:bg-gray-200", blue: "bg-blue-100 text-blue-800", red: "bg-red-100 text-red-800", error: "bg-red-100 text-red-800", diff --git a/packages/ui/components/top-banner/TopBanner.tsx b/packages/ui/components/top-banner/TopBanner.tsx index 9714ace906..8f5cf0b19f 100644 --- a/packages/ui/components/top-banner/TopBanner.tsx +++ b/packages/ui/components/top-banner/TopBanner.tsx @@ -4,6 +4,8 @@ import classNames from "classnames"; import noop from "lodash/noop"; import { ReactNode } from "react"; +import { Icon } from "@calcom/ui"; + export type TopBannerProps = { text: string; variant?: keyof typeof variantClassName; @@ -23,17 +25,20 @@ export function TopBanner(props: TopBannerProps) {

      - {["warning", "error"].includes(variant) && ( -

      - {actions &&
      {actions}
      } + {actions &&
      {actions}
      }
      {typeof onClose === "function" && ( + + +
      + + +
      + + {/* logo icon for tablet */} - - + + -
      - - - + + +
      + + {isCalcom && } + +
      +
      + + + + + +
      - - -
      - - {/* logo icon for tablet */} - - - - - - - -
      - - {isCalcom && } - {/* Save it for next preview version -
      - -
      */} - -
      - - - - - - -
      - - + +
    + +
    ); } @@ -855,35 +853,17 @@ export function ShellMain(props: LayoutProps) { ); } -const SettingsSidebarContainerDefault = () => null; - function MainContainer({ - SettingsSidebarContainer: SettingsSidebarContainerProp = , MobileNavigationContainer: MobileNavigationContainerProp = , TopNavContainer: TopNavContainerProp = , ...props }: LayoutProps) { - const [sideContainerOpen, setSideContainerOpen] = props.drawerState || [false, noop]; - return ( -
    +
    {/* show top navigation for md and smaller (tablet and phones) */} {TopNavContainerProp} - {/* The following is used for settings navigation on medium and smaller screens */} -
    { - setSideContainerOpen(false); - }} - /> - {SettingsSidebarContainerProp}
    - {/* add padding to top for mobile when App Bar is fixed */} -
    {!props.withoutMain ? {props.children} : props.children} {/* show bottom navigation for md and smaller (tablet and phones) on pages where back button doesn't exist */} @@ -906,7 +886,7 @@ function TopNav() { <>
    )} @@ -76,7 +76,7 @@ function WizardForm(props: {
    {!props.disableNavigation && (
    - +
    )}
    diff --git a/packages/ui/v2/core/layouts/SettingsLayout.tsx b/packages/ui/v2/core/layouts/SettingsLayout.tsx index c71299b38d..a92c386a36 100644 --- a/packages/ui/v2/core/layouts/SettingsLayout.tsx +++ b/packages/ui/v2/core/layouts/SettingsLayout.tsx @@ -100,7 +100,15 @@ const useTabs = () => { }); }; -const SettingsSidebarContainer = ({ className = "" }) => { +interface SettingsSidebarContainerProps { + className?: string; + navigationIsOpenedOnMobile?: boolean; +} + +const SettingsSidebarContainer = ({ + className = "", + navigationIsOpenedOnMobile, +}: SettingsSidebarContainerProps) => { const { t } = useLocale(); const router = useRouter(); const tabsWithPermissions = useTabs(); @@ -127,7 +135,13 @@ const SettingsSidebarContainer = ({ className = "" }) => { return (