Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0860036607 | ||
|
|
d080a0ef2d | ||
|
|
a6eab74fd1 | ||
|
|
0a2e275908 | ||
|
|
3b05ebc5b5 | ||
|
|
e5b4d2fb80 | ||
|
|
00a2a3d8a8 | ||
|
|
30ba168fc3 | ||
|
|
12eec186b2 | ||
|
|
4417d649ea | ||
|
|
7058da70e6 | ||
|
|
2cb383c337 | ||
|
|
fe5eca8eb3 | ||
|
|
7c6468953a | ||
|
|
28a0553d18 | ||
|
|
ffcb04fa99 | ||
|
|
5ca216b922 | ||
|
|
0abff9f562 | ||
|
|
942dc38b58 | ||
|
|
827d8995a1 | ||
|
|
850e222f0e | ||
|
|
ede2357203 | ||
|
|
559ab4192f | ||
|
|
3c8c6d0574 | ||
|
|
2ae2c15083 | ||
|
|
313bb89789 | ||
|
|
95e037c664 | ||
|
|
da026d909d | ||
|
|
c6b50bfd14 | ||
|
|
30b433397a | ||
|
|
ff1aefe539 | ||
|
|
5685d343b1 | ||
|
|
ae3f30fabf | ||
|
|
c11e76bac9 | ||
|
|
eed34559b5 | ||
|
|
bf958c82da | ||
|
|
728a386bf0 | ||
|
|
41efb5ea04 | ||
|
|
b268540d35 | ||
|
|
bdd1d3a99d | ||
|
|
dd7673b1f9 | ||
|
|
29f37a7e98 | ||
|
|
3983351dac | ||
|
|
01322431fc | ||
|
|
dcd06b0663 | ||
|
|
26e50d24b0 | ||
|
|
74803dc122 | ||
|
|
de65b85862 | ||
|
|
0ee56be1d8 | ||
|
|
de6f7dcfc0 | ||
|
|
956f2c7a2b | ||
|
|
14de0a1d62 | ||
|
|
51cca7481f | ||
|
|
3fe0280dee | ||
|
|
876fc188d4 | ||
|
|
d1810d9f14 | ||
|
|
a25cd43ddf | ||
|
|
833a91375e | ||
|
|
8124627cc5 | ||
|
|
749747ad7e | ||
|
|
967f70a5ec | ||
|
|
51c9b94fb5 | ||
|
|
1976dd15c3 | ||
|
|
03671e2a58 | ||
|
|
5e5f223908 | ||
|
|
9076a7eb42 | ||
|
|
a9e50d76ca | ||
|
|
bd4722554d | ||
|
|
105d373b8e | ||
|
|
df91dacc3f | ||
|
|
396b81af58 | ||
|
|
8467a9c53b | ||
|
|
e99e8b169a | ||
|
|
7b8902a769 | ||
|
|
2408338ed4 | ||
|
|
8f0ff58bcf | ||
|
|
fac47544d1 | ||
|
|
b09f3e5c8d | ||
|
|
8bb051ac4e | ||
|
|
fd3fffd7dd | ||
|
|
312a3f2543 | ||
|
|
f52f2f32fa | ||
|
|
fdd0a5a03d | ||
|
|
ca3e3600f7 | ||
|
|
ba944b471a | ||
|
|
7978f0317a | ||
|
|
d918f34ae8 | ||
|
|
1fed08fa9b | ||
|
|
aabfd3b3f6 | ||
|
|
60e0bbf4b7 | ||
|
|
0a7f65cd9b | ||
|
|
f41f4b01a9 | ||
|
|
a7b38b1b58 | ||
|
|
a0f1ceba19 | ||
|
|
5c6e104ff5 | ||
|
|
29b1f7bf51 | ||
|
|
7ec3d04acc | ||
|
|
13a765e50a |
No files matched your search
@@ -138,6 +138,11 @@ FORMBRICKS_HOST_URL=https://app.formbricks.com
|
|||||||
FORMBRICKS_ENVIRONMENT_ID=
|
FORMBRICKS_ENVIRONMENT_ID=
|
||||||
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
FORMBRICKS_FEEDBACK_SURVEY_ID=
|
||||||
|
|
||||||
|
# AvatarAPI
|
||||||
|
# Used to pre-fill avatar during signup
|
||||||
|
AVATARAPI_USERNAME=
|
||||||
|
AVATARAPI_PASSWORD=
|
||||||
|
|
||||||
# Twilio
|
# Twilio
|
||||||
# Used to send SMS reminders in workflows
|
# Used to send SMS reminders in workflows
|
||||||
TWILIO_SID=
|
TWILIO_SID=
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ jobs:
|
|||||||
shard: [1, 2]
|
shard: [1, 2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ jobs:
|
|||||||
shard: [1, 2]
|
shard: [1, 2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
|
|||||||
@@ -28,6 +28,10 @@ jobs:
|
|||||||
shard: [1, 2]
|
shard: [1, 2]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
|
|||||||
@@ -26,6 +26,10 @@ jobs:
|
|||||||
matrix:
|
matrix:
|
||||||
shard: [1, 2, 3, 4, 5]
|
shard: [1, 2, 3, 4, 5]
|
||||||
steps:
|
steps:
|
||||||
|
- uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: ./.github/actions/dangerous-git-checkout
|
- uses: ./.github/actions/dangerous-git-checkout
|
||||||
- uses: ./.github/actions/yarn-install
|
- uses: ./.github/actions/yarn-install
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ jobs:
|
|||||||
body="${body//'%'/'%25'}"
|
body="${body//'%'/'%25'}"
|
||||||
body="${body//$'\n'/'%0A'}"
|
body="${body//$'\n'/'%0A'}"
|
||||||
body="${body//$'\r'/'%0D'}"
|
body="${body//$'\r'/'%0D'}"
|
||||||
echo "{body}=${body}" >> $GITHUB_OUTPUT
|
echo ::set-output name=body::$body
|
||||||
|
|
||||||
- name: Find Comment
|
- name: Find Comment
|
||||||
uses: peter-evans/find-comment@v2
|
uses: peter-evans/find-comment@v2
|
||||||
|
|||||||
@@ -12,14 +12,6 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
login:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: Login to Docker Hub
|
|
||||||
uses: docker/login-action@v3
|
|
||||||
with:
|
|
||||||
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
||||||
password: ${{ secrets.DOCKERHUB_TOKEN }}
|
|
||||||
changes:
|
changes:
|
||||||
name: Detect changes
|
name: Detect changes
|
||||||
runs-on: buildjet-4vcpu-ubuntu-2204
|
runs-on: buildjet-4vcpu-ubuntu-2204
|
||||||
|
|||||||
@@ -448,7 +448,7 @@ Don't code but still want to contribute? Join our [discord](https://go.cal.com/d
|
|||||||
3. Enable the selected API.
|
3. Enable the selected API.
|
||||||
4. Next, go to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) from the side pane. Select the app type (Internal or External) and enter the basic app details on the first page.
|
4. Next, go to the [OAuth consent screen](https://console.cloud.google.com/apis/credentials/consent) from the side pane. Select the app type (Internal or External) and enter the basic app details on the first page.
|
||||||
5. In the second page on Scopes, select Add or Remove Scopes. Search for Calendar.event and select the scope with scope value `.../auth/calendar.events`, `.../auth/calendar.readonly` and select Update.
|
5. In the second page on Scopes, select Add or Remove Scopes. Search for Calendar.event and select the scope with scope value `.../auth/calendar.events`, `.../auth/calendar.readonly` and select Update.
|
||||||
6. In the third page (Test Users), add the Google account(s) you'll using. Make sure the details are correct on the last page of the wizard and your consent screen will be configured.
|
6. In the third page (Test Users), add the Google account(s) you'll be using. Make sure the details are correct on the last page of the wizard and your consent screen will be configured.
|
||||||
7. Now select [Credentials](https://console.cloud.google.com/apis/credentials) from the side pane and then select Create Credentials. Select the OAuth Client ID option.
|
7. Now select [Credentials](https://console.cloud.google.com/apis/credentials) from the side pane and then select Create Credentials. Select the OAuth Client ID option.
|
||||||
8. Select Web Application as the Application Type.
|
8. Select Web Application as the Application Type.
|
||||||
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
|
9. Under Authorized redirect URI's, select Add URI and then add the URI `<Cal.com URL>/api/integrations/googlecalendar/callback` and `<Cal.com URL>/api/auth/callback/google` replacing Cal.com URL with the URI at which your application runs.
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ const fetchBookings = async ({
|
|||||||
const params = {
|
const params = {
|
||||||
apiKey,
|
apiKey,
|
||||||
userId: userId.toString(),
|
userId: userId.toString(),
|
||||||
|
dateFrom: from,
|
||||||
|
dateTo: to,
|
||||||
};
|
};
|
||||||
|
|
||||||
const urlParams = new URLSearchParams(params);
|
const urlParams = new URLSearchParams(params);
|
||||||
@@ -40,11 +42,9 @@ const fetchBookings = async ({
|
|||||||
|
|
||||||
const bookings: Booking[] = data.bookings
|
const bookings: Booking[] = data.bookings
|
||||||
.filter((booking: Booking) => {
|
.filter((booking: Booking) => {
|
||||||
const afterFrom = new Date(booking.startTime).getTime() > new Date(from).getTime();
|
|
||||||
const beforeTo = new Date(booking.endTime).getTime() < new Date(to).getTime();
|
|
||||||
const notCancelled = booking.status !== BOOKING_STATUS.CANCELLED;
|
const notCancelled = booking.status !== BOOKING_STATUS.CANCELLED;
|
||||||
|
|
||||||
return afterFrom && beforeTo && notCancelled;
|
return notCancelled;
|
||||||
})
|
})
|
||||||
.map(({ endTime, eventTypeId, id, startTime, status, title }: Booking) => ({
|
.map(({ endTime, eventTypeId, id, startTime, status, title }: Booking) => ({
|
||||||
endTime,
|
endTime,
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ import type { User, UserList } from "../types/user";
|
|||||||
import type { WorkingHours } from "../types/workingHours";
|
import type { WorkingHours } from "../types/workingHours";
|
||||||
import now from "./now";
|
import now from "./now";
|
||||||
|
|
||||||
const gptModel = "gpt-4";
|
const gptModel = "gpt-4-0613";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
|
* Core of the Cal.ai booking agent: a LangChain Agent Executor.
|
||||||
@@ -58,7 +58,14 @@ If you can't find a referenced user, ask the user for their email or @username.
|
|||||||
|
|
||||||
The primary user's id is: ${userId}
|
The primary user's id is: ${userId}
|
||||||
The primary user's username is: ${user.username}
|
The primary user's username is: ${user.username}
|
||||||
The current time in the primary user's timezone is: ${now(user.timeZone)}
|
The current time in the primary user's timezone is: ${now(user.timeZone, {
|
||||||
|
weekday: "long",
|
||||||
|
year: "numeric",
|
||||||
|
month: "long",
|
||||||
|
day: "numeric",
|
||||||
|
hour: "numeric",
|
||||||
|
minute: "numeric",
|
||||||
|
})}
|
||||||
The primary user's time zone is: ${user.timeZone}
|
The primary user's time zone is: ${user.timeZone}
|
||||||
The primary user's event types are: ${user.eventTypes
|
The primary user's event types are: ${user.eventTypes
|
||||||
.map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
|
.map((e: EventType) => `ID: ${e.id}, Slug: ${e.slug}, Title: ${e.title}, Length: ${e.length};`)
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
export default function now(timeZone: string) {
|
export default function now(timeZone: string, options: Intl.DateTimeFormatOptions = {}) {
|
||||||
return new Date().toLocaleString("en-US", {
|
return new Date().toLocaleString("en-US", {
|
||||||
timeZone,
|
timeZone,
|
||||||
|
...options,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -13,6 +13,12 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": [
|
||||||
|
"next-env.d.ts",
|
||||||
|
"**/*.ts",
|
||||||
|
"**/*.tsx",
|
||||||
|
".next/types/**/*.ts",
|
||||||
|
"../../packages/types/window.d.ts"
|
||||||
|
],
|
||||||
"exclude": ["node_modules"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
@@ -18,6 +18,11 @@ const schemaBookingBaseBodyParams = Booking.pick({
|
|||||||
|
|
||||||
export const schemaBookingCreateBodyParams = extendedBookingCreateBody.merge(schemaQueryUserId.partial());
|
export const schemaBookingCreateBodyParams = extendedBookingCreateBody.merge(schemaQueryUserId.partial());
|
||||||
|
|
||||||
|
export const schemaBookingGetParams = z.object({
|
||||||
|
dateFrom: iso8601.optional(),
|
||||||
|
dateTo: iso8601.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
const schemaBookingEditParams = z
|
const schemaBookingEditParams = z
|
||||||
.object({
|
.object({
|
||||||
title: z.string().optional(),
|
title: z.string().optional(),
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ export const membershipEditBodySchema = Membership.omit({
|
|||||||
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
/** To avoid complication, let's avoid updating these, instead you can delete and create a new invite */
|
||||||
teamId: true,
|
teamId: true,
|
||||||
userId: true,
|
userId: true,
|
||||||
|
id: true,
|
||||||
})
|
})
|
||||||
.partial({
|
.partial({
|
||||||
accepted: true,
|
accepted: true,
|
||||||
|
|||||||
@@ -5,14 +5,23 @@ import { _TeamModel as Team } from "@calcom/prisma/zod";
|
|||||||
export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true }).partial({
|
export const schemaTeamBaseBodyParams = Team.omit({ id: true, createdAt: true }).partial({
|
||||||
hideBranding: true,
|
hideBranding: true,
|
||||||
metadata: true,
|
metadata: true,
|
||||||
|
pendingPayment: true,
|
||||||
});
|
});
|
||||||
|
|
||||||
const schemaTeamRequiredParams = z.object({});
|
const schemaTeamRequiredParams = z.object({
|
||||||
|
name: z.string().max(255),
|
||||||
|
});
|
||||||
|
|
||||||
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams).strict();
|
export const schemaTeamBodyParams = schemaTeamBaseBodyParams.merge(schemaTeamRequiredParams).strict();
|
||||||
|
|
||||||
export const schemaTeamUpdateBodyParams = schemaTeamBodyParams.partial();
|
export const schemaTeamUpdateBodyParams = schemaTeamBodyParams.partial();
|
||||||
|
|
||||||
|
const schemaOwnerId = z.object({
|
||||||
|
ownerId: z.number().optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const schemaTeamCreateBodyParams = schemaTeamBodyParams.merge(schemaOwnerId).strict();
|
||||||
|
|
||||||
export const schemaTeamReadPublic = Team.omit({});
|
export const schemaTeamReadPublic = Team.omit({});
|
||||||
|
|
||||||
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);
|
export const schemaTeamsReadPublic = z.array(schemaTeamReadPublic);
|
||||||
@@ -33,7 +33,7 @@
|
|||||||
"@sentry/nextjs": "^7.73.0",
|
"@sentry/nextjs": "^7.73.0",
|
||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"next": "^13.4.6",
|
"next": "^13.5.4",
|
||||||
"next-api-middleware": "^1.0.1",
|
"next-api-middleware": "^1.0.1",
|
||||||
"next-axiom": "^0.17.0",
|
"next-axiom": "^0.17.0",
|
||||||
"next-swagger-doc": "^0.3.6",
|
"next-swagger-doc": "^0.3.6",
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import type { NextApiRequest } from "next";
|
|||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
|
|
||||||
import { schemaBookingReadPublic } from "~/lib/validations/booking";
|
import { schemaBookingGetParams, schemaBookingReadPublic } from "~/lib/validations/booking";
|
||||||
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
import { schemaQuerySingleOrMultipleAttendeeEmails } from "~/lib/validations/shared/queryAttendeeEmail";
|
||||||
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/queryUserId";
|
||||||
|
|
||||||
@@ -119,13 +119,13 @@ import { schemaQuerySingleOrMultipleUserIds } from "~/lib/validations/shared/que
|
|||||||
* @returns An object that represents the WHERE clause for the findMany/findUnique operation.
|
* @returns An object that represents the WHERE clause for the findMany/findUnique operation.
|
||||||
*/
|
*/
|
||||||
function buildWhereClause(
|
function buildWhereClause(
|
||||||
userId: number,
|
userId: number | null,
|
||||||
attendeeEmails: string[],
|
attendeeEmails: string[],
|
||||||
userIds: number[] = [],
|
userIds: number[] = [],
|
||||||
userEmails: string[] = []
|
userEmails: string[] = []
|
||||||
) {
|
) {
|
||||||
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
const filterByAttendeeEmails = attendeeEmails.length > 0;
|
||||||
const userFilter = userIds.length > 0 ? { userId: { in: userIds } } : { userId };
|
const userFilter = userIds.length > 0 ? { userId: { in: userIds } } : !!userId ? { userId } : {};
|
||||||
let whereClause = {};
|
let whereClause = {};
|
||||||
if (filterByAttendeeEmails) {
|
if (filterByAttendeeEmails) {
|
||||||
whereClause = {
|
whereClause = {
|
||||||
@@ -162,6 +162,9 @@ function buildWhereClause(
|
|||||||
|
|
||||||
async function handler(req: NextApiRequest) {
|
async function handler(req: NextApiRequest) {
|
||||||
const { userId, isAdmin, prisma } = req;
|
const { userId, isAdmin, prisma } = req;
|
||||||
|
|
||||||
|
const { dateFrom, dateTo } = schemaBookingGetParams.parse(req.query);
|
||||||
|
|
||||||
const args: Prisma.BookingFindManyArgs = {};
|
const args: Prisma.BookingFindManyArgs = {};
|
||||||
args.include = {
|
args.include = {
|
||||||
attendees: true,
|
attendees: true,
|
||||||
@@ -189,7 +192,7 @@ async function handler(req: NextApiRequest) {
|
|||||||
const userEmails = users.map((u) => u.email);
|
const userEmails = users.map((u) => u.email);
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
args.where = buildWhereClause(userId, attendeeEmails, userIds, userEmails);
|
||||||
} else if (filterByAttendeeEmails) {
|
} else if (filterByAttendeeEmails) {
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
args.where = buildWhereClause(null, attendeeEmails, [], []);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
const user = await prisma.user.findUnique({
|
const user = await prisma.user.findUnique({
|
||||||
@@ -203,6 +206,20 @@ async function handler(req: NextApiRequest) {
|
|||||||
}
|
}
|
||||||
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
args.where = buildWhereClause(userId, attendeeEmails, [], []);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dateFrom) {
|
||||||
|
args.where = {
|
||||||
|
...args.where,
|
||||||
|
startTime: { gte: dateFrom },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (dateTo) {
|
||||||
|
args.where = {
|
||||||
|
...args.where,
|
||||||
|
endTime: { lte: dateTo },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
const data = await prisma.booking.findMany(args);
|
const data = await prisma.booking.findMany(args);
|
||||||
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
return { bookings: data.map((booking) => schemaBookingReadPublic.parse(booking)) };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import prismock from "../../../../../tests/libs/__mocks__/prisma";
|
||||||
|
|
||||||
|
import type { Request, Response } from "express";
|
||||||
|
import type { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
import { createMocks } from "node-mocks-http";
|
||||||
|
import { describe, expect, test } from "vitest";
|
||||||
|
|
||||||
|
import dayjs from "@calcom/dayjs";
|
||||||
|
|
||||||
|
import handler from "./_get";
|
||||||
|
|
||||||
|
type CustomNextApiRequest = NextApiRequest & Request;
|
||||||
|
type CustomNextApiResponse = NextApiResponse & Response;
|
||||||
|
|
||||||
|
function buildMockData() {
|
||||||
|
prismock.user.create({
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
username: "test",
|
||||||
|
name: "Test User",
|
||||||
|
email: "test@example.com",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
prismock.eventType.create({
|
||||||
|
data: {
|
||||||
|
id: 1,
|
||||||
|
slug: "test",
|
||||||
|
length: 30,
|
||||||
|
title: "Test Event Type",
|
||||||
|
userId: 1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("GET /api/slots", () => {
|
||||||
|
describe("Errors", () => {
|
||||||
|
test("Missing required data", async () => {
|
||||||
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
});
|
||||||
|
|
||||||
|
await handler(req, res);
|
||||||
|
|
||||||
|
expect(res.statusCode).toBe(400);
|
||||||
|
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"message": "invalid_type in 'startTime': Required; invalid_type in 'endTime': Required",
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("Success", () => {
|
||||||
|
describe("Regular event-type", () => {
|
||||||
|
test("Returns and event type available slots", async () => {
|
||||||
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
query: {
|
||||||
|
eventTypeId: 1,
|
||||||
|
startTime: dayjs().format(),
|
||||||
|
endTime: dayjs().add(1, "day").format(),
|
||||||
|
usernameList: "test",
|
||||||
|
},
|
||||||
|
prisma: prismock,
|
||||||
|
});
|
||||||
|
buildMockData();
|
||||||
|
await handler(req, res);
|
||||||
|
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
|
||||||
|
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"slots": {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
test("Returns and event type available slots with passed timeZone", async () => {
|
||||||
|
const { req, res } = createMocks<CustomNextApiRequest, CustomNextApiResponse>({
|
||||||
|
method: "GET",
|
||||||
|
query: {
|
||||||
|
eventTypeId: 1,
|
||||||
|
startTime: dayjs().format(),
|
||||||
|
endTime: dayjs().add(1, "day").format(),
|
||||||
|
usernameList: "test",
|
||||||
|
timeZone: "UTC",
|
||||||
|
},
|
||||||
|
prisma: prismock,
|
||||||
|
});
|
||||||
|
buildMockData();
|
||||||
|
await handler(req, res);
|
||||||
|
console.log({ statusCode: res._getStatusCode(), data: JSON.parse(res._getData()) });
|
||||||
|
expect(JSON.parse(res._getData())).toMatchInlineSnapshot(`
|
||||||
|
{
|
||||||
|
"slots": {},
|
||||||
|
}
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -22,7 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
const { usernameList, ...rest } = req.query;
|
const { usernameList, ...rest } = req.query;
|
||||||
let slugs = usernameList;
|
let slugs = usernameList;
|
||||||
if (!Array.isArray(usernameList)) {
|
if (!Array.isArray(usernameList)) {
|
||||||
slugs = usernameList ? [usernameList] : [];
|
slugs = usernameList ? [usernameList] : undefined;
|
||||||
}
|
}
|
||||||
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
|
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
|
||||||
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
|
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
|
||||||
@@ -38,9 +38,9 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
|
|||||||
},
|
},
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
: availableSlots;
|
: availableSlots.slots;
|
||||||
|
|
||||||
return slotsInProvidedTimeZone;
|
return { slots: slotsInProvidedTimeZone };
|
||||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
} catch (cause) {
|
} catch (cause) {
|
||||||
if (cause instanceof TRPCError) {
|
if (cause instanceof TRPCError) {
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
import type { NextApiRequest } from "next";
|
import type { NextApiRequest } from "next";
|
||||||
|
|
||||||
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
|
import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
|
||||||
|
import stripe from "@calcom/app-store/stripepayment/lib/server";
|
||||||
|
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import { defaultResponder } from "@calcom/lib/server";
|
import { defaultResponder } from "@calcom/lib/server";
|
||||||
import { MembershipRole } from "@calcom/prisma/enums";
|
import { MembershipRole } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
import { schemaMembershipPublic } from "~/lib/validations/membership";
|
import { schemaMembershipPublic } from "~/lib/validations/membership";
|
||||||
import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/team";
|
import { schemaTeamCreateBodyParams, schemaTeamReadPublic } from "~/lib/validations/team";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @swagger
|
* @swagger
|
||||||
@@ -31,6 +33,12 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/te
|
|||||||
* required:
|
* required:
|
||||||
* - name
|
* - name
|
||||||
* - slug
|
* - slug
|
||||||
|
* - hideBookATeamMember
|
||||||
|
* - brandColor
|
||||||
|
* - darkBrandColor
|
||||||
|
* - timeZone
|
||||||
|
* - weekStart
|
||||||
|
* - isPrivate
|
||||||
* properties:
|
* properties:
|
||||||
* name:
|
* name:
|
||||||
* type: string
|
* type: string
|
||||||
@@ -38,6 +46,27 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/te
|
|||||||
* slug:
|
* slug:
|
||||||
* type: string
|
* type: string
|
||||||
* description: A unique slug that works as path for the team public page
|
* description: A unique slug that works as path for the team public page
|
||||||
|
* hideBookATeamMember:
|
||||||
|
* type: boolean
|
||||||
|
* description: Flag to hide or show the book a team member option
|
||||||
|
* brandColor:
|
||||||
|
* type: string
|
||||||
|
* description: Primary brand color for the team
|
||||||
|
* darkBrandColor:
|
||||||
|
* type: string
|
||||||
|
* description: Dark variant of the primary brand color for the team
|
||||||
|
* timeZone:
|
||||||
|
* type: string
|
||||||
|
* description: Time zone of the team
|
||||||
|
* weekStart:
|
||||||
|
* type: string
|
||||||
|
* description: Starting day of the week for the team
|
||||||
|
* isPrivate:
|
||||||
|
* type: boolean
|
||||||
|
* description: Flag indicating if the team is private
|
||||||
|
* ownerId:
|
||||||
|
* type: number
|
||||||
|
* description: ID of the team owner - only admins can set this.
|
||||||
* tags:
|
* tags:
|
||||||
* - teams
|
* - teams
|
||||||
* responses:
|
* responses:
|
||||||
@@ -49,23 +78,23 @@ import { schemaTeamBodyParams, schemaTeamReadPublic } from "~/lib/validations/te
|
|||||||
* description: Authorization information is missing or invalid.
|
* description: Authorization information is missing or invalid.
|
||||||
*/
|
*/
|
||||||
async function postHandler(req: NextApiRequest) {
|
async function postHandler(req: NextApiRequest) {
|
||||||
const { prisma, body, userId } = req;
|
const { prisma, body, userId, isAdmin } = req;
|
||||||
const data = schemaTeamBodyParams.parse(body);
|
const { ownerId, ...data } = schemaTeamCreateBodyParams.parse(body);
|
||||||
|
|
||||||
|
await checkPermissions(req);
|
||||||
|
|
||||||
|
const effectiveUserId = isAdmin && ownerId ? ownerId : userId;
|
||||||
|
|
||||||
if (data.slug) {
|
if (data.slug) {
|
||||||
const alreadyExist = await prisma.team.findFirst({
|
const alreadyExist = await prisma.team.findFirst({
|
||||||
where: {
|
where: {
|
||||||
slug: data.slug,
|
slug: {
|
||||||
|
mode: "insensitive",
|
||||||
|
equals: data.slug,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
if (alreadyExist) throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
|
if (alreadyExist) throw new HttpError({ statusCode: 409, message: "Team slug already exists" });
|
||||||
if (IS_TEAM_BILLING_ENABLED) {
|
|
||||||
// Setting slug in metadata, so it can be published later
|
|
||||||
data.metadata = {
|
|
||||||
requestedSlug: data.slug,
|
|
||||||
};
|
|
||||||
delete data.slug;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if parentId is related to this user
|
// Check if parentId is related to this user
|
||||||
@@ -87,24 +116,102 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
...data,
|
...data,
|
||||||
metadata: data.metadata === null ? {} : data.metadata || undefined,
|
metadata: data.metadata === null ? {} : data.metadata || undefined,
|
||||||
};
|
};
|
||||||
const team = await prisma.team.create({
|
|
||||||
|
if (!IS_TEAM_BILLING_ENABLED) {
|
||||||
|
const team = await prisma.team.create({
|
||||||
|
data: {
|
||||||
|
...cloneData,
|
||||||
|
members: {
|
||||||
|
create: { userId: effectiveUserId, role: MembershipRole.OWNER, accepted: true },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
include: { members: true },
|
||||||
|
});
|
||||||
|
|
||||||
|
req.statusCode = 201;
|
||||||
|
|
||||||
|
return {
|
||||||
|
team: schemaTeamReadPublic.parse(team),
|
||||||
|
owner: schemaMembershipPublic.parse(team.members[0]),
|
||||||
|
message: `Team created successfully. We also made user with ID=${ownerId} the owner of this team.`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const pendingPaymentTeam = await prisma.team.create({
|
||||||
data: {
|
data: {
|
||||||
...cloneData,
|
...cloneData,
|
||||||
createdAt: new Date(),
|
pendingPayment: true,
|
||||||
members: {
|
|
||||||
// We're also creating the relation membership of team ownership in this call.
|
|
||||||
create: { userId, role: MembershipRole.OWNER, accepted: true },
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
include: { members: true },
|
|
||||||
});
|
});
|
||||||
req.statusCode = 201;
|
|
||||||
// We are also returning the new ownership relation as owner besides team.
|
const checkoutSession = await generateTeamCheckoutSession({
|
||||||
|
pendingPaymentTeamId: pendingPaymentTeam.id,
|
||||||
|
ownerId: effectiveUserId,
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
team: schemaTeamReadPublic.parse(team),
|
message:
|
||||||
owner: schemaMembershipPublic.parse(team.members[0]),
|
"Your team will be created once we receive your payment. Please complete the payment using the payment link.",
|
||||||
message: "Team created successfully, we also made you the owner of this team",
|
paymentLink: checkoutSession.url,
|
||||||
|
pendingTeam: {
|
||||||
|
...schemaTeamReadPublic.parse(pendingPaymentTeam),
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function checkPermissions(req: NextApiRequest) {
|
||||||
|
const { isAdmin } = req;
|
||||||
|
const body = schemaTeamCreateBodyParams.parse(req.body);
|
||||||
|
|
||||||
|
/* Non-admin users can only create teams for themselves */
|
||||||
|
if (!isAdmin && body.ownerId)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 401,
|
||||||
|
message: "ADMIN required for `ownerId`",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const generateTeamCheckoutSession = async ({
|
||||||
|
pendingPaymentTeamId,
|
||||||
|
ownerId,
|
||||||
|
}: {
|
||||||
|
pendingPaymentTeamId: number;
|
||||||
|
ownerId: number;
|
||||||
|
}) => {
|
||||||
|
const customer = await getStripeCustomerIdFromUserId(ownerId);
|
||||||
|
const session = await stripe.checkout.sessions.create({
|
||||||
|
customer,
|
||||||
|
mode: "subscription",
|
||||||
|
allow_promotion_codes: true,
|
||||||
|
success_url: `${WEBAPP_URL}/api/teams/api/create?session_id={CHECKOUT_SESSION_ID}`,
|
||||||
|
cancel_url: `${WEBAPP_URL}/settings/my-account/profile`,
|
||||||
|
line_items: [
|
||||||
|
{
|
||||||
|
/** We only need to set the base price and we can upsell it directly on Stripe's checkout */
|
||||||
|
price: process.env.STRIPE_TEAM_MONTHLY_PRICE_ID,
|
||||||
|
/**Initially it will be just the team owner */
|
||||||
|
quantity: 1,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
customer_update: {
|
||||||
|
address: "auto",
|
||||||
|
},
|
||||||
|
automatic_tax: {
|
||||||
|
enabled: true,
|
||||||
|
},
|
||||||
|
metadata: {
|
||||||
|
pendingPaymentTeamId,
|
||||||
|
ownerId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!session.url)
|
||||||
|
throw new HttpError({
|
||||||
|
statusCode: 500,
|
||||||
|
message: "Failed generating a checkout session URL.",
|
||||||
|
});
|
||||||
|
|
||||||
|
return session;
|
||||||
|
};
|
||||||
|
|
||||||
export default defaultResponder(postHandler);
|
export default defaultResponder(postHandler);
|
||||||
@@ -92,7 +92,7 @@ export async function patchHandler(req: NextApiRequest) {
|
|||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.data.eventTriggers) {
|
if (eventTriggers) {
|
||||||
const eventTriggersSet = new Set(eventTriggers);
|
const eventTriggersSet = new Set(eventTriggers);
|
||||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -92,7 +92,7 @@ async function postHandler(req: NextApiRequest) {
|
|||||||
args.data.userId = bodyUserId;
|
args.data.userId = bodyUserId;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (args.data.eventTriggers) {
|
if (eventTriggers) {
|
||||||
const eventTriggersSet = new Set(eventTriggers);
|
const eventTriggersSet = new Set(eventTriggers);
|
||||||
args.data.eventTriggers = Array.from(eventTriggersSet);
|
args.data.eventTriggers = Array.from(eventTriggersSet);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
"@storybook/blocks": "^7.6.3",
|
"@storybook/blocks": "^7.6.3",
|
||||||
"@storybook/nextjs": "^7.6.3",
|
"@storybook/nextjs": "^7.6.3",
|
||||||
"@storybook/preview-api": "^7.6.3",
|
"@storybook/preview-api": "^7.6.3",
|
||||||
"next": "^13.4.6",
|
"next": "^13.5.4",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
"storybook-addon-rtl-direction": "^0.0.19"
|
"storybook-addon-rtl-direction": "^0.0.19"
|
||||||
|
|||||||
@@ -14,7 +14,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"highlight.js": "^11.6.0",
|
"highlight.js": "^11.6.0",
|
||||||
"isarray": "2.0.5",
|
"isarray": "2.0.5",
|
||||||
"next": "^13.4.6",
|
"next": "^13.5.4",
|
||||||
"openapi-snippet": "^0.13.0",
|
"openapi-snippet": "^0.13.0",
|
||||||
"react": "^18.2.0",
|
"react": "^18.2.0",
|
||||||
"react-dom": "^18.2.0",
|
"react-dom": "^18.2.0",
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import type { GetStaticProps, GetStaticPropsContext } from "next";
|
||||||
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
|
||||||
|
export const withAppDirSsg =
|
||||||
|
<T extends Record<string, any>>(getStaticProps: GetStaticProps<T>) =>
|
||||||
|
async (context: GetStaticPropsContext) => {
|
||||||
|
const ssgResponse = await getStaticProps(context);
|
||||||
|
|
||||||
|
if ("redirect" in ssgResponse) {
|
||||||
|
redirect(ssgResponse.redirect.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("notFound" in ssgResponse) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = await Promise.resolve(ssgResponse.props);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...ssgResponse.props,
|
||||||
|
// includes dehydratedState required for future page trpcPropvider
|
||||||
|
...("trpcState" in props && { dehydratedState: props.trpcState }),
|
||||||
|
};
|
||||||
|
};
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
import type { GetServerSideProps, GetServerSidePropsContext } from "next";
|
||||||
import { notFound, redirect } from "next/navigation";
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
|
||||||
export const withAppDir =
|
export const withAppDirSsr =
|
||||||
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
|
||||||
async (context: GetServerSidePropsContext): Promise<T> => {
|
async (context: GetServerSidePropsContext) => {
|
||||||
const ssrResponse = await getServerSideProps(context);
|
const ssrResponse = await getServerSideProps(context);
|
||||||
|
|
||||||
if ("redirect" in ssrResponse) {
|
if ("redirect" in ssrResponse) {
|
||||||
@@ -13,9 +13,11 @@ export const withAppDir =
|
|||||||
notFound();
|
notFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const props = await Promise.resolve(ssrResponse.props);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...ssrResponse.props,
|
...props,
|
||||||
// includes dehydratedState required for future page trpcPropvider
|
// includes dehydratedState required for future page trpcPropvider
|
||||||
...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }),
|
...("trpcState" in props && { dehydratedState: props.trpcState }),
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -1,11 +1,16 @@
|
|||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { type DehydratedState, Hydrate } from "@tanstack/react-query";
|
import { type DehydratedState, HydrationBoundary } from "@tanstack/react-query";
|
||||||
import { useMemo } from "react";
|
import { useMemo } from "react";
|
||||||
|
|
||||||
import type { DataTransformer } from "@trpc/server";
|
type DataTransformer = {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
serialize: (data: any) => any;
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
deserialize: (data: any) => any;
|
||||||
|
};
|
||||||
|
|
||||||
export function createHydrateClient(opts: { transformer?: DataTransformer }) {
|
export function createHydrateClient(opts: { transformer: DataTransformer }) {
|
||||||
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
|
return function HydrateClient(props: { children: React.ReactNode; state: DehydratedState }) {
|
||||||
const { state, children } = props;
|
const { state, children } = props;
|
||||||
|
|
||||||
@@ -16,6 +21,6 @@ export function createHydrateClient(opts: { transformer?: DataTransformer }) {
|
|||||||
return state;
|
return state;
|
||||||
}, [state]);
|
}, [state]);
|
||||||
|
|
||||||
return <Hydrate state={transformedState}>{children}</Hydrate>;
|
return <HydrationBoundary state={transformedState}>{children}</HydrationBoundary>;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -4,10 +4,7 @@ import { trpc } from "app/_trpc/client";
|
|||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import superjson from "superjson";
|
import superjson from "superjson";
|
||||||
|
|
||||||
import { httpBatchLink } from "@calcom/trpc/client/links/httpBatchLink";
|
import { httpBatchLink, httpLink, loggerLink, splitLink } from "@calcom/trpc/client";
|
||||||
import { httpLink } from "@calcom/trpc/client/links/httpLink";
|
|
||||||
import { loggerLink } from "@calcom/trpc/client/links/loggerLink";
|
|
||||||
import { splitLink } from "@calcom/trpc/client/links/splitLink";
|
|
||||||
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
import { ENDPOINTS } from "@calcom/trpc/react/shared";
|
||||||
|
|
||||||
export type Endpoint = (typeof ENDPOINTS)[number];
|
export type Endpoint = (typeof ENDPOINTS)[number];
|
||||||
|
|||||||
@@ -0,0 +1,197 @@
|
|||||||
|
import LegacyPage, { getLayout } from "@pages/apps/[slug]/[...pages]";
|
||||||
|
import type { PageProps, SearchParams } from "app/_types";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
import { notFound, redirect } from "next/navigation";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||||
|
import RoutingFormsRoutingConfig, {
|
||||||
|
serverSidePropsConfig,
|
||||||
|
} from "@calcom/app-store/routing-forms/pages/app-routing.config";
|
||||||
|
import TypeformRoutingConfig from "@calcom/app-store/typeform/pages/app-routing.config";
|
||||||
|
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
import prisma from "@calcom/prisma";
|
||||||
|
import type { AppGetServerSideProps } from "@calcom/types/AppGetServerSideProps";
|
||||||
|
|
||||||
|
import type { AppProps } from "@lib/app-providers";
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
import PageWrapper from "@components/PageWrapperAppDir";
|
||||||
|
|
||||||
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
type AppPageType = {
|
||||||
|
getServerSideProps: AppGetServerSideProps;
|
||||||
|
// A component than can accept any properties
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
default: ((props: any) => JSX.Element) &
|
||||||
|
Pick<AppProps["Component"], "isBookingPage" | "getLayout" | "PageWrapper">;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Found = {
|
||||||
|
notFound: false;
|
||||||
|
Component: AppPageType["default"];
|
||||||
|
getServerSideProps: AppPageType["getServerSideProps"];
|
||||||
|
};
|
||||||
|
|
||||||
|
const AppsRouting = {
|
||||||
|
"routing-forms": RoutingFormsRoutingConfig,
|
||||||
|
typeform: TypeformRoutingConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
const paramsSchema = z.object({
|
||||||
|
slug: z.string(),
|
||||||
|
pages: z.array(z.string()),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const p = paramsSchema.safeParse(params);
|
||||||
|
|
||||||
|
if (!p.success) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainPage = p.data.pages[0];
|
||||||
|
|
||||||
|
if (mainPage === "forms") {
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => `Forms | ${APP_NAME}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const legacyContext = buildLegacyCtx(
|
||||||
|
headers(),
|
||||||
|
cookies(),
|
||||||
|
params,
|
||||||
|
searchParams
|
||||||
|
) as unknown as GetServerSidePropsContext;
|
||||||
|
const { form } = await getPageProps(legacyContext);
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => `${form.name} | ${APP_NAME}`,
|
||||||
|
() => form.description
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
function getRoute(appName: string, pages: string[]) {
|
||||||
|
const routingConfig = AppsRouting[appName as keyof typeof AppsRouting] as Record<string, AppPageType>;
|
||||||
|
|
||||||
|
if (!routingConfig) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const mainPage = pages[0];
|
||||||
|
const appPage = routingConfig.layoutHandler || (routingConfig[mainPage] as AppPageType);
|
||||||
|
|
||||||
|
const getServerSidePropsHandler = serverSidePropsConfig[mainPage];
|
||||||
|
|
||||||
|
if (!appPage) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
notFound: false,
|
||||||
|
Component: appPage.default,
|
||||||
|
...appPage,
|
||||||
|
getServerSideProps: getServerSidePropsHandler,
|
||||||
|
} as Found;
|
||||||
|
}
|
||||||
|
|
||||||
|
const getPageProps = async ({ params, query, req }: GetServerSidePropsContext) => {
|
||||||
|
const p = paramsSchema.safeParse(params);
|
||||||
|
|
||||||
|
if (!p.success) {
|
||||||
|
return notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const { slug: appName, pages } = p.data;
|
||||||
|
|
||||||
|
const route = getRoute(appName, pages);
|
||||||
|
|
||||||
|
if (route.notFound) {
|
||||||
|
return route;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (route.getServerSideProps) {
|
||||||
|
// TODO: Document somewhere that right now it is just a convention that filename should have appPages in it's name.
|
||||||
|
// appPages is actually hardcoded here and no matter the fileName the same variable would be used.
|
||||||
|
// We can write some validation logic later on that ensures that [...appPages].tsx file exists
|
||||||
|
params!.appPages = pages.slice(1);
|
||||||
|
|
||||||
|
const ctx = { req, params, query };
|
||||||
|
|
||||||
|
const session = await getServerSession({ req });
|
||||||
|
const user = session?.user;
|
||||||
|
const app = await getAppWithMetadata({ slug: appName });
|
||||||
|
|
||||||
|
if (!app) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await route.getServerSideProps(
|
||||||
|
{
|
||||||
|
...ctx,
|
||||||
|
params: {
|
||||||
|
...ctx.params,
|
||||||
|
appPages: pages.slice(1),
|
||||||
|
},
|
||||||
|
} as GetServerSidePropsContext<{
|
||||||
|
slug: string;
|
||||||
|
pages: string[];
|
||||||
|
appPages: string[];
|
||||||
|
}>,
|
||||||
|
prisma,
|
||||||
|
user,
|
||||||
|
ssrInit
|
||||||
|
);
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
//@ts-ignore
|
||||||
|
|
||||||
|
if (result.notFound) {
|
||||||
|
notFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (result.redirect) {
|
||||||
|
redirect(result.redirect.destination);
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
appName,
|
||||||
|
appUrl: app.simplePath || `/apps/${appName}`,
|
||||||
|
...result.props,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
appName,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default async function Page({ params, searchParams }: PageProps) {
|
||||||
|
const h = headers();
|
||||||
|
const nonce = h.get("x-nonce") ?? undefined;
|
||||||
|
|
||||||
|
const legacyContext = buildLegacyCtx(
|
||||||
|
h,
|
||||||
|
cookies(),
|
||||||
|
params,
|
||||||
|
searchParams
|
||||||
|
) as unknown as GetServerSidePropsContext;
|
||||||
|
const props = await getPageProps(legacyContext);
|
||||||
|
return (
|
||||||
|
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
||||||
|
<LegacyPage {...props} />
|
||||||
|
</PageWrapper>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null })<"L">;
|
|
||||||
@@ -1,40 +1,33 @@
|
|||||||
import AppPage from "@pages/apps/[slug]/index";
|
import Page from "@pages/apps/[slug]/index";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import fs from "fs";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import matter from "gray-matter";
|
import type { InferGetStaticPropsType } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { cookies, headers } from "next/headers";
|
||||||
import path from "path";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
|
|
||||||
import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants";
|
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
const sourceSchema = z.object({
|
import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
|
||||||
content: z.string(),
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
data: z.object({
|
|
||||||
description: z.string().optional(),
|
|
||||||
items: z
|
|
||||||
.array(
|
|
||||||
z.union([
|
|
||||||
z.string(),
|
|
||||||
z.object({
|
|
||||||
iframe: z.object({ src: z.string() }),
|
|
||||||
}),
|
|
||||||
])
|
|
||||||
)
|
|
||||||
.optional(),
|
|
||||||
}),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||||
const { data } = await getPageProps({ params });
|
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const legacyContext = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
const res = await getData(legacyContext);
|
||||||
|
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => `${data.name} | ${APP_NAME}`,
|
() => `${res?.data.name} | ${APP_NAME}`,
|
||||||
() => data.description
|
() => res?.data.description ?? ""
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -53,74 +46,6 @@ export const generateStaticParams = async () => {
|
|||||||
return [];
|
return [];
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
export default WithLayout({ getLayout: null, Page, getData });
|
||||||
if (typeof params?.slug !== "string") {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const appMeta = await getAppWithMetadata({
|
|
||||||
slug: params?.slug,
|
|
||||||
});
|
|
||||||
|
|
||||||
const appFromDb = await prisma.app.findUnique({
|
|
||||||
where: { slug: params.slug.toLowerCase() },
|
|
||||||
});
|
|
||||||
|
|
||||||
const isAppAvailableInFileSystem = appMeta;
|
|
||||||
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
|
|
||||||
|
|
||||||
if (!IS_PRODUCTION && isAppDisabled) {
|
|
||||||
return {
|
|
||||||
isAppDisabled: true as const,
|
|
||||||
data: {
|
|
||||||
...appMeta,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!appFromDb || !appMeta || isAppDisabled) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTemplate = appMeta.isTemplate;
|
|
||||||
const appDirname = path.join(isTemplate ? "templates" : "", appFromDb.dirName);
|
|
||||||
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
|
|
||||||
const postFilePath = path.join(README_PATH);
|
|
||||||
let source = "";
|
|
||||||
|
|
||||||
try {
|
|
||||||
source = fs.readFileSync(postFilePath).toString();
|
|
||||||
source = source.replace(/{DESCRIPTION}/g, appMeta.description);
|
|
||||||
} catch (error) {
|
|
||||||
/* If the app doesn't have a README we fallback to the package description */
|
|
||||||
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
|
|
||||||
source = appMeta.description;
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = matter(source);
|
|
||||||
const { content, data } = sourceSchema.parse({ content: result.content, data: result.data });
|
|
||||||
if (data.items) {
|
|
||||||
data.items = data.items.map((item) => {
|
|
||||||
if (typeof item === "string") {
|
|
||||||
return getAppAssetFullPath(item, {
|
|
||||||
dirName: appMeta.dirName,
|
|
||||||
isTemplate: appMeta.isTemplate,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return item;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
isAppDisabled: false as const,
|
|
||||||
source: { content, data },
|
|
||||||
data: appMeta,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
|
||||||
const pageProps = await getPageProps({ params });
|
|
||||||
|
|
||||||
return <AppPage {...pageProps} />;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
import SetupPage from "@pages/apps/[slug]/setup";
|
import Page from "@pages/apps/[slug]/setup";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { cookies, headers } from "next/headers";
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
import { notFound, redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
|
import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
@@ -14,23 +14,8 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
type T = InferGetServerSidePropsType<typeof getServerSideProps>;
|
||||||
const req = { headers: headers(), cookies: cookies() };
|
|
||||||
|
|
||||||
const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext);
|
const getData = withAppDirSsr<T>(getServerSideProps);
|
||||||
|
|
||||||
if (!result || "notFound" in result) {
|
export default WithLayout({ getLayout: null, Page, getData });
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if ("redirect" in result) {
|
|
||||||
redirect(result.redirect.destination);
|
|
||||||
}
|
|
||||||
|
|
||||||
return result.props;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
|
||||||
const pageProps = await getPageProps({ params });
|
|
||||||
return <SetupPage {...pageProps} />;
|
|
||||||
}
|
|
||||||
@@ -1,15 +1,15 @@
|
|||||||
import CategoryPage from "@pages/apps/categories/[category]";
|
import CategoryPage, { type PageProps } from "@pages/apps/categories/[category]";
|
||||||
import { Prisma } from "@prisma/client";
|
import { Prisma } from "@prisma/client";
|
||||||
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import z from "zod";
|
|
||||||
|
|
||||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { AppCategories } from "@calcom/prisma/enums";
|
import { AppCategories } from "@calcom/prisma/enums";
|
||||||
|
|
||||||
|
import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => `${APP_NAME} | ${APP_NAME}`,
|
() => `${APP_NAME} | ${APP_NAME}`,
|
||||||
@@ -34,38 +34,7 @@ export const generateStaticParams = async () => {
|
|||||||
return paths.map((category) => ({ category }));
|
return paths.map((category) => ({ category }));
|
||||||
};
|
};
|
||||||
|
|
||||||
const querySchema = z.object({
|
const getData = withAppDirSsg<PageProps>(getStaticProps);
|
||||||
category: z.nativeEnum(AppCategories),
|
|
||||||
});
|
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
export default WithLayout({ getData, Page: CategoryPage, getLayout: null })<"P">;
|
||||||
const p = querySchema.safeParse(params);
|
|
||||||
|
|
||||||
if (!p.success) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const appQuery = await prisma.app.findMany({
|
|
||||||
where: {
|
|
||||||
categories: {
|
|
||||||
has: p.data.category,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
slug: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const dbAppsSlugs = appQuery.map((category) => category.slug);
|
|
||||||
|
|
||||||
const appStore = await getAppRegistry();
|
|
||||||
|
|
||||||
const apps = appStore.filter((app) => dbAppsSlugs.includes(app.slug));
|
|
||||||
return {
|
|
||||||
apps,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-expect-error getData arg
|
|
||||||
export default WithLayout({ getData: getPageProps, Page: CategoryPage })<"P">;
|
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import LegacyPage from "@pages/apps/categories/index";
|
import Page from "@pages/apps/categories/index";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { getServerSideProps } from "@lib/apps/categories/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
@@ -16,29 +14,4 @@ export const generateMetadata = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), Page, getLayout: null })<"P">;
|
||||||
const ssr = await ssrInit(ctx);
|
|
||||||
|
|
||||||
const session = await getServerSession({ req: ctx.req });
|
|
||||||
|
|
||||||
let appStore;
|
|
||||||
if (session?.user?.id) {
|
|
||||||
appStore = await getAppRegistryWithCredentials(session.user.id);
|
|
||||||
} else {
|
|
||||||
appStore = await getAppRegistry();
|
|
||||||
}
|
|
||||||
|
|
||||||
const categories = appStore.reduce((c, app) => {
|
|
||||||
for (const category of app.categories) {
|
|
||||||
c[category] = c[category] ? c[category] + 1 : 1;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}, {} as Record<string, number>);
|
|
||||||
|
|
||||||
return {
|
|
||||||
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getData, Page: LegacyPage, getLayout: null })<"P">;
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null })<"L">;
|
|
||||||
@@ -1,14 +1,11 @@
|
|||||||
import LegacyPage from "@pages/apps/installed/[category]";
|
import Page from "@pages/apps/installed/[category]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { notFound } from "next/navigation";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import { AppCategories } from "@calcom/prisma/enums";
|
|
||||||
|
|
||||||
const querySchema = z.object({
|
import { getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps";
|
||||||
category: z.nativeEnum(AppCategories),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
@@ -17,20 +14,6 @@ export const generateMetadata = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
const p = querySchema.safeParse(params);
|
|
||||||
|
|
||||||
if (!p.success) {
|
export default WithLayout({ getLayout: null, getData, Page });
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
category: p.data.category,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
|
|
||||||
await getPageProps({ params });
|
|
||||||
|
|
||||||
return <LegacyPage />;
|
|
||||||
}
|
|
||||||
@@ -1,17 +1,12 @@
|
|||||||
import AppsPage from "@pages/apps";
|
import AppsPage from "@pages/apps";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
|
|
||||||
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
|
||||||
import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import type { AppCategories } from "@calcom/prisma/enums";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { getServerSideProps } from "@lib/apps/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () => {
|
export const generateMetadata = async () => {
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
@@ -20,44 +15,4 @@ export const generateMetadata = async () => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
export default WithLayout({ getLayout, getData: withAppDirSsr(getServerSideProps), Page: AppsPage });
|
||||||
const ssr = await ssrInit(ctx);
|
|
||||||
|
|
||||||
const session = await getServerSession({ req: ctx.req });
|
|
||||||
|
|
||||||
let appStore, userAdminTeams: UserAdminTeams;
|
|
||||||
if (session?.user?.id) {
|
|
||||||
userAdminTeams = await getUserAdminTeams({ userId: session.user.id, getUserInfo: true });
|
|
||||||
appStore = await getAppRegistryWithCredentials(session.user.id, userAdminTeams);
|
|
||||||
} else {
|
|
||||||
appStore = await getAppRegistry();
|
|
||||||
userAdminTeams = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
const categoryQuery = appStore.map(({ categories }) => ({
|
|
||||||
categories: categories || [],
|
|
||||||
}));
|
|
||||||
|
|
||||||
const categories = categoryQuery.reduce((c, app) => {
|
|
||||||
for (const category of app.categories) {
|
|
||||||
c[category] = c[category] ? c[category] + 1 : 1;
|
|
||||||
}
|
|
||||||
return c;
|
|
||||||
}, {} as Record<string, number>);
|
|
||||||
|
|
||||||
return {
|
|
||||||
categories: Object.entries(categories)
|
|
||||||
.map(([name, count]): { name: AppCategories; count: number } => ({
|
|
||||||
name: name as AppCategories,
|
|
||||||
count,
|
|
||||||
}))
|
|
||||||
.sort(function (a, b) {
|
|
||||||
return b.count - a.count;
|
|
||||||
}),
|
|
||||||
appStore,
|
|
||||||
userAdminTeams,
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData, Page: AppsPage });
|
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import SetNewUserPassword from "@pages/auth/forgot-password/[id]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/forgot-password/[id]/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("reset_password"),
|
||||||
|
(t) => t("change_your_password")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: SetNewUserPassword,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
import ForgotPassword from "@pages/auth/forgot-password";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/forgot-password/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("reset_password"),
|
||||||
|
(t) => t("change_your_password")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: ForgotPassword,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import Login from "@pages/auth/login";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/login/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${t("login")} | ${APP_NAME}`,
|
||||||
|
(t) => t("login")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: Login,
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import Logout from "@pages/auth/logout";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/logout/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, Page: Logout, getData: withAppDirSsr(getServerSideProps) })<"P">;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Setup from "@pages/auth/setup";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/setup/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: Setup,
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
import signin from "@pages/auth/signin";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/signin/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: signin,
|
||||||
|
// @ts-expect-error TODO: fix this
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import Provider from "@pages/auth/sso/[provider]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetServerSidePropsType } from "next";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/sso/[provider]/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: Provider,
|
||||||
|
getData: withAppDirSsr<InferGetServerSidePropsType<typeof getServerSideProps>>(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import DirectSSOLogin from "@pages/auth/sso/direct";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/sso/direct/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: DirectSSOLogin,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import VerifyEmailPage from "@pages/auth/verify-email";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export const generateMetadata = async () => {
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => t("check_your_email"),
|
||||||
|
(t) => t("check_your_email")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: VerifyEmailPage,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import VerifyPage from "@pages/auth/verify";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@server/lib/auth/verify/getServerSideProps";
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
getLayout: null,
|
||||||
|
Page: VerifyPage,
|
||||||
|
getData: withAppDirSsr(getServerSideProps),
|
||||||
|
})<"P">;
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import OldPage from "@pages/booking/[uid]";
|
import { type PageProps, getServerSideProps } from "@pages/booking/[uid]";
|
||||||
|
import OldPage from "@pages/booking/[uid]/embed";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import { getData } from "../page";
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
const getEmbedData = withEmbedSsrAppDir(getData);
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
// @ts-expect-error Type '(context: GetServerSidePropsContext) => Promise<any>' is not assignable to type '(arg: {
|
|
||||||
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage });
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: OldPage });
|
||||||
@@ -1,204 +1,36 @@
|
|||||||
import OldPage from "@pages/booking/[uid]";
|
import OldPage, { type PageProps } from "@pages/booking/[uid]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { notFound } from "next/navigation";
|
import { cookies, headers } from "next/headers";
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { BookingStatus } from "@calcom/prisma/enums";
|
||||||
import { getBookingWithResponses } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import { parseRecurringEvent } from "@calcom/lib";
|
|
||||||
import { getDefaultEvent } from "@calcom/lib/defaultEvents";
|
|
||||||
import { maybeGetBookingUidFromSeat } from "@calcom/lib/server/maybeGetBookingUidFromSeat";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
||||||
|
|
||||||
import { getRecurringBookings, handleSeatsEventTypeOnBooking, getEventTypesFromDB } from "@lib/booking";
|
import { getServerSideProps } from "@lib/booking/[uid]/getServerSideProps";
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
const stringToBoolean = z
|
searchParams,
|
||||||
.string()
|
}: {
|
||||||
.optional()
|
params: Params;
|
||||||
.transform((val) => val === "true");
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
const querySchema = z.object({
|
const { bookingInfo, eventType, recurringBookings } = await getData(
|
||||||
uid: z.string(),
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
email: z.string().optional(),
|
|
||||||
eventTypeSlug: z.string().optional(),
|
|
||||||
cancel: stringToBoolean,
|
|
||||||
allRemainingBookings: stringToBoolean,
|
|
||||||
changes: stringToBoolean,
|
|
||||||
reschedule: stringToBoolean,
|
|
||||||
isSuccessBookingPage: stringToBoolean,
|
|
||||||
formerTime: z.string().optional(),
|
|
||||||
seatReferenceUid: z.string().optional(),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
|
||||||
await _generateMetadata(
|
|
||||||
() => "",
|
|
||||||
() => ""
|
|
||||||
);
|
);
|
||||||
|
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
|
||||||
|
|
||||||
export const getData = async (context: GetServerSidePropsContext) => {
|
return await _generateMetadata(
|
||||||
const ssr = await ssrInit(context);
|
(t) =>
|
||||||
const session = await getServerSession(context);
|
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
|
||||||
let tz: string | null = null;
|
(t) =>
|
||||||
let userTimeFormat: number | null = null;
|
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`)
|
||||||
let requiresLoginToUpdate = false;
|
);
|
||||||
if (session) {
|
|
||||||
const user = await ssr.viewer.me.fetch();
|
|
||||||
tz = user.timeZone;
|
|
||||||
userTimeFormat = user.timeFormat;
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedQuery = querySchema.safeParse(context.query);
|
|
||||||
|
|
||||||
if (!parsedQuery.success) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { uid, eventTypeSlug, seatReferenceUid } = parsedQuery.data;
|
|
||||||
|
|
||||||
const { uid: maybeUid } = await maybeGetBookingUidFromSeat(prisma, uid);
|
|
||||||
const bookingInfoRaw = await prisma.booking.findFirst({
|
|
||||||
where: {
|
|
||||||
uid: maybeUid,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
title: true,
|
|
||||||
id: true,
|
|
||||||
uid: true,
|
|
||||||
description: true,
|
|
||||||
customInputs: true,
|
|
||||||
smsReminderNumber: true,
|
|
||||||
recurringEventId: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
location: true,
|
|
||||||
status: true,
|
|
||||||
metadata: true,
|
|
||||||
cancellationReason: true,
|
|
||||||
responses: true,
|
|
||||||
rejectionReason: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
username: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
attendees: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
eventTypeId: true,
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
eventName: true,
|
|
||||||
slug: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
seatsReferences: {
|
|
||||||
select: {
|
|
||||||
referenceUid: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!bookingInfoRaw) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventTypeRaw = !bookingInfoRaw.eventTypeId
|
|
||||||
? getDefaultEvent(eventTypeSlug || "")
|
|
||||||
: await getEventTypesFromDB(bookingInfoRaw.eventTypeId);
|
|
||||||
if (!eventTypeRaw) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventTypeRaw.seatsPerTimeSlot && !seatReferenceUid && !session) {
|
|
||||||
requiresLoginToUpdate = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookingInfo = getBookingWithResponses(bookingInfoRaw);
|
|
||||||
// @NOTE: had to do this because Server side cant return [Object objects]
|
|
||||||
// probably fixable with json.stringify -> json.parse
|
|
||||||
bookingInfo["startTime"] = (bookingInfo?.startTime as Date)?.toISOString() as unknown as Date;
|
|
||||||
bookingInfo["endTime"] = (bookingInfo?.endTime as Date)?.toISOString() as unknown as Date;
|
|
||||||
|
|
||||||
eventTypeRaw.users = !!eventTypeRaw.hosts?.length
|
|
||||||
? eventTypeRaw.hosts.map((host) => host.user)
|
|
||||||
: eventTypeRaw.users;
|
|
||||||
|
|
||||||
if (!eventTypeRaw.users.length) {
|
|
||||||
if (!eventTypeRaw.owner) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
eventTypeRaw.users.push({
|
|
||||||
...eventTypeRaw.owner,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const eventType = {
|
|
||||||
...eventTypeRaw,
|
|
||||||
periodStartDate: eventTypeRaw.periodStartDate?.toString() ?? null,
|
|
||||||
periodEndDate: eventTypeRaw.periodEndDate?.toString() ?? null,
|
|
||||||
metadata: EventTypeMetaDataSchema.parse(eventTypeRaw.metadata),
|
|
||||||
recurringEvent: parseRecurringEvent(eventTypeRaw.recurringEvent),
|
|
||||||
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
|
||||||
};
|
|
||||||
|
|
||||||
const profile = {
|
|
||||||
name: eventType.team?.name || eventType.users[0]?.name || null,
|
|
||||||
email: eventType.team ? null : eventType.users[0].email || null,
|
|
||||||
theme: (!eventType.team?.name && eventType.users[0]?.theme) || null,
|
|
||||||
brandColor: eventType.team ? null : eventType.users[0].brandColor || null,
|
|
||||||
darkBrandColor: eventType.team ? null : eventType.users[0].darkBrandColor || null,
|
|
||||||
slug: eventType.team?.slug || eventType.users[0]?.username || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (bookingInfo !== null && eventType.seatsPerTimeSlot) {
|
|
||||||
await handleSeatsEventTypeOnBooking(eventType, bookingInfo, seatReferenceUid, session?.user.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const payment = await prisma.payment.findFirst({
|
|
||||||
where: {
|
|
||||||
bookingId: bookingInfo.id,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
success: true,
|
|
||||||
refunded: true,
|
|
||||||
currency: true,
|
|
||||||
amount: true,
|
|
||||||
paymentOption: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
themeBasis: eventType.team ? eventType.team.slug : eventType.users[0]?.username,
|
|
||||||
hideBranding: eventType.team ? eventType.team.hideBranding : eventType.users[0].hideBranding,
|
|
||||||
profile,
|
|
||||||
eventType,
|
|
||||||
recurringBookings: await getRecurringBookings(bookingInfo.recurringEventId),
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
dynamicEventName: bookingInfo?.eventType?.eventName || "",
|
|
||||||
bookingInfo,
|
|
||||||
paymentStatus: payment,
|
|
||||||
...(tz && { tz }),
|
|
||||||
userTimeFormat,
|
|
||||||
requiresLoginToUpdate,
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ts-expect-error Argument of type '{ req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }' is not assignable to parameter of type 'GetServerSidePropsContext'.
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null, getData, Page: OldPage });
|
export default WithLayout({ getLayout: null, getData, Page: OldPage });
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
|
||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
|
||||||
|
|
||||||
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
|
||||||
|
|
||||||
const querySchema = z.object({
|
|
||||||
status: z.enum(validStatuses),
|
|
||||||
});
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
|
||||||
await _generateMetadata(
|
|
||||||
(t) => `${APP_NAME} | ${t("bookings")}`,
|
|
||||||
() => ""
|
|
||||||
);
|
|
||||||
|
|
||||||
export const generateStaticParams = async () => {
|
|
||||||
return validStatuses.map((status) => ({ status }));
|
|
||||||
};
|
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const parsedParams = querySchema.safeParse(ctx.params);
|
|
||||||
|
|
||||||
if (!parsedParams.success) {
|
|
||||||
notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const ssg = await ssgInit(ctx);
|
|
||||||
|
|
||||||
return {
|
|
||||||
status: parsedParams.data.status,
|
|
||||||
dehydratedState: ssg.dehydrate(),
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData })<"L">;
|
|
||||||
|
|
||||||
export const dynamic = "force-static";
|
|
||||||
@@ -1 +1,29 @@
|
|||||||
export { default } from "@pages/bookings/[status]";
|
import Page from "@pages/bookings/[status]";
|
||||||
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import type { InferGetStaticPropsType } from "next";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
|
|
||||||
|
import { getStaticProps } from "@lib/bookings/[status]/getStaticProps";
|
||||||
|
|
||||||
|
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
|
||||||
|
|
||||||
|
type Y = InferGetStaticPropsType<typeof getStaticProps>;
|
||||||
|
const getData = withAppDirSsg<Y>(getStaticProps);
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => `${APP_NAME} | ${t("bookings")}`,
|
||||||
|
() => ""
|
||||||
|
);
|
||||||
|
|
||||||
|
export const generateStaticParams = async () => {
|
||||||
|
return validStatuses.map((status) => ({ status }));
|
||||||
|
};
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout, getData, Page })<"P">;
|
||||||
|
|
||||||
|
export const dynamic = "force-static";
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
import LegacyPage from "@pages/connect-and-join";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, Page: LegacyPage })<"P">;
|
||||||
@@ -1,27 +1,21 @@
|
|||||||
import LegacyPage, { type PageProps } from "@pages/d/[link]/[slug]";
|
import LegacyPage from "@pages/d/[link]/[slug]";
|
||||||
import { withAppDir } from "app/AppDirSSRHOC";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { Params, SearchParams } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import { getBookingForReschedule, getMultipleDurationValue } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import type { GetBookingType } from "@calcom/features/bookings/lib/get-booking";
|
|
||||||
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
|
|
||||||
import slugify from "@calcom/lib/slugify";
|
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
import { getServerSideProps } from "@lib/d/[link]/[slug]/getServerSideProps";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
searchParams,
|
||||||
const pageProps = await getPageProps(
|
}: {
|
||||||
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
|
params: Params;
|
||||||
);
|
searchParams: SearchParams;
|
||||||
|
}) => {
|
||||||
|
const pageProps = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
|
||||||
|
|
||||||
const { entity, booking, user, slug, isTeamEvent } = pageProps;
|
const { entity, booking, user, slug, isTeamEvent } = pageProps;
|
||||||
const rescheduleUid = booking?.uid;
|
const rescheduleUid = booking?.uid;
|
||||||
@@ -38,95 +32,5 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getPageProps(context: GetServerSidePropsContext) {
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
const session = await getServerSession({ req: context.req });
|
|
||||||
const { link, slug } = paramsSchema.parse(context.params);
|
|
||||||
const { rescheduleUid, duration: queryDuration } = context.query;
|
|
||||||
const { currentOrgDomain, isValidOrgDomain } = orgDomainConfig(context.req);
|
|
||||||
const org = isValidOrgDomain ? currentOrgDomain : null;
|
|
||||||
|
|
||||||
const hashedLink = await prisma.hashedLink.findUnique({
|
|
||||||
where: {
|
|
||||||
link,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
eventTypeId: true,
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
users: {
|
|
||||||
select: {
|
|
||||||
username: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
const username = hashedLink?.eventType.users[0]?.username;
|
|
||||||
|
|
||||||
if (!hashedLink || !username) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const user = await prisma.user.findFirst({
|
|
||||||
where: {
|
|
||||||
username,
|
|
||||||
organization: isValidOrgDomain
|
|
||||||
? {
|
|
||||||
slug: currentOrgDomain,
|
|
||||||
}
|
|
||||||
: null,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
away: true,
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
let booking: GetBookingType | null = null;
|
|
||||||
if (rescheduleUid) {
|
|
||||||
booking = await getBookingForReschedule(`${rescheduleUid}`, session?.user?.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
const isTeamEvent = !!hashedLink.eventType?.team?.id;
|
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
|
|
||||||
// We use this to both prefetch the query on the server,
|
|
||||||
// as well as to check if the event exist, so we c an show a 404 otherwise.
|
|
||||||
const eventData = await ssr.viewer.public.event.fetch({ username, eventSlug: slug, isTeamEvent, org });
|
|
||||||
|
|
||||||
if (!eventData) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
entity: eventData.entity,
|
|
||||||
duration: getMultipleDurationValue(eventData.metadata?.multipleDuration, queryDuration, eventData.length),
|
|
||||||
booking,
|
|
||||||
away: user?.away,
|
|
||||||
user: username,
|
|
||||||
slug,
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
isBrandingHidden: user?.hideBranding,
|
|
||||||
// Sending the team event from the server, because this template file
|
|
||||||
// is reused for both team and user events.
|
|
||||||
isTeamEvent,
|
|
||||||
hashedLink: link,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
const paramsSchema = z.object({ link: z.string(), slug: z.string().transform((s) => slugify(s)) });
|
|
||||||
|
|
||||||
// @ts-expect-error arg
|
|
||||||
const getData = withAppDir<PageProps>(getPageProps);
|
|
||||||
export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">;
|
export default WithLayout({ getLayout: null, Page: LegacyPage, getData })<"P">;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout })<"L">;
|
|
||||||
@@ -1,4 +1,7 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
||||||
import EnterprisePage from "@components/EnterprisePage";
|
import EnterprisePage from "@components/EnterprisePage";
|
||||||
|
|
||||||
@@ -8,4 +11,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("create_your_org_description")
|
(t) => t("create_your_org_description")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default EnterprisePage;
|
export default WithLayout({ getLayout, Page: EnterprisePage })<"P">;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout })<"L">;
|
|
||||||
@@ -1,5 +1,8 @@
|
|||||||
import EventTypes from "@pages/event-types";
|
import EventTypes from "@pages/event-types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -7,4 +10,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("event_types_page_subtitle")
|
(t) => t("event_types_page_subtitle")
|
||||||
);
|
);
|
||||||
|
|
||||||
export default EventTypes;
|
export default WithLayout({ getLayout, Page: EventTypes })<"P">;
|
||||||
@@ -1,57 +1,7 @@
|
|||||||
import LegacyPage from "@pages/getting-started/[[...step]]";
|
import Page from "@pages/getting-started/[[...step]]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
import { getServerSideProps } from "@lib/getting-started/[[...step]]/getServerSideProps";
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
export default WithLayout({ getLayout: null, getData: withAppDirSsr(getServerSideProps), Page });
|
||||||
|
|
||||||
const getData = async (ctx: GetServerSidePropsContext) => {
|
|
||||||
const session = await getServerSession({ req: ctx.req });
|
|
||||||
|
|
||||||
if (!session?.user?.id) {
|
|
||||||
return redirect("/auth/login");
|
|
||||||
}
|
|
||||||
const ssr = await ssrInit(ctx);
|
|
||||||
await ssr.viewer.me.prefetch();
|
|
||||||
|
|
||||||
const user = await prisma.user.findUnique({
|
|
||||||
where: {
|
|
||||||
id: session.user.id,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
completedOnboarding: true,
|
|
||||||
teams: {
|
|
||||||
select: {
|
|
||||||
accepted: true,
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
name: true,
|
|
||||||
logo: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!user) {
|
|
||||||
throw new Error("User from session not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (user.completedOnboarding) {
|
|
||||||
redirect("/event-types");
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false,
|
|
||||||
requiresLicense: false,
|
|
||||||
themeBasis: null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null, getData, Page: LegacyPage });
|
|
||||||
@@ -1,10 +1,11 @@
|
|||||||
import LegacyPage from "@pages/insights/index";
|
import LegacyPage, { getServerSideProps } from "@pages/insights/index";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
|
||||||
|
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -12,15 +13,6 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("insights_subtitle")
|
(t) => t("insights_subtitle")
|
||||||
);
|
);
|
||||||
|
|
||||||
async function getData() {
|
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
|
||||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
|
||||||
const flags = await getFeatureFlagMap(prisma);
|
|
||||||
|
|
||||||
if (flags.insights === false) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout, getData, Page: LegacyPage });
|
export default WithLayout({ getLayout, getData, Page: LegacyPage });
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
|
||||||
|
import Page from "@pages/org/[orgSlug]/[user]/[type]/embed";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import Page, { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData, isBookingPage: true, Page });
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
|
||||||
|
import Page from "@pages/org/[orgSlug]/[user]/embed";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
import Page, { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData, isBookingPage: true, Page });
|
||||||
@@ -1,18 +1,10 @@
|
|||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import { redirect, notFound } from "next/navigation";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import PaymentPage from "@calcom/features/ee/payments/components/PaymentPage";
|
import PaymentPage from "@calcom/features/ee/payments/components/PaymentPage";
|
||||||
import { getClientSecretFromPayment } from "@calcom/features/ee/payments/pages/getClientSecretFromPayment";
|
import { getServerSideProps, type PaymentPageProps } from "@calcom/features/ee/payments/pages/payment";
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import prisma from "@calcom/prisma";
|
|
||||||
import { BookingStatus } from "@calcom/prisma/enums";
|
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -21,147 +13,6 @@ export const generateMetadata = async () =>
|
|||||||
() => ""
|
() => ""
|
||||||
);
|
);
|
||||||
|
|
||||||
const querySchema = z.object({
|
const getData = withAppDirSsr<PaymentPageProps>(getServerSideProps);
|
||||||
uid: z.string(),
|
|
||||||
});
|
|
||||||
|
|
||||||
async function getData(context: GetServerSidePropsContext) {
|
|
||||||
const session = await getServerSession({ req: context.req });
|
|
||||||
|
|
||||||
if (!session?.user?.id) {
|
|
||||||
return redirect("/auth/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
await ssr.viewer.me.prefetch();
|
|
||||||
|
|
||||||
const { uid } = querySchema.parse(context.params);
|
|
||||||
const rawPayment = await prisma.payment.findFirst({
|
|
||||||
where: {
|
|
||||||
uid,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
data: true,
|
|
||||||
success: true,
|
|
||||||
uid: true,
|
|
||||||
refunded: true,
|
|
||||||
bookingId: true,
|
|
||||||
appId: true,
|
|
||||||
amount: true,
|
|
||||||
currency: true,
|
|
||||||
paymentOption: true,
|
|
||||||
booking: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
uid: true,
|
|
||||||
description: true,
|
|
||||||
title: true,
|
|
||||||
startTime: true,
|
|
||||||
endTime: true,
|
|
||||||
attendees: {
|
|
||||||
select: {
|
|
||||||
email: true,
|
|
||||||
name: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
eventTypeId: true,
|
|
||||||
location: true,
|
|
||||||
status: true,
|
|
||||||
rejectionReason: true,
|
|
||||||
cancellationReason: true,
|
|
||||||
eventType: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
title: true,
|
|
||||||
description: true,
|
|
||||||
length: true,
|
|
||||||
eventName: true,
|
|
||||||
requiresConfirmation: true,
|
|
||||||
userId: true,
|
|
||||||
metadata: true,
|
|
||||||
users: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
username: true,
|
|
||||||
hideBranding: true,
|
|
||||||
theme: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
team: {
|
|
||||||
select: {
|
|
||||||
name: true,
|
|
||||||
hideBranding: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
price: true,
|
|
||||||
currency: true,
|
|
||||||
successRedirectUrl: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!rawPayment) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { data, booking: _booking, ...restPayment } = rawPayment;
|
|
||||||
|
|
||||||
const payment = {
|
|
||||||
...restPayment,
|
|
||||||
data: data as Record<string, unknown>,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!_booking) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const { startTime, endTime, eventType, ...restBooking } = _booking;
|
|
||||||
const booking = {
|
|
||||||
...restBooking,
|
|
||||||
startTime: startTime.toString(),
|
|
||||||
endTime: endTime.toString(),
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!eventType) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (eventType.users.length === 0 && !!!eventType.team) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const [user] = eventType?.users.length
|
|
||||||
? eventType.users
|
|
||||||
: [{ name: null, theme: null, hideBranding: null, username: null }];
|
|
||||||
const profile = {
|
|
||||||
name: eventType.team?.name || user?.name || null,
|
|
||||||
theme: (!eventType.team?.name && user?.theme) || null,
|
|
||||||
hideBranding: eventType.team?.hideBranding || user?.hideBranding || null,
|
|
||||||
};
|
|
||||||
|
|
||||||
if (
|
|
||||||
([BookingStatus.CANCELLED, BookingStatus.REJECTED] as BookingStatus[]).includes(
|
|
||||||
booking.status as BookingStatus
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
return redirect(`/booking/${booking.uid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
user,
|
|
||||||
eventType: {
|
|
||||||
...eventType,
|
|
||||||
metadata: EventTypeMetaDataSchema.parse(eventType.metadata),
|
|
||||||
},
|
|
||||||
booking,
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
payment,
|
|
||||||
clientSecret: getClientSecretFromPayment(payment),
|
|
||||||
profile,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null, getData, Page: PaymentPage });
|
export default WithLayout({ getLayout: null, getData, Page: PaymentPage });
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
import { getServerSideProps } from "@pages/reschedule/[uid]";
|
import { getServerSideProps } from "@pages/reschedule/[uid]";
|
||||||
import { withAppDir } from "app/AppDirSSRHOC";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { SearchParams } from "app/_types";
|
||||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||||
import { cookies, headers } from "next/headers";
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
@@ -8,12 +9,12 @@ import withEmbedSsr from "@lib/withEmbedSsr";
|
|||||||
|
|
||||||
type PageProps = Readonly<{
|
type PageProps = Readonly<{
|
||||||
params: Params;
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const Page = async ({ params }: PageProps) => {
|
const Page = async ({ params, searchParams }: PageProps) => {
|
||||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params);
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }'
|
await withAppDirSsr(withEmbedSsr(getServerSideProps))(legacyCtx);
|
||||||
await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx);
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import OldPage, { getServerSideProps as _getServerSideProps } from "@pages/reschedule/[uid]";
|
import { getServerSideProps } from "@pages/reschedule/[uid]";
|
||||||
import { withAppDir } from "app/AppDirSSRHOC";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { SearchParams } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||||
import { headers, cookies } from "next/headers";
|
import { headers, cookies } from "next/headers";
|
||||||
@@ -14,17 +15,17 @@ export const generateMetadata = async () =>
|
|||||||
|
|
||||||
type PageProps = Readonly<{
|
type PageProps = Readonly<{
|
||||||
params: Params;
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
const getData = withAppDir(_getServerSideProps);
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
|
|
||||||
const Page = async ({ params }: PageProps) => {
|
const Page = async ({ params, searchParams }: PageProps) => {
|
||||||
const legacyCtx = buildLegacyCtx(headers(), cookies(), params);
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
|
||||||
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }'
|
|
||||||
await getData(legacyCtx);
|
await getData(legacyCtx);
|
||||||
|
|
||||||
return <OldPage />;
|
return null;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default Page;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import { redirect } from "next/navigation";
|
||||||
|
|
||||||
|
const getPageProps = () => {
|
||||||
|
return redirect(`/apps/routing-forms/forms`);
|
||||||
|
};
|
||||||
|
const Page = () => {
|
||||||
|
getPageProps();
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -0,0 +1,36 @@
|
|||||||
|
import { type SearchParams } from "app/_types";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
import { redirect } from "next/navigation";
|
||||||
|
import z from "zod";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
const paramsSchema = z
|
||||||
|
.object({
|
||||||
|
pages: z.array(z.string()),
|
||||||
|
})
|
||||||
|
.catch({
|
||||||
|
pages: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
const getPageProps = async (context: GetServerSidePropsContext) => {
|
||||||
|
const { pages } = paramsSchema.parse(context.params);
|
||||||
|
|
||||||
|
return redirect(`/apps/routing-forms/${pages.length ? pages.join("/") : ""}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
type PageProps = Readonly<{
|
||||||
|
params: Params;
|
||||||
|
searchParams: SearchParams;
|
||||||
|
}>;
|
||||||
|
|
||||||
|
const Page = async ({ params, searchParams }: PageProps) => {
|
||||||
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
await getPageProps(legacyCtx as unknown as GetServerSidePropsContext);
|
||||||
|
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default Page;
|
||||||
@@ -1,36 +1,32 @@
|
|||||||
import { getServerCaller } from "app/_trpc/serverClient";
|
|
||||||
import { type Params } from "app/_types";
|
import { type Params } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { cookies, headers } from "next/headers";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
|
|
||||||
import Page from "@calcom/features/ee/users/pages/users-edit-view";
|
import Page from "@calcom/features/ee/users/pages/users-edit-view";
|
||||||
import prisma from "@calcom/prisma";
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
const userIdSchema = z.object({ id: z.coerce.number() });
|
const userIdSchema = z.object({ id: z.coerce.number() });
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: { params: Params }) => {
|
export const generateMetadata = async ({ params }: { params: Params }) => {
|
||||||
const input = userIdSchema.safeParse(params);
|
const input = userIdSchema.safeParse(params);
|
||||||
|
|
||||||
let title = "";
|
|
||||||
if (!input.success) {
|
if (!input.success) {
|
||||||
title = "Editing user";
|
return await _generateMetadata(
|
||||||
} else {
|
() => "",
|
||||||
const req = {
|
() => "Here you can edit a current user."
|
||||||
headers: headers(),
|
);
|
||||||
cookies: cookies(),
|
|
||||||
};
|
|
||||||
|
|
||||||
// @ts-expect-error Type '{ headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }' is not assignable to type 'NextApiRequest'
|
|
||||||
const data = await getServerCaller({ req, prisma }).viewer.users.get({ userId: input.data.id });
|
|
||||||
const { user } = data;
|
|
||||||
title = `Editing user: ${user.username}`;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const userId = input.data.id;
|
||||||
|
const { trpc } = await import("@calcom/trpc");
|
||||||
|
const [data] = trpc.viewer.users.get.useSuspenseQuery({ userId });
|
||||||
|
const { user } = data;
|
||||||
|
const title = `Editing user: ${user.username}`;
|
||||||
|
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => title,
|
() => title,
|
||||||
() => "Here you can edit a current user."
|
() => "Here you can edit a current user."
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Page;
|
export default WithLayout({ getLayout, Page })<"P">;
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import Page from "@calcom/features/ee/users/pages/users-add-view";
|
import Page from "@calcom/features/ee/users/pages/users-add-view";
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -8,4 +10,4 @@ export const generateMetadata = async () =>
|
|||||||
() => "Here you can add a new user."
|
() => "Here you can add a new user."
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Page;
|
export default WithLayout({ getLayout, Page })<"P">;
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
|
||||||
|
|
||||||
export default WithLayout({ getLayout })<"L">;
|
|
||||||
@@ -1,6 +1,8 @@
|
|||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
import Page from "@calcom/features/ee/users/pages/users-listing-view";
|
import Page from "@calcom/features/ee/users/pages/users-listing-view";
|
||||||
|
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -8,4 +10,4 @@ export const generateMetadata = async () =>
|
|||||||
() => "A list of all the users in your account including their name, title, email and role."
|
() => "A list of all the users in your account including their name, title, email and role."
|
||||||
);
|
);
|
||||||
|
|
||||||
export default Page;
|
export default WithLayout({ getLayout, Page })<"P">;
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import LegacyPage, {
|
import LegacyPage, {
|
||||||
buildWrappedOnboardTeamMembersPage,
|
buildWrappedOnboardTeamMembersPage,
|
||||||
} from "@pages/settings/organizations/[id]/onboard-admins";
|
} from "@pages/settings/organizations/[id]/onboard-members";
|
||||||
import { type Params } from "app/_types";
|
import { type Params } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { headers } from "next/headers";
|
import { headers } from "next/headers";
|
||||||
|
|||||||
@@ -1,10 +1,9 @@
|
|||||||
import LegacyPage, { WrappedCreateNewOrganizationPage } from "@pages/settings/organizations/new/index";
|
import LegacyPage, { getServerSideProps, LayoutWrapperAppDir } from "@pages/settings/organizations/new/index";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
|
|
||||||
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
|
import { type inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -12,23 +11,8 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("organizations_description")
|
(t) => t("organizations_description")
|
||||||
);
|
);
|
||||||
|
|
||||||
const getPageProps = async (context: GetServerSidePropsContext) => {
|
|
||||||
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
|
|
||||||
const flags = await getFeatureFlagMap(prisma);
|
|
||||||
// Check if organizations are enabled
|
|
||||||
if (flags["organizations"] !== true) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
const querySlug = context.query.slug as string;
|
|
||||||
|
|
||||||
return {
|
|
||||||
querySlug: querySlug ?? null,
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export default WithLayout({
|
export default WithLayout({
|
||||||
getLayout: WrappedCreateNewOrganizationPage,
|
getLayout: LayoutWrapperAppDir,
|
||||||
Page: LegacyPage,
|
Page: LegacyPage,
|
||||||
getData: getPageProps,
|
getData: withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps),
|
||||||
});
|
});
|
||||||
@@ -0,0 +1,20 @@
|
|||||||
|
import LegacyPage, { type SignupProps } from "@pages/signup";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
import { getServerSideProps } from "@lib/signup/getServerSideProps";
|
||||||
|
|
||||||
|
export const generateMetadata = async () =>
|
||||||
|
await _generateMetadata(
|
||||||
|
(t) => t("sign_up"),
|
||||||
|
(t) => t("sign_up")
|
||||||
|
);
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<SignupProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: LegacyPage,
|
||||||
|
getLayout: null,
|
||||||
|
getData,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import LegacyPage, { getServerSideProps } from "@pages/team/[slug]/[type]/embed";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
// @ts-expect-error Type is missing the following properties from type: entity, duration, booking, away, and 7 more.
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import LegacyPage, { type PageProps, getServerSideProps } from "@pages/team/[slug]/[type]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
|
||||||
|
const props = await getData(legacyCtx as unknown as GetServerSidePropsContext);
|
||||||
|
const { entity, user, slug, booking } = props;
|
||||||
|
const { trpc } = await import("@calcom/trpc");
|
||||||
|
const { data: event } = trpc.viewer.public.event.useQuery(
|
||||||
|
{ username: user, eventSlug: slug, isTeamEvent: false, org: entity.orgSlug ?? null },
|
||||||
|
{ refetchOnWindowFocus: false }
|
||||||
|
);
|
||||||
|
|
||||||
|
const profileName = event?.profile?.name ?? "";
|
||||||
|
const title = event?.title ?? "";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
(t) => `${booking?.uid && !!booking ? t("reschedule") : ""} ${title} | ${profileName}`,
|
||||||
|
(t) => `${booking?.uid ? t("reschedule") : ""} ${title}`
|
||||||
|
);
|
||||||
|
};
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: LegacyPage,
|
||||||
|
getData,
|
||||||
|
getLayout: null,
|
||||||
|
isBookingPage: true,
|
||||||
|
})<"P">;
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
import LegacyPage, { type PageProps, getServerSideProps } from "@pages/team/[slug]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import withEmbedSsrAppDir from "app/WithEmbedSSR";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
const getEmbedData = withEmbedSsrAppDir(getData);
|
||||||
|
|
||||||
|
export default WithLayout({ getLayout: null, getData: getEmbedData, Page: LegacyPage })<"P">;
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
import LegacyPage, { getServerSideProps, type PageProps } from "@pages/team/[slug]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import { _generateMetadata } from "app/_utils";
|
||||||
|
import { WithLayout } from "app/layoutHOC";
|
||||||
|
import { type GetServerSidePropsContext } from "next";
|
||||||
|
import { cookies, headers } from "next/headers";
|
||||||
|
|
||||||
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
|
export const generateMetadata = async ({
|
||||||
|
params,
|
||||||
|
searchParams,
|
||||||
|
}: {
|
||||||
|
params: Record<string, string | string[]>;
|
||||||
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
|
}) => {
|
||||||
|
const props = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
|
);
|
||||||
|
const teamName = props.team.name || "Nameless Team";
|
||||||
|
|
||||||
|
return await _generateMetadata(
|
||||||
|
() => teamName,
|
||||||
|
() => teamName
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const getData = withAppDirSsr<PageProps>(getServerSideProps);
|
||||||
|
|
||||||
|
export default WithLayout({
|
||||||
|
Page: LegacyPage,
|
||||||
|
getData,
|
||||||
|
getLayout: null,
|
||||||
|
isBookingPage: true,
|
||||||
|
})<"P">;
|
||||||
@@ -1,13 +1,11 @@
|
|||||||
import OldPage from "@pages/teams/index";
|
import Page from "@pages/teams/index";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
import { getLayout } from "@calcom/features/MainLayoutAppDir";
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { getServerSideProps } from "@lib/teams/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -15,23 +13,4 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("create_manage_teams_collaborative")
|
(t) => t("create_manage_teams_collaborative")
|
||||||
);
|
);
|
||||||
|
|
||||||
async function getData(context: GetServerSidePropsContext) {
|
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout, Page })<"P">;
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
|
|
||||||
await ssr.viewer.me.prefetch();
|
|
||||||
|
|
||||||
const session = await getServerSession({
|
|
||||||
req: context.req,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
const token = Array.isArray(context.query.token) ? context.query.token[0] : context.query.token;
|
|
||||||
|
|
||||||
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
|
|
||||||
return redirect(callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login");
|
|
||||||
}
|
|
||||||
|
|
||||||
return { dehydratedState: ssr.dehydrate() };
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WithLayout({ getData, getLayout, Page: OldPage })<"P">;
|
|
||||||
@@ -1,15 +1,11 @@
|
|||||||
import OldPage from "@pages/video/[uid]";
|
import Page, { type JoinCallPageProps } from "@pages/video/[uid]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import MarkdownIt from "markdown-it";
|
|
||||||
import type { GetServerSidePropsContext } from "next";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
|
|
||||||
import { APP_NAME } from "@calcom/lib/constants";
|
import { APP_NAME } from "@calcom/lib/constants";
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -17,86 +13,6 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("quick_video_meeting")
|
(t) => t("quick_video_meeting")
|
||||||
);
|
);
|
||||||
|
|
||||||
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
|
const getData = withAppDirSsr<JoinCallPageProps>(getServerSideProps);
|
||||||
|
|
||||||
async function getData(context: GetServerSidePropsContext) {
|
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
||||||
const ssr = await ssrInit(context);
|
|
||||||
|
|
||||||
const booking = await prisma.booking.findUnique({
|
|
||||||
where: {
|
|
||||||
uid: context.query.uid as string,
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
...bookingMinimalSelect,
|
|
||||||
uid: true,
|
|
||||||
description: true,
|
|
||||||
isRecorded: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
id: true,
|
|
||||||
timeZone: true,
|
|
||||||
name: true,
|
|
||||||
email: true,
|
|
||||||
organization: {
|
|
||||||
select: {
|
|
||||||
calVideoLogo: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
references: {
|
|
||||||
select: {
|
|
||||||
uid: true,
|
|
||||||
type: true,
|
|
||||||
meetingUrl: true,
|
|
||||||
meetingPassword: true,
|
|
||||||
},
|
|
||||||
where: {
|
|
||||||
type: "daily_video",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!booking || booking.references.length === 0 || !booking.references[0].meetingUrl) {
|
|
||||||
return redirect("/video/no-meeting-found");
|
|
||||||
}
|
|
||||||
|
|
||||||
//daily.co calls have a 60 minute exit buffer when a user enters a call when it's not available it will trigger the modals
|
|
||||||
const now = new Date();
|
|
||||||
const exitDate = new Date(now.getTime() - 60 * 60 * 1000);
|
|
||||||
|
|
||||||
//find out if the meeting is in the past
|
|
||||||
const isPast = booking?.endTime <= exitDate;
|
|
||||||
if (isPast) {
|
|
||||||
return redirect(`/video/meeting-ended/${booking?.uid}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookingObj = Object.assign({}, booking, {
|
|
||||||
startTime: booking.startTime.toString(),
|
|
||||||
endTime: booking.endTime.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const session = await getServerSession({ req: context.req });
|
|
||||||
|
|
||||||
// set meetingPassword to null for guests
|
|
||||||
if (session?.user.id !== bookingObj.user?.id) {
|
|
||||||
bookingObj.references.forEach((bookRef) => {
|
|
||||||
bookRef.meetingPassword = null;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
meetingUrl: bookingObj.references[0].meetingUrl ?? "",
|
|
||||||
...(typeof bookingObj.references[0].meetingPassword === "string" && {
|
|
||||||
meetingPassword: bookingObj.references[0].meetingPassword,
|
|
||||||
}),
|
|
||||||
booking: {
|
|
||||||
...bookingObj,
|
|
||||||
...(bookingObj.description && { description: md.render(bookingObj.description) }),
|
|
||||||
},
|
|
||||||
dehydratedState: ssr.dehydrate(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
import OldPage from "@pages/video/meeting-ended/[uid]";
|
import Page from "@pages/video/meeting-ended/[uid]";
|
||||||
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
import { getServerSideProps } from "@lib/video/meeting-ended/[uid]/getServerSideProps";
|
||||||
|
|
||||||
export const generateMetadata = async () =>
|
export const generateMetadata = async () =>
|
||||||
await _generateMetadata(
|
await _generateMetadata(
|
||||||
@@ -12,41 +11,6 @@ export const generateMetadata = async () =>
|
|||||||
() => "Meeting Unavailable"
|
() => "Meeting Unavailable"
|
||||||
);
|
);
|
||||||
|
|
||||||
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
const booking = await prisma.booking.findUnique({
|
|
||||||
where: {
|
|
||||||
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
|
|
||||||
},
|
|
||||||
select: {
|
|
||||||
...bookingMinimalSelect,
|
|
||||||
uid: true,
|
|
||||||
user: {
|
|
||||||
select: {
|
|
||||||
credentials: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
references: {
|
|
||||||
select: {
|
|
||||||
uid: true,
|
|
||||||
type: true,
|
|
||||||
meetingUrl: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!booking) {
|
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
||||||
return redirect("/video/no-meeting-found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookingObj = Object.assign({}, booking, {
|
|
||||||
startTime: booking.startTime.toString(),
|
|
||||||
endTime: booking.endTime.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
booking: bookingObj,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
|
||||||
@@ -1,15 +1,12 @@
|
|||||||
import OldPage from "@pages/video/meeting-not-started/[uid]";
|
import Page from "@pages/video/meeting-not-started/[uid]";
|
||||||
import { type Params } from "app/_types";
|
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||||
|
import type { PageProps } from "app/_types";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
|
||||||
import { redirect } from "next/navigation";
|
|
||||||
|
|
||||||
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
|
||||||
|
|
||||||
type PageProps = Readonly<{
|
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
|
||||||
params: Params;
|
|
||||||
}>;
|
|
||||||
|
|
||||||
export const generateMetadata = async ({ params }: PageProps) => {
|
export const generateMetadata = async ({ params }: PageProps) => {
|
||||||
const booking = await prisma.booking.findUnique({
|
const booking = await prisma.booking.findUnique({
|
||||||
@@ -25,26 +22,6 @@ export const generateMetadata = async ({ params }: PageProps) => {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
|
const getData = withAppDirSsr(getServerSideProps);
|
||||||
const booking = await prisma.booking.findUnique({
|
|
||||||
where: {
|
|
||||||
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
|
|
||||||
},
|
|
||||||
select: bookingMinimalSelect,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!booking) {
|
export default WithLayout({ getData, Page, getLayout: null })<"P">;
|
||||||
return redirect("/video/no-meeting-found");
|
|
||||||
}
|
|
||||||
|
|
||||||
const bookingObj = Object.assign({}, booking, {
|
|
||||||
startTime: booking.startTime.toString(),
|
|
||||||
endTime: booking.endTime.toString(),
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
booking: bookingObj,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export default WithLayout({ getData, Page: OldPage, getLayout: null })<"P">;
|
|
||||||
@@ -1,7 +1,7 @@
|
|||||||
import LegacyPage from "@pages/video/no-meeting-found";
|
import LegacyPage from "@pages/video/no-meeting-found";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
import type { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@ export const generateMetadata = async () =>
|
|||||||
(t) => t("no_meeting_found")
|
(t) => t("no_meeting_found")
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// ssr was added by Intuita, legacy page does not have it
|
||||||
const getData = async (context: GetServerSidePropsContext) => {
|
const getData = async (context: GetServerSidePropsContext) => {
|
||||||
const ssr = await ssrInit(context);
|
const ssr = await ssrInit(context);
|
||||||
|
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
|
import LegacyPage, { getStaticProps } from "@pages/workflows/[workflow]";
|
||||||
|
import { withAppDirSsg } from "app/WithAppDirSsg";
|
||||||
import { _generateMetadata } from "app/_utils";
|
import { _generateMetadata } from "app/_utils";
|
||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import { type GetServerSidePropsContext } from "next";
|
import { type GetServerSidePropsContext } from "next";
|
||||||
import { headers, cookies } from "next/headers";
|
import { headers, cookies } from "next/headers";
|
||||||
import { notFound } from "next/navigation";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import LegacyPage from "@calcom/features/ee/workflows/pages/workflow";
|
|
||||||
|
|
||||||
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
|
||||||
|
|
||||||
const querySchema = z.object({
|
export const generateMetadata = async ({
|
||||||
workflow: z.string(),
|
params,
|
||||||
});
|
searchParams,
|
||||||
|
}: {
|
||||||
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
|
params: Record<string, string | string[]>;
|
||||||
const { workflow } = await getProps(
|
searchParams: { [key: string]: string | string[] | undefined };
|
||||||
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
|
}) => {
|
||||||
|
const { workflow } = await getData(
|
||||||
|
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
|
||||||
);
|
);
|
||||||
return await _generateMetadata(
|
return await _generateMetadata(
|
||||||
() => workflow ?? "Untitled",
|
() => workflow ?? "Untitled",
|
||||||
@@ -23,19 +23,12 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
async function getProps(context: GetServerSidePropsContext) {
|
const getData = withAppDirSsg(getStaticProps);
|
||||||
const safeParams = querySchema.safeParse(context.params);
|
|
||||||
|
|
||||||
console.log("Built workflow page:", safeParams);
|
|
||||||
if (!safeParams.success) {
|
|
||||||
return notFound();
|
|
||||||
}
|
|
||||||
return { workflow: safeParams.data.workflow };
|
|
||||||
}
|
|
||||||
|
|
||||||
export const generateStaticParams = () => [];
|
export const generateStaticParams = () => [];
|
||||||
|
|
||||||
export default WithLayout({ getLayout: null, getData: getProps, Page: LegacyPage })<"P">;
|
// @ts-expect-error TODO: fix this
|
||||||
|
export default WithLayout({ getLayout: null, getData, Page: LegacyPage })<"P">;
|
||||||
export const dynamic = "force-static";
|
export const dynamic = "force-static";
|
||||||
// generate segments on demand
|
// generate segments on demand
|
||||||
export const dynamicParams = true;
|
export const dynamicParams = true;
|
||||||
|
|||||||
@@ -9,21 +9,35 @@ import PageWrapper from "@components/PageWrapperAppDir";
|
|||||||
type WithLayoutParams<T extends Record<string, any>> = {
|
type WithLayoutParams<T extends Record<string, any>> = {
|
||||||
getLayout: ((page: React.ReactElement) => React.ReactNode) | null;
|
getLayout: ((page: React.ReactElement) => React.ReactNode) | null;
|
||||||
Page?: (props: T) => React.ReactElement | null;
|
Page?: (props: T) => React.ReactElement | null;
|
||||||
getData?: (arg: GetServerSidePropsContext) => Promise<T>;
|
getData?: (arg: GetServerSidePropsContext) => Promise<T | undefined>;
|
||||||
|
isBookingPage?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
export function WithLayout<T extends Record<string, any>>({ getLayout, getData, Page }: WithLayoutParams<T>) {
|
export function WithLayout<T extends Record<string, any>>({
|
||||||
|
getLayout,
|
||||||
|
getData,
|
||||||
|
Page,
|
||||||
|
isBookingPage,
|
||||||
|
}: WithLayoutParams<T>) {
|
||||||
return async <P extends "P" | "L">(p: P extends "P" ? PageProps : LayoutProps) => {
|
return async <P extends "P" | "L">(p: P extends "P" ? PageProps : LayoutProps) => {
|
||||||
const h = headers();
|
const h = headers();
|
||||||
const nonce = h.get("x-nonce") ?? undefined;
|
const nonce = h.get("x-nonce") ?? undefined;
|
||||||
const props = getData
|
let props = {} as T;
|
||||||
? await getData(buildLegacyCtx(h, cookies(), p.params) as unknown as GetServerSidePropsContext)
|
|
||||||
: ({} as T);
|
if ("searchParams" in p && getData) {
|
||||||
|
props = (await getData(buildLegacyCtx(h, cookies(), p.params, p.searchParams))) ?? ({} as T);
|
||||||
|
}
|
||||||
|
|
||||||
const children = "children" in p ? p.children : null;
|
const children = "children" in p ? p.children : null;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<PageWrapper getLayout={getLayout} requiresLicense={false} nonce={nonce} themeBasis={null} {...props}>
|
<PageWrapper
|
||||||
|
getLayout={getLayout}
|
||||||
|
requiresLicense={false}
|
||||||
|
nonce={nonce}
|
||||||
|
themeBasis={null}
|
||||||
|
isBookingPage={isBookingPage}
|
||||||
|
{...props}>
|
||||||
{Page ? <Page {...props} /> : children}
|
{Page ? <Page {...props} /> : children}
|
||||||
</PageWrapper>
|
</PageWrapper>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -2,13 +2,13 @@ import NotFoundPage from "@pages/404";
|
|||||||
import { WithLayout } from "app/layoutHOC";
|
import { WithLayout } from "app/layoutHOC";
|
||||||
import type { GetStaticPropsContext } from "next";
|
import type { GetStaticPropsContext } from "next";
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
import { getTranslations } from "@server/lib/getTranslations";
|
||||||
|
|
||||||
const getData = async (context: GetStaticPropsContext) => {
|
const getData = async (context: GetStaticPropsContext) => {
|
||||||
const ssg = await ssgInit(context);
|
const i18n = await getTranslations(context);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dehydratedState: ssg.dehydrate(),
|
i18n,
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -89,8 +89,7 @@ function PageWrapper(props: AppProps) {
|
|||||||
</LicenseRequired>
|
</LicenseRequired>
|
||||||
) : (
|
) : (
|
||||||
<Component {...pageProps} err={err} />
|
<Component {...pageProps} err={err} />
|
||||||
),
|
)
|
||||||
router
|
|
||||||
)}
|
)}
|
||||||
</AppProviders>
|
</AppProviders>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -16,10 +16,10 @@ import { Plus } from "@calcom/ui/components/icon";
|
|||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
|
|
||||||
interface AdditionalCalendarSelectorProps {
|
interface AdditionalCalendarSelectorProps {
|
||||||
isLoading?: boolean;
|
isPending?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorProps): JSX.Element | null => {
|
const AdditionalCalendarSelector = ({ isPending }: AdditionalCalendarSelectorProps): JSX.Element | null => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
|
const query = trpc.viewer.integrations.useQuery({ variant: "calendar", onlyInstalled: true });
|
||||||
|
|
||||||
@@ -42,7 +42,7 @@ const AdditionalCalendarSelector = ({ isLoading }: AdditionalCalendarSelectorPro
|
|||||||
return (
|
return (
|
||||||
<Dropdown modal={false}>
|
<Dropdown modal={false}>
|
||||||
<DropdownMenuTrigger asChild>
|
<DropdownMenuTrigger asChild>
|
||||||
<Button StartIcon={Plus} color="secondary" {...(isLoading && { loading: isLoading })}>
|
<Button StartIcon={Plus} color="secondary" {...(isPending && { loading: isPending })}>
|
||||||
{t("add")}
|
{t("add")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import type { IframeHTMLAttributes } from "react";
|
import type { IframeHTMLAttributes } from "react";
|
||||||
import React, { useState } from "react";
|
import React, { useState, useEffect } from "react";
|
||||||
|
|
||||||
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
||||||
import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components";
|
import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components";
|
||||||
@@ -92,17 +92,19 @@ export const AppPage = ({
|
|||||||
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
|
const [existingCredentials, setExistingCredentials] = useState<number[]>([]);
|
||||||
const [showDisconnectIntegration, setShowDisconnectIntegration] = useState(false);
|
const [showDisconnectIntegration, setShowDisconnectIntegration] = useState(false);
|
||||||
|
|
||||||
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery(
|
const appDbQuery = trpc.viewer.appCredentialsByType.useQuery({ appType: type });
|
||||||
{ appType: type },
|
|
||||||
{
|
useEffect(
|
||||||
onSettled(data) {
|
function refactorMeWithoutEffect() {
|
||||||
const credentialsCount = data?.credentials.length || 0;
|
const data = appDbQuery.data;
|
||||||
setShowDisconnectIntegration(
|
|
||||||
data?.userAdminTeams.length ? credentialsCount >= data?.userAdminTeams.length : credentialsCount > 0
|
const credentialsCount = data?.credentials.length || 0;
|
||||||
);
|
setShowDisconnectIntegration(
|
||||||
setExistingCredentials(data?.credentials.map((credential) => credential.id) || []);
|
data?.userAdminTeams.length ? credentialsCount >= data?.userAdminTeams.length : credentialsCount > 0
|
||||||
},
|
);
|
||||||
}
|
setExistingCredentials(data?.credentials.map((credential) => credential.id) || []);
|
||||||
|
},
|
||||||
|
[appDbQuery.data]
|
||||||
);
|
);
|
||||||
|
|
||||||
const dependencyData = trpc.viewer.appsRouter.queryForDependencies.useQuery(dependencies, {
|
const dependencyData = trpc.viewer.appsRouter.queryForDependencies.useQuery(dependencies, {
|
||||||
@@ -190,7 +192,7 @@ export const AppPage = ({
|
|||||||
)}
|
)}
|
||||||
</header>
|
</header>
|
||||||
</div>
|
</div>
|
||||||
{!appDbQuery.isLoading ? (
|
{!appDbQuery.isPending ? (
|
||||||
isGlobal ||
|
isGlobal ||
|
||||||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
|
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
|
||||||
<div className="flex space-x-3">
|
<div className="flex space-x-3">
|
||||||
@@ -211,7 +213,7 @@ export const AppPage = ({
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
mutation.mutate({ type, variant, slug });
|
mutation.mutate({ type, variant, slug });
|
||||||
},
|
},
|
||||||
loading: mutation.isLoading,
|
loading: mutation.isPending,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -250,7 +252,7 @@ export const AppPage = ({
|
|||||||
onClick: () => {
|
onClick: () => {
|
||||||
mutation.mutate({ type, variant, slug });
|
mutation.mutate({ type, variant, slug });
|
||||||
},
|
},
|
||||||
loading: mutation.isLoading,
|
loading: mutation.isPending,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -272,7 +274,7 @@ export const AppPage = ({
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
{dependencies &&
|
{dependencies &&
|
||||||
(!dependencyData.isLoading ? (
|
(!dependencyData.isPending ? (
|
||||||
<div className="mt-6">
|
<div className="mt-6">
|
||||||
<AppDependencyComponent appName={name} dependencyData={dependencyData.data} />
|
<AppDependencyComponent appName={name} dependencyData={dependencyData.data} />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ type Props = {
|
|||||||
onChanged: () => unknown | Promise<unknown>;
|
onChanged: () => unknown | Promise<unknown>;
|
||||||
fromOnboarding?: boolean;
|
fromOnboarding?: boolean;
|
||||||
destinationCalendarId?: string;
|
destinationCalendarId?: string;
|
||||||
isLoading?: boolean;
|
isPending?: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
function CalendarList(props: Props) {
|
function CalendarList(props: Props) {
|
||||||
@@ -74,7 +74,7 @@ function ConnectedCalendarsList(props: Props) {
|
|||||||
suspense: true,
|
suspense: true,
|
||||||
refetchOnWindowFocus: false,
|
refetchOnWindowFocus: false,
|
||||||
});
|
});
|
||||||
const { fromOnboarding, isLoading } = props;
|
const { fromOnboarding, isPending } = props;
|
||||||
return (
|
return (
|
||||||
<QueryCell
|
<QueryCell
|
||||||
query={query}
|
query={query}
|
||||||
@@ -97,7 +97,7 @@ function ConnectedCalendarsList(props: Props) {
|
|||||||
<div className="flex flex-col xl:flex-row xl:space-x-5">
|
<div className="flex flex-col xl:flex-row xl:space-x-5">
|
||||||
{!!data.connectedCalendars.length && (
|
{!!data.connectedCalendars.length && (
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<AdditionalCalendarSelector isLoading={isLoading} />
|
<AdditionalCalendarSelector isPending={isPending} />
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -229,7 +229,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
|||||||
hidePlaceholder
|
hidePlaceholder
|
||||||
value={data.destinationCalendar?.externalId}
|
value={data.destinationCalendar?.externalId}
|
||||||
onChange={mutation.mutate}
|
onChange={mutation.mutate}
|
||||||
isLoading={mutation.isLoading}
|
isPending={mutation.isPending}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -239,7 +239,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
|||||||
onChanged={onChanged}
|
onChanged={onChanged}
|
||||||
fromOnboarding={fromOnboarding}
|
fromOnboarding={fromOnboarding}
|
||||||
destinationCalendarId={data.destinationCalendar?.externalId}
|
destinationCalendarId={data.destinationCalendar?.externalId}
|
||||||
isLoading={mutation.isLoading}
|
isPending={mutation.isPending}
|
||||||
/>
|
/>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
|
|||||||
@@ -100,9 +100,9 @@ export const InstallAppButtonChild = ({
|
|||||||
<DropdownMenuContent
|
<DropdownMenuContent
|
||||||
className="w-auto"
|
className="w-auto"
|
||||||
onInteractOutside={(event) => {
|
onInteractOutside={(event) => {
|
||||||
if (mutation.isLoading) event.preventDefault();
|
if (mutation.isPending) event.preventDefault();
|
||||||
}}>
|
}}>
|
||||||
{mutation.isLoading && (
|
{mutation.isPending && (
|
||||||
<div className="z-1 fixed inset-0 flex items-center justify-center">
|
<div className="z-1 fixed inset-0 flex items-center justify-center">
|
||||||
<Spinner />
|
<Spinner />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
setRejectionDialogIsOpen(true);
|
setRejectionDialogIsOpen(true);
|
||||||
},
|
},
|
||||||
icon: Ban,
|
icon: Ban,
|
||||||
disabled: mutation.isLoading,
|
disabled: mutation.isPending,
|
||||||
},
|
},
|
||||||
// For bookings with payment, only confirm if the booking is paid for
|
// For bookings with payment, only confirm if the booking is paid for
|
||||||
...((isPending && !paymentAppData.enabled) ||
|
...((isPending && !paymentAppData.enabled) ||
|
||||||
@@ -147,12 +147,13 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
? [
|
? [
|
||||||
{
|
{
|
||||||
id: "confirm",
|
id: "confirm",
|
||||||
|
bookingId: booking.id,
|
||||||
label: (isTabRecurring || isTabUnconfirmed) && isRecurring ? t("confirm_all") : t("confirm"),
|
label: (isTabRecurring || isTabUnconfirmed) && isRecurring ? t("confirm_all") : t("confirm"),
|
||||||
onClick: () => {
|
onClick: () => {
|
||||||
bookingConfirm(true);
|
bookingConfirm(true);
|
||||||
},
|
},
|
||||||
icon: Check,
|
icon: Check,
|
||||||
disabled: mutation.isLoading,
|
disabled: mutation.isPending,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
: []),
|
: []),
|
||||||
@@ -292,7 +293,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
setViewRecordingsDialogIsOpen(true);
|
setViewRecordingsDialogIsOpen(true);
|
||||||
},
|
},
|
||||||
color: showCheckRecordingButton ? "secondary" : "primary",
|
color: showCheckRecordingButton ? "secondary" : "primary",
|
||||||
disabled: mutation.isLoading,
|
disabled: mutation.isPending,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -347,7 +348,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
<DialogFooter>
|
<DialogFooter>
|
||||||
<DialogClose />
|
<DialogClose />
|
||||||
<Button
|
<Button
|
||||||
disabled={mutation.isLoading}
|
disabled={mutation.isPending}
|
||||||
data-testid="rejection-confirm"
|
data-testid="rejection-confirm"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
bookingConfirm(false);
|
bookingConfirm(false);
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
|
|||||||
<DialogClose />
|
<DialogClose />
|
||||||
<Button
|
<Button
|
||||||
data-testid="send_request"
|
data-testid="send_request"
|
||||||
disabled={chargeCardMutation.isLoading || chargeError}
|
disabled={chargeCardMutation.isPending || chargeError}
|
||||||
onClick={() =>
|
onClick={() =>
|
||||||
chargeCardMutation.mutate({
|
chargeCardMutation.mutate({
|
||||||
bookingId,
|
bookingId,
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
|||||||
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
|
const { isOpenDialog, setIsOpenDialog, bookingUId: bookingId } = props;
|
||||||
const [rescheduleReason, setRescheduleReason] = useState("");
|
const [rescheduleReason, setRescheduleReason] = useState("");
|
||||||
|
|
||||||
const { mutate: rescheduleApi, isLoading } = trpc.viewer.bookings.requestReschedule.useMutation({
|
const { mutate: rescheduleApi, isPending } = trpc.viewer.bookings.requestReschedule.useMutation({
|
||||||
async onSuccess() {
|
async onSuccess() {
|
||||||
showToast(t("reschedule_request_sent"), "success");
|
showToast(t("reschedule_request_sent"), "success");
|
||||||
setIsOpenDialog(false);
|
setIsOpenDialog(false);
|
||||||
@@ -65,7 +65,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
|
|||||||
<DialogClose />
|
<DialogClose />
|
||||||
<Button
|
<Button
|
||||||
data-testid="send_request"
|
data-testid="send_request"
|
||||||
disabled={isLoading}
|
disabled={isPending}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
rescheduleApi({
|
rescheduleApi({
|
||||||
bookingId,
|
bookingId,
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import type { EditableSchema } from "@calcom/features/form-builder/schema";
|
|||||||
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
|
import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector";
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
import { APP_NAME, CAL_URL } from "@calcom/lib/constants";
|
||||||
|
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { Prisma } from "@calcom/prisma/client";
|
import type { Prisma } from "@calcom/prisma/client";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
@@ -35,7 +36,6 @@ import {
|
|||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
import { Copy, Edit, Info } from "@calcom/ui/components/icon";
|
import { Copy, Edit, Info } from "@calcom/ui/components/icon";
|
||||||
import { IS_VISUAL_REGRESSION_TESTING } from "@calcom/web/constants";
|
|
||||||
|
|
||||||
import RequiresConfirmationController from "./RequiresConfirmationController";
|
import RequiresConfirmationController from "./RequiresConfirmationController";
|
||||||
|
|
||||||
@@ -55,8 +55,8 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
const [showEventNameTip, setShowEventNameTip] = useState(false);
|
const [showEventNameTip, setShowEventNameTip] = useState(false);
|
||||||
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!eventType.hashedLink);
|
const [hashedLinkVisible, setHashedLinkVisible] = useState(!!formMethods.getValues("hashedLink"));
|
||||||
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!eventType.successRedirectUrl);
|
const [redirectUrlVisible, setRedirectUrlVisible] = useState(!!formMethods.getValues("successRedirectUrl"));
|
||||||
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
|
const [hashedUrl, setHashedUrl] = useState(eventType.hashedLink?.link);
|
||||||
|
|
||||||
const bookingFields: Prisma.JsonObject = {};
|
const bookingFields: Prisma.JsonObject = {};
|
||||||
@@ -66,27 +66,31 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
user?.theme === "dark" ||
|
user?.theme === "dark" ||
|
||||||
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
(!user?.theme && typeof document !== "undefined" && document.documentElement.classList.contains("dark"));
|
||||||
|
|
||||||
eventType.bookingFields.forEach(({ name }) => {
|
formMethods.getValues().bookingFields.forEach(({ name }) => {
|
||||||
bookingFields[name] = `${name} input`;
|
bookingFields[name] = `${name} input`;
|
||||||
});
|
});
|
||||||
|
|
||||||
const eventNameObject: EventNameObjectType = {
|
const eventNameObject: EventNameObjectType = {
|
||||||
attendeeName: t("scheduler"),
|
attendeeName: t("scheduler"),
|
||||||
eventType: eventType.title,
|
eventType: formMethods.getValues("title"),
|
||||||
eventName: eventType.eventName,
|
eventName: formMethods.getValues("eventName"),
|
||||||
host: eventType.users[0]?.name || "Nameless",
|
host: formMethods.getValues("users")[0]?.name || "Nameless",
|
||||||
bookingFields: bookingFields,
|
bookingFields: bookingFields,
|
||||||
t,
|
t,
|
||||||
};
|
};
|
||||||
|
|
||||||
const [requiresConfirmation, setRequiresConfirmation] = useState(eventType.requiresConfirmation);
|
const [requiresConfirmation, setRequiresConfirmation] = useState(
|
||||||
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${eventType.slug}`;
|
formMethods.getValues("requiresConfirmation")
|
||||||
|
);
|
||||||
|
const placeholderHashedLink = `${CAL_URL}/d/${hashedUrl}/${formMethods.getValues("slug")}`;
|
||||||
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
|
const seatsEnabled = formMethods.watch("seatsPerTimeSlotEnabled");
|
||||||
const noShowFeeEnabled = eventType.metadata?.apps?.stripe?.paymentOption === "HOLD";
|
const noShowFeeEnabled =
|
||||||
|
formMethods.getValues("metadata")?.apps?.stripe?.enabled === true &&
|
||||||
|
formMethods.getValues("metadata")?.apps?.stripe?.paymentOption === "HOLD";
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
!hashedUrl && setHashedUrl(generateHashedLink(eventType.users[0]?.id ?? team?.id));
|
!hashedUrl && setHashedUrl(generateHashedLink(formMethods.getValues("users")[0]?.id ?? team?.id));
|
||||||
}, [eventType.users, hashedUrl, team?.id]);
|
}, [formMethods.getValues("users"), hashedUrl, team?.id]);
|
||||||
|
|
||||||
const toggleGuests = (enabled: boolean) => {
|
const toggleGuests = (enabled: boolean) => {
|
||||||
const bookingFields = formMethods.getValues("bookingFields");
|
const bookingFields = formMethods.getValues("bookingFields");
|
||||||
@@ -108,7 +112,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { shouldLockDisableProps } = useLockedFieldsManager(
|
const { shouldLockDisableProps } = useLockedFieldsManager(
|
||||||
eventType,
|
formMethods.getValues(),
|
||||||
t("locked_fields_admin_description"),
|
t("locked_fields_admin_description"),
|
||||||
t("locked_fields_member_description")
|
t("locked_fields_member_description")
|
||||||
);
|
);
|
||||||
@@ -122,7 +126,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
|
|
||||||
const closeEventNameTip = () => setShowEventNameTip(false);
|
const closeEventNameTip = () => setShowEventNameTip(false);
|
||||||
|
|
||||||
const setEventName = (value: string) => formMethods.setValue("eventName", value);
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col space-y-4">
|
<div className="flex flex-col space-y-4">
|
||||||
{/**
|
{/**
|
||||||
@@ -145,12 +148,9 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
control={formMethods.control}
|
|
||||||
name="destinationCalendar"
|
name="destinationCalendar"
|
||||||
defaultValue={eventType.destinationCalendar || undefined}
|
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<DestinationCalendarSelector
|
<DestinationCalendarSelector
|
||||||
destinationCalendar={eventType.destinationCalendar}
|
|
||||||
value={value ? value.externalId : undefined}
|
value={value ? value.externalId : undefined}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
hidePlaceholder
|
hidePlaceholder
|
||||||
@@ -167,7 +167,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
type="text"
|
type="text"
|
||||||
{...shouldLockDisableProps("eventName")}
|
{...shouldLockDisableProps("eventName")}
|
||||||
placeholder={eventNamePlaceholder}
|
placeholder={eventNamePlaceholder}
|
||||||
defaultValue={eventType.eventName || ""}
|
|
||||||
{...formMethods.register("eventName")}
|
{...formMethods.register("eventName")}
|
||||||
addOnSuffix={
|
addOnSuffix={
|
||||||
<Button
|
<Button
|
||||||
@@ -194,7 +193,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
{...shouldLockDisableProps("bookingFields")}
|
{...shouldLockDisableProps("bookingFields")}
|
||||||
dataStore={{
|
dataStore={{
|
||||||
options: {
|
options: {
|
||||||
locations: getLocationsOptionsForSelect(eventType?.locations ?? [], t),
|
locations: getLocationsOptionsForSelect(formMethods.getValues("locations") ?? [], t),
|
||||||
},
|
},
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
@@ -203,15 +202,13 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
<RequiresConfirmationController
|
<RequiresConfirmationController
|
||||||
eventType={eventType}
|
eventType={eventType}
|
||||||
seatsEnabled={seatsEnabled}
|
seatsEnabled={seatsEnabled}
|
||||||
metadata={eventType.metadata}
|
metadata={formMethods.getValues("metadata")}
|
||||||
requiresConfirmation={requiresConfirmation}
|
requiresConfirmation={requiresConfirmation}
|
||||||
onRequiresConfirmation={setRequiresConfirmation}
|
onRequiresConfirmation={setRequiresConfirmation}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="requiresBookerEmailVerification"
|
name="requiresBookerEmailVerification"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={eventType.requiresBookerEmailVerification}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
labelClassName="text-sm"
|
labelClassName="text-sm"
|
||||||
@@ -228,8 +225,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="hideCalendarNotes"
|
name="hideCalendarNotes"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={eventType.hideCalendarNotes}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
labelClassName="text-sm"
|
labelClassName="text-sm"
|
||||||
@@ -246,7 +241,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="successRedirectUrl"
|
name="successRedirectUrl"
|
||||||
control={formMethods.control}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<>
|
<>
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
@@ -274,7 +268,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
placeholder={t("external_redirect_url")}
|
placeholder={t("external_redirect_url")}
|
||||||
required={redirectUrlVisible}
|
required={redirectUrlVisible}
|
||||||
type="text"
|
type="text"
|
||||||
defaultValue={eventType.successRedirectUrl || ""}
|
|
||||||
{...formMethods.register("successRedirectUrl")}
|
{...formMethods.register("successRedirectUrl")}
|
||||||
/>
|
/>
|
||||||
<div
|
<div
|
||||||
@@ -327,7 +320,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
hint={t("private_link_hint")}
|
hint={t("private_link_hint")}
|
||||||
defaultValue={placeholderHashedLink}
|
defaultValue={placeholderHashedLink}
|
||||||
addOnSuffix={
|
addOnSuffix={
|
||||||
<Tooltip content={eventType.hashedLink ? t("copy_to_clipboard") : t("enabled_after_update")}>
|
<Tooltip
|
||||||
|
content={
|
||||||
|
formMethods.getValues("hashedLink") ? t("copy_to_clipboard") : t("enabled_after_update")
|
||||||
|
}>
|
||||||
<Button
|
<Button
|
||||||
color="minimal"
|
color="minimal"
|
||||||
size="sm"
|
size="sm"
|
||||||
@@ -336,7 +332,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
aria-label="copy link"
|
aria-label="copy link"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
navigator.clipboard.writeText(placeholderHashedLink);
|
navigator.clipboard.writeText(placeholderHashedLink);
|
||||||
if (eventType.hashedLink) {
|
if (formMethods.getValues("hashedLink")) {
|
||||||
showToast(t("private_link_copied"), "success");
|
showToast(t("private_link_copied"), "success");
|
||||||
} else {
|
} else {
|
||||||
showToast(t("enabled_after_update_description"), "warning");
|
showToast(t("enabled_after_update_description"), "warning");
|
||||||
@@ -353,8 +349,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="seatsPerTimeSlotEnabled"
|
name="seatsPerTimeSlotEnabled"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={!!eventType.seatsPerTimeSlot}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<>
|
<>
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
@@ -378,7 +372,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
formMethods.setValue("requiresConfirmation", false);
|
formMethods.setValue("requiresConfirmation", false);
|
||||||
setRequiresConfirmation(false);
|
setRequiresConfirmation(false);
|
||||||
formMethods.setValue("metadata.multipleDuration", undefined);
|
formMethods.setValue("metadata.multipleDuration", undefined);
|
||||||
formMethods.setValue("seatsPerTimeSlot", 2);
|
formMethods.setValue("seatsPerTimeSlot", eventType.seatsPerTimeSlot ?? 2);
|
||||||
} else {
|
} else {
|
||||||
formMethods.setValue("seatsPerTimeSlot", null);
|
formMethods.setValue("seatsPerTimeSlot", null);
|
||||||
toggleGuests(true);
|
toggleGuests(true);
|
||||||
@@ -388,8 +382,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
<Controller
|
<Controller
|
||||||
name="seatsPerTimeSlot"
|
name="seatsPerTimeSlot"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={eventType.seatsPerTimeSlot}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
@@ -399,7 +391,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
label={t("number_of_seats")}
|
label={t("number_of_seats")}
|
||||||
type="number"
|
type="number"
|
||||||
disabled={seatsLocked.disabled}
|
disabled={seatsLocked.disabled}
|
||||||
defaultValue={value || 2}
|
defaultValue={value}
|
||||||
min={1}
|
min={1}
|
||||||
containerClassName="max-w-80"
|
containerClassName="max-w-80"
|
||||||
addOnSuffix={<>{t("seats")}</>}
|
addOnSuffix={<>{t("seats")}</>}
|
||||||
@@ -408,21 +400,29 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<div className="mt-4">
|
<div className="mt-4">
|
||||||
<CheckboxField
|
<Controller
|
||||||
description={t("show_attendees")}
|
name="seatsShowAttendees"
|
||||||
disabled={seatsLocked.disabled}
|
render={({ field: { value, onChange } }) => (
|
||||||
onChange={(e) => formMethods.setValue("seatsShowAttendees", e.target.checked)}
|
<CheckboxField
|
||||||
defaultChecked={!!eventType.seatsShowAttendees}
|
description={t("show_attendees")}
|
||||||
|
disabled={seatsLocked.disabled}
|
||||||
|
onChange={(e) => onChange(e)}
|
||||||
|
checked={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<CheckboxField
|
<Controller
|
||||||
description={t("show_available_seats_count")}
|
name="seatsShowAvailabilityCount"
|
||||||
disabled={seatsLocked.disabled}
|
render={({ field: { value, onChange } }) => (
|
||||||
onChange={(e) =>
|
<CheckboxField
|
||||||
formMethods.setValue("seatsShowAvailabilityCount", e.target.checked)
|
description={t("show_available_seats_count")}
|
||||||
}
|
disabled={seatsLocked.disabled}
|
||||||
defaultChecked={!!eventType.seatsShowAvailabilityCount}
|
onChange={(e) => onChange(e)}
|
||||||
|
checked={value}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -436,8 +436,6 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="lockTimeZoneToggleOnBookingPage"
|
name="lockTimeZoneToggleOnBookingPage"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={eventType.lockTimeZoneToggleOnBookingPage}
|
|
||||||
render={({ field: { value, onChange } }) => (
|
render={({ field: { value, onChange } }) => (
|
||||||
<SettingsToggle
|
<SettingsToggle
|
||||||
labelClassName="text-sm"
|
labelClassName="text-sm"
|
||||||
@@ -452,59 +450,47 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
{allowDisablingAttendeeConfirmationEmails(workflows) && (
|
{allowDisablingAttendeeConfirmationEmails(workflows) && (
|
||||||
<>
|
<Controller
|
||||||
<Controller
|
name="metadata.disableStandardEmails.confirmation.attendee"
|
||||||
name="metadata.disableStandardEmails.confirmation.attendee"
|
render={({ field: { value, onChange } }) => (
|
||||||
control={formMethods.control}
|
<>
|
||||||
render={({ field: { value, onChange } }) => (
|
<SettingsToggle
|
||||||
<>
|
labelClassName="text-sm"
|
||||||
<SettingsToggle
|
toggleSwitchAtTheEnd={true}
|
||||||
labelClassName="text-sm"
|
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||||
toggleSwitchAtTheEnd={true}
|
title={t("disable_attendees_confirmation_emails")}
|
||||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
description={t("disable_attendees_confirmation_emails_description")}
|
||||||
title={t("disable_attendees_confirmation_emails")}
|
checked={value}
|
||||||
description={t("disable_attendees_confirmation_emails_description")}
|
onCheckedChange={(e) => onChange(e)}
|
||||||
checked={value || false}
|
/>
|
||||||
onCheckedChange={(e) => {
|
</>
|
||||||
formMethods.setValue("metadata.disableStandardEmails.confirmation.attendee", e);
|
)}
|
||||||
onChange(e);
|
/>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{allowDisablingHostConfirmationEmails(workflows) && (
|
{allowDisablingHostConfirmationEmails(workflows) && (
|
||||||
<>
|
<Controller
|
||||||
<Controller
|
name="metadata.disableStandardEmails.confirmation.host"
|
||||||
name="metadata.disableStandardEmails.confirmation.host"
|
defaultValue={!!formMethods.getValues("seatsPerTimeSlot")}
|
||||||
control={formMethods.control}
|
render={({ field: { value, onChange } }) => (
|
||||||
defaultValue={!!eventType.seatsPerTimeSlot}
|
<>
|
||||||
render={({ field: { value, onChange } }) => (
|
<SettingsToggle
|
||||||
<>
|
labelClassName="text-sm"
|
||||||
<SettingsToggle
|
toggleSwitchAtTheEnd={true}
|
||||||
labelClassName="text-sm"
|
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
||||||
toggleSwitchAtTheEnd={true}
|
title={t("disable_host_confirmation_emails")}
|
||||||
switchContainerClassName="border-subtle rounded-lg border py-6 px-4 sm:px-6"
|
description={t("disable_host_confirmation_emails_description")}
|
||||||
title={t("disable_host_confirmation_emails")}
|
checked={value}
|
||||||
description={t("disable_host_confirmation_emails_description")}
|
onCheckedChange={(e) => onChange(e)}
|
||||||
checked={value || false}
|
/>
|
||||||
onCheckedChange={(e) => {
|
</>
|
||||||
formMethods.setValue("metadata.disableStandardEmails.confirmation.host", e);
|
)}
|
||||||
onChange(e);
|
/>
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
)}
|
)}
|
||||||
{showEventNameTip && (
|
{showEventNameTip && (
|
||||||
<CustomEventTypeModal
|
<CustomEventTypeModal
|
||||||
close={closeEventNameTip}
|
close={closeEventNameTip}
|
||||||
setValue={setEventName}
|
setValue={(val: string) => formMethods.setValue("eventName", val)}
|
||||||
defaultValue={formMethods.getValues("eventName") || eventType.eventName || ""}
|
defaultValue={formMethods.getValues("eventName")}
|
||||||
placeHolder={eventNamePlaceholder}
|
placeHolder={eventNamePlaceholder}
|
||||||
event={eventNameObject}
|
event={eventNameObject}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -18,21 +18,21 @@ export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
|
|||||||
|
|
||||||
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const { data: eventTypeApps, isLoading } = trpc.viewer.integrations.useQuery({
|
const { data: eventTypeApps, isPending } = trpc.viewer.integrations.useQuery({
|
||||||
extendsFeature: "EventType",
|
extendsFeature: "EventType",
|
||||||
teamId: eventType.team?.id || eventType.parent?.teamId,
|
teamId: eventType.team?.id || eventType.parent?.teamId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const methods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
const installedApps =
|
const installedApps =
|
||||||
eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || [];
|
eventTypeApps?.items.filter((app) => app.userCredentialIds.length || app.teams.length) || [];
|
||||||
const notInstalledApps =
|
const notInstalledApps =
|
||||||
eventTypeApps?.items.filter((app) => !app.userCredentialIds.length && !app.teams.length) || [];
|
eventTypeApps?.items.filter((app) => !app.userCredentialIds.length && !app.teams.length) || [];
|
||||||
const allAppsData = methods.watch("metadata")?.apps || {};
|
const allAppsData = formMethods.watch("metadata")?.apps || {};
|
||||||
|
|
||||||
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
|
const setAllAppsData = (_allAppsData: typeof allAppsData) => {
|
||||||
methods.setValue("metadata", {
|
formMethods.setValue("metadata", {
|
||||||
...methods.getValues("metadata"),
|
...formMethods.getValues("metadata"),
|
||||||
apps: _allAppsData,
|
apps: _allAppsData,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
@@ -47,7 +47,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const eventTypeFormMetadata = methods.getValues("metadata");
|
const eventTypeFormMetadata = formMethods.getValues("metadata");
|
||||||
|
|
||||||
const getAppDataSetter = (
|
const getAppDataSetter = (
|
||||||
appId: EventTypeAppsList,
|
appId: EventTypeAppsList,
|
||||||
@@ -56,8 +56,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
|||||||
): SetAppData => {
|
): SetAppData => {
|
||||||
return function (key, value) {
|
return function (key, value) {
|
||||||
// Always get latest data available in Form because consequent calls to setData would update the Form but not allAppsData(it would update during next render)
|
// Always get latest data available in Form because consequent calls to setData would update the Form but not allAppsData(it would update during next render)
|
||||||
const allAppsDataFromForm = methods.getValues("metadata")?.apps || {};
|
const allAppsDataFromForm = formMethods.getValues("metadata")?.apps || {};
|
||||||
|
|
||||||
const appData = allAppsDataFromForm[appId];
|
const appData = allAppsDataFromForm[appId];
|
||||||
setAllAppsData({
|
setAllAppsData({
|
||||||
...allAppsDataFromForm,
|
...allAppsDataFromForm,
|
||||||
@@ -138,7 +137,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
|||||||
message={t("locked_apps_description")}
|
message={t("locked_apps_description")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
{!isLoading && !installedApps?.length ? (
|
{!isPending && !installedApps?.length ? (
|
||||||
<EmptyScreen
|
<EmptyScreen
|
||||||
Icon={Grid}
|
Icon={Grid}
|
||||||
headline={t("empty_installed_apps_headline")}
|
headline={t("empty_installed_apps_headline")}
|
||||||
@@ -179,7 +178,7 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
|
|||||||
</div>
|
</div>
|
||||||
{!shouldLockDisableProps("apps").disabled && (
|
{!shouldLockDisableProps("apps").disabled && (
|
||||||
<div className="bg-muted mt-6 rounded-md p-8">
|
<div className="bg-muted mt-6 rounded-md p-8">
|
||||||
{!isLoading && notInstalledApps?.length ? (
|
{!isPending && notInstalledApps?.length ? (
|
||||||
<>
|
<>
|
||||||
<h2 className="text-emphasis mb-2 text-xl font-semibold leading-5 tracking-[0.01em]">
|
<h2 className="text-emphasis mb-2 text-xl font-semibold leading-5 tracking-[0.01em]">
|
||||||
{t("available_apps_lower_case")}
|
{t("available_apps_lower_case")}
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ const EventTypeScheduleDetails = memo(
|
|||||||
const { watch } = useFormContext<FormValues>();
|
const { watch } = useFormContext<FormValues>();
|
||||||
|
|
||||||
const scheduleId = watch("schedule");
|
const scheduleId = watch("schedule");
|
||||||
const { isLoading, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
|
const { isPending, data: schedule } = trpc.viewer.availability.schedule.get.useQuery(
|
||||||
{
|
{
|
||||||
scheduleId:
|
scheduleId:
|
||||||
scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
|
scheduleId || loggedInUser?.defaultScheduleId || selectedScheduleValue?.value || undefined,
|
||||||
@@ -112,7 +112,7 @@ const EventTypeScheduleDetails = memo(
|
|||||||
)}>
|
)}>
|
||||||
{day}
|
{day}
|
||||||
</span>
|
</span>
|
||||||
{isLoading ? (
|
{isPending ? (
|
||||||
<SkeletonText className="block h-5 w-60" />
|
<SkeletonText className="block h-5 w-60" />
|
||||||
) : isAvailable ? (
|
) : isAvailable ? (
|
||||||
<div className="space-y-3 text-right">
|
<div className="space-y-3 text-right">
|
||||||
@@ -142,7 +142,7 @@ const EventTypeScheduleDetails = memo(
|
|||||||
{!!schedule?.id && !schedule.isManaged && !schedule.readOnly && (
|
{!!schedule?.id && !schedule.isManaged && !schedule.readOnly && (
|
||||||
<Button
|
<Button
|
||||||
href={`/availability/${schedule.id}`}
|
href={`/availability/${schedule.id}`}
|
||||||
disabled={isLoading}
|
disabled={isPending}
|
||||||
color="minimal"
|
color="minimal"
|
||||||
EndIcon={ExternalLink}
|
EndIcon={ExternalLink}
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -169,8 +169,15 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
const watchSchedule = watch("schedule");
|
const watchSchedule = watch("schedule");
|
||||||
const [options, setOptions] = useState<AvailabilityOption[]>([]);
|
const [options, setOptions] = useState<AvailabilityOption[]>([]);
|
||||||
|
|
||||||
const { isLoading } = trpc.viewer.availability.list.useQuery(undefined, {
|
const { data, isPending } = trpc.viewer.availability.list.useQuery(undefined);
|
||||||
onSuccess: ({ schedules }) => {
|
|
||||||
|
useEffect(
|
||||||
|
function refactorMeWithoutEffect() {
|
||||||
|
if (!data) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const schedules = data.schedules;
|
||||||
|
|
||||||
const options = schedules.map((schedule) => ({
|
const options = schedules.map((schedule) => ({
|
||||||
value: schedule.id,
|
value: schedule.id,
|
||||||
label: schedule.name,
|
label: schedule.name,
|
||||||
@@ -225,8 +232,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
|
|
||||||
setValue("availability", value);
|
setValue("availability", value);
|
||||||
},
|
},
|
||||||
});
|
[data]
|
||||||
|
);
|
||||||
const availabilityValue = watch("availability");
|
const availabilityValue = watch("availability");
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -241,8 +248,8 @@ const EventTypeSchedule = ({ eventType }: { eventType: EventTypeSetup }) => {
|
|||||||
{t("availability")}
|
{t("availability")}
|
||||||
{shouldLockIndicator("availability")}
|
{shouldLockIndicator("availability")}
|
||||||
</label>
|
</label>
|
||||||
{isLoading && <SelectSkeletonLoader />}
|
{isPending && <SelectSkeletonLoader />}
|
||||||
{!isLoading && (
|
{!isPending && (
|
||||||
<Controller
|
<Controller
|
||||||
name="schedule"
|
name="schedule"
|
||||||
render={({ field }) => {
|
render={({ field }) => {
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as RadioGroup from "@radix-ui/react-radio-group";
|
import * as RadioGroup from "@radix-ui/react-radio-group";
|
||||||
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
import type { FormValues } from "pages/event-types/[type]";
|
||||||
import type { Key } from "react";
|
import type { Key } from "react";
|
||||||
import React, { useEffect, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import type { UseFormRegisterReturn } from "react-hook-form";
|
import type { UseFormRegisterReturn } from "react-hook-form";
|
||||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
import { Controller, useFormContext } from "react-hook-form";
|
||||||
import type { SingleValue } from "react-select";
|
import type { SingleValue } from "react-select";
|
||||||
|
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
@@ -107,7 +107,7 @@ const MinimumBookingNoticeInput = React.forwardRef<
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
export const EventLimitsTab = () => {
|
||||||
const { t, i18n } = useLocale();
|
const { t, i18n } = useLocale();
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
|
|
||||||
@@ -126,36 +126,20 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const periodType =
|
const watchPeriodType = formMethods.watch("periodType");
|
||||||
PERIOD_TYPES.find((s) => s.type === eventType.periodType) ||
|
|
||||||
PERIOD_TYPES.find((s) => s.type === "UNLIMITED");
|
|
||||||
|
|
||||||
const [periodDates] = useState<{ startDate: Date; endDate: Date }>({
|
|
||||||
startDate: new Date(eventType.periodStartDate || Date.now()),
|
|
||||||
endDate: new Date(eventType.periodEndDate || Date.now()),
|
|
||||||
});
|
|
||||||
const watchPeriodType = useWatch({
|
|
||||||
control: formMethods.control,
|
|
||||||
name: "periodType",
|
|
||||||
defaultValue: periodType?.type,
|
|
||||||
});
|
|
||||||
|
|
||||||
const optionsPeriod = [
|
const optionsPeriod = [
|
||||||
{ value: 1, label: t("calendar_days") },
|
|
||||||
{ value: 0, label: t("business_days") },
|
{ value: 0, label: t("business_days") },
|
||||||
|
{ value: 1, label: t("calendar_days") },
|
||||||
];
|
];
|
||||||
|
|
||||||
// offsetStart toggle is client-side only, opened by default if offsetStart is set
|
const [offsetToggle, setOffsetToggle] = useState(formMethods.getValues("offsetStart") > 0);
|
||||||
const offsetStartValue = useWatch({
|
|
||||||
control: formMethods.control,
|
|
||||||
name: "offsetStart",
|
|
||||||
});
|
|
||||||
const [offsetToggle, setOffsetToggle] = useState(() => offsetStartValue > 0);
|
|
||||||
|
|
||||||
// Preview how the offset will affect start times
|
// Preview how the offset will affect start times
|
||||||
|
const watchOffsetStartValue = formMethods.watch("offsetStart");
|
||||||
const offsetOriginalTime = new Date();
|
const offsetOriginalTime = new Date();
|
||||||
offsetOriginalTime.setHours(9, 0, 0, 0);
|
offsetOriginalTime.setHours(9, 0, 0, 0);
|
||||||
const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + offsetStartValue * 60 * 1000);
|
const offsetAdjustedTime = new Date(offsetOriginalTime.getTime() + watchOffsetStartValue * 60 * 1000);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
@@ -165,8 +149,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
<Label htmlFor="beforeBufferTime">{t("before_event")}</Label>
|
<Label htmlFor="beforeBufferTime">{t("before_event")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="beforeBufferTime"
|
name="beforeBufferTime"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={eventType.beforeEventBuffer || 0}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const beforeBufferOptions = [
|
const beforeBufferOptions = [
|
||||||
{
|
{
|
||||||
@@ -197,8 +179,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
<Label htmlFor="afterBufferTime">{t("after_event")}</Label>
|
<Label htmlFor="afterBufferTime">{t("after_event")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="afterBufferTime"
|
name="afterBufferTime"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={eventType.afterEventBuffer || 0}
|
|
||||||
render={({ field: { onChange, value } }) => {
|
render={({ field: { onChange, value } }) => {
|
||||||
const afterBufferOptions = [
|
const afterBufferOptions = [
|
||||||
{
|
{
|
||||||
@@ -235,7 +215,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
<Label htmlFor="slotInterval">{t("slot_interval")}</Label>
|
<Label htmlFor="slotInterval">{t("slot_interval")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="slotInterval"
|
name="slotInterval"
|
||||||
control={formMethods.control}
|
|
||||||
render={() => {
|
render={() => {
|
||||||
const slotIntervalOptions = [
|
const slotIntervalOptions = [
|
||||||
{
|
{
|
||||||
@@ -254,8 +233,9 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
|
formMethods.setValue("slotInterval", val && (val.value || 0) > 0 ? val.value : null);
|
||||||
}}
|
}}
|
||||||
defaultValue={
|
defaultValue={
|
||||||
slotIntervalOptions.find((option) => option.value === eventType.slotInterval) ||
|
slotIntervalOptions.find(
|
||||||
slotIntervalOptions[0]
|
(option) => option.value === formMethods.getValues("slotInterval")
|
||||||
|
) || slotIntervalOptions[0]
|
||||||
}
|
}
|
||||||
options={slotIntervalOptions}
|
options={slotIntervalOptions}
|
||||||
/>
|
/>
|
||||||
@@ -267,7 +247,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
</div>
|
</div>
|
||||||
<Controller
|
<Controller
|
||||||
name="bookingLimits"
|
name="bookingLimits"
|
||||||
control={formMethods.control}
|
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { value } }) => {
|
||||||
const isChecked = Object.keys(value ?? {}).length > 0;
|
const isChecked = Object.keys(value ?? {}).length > 0;
|
||||||
return (
|
return (
|
||||||
@@ -300,7 +279,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="onlyShowFirstAvailableSlot"
|
name="onlyShowFirstAvailableSlot"
|
||||||
control={formMethods.control}
|
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { value } }) => {
|
||||||
const isChecked = value;
|
const isChecked = value;
|
||||||
return (
|
return (
|
||||||
@@ -323,7 +301,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="durationLimits"
|
name="durationLimits"
|
||||||
control={formMethods.control}
|
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { value } }) => {
|
||||||
const isChecked = Object.keys(value ?? {}).length > 0;
|
const isChecked = Object.keys(value ?? {}).length > 0;
|
||||||
return (
|
return (
|
||||||
@@ -361,7 +338,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
<Controller
|
<Controller
|
||||||
name="periodType"
|
name="periodType"
|
||||||
control={formMethods.control}
|
|
||||||
render={({ field: { value } }) => {
|
render={({ field: { value } }) => {
|
||||||
const isChecked = value && value !== "UNLIMITED";
|
const isChecked = value && value !== "UNLIMITED";
|
||||||
|
|
||||||
@@ -380,7 +356,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
|
onCheckedChange={(bool) => formMethods.setValue("periodType", bool ? "ROLLING" : "UNLIMITED")}>
|
||||||
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
|
||||||
<RadioGroup.Root
|
<RadioGroup.Root
|
||||||
defaultValue={watchPeriodType}
|
|
||||||
value={watchPeriodType}
|
value={watchPeriodType}
|
||||||
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
|
onValueChange={(val) => formMethods.setValue("periodType", val as PeriodType)}>
|
||||||
{PERIOD_TYPES.map((period) => {
|
{PERIOD_TYPES.map((period) => {
|
||||||
@@ -408,22 +383,23 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
className="border-default my-0 block w-16 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2"
|
className="border-default my-0 block w-16 text-sm [appearance:textfield] ltr:mr-2 rtl:ml-2"
|
||||||
placeholder="30"
|
placeholder="30"
|
||||||
{...formMethods.register("periodDays", { valueAsNumber: true })}
|
{...formMethods.register("periodDays", { valueAsNumber: true })}
|
||||||
defaultValue={eventType.periodDays || 30}
|
|
||||||
/>
|
/>
|
||||||
<Select
|
<Select
|
||||||
options={optionsPeriod}
|
options={optionsPeriod}
|
||||||
isSearchable={false}
|
isSearchable={false}
|
||||||
onChange={(opt) => {
|
onChange={(opt) =>
|
||||||
formMethods.setValue(
|
formMethods.setValue("periodCountCalendarDays", opt?.value === 1 ? "1" : "0")
|
||||||
"periodCountCalendarDays",
|
|
||||||
opt?.value.toString() as "0" | "1"
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
defaultValue={
|
|
||||||
optionsPeriod.find(
|
|
||||||
(opt) => opt.value === (eventType.periodCountCalendarDays ? 1 : 0)
|
|
||||||
) ?? optionsPeriod[0]
|
|
||||||
}
|
}
|
||||||
|
name="periodCoundCalendarDays"
|
||||||
|
value={optionsPeriod.find((opt) => {
|
||||||
|
opt.value ===
|
||||||
|
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0);
|
||||||
|
})}
|
||||||
|
defaultValue={optionsPeriod.find(
|
||||||
|
(opt) =>
|
||||||
|
opt.value ===
|
||||||
|
(formMethods.getValues("periodCountCalendarDays") === "1" ? 1 : 0)
|
||||||
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -431,8 +407,6 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
|
<div className="me-2 ms-2 inline-flex space-x-2 rtl:space-x-reverse">
|
||||||
<Controller
|
<Controller
|
||||||
name="periodDates"
|
name="periodDates"
|
||||||
control={formMethods.control}
|
|
||||||
defaultValue={periodDates}
|
|
||||||
render={() => (
|
render={() => (
|
||||||
<DateRangePicker
|
<DateRangePicker
|
||||||
startDate={formMethods.getValues("periodDates").startDate}
|
startDate={formMethods.getValues("periodDates").startDate}
|
||||||
@@ -481,7 +455,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
type="number"
|
type="number"
|
||||||
containerClassName="max-w-80"
|
containerClassName="max-w-80"
|
||||||
label={t("offset_start")}
|
label={t("offset_start")}
|
||||||
{...formMethods.register("offsetStart")}
|
{...formMethods.register("offsetStart", { setValueAs: (value) => Number(value) })}
|
||||||
addOnSuffix={<>{t("minutes")}</>}
|
addOnSuffix={<>{t("minutes")}</>}
|
||||||
hint={t("offset_start_description", {
|
hint={t("offset_start_description", {
|
||||||
originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
originalTime: offsetOriginalTime.toLocaleTimeString(i18n.language, { timeStyle: "short" }),
|
||||||
@@ -528,7 +502,10 @@ const IntervalLimitItem = ({
|
|||||||
onIntervalSelect,
|
onIntervalSelect,
|
||||||
}: IntervalLimitItemProps) => {
|
}: IntervalLimitItemProps) => {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 flex max-h-9 items-center space-x-2 text-sm rtl:space-x-reverse" key={limitKey}>
|
<div
|
||||||
|
data-testid="add-limit"
|
||||||
|
className="mb-4 flex max-h-9 items-center space-x-2 text-sm rtl:space-x-reverse"
|
||||||
|
key={limitKey}>
|
||||||
<TextField
|
<TextField
|
||||||
required
|
required
|
||||||
type="number"
|
type="number"
|
||||||
|
|||||||
@@ -9,9 +9,5 @@ export const EventRecurringTab = ({ eventType }: Pick<EventTypeSetupProps, "even
|
|||||||
|
|
||||||
const requirePayment = paymentAppData.price > 0;
|
const requirePayment = paymentAppData.price > 0;
|
||||||
|
|
||||||
return (
|
return <RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />;
|
||||||
<div className="">
|
|
||||||
<RecurringEventController paymentEnabled={requirePayment} eventType={eventType} />
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
Loaded 100 of 581 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user