Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
214528ffa8 | ||
|
|
7f79779bf8 | ||
|
|
ed0f8fe0e7 | ||
|
|
273fc544e7 | ||
|
|
f24e8d12f0 | ||
|
|
19712f62e3 | ||
|
|
0ec701f9e6 | ||
|
|
e4cfaa56f0 | ||
|
|
c7641f852f | ||
|
|
e2597fbcd2 | ||
|
|
ce88018ecc | ||
|
|
0eb4e6c7d4 | ||
|
|
10c9c63ad8 | ||
|
|
a18defaa36 |
@@ -14,4 +14,4 @@ jobs:
|
||||
- uses: ./.github/actions/yarn-install
|
||||
- name: Run Lint
|
||||
run: yarn lint
|
||||
continue-on-error: false
|
||||
continue-on-error: true
|
||||
|
||||
Vendored
Vendored
+2
@@ -0,0 +1,2 @@
|
||||
undecided:
|
||||
- "@calcom/prisma"
|
||||
@@ -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();
|
||||
};
|
||||
@@ -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>;
|
||||
};
|
||||
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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*",
|
||||
|
||||
@@ -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": "*",
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
@@ -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 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
|
||||
}
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -49,7 +49,7 @@ module.exports = {
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
patterns: ["lodash"],
|
||||
patterns: [],
|
||||
},
|
||||
],
|
||||
"prefer-template": "error",
|
||||
|
||||
@@ -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,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,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";
|
||||
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
Vendored
+1
-1
@@ -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";
|
||||
|
||||
|
||||
Reference in New Issue
Block a user