Compare commits

..
14 Commits
Author SHA1 Message Date
GitHub Actions 214528ffa8 chore: release v5.1.7
Post release / release (push) Canceled after 0s
2025-03-25 00:23:18 +00:00
Keith WilliamsandAlex van Andel 7f79779bf8 chore: Upgrade jackson (#20107)
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2025-03-25 00:18:26 +00:00
GitHub Actions ed0f8fe0e7 chore: release v5.1.6
Post release / release (push) Canceled after 0s
2025-03-24 23:23:16 +00:00
Benny Joo 273fc544e7 feat: rewrite all routes starting with supported lang code (#20346)
* feat: rewrite all routes starting with matching locale code

* update e2e test
2025-03-24 23:15:39 +00:00
Joe Au-Yeung f24e8d12f0 feat: Salesforce - handle picklist field type (#20342)
* Add picklist to `SalesforceFieldType` enum

* Add picklist value to Salesforce settings

* Add `getPicklistFieldValue` method

* Find picklist option based on active and value
2025-03-24 23:05:30 +00:00
Omar López 19712f62e3 feat: improved observability on api v1 and webapp (#20302)
* feat: associate errors to users on api v1

* Added webapp

* Update layout.tsx

* Update captureUserId.ts
2025-03-24 23:01:20 +00:00
Benny JooandAnik Dhabal Babu 0ec701f9e6 fix: rewrite /en/* paths to /* (#20331)
* fix: add back i18n config to next.config.js

* add back localeDetection

* rewrite /en/* paths to /

* add e2e test

* add comment

* add test

---------

Co-authored-by: Anik Dhabal Babu <81948346+anikdhabal@users.noreply.github.com>
2025-03-24 16:50:55 -03:00
sean-brydon e4cfaa56f0 no throw 202 (#20339) 2025-03-24 16:48:38 +00:00
c7641f852f feat: Added org username to admin users view (#20254)
* feat : Added org username to admin users view

* styling tweaks (#20215)

* minor-changes

---------

Co-authored-by: Tushar Bhatt <95581504+TusharBhatt1@users.noreply.github.com>
Co-authored-by: Tushar <tusharbhatt0135@gmail.com>
2025-03-24 15:03:58 +00:00
Ayanava Karmakar e2597fbcd2 docs: fix invalid JSX in code snippets in platform/guides/booking-redirects (#20321) 2025-03-24 14:32:02 +00:00
Zayd Krunz ce88018ecc Update aws.mdx (#20330) 2025-03-24 14:31:01 +00:00
Vijay 0eb4e6c7d4 explicitly specify default visibility value till initializes (#20312) 2025-03-24 14:25:56 +00:00
Keith Williams 10c9c63ad8 revert: "fix: lint (#20325)" (#20337)
This reverts commit 1dc63e0df7.
2025-03-24 11:02:04 -03:00
Omar López a18defaa36 fix: increase memory requirement for building atoms (#20306) 2025-03-24 10:50:35 -03:00
46 changed files with 2018 additions and 1444 deletions
+1 -1
View File
@@ -14,4 +14,4 @@ jobs:
- uses: ./.github/actions/yarn-install
- name: Run Lint
run: yarn lint
continue-on-error: false
continue-on-error: true
View File
+2
View File
@@ -0,0 +1,2 @@
undecided:
- "@calcom/prisma"
+8
View File
@@ -0,0 +1,8 @@
import { setUser as SentrySetUser } from "@sentry/nextjs";
import type { NextMiddleware } from "next-api-middleware";
export const captureUserId: NextMiddleware = async (req, res, next) => {
if (req.userId) SentrySetUser({ id: req.userId });
await next();
};
+7 -4
View File
@@ -2,15 +2,16 @@ import { label } from "next-api-middleware";
import { addRequestId } from "./addRequestid";
import { captureErrors } from "./captureErrors";
import { captureUserId } from "./captureUserId";
import { checkIsInMaintenanceMode } from "./checkIsInMaintenanceMode";
import { extendRequest } from "./extendRequest";
import {
HTTP_POST,
HTTP_DELETE,
HTTP_PATCH,
HTTP_GET,
HTTP_GET_OR_POST,
HTTP_GET_DELETE_PATCH,
HTTP_GET_OR_POST,
HTTP_PATCH,
HTTP_POST,
} from "./httpMethods";
import { rateLimitApiKey } from "./rateLimitApiKey";
import { verifyApiKey } from "./verifyApiKey";
@@ -31,6 +32,7 @@ const middleware = {
extendRequest,
pagination: withPagination,
captureErrors,
captureUserId,
verifyCredentialSyncEnabled,
};
@@ -44,8 +46,9 @@ const middlewareOrder = [
"verifyApiKey",
"rateLimitApiKey",
"addRequestId",
"captureUserId",
] as Middleware[]; // <-- Provide a list of middleware to call automatically
const withMiddleware = label(middleware, middlewareOrder);
export { withMiddleware, middleware, middlewareOrder };
export { middleware, middlewareOrder, withMiddleware };
@@ -1,6 +1,16 @@
import { setUser as SentrySetUser } from "@sentry/nextjs";
import { cookies, headers } from "next/headers";
import React from "react";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import Shell from "@calcom/features/shell/Shell";
const Layout = ({ children }: { children: React.ReactNode }) => {
import { buildLegacyRequest } from "@lib/buildLegacyCtx";
const Layout = async ({ children }: { children: React.ReactNode }) => {
const session = await getServerSession({ req: buildLegacyRequest(await headers(), await cookies()) });
if (session?.user?.id) SentrySetUser({ id: session.user.id });
return <Shell withoutMain={true}>{children}</Shell>;
};
+3 -1
View File
@@ -150,7 +150,9 @@ export default async function RootLayout({ children }: { children: React.ReactNo
// - Tells iframe which mode it should be in (dark/light) - if there is a a UI instruction for that
visibility: "hidden",
}
: {}
: {
visibility: "visible",
}
}>
<IconSprites />
{!!process.env.NEXT_PUBLIC_BODY_SCRIPTS && (
+2 -2
View File
@@ -20,9 +20,9 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTelemetry } from "@calcom/lib/hooks/useTelemetry";
import { collectPageParameters, telemetryEventTypes } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import { EmailField } from "@calcom/ui/components/form";
import { Alert } from "@calcom/ui/components/alert";
import { Button } from "@calcom/ui/components/button";
import { EmailField } from "@calcom/ui/components/form";
import { PasswordField } from "@calcom/ui/components/form";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
@@ -184,7 +184,7 @@ PageProps & WithNonceProps<{}>) {
: isSAMLLoginEnabled && !isPending && data?.connectionExists;
return (
<div className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand:#111827] [--cal-brand-subtle:#9CA3AF] [--cal-brand-emphasis:#101010] [--cal-brand-text:white] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand-text:black] dark:[--cal-brand:white]">
<div className="dark:bg-brand dark:text-brand-contrast text-emphasis min-h-screen [--cal-brand-emphasis:#101010] [--cal-brand-subtle:#9CA3AF] [--cal-brand-text:white] [--cal-brand:#111827] dark:[--cal-brand-emphasis:#e1e1e1] dark:[--cal-brand-text:black] dark:[--cal-brand:white]">
<AuthContainer
showLogo
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
@@ -58,7 +58,6 @@ import { HorizontalTabs } from "@calcom/ui/components/navigation";
import { Skeleton } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";
import { Tooltip } from "@calcom/ui/components/tooltip";
import ServerTrans from "@calcom/web/components/ServerTrans";
import useMeQuery from "@lib/hooks/useMeQuery";
@@ -1,7 +1,7 @@
"use client";
import { signOut } from "next-auth/react";
import type { TFunction } from "i18next";
import { signOut } from "next-auth/react";
import { usePathname, useRouter } from "next/navigation";
import { Suspense } from "react";
import { Toaster } from "sonner";
@@ -12,12 +12,12 @@ import { localeOptions } from "@calcom/lib/i18n";
import { nameOfDay } from "@calcom/lib/weekday";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import { Form } from "@calcom/ui/components/form";
import { Label } from "@calcom/ui/components/form";
import { Select } from "@calcom/ui/components/form";
import { SettingsToggle } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import classNames from "@calcom/ui/classNames";
import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";
+2 -2
View File
@@ -6,9 +6,9 @@ import Shell from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Button } from "@calcom/ui/components/button";
import { EmptyScreen } from "@calcom/ui/components/empty-screen";
import { showToast } from "@calcom/ui/components/toast";
import { Button } from "@calcom/ui/components/button";
export type OrgUpgradeBannerProps = {
data: RouterOutputs["viewer"]["me"]["getUserTopBanners"]["orgUpgradeBanner"];
@@ -30,7 +30,7 @@ export default function UpgradePage() {
const doesUserHaveOrgToUpgrade = trpc.viewer.organizations.checkIfOrgNeedsUpgrade.useQuery();
return (
<Shell >
<Shell>
<div className="max-w-screen-lg">
{doesUserHaveOrgToUpgrade.data ? (
<EmptyScreen
+8
View File
@@ -5,6 +5,9 @@ const englishTranslation = require("./public/static/locales/en/common.json");
const { withAxiom } = require("next-axiom");
const { withSentryConfig } = require("@sentry/nextjs");
const { version } = require("./package.json");
const {
i18n: { locales },
} = require("./next-i18next.config");
const {
nextJsOrgRewriteConfig,
orgUserRoutePath,
@@ -288,6 +291,11 @@ const nextConfig = {
async rewrites() {
const { orgSlug } = nextJsOrgRewriteConfig;
const beforeFiles = [
{
// This should be the first item in `beforeFiles` to take precedence over other rewrites
source: `/(${locales.join("|")})/:path*`,
destination: "/:path*",
},
{
source: "/forms/:formQuery*",
destination: "/apps/routing-forms/routing-link/:formQuery*",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "5.1.5",
"version": "5.1.7",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -27,7 +27,7 @@
"yarn": "3.4.1"
},
"dependencies": {
"@boxyhq/saml-jackson": "1.18.6",
"@boxyhq/saml-jackson": "1.42.0",
"@calcom/app-store": "*",
"@calcom/app-store-cli": "*",
"@calcom/dayjs": "*",
+31
View File
@@ -0,0 +1,31 @@
import { expect } from "@playwright/test";
import { test } from "./lib/fixtures";
test.describe("Locale-specific pages must not 404", () => {
for (const locale of ["en", "fr", "es"]) {
test.describe(`/${locale}/* pages`, () => {
test(`/${locale}/login shouldn't 404`, async ({ page }) => {
const response = await page.goto(`/${locale}/login`);
expect(response?.status()).not.toBe(404);
});
test(`/${locale}/auth/login shouldn't 404`, async ({ page }) => {
const response = await page.goto(`/${locale}/auth/login`);
expect(response?.status()).not.toBe(404);
});
test(`/${locale}/[user] page shouldn't 404`, async ({ page, users }) => {
const user = await users.create();
const response = await page.goto(`/${locale}/${user.username}`);
expect(response?.status()).not.toBe(404);
});
test(`/${locale}/[user]/[type] page shouldn't 404`, async ({ page, users }) => {
const user = await users.create();
const response = await page.goto(`/${locale}/${user.username}/30-min`);
expect(response?.status()).not.toBe(404);
});
});
}
});
+48 -35
View File
@@ -14,16 +14,24 @@ test.afterEach(async ({ users }) => {
await users.deleteAll();
});
const assertChecks = async (page: any, entityName: string, entityType: string) => {
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(entityName)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description(entityType)}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
};
test.describe("Unpublished", () => {
test("Regular team profile", async ({ page, users }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true });
const { team } = await owner.getFirstTeamMembership();
const { requestedSlug } = team.metadata as { requestedSlug: string };
await page.goto(`/team/${requestedSlug}`);
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(team.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("team")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
const prefixes = ["", "/en"];
for (const prefix of prefixes) {
await page.goto(`${prefix}/team/${requestedSlug}`);
await assertChecks(page, team.name, "team");
}
});
test("Regular team event type", async ({ page, users }) => {
@@ -35,22 +43,24 @@ test.describe("Unpublished", () => {
const { team } = await owner.getFirstTeamMembership();
const { requestedSlug } = team.metadata as { requestedSlug: string };
const { slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
await page.goto(`/team/${requestedSlug}/${teamEventSlug}`);
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(team.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("team")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
const prefixes = ["", "/en"];
for (const prefix of prefixes) {
await page.goto(`${prefix}/team/${requestedSlug}/${teamEventSlug}`);
await assertChecks(page, team.name, "team");
}
});
test("Organization profile", async ({ users, page }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
await page.goto(`/org/${requestedSlug}`);
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(org.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("organization")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
const prefixes = ["", "/en"];
for (const prefix of prefixes) {
await page.goto(`${prefix}/org/${requestedSlug}`);
await assertChecks(page, org.name, "organization");
}
});
test("Organization sub-team", async ({ users, page }) => {
@@ -63,11 +73,12 @@ test.describe("Unpublished", () => {
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
const [{ slug: subteamSlug }] = org.children as { slug: string }[];
await page.goto(`/org/${requestedSlug}/team/${subteamSlug}`);
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(org.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("organization")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
const prefixes = ["", "/en"];
for (const prefix of prefixes) {
await page.goto(`${prefix}/org/${requestedSlug}/team/${subteamSlug}`);
await assertChecks(page, org.name, "organization");
}
});
test("Organization sub-team event-type", async ({ users, page }) => {
@@ -81,23 +92,24 @@ test.describe("Unpublished", () => {
const { requestedSlug } = org.metadata as { requestedSlug: string };
const [{ slug: subteamSlug, id: subteamId }] = org.children as { slug: string; id: number }[];
const { slug: subteamEventSlug } = await owner.getFirstTeamEvent(subteamId);
await page.goto(`/org/${requestedSlug}/team/${subteamSlug}/${subteamEventSlug}`);
const prefixes = ["", "/en"];
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(org.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("organization")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
for (const prefix of prefixes) {
await page.goto(`${prefix}/org/${requestedSlug}/team/${subteamSlug}/${subteamEventSlug}`);
await assertChecks(page, org.name, "organization");
}
});
test("Organization user", async ({ users, page }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
await page.goto(`/org/${requestedSlug}/${owner.username}`);
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(org.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("organization")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
const prefixes = ["", "/en"];
for (const prefix of prefixes) {
await page.goto(`${prefix}/org/${requestedSlug}/${owner.username}`);
await assertChecks(page, org.name, "organization");
}
});
test("Organization user event-type", async ({ users, page }) => {
@@ -105,10 +117,11 @@ test.describe("Unpublished", () => {
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
const [{ slug: ownerEventType }] = owner.eventTypes;
await page.goto(`/org/${requestedSlug}/${owner.username}/${ownerEventType}`);
await expect(page.locator('[data-testid="empty-screen"]')).toHaveCount(1);
await expect(page.locator(`h2:has-text("${title(org.name)}")`)).toHaveCount(1);
await expect(page.locator(`div:text("${description("organization")}")`)).toHaveCount(1);
await expect(page.locator(`img`)).toHaveAttribute("src", /.*/);
const prefixes = ["", "/en"];
for (const prefix of prefixes) {
await page.goto(`${prefix}/org/${requestedSlug}/${owner.username}/${ownerEventType}`);
await assertChecks(page, org.name, "organization");
}
});
});
@@ -3036,5 +3036,6 @@
"tip_username_plus": "Tip: You can a '+' between usernames: cal.com/anna+brian to make a dynamic group meeting",
"user_has_no_team_yet": "You don't have a team yet",
"no_team_members": "You don't have team members yet",
"picklist": "Picklist",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+2 -2
View File
@@ -77,7 +77,7 @@ describe("Plain Integration API", () => {
const userData = { id: 1, email: `${tier}@example.com`, name: `${tier} User` };
(getServerSession as any).mockResolvedValue({ user: userData });
prismaMock.user.findUnique.mockResolvedValue({ ...userData, teams });
const params = Promise.resolve<Params>({})
const params = Promise.resolve<Params>({});
const data = await (await POST({} as NextRequest, { params })).json();
expect(data).toMatchObject({ userTier: tier, email: userData.email, fullName: userData.name });
@@ -85,7 +85,7 @@ describe("Plain Integration API", () => {
it("should return 401 when no session exists", async () => {
(getServerSession as any).mockResolvedValue(null);
const params = Promise.resolve<Params>({})
const params = Promise.resolve<Params>({});
expect((await POST({} as NextRequest, { params })).status).toBe(401);
});
+37 -34
View File
@@ -33,13 +33,13 @@ export default function Bookings(props: { calUsername: string; calEmail: string
const { isLoading, data: booking, refetch } = useBooking((router.query.bookingUid as string) ?? "");
return (
if (!Array.isArray(booking)) {
<p>{booking.title}</p>
} else {
{for (const recurrence of booking) {
<p>{recurrence.title}</p>
}}
}
<>
{!Array.isArray(booking) ? (
<p>{booking.title}</p>
) : (
booking.map((recurrence) => <p key={recurrence.id}>{recurrence.title}</p>)
)}
</>
)
};
```
@@ -96,33 +96,36 @@ const { mutate: cancelBooking } = useCancelBooking({
},
});
...
return (
if (!Array.isArray(booking)) {
<button
className="underline"
onClick={() => {
cancelBooking({
id: booking.id,
cancellationReason: "User request",
});
}}>
Cancel
</button>
} else {
{for (const recurrence of booking) {
<button
className="underline"
onClick={() => {
cancelBooking({
id: recurrence.id,
cancellationReason: "User request",
});
}}>
Cancel
</button>
}}
}
)
return !Array.isArray(booking) ? (
<button
className="underline"
onClick={() => {
cancelBooking({
id: booking.id,
cancellationReason: "User request",
});
}}
>
Cancel
</button>
) : (
<>
{booking.map((recurrence) => (
<button
key={recurrence.id}
className="underline"
onClick={() => {
cancelBooking({
id: recurrence.id,
cancellationReason: "User request",
});
}}
>
Cancel
</button>
))}
</>
);
```
An example implementation can be found [here](https://github.com/calcom/cal.com/blob/main/packages/platform/examples/base/src/pages/booking.tsx).
+1 -7
View File
@@ -7,12 +7,6 @@ Deploying Cal.com on AWS
___
## One Click Deployment
<a href="https://console.aws.amazon.com/amplify/home#/deploy?repo=https://github.com/calcom/docker" target="_blank">
<img src="https://oneclick.amplifyapp.com/button.svg" noZoom alt="Deploy on AWS" />
</a>
## Manual Deployment
<Steps>
<Step title="Prerequisites">
@@ -51,4 +45,4 @@ ___
### Additional Resources
https://docs.aws.amazon.com/
https://docs.aws.amazon.com/
@@ -1,8 +1,8 @@
export const routingFormsComponents = {
"form-edit": () => import("./form-edit/[...appPages]").then((mod) => mod.default),
"route-builder": () => import("./route-builder/[...appPages]").then((mod) => mod.default),
"forms": () => import("./forms/[...appPages]").then((mod) => mod.default),
forms: () => import("./forms/[...appPages]").then((mod) => mod.default),
"routing-link": () => import("./routing-link/[...appPages]").then((mod) => mod.default),
"reporting": () => import("./reporting/[...appPages]").then((mod) => mod.default),
reporting: () => import("./reporting/[...appPages]").then((mod) => mod.default),
"incomplete-booking": () => import("./incomplete-booking/[...appPages]").then((mod) => mod.default),
};
};
@@ -8,9 +8,9 @@ import type { EventTypeAppCardComponent } from "@calcom/app-store/types";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { SchedulingType } from "@calcom/prisma/enums";
import { InputField } from "@calcom/ui/components/form";
import { Alert } from "@calcom/ui/components/alert";
import { Button } from "@calcom/ui/components/button";
import { InputField } from "@calcom/ui/components/form";
import { Select } from "@calcom/ui/components/form";
import { Switch } from "@calcom/ui/components/form";
import { showToast } from "@calcom/ui/components/toast";
@@ -63,6 +63,7 @@ const EventTypeAppCard: EventTypeAppCardComponent = function EventTypeAppCard({
{ label: t("date"), value: SalesforceFieldType.DATE },
{ label: t("phone").charAt(0).toUpperCase() + t("phone").slice(1), value: SalesforceFieldType.PHONE },
{ label: t("custom"), value: SalesforceFieldType.CUSTOM },
{ label: t("picklist"), value: SalesforceFieldType.PICKLIST },
];
const [writeToPersonObjectFieldType, setWriteToPersonObjectFieldType] = useState(fieldTypeOptions[0]);
@@ -1151,6 +1151,18 @@ export default class SalesforceCRMService implements CRM {
writeOnRecordBody[field.name] = dateValue;
continue;
}
} else if (field.type === SalesforceFieldType.PICKLIST) {
const picklistValue = await this.getPicklistFieldValue({
fieldConfigValue: fieldConfig.value,
salesforceField: field,
calEventResponses,
bookingUid,
contactId,
});
if (picklistValue) {
writeOnRecordBody[field.name] = picklistValue;
continue;
}
}
}
@@ -1294,6 +1306,49 @@ export default class SalesforceCRMService implements CRM {
return null;
}
private async getPicklistFieldValue({
fieldConfigValue,
salesforceField,
calEventResponses,
bookingUid,
contactId,
}: {
fieldConfigValue: string;
salesforceField: Field;
calEventResponses?: CalEventResponses | null;
bookingUid?: string | null;
contactId: string;
}) {
const log = logger.getSubLogger({ prefix: [`[getPicklistFieldValue] ${contactId}`] });
const picklistOptions = salesforceField.picklistValues;
if (!picklistOptions || !picklistOptions.length) {
log.warn(`No picklist values found for field ${salesforceField.name}`);
return null;
}
// Get the text value from the field
const fieldTextValue = await this.getTextFieldValue({
fieldValue: fieldConfigValue,
fieldLength: salesforceField.length,
calEventResponses,
bookingUid,
});
if (!fieldTextValue) {
log.warn(`No text value found for field ${salesforceField.name}`);
return null;
}
// Get the picklist value from the field
const picklistValue = picklistOptions.find((option) => option.active && option.value === fieldTextValue);
if (!picklistValue) {
log.warn(`No picklist value found for field ${salesforceField.name} and value ${fieldTextValue}`);
return null;
}
return picklistValue.value;
}
private async fetchPersonRecord(
contactId: string,
existingFields: Field[],
@@ -14,6 +14,7 @@ export enum SalesforceFieldType {
TEXT = "string",
PHONE = "phone",
CUSTOM = "custom",
PICKLIST = "picklist",
}
export enum DateFieldTypeData {
+1 -1
View File
@@ -49,7 +49,7 @@ module.exports = {
"no-restricted-imports": [
"error",
{
patterns: ["lodash"],
patterns: [],
},
],
"prefer-template": "error",
+1 -2
View File
@@ -1,6 +1,5 @@
// eslint-disable-next-line no-restricted-imports
import { cloneDeep } from "lodash";
import type { TFunction } from "i18next";
import { default as cloneDeep } from "lodash/cloneDeep";
import type { z } from "zod";
import type BaseEmail from "@calcom/emails/templates/_base-email";
+1 -1
View File
@@ -1,5 +1,5 @@
import type { EventStatus } from "ics";
import type { TFunction } from "i18next";
import type { EventStatus } from "ics";
import type { CalendarEvent } from "@calcom/types/Calendar";
+1 -1
View File
@@ -1,6 +1,6 @@
import type { TFunction } from "i18next";
import type { DateArray, ParticipationRole, EventStatus, ParticipationStatus } from "ics";
import { createEvent } from "ics";
import type { TFunction } from "i18next";
import { RRule } from "rrule";
import { getRichDescription } from "@calcom/lib/CalEventParser";
@@ -1,6 +1,5 @@
// eslint-disable-next-line no-restricted-imports
import { cloneDeep } from "lodash";
import type { TFunction } from "i18next";
import { default as cloneDeep } from "lodash/cloneDeep";
import { getRichDescription } from "@calcom/lib/CalEventParser";
import { TimeFormat } from "@calcom/lib/timeFormat";
@@ -1,6 +1,5 @@
// eslint-disable-next-line no-restricted-imports
import { cloneDeep } from "lodash";
import type { TFunction } from "i18next";
import { default as cloneDeep } from "lodash/cloneDeep";
import { getRichDescription } from "@calcom/lib/CalEventParser";
import { EMAIL_FROM_NAME } from "@calcom/lib/constants";
@@ -1,6 +1,5 @@
import logger from "@calcom/lib/logger";
import { HttpCode } from "./__handler";
import type { LazyModule, SWHMap } from "./__handler";
type Data = SWHMap["invoice.paid"]["data"];
@@ -30,10 +29,20 @@ const stripeWebhookProductHandler = (handlers: Handlers) => async (data: Data) =
}
const handlerGetter = handlers[productId as keyof typeof handlers];
if (!handlerGetter) throw new HttpCode(202, `No product handler found for product: ${productId}`);
/**
* If no handler is found, we skip the product. A handler could be null if we don't need webhooks to handle the business logic for the product.
*/
if (!handlerGetter) {
log.error(`Skipping product: ${productId} because no handler found`);
return { success: true };
}
const handler = (await handlerGetter())?.default;
// auto catch unsupported Stripe products.
if (!handler) throw new HttpCode(202, `No product handler found for product: ${productId}`);
if (!handler) {
log.error(`Skipping product: ${productId} because no handler found`);
return { success: true };
}
return await handler(data);
};
@@ -1,6 +1,5 @@
// eslint-disable-next-line no-restricted-imports
import { get } from "lodash";
import type { TFunction } from "i18next";
import { default as get } from "lodash/get";
import type { Dispatch, SetStateAction } from "react";
import { useState } from "react";
import type { UseFormReturn } from "react-hook-form";
@@ -10,11 +9,11 @@ import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import type { Prisma } from "@calcom/prisma/client";
import { SchedulingType } from "@calcom/prisma/enums";
import type { _EventTypeModel } from "@calcom/prisma/zod/eventtype";
import classNames from "@calcom/ui/classNames";
import { Badge } from "@calcom/ui/components/badge";
import { Switch } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
import { Tooltip } from "@calcom/ui/components/tooltip";
import classNames from "@calcom/ui/classNames";
import { Switch } from "@calcom/ui/components/form";
export const LockedSwitch = (
isManagedEventType: boolean,
@@ -8,7 +8,6 @@ import { Badge } from "@calcom/ui/components/badge";
import { Dialog, ConfirmationDialogContent } from "@calcom/ui/components/dialog";
import { DropdownActions, Table } from "@calcom/ui/components/table";
import { showToast } from "@calcom/ui/components/toast";
import ServerTrans from "@calcom/web/components/ServerTrans";
import { subdomainSuffix } from "../../../../organizations/lib/orgDomains";
@@ -17,9 +17,9 @@ import { UserPermissionRole } from "@calcom/prisma/enums";
import { CreationSource } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import type { Ensure } from "@calcom/types/utils";
import { TextField } from "@calcom/ui/components/form";
import { Alert } from "@calcom/ui/components/alert";
import { Button } from "@calcom/ui/components/button";
import { TextField } from "@calcom/ui/components/form";
import { Form } from "@calcom/ui/components/form";
export const CreateANewPlatformForm = () => {
@@ -16,10 +16,10 @@ import { telemetryEventTypes } from "@calcom/lib/telemetry";
import { MembershipRole } from "@calcom/prisma/enums";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Badge } from "@calcom/ui/components/badge";
import { UserAvatar } from "@calcom/ui/components/avatar";
import { Button } from "@calcom/ui/components/button";
import classNames from "@calcom/ui/classNames";
import { UserAvatar } from "@calcom/ui/components/avatar";
import { Badge } from "@calcom/ui/components/badge";
import { Button } from "@calcom/ui/components/button";
import { SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui/components/skeleton";
import { showToast } from "@calcom/ui/components/toast";
@@ -186,14 +186,16 @@ function UsersTableBare() {
/>
<div className="text-subtle ml-4 font-medium">
<div className="flex flex-row">
<div className="flex gap-3">
<span className="text-default">{user.name}</span>
<span className="ml-3">/{user.username}</span>
{user.locked && (
<span className="ml-3">
<Icon name="lock" />
<span>/{user.username}</span>
{user.profiles[0]?.username && (
<span className="flex items-center gap-1">
<Icon name="building" className="text-subtle size-5" />
<span>{user.profiles[0]?.username}</span>
</span>
)}
{user.locked && <Icon name="lock" />}
<br />
</div>
<span className="break-all">{user.email}</span>
@@ -9,12 +9,12 @@ import type {
SelectClassNames,
} from "@calcom/features/eventtypes/lib/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import { Button } from "@calcom/ui/components/button";
import { Dialog, DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import { Label } from "@calcom/ui/components/form";
import { Select } from "@calcom/ui/components/form";
import { TextField } from "@calcom/ui/components/form";
import { Button } from "@calcom/ui/components/button";
import classNames from "@calcom/ui/classNames";
import { Dialog, DialogContent, DialogFooter, DialogClose } from "@calcom/ui/components/dialog";
import type { CheckedSelectOption } from "./CheckedTeamSelect";
import WeightDescription from "./WeightDescription";
@@ -1,8 +1,6 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import classNames from "@calcom/ui/classNames";
import type { DialogProps } from "@calcom/ui/components/dialog";
import { ConfirmationDialogContent, Dialog } from "@calcom/ui/components/dialog";
import ServerTrans from "@calcom/web/components/ServerTrans";
export function DeleteDialog({
isManagedEvent,
@@ -4,8 +4,8 @@ import { Controller, useFormContext } from "react-hook-form";
import type { z } from "zod";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { InfoBadge } from "@calcom/ui/components/badge";
import classNames from "@calcom/ui/classNames";
import { InfoBadge } from "@calcom/ui/components/badge";
import { Label } from "@calcom/ui/components/form";
import { Icon } from "@calcom/ui/components/icon";
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-empty-function */
import { isValidPhoneNumber } from "libphonenumber-js";
import type { TFunction } from "i18next";
import { isValidPhoneNumber } from "libphonenumber-js";
// eslint-disable-next-line @calcom/eslint/deprecated-imports-next-router
import { z } from "zod";
+1 -1
View File
@@ -7,7 +7,7 @@
"version": "1.0.52",
"scripts": {
"dev": "yarn vite build --watch & npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify --watch",
"build": "yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
"build": "NODE_OPTIONS='--max_old_space_size=16384' yarn vite build && npx tailwindcss -i ./globals.css -o ./globals.min.css --postcss --minify && mkdir ./dist/packages/prisma-client && cp -rf ../../../node_modules/.prisma/client/*.d.ts ./dist/packages/prisma-client",
"publish": "rm -rf dist && yarn build && npm publish --access public",
"test": "jest"
},
Binary file not shown.
@@ -1,3 +1,4 @@
import { setUser as SentrySetUser } from "@sentry/nextjs";
import type { Session } from "next-auth";
import { WEBAPP_URL } from "@calcom/lib/constants";
@@ -154,6 +155,8 @@ export const isAuthed = middleware(async ({ ctx, next }) => {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
SentrySetUser({ id: user.id });
return next({
ctx: { user, session },
});
@@ -34,6 +34,15 @@ const listPaginatedHandler = async ({ input }: GetOptions) => {
contains: searchTerm.toLocaleLowerCase(),
},
},
{
profiles: {
some: {
username: {
contains: searchTerm.toLowerCase(),
},
},
},
},
],
};
} else {
@@ -58,6 +67,11 @@ const listPaginatedHandler = async ({ input }: GetOptions) => {
name: true,
timeZone: true,
role: true,
profiles: {
select: {
username: true,
},
},
},
});
+1 -1
View File
@@ -6,8 +6,8 @@ import type {
SelectedCalendar as _SelectedCalendar,
} from "@prisma/client";
import type { Dayjs } from "dayjs";
import type { Time } from "ical.js";
import type { TFunction } from "i18next";
import type { Time } from "ical.js";
import type { Frequency } from "rrule";
import type z from "zod";
+1732 -1310
View File
File diff suppressed because it is too large Load Diff