Compare commits

...
53 Commits
Author SHA1 Message Date
Keith Williams 5ca216b922 v3.7.3
release.yml / release (push) Canceled after 0s
2024-01-23 09:34:58 -03:00
0abff9f562 feat: Add ICS Feed support (#13337)
* feat: ics-feed proof of concept pt. 1

* feat: ics-feedcalendar fixes

* fix: ics feed i18n and icon issues

* fix: remove debug log

* chore: revert yarn.lock

* fix: remove debug log

---------

Co-authored-by: Omar López <zomars@me.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2024-01-23 12:30:40 +00:00
Carina WollendorferandCarinaWolli 942dc38b58 fix: dark mode for OAuth /authorize page (#13363)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-01-22 21:40:03 +00:00
Benny Joo 827d8995a1 chore: [app-router-migration 38]: Refactor migrated pages and fix generateMetadata functions (#13314)
* refactor to keep consistencies in files

* manual: more refactors

* revert unnecessary changes

* fix type errors

* fix build error

* fix
2024-01-22 17:32:15 -03:00
850e222f0e feat: instant meeting improvements (#13258)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2024-01-22 19:48:23 +00:00
Mehul ede2357203 fix: SAML postgres err (#12437) 2024-01-22 15:41:43 +00:00
559ab4192f fix: team invite broken link (#13344)
* fix: team invite broken link

* default value fix

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-22 15:31:57 +00:00
3c8c6d0574 feat: Add Owner during organization creation (#13069)
* feat: Add Owner during organization creation

* chore: remove dead code

* fix: change route from onboard-admin to onboard-members

* fix: change button name to add_members from add_team_member

* fix: change MembershipRole badge colour

* fix: previous membership role bug

* update: "On-board administrators" tests

* fix: wrong import

* Reverted extra changes

* fix: current user cannot remove itself

* Reverted extra changes

* Reverted extra changes 2

* send isOrg to removeMember handler

* fix: removed prisma.user.delete cleanup as not needed

---------

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-01-22 10:24:09 -05:00
Hariom Balhara 2ae2c15083 Validate that a user isnt a member of the organization before marking the username unavailable (#13354) 2024-01-22 08:01:55 -03:00
313bb89789 refactor: Booker Component (#13176)
* refactor: booker component

* refactor: booker component

* refactor: useBookings, useOverlayCalendar

* fix: imports and types

* fix: don't allow uid update in bookings update api (#13071)

* don't allow uid update in bookings update api

* omit uid from schemaBookingEditBodyParams instead of updating schemaBookingBaseBodyParams

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>

* New Crowdin translations by Github Action

* fix: use DateRangePicker component in my-account/out-of-office (#13207)

* fix: remove duplicate useBrandColors

* fix: add missing localstorage package

* fix: booking form initial values

* fix: redirect to payment form

* refactor: handle fetching schedule only in Booker

* fixup! refactor: handle fetching schedule only in Booker

* fix: remove empty file

* useBookerLayout.ts useEffect dependencies

isMobile and isEmbed were missing and validateLayout was redundant

* useInitialFormValues useEffect dependencies

* fix: remove unused hook

* refactor: split logic from data fetch overlayCalendar

* fix: small changes

* fix: inifinite loop

* fix: toggle calendar

---------

Co-authored-by: Rajiv Sahal <sahalrajiv-extc@atharvacoe.ac.in>
Co-authored-by: Riddhesh Mahajan <40472653+riddhesh-mahajan@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
Co-authored-by: Carina Wollendorfer <30310907+CarinaWolli@users.noreply.github.com>
Co-authored-by: Lauris Skraucis <lauris.skraucis@gmail.com>
2024-01-22 11:57:00 +02:00
MehulandKeith Williams 95e037c664 fix: event api accepts invalid start & end values (#13203)
* fix: event api accepts invalid start & end values

* fix: unit tests

* test: added 'event length check during booking'

* test: fix unit test

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-20 17:31:11 +00:00
techknowlogick da026d909d feat(app-store): add feishu calendar (#13187) 2024-01-20 14:36:09 +00:00
Riddhesh Mahajan c6b50bfd14 prevent primary key update in memberships api (#13083) 2024-01-20 10:41:50 -03:00
Peer Richelsenandsean-brydon 30b433397a Removed shell from troubleshooter (#13303)
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-01-20 10:29:12 -03:00
DmytroHryshyn ff1aefe539 chore: [app-router-migration 39] refactor ssr/ssg functions (#13336) 2024-01-19 15:58:37 -07:00
Crowdin Bot 5685d343b1 New Crowdin translations by Github Action 2024-01-19 22:40:08 +00:00
Peer RichelsenandUdit Takkar ae3f30fabf chore: fixed some of the terms language (#13334)
* fixed some of the terms language

* minor changes

* Update apps/web/pages/settings/organizations/[id]/onboard-admins.tsx

* fixed yarn

* nit

* fix: trans

---------

Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-01-19 22:37:04 +00:00
c11e76bac9 chore: [app-router-migration 30] migrate the "login" page (#13195)
Co-authored-by: Dmytro Hryshyn <dev.dmytroh@gmail.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2024-01-19 15:14:14 -07:00
eed34559b5 feat: added upgrade page (#13287)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Utkarsh Nagar <utkarshnagar2000@gmail.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2024-01-19 21:37:40 +00:00
bf958c82da chore: [app-router-migration 32] migrate the "/auth/forgot-password/*" pages (#13197)
Co-authored-by: Dmytro Hryshyn <dev.dmytroh@gmail.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2024-01-19 21:08:05 +00:00
Sasha 728a386bf0 chore: [app-router-migration 35] migrate the "sso" pages (#13200)
## What does this PR do?

- This PR migrates the `/auth/sso/*` page group to the app directory (which runs under the App Router).
- The migrated page exists under `/future/auth/sso/*` and is accessible at all times.

## Requirement/Documentation

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

- [x] Chore (refactoring code, technical debt, workflow improvements)
- [x] New feature (non-breaking change which adds functionality)

## How should this be tested?

There should be no changes between the old sso and new sso routes in terms of content and no downgrading in terms of performance.
The pages were not tested due to being unable to reproduce the flow locally.

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2024-01-19 14:00:36 -07:00
Sasha 41efb5ea04 chore: [app-router-migration 34] migrate the "/auth/logout" and "/auth/signin" pages (#13199)
## What does this PR do?

- This PR migrates the `/auth/logout` and `/auth/signin` pages to the app directory (which runs under the App Router).
- Using `/future/auth/logout` is available at all times and is supposed to log the user out as `/auth/logout` page does.
- However `signin` page (or fragment) was used before should behave the same using the `/future/auth/signin` route.

## Requirement/Documentation

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

- [x] Chore (refactoring code, technical debt, workflow improvements)
- [x] New feature (non-breaking change which adds functionality)

## How should this be tested?

- Using `/future/auth/logout` is available at all times and is supposed to log the user out as `/auth/logout` page does.
- However `signin` page (or fragment) was used before should behave the same using the `/future/auth/signin` route. It was not tested locally.

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2024-01-19 13:40:53 -07:00
Sasha b268540d35 chore: [app-router-migration 33] migrate the "verify email" pages (#13198)
## What does this PR do?

- This PR migrates the `/auth/verify` and `/auth/verify-email` pages to the app directory (which runs under the App Router).
- The migrated pages exist under `/future/auth/verify` and `/future/auth/verify-email` and are accessible at all times.

## Requirement/Documentation

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

- [x] Chore (refactoring code, technical debt, workflow improvements)
- [x] New feature (non-breaking change which adds functionality)

## How should this be tested?

There should be no changes between the old verify and new verify routes in terms of content and no downgrading in terms of performance.
The pages were not tested due to being unable to reproduce the flow locally.

## Mandatory Tasks

- [x] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.
2024-01-19 12:02:50 -07:00
DmytroHryshynandBenny Joo bdd1d3a99d chore: [app-router-migration 16]: migrate routing forms page (#13015)
Co-authored-by: Benny Joo <sldisek783@gmail.com>
2024-01-19 11:57:08 -07:00
Greg PabianandBenny Joo dd7673b1f9 chore: [app-router-migration 27] migrate the "signup" page (#13061)
Co-authored-by: Benny Joo <sldisek783@gmail.com>
2024-01-19 11:54:43 -07:00
Nils Jacobsen 29f37a7e98 fix: update inlang settings.json (#13295)
> I'm in the core Team of inlang

## Fix inlang's configuration file

We changed the `project.inlang.json` to a `settings.json` in a `project.inlang` dir. This enables the inlang project to store app information or metadata in a dir that is not relevant to you as an end user. 

#### Old
```tree
├── project.inlang.json
```

#### New
```tree
├── project.inlang
│   ├── settings.json
```
2024-01-19 11:34:52 -07:00
Anik Dhabal BabuandUdit Takkar 3983351dac fix: Allow impersonation from the /settings/admin/users page (#13281)
* add impersonation in the dropdown menu

* add logo

* fix another minor issue

* Update UsersTable.tsx

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-01-19 17:07:38 +00:00
Peer RichelsenandKeith Williams 01322431fc chore: flappy bird ui fixes (#13329)
* Update BookEventForm.tsx

* Apply suggestions from code review

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-19 14:32:08 +00:00
Maaz Ahmed KhanandKeith Williams dcd06b0663 fix: Troubleshooter - Vertical scrolling not working on left panel or calendar (Re-opened) (#13243)
* Fix scrolling on calendar

* Fix scrolling on TroubleshooterSidebar

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-19 11:02:56 -03:00
James GarbuttandKeith Williams 26e50d24b0 test(core): add tests for events utils (#13265)
This adds tests for the following utils found in `core`:

- event utils
- getAggregateWorkingHours
- getCalendarsEvents

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-19 13:22:28 +00:00
DmytroHryshyn 74803dc122 fix: fix-e2e-tests (#13330) 2024-01-19 10:02:34 -03:00
Carina WollendorferandCarinaWolli de65b85862 show correct dates (#13309)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-01-19 12:50:38 +00:00
Alex van AndelandOmar López 0ee56be1d8 chore: Cache-Control new style avatar URLs (#13285)
Co-authored-by: Omar López <zomars@me.com>
2024-01-19 11:57:02 +00:00
aman sagarandUdit Takkar de6f7dcfc0 fix: destructuring error (#13312)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-01-19 10:28:35 +00:00
956f2c7a2b feat: show pay & book label to the meeting confirmation button when payment is enabled. (#12960)
* feat: Meeting confirmation button when payment is enabled says `pay & book`

* Update apps/web/public/static/locales/en/common.json

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Keith Williams <keithwillcode@gmail.com>
2024-01-19 10:20:17 +00:00
14de0a1d62 test: Check the limits tab and your functionalities (teste2e-limits) (#12307)
* Check the limits tab and your functionalities

* Change way to grab element

---------

Co-authored-by: gitstart-calcom <gitstart-calcom@users.noreply.github.com>
Co-authored-by: GitStart-Cal.com <121884634+gitstart-calcom@users.noreply.github.com>
2024-01-19 10:18:10 +00:00
sean-brydon 51cca7481f fix: date range sizing - ooo mobile mode (#13289) 2024-01-19 09:56:43 +00:00
Carina WollendorferandCarinaWolli 3fe0280dee fix event title in calendar event (#13316)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-01-19 09:28:47 +00:00
Syed Ali Shahbaz 876fc188d4 Make usernameList optional and remove return obj discrepancy (#13323) 2024-01-19 09:28:03 +00:00
d1810d9f14 refactor: Canceling Seats (#13136)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2024-01-19 04:01:06 +00:00
a25cd43ddf fix: start and end date for ooo entry (#13284)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-01-19 04:00:21 +00:00
833a91375e feat: integrate avatarapi to pre-fill avatar (#13185)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2024-01-19 03:57:07 +00:00
8124627cc5 feat: preview of installed app name on the schedule screen (#13221)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Omar López <zomars@me.com>
2024-01-19 03:56:56 +00:00
Omar López 749747ad7e fix: skip e2e tests that are memory crashing (#13319) 2024-01-18 20:33:15 -07:00
Peer Richelsen 967f70a5ec fix: corrected signup condition to show social proof (#13317) 2024-01-18 15:54:02 -07:00
zomars 51c9b94fb5 chore: Syncs nextjs 2024-01-18 14:20:29 -07:00
Benny Joo 1976dd15c3 chore: [app-router-migration 37] Fix Build Errors in /bookings and /booking (#13313) 2024-01-18 13:46:42 -07:00
Anik Dhabal Babu 03671e2a58 fixes issue #13304 (#13306) 2024-01-18 16:05:43 +00:00
Nishith Munda 5e5f223908 fix: mobile-navbar (#13273) 2024-01-18 14:58:04 +00:00
zomars 9076a7eb42 fix: future build and type fixes 2024-01-17 12:01:32 -07:00
Benny Joo a9e50d76ca chore: [app-router-migration 29] Migrate /team/* page group (#13102) 2024-01-17 10:28:32 -07:00
bd4722554d chore: [app-router-migration 13.5] Add ability to reuse legacy getServerSideProps and getStaticProps on the /future pages (#13184)
Co-authored-by: Greg Pabian <35925521+grzpab@users.noreply.github.com>
Co-authored-by: Benny Joo <sldisek783@gmail.com>
Co-authored-by: zomars <zomars@me.com>
2024-01-17 10:27:15 -07:00
zomars 105d373b8e chore: Moves docker login to e2e jobs 2024-01-17 10:25:57 -07:00
320 changed files with 9062 additions and 4943 deletions
+5
View File
@@ -138,6 +138,11 @@ FORMBRICKS_HOST_URL=https://app.formbricks.com
FORMBRICKS_ENVIRONMENT_ID=
FORMBRICKS_FEEDBACK_SURVEY_ID=
# AvatarAPI
# Used to pre-fill avatar during signup
AVATARAPI_USERNAME=
AVATARAPI_PASSWORD=
# Twilio
# Used to send SMS reminders in workflows
TWILIO_SID=
+4
View File
@@ -28,6 +28,10 @@ jobs:
shard: [1, 2]
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
+4
View File
@@ -23,6 +23,10 @@ jobs:
shard: [1, 2]
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
+4
View File
@@ -28,6 +28,10 @@ jobs:
shard: [1, 2]
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
+4
View File
@@ -26,6 +26,10 @@ jobs:
matrix:
shard: [1, 2, 3, 4, 5]
steps:
- uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
- uses: ./.github/actions/yarn-install
-8
View File
@@ -12,14 +12,6 @@ concurrency:
cancel-in-progress: true
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:
name: Detect changes
runs-on: buildjet-4vcpu-ubuntu-2204
+1
View File
@@ -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 */
teamId: true,
userId: true,
id: true,
})
.partial({
accepted: true,
+1 -1
View File
@@ -33,7 +33,7 @@
"@sentry/nextjs": "^7.73.0",
"bcryptjs": "^2.4.3",
"memory-cache": "^0.2.0",
"next": "^13.4.6",
"next": "^13.5.4",
"next-api-middleware": "^1.0.1",
"next-axiom": "^0.17.0",
"next-swagger-doc": "^0.3.6",
+2 -2
View File
@@ -22,7 +22,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
const { usernameList, ...rest } = req.query;
let slugs = usernameList;
if (!Array.isArray(usernameList)) {
slugs = usernameList ? [usernameList] : [];
slugs = usernameList ? [usernameList] : undefined;
}
const input = getScheduleSchema.parse({ usernameList: slugs, ...rest });
const timeZoneSupported = input.timeZone ? isSupportedTimeZone(input.timeZone) : false;
@@ -40,7 +40,7 @@ async function handler(req: NextApiRequest, res: NextApiResponse) {
)
: availableSlots;
return slotsInProvidedTimeZone;
return { slots: slotsInProvidedTimeZone };
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} catch (cause) {
if (cause instanceof TRPCError) {
+1 -1
View File
@@ -92,7 +92,7 @@ export async function patchHandler(req: NextApiRequest) {
args.data.userId = bodyUserId;
}
if (args.data.eventTriggers) {
if (eventTriggers) {
const eventTriggersSet = new Set(eventTriggers);
args.data.eventTriggers = Array.from(eventTriggersSet);
}
+1 -1
View File
@@ -92,7 +92,7 @@ async function postHandler(req: NextApiRequest) {
args.data.userId = bodyUserId;
}
if (args.data.eventTriggers) {
if (eventTriggers) {
const eventTriggersSet = new Set(eventTriggers);
args.data.eventTriggers = Array.from(eventTriggersSet);
}
+1 -1
View File
@@ -24,7 +24,7 @@
"@storybook/blocks": "^7.6.3",
"@storybook/nextjs": "^7.6.3",
"@storybook/preview-api": "^7.6.3",
"next": "^13.4.6",
"next": "^13.5.4",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"storybook-addon-rtl-direction": "^0.0.19"
+1 -1
View File
@@ -14,7 +14,7 @@
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^13.4.6",
"next": "^13.5.4",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
+24
View File
@@ -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 { notFound, redirect } from "next/navigation";
export const withAppDir =
export const withAppDirSsr =
<T extends Record<string, any>>(getServerSideProps: GetServerSideProps<T>) =>
async (context: GetServerSidePropsContext): Promise<T> => {
async (context: GetServerSidePropsContext) => {
const ssrResponse = await getServerSideProps(context);
if ("redirect" in ssrResponse) {
@@ -13,9 +13,11 @@ export const withAppDir =
notFound();
}
const props = await Promise.resolve(ssrResponse.props);
return {
...ssrResponse.props,
...props,
// includes dehydratedState required for future page trpcPropvider
...("trpcState" in ssrResponse.props && { dehydratedState: ssrResponse.props.trpcState }),
...("trpcState" in props && { dehydratedState: props.trpcState }),
};
};
@@ -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">;
+23 -98
View File
@@ -1,40 +1,33 @@
import AppPage from "@pages/apps/[slug]/index";
import Page from "@pages/apps/[slug]/index";
import { Prisma } from "@prisma/client";
import { withAppDirSsg } from "app/WithAppDirSsg";
import { _generateMetadata } from "app/_utils";
import fs from "fs";
import matter from "gray-matter";
import { notFound } from "next/navigation";
import path from "path";
import { z } from "zod";
import { WithLayout } from "app/layoutHOC";
import type { InferGetStaticPropsType } from "next";
import { cookies, headers } from "next/headers";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import { APP_NAME, IS_PRODUCTION } from "@calcom/lib/constants";
import { APP_NAME } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
const sourceSchema = z.object({
content: z.string(),
data: z.object({
description: z.string().optional(),
items: z
.array(
z.union([
z.string(),
z.object({
iframe: z.object({ src: z.string() }),
}),
])
)
.optional(),
}),
});
import { getStaticProps } from "@lib/apps/[slug]/getStaticProps";
import { buildLegacyCtx } from "@lib/buildLegacyCtx";
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
const { data } = await getPageProps({ params });
type Y = InferGetStaticPropsType<typeof getStaticProps>;
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(
() => `${data.name} | ${APP_NAME}`,
() => data.description
() => `${res?.data.name} | ${APP_NAME}`,
() => res?.data.description ?? ""
);
};
@@ -53,74 +46,6 @@ export const generateStaticParams = async () => {
return [];
};
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
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 default WithLayout({ getLayout: null, Page, getData });
export const dynamic = "force-static";
+7 -22
View File
@@ -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 type { GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { notFound, redirect } from "next/navigation";
import { WithLayout } from "app/layoutHOC";
import type { InferGetServerSidePropsType } from "next";
import { getServerSideProps } from "@calcom/app-store/_pages/setup/_getServerSideProps";
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[]> }) => {
const req = { headers: headers(), cookies: cookies() };
type T = InferGetServerSidePropsType<typeof getServerSideProps>;
const result = await getServerSideProps({ params, req } as unknown as GetServerSidePropsContext);
const getData = withAppDirSsr<T>(getServerSideProps);
if (!result || "notFound" in result) {
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} />;
}
export default WithLayout({ getLayout: null, Page, getData });
@@ -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 { withAppDirSsg } from "app/WithAppDirSsg";
import { _generateMetadata } from "app/_utils";
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 prisma from "@calcom/prisma";
import { AppCategories } from "@calcom/prisma/enums";
import { getStaticProps } from "@lib/apps/categories/[category]/getStaticProps";
export const generateMetadata = async () => {
return await _generateMetadata(
() => `${APP_NAME} | ${APP_NAME}`,
@@ -34,38 +34,7 @@ export const generateStaticParams = async () => {
return paths.map((category) => ({ category }));
};
const querySchema = z.object({
category: z.nativeEnum(AppCategories),
});
const getData = withAppDirSsg<PageProps>(getStaticProps);
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
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 default WithLayout({ getData, Page: CategoryPage, getLayout: null })<"P">;
export const dynamic = "force-static";
+4 -31
View File
@@ -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 { 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 { ssrInit } from "@server/lib/ssr";
import { getServerSideProps } from "@lib/apps/categories/getServerSideProps";
export const generateMetadata = async () => {
return await _generateMetadata(
@@ -16,29 +14,4 @@ export const generateMetadata = async () => {
);
};
const getData = async (ctx: GetServerSidePropsContext) => {
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">;
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), Page, 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 { notFound } from "next/navigation";
import { z } from "zod";
import { WithLayout } from "app/layoutHOC";
import { APP_NAME } from "@calcom/lib/constants";
import { AppCategories } from "@calcom/prisma/enums";
const querySchema = z.object({
category: z.nativeEnum(AppCategories),
});
import { getServerSideProps } from "@lib/apps/installed/[category]/getServerSideProps";
export const generateMetadata = async () => {
return await _generateMetadata(
@@ -17,20 +14,6 @@ export const generateMetadata = async () => {
);
};
const getPageProps = async ({ params }: { params: Record<string, string | string[]> }) => {
const p = querySchema.safeParse(params);
const getData = withAppDirSsr(getServerSideProps);
if (!p.success) {
return notFound();
}
return {
category: p.data.category,
};
};
export default async function Page({ params }: { params: Record<string, string | string[]> }) {
await getPageProps({ params });
return <LegacyPage />;
}
export default WithLayout({ getLayout: null, getData, Page });
+3 -48
View File
@@ -1,17 +1,12 @@
import AppsPage from "@pages/apps";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
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 { 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 type { AppCategories } from "@calcom/prisma/enums";
import { ssrInit } from "@server/lib/ssr";
import { getServerSideProps } from "@lib/apps/getServerSideProps";
export const generateMetadata = async () => {
return await _generateMetadata(
@@ -20,44 +15,4 @@ export const generateMetadata = async () => {
);
};
const getData = async (ctx: GetServerSidePropsContext) => {
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 });
export default WithLayout({ getLayout, getData: withAppDirSsr(getServerSideProps), 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">;
+22
View File
@@ -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">;
+7
View File
@@ -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">;
+13
View File
@@ -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">;
+11
View File
@@ -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 { WithLayout } from "app/layoutHOC";
import { getData } from "../page";
const getData = withAppDirSsr<PageProps>(getServerSideProps);
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 });
+26 -194
View File
@@ -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 { WithLayout } from "app/layoutHOC";
import type { GetServerSidePropsContext } from "next";
import { notFound } from "next/navigation";
import { z } from "zod";
import { type GetServerSidePropsContext } from "next";
import { cookies, headers } from "next/headers";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
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 { BookingStatus } from "@calcom/prisma/enums";
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";
const stringToBoolean = z
.string()
.optional()
.transform((val) => val === "true");
const querySchema = z.object({
uid: z.string(),
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(
() => "",
() => ""
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const { bookingInfo, eventType, recurringBookings } = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
const needsConfirmation = bookingInfo.status === BookingStatus.PENDING && eventType.requiresConfirmation;
export const getData = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
const session = await getServerSession(context);
let tz: string | null = null;
let userTimeFormat: number | null = null;
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,
};
return await _generateMetadata(
(t) =>
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`),
(t) =>
t(`booking_${needsConfirmation ? "submitted" : "confirmed"}${recurringBookings ? "_recurring" : ""}`)
);
};
// @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 });
@@ -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";
+29 -1
View File
@@ -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";
+13 -109
View File
@@ -1,27 +1,21 @@
import LegacyPage, { type PageProps } from "@pages/d/[link]/[slug]";
import { withAppDir } from "app/AppDirSSRHOC";
import LegacyPage from "@pages/d/[link]/[slug]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { Params, SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import type { GetServerSidePropsContext } from "next";
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 { getServerSideProps } from "@lib/d/[link]/[slug]/getServerSideProps";
import { ssrInit } from "@server/lib/ssr";
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
const pageProps = await getPageProps(
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
);
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Params;
searchParams: SearchParams;
}) => {
const pageProps = await getData(buildLegacyCtx(headers(), cookies(), params, searchParams));
const { entity, booking, user, slug, isTeamEvent } = pageProps;
const rescheduleUid = booking?.uid;
@@ -38,95 +32,5 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
);
};
async function getPageProps(context: GetServerSidePropsContext) {
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);
const getData = withAppDirSsr(getServerSideProps);
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">;
+4 -1
View File
@@ -1,4 +1,7 @@
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
import EnterprisePage from "@components/EnterprisePage";
@@ -8,4 +11,4 @@ export const generateMetadata = async () =>
(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">;
+4 -1
View File
@@ -1,5 +1,8 @@
import EventTypes from "@pages/event-types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { getLayout } from "@calcom/features/MainLayoutAppDir";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -7,4 +10,4 @@ export const generateMetadata = async () =>
(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 { type GetServerSidePropsContext } from "next";
import { redirect } from "next/navigation";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import prisma from "@calcom/prisma";
import { getServerSideProps } from "@lib/getting-started/[[...step]]/getServerSideProps";
import { ssrInit } from "@server/lib/ssr";
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 });
export default WithLayout({ getLayout: null, getData: withAppDirSsr(getServerSideProps), Page });
+5 -13
View File
@@ -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 { WithLayout } from "app/layoutHOC";
import { notFound } from "next/navigation";
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 () =>
await _generateMetadata(
@@ -12,15 +13,6 @@ export const generateMetadata = async () =>
(t) => t("insights_subtitle")
);
async function getData() {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const flags = await getFeatureFlagMap(prisma);
if (flags.insights === false) {
return notFound();
}
return {};
}
const getData = withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps);
export default WithLayout({ getLayout, getData, Page: LegacyPage });
@@ -1,13 +1,12 @@
import { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
import Page from "@pages/org/[orgSlug]/[user]/[type]/embed";
import { withAppDir } from "app/AppDirSSRHOC";
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 = withAppDir<PageProps>(getServerSideProps);
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
@@ -1,9 +1,9 @@
import Page, { type PageProps } from "@pages/org/[orgSlug]/[user]/[type]";
import { withAppDir } from "app/AppDirSSRHOC";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { WithLayout } from "app/layoutHOC";
import { getServerSideProps } from "@lib/org/[orgSlug]/[user]/[type]/getServerSideProps";
const getData = withAppDir<PageProps>(getServerSideProps);
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData, isBookingPage: true, Page });
@@ -1,10 +1,11 @@
import { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
import Page from "@pages/org/[orgSlug]/[user]/embed";
import { withAppDir } from "app/AppDirSSRHOC";
import { withAppDirSsr } from "app/WithAppDirSsr";
import withEmbedSsrAppDir from "app/WithEmbedSSR";
import { WithLayout } from "app/layoutHOC";
const getData = withAppDir<PageProps>(getServerSideProps);
const getData = withAppDirSsr<PageProps>(getServerSideProps);
const getEmbedData = withEmbedSsrAppDir(getData);
export default WithLayout({ getLayout: null, getData: getEmbedData, isBookingPage: true, Page });
@@ -1,7 +1,7 @@
import Page, { getServerSideProps, type PageProps } from "@pages/org/[orgSlug]/[user]";
import { withAppDir } from "app/AppDirSSRHOC";
import { withAppDirSsr } from "app/WithAppDirSsr";
import { WithLayout } from "app/layoutHOC";
const getData = withAppDir<PageProps>(getServerSideProps);
const getData = withAppDirSsr<PageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData, isBookingPage: true, Page });
+3 -152
View File
@@ -1,18 +1,10 @@
import { withAppDirSsr } from "app/WithAppDirSsr";
import { _generateMetadata } from "app/_utils";
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 { 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 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 () =>
await _generateMetadata(
@@ -21,147 +13,6 @@ export const generateMetadata = async () =>
() => ""
);
const querySchema = z.object({
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,
};
}
const getData = withAppDirSsr<PaymentPageProps>(getServerSideProps);
export default WithLayout({ getLayout: null, getData, Page: PaymentPage });
@@ -1,5 +1,6 @@
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 { cookies, headers } from "next/headers";
@@ -8,12 +9,12 @@ import withEmbedSsr from "@lib/withEmbedSsr";
type PageProps = Readonly<{
params: Params;
searchParams: SearchParams;
}>;
const Page = async ({ params }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params);
// @ts-expect-error Argument of type '{ query: Params; params: Params; req: { headers: ReadonlyHeaders; cookies: ReadonlyRequestCookies; }; }'
await withAppDir(withEmbedSsr(getServerSideProps))(legacyCtx);
const Page = async ({ params, searchParams }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params, searchParams);
await withAppDirSsr(withEmbedSsr(getServerSideProps))(legacyCtx);
return null;
};
@@ -1,5 +1,6 @@
import OldPage, { getServerSideProps as _getServerSideProps } from "@pages/reschedule/[uid]";
import { withAppDir } from "app/AppDirSSRHOC";
import { getServerSideProps } from "@pages/reschedule/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { SearchParams } from "app/_types";
import { _generateMetadata } from "app/_utils";
import type { Params } from "next/dist/shared/lib/router/utils/route-matcher";
import { headers, cookies } from "next/headers";
@@ -14,17 +15,17 @@ export const generateMetadata = async () =>
type PageProps = Readonly<{
params: Params;
searchParams: SearchParams;
}>;
const getData = withAppDir(_getServerSideProps);
const getData = withAppDirSsr(getServerSideProps);
const Page = async ({ params }: PageProps) => {
const legacyCtx = buildLegacyCtx(headers(), cookies(), params);
const Page = async ({ params, searchParams }: PageProps) => {
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);
return <OldPage />;
return null;
};
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 { _generateMetadata } from "app/_utils";
import { cookies, headers } from "next/headers";
import { WithLayout } from "app/layoutHOC";
import { z } from "zod";
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() });
export const generateMetadata = async ({ params }: { params: Params }) => {
const input = userIdSchema.safeParse(params);
let title = "";
if (!input.success) {
title = "Editing user";
} else {
const req = {
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}`;
return await _generateMetadata(
() => "",
() => "Here you can edit a current user."
);
}
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(
() => title,
() => "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 { WithLayout } from "app/layoutHOC";
import Page from "@calcom/features/ee/users/pages/users-add-view";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -8,4 +10,4 @@ export const generateMetadata = async () =>
() => "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 { WithLayout } from "app/layoutHOC";
import Page from "@calcom/features/ee/users/pages/users-listing-view";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayoutAppDir";
export const generateMetadata = async () =>
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."
);
export default Page;
export default WithLayout({ getLayout, Page })<"P">;
@@ -1,6 +1,6 @@
import LegacyPage, {
buildWrappedOnboardTeamMembersPage,
} from "@pages/settings/organizations/[id]/onboard-admins";
} from "@pages/settings/organizations/[id]/onboard-members";
import { type Params } from "app/_types";
import { _generateMetadata } from "app/_utils";
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 { 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 () =>
await _generateMetadata(
@@ -12,23 +11,8 @@ export const generateMetadata = async () =>
(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({
getLayout: WrappedCreateNewOrganizationPage,
getLayout: LayoutWrapperAppDir,
Page: LegacyPage,
getData: getPageProps,
getData: withAppDirSsr<inferSSRProps<typeof getServerSideProps>>(getServerSideProps),
});
+20
View File
@@ -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">;
+35
View File
@@ -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">;
+4 -25
View File
@@ -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 { WithLayout } from "app/layoutHOC";
import type { GetServerSidePropsContext } from "next";
import { redirect } from "next/navigation";
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 () =>
await _generateMetadata(
@@ -15,23 +13,4 @@ export const generateMetadata = async () =>
(t) => t("create_manage_teams_collaborative")
);
async function getData(context: GetServerSidePropsContext) {
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">;
export default WithLayout({ getData: withAppDirSsr(getServerSideProps), getLayout, Page })<"P">;
+5 -89
View File
@@ -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 { 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 prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { ssrInit } from "@server/lib/ssr";
import { getServerSideProps } from "@lib/video/[uid]/getServerSideProps";
export const generateMetadata = async () =>
await _generateMetadata(
@@ -17,86 +13,6 @@ export const generateMetadata = async () =>
(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) {
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">;
export default WithLayout({ getData, Page, 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 { 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 () =>
await _generateMetadata(
@@ -12,41 +11,6 @@ export const generateMetadata = async () =>
() => "Meeting Unavailable"
);
async function getData(context: Omit<GetServerSidePropsContext, "res" | "resolvedUrl">) {
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,
},
},
},
});
const getData = withAppDirSsr(getServerSideProps);
if (!booking) {
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">;
export default WithLayout({ getData, Page, getLayout: null })<"P">;
@@ -1,15 +1,12 @@
import OldPage from "@pages/video/meeting-not-started/[uid]";
import { type Params } from "app/_types";
import Page from "@pages/video/meeting-not-started/[uid]";
import { withAppDirSsr } from "app/WithAppDirSsr";
import type { PageProps } from "app/_types";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import { redirect } from "next/navigation";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
type PageProps = Readonly<{
params: Params;
}>;
import { getServerSideProps } from "@lib/video/meeting-not-started/[uid]/getServerSideProps";
export const generateMetadata = async ({ params }: PageProps) => {
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 booking = await prisma.booking.findUnique({
where: {
uid: typeof context?.params?.uid === "string" ? context.params.uid : "",
},
select: bookingMinimalSelect,
});
const getData = withAppDirSsr(getServerSideProps);
if (!booking) {
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">;
export default WithLayout({ getData, Page, getLayout: null })<"P">;
@@ -1,7 +1,7 @@
import LegacyPage from "@pages/video/no-meeting-found";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
import type { GetServerSidePropsContext } from "next";
import { ssrInit } from "@server/lib/ssr";
@@ -11,6 +11,7 @@ export const generateMetadata = async () =>
(t) => t("no_meeting_found")
);
// ssr was added by Intuita, legacy page does not have it
const getData = async (context: GetServerSidePropsContext) => {
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 { WithLayout } from "app/layoutHOC";
import { type GetServerSidePropsContext } from "next";
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";
const querySchema = z.object({
workflow: z.string(),
});
export const generateMetadata = async ({ params }: { params: Record<string, string | string[]> }) => {
const { workflow } = await getProps(
buildLegacyCtx(headers(), cookies(), params) as unknown as GetServerSidePropsContext
export const generateMetadata = async ({
params,
searchParams,
}: {
params: Record<string, string | string[]>;
searchParams: { [key: string]: string | string[] | undefined };
}) => {
const { workflow } = await getData(
buildLegacyCtx(headers(), cookies(), params, searchParams) as unknown as GetServerSidePropsContext
);
return await _generateMetadata(
() => workflow ?? "Untitled",
@@ -23,19 +23,12 @@ export const generateMetadata = async ({ params }: { params: Record<string, stri
);
};
async function getProps(context: GetServerSidePropsContext) {
const safeParams = querySchema.safeParse(context.params);
console.log("Built workflow page:", safeParams);
if (!safeParams.success) {
return notFound();
}
return { workflow: safeParams.data.workflow };
}
const getData = withAppDirSsg(getStaticProps);
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";
// generate segments on demand
export const dynamicParams = true;
+6 -4
View File
@@ -9,7 +9,7 @@ import PageWrapper from "@components/PageWrapperAppDir";
type WithLayoutParams<T extends Record<string, any>> = {
getLayout: ((page: React.ReactElement) => React.ReactNode) | null;
Page?: (props: T) => React.ReactElement | null;
getData?: (arg: GetServerSidePropsContext) => Promise<T>;
getData?: (arg: GetServerSidePropsContext) => Promise<T | undefined>;
isBookingPage?: boolean;
};
@@ -22,9 +22,11 @@ export function WithLayout<T extends Record<string, any>>({
return async <P extends "P" | "L">(p: P extends "P" ? PageProps : LayoutProps) => {
const h = headers();
const nonce = h.get("x-nonce") ?? undefined;
const props = getData
? await getData(buildLegacyCtx(h, cookies(), p.params) as unknown as GetServerSidePropsContext)
: ({} as T);
let props = {} 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;
+3 -3
View File
@@ -2,13 +2,13 @@ import NotFoundPage from "@pages/404";
import { WithLayout } from "app/layoutHOC";
import type { GetStaticPropsContext } from "next";
import { ssgInit } from "@server/lib/ssg";
import { getTranslations } from "@server/lib/getTranslations";
const getData = async (context: GetStaticPropsContext) => {
const ssg = await ssgInit(context);
const i18n = await getTranslations(context);
return {
dehydratedState: ssg.dehydrate(),
i18n,
};
};
+1 -2
View File
@@ -89,8 +89,7 @@ function PageWrapper(props: AppProps) {
</LicenseRequired>
) : (
<Component {...pageProps} err={err} />
),
router
)
)}
</AppProviders>
);
@@ -528,7 +528,10 @@ const IntervalLimitItem = ({
onIntervalSelect,
}: IntervalLimitItemProps) => {
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
required
type="number"
@@ -1,3 +1,4 @@
import type { Webhook } from "@prisma/client";
import { useSession } from "next-auth/react";
import type { EventTypeSetup, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
@@ -5,10 +6,16 @@ import { useFormContext } from "react-hook-form";
import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired";
import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hooks/useLockedFieldsManager";
import { WebhookForm } from "@calcom/features/webhooks/components";
import type { WebhookFormSubmitData } from "@calcom/features/webhooks/components/WebhookForm";
import WebhookListItem from "@calcom/features/webhooks/components/WebhookListItem";
import { subscriberUrlReserved } from "@calcom/features/webhooks/lib/subscriberUrlReserved";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Alert, Button, EmptyScreen, SettingsToggle } from "@calcom/ui";
import { PhoneCall } from "@calcom/ui/components/icon";
import { WebhookTriggerEvents } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import { Alert, Button, EmptyScreen, SettingsToggle, Dialog, DialogContent, showToast } from "@calcom/ui";
import { PhoneCall, Plus, Lock, Webhook as TbWebhook } from "@calcom/ui/components/icon";
type InstantEventControllerProps = {
eventType: EventTypeSetup;
@@ -82,11 +89,7 @@ export default function InstantEventController({
}
}}>
<div className="border-subtle rounded-b-lg border border-t-0 p-6">
{instantEventState && (
<div data-testid="instant-event-collapsible" className="flex flex-col gap-2 text-sm">
<p>{t("warning_payment_instant_meeting_event")}</p>
</div>
)}
{instantEventState && <InstantMeetingWebhooks eventType={eventType} />}
</div>
</SettingsToggle>
</>
@@ -97,3 +100,201 @@ export default function InstantEventController({
</LicenseRequired>
);
}
const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) => {
const { t } = useLocale();
const utils = trpc.useContext();
const { data: webhooks } = trpc.viewer.webhook.list.useQuery({
eventTypeId: eventType.id,
eventTriggers: [WebhookTriggerEvents.INSTANT_MEETING],
});
const { data: installedApps, isLoading } = trpc.viewer.integrations.useQuery({
variant: "other",
onlyInstalled: true,
});
const [createModalOpen, setCreateModalOpen] = useState(false);
const [editModalOpen, setEditModalOpen] = useState(false);
const [webhookToEdit, setWebhookToEdit] = useState<Webhook>();
const editWebhookMutation = trpc.viewer.webhook.edit.useMutation({
async onSuccess() {
setEditModalOpen(false);
await utils.viewer.webhook.list.invalidate();
showToast(t("webhook_updated_successfully"), "success");
},
onError(error) {
showToast(`${error.message}`, "error");
},
});
const createWebhookMutation = trpc.viewer.webhook.create.useMutation({
async onSuccess() {
showToast(t("webhook_created_successfully"), "success");
await utils.viewer.webhook.list.invalidate();
setCreateModalOpen(false);
},
onError(error) {
showToast(`${error.message}`, "error");
},
});
const onCreateWebhook = async (values: WebhookFormSubmitData) => {
if (
subscriberUrlReserved({
subscriberUrl: values.subscriberUrl,
id: values.id,
webhooks,
eventTypeId: eventType.id,
})
) {
showToast(t("webhook_subscriber_url_reserved"), "error");
return;
}
if (!values.payloadTemplate) {
values.payloadTemplate = null;
}
createWebhookMutation.mutate({
subscriberUrl: values.subscriberUrl,
eventTriggers: values.eventTriggers,
active: values.active,
payloadTemplate: values.payloadTemplate,
secret: values.secret,
eventTypeId: eventType.id,
});
};
const NewWebhookButton = () => {
const { t } = useLocale();
return (
<Button
color="secondary"
data-testid="new_webhook"
StartIcon={Plus}
onClick={() => setCreateModalOpen(true)}>
{t("new_webhook")}
</Button>
);
};
const { shouldLockDisableProps, isChildrenManagedEventType, isManagedEventType } = useLockedFieldsManager(
eventType,
t("locked_fields_admin_description"),
t("locked_fields_member_description")
);
const webhookLockedStatus = shouldLockDisableProps("webhooks");
return (
<div>
{webhooks && !isLoading && (
<>
<div>
{webhooks.length ? (
<>
<div className="border-subtle my-2 rounded-md border">
{webhooks.map((webhook, index) => {
return (
<WebhookListItem
key={webhook.id}
webhook={webhook}
lastItem={webhooks.length === index + 1}
canEditWebhook={!webhookLockedStatus.disabled}
onEditWebhook={() => {
setEditModalOpen(true);
setWebhookToEdit(webhook);
}}
/>
);
})}
</div>
<p className="text-default text-sm font-normal">
{t("warning_payment_instant_meeting_event")}
</p>
</>
) : (
<>
<p className="text-default mb-4 text-sm font-normal">
{t("warning_payment_instant_meeting_event")}
</p>
<EmptyScreen
Icon={TbWebhook}
headline={t("create_your_first_webhook")}
description={t("create_instant_meeting_webhook_description")}
buttonRaw={
isChildrenManagedEventType && !isManagedEventType ? (
<Button StartIcon={Lock} color="secondary" disabled>
{t("locked_by_admin")}
</Button>
) : (
<NewWebhookButton />
)
}
/>
</>
)}
</div>
{/* New webhook dialog */}
<Dialog open={createModalOpen} onOpenChange={(isOpen) => !isOpen && setCreateModalOpen(false)}>
<DialogContent
enableOverflow
title={t("create_webhook")}
description={t("create_webhook_team_event_type")}>
<WebhookForm
noRoutingFormTriggers={true}
onSubmit={onCreateWebhook}
onCancel={() => setCreateModalOpen(false)}
apps={installedApps?.items.map((app) => app.slug)}
selectOnlyInstantMeetingOption={true}
/>
</DialogContent>
</Dialog>
{/* Edit webhook dialog */}
<Dialog open={editModalOpen} onOpenChange={(isOpen) => !isOpen && setEditModalOpen(false)}>
<DialogContent enableOverflow title={t("edit_webhook")}>
<WebhookForm
noRoutingFormTriggers={true}
webhook={webhookToEdit}
apps={installedApps?.items.map((app) => app.slug)}
onCancel={() => setEditModalOpen(false)}
onSubmit={(values: WebhookFormSubmitData) => {
if (
subscriberUrlReserved({
subscriberUrl: values.subscriberUrl,
id: webhookToEdit?.id,
webhooks,
eventTypeId: eventType.id,
})
) {
showToast(t("webhook_subscriber_url_reserved"), "error");
return;
}
if (values.changeSecret) {
values.secret = values.newSecret.length ? values.newSecret : null;
}
if (!values.payloadTemplate) {
values.payloadTemplate = null;
}
editWebhookMutation.mutate({
id: webhookToEdit?.id || "",
subscriberUrl: values.subscriberUrl,
eventTriggers: values.eventTriggers,
active: values.active,
payloadTemplate: values.payloadTemplate,
secret: values.secret,
eventTypeId: webhookToEdit?.eventTypeId || undefined,
});
}}
/>
</DialogContent>
</Dialog>
</>
)}
</div>
);
};
+3 -2
View File
@@ -37,6 +37,7 @@ export type AppProps = Omit<
WithNonceProps<{
themeBasis?: string;
session: Session;
i18n?: SSRConfig;
}>
>
>,
@@ -46,7 +47,7 @@ export type AppProps = Omit<
requiresLicense?: boolean;
isThemeSupported?: boolean;
isBookingPage?: boolean | ((arg: { router: NextAppProps["router"] }) => boolean);
getLayout?: (page: React.ReactElement, router: NextAppProps["router"]) => ReactNode;
getLayout?: (page: React.ReactElement) => ReactNode;
PageWrapper?: (props: AppProps) => JSX.Element;
};
@@ -108,7 +109,7 @@ const CustomI18nextProvider = (props: AppPropsWithoutNonce) => {
}, [locale]);
const clientViewerI18n = useViewerI18n(locale);
const i18n = clientViewerI18n.data?.i18n;
const i18n = clientViewerI18n.data?.i18n ?? props.pageProps.i18n;
const passedProps = {
...props,
@@ -0,0 +1,94 @@
import fs from "fs";
import matter from "gray-matter";
import MarkdownIt from "markdown-it";
import type { GetStaticPropsContext } from "next";
import path from "path";
import { z } from "zod";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import { getAppAssetFullPath } from "@calcom/app-store/getAppAssetFullPath";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import prisma from "@calcom/prisma";
const md = new MarkdownIt("default", { html: true, breaks: true });
export const sourceSchema = z.object({
content: z.string(),
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 getStaticProps = async (ctx: GetStaticPropsContext) => {
if (typeof ctx.params?.slug !== "string") return { notFound: true } as const;
const appMeta = await getAppWithMetadata({
slug: ctx.params?.slug,
});
const appFromDb = await prisma.app.findUnique({
where: { slug: ctx.params.slug.toLowerCase() },
});
const isAppAvailableInFileSystem = appMeta;
const isAppDisabled = isAppAvailableInFileSystem && (!appFromDb || !appFromDb.enabled);
if (!IS_PRODUCTION && isAppDisabled) {
return {
props: {
isAppDisabled: true as const,
data: {
...appMeta,
},
},
};
}
if (!appFromDb || !appMeta || isAppDisabled) return { notFound: true } as const;
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 {
props: {
isAppDisabled: false as const,
source: { content, data },
data: appMeta,
},
};
};
@@ -0,0 +1,31 @@
import type { GetStaticPropsContext } from "next";
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
import prisma from "@calcom/prisma";
import type { AppCategories } from "@calcom/prisma/enums";
export const getStaticProps = async (context: GetStaticPropsContext) => {
const category = context.params?.category as AppCategories;
const appQuery = await prisma.app.findMany({
where: {
categories: {
has: 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 {
props: {
apps,
},
};
};
@@ -0,0 +1,35 @@
import type { GetServerSidePropsContext } from "next";
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { ssrInit } from "@server/lib/ssr";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req } = context;
const ssr = await ssrInit(context);
const session = await getServerSession({ 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 {
props: {
categories: Object.entries(categories).map(([name, count]) => ({ name, count })),
trpcState: ssr.dehydrate(),
},
};
};
+52
View File
@@ -0,0 +1,52 @@
import type { GetServerSidePropsContext } from "next";
import { getAppRegistry, getAppRegistryWithCredentials } from "@calcom/app-store/_appRegistry";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import getUserAdminTeams from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import type { UserAdminTeams } from "@calcom/features/ee/teams/lib/getUserAdminTeams";
import type { AppCategories } from "@calcom/prisma/enums";
import { ssrInit } from "@server/lib/ssr";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req } = context;
const ssr = await ssrInit(context);
const session = await getServerSession({ 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 {
props: {
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,
trpcState: ssr.dehydrate(),
},
};
};
@@ -0,0 +1,43 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { AppCategories } from "@calcom/prisma/enums";
export type querySchemaType = z.infer<typeof querySchema>;
export const querySchema = z.object({
category: z.nativeEnum(AppCategories),
});
export async function getServerSideProps(ctx: GetServerSidePropsContext) {
// get return-to cookie and redirect if needed
const { cookies } = ctx.req;
const returnTo = cookies["return-to"];
if (cookies && returnTo) {
ctx.res.setHeader("Set-Cookie", "return-to=; path=/; expires=Thu, 01 Jan 1970 00:00:00 GMT");
const redirect = {
redirect: {
destination: `${returnTo}`,
permanent: false,
},
} as const;
return redirect;
}
const params = querySchema.safeParse(ctx.params);
if (!params.success) {
const notFound = { notFound: true } as const;
return notFound;
}
return {
props: {
category: params.data.category,
},
};
}
@@ -0,0 +1,3 @@
export async function getServerSideProps() {
return { redirect: { permanent: false, destination: "/apps/installed/calendar" } };
}
@@ -0,0 +1,24 @@
import { type GetStaticProps } from "next";
import { z } from "zod";
import { getTranslations } from "@server/lib/getTranslations";
const validStatuses = ["upcoming", "recurring", "past", "cancelled", "unconfirmed"] as const;
const querySchema = z.object({
status: z.enum(validStatuses),
});
export const getStaticProps: GetStaticProps = async (ctx) => {
const params = querySchema.safeParse(ctx.params);
const i18n = await getTranslations(ctx);
if (!params.success) return { notFound: true };
return {
props: {
status: params.data.status,
i18n,
},
};
};
+38 -12
View File
@@ -1,23 +1,49 @@
import type { SearchParams } from "app/_types";
import { type Params } from "app/_types";
import type { GetServerSidePropsContext } from "next";
import { type ReadonlyHeaders } from "next/dist/server/web/spec-extension/adapters/headers";
import { type ReadonlyRequestCookies } from "next/dist/server/web/spec-extension/adapters/request-cookies";
// returns query object same as ctx.query but for app dir
export const getQuery = (url: string, params: Params) => {
if (!url.length) {
return params;
}
const createProxifiedObject = (object: Record<string, string>) =>
new Proxy(object, {
set: () => {
throw new Error("You are trying to modify 'headers' or 'cookies', which is not supported in app dir");
},
});
const { searchParams } = new URL(url);
const searchParamsObj = Object.fromEntries(searchParams.entries());
const buildLegacyHeaders = (headers: ReadonlyHeaders) => {
const headersObject = Object.fromEntries(headers.entries());
return { ...searchParamsObj, ...params };
return createProxifiedObject(headersObject);
};
export const buildLegacyCtx = (headers: ReadonlyHeaders, cookies: ReadonlyRequestCookies, params: Params) => {
const buildLegacyCookies = (cookies: ReadonlyRequestCookies) => {
const cookiesObject = cookies.getAll().reduce<Record<string, string>>((acc, { name, value }) => {
acc[name] = value;
return acc;
}, {});
return createProxifiedObject(cookiesObject);
};
export const buildLegacyCtx = (
headers: ReadonlyHeaders,
cookies: ReadonlyRequestCookies,
params: Params,
searchParams: SearchParams
) => {
return {
query: getQuery(headers.get("x-url") ?? "", params),
query: { ...searchParams, ...params },
params,
req: { headers, cookies },
};
req: { headers: buildLegacyHeaders(headers), cookies: buildLegacyCookies(cookies) },
res: new Proxy(Object.create(null), {
// const { req, res } = ctx - valid
// res.anything - throw
get() {
throw new Error(
"You are trying to access the 'res' property of the context, which is not supported in app dir"
);
},
}),
} as unknown as GetServerSidePropsContext;
};
@@ -0,0 +1,121 @@
import type { GetServerSidePropsContext } from "next";
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 type { inferSSRProps } from "@lib/types/inferSSRProps";
import type { EmbedProps } from "@lib/withEmbedSsr";
export type PageProps = inferSSRProps<typeof getServerSideProps> & EmbedProps;
async function getUserPageProps(context: GetServerSidePropsContext) {
const session = await getServerSession(context);
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 { ssrInit } = await import("@server/lib/ssr");
const ssr = await ssrInit(context);
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;
const notFound = {
notFound: true,
} as const;
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;
// 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 {
props: {
entity: eventData.entity,
duration: getMultipleDurationValue(
eventData.metadata?.multipleDuration,
queryDuration,
eventData.length
),
booking,
away: user?.away,
user: username,
slug,
trpcState: 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)) });
// Booker page fetches a tiny bit of data server side, to determine early
// whether the page should show an away state or dynamic booking not allowed.
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
return await getUserPageProps(context);
};
@@ -0,0 +1,42 @@
import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { asStringOrThrow } from "@lib/asStringOrNull";
import type { inferSSRProps } from "@lib/types/inferSSRProps";
import { ssrInit } from "@server/lib/ssr";
export type PageProps = inferSSRProps<typeof getServerSideProps>;
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req, res, query } = context;
const session = await getServerSession({ req, res });
const typeParam = parseInt(asStringOrThrow(query.type));
const ssr = await ssrInit(context);
if (Number.isNaN(typeParam)) {
return {
notFound: true,
};
}
if (!session?.user?.id) {
return {
redirect: {
permanent: false,
destination: "/auth/login",
},
};
}
await ssr.viewer.eventTypes.get.prefetch({ id: typeParam });
return {
props: {
type: typeParam,
trpcState: ssr.dehydrate(),
},
};
};
+11
View File
@@ -0,0 +1,11 @@
// returns query object same as ctx.query but for app dir
export const getQuery = (url: string, params: Record<string, string | string[]>) => {
if (!url.length) {
return params;
}
const { searchParams } = new URL(url);
const searchParamsObj = Object.fromEntries(searchParams.entries());
return { ...searchParamsObj, ...params };
};
@@ -0,0 +1,59 @@
import type { GetServerSidePropsContext } from "next";
import { serverSideTranslations } from "next-i18next/serverSideTranslations";
import { getLocale } from "@calcom/features/auth/lib/getLocale";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import prisma from "@calcom/prisma";
import { ssrInit } from "@server/lib/ssr";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const { req } = context;
const session = await getServerSession({ req });
if (!session?.user?.id) {
return { redirect: { permanent: false, destination: "/auth/login" } };
}
const ssr = await ssrInit(context);
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) {
return { redirect: { permanent: false, destination: "/event-types" } };
}
const locale = await getLocale(context.req);
return {
props: {
...(await serverSideTranslations(locale, ["common"])),
trpcState: ssr.dehydrate(),
hasPendingInvites: user.teams.find((team) => team.accepted === false) ?? false,
},
};
};
@@ -0,0 +1,15 @@
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
// If feature flag is disabled, return not found on getServerSideProps
export const getServerSideProps = async () => {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const flags = await getFeatureFlagMap(prisma);
if (flags.insights === false) {
return {
notFound: true,
} as const;
}
return { props: {} };
};
@@ -0,0 +1,22 @@
import type { GetServerSidePropsContext } from "next";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
export const getServerSideProps = 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: true,
} as const;
}
const querySlug = context.query.slug as string;
return {
props: {
querySlug: querySlug ?? null,
},
};
};
+171
View File
@@ -0,0 +1,171 @@
import type { GetServerSidePropsContext } from "next";
import { z } from "zod";
import { getOrgUsernameFromEmail } from "@calcom/features/auth/signup/utils/getOrgUsernameFromEmail";
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import slugify from "@calcom/lib/slugify";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { IS_GOOGLE_LOGIN_ENABLED } from "@server/lib/constants";
import { ssrInit } from "@server/lib/ssr";
const checkValidEmail = (email: string) => z.string().email().safeParse(email).success;
const querySchema = z.object({
username: z
.string()
.optional()
.transform((val) => val || ""),
email: z.string().email().optional(),
});
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const flags = await getFeatureFlagMap(prisma);
const ssr = await ssrInit(ctx);
const token = z.string().optional().parse(ctx.query.token);
const props = {
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
isSAMLLoginEnabled,
trpcState: ssr.dehydrate(),
prepopulateFormValues: undefined,
};
// username + email prepopulated from query params
const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query);
if ((process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" && !token) || flags["disable-signup"]) {
return {
notFound: true,
} as const;
}
// no token given, treat as a normal signup without verification token
if (!token) {
return {
props: JSON.parse(
JSON.stringify({
...props,
prepopulateFormValues: {
username: preFillusername || null,
email: prefilEmail || null,
},
})
),
};
}
const verificationToken = await prisma.verificationToken.findUnique({
where: {
token,
},
include: {
team: {
select: {
metadata: true,
parentId: true,
parent: {
select: {
slug: true,
metadata: true,
},
},
slug: true,
},
},
},
});
if (!verificationToken || verificationToken.expires < new Date()) {
return {
notFound: true,
} as const;
}
const existingUser = await prisma.user.findFirst({
where: {
AND: [
{
email: verificationToken?.identifier,
},
{
emailVerified: {
not: null,
},
},
],
},
});
if (existingUser) {
return {
redirect: {
permanent: false,
destination: `/auth/login?callbackUrl=${WEBAPP_URL}/${ctx.query.callbackUrl}`,
},
};
}
const guessUsernameFromEmail = (email: string) => {
const [username] = email.split("@");
return username;
};
let username = guessUsernameFromEmail(verificationToken.identifier);
const tokenTeam = {
...verificationToken?.team,
metadata: teamMetadataSchema.parse(verificationToken?.team?.metadata),
};
const isATeamInOrganization = tokenTeam?.parentId !== null;
const isOrganization = tokenTeam.metadata?.isOrganization;
// Detect if the team is an org by either the metadata flag or if it has a parent team
const isOrganizationOrATeamInOrganization = isOrganization || isATeamInOrganization;
// If we are dealing with an org, the slug may come from the team itself or its parent
const orgSlug = isOrganizationOrATeamInOrganization
? tokenTeam.metadata?.requestedSlug || tokenTeam.parent?.slug || tokenTeam.slug
: null;
// Org context shouldn't check if a username is premium
if (!IS_SELF_HOSTED && !isOrganizationOrATeamInOrganization) {
// Im not sure we actually hit this because of next redirects signup to website repo - but just in case this is pretty cool :)
const { available, suggestion } = await checkPremiumUsername(username);
username = available ? username : suggestion || username;
}
const isValidEmail = checkValidEmail(verificationToken.identifier);
const isOrgInviteByLink = isOrganizationOrATeamInOrganization && !isValidEmail;
const parentMetaDataForSubteam = tokenTeam?.parent?.metadata
? teamMetadataSchema.parse(tokenTeam.parent.metadata)
: null;
return {
props: {
...props,
token,
prepopulateFormValues: !isOrgInviteByLink
? {
email: verificationToken.identifier,
username: isOrganizationOrATeamInOrganization
? getOrgUsernameFromEmail(
verificationToken.identifier,
(isOrganization
? tokenTeam.metadata?.orgAutoAcceptEmail
: parentMetaDataForSubteam?.orgAutoAcceptEmail) || ""
)
: slugify(username),
}
: null,
orgSlug,
orgAutoAcceptEmail: isOrgInviteByLink
? tokenTeam?.metadata?.orgAutoAcceptEmail ?? parentMetaDataForSubteam?.orgAutoAcceptEmail ?? null
: null,
},
};
};
@@ -0,0 +1,137 @@
import type { GetServerSidePropsContext } from "next";
import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomains";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { getBookerBaseUrlSync } from "@calcom/lib/getBookerUrl/client";
import logger from "@calcom/lib/logger";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
import slugify from "@calcom/lib/slugify";
import { stripMarkdown } from "@calcom/lib/stripMarkdown";
import prisma from "@calcom/prisma";
import { RedirectType } from "@calcom/prisma/client";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { getTemporaryOrgRedirect } from "@lib/getTemporaryOrgRedirect";
import { ssrInit } from "@server/lib/ssr";
const log = logger.getSubLogger({ prefix: ["team/[slug]"] });
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const slug = Array.isArray(context.query?.slug) ? context.query.slug.pop() : context.query.slug;
const { isValidOrgDomain, currentOrgDomain } = orgDomainConfig(context.req, context.params?.orgSlug);
const isOrgContext = isValidOrgDomain && currentOrgDomain;
// Provided by Rewrite from next.config.js
const isOrgProfile = context.query?.isOrgProfile === "1";
const flags = await getFeatureFlagMap(prisma);
const isOrganizationFeatureEnabled = flags["organizations"];
log.debug("getServerSideProps", {
isOrgProfile,
isOrganizationFeatureEnabled,
isValidOrgDomain,
currentOrgDomain,
});
const team = await getTeamWithMembers({
slug: slugify(slug ?? ""),
orgSlug: currentOrgDomain,
isTeamView: true,
isOrgView: isValidOrgDomain && isOrgProfile,
});
if (!isOrgContext && slug) {
const redirect = await getTemporaryOrgRedirect({
slugs: slug,
redirectType: RedirectType.Team,
eventTypeSlug: null,
currentQuery: context.query,
});
if (redirect) {
return redirect;
}
}
const ssr = await ssrInit(context);
const metadata = teamMetadataSchema.parse(team?.metadata ?? {});
// Taking care of sub-teams and orgs
if (
(!isValidOrgDomain && team?.parent) ||
(!isValidOrgDomain && !!metadata?.isOrganization) ||
!isOrganizationFeatureEnabled
) {
return { notFound: true } as const;
}
if (!team || (team.parent && !team.parent.slug)) {
const unpublishedTeam = await prisma.team.findFirst({
where: {
...(team?.parent
? { id: team.parent.id }
: {
metadata: {
path: ["requestedSlug"],
equals: slug,
},
}),
},
});
if (!unpublishedTeam) return { notFound: true } as const;
return {
props: {
isUnpublished: true,
team: { ...unpublishedTeam, createdAt: null },
trpcState: ssr.dehydrate(),
},
} as const;
}
team.eventTypes =
team.eventTypes?.map((type) => ({
...type,
users: type.users.map((user) => ({
...user,
avatar: `/${user.username}/avatar.png`,
})),
descriptionAsSafeHTML: markdownToSafeHTML(type.description),
})) ?? null;
const safeBio = markdownToSafeHTML(team.bio) || "";
const members = !team.isPrivate
? team.members.map((member) => {
return {
name: member.name,
id: member.id,
bio: member.bio,
subteams: member.subteams,
username: member.username,
accepted: member.accepted,
organizationId: member.organizationId,
safeBio: markdownToSafeHTML(member.bio || ""),
bookerUrl: getBookerBaseUrlSync(member.organization?.slug || ""),
};
})
: [];
const markdownStrippedBio = stripMarkdown(team?.bio || "");
const { inviteToken: _inviteToken, ...serializableTeam } = team;
return {
props: {
team: { ...serializableTeam, safeBio, members, metadata },
themeBasis: serializableTeam.slug,
trpcState: ssr.dehydrate(),
markdownStrippedBio,
isValidOrgDomain,
currentOrgDomain,
},
} as const;
};
+25
View File
@@ -0,0 +1,25 @@
import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { ssrInit } from "@server/lib/ssr";
export const getServerSideProps = async (context: GetServerSidePropsContext) => {
const ssr = await ssrInit(context);
await ssr.viewer.me.prefetch();
const session = await getServerSession({ req: context.req, res: context.res });
const token = Array.isArray(context.query?.token) ? context.query.token[0] : context.query?.token;
const callbackUrl = token ? `/teams?token=${encodeURIComponent(token)}` : null;
if (!session) {
return {
redirect: {
destination: callbackUrl ? `/auth/login?callbackUrl=${callbackUrl}` : "/auth/login",
permanent: false,
},
};
}
return { props: { trpcState: ssr.dehydrate() } };
};
@@ -0,0 +1,105 @@
import MarkdownIt from "markdown-it";
import type { GetServerSidePropsContext } from "next";
import { getServerSession } from "@calcom/features/auth/lib/getServerSession";
import { getCalVideoReference } from "@calcom/features/get-cal-video-reference";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
import { ssrInit } from "@server/lib/ssr";
const md = new MarkdownIt("default", { html: true, breaks: true, linkify: true });
export async function getServerSideProps(context: GetServerSidePropsContext) {
const { req } = context;
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: {
destination: "/video/no-meeting-found",
permanent: false,
},
};
}
//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: {
destination: `/video/meeting-ended/${booking?.uid}`,
permanent: false,
},
};
}
const bookingObj = Object.assign({}, booking, {
startTime: booking.startTime.toString(),
endTime: booking.endTime.toString(),
});
const session = await getServerSession({ req });
// set meetingPassword to null for guests
if (session?.user.id !== bookingObj.user?.id) {
bookingObj.references.forEach((bookRef) => {
bookRef.meetingPassword = null;
});
}
const videoReference = getCalVideoReference(bookingObj.references);
return {
props: {
meetingUrl: videoReference.meetingUrl ?? "",
...(typeof videoReference.meetingPassword === "string" && {
meetingPassword: videoReference.meetingPassword,
}),
booking: {
...bookingObj,
...(bookingObj.description && { description: md.render(bookingObj.description) }),
},
trpcState: ssr.dehydrate(),
},
};
}
@@ -0,0 +1,49 @@
import type { GetServerSidePropsContext } from "next";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
export async function getServerSideProps(context: GetServerSidePropsContext) {
const booking = await prisma.booking.findUnique({
where: {
uid: context.query.uid as string,
},
select: {
...bookingMinimalSelect,
uid: true,
user: {
select: {
credentials: true,
},
},
references: {
select: {
uid: true,
type: true,
meetingUrl: true,
},
},
},
});
if (!booking) {
const redirect = {
redirect: {
destination: "/video/no-meeting-found",
permanent: false,
},
} as const;
return redirect;
}
const bookingObj = Object.assign({}, booking, {
startTime: booking.startTime.toString(),
endTime: booking.endTime.toString(),
});
return {
props: {
booking: bookingObj,
},
};
}
@@ -0,0 +1,35 @@
import type { GetServerSidePropsContext } from "next";
import prisma, { bookingMinimalSelect } from "@calcom/prisma";
// change the type
export async function getServerSideProps(context: GetServerSidePropsContext) {
const booking = await prisma.booking.findUnique({
where: {
uid: context.query.uid as string,
},
select: bookingMinimalSelect,
});
if (!booking) {
const redirect = {
redirect: {
destination: "/video/no-meeting-found",
permanent: false,
},
} as const;
return redirect;
}
const bookingObj = Object.assign({}, booking, {
startTime: booking.startTime.toString(),
endTime: booking.endTime.toString(),
});
return {
props: {
booking: bookingObj,
},
};
}
@@ -0,0 +1,19 @@
import type { GetStaticProps } from "next";
import { z } from "zod";
const querySchema = z.object({
workflow: z.string(),
});
export const getStaticProps: GetStaticProps = (ctx) => {
const params = querySchema.safeParse(ctx.params);
console.log("Built workflow page:", params);
if (!params.success) return { notFound: true };
return {
props: {
workflow: params.data.workflow,
},
revalidate: 10, // seconds
};
};
+7
View File
@@ -1,5 +1,6 @@
import { get } from "@vercel/edge-config";
import { collectEvents } from "next-collect/server";
import { cookies } from "next/headers";
import type { NextRequest } from "next/server";
import { NextResponse } from "next/server";
@@ -81,6 +82,10 @@ const middleware = async (req: NextRequest): Promise<NextResponse<unknown>> => {
}
}
if (url.pathname.startsWith("/future/auth/logout")) {
cookies().delete("next-auth.session-token");
}
requestHeaders.set("x-pathname", url.pathname);
const locale = await getLocale(req);
@@ -112,10 +117,12 @@ export const config = {
"/api/trpc/:path*",
"/login",
"/auth/login",
"/future/auth/login",
/**
* Paths required by routingForms.handle
*/
"/apps/routing_forms/:path*",
"/event-types",
"/future/event-types/",
"/settings/admin/:path*",
+2 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.7.2",
"version": "3.7.3",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -91,7 +91,7 @@
"memory-cache": "^0.2.0",
"micro": "^10.0.1",
"mime-types": "^2.1.35",
"next": "^13.4.6",
"next": "^13.5.4",
"next-auth": "^4.22.1",
"next-axiom": "^0.17.0",
"next-collect": "^0.2.1",
+3 -3
View File
@@ -17,7 +17,7 @@ import { Discord } from "@calcom/ui/components/icon/Discord";
import PageWrapper from "@components/PageWrapper";
import { ssgInit } from "@server/lib/ssg";
import { getTranslations } from "@server/lib/getTranslations";
enum pageType {
ORG = "org",
@@ -273,11 +273,11 @@ export default function Custom404() {
Custom404.PageWrapper = PageWrapper;
export const getStaticProps = async (context: GetStaticPropsContext) => {
const ssr = await ssgInit(context);
const i18n = await getTranslations(context);
return {
props: {
trpcState: ssr.dehydrate(),
i18n,
},
};
};

Some files were not shown because too many files have changed in this diff Show More