Compare commits

..
31 Commits
Author SHA1 Message Date
Omar López 4933295810 v3.9.5
release.yml / release (push) Canceled after 0s
2024-04-04 11:34:47 -07:00
Carina WollendorferandCarinaWolli ed0cd30092 fix: improve api/cron/webhookTriggers endpoint (#14188)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2024-04-04 10:01:57 -07:00
Alex van Andel 1132d9d0dc chore: teams filter improvements (#14315)
* Changed translations, many languages were wrong

* Clear localStorage from the useTypedQuery

* Missed one
2024-04-04 09:59:58 -07:00
9bd3a362a2 feat: added Rating Template to Workflows (#14215)
Co-authored-by: alishaz-polymath <alishahbaz7@gmail.com>
Co-authored-by: Syed Ali Shahbaz <52925846+alishaz-polymath@users.noreply.github.com>
2024-04-04 09:58:52 -07:00
Lauris SkraucisandMorgan 844076badb docs: v2 event types swagger (#14296)
* docs: GET /event-types/:id response

* docs: GET /event-types response

* docs: UPDATE /event-types/:id response

* docs: DELETE /event-types/:id response

* docs: GET /event-types/:username/public response

* docs: prod docs show only event-types, bookings cancel and reschedule, managed users

* fix merge main

* Revert "docs: prod docs show only event-types, bookings cancel and reschedule, managed users"

This reverts commit 96cdc4b4840e3841c6ed27bd3a030d0574aad19e.

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2024-04-04 16:20:03 +00:00
Lauris Skraucis c0523a31a7 Revert "refactor: OAuth creation form hide apps permissions (#14264)" (#14336)
This reverts commit fe62f9c6de.
2024-04-04 15:24:05 +00:00
Morgan 1fff642860 fix: urls, gcal check, oauth client controller (#14335)
* fix: urls, gcal check, oauth client controller

* fixup! fix: urls, gcal check, oauth client controller
2024-04-04 14:58:15 +00:00
89fe383c9f feat: implement turnstile on signup (#13913)
* wip

* feat: turnstile + removal of trpcState

* fix: use react-turnstile

* fix: i18n hydration errors

* wip: server side validation for cf token

* fix: hide in e2e tests

* feat:throw on error of token

* fix: set correct form values onVerify

* fix: use getIp for remoteIp and use correct header for token input

* chore: update .env.example

* fix: Update .env.example

* Update apps/web/pages/signup.tsx

* chore: fix typo

* fix: typo

* feat: hide until error

* feat: appearance interaction only

* remove cloudflare from e2e

* fix: legacyBehavior

* fix links + wait for load

* fix: signup tests

* test: wait for load state

---------

Co-authored-by: Keith Williams <keithwillcode@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-04 10:21:06 -03:00
sean-brydon 4a173c48b8 fix: correct limit amount in api middleware (#14333) 2024-04-04 12:20:26 +00:00
Morgan a72a3e45e9 fix: refresh access token expiry 60m (#14331)
* fix: refresh access token expiry 60m

* fix: exception filters logging request

* fix: never auto accept domain for managed users
2024-04-04 09:07:43 -03:00
a8d8c992a4 chore: added email and company form to cal.ai form (#14306)
* added email and company form

* i18n

* feat: add guest email and company

* chore: update and format prompt

* chore: hint var

* chore: fix err message

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Udit Takkar <udit222001@gmail.com>
2024-04-04 11:57:43 +00:00
Hariom Balhara 99f9a121cb fix: Show proper error when API_V2_URL variable is missing (#14269) 2024-04-04 11:51:39 +00:00
Crowdin Bot 6a0e2392fe New Crowdin translations by Github Action 2024-04-04 11:49:00 +00:00
Joe Au-Yeung addec46feb chore: Remove payment keys from appKeysSchema (#14298)
* Remove payment keys from appKeysSchema

* Update .env.example
2024-04-04 11:44:16 +00:00
Crowdin Bot a4fe9a51b5 New Crowdin translations by Github Action 2024-04-04 11:41:42 +00:00
Richard Poelderl 78b8bbb5aa update docs for yarn.lock management (#14230) 2024-04-04 08:37:15 -03:00
Lauris Skraucis d7ece4f583 Revert "chore: downgrade react query atoms (#14328)" (#14332)
This reverts commit 8fc9b9ae9d.
2024-04-04 10:55:15 +00:00
Lauris Skraucis 8fc9b9ae9d chore: downgrade react query atoms (#14328)
* refactor: downgrade atoms react-query to v4

* driveby: prettier format file

* refactor: react-query isPending to isLoading

* chore: bump atoms version

* refactor: isPending -> isLoading

* chore: bump version
2024-04-04 11:28:35 +01:00
Udit Takkar d1175bf7de fix: pass begin_message for updating LLM (#14327) 2024-04-04 11:14:19 +01:00
Crowdin Bot 3900453482 New Crowdin translations by Github Action 2024-04-04 09:46:04 +00:00
Morganandsupalarry 68e7e76b31 feat: list managed users of an oauth client (#14330)
* feat: list managed users with apiv2 api

* refactor: isPending -> isLoading

* Revert "refactor: isPending -> isLoading"

This reverts commit dc2108e05508070185d57633346f3c532458e2a8.

---------

Co-authored-by: supalarry <laurisskraucis@gmail.com>
2024-04-04 09:41:47 +00:00
zomars 986d196a37 hotfix: lazy icon dev performance 2024-04-03 22:14:30 -07:00
d30fb213d2 fix: Add adminReview option to allow impersonating org members (#14275)
* added layouts for moving teams step

* added admin section for org creation step

* extended org creation to also move existing teams

* wip

* wip

* wip

* further changes

* Add checkout for org in onboarding

* Fix ts errors

* Self review feedback

* Self review addressed

* Fix unit tests

* Fix ts error

* Seans feedback addressed

* feat: fix correct accounts pending

* fix: unit tests for new invite member permissions

* tests: org admin onboarding tests for existing user

* tests: Inital user self serve flow

* chore: update teamAndUserFixture to create X amount of teams

* chore: add testId to card actionButton

* test: add test-Id to continue or checkout button

* tests: add tests for migrating existing teams

* feat: match new designs

* fix: isAdminCheck

* chore: fix pricing copy

* fix: flacky tests

* Fix tests?

* More test fixes

* Check all checkboxes

* Fix type error

* Fix failing test and typescript issues

* Fix unpaid org allowing auto-add users

* Add self-serve flag

* Skip tests

* Add adminReview option

* Fixes

* Add server checks for impersonation

* Improved error

* feat: extract - and disalow updating from org uset list if not reviewed

---------

Co-authored-by: Peer Richelsen <peer@cal.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2024-04-04 04:48:41 +00:00
3f02de8214 chore: icon cleanup (#13770)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2024-04-03 23:23:57 +00:00
Omar López 7421cea0da fixes: API builds after rename (#14319) 2024-04-04 00:12:09 +01:00
Lauris Skraucis 507daa6a0c chore: publish atoms on npm and bump version (#14309) 2024-04-03 18:37:00 +03:00
Crowdin Bot 61f68af435 New Crowdin translations by Github Action 2024-04-03 13:32:23 +00:00
b3f4cfab32 feat: new org onboarding wizard (#13139)
* added layouts for moving teams step

* added admin section for org creation step

* extended org creation to also move existing teams

* wip

* wip

* wip

* further changes

* Add checkout for org in onboarding

* Fix ts errors

* Self review feedback

* Self review addressed

* Fix unit tests

* Fix ts error

* Seans feedback addressed

* feat: fix correct accounts pending

* fix: unit tests for new invite member permissions

* tests: org admin onboarding tests for existing user

* tests: Inital user self serve flow

* chore: update teamAndUserFixture to create X amount of teams

* chore: add testId to card actionButton

* test: add test-Id to continue or checkout button

* tests: add tests for migrating existing teams

* feat: match new designs

* fix: isAdminCheck

* chore: fix pricing copy

* fix: flacky tests

* Fix tests?

* More test fixes

* Check all checkboxes

* Fix type error

* Fix failing test and typescript issues

* Fix unpaid org allowing auto-add users

* Add self-serve flag

* Skip tests

---------

Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Hariom <hariombalhara@gmail.com>
Co-authored-by: sean-brydon <sean@cal.com>
Co-authored-by: Joe Au-Yeung <j.auyeung419@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2024-04-03 18:53:02 +05:30
Morgan c3b1de498d fix: org guard did not have user org profile + missing db read in selectedCalendarRepo (#14303)
* fix: user did not contain profile with next-auth-guard

* fix: missing dbread instance
2024-04-03 10:49:19 +03:00
Erik cdf059361e chore: Tidy up unused variables, etc (#14289) 2024-04-02 22:06:42 +00:00
Alex van AndelandUdit Takkar 2aada9a7da chore: Move avatars using cursor (#14257)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2024-04-02 12:59:39 -07:00
464 changed files with 7126 additions and 3272 deletions
+5 -2
View File
@@ -169,8 +169,7 @@ STRIPE_WEBHOOK_SECRET=
STRIPE_WEBHOOK_SECRET_APPS=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
PAYMENT_FEE_PERCENTAGE=
# Use for internal Public API Keys and optional
API_KEY_PREFIX=cal_
@@ -213,6 +212,10 @@ E2E_TEST_MAILHOG_ENABLED=
# **********************************************************************************************************
# Cloudflare Turnstile
NEXT_PUBLIC_CLOUDFLARE_SITEKEY=
CLOUDFLARE_TURNSTILE_SECRET=
# Set the following value to true if you wish to enable Team Impersonation
NEXT_PUBLIC_TEAM_IMPERSONATION=false
+1
View File
@@ -4,3 +4,4 @@ node_modules
**/**/public
packages/prisma/zod
apps/web/public/embed
packages/ui/components/icon/dynamicIconImports.tsx
+1
View File
@@ -17,3 +17,4 @@ packages/prisma/zod
packages/prisma/enums
apps/web/public/embed
apps/api/v2/swagger/documentation.json
packages/ui/components/icon/dynamicIconImports.tsx
+13 -2
View File
@@ -13,8 +13,6 @@ Contributions are what makes the open source community such an amazing place to
- Issues from non-core members automatically receive the `🚨 needs approval` label.
- We greatly value new feature ideas. To ensure consistency in the product's direction, they undergo review and approval.
## Priorities
<table>
@@ -178,11 +176,15 @@ If you get errors, be sure to fix them before committing.
Do not commit your `yarn.lock` unless you've made changes to the `package.json`. If you've already committed `yarn.lock` unintentionally, follow these steps to undo:
If your last commit has the `yarn.lock` file alongside other files and you only wish to uncommit the `yarn.lock`:
```bash
git checkout HEAD~1 yarn.lock
git commit -m "Revert yarn.lock changes"
```
_NB_: You may have to bypass the pre-commit hook with by appending `--no-verify` to the git commit
If you've pushed the commit with the `yarn.lock`:
1. Correct the commit locally using the above method.
2. Carefully force push:
@@ -194,26 +196,35 @@ If `yarn.lock` was committed a while ago and there have been several commits sin
1. **Checkout a Previous Version**:
- Find the commit hash before the `yarn.lock` was unintentionally committed. You can do this by viewing the Git log:
```bash
git log yarn.lock
```
- Once you have identified the commit hash, use it to checkout the previous version of `yarn.lock`:
```bash
git checkout <commit_hash> yarn.lock
```
2. **Commit the Reverted Version**:
- After checking out the previous version of the `yarn.lock`, commit this change:
```bash
git commit -m "Revert yarn.lock to its state before unintended changes"
```
3. **Proceed with Caution**:
- If you need to push this change, first pull the latest changes from your remote branch to ensure you're not overwriting other recent changes:
```bash
git pull origin <your-branch-name>
```
- Then push the updated branch:
```bash
git push origin <your-branch-name>
```
Lastly, make sure to keep the branches updated (e.g. click the `Update branch` button on GitHub PR).
+1 -2
View File
@@ -1,7 +1,6 @@
import type { NextMiddleware } from "next-api-middleware";
import { checkRateLimitAndThrowError } from "@calcom/lib/checkRateLimitAndThrowError";
import { API_KEY_RATE_LIMIT } from "@calcom/lib/rateLimit";
export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
if (!req.query.apiKey) return res.status(401).json({ message: "No apiKey provided" });
@@ -11,7 +10,7 @@ export const rateLimitApiKey: NextMiddleware = async (req, res, next) => {
identifier: req.query.apiKey as string,
rateLimitingType: "api",
onRateLimiterResponse: (response) => {
res.setHeader("X-RateLimit-Limit", API_KEY_RATE_LIMIT);
res.setHeader("X-RateLimit-Limit", response.limit);
res.setHeader("X-RateLimit-Remaining", response.remaining);
res.setHeader("X-RateLimit-Reset", response.reset);
},
@@ -1,7 +1,7 @@
import type { Prisma } from "@prisma/client";
import type { NextApiRequest } from "next";
import { purchaseTeamSubscription } from "@calcom/features/ee/teams/lib/payments";
import { purchaseTeamOrOrgSubscription } from "@calcom/features/ee/teams/lib/payments";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
@@ -105,10 +105,11 @@ export async function patchHandler(req: NextApiRequest) {
};
delete data.slug;
if (IS_TEAM_BILLING_ENABLED) {
const checkoutSession = await purchaseTeamSubscription({
const checkoutSession = await purchaseTeamOrOrgSubscription({
teamId: _team.id,
seats: _team.members.length,
userId,
pricePerSeat: null,
});
if (!checkoutSession.url)
throw new TRPCError({
+3 -1
View File
@@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";
import { getStripeCustomerIdFromUserId } from "@calcom/app-store/stripepayment/lib/customer";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { IS_PRODUCTION } from "@calcom/lib/constants";
import { IS_TEAM_BILLING_ENABLED, WEBAPP_URL } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
@@ -197,8 +198,9 @@ const generateTeamCheckoutSession = async ({
customer_update: {
address: "auto",
},
// Disabled when testing locally as usually developer doesn't setup Tax in Stripe Test mode
automatic_tax: {
enabled: true,
enabled: IS_PRODUCTION,
},
metadata: {
pendingPaymentTeamId,
+1 -1
View File
@@ -8,7 +8,7 @@ export class AppLoggerMiddleware implements NestMiddleware {
private logger = new Logger("HTTP");
use(request: Request, response: Response, next: NextFunction): void {
const { ip, method, protocol, originalUrl, path: url } = request;
const { ip, method, originalUrl } = request;
const userAgent = request.get("user-agent") || "";
response.on("close", () => {
@@ -1,5 +1,5 @@
import { CreateBookingInput } from "@/ee/bookings/inputs/create-booking.input";
import { IsBoolean, IsNumber, IsString, IsOptional, IsArray } from "class-validator";
import { IsBoolean, IsNumber, IsOptional } from "class-validator";
import type { AppsStatus } from "@calcom/platform-libraries";
@@ -2,7 +2,7 @@ import { CalendarsService } from "@/ee/calendars/services/calendars.service";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { UserWithProfile } from "@/modules/users/users.repository";
import { Controller, Get, Logger, UseGuards, Query } from "@nestjs/common";
import { Controller, Get, UseGuards, Query } from "@nestjs/common";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ConnectedDestinationCalendars } from "@calcom/platform-libraries";
@@ -16,8 +16,6 @@ import { EventBusyDate } from "@calcom/types/Calendar";
})
@UseGuards(AccessTokenGuard)
export class CalendarsController {
private readonly logger = new Logger("ee overlay calendars controller");
constructor(private readonly calendarsService: CalendarsService) {}
@Get("/busy-times")
@@ -1,8 +1,12 @@
import { CreateEventTypeInput } from "@/ee/event-types/inputs/create-event-type.input";
import { UpdateEventTypeInput } from "@/ee/event-types/inputs/update-event-type.input";
import { CreateEventTypeOutput } from "@/ee/event-types/outputs/create-event-type.output";
import { DeleteEventTypeOutput } from "@/ee/event-types/outputs/delete-event-type.output";
import { GetEventTypeOutput } from "@/ee/event-types/outputs/get-event-type.output";
import { GetEventTypesPublicOutput } from "@/ee/event-types/outputs/get-event-types-public.output";
import { GetEventTypesOutput } from "@/ee/event-types/outputs/get-event-types.output";
import { UpdateEventTypeOutput } from "@/ee/event-types/outputs/update-event-type.output";
import { EventTypesService } from "@/ee/event-types/services/event-types.service";
import { ForAtom } from "@/lib/atoms/decorators/for-atom.decorator";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
@@ -22,17 +26,9 @@ import {
Delete,
} from "@nestjs/common";
import { ApiTags as DocsTags } from "@nestjs/swagger";
import { EventType } from "@prisma/client";
import { EventTypesByViewer } from "@calcom/lib";
import { EVENT_TYPE_READ, EVENT_TYPE_WRITE, SUCCESS_STATUS } from "@calcom/platform-constants";
import type {
EventType as AtomEventType,
EventTypesPublic,
UpdateEventTypeReturn,
} from "@calcom/platform-libraries";
import { getEventTypesByViewer } from "@calcom/platform-libraries";
import { ApiResponse, ApiSuccessResponse } from "@calcom/platform-types";
@Controller({
path: "event-types",
@@ -63,12 +59,9 @@ export class EventTypesController {
@UseGuards(AccessTokenGuard)
async getEventType(
@Param("eventTypeId") eventTypeId: string,
@ForAtom() forAtom: boolean,
@GetUser() user: UserWithProfile
): Promise<ApiSuccessResponse<EventType | AtomEventType>> {
const eventType = forAtom
? await this.eventTypesService.getUserEventTypeForAtom(user, Number(eventTypeId))
: await this.eventTypesService.getUserEventType(user.id, Number(eventTypeId));
): Promise<GetEventTypeOutput> {
const eventType = await this.eventTypesService.getUserEventTypeForAtom(user, Number(eventTypeId));
if (!eventType) {
throw new NotFoundException(`Event type with id ${eventTypeId} not found`);
@@ -83,7 +76,7 @@ export class EventTypesController {
@Get("/")
@Permissions([EVENT_TYPE_READ])
@UseGuards(AccessTokenGuard)
async getEventTypes(@GetUser() user: UserWithProfile): Promise<ApiSuccessResponse<EventTypesByViewer>> {
async getEventTypes(@GetUser() user: UserWithProfile): Promise<GetEventTypesOutput> {
const eventTypes = await getEventTypesByViewer({
id: user.id,
profile: {
@@ -99,9 +92,7 @@ export class EventTypesController {
@Get("/:username/public")
@Permissions([EVENT_TYPE_READ])
async getPublicEventTypes(
@Param("username") username: string
): Promise<ApiSuccessResponse<EventTypesPublic>> {
async getPublicEventTypes(@Param("username") username: string): Promise<GetEventTypesPublicOutput> {
const eventTypes = await this.eventTypesService.getEventTypesPublicByUsername(username);
return {
@@ -118,7 +109,7 @@ export class EventTypesController {
@Param("eventTypeId") eventTypeId: number,
@Body() body: UpdateEventTypeInput,
@GetUser() user: UserWithProfile
): Promise<ApiResponse<UpdateEventTypeReturn["eventType"]>> {
): Promise<UpdateEventTypeOutput> {
const eventType = await this.eventTypesService.updateEventType(eventTypeId, body, user);
return {
@@ -133,12 +124,17 @@ export class EventTypesController {
async deleteEventType(
@Param("eventTypeId") eventTypeId: number,
@GetUser("id") userId: number
): Promise<ApiResponse<EventType>> {
): Promise<DeleteEventTypeOutput> {
const eventType = await this.eventTypesService.deleteEventType(eventTypeId, userId);
return {
status: SUCCESS_STATUS,
data: eventType,
data: {
id: eventType.id,
length: eventType.length,
slug: eventType.slug,
title: eventType.title,
},
};
}
}
@@ -0,0 +1,38 @@
import {
CREATE_EVENT_LENGTH_EXAMPLE,
CREATE_EVENT_SLUG_EXAMPLE,
CREATE_EVENT_TITLE_EXAMPLE,
} from "@/ee/event-types/inputs/create-event-type.input";
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsInt, IsString } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
class DeleteData {
@IsInt()
@DocsProperty({ example: 1 })
id!: number;
@IsInt()
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
length!: number;
@IsString()
@DocsProperty({ example: CREATE_EVENT_SLUG_EXAMPLE })
slug!: string;
@IsString()
@DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE })
title!: string;
}
export class DeleteEventTypeOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@Type(() => DeleteData)
data!: DeleteData;
}
@@ -0,0 +1,28 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
class Data {
@ApiProperty({
type: EventTypeOutput,
})
@ValidateNested()
@Type(() => EventTypeOutput)
eventType!: EventTypeOutput;
}
export class GetEventTypeOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: Data,
})
@ValidateNested()
@Type(() => Data)
data!: Data;
}
@@ -0,0 +1,43 @@
import {
CREATE_EVENT_LENGTH_EXAMPLE,
CREATE_EVENT_SLUG_EXAMPLE,
CREATE_EVENT_TITLE_EXAMPLE,
} from "@/ee/event-types/inputs/create-event-type.input";
import { ApiProperty } from "@nestjs/swagger";
import { ApiProperty as DocsProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsArray, IsEnum, IsInt, IsString, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
class PublicEventType {
@IsInt()
@DocsProperty({ example: 1 })
id!: number;
@IsInt()
@DocsProperty({ example: CREATE_EVENT_LENGTH_EXAMPLE })
length!: number;
@IsString()
@DocsProperty({ example: CREATE_EVENT_SLUG_EXAMPLE })
slug!: string;
@IsString()
@DocsProperty({ example: CREATE_EVENT_TITLE_EXAMPLE })
title!: string;
@IsString()
description?: string | null;
}
export class GetEventTypesPublicOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ValidateNested({ each: true })
@Type(() => PublicEventType)
@IsArray()
data!: PublicEventType[];
}
@@ -0,0 +1,31 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsArray, IsEnum, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
class EventTypeGroup {
@ValidateNested({ each: true })
@Type(() => EventTypeOutput)
@IsArray()
eventTypes!: EventTypeOutput[];
}
class GetEventTypesData {
@ValidateNested({ each: true })
@Type(() => EventTypeGroup)
@IsArray()
eventTypeGroups!: EventTypeGroup[];
}
export class GetEventTypesOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ValidateNested({ each: true })
@Type(() => GetEventTypesData)
@IsArray()
data!: GetEventTypesData;
}
@@ -0,0 +1,20 @@
import { EventTypeOutput } from "@/ee/event-types/outputs/event-type.output";
import { ApiProperty } from "@nestjs/swagger";
import { Type } from "class-transformer";
import { IsEnum, IsNotEmptyObject, ValidateNested } from "class-validator";
import { SUCCESS_STATUS, ERROR_STATUS } from "@calcom/platform-constants";
export class UpdateEventTypeOutput {
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
@ApiProperty({
type: EventTypeOutput,
})
@IsNotEmptyObject()
@ValidateNested()
@Type(() => EventTypeOutput)
data!: EventTypeOutput;
}
@@ -101,11 +101,7 @@ export class EventTypesService {
}
async createUserDefaultEventTypes(userId: number) {
const thirtyMinutes = DEFAULT_EVENT_TYPES.thirtyMinutes;
const thirtyMinutesVideo = DEFAULT_EVENT_TYPES.thirtyMinutesVideo;
const sixtyMinutes = DEFAULT_EVENT_TYPES.sixtyMinutes;
const sixtyMinutesVideo = DEFAULT_EVENT_TYPES.sixtyMinutesVideo;
const { sixtyMinutes, sixtyMinutesVideo, thirtyMinutes, thirtyMinutesVideo } = DEFAULT_EVENT_TYPES;
const defaultEventTypes = await Promise.all([
this.eventTypesRepository.createUserEventType(userId, thirtyMinutes),
@@ -120,7 +116,7 @@ export class EventTypesService {
async updateEventType(eventTypeId: number, body: UpdateEventTypeInput, user: UserWithProfile) {
this.checkCanUpdateEventType(user.id, eventTypeId);
const eventTypeUser = await this.getUserToUpdateEvent(user);
const { eventType } = await updateEventType({
await updateEventType({
input: { id: eventTypeId, ...body },
ctx: {
user: eventTypeUser,
@@ -130,6 +126,8 @@ export class EventTypesService {
},
});
const { eventType } = await this.getUserEventTypeForAtom(user, eventTypeId);
return eventType;
}
@@ -32,6 +32,7 @@ describe("Platform Gcal Endpoints", () => {
let gcalCredentials: Credential;
let accessTokenSecret: string;
let refreshTokenSecret: string;
beforeAll(async () => {
const moduleRef = await Test.createTestingModule({
providers: [PrismaExceptionFilter, HttpExceptionFilter],
+2 -4
View File
@@ -1,5 +1,4 @@
import { AppsRepository } from "@/modules/apps/apps.repository";
import { GcalService } from "@/modules/apps/services/gcal.service";
import { GCalService } from "@/modules/apps/services/gcal.service";
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { Permissions } from "@/modules/auth/decorators/permissions/permissions.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
@@ -47,12 +46,11 @@ export class GcalController {
private readonly logger = new Logger("Platform Gcal Provider");
constructor(
private readonly appRepository: AppsRepository,
private readonly credentialRepository: CredentialsRepository,
private readonly tokensRepository: TokensRepository,
private readonly selectedCalendarsRepository: SelectedCalendarsRepository,
private readonly config: ConfigService,
private readonly gcalService: GcalService
private readonly gcalService: GCalService
) {}
private redirectUri = `${this.config.get("api.url")}/ee/gcal/oauth/save`;
+2 -2
View File
@@ -1,6 +1,6 @@
import { GcalController } from "@/ee/gcal/gcal.controller";
import { AppsRepository } from "@/modules/apps/apps.repository";
import { GcalService } from "@/modules/apps/services/gcal.service";
import { GCalService } from "@/modules/apps/services/gcal.service";
import { CredentialsRepository } from "@/modules/credentials/credentials.repository";
import { OAuthClientModule } from "@/modules/oauth-clients/oauth-client.module";
import { PrismaModule } from "@/modules/prisma/prisma.module";
@@ -11,7 +11,7 @@ import { ConfigService } from "@nestjs/config";
@Module({
imports: [PrismaModule, TokensModule, OAuthClientModule],
providers: [AppsRepository, ConfigService, CredentialsRepository, SelectedCalendarsRepository, GcalService],
providers: [AppsRepository, ConfigService, CredentialsRepository, SelectedCalendarsRepository, GCalService],
controllers: [GcalController],
})
export class GcalModule {}
@@ -1,7 +1,6 @@
import { GetUser } from "@/modules/auth/decorators/get-user/get-user.decorator";
import { AccessTokenGuard } from "@/modules/auth/guards/access-token/access-token.guard";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
BadRequestException,
@@ -9,7 +8,6 @@ import {
Get,
HttpCode,
HttpStatus,
Logger,
NotFoundException,
Param,
UnauthorizedException,
@@ -24,12 +22,7 @@ import { ApiResponse } from "@calcom/platform-types";
version: "2",
})
export class CalProviderController {
private readonly logger = new Logger("Platform Provider Controller");
constructor(
private readonly tokensRepository: TokensRepository,
private readonly oauthClientRepository: OAuthClientRepository
) {}
constructor(private readonly oauthClientRepository: OAuthClientRepository) {}
@Get("/:clientId")
@HttpCode(HttpStatus.OK)
@@ -1,4 +1,5 @@
import { ArgumentsHost, Catch, ExceptionFilter, HttpException, Logger } from "@nestjs/common";
import { Request } from "express";
import { ERROR_STATUS } from "@calcom/platform-constants";
import { Response } from "@calcom/platform-types";
@@ -10,11 +11,14 @@ export class HttpExceptionFilter implements ExceptionFilter<HttpException> {
catch(exception: HttpException, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const request = ctx.getRequest<Request>();
const statusCode = exception.getStatus();
this.logger.error(`Http Exception Filter: ${exception?.message}`, {
exception,
request,
body: request.body,
headers: request.headers,
url: request.url,
method: request.method,
});
response.status(statusCode).json({
status: ERROR_STATUS,
@@ -7,6 +7,7 @@ import {
PrismaClientUnknownRequestError,
PrismaClientValidationError,
} from "@prisma/client/runtime/library";
import { Request } from "express";
import { ERROR_STATUS, INTERNAL_SERVER_ERROR } from "@calcom/platform-constants";
import { Response } from "@calcom/platform-types";
@@ -31,10 +32,13 @@ export class PrismaExceptionFilter implements ExceptionFilter {
catch(error: PrismaError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const request = ctx.getRequest<Request>();
this.logger.error(`PrismaError: ${error.message}`, {
error,
request,
body: request.body,
headers: request.headers,
url: request.url,
method: request.method,
});
response.status(HttpStatus.INTERNAL_SERVER_ERROR).json({
status: ERROR_STATUS,
@@ -1,6 +1,7 @@
import { ArgumentsHost, Catch, Logger, HttpStatus } from "@nestjs/common";
import { BaseExceptionFilter } from "@nestjs/core";
import * as Sentry from "@sentry/node";
import { Request } from "express";
import { ERROR_STATUS, INTERNAL_SERVER_ERROR } from "@calcom/platform-constants";
import { Response } from "@calcom/platform-types";
@@ -12,11 +13,14 @@ export class SentryFilter extends BaseExceptionFilter {
handleUnknownError(exception: any, host: ArgumentsHost): void {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const request = ctx.getRequest<Request>();
this.logger.error(`Sentry Exception Filter: ${exception?.message}`, {
exception,
request,
body: request.body,
headers: request.headers,
url: request.url,
method: request.method,
});
// capture if client has been init
@@ -1,4 +1,5 @@
import { ArgumentsHost, Catch, ExceptionFilter, Logger } from "@nestjs/common";
import { Request } from "express";
import { ERROR_STATUS } from "@calcom/platform-constants";
import { TRPCError } from "@calcom/platform-libraries";
@@ -11,7 +12,8 @@ export class TRPCExceptionFilter implements ExceptionFilter {
catch(exception: TRPCError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const request = ctx.getRequest<Request>();
let statusCode = 500;
switch (exception.code) {
case "UNAUTHORIZED":
@@ -38,10 +40,15 @@ export class TRPCExceptionFilter implements ExceptionFilter {
statusCode = 500;
break;
}
this.logger.error(`TRPC Exception Filter: ${exception?.message}`, {
exception,
request,
body: request.body,
headers: request.headers,
url: request.url,
method: request.method,
});
response.status(statusCode).json({
status: ERROR_STATUS,
timestamp: new Date().toISOString(),
@@ -1,5 +1,6 @@
import type { ArgumentsHost, ExceptionFilter } from "@nestjs/common";
import { Catch, HttpStatus, Logger } from "@nestjs/common";
import { Request } from "express";
import { ZodError } from "zod";
import { BAD_REQUEST, ERROR_STATUS } from "@calcom/platform-constants";
@@ -12,11 +13,16 @@ export class ZodExceptionFilter implements ExceptionFilter {
catch(error: ZodError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
const request = ctx.getRequest<Request>();
this.logger.error(`ZodError: ${error.message}`, {
error,
request,
body: request.body,
headers: request.headers,
url: request.url,
method: request.method,
});
response.status(HttpStatus.BAD_REQUEST).json({
status: ERROR_STATUS,
timestamp: new Date().toISOString(),
@@ -1,6 +1,6 @@
import { createParamDecorator, ExecutionContext } from "@nestjs/common";
export const ForAtom = createParamDecorator((data: unknown, ctx: ExecutionContext) => {
export const ForAtom = createParamDecorator((_data: unknown, ctx: ExecutionContext) => {
const request = ctx.switchToHttp().getRequest();
return request.query.for === "atom";
});
@@ -1,4 +1,11 @@
import { UserWithProfile } from "@/modules/users/users.repository";
export class BaseStrategy {
success!: (user: unknown) => void;
error!: (error: Error) => void;
}
export class NextAuthPassportStrategy {
success!: (user: UserWithProfile) => void;
error!: (error: Error) => void;
}
@@ -4,9 +4,11 @@ import { google } from "googleapis";
import { z } from "zod";
@Injectable()
export class GcalService {
export class GCalService {
private logger = new Logger("GcalService");
private gcalResponseSchema = z.object({ client_id: z.string(), client_secret: z.string() });
constructor(private readonly appsRepository: AppsRepository) {}
async getOAuthClient(redirectUri: string) {
@@ -17,9 +19,7 @@ export class GcalService {
throw new NotFoundException();
}
const { client_id, client_secret } = z
.object({ client_id: z.string(), client_secret: z.string() })
.parse(app.keys);
const { client_id, client_secret } = this.gcalResponseSchema.parse(app.keys);
const oAuth2Client = new google.auth.OAuth2(client_id, client_secret, redirectUri);
return oAuth2Client;
@@ -38,7 +38,7 @@ export class OrganizationRolesGuard implements CanActivate {
async isPlatform(organizationId: number) {
const isPlatform = await this.organizationsService.isPlatform(organizationId);
if (!isPlatform) {
throw new ForbiddenException("Organization is not a platform.");
throw new ForbiddenException("Organization is not a platform (SHP).");
}
}
@@ -1,4 +1,4 @@
import { BaseStrategy } from "@/lib/passport/strategies/types";
import { NextAuthPassportStrategy } from "@/lib/passport/strategies/types";
import { UsersRepository } from "@/modules/users/users.repository";
import { Injectable, UnauthorizedException } from "@nestjs/common";
import { ConfigService } from "@nestjs/config";
@@ -7,7 +7,7 @@ import type { Request } from "express";
import { getToken } from "next-auth/jwt";
@Injectable()
export class NextAuthStrategy extends PassportStrategy(BaseStrategy, "next-auth") {
export class NextAuthStrategy extends PassportStrategy(NextAuthPassportStrategy, "next-auth") {
constructor(private readonly userRepository: UsersRepository, private readonly config: ConfigService) {
super();
}
@@ -25,7 +25,7 @@ export class NextAuthStrategy extends PassportStrategy(BaseStrategy, "next-auth"
throw new UnauthorizedException("Email not found in the authentication token.");
}
const user = await this.userRepository.findByEmail(payload.email);
const user = await this.userRepository.findByEmailWithProfile(payload.email);
if (!user) {
throw new UnauthorizedException("User associated with the authentication token email not found.");
}
@@ -179,6 +179,21 @@ describe("OAuth Client Users Endpoints", () => {
).toBeTruthy();
}
it(`/GET: return list of managed users`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/oauth-clients/${oAuthClient.id}/users?limit=10&offset=0`)
.set("x-cal-secret-key", oAuthClient.secret)
.set("Origin", `${CLIENT_REDIRECT_URI}`)
.expect(200);
const responseBody: ApiSuccessResponse<UserReturned[]> = response.body;
expect(responseBody.status).toEqual(SUCCESS_STATUS);
expect(responseBody.data).toBeDefined();
expect(responseBody.data?.length).toBeGreaterThan(0);
expect(responseBody.data[0].email).toEqual(postResponseData.user.email);
});
it(`/GET/:id`, async () => {
const response = await request(app.getHttpServer())
.get(`/api/v2/oauth-clients/${oAuthClient.id}/users/${postResponseData.user.id}`)
@@ -20,11 +20,12 @@ import {
Patch,
BadRequestException,
Delete,
Query,
} from "@nestjs/common";
import { User } from "@prisma/client";
import { SUCCESS_STATUS } from "@calcom/platform-constants";
import { ApiResponse } from "@calcom/platform-types";
import { ApiResponse, Pagination } from "@calcom/platform-types";
@Controller({
path: "oauth-clients/:clientId/users",
@@ -39,6 +40,27 @@ export class OAuthClientUsersController {
private readonly oauthRepository: OAuthClientRepository
) {}
@Get("/")
@UseGuards(OAuthClientCredentialsGuard)
async getManagedUsers(
@Param("clientId") oAuthClientId: string,
@Query() queryParams: Pagination
): Promise<ApiResponse<User[]>> {
this.logger.log(`getting managed users with data for OAuth Client with ID ${oAuthClientId}`);
const { offset, limit } = queryParams;
const existingUsers = await this.userRepository.findManagedUsersByOAuthClientId(
oAuthClientId,
offset ?? 0,
limit ?? 50
);
return {
status: SUCCESS_STATUS,
data: existingUsers,
};
}
@Post("/")
@UseGuards(OAuthClientCredentialsGuard)
async createUser(
@@ -8,6 +8,7 @@ import { GetOAuthClientResponseDto } from "@/modules/oauth-clients/controllers/o
import { GetOAuthClientsResponseDto } from "@/modules/oauth-clients/controllers/oauth-clients/responses/GetOAuthClientsResponse.dto";
import { UpdateOAuthClientInput } from "@/modules/oauth-clients/inputs/update-oauth-client.input";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { UserWithProfile } from "@/modules/users/users.repository";
import {
Body,
Controller,
@@ -57,9 +58,10 @@ export class OAuthClientsController {
type: CreateOAuthClientResponseDto,
})
async createOAuthClient(
@GetUser("organizationId") organizationId: number,
@GetUser() user: UserWithProfile,
@Body() body: CreateOAuthClientInput
): Promise<CreateOAuthClientResponseDto> {
const organizationId = (user.movedToProfile?.organizationId ?? user.organizationId) as number;
this.logger.log(
`For organisation ${organizationId} creating OAuth Client with data: ${JSON.stringify(body)}`
);
@@ -77,9 +79,9 @@ export class OAuthClientsController {
@HttpCode(HttpStatus.OK)
@Roles([MembershipRole.ADMIN, MembershipRole.OWNER, MembershipRole.MEMBER])
@DocsOperation({ description: AUTH_DOCUMENTATION })
async getOAuthClients(
@GetUser("organizationId") organizationId: number
): Promise<GetOAuthClientsResponseDto> {
async getOAuthClients(@GetUser() user: UserWithProfile): Promise<GetOAuthClientsResponseDto> {
const organizationId = (user.movedToProfile?.organizationId ?? user.organizationId) as number;
const clients = await this.oauthClientRepository.getOrganizationOAuthClients(organizationId);
return { status: SUCCESS_STATUS, data: clients };
}
@@ -27,7 +27,6 @@ export class OAuthClientUsersService {
const username = generateShortHash(body.email, oAuthClientId);
user = await this.userRepository.create(body, username, oAuthClientId, isPlatformManaged);
} else {
const [_, emailDomain] = body.email.split("@");
user = (
await createNewUsersConnectToOrgIfExists({
usernamesOrEmails: [body.email],
@@ -39,7 +38,7 @@ export class OAuthClientUsersService {
language: "en",
},
parentId: null,
autoAcceptEmailDomain: emailDomain,
autoAcceptEmailDomain: "never-auto-accept-email-domain-for-managed-users",
connectionInfoMap: {
[body.email]: {
orgId: organizationId,
@@ -1,20 +1,18 @@
import { TokenExpiredException } from "@/modules/auth/guards/access-token/token-expired.exception";
import { OAuthClientRepository } from "@/modules/oauth-clients/oauth-client.repository";
import { TokensRepository } from "@/modules/tokens/tokens.repository";
import { BadRequestException, Injectable, Logger, UnauthorizedException } from "@nestjs/common";
import { BadRequestException, Injectable, UnauthorizedException } from "@nestjs/common";
import { INVALID_ACCESS_TOKEN } from "@calcom/platform-constants";
@Injectable()
export class OAuthFlowService {
private logger = new Logger("OAuthFlowService");
constructor(
private readonly tokensRepository: TokensRepository,
private readonly oAuthClientRepository: OAuthClientRepository
) //private readonly redisService: RedisIOService
{}
private readonly oAuthClientRepository: OAuthClientRepository //private readonly redisService: RedisIOService
) {}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async propagateAccessToken(accessToken: string) {
// this.logger.log("Propagating access token to redis", accessToken);
// TODO propagate
@@ -48,6 +46,7 @@ export class OAuthFlowService {
return true;
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
private async readFromCache(secret: string) {
return { status: "CACHE_MISS" };
}
@@ -7,7 +7,7 @@ import { PrismaClient } from "@prisma/client";
export class PrismaReadService implements OnModuleInit {
public prisma: PrismaClient;
constructor(private readonly configService: ConfigService) {
constructor(readonly configService: ConfigService) {
const dbUrl = configService.get("db.readUrl", { infer: true });
this.prisma = new PrismaClient({
@@ -7,7 +7,7 @@ import { PrismaClient } from "@prisma/client";
export class PrismaWriteService implements OnModuleInit {
public prisma: PrismaClient;
constructor(private readonly configService: ConfigService) {
constructor(readonly configService: ConfigService) {
const dbUrl = configService.get("db.writeUrl", { infer: true });
this.prisma = new PrismaClient({
@@ -101,7 +101,7 @@ export class TokensRepository {
}
async refreshOAuthTokens(clientId: string, refreshTokenSecret: string, tokenUserId: number) {
const accessExpiry = DateTime.now().plus({ minute: 1 }).startOf("minute").toJSDate();
const accessExpiry = DateTime.now().plus({ minute: 60 }).startOf("minute").toJSDate();
const refreshExpiry = DateTime.now().plus({ year: 1 }).startOf("day").toJSDate();
// eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -83,6 +83,17 @@ export class UsersRepository {
});
}
async findByEmailWithProfile(email: string) {
return this.dbRead.prisma.user.findUnique({
where: {
email,
},
include: {
movedToProfile: true,
},
});
}
async findByUsername(username: string) {
return this.dbRead.prisma.user.findFirst({
where: {
@@ -91,6 +102,21 @@ export class UsersRepository {
});
}
async findManagedUsersByOAuthClientId(oauthClientId: string, cursor: number, limit: number) {
return this.dbRead.prisma.user.findMany({
where: {
platformOAuthClients: {
some: {
id: oauthClientId,
},
},
isPlatformManaged: true,
},
take: limit,
skip: cursor,
});
}
async update(userId: number, updateData: UpdateManagedPlatformUserInput) {
this.formatInput(updateData);
+230 -5
View File
@@ -71,6 +71,31 @@
}
},
"/api/v2/oauth-clients/{clientId}/users": {
"get": {
"operationId": "OAuthClientUsersController_getManagedUsers",
"parameters": [
{
"name": "clientId",
"required": true,
"in": "path",
"schema": {
"type": "string"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
}
}
},
"post": {
"operationId": "OAuthClientUsersController_createUser",
"parameters": [
@@ -552,7 +577,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/GetEventTypesOutput"
}
}
}
@@ -582,7 +607,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/GetEventTypeOutput"
}
}
}
@@ -620,7 +645,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/UpdateEventTypeOutput"
}
}
}
@@ -648,7 +673,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/DeleteEventTypeOutput"
}
}
}
@@ -678,7 +703,7 @@
"content": {
"application/json": {
"schema": {
"type": "object"
"$ref": "#/components/schemas/GetEventTypesPublicOutput"
}
}
}
@@ -1720,6 +1745,139 @@
"data"
]
},
"Data": {
"type": "object",
"properties": {
"eventType": {
"$ref": "#/components/schemas/EventTypeOutput"
}
},
"required": [
"eventType"
]
},
"GetEventTypeOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/Data"
}
},
"required": [
"status",
"data"
]
},
"EventTypeGroup": {
"type": "object",
"properties": {
"eventTypes": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EventTypeOutput"
}
}
},
"required": [
"eventTypes"
]
},
"GetEventTypesData": {
"type": "object",
"properties": {
"eventTypeGroups": {
"type": "array",
"items": {
"$ref": "#/components/schemas/EventTypeGroup"
}
}
},
"required": [
"eventTypeGroups"
]
},
"GetEventTypesOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/GetEventTypesData"
}
},
"required": [
"status",
"data"
]
},
"PublicEventType": {
"type": "object",
"properties": {
"id": {
"type": "number",
"example": 1
},
"length": {
"type": "number",
"example": 60
},
"slug": {
"type": "string",
"example": "cooking-class"
},
"title": {
"type": "string",
"example": "Learn the secrets of masterchief!"
},
"description": {
"type": "string",
"nullable": true
}
},
"required": [
"id",
"length",
"slug",
"title"
]
},
"GetEventTypesPublicOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/PublicEventType"
}
}
},
"required": [
"status",
"data"
]
},
"UpdateEventTypeInput": {
"type": "object",
"properties": {
@@ -1747,6 +1905,73 @@
}
}
},
"UpdateEventTypeOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/EventTypeOutput"
}
},
"required": [
"status",
"data"
]
},
"DeleteData": {
"type": "object",
"properties": {
"id": {
"type": "number",
"example": 1
},
"length": {
"type": "number",
"example": 60
},
"slug": {
"type": "string",
"example": "cooking-class"
},
"title": {
"type": "string",
"example": "Learn the secrets of masterchief!"
}
},
"required": [
"id",
"length",
"slug",
"title"
]
},
"DeleteEventTypeOutput": {
"type": "object",
"properties": {
"status": {
"type": "string",
"example": "success",
"enum": [
"success",
"error"
]
},
"data": {
"$ref": "#/components/schemas/DeleteData"
}
},
"required": [
"status",
"data"
]
},
"CreateAvailabilityInput": {
"type": "object",
"properties": {
@@ -1,16 +1,16 @@
import { BaseStrategy } from "@/lib/passport/strategies/types";
import { NextAuthPassportStrategy } from "@/lib/passport/strategies/types";
import { UsersRepository } from "@/modules/users/users.repository";
import { Injectable } from "@nestjs/common";
import { PassportStrategy } from "@nestjs/passport";
@Injectable()
export class NextAuthMockStrategy extends PassportStrategy(BaseStrategy, "next-auth") {
export class NextAuthMockStrategy extends PassportStrategy(NextAuthPassportStrategy, "next-auth") {
constructor(private readonly email: string, private readonly userRepository: UsersRepository) {
super();
}
async authenticate() {
try {
const user = await this.userRepository.findByEmail(this.email);
const user = await this.userRepository.findByEmailWithProfile(this.email);
if (!user) {
throw new Error("User with the provided email not found");
}
+2 -1
View File
@@ -9,7 +9,8 @@ const env: Partial<Omit<Environment, "NODE_ENV">> = {
LOG_LEVEL: "trace",
REDIS_URL: "redis://localhost:9199",
};
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
process.env = {
...env,
...process.env,
@@ -1,11 +0,0 @@
import LegacyPage, { WrappedSetPasswordPage } from "@pages/settings/organizations/[id]/set-password";
import { _generateMetadata } from "app/_utils";
import { WithLayout } from "app/layoutHOC";
export const generateMetadata = async () =>
await _generateMetadata(
(t) => t("set_a_password"),
(t) => t("set_a_password_description")
);
export default WithLayout({ Page: LegacyPage, getLayout: WrappedSetPasswordPage });
+2 -2
View File
@@ -1,7 +1,7 @@
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { X } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
export default function AddToHomescreen() {
const { t } = useLocale();
@@ -40,7 +40,7 @@ export default function AddToHomescreen() {
type="button"
className="-mr-1 flex rounded-md p-2 hover:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-white">
<span className="sr-only">{t("dismiss")}</span>
<X className="text-inverted h-6 w-6 dark:text-white" aria-hidden="true" />
<Icon name="x" className="text-inverted h-6 w-6 dark:text-white" aria-hidden="true" />
</button>
</div>
</div>
+2 -3
View File
@@ -9,8 +9,7 @@ import { getPlaceholderAvatar } from "@calcom/lib/defaultAvatarImage";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { Badge, ListItemText, Avatar } from "@calcom/ui";
import { AlertCircle } from "@calcom/ui/components/icon";
import { Avatar, Badge, Icon, ListItemText } from "@calcom/ui";
type ShouldHighlight =
| {
@@ -105,7 +104,7 @@ export default function AppListCard(props: AppListCardProps) {
<ListItemText component="p">{description}</ListItemText>
{invalidCredential && (
<div className="flex gap-x-2 pt-2">
<AlertCircle className="h-8 w-8 text-red-500 sm:h-4 sm:w-4" />
<Icon name="circle-alert" className="h-8 w-8 text-red-500 sm:h-4 sm:w-4" />
<ListItemText component="p" className="whitespace-pre-wrap text-red-500">
{t("invalid_credential")}
</ListItemText>
+7 -8
View File
@@ -3,40 +3,39 @@
import { ShellMain } from "@calcom/features/shell/Shell";
import { UpgradeTip } from "@calcom/features/tips";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, ButtonGroup } from "@calcom/ui";
import { BarChart, CreditCard, Globe, Lock, Paintbrush, Users } from "@calcom/ui/components/icon";
import { Button, ButtonGroup, Icon } from "@calcom/ui";
export default function EnterprisePage() {
const { t } = useLocale();
const features = [
{
icon: <Globe className="h-5 w-5 text-red-500" />,
icon: <Icon name="globe" className="h-5 w-5 text-red-500" />,
title: t("branded_subdomain"),
description: t("branded_subdomain_description"),
},
{
icon: <BarChart className="h-5 w-5 text-blue-500" />,
icon: <Icon name="bar-chart" className="h-5 w-5 text-blue-500" />,
title: t("org_insights"),
description: t("org_insights_description"),
},
{
icon: <Paintbrush className="h-5 w-5 text-pink-500" />,
icon: <Icon name="paintbrush" className="h-5 w-5 text-pink-500" />,
title: t("extensive_whitelabeling"),
description: t("extensive_whitelabeling_description"),
},
{
icon: <Users className="h-5 w-5 text-orange-500" />,
icon: <Icon name="users" className="h-5 w-5 text-orange-500" />,
title: t("unlimited_teams"),
description: t("unlimited_teams_description"),
},
{
icon: <CreditCard className="h-5 w-5 text-green-500" />,
icon: <Icon name="credit-card" className="h-5 w-5 text-green-500" />,
title: t("unified_billing"),
description: t("unified_billing_description"),
},
{
icon: <Lock className="h-5 w-5 text-purple-500" />,
icon: <Icon name="lock" className="h-5 w-5 text-purple-500" />,
title: t("advanced_managed_events"),
description: t("advanced_managed_events_description"),
},
@@ -6,12 +6,11 @@ import { trpc } from "@calcom/trpc/react";
import {
Button,
Dropdown,
DropdownMenuContent,
DropdownMenuTrigger,
DropdownMenuItem,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@calcom/ui";
import { Plus } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -42,7 +41,7 @@ const AdditionalCalendarSelector = ({ isPending }: AdditionalCalendarSelectorPro
return (
<Dropdown modal={false}>
<DropdownMenuTrigger asChild>
<Button StartIcon={Plus} color="secondary" {...(isPending && { loading: isPending })}>
<Button StartIcon="plus" color="secondary" {...(isPending && { loading: isPending })}>
{t("add")}
</Button>
</DropdownMenuTrigger>
@@ -50,7 +49,7 @@ const AdditionalCalendarSelector = ({ isPending }: AdditionalCalendarSelectorPro
{options.map((data) => (
<DropdownMenuItem key={data.slug} className="focus:outline-none">
{data.slug === "add-new" ? (
<DropdownItem StartIcon={Plus} color="minimal" href="/apps/categories/calendar">
<DropdownItem StartIcon="plus" color="minimal" href="/apps/categories/calendar">
{t("install_new_calendar_app")}
</DropdownItem>
) : (
+6 -7
View File
@@ -11,17 +11,16 @@ import type { AppCategories } from "@calcom/prisma/enums";
import { trpc, type RouterOutputs } from "@calcom/trpc";
import type { App } from "@calcom/types/App";
import {
Alert,
Button,
Dropdown,
DropdownItem,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
List,
showToast,
Button,
DropdownMenuItem,
Alert,
} from "@calcom/ui";
import { MoreHorizontal, Trash, Video } from "@calcom/ui/components/icon";
import AppListCard from "@components/AppListCard";
@@ -82,7 +81,7 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
<div className="flex justify-end">
<Dropdown modal={false}>
<DropdownMenuTrigger asChild>
<Button StartIcon={MoreHorizontal} variant="icon" color="secondary" />
<Button StartIcon="ellipsis" variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent>
{!appIsDefault && variant === "conferencing" && !item.credentialOwner?.teamId && (
@@ -90,7 +89,7 @@ export const AppList = ({ data, handleDisconnect, variant, listClassName }: AppL
<DropdownItem
type="button"
color="secondary"
StartIcon={Video}
StartIcon="video"
onClick={() => {
const locationType = getEventLocationTypeFromApp(item?.locationOption?.value ?? "");
if (locationType?.linkType === "static") {
@@ -214,7 +213,7 @@ function ConnectOrDisconnectIntegrationMenuItem(props: {
color="destructive"
onClick={() => handleDisconnect(credentialId, teamId)}
disabled={isGlobal}
StartIcon={Trash}>
StartIcon="trash">
{t("remove_app")}
</DropdownItem>
</DropdownMenuItem>
+9 -10
View File
@@ -1,6 +1,6 @@
import Link from "next/link";
import type { IframeHTMLAttributes } from "react";
import React, { useState, useEffect } from "react";
import React, { useEffect, useState } from "react";
import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
import { AppDependencyComponent, InstallAppButton } from "@calcom/app-store/components";
@@ -10,8 +10,7 @@ import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/consta
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import type { App as AppType } from "@calcom/types/App";
import { Badge, Button, showToast, SkeletonButton, SkeletonText } from "@calcom/ui";
import { BookOpen, Check, ExternalLink, File, Flag, Mail, Shield } from "@calcom/ui/components/icon";
import { Badge, Button, Icon, SkeletonButton, SkeletonText, showToast } from "@calcom/ui";
import { InstallAppButtonChild } from "./InstallAppButtonChild";
@@ -196,7 +195,7 @@ export const AppPage = ({
isGlobal ||
(existingCredentials.length > 0 && allowedMultipleInstalls ? (
<div className="flex space-x-3">
<Button StartIcon={Check} color="secondary" disabled>
<Button StartIcon="check" color="secondary" disabled>
{existingCredentials.length > 0
? t("active_install", { count: existingCredentials.length })
: t("default")}
@@ -323,7 +322,7 @@ export const AppPage = ({
rel="noreferrer"
className="text-emphasis text-sm font-normal no-underline hover:underline"
href={docs}>
<BookOpen className="text-subtle -mt-1 mr-1 inline h-4 w-4" />
<Icon name="book-open" className="text-subtle -mt-1 mr-1 inline h-4 w-4" />
{t("documentation")}
</a>
</li>
@@ -335,7 +334,7 @@ export const AppPage = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={website}>
<ExternalLink className="text-subtle -mt-px mr-1 inline h-4 w-4" />
<Icon name="external-link" className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{website.replace("https://", "")}
</a>
</li>
@@ -347,7 +346,7 @@ export const AppPage = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={`mailto:${email}`}>
<Mail className="text-subtle -mt-px mr-1 inline h-4 w-4" />
<Icon name="mail" className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{email}
</a>
@@ -360,7 +359,7 @@ export const AppPage = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={tos}>
<File className="text-subtle -mt-px mr-1 inline h-4 w-4" />
<Icon name="file" className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{t("terms_of_service")}
</a>
</li>
@@ -372,7 +371,7 @@ export const AppPage = ({
rel="noreferrer"
className="text-emphasis font-normal no-underline hover:underline"
href={privacy}>
<Shield className="text-subtle -mt-px mr-1 inline h-4 w-4" />
<Icon name="shield" className="text-subtle -mt-px mr-1 inline h-4 w-4" />
{t("privacy_policy")}
</a>
</li>
@@ -383,7 +382,7 @@ export const AppPage = ({
{t("every_app_published", { appName: APP_NAME, companyName: COMPANY_NAME })}
</span>
<a className="mt-2 block text-xs text-red-500" href={`mailto:${SUPPORT_MAIL_ADDRESS}`}>
<Flag className="inline h-3 w-3" /> {t("report_app")}
<Icon name="flag" className="inline h-3 w-3" /> {t("report_app")}
</a>
</div>
</div>
@@ -11,13 +11,12 @@ import {
Alert,
Button,
EmptyScreen,
List,
AppSkeletonLoader as SkeletonLoader,
ShellSubHeading,
Label,
List,
ShellSubHeading,
AppSkeletonLoader as SkeletonLoader,
showToast,
} from "@calcom/ui";
import { Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import useRouterQuery from "@lib/hooks/useRouterQuery";
@@ -267,7 +266,7 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
</>
) : (
<EmptyScreen
Icon={Calendar}
Icon="calendar"
headline={t("no_category_apps", {
category: t("calendar").toLowerCase(),
})}
@@ -122,14 +122,13 @@ export const InstallAppButtonChild = ({
data-testid={team.isUser ? "install-app-button-personal" : "anything else"}
key={team.id}
disabled={isInstalled}
StartIcon={(props: { className?: string }) => (
CustomStartIcon={
<Avatar
alt={team.logo || ""}
imageSrc={team.logo || `${WEBAPP_URL}/${team.logo}/avatar.png`} // if no image, use default avatar
size="sm"
{...props}
/>
)}
}
onClick={() => {
mutation.mutate(
team.isUser ? addAppMutationInput : { ...addAppMutationInput, teamId: team.id }
@@ -6,7 +6,6 @@ import React from "react";
import { ShellMain } from "@calcom/features/shell/Shell";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmptyScreen } from "@calcom/ui";
import { AlertCircle } from "@calcom/ui/components/icon";
type AppsLayoutProps = {
children: React.ReactNode;
@@ -27,7 +26,7 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
<main className="w-full">
{emptyStore ? (
<EmptyScreen
Icon={AlertCircle}
Icon="circle-alert"
headline={isAdmin ? t("no_apps") : t("no_apps_configured")}
description={isAdmin ? t("enable_in_settings") : t("please_contact_admin")}
buttonText={isAdmin ? t("apps_settings") : ""}
+12
View File
@@ -0,0 +1,12 @@
import type { TurnstileProps } from "react-turnstile";
import Turnstile from "react-turnstile";
import { CLOUDFLARE_SITE_ID } from "@calcom/lib/constants";
type Props = Omit<TurnstileProps, "sitekey">;
export default function TurnstileWidget(props: Props) {
if (!CLOUDFLARE_SITE_ID || process.env.NEXT_PUBLIC_IS_E2E) return null;
return <Turnstile {...props} sitekey={CLOUDFLARE_SITE_ID} theme="auto" />;
}
@@ -1,7 +1,4 @@
import React from "react";
import { Button, SkeletonText } from "@calcom/ui";
import { MoreHorizontal } from "@calcom/ui/components/icon";
import classNames from "@lib/classNames";
@@ -31,7 +28,7 @@ function SkeletonItem() {
type="button"
variant="icon"
color="secondary"
StartIcon={MoreHorizontal}
StartIcon="ellipsis"
disabled
/>
</div>
+11 -10
View File
@@ -29,13 +29,13 @@ import {
DialogClose,
DialogContent,
DialogFooter,
Icon,
MeetingTimeInTimezones,
showToast,
TableActions,
TextAreaField,
Tooltip,
} from "@calcom/ui";
import { Ban, Check, Clock, CreditCard, MapPin, RefreshCcw, Send, X } from "@calcom/ui/components/icon";
import { ChargeCardDialog } from "@components/dialog/ChargeCardDialog";
import { EditLocationDialog } from "@components/dialog/EditLocationDialog";
@@ -138,7 +138,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setRejectionDialogIsOpen(true);
},
icon: Ban,
icon: "ban",
disabled: mutation.isPending,
},
// For bookings with payment, only confirm if the booking is paid for
@@ -152,7 +152,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
bookingConfirm(true);
},
icon: Check,
icon: "check" as const,
disabled: mutation.isPending,
},
]
@@ -169,7 +169,7 @@ function BookingListItem(booking: BookingItemProps) {
isTabRecurring && isRecurring ? "&allRemainingBookings=true" : ""
}${booking.seatsReferences.length ? `&seatReferenceUid=${getSeatReferenceUid()}` : ""}
`,
icon: X,
icon: "x" as const,
},
{
id: "edit_booking",
@@ -177,7 +177,7 @@ function BookingListItem(booking: BookingItemProps) {
actions: [
{
id: "reschedule",
icon: Clock,
icon: "clock" as const,
label: t("reschedule_booking"),
href: `${bookerUrl}/reschedule/${booking.uid}${
booking.seatsReferences.length ? `?seatReferenceUid=${getSeatReferenceUid()}` : ""
@@ -185,7 +185,7 @@ function BookingListItem(booking: BookingItemProps) {
},
{
id: "reschedule_request",
icon: Send,
icon: "send" as const,
iconClassName: "rotate-45 w-[16px] -translate-x-0.5 ",
label: t("send_reschedule_request"),
onClick: () => {
@@ -198,7 +198,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setIsOpenLocationDialog(true);
},
icon: MapPin,
icon: "map-pin" as const,
},
],
},
@@ -212,7 +212,7 @@ function BookingListItem(booking: BookingItemProps) {
onClick: () => {
setChargeCardDialogIsOpen(true);
},
icon: CreditCard,
icon: "credit-card" as const,
},
];
@@ -226,7 +226,7 @@ function BookingListItem(booking: BookingItemProps) {
const RequestSentMessage = () => {
return (
<Badge startIcon={Send} size="md" variant="gray" data-testid="request_reschedule_sent">
<Badge startIcon="send" size="md" variant="gray" data-testid="request_reschedule_sent">
{t("reschedule_request_sent")}
</Badge>
);
@@ -599,7 +599,8 @@ const RecurringBookingsTooltip = ({
);
})}>
<div className="text-default">
<RefreshCcw
<Icon
name="refresh-ccw"
strokeWidth="3"
className="text-muted float-left mr-1 mt-1.5 inline-block h-3 w-3"
/>
@@ -5,8 +5,7 @@ import { sdkActionManager } from "@calcom/embed-core/embed-iframe";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import type { RecurringEvent } from "@calcom/types/Calendar";
import { Button, TextArea } from "@calcom/ui";
import { X } from "@calcom/ui/components/icon";
import { Button, Icon, TextArea } from "@calcom/ui";
type Props = {
booking: {
@@ -57,7 +56,7 @@ export default function CancelBooking(props: Props) {
{error && (
<div className="mt-8">
<div className="bg-error mx-auto flex h-12 w-12 items-center justify-center rounded-full">
<X className="h-6 w-6 text-red-600" />
<Icon name="x" className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-emphasis text-lg font-medium leading-6" id="modal-title">
@@ -1,5 +1,5 @@
import { useState } from "react";
import type { Dispatch, SetStateAction } from "react";
import { useState } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
@@ -10,9 +10,9 @@ import {
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
} from "@calcom/ui";
import { CreditCard, AlertTriangle } from "@calcom/ui/components/icon";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -48,7 +48,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
<DialogContent>
<div className="flex flex-row space-x-3">
<div className=" bg-subtle flex h-10 w-10 flex-shrink-0 justify-center rounded-full">
<CreditCard className="m-auto h-6 w-6" />
<Icon name="credit-card" className="m-auto h-6 w-6" />
</div>
<div className="pt-1">
<DialogHeader title={t("charge_card")} />
@@ -56,7 +56,7 @@ export const ChargeCardDialog = (props: IRescheduleDialog) => {
{chargeError && (
<div className="mt-4 flex text-red-500">
<AlertTriangle className="mr-2 h-5 w-5 " aria-hidden="true" />
<Icon name="triangle-alert" className="mr-2 h-5 w-5 " aria-hidden="true" />
<p className="text-sm">{t("error_charging_card")}</p>
</div>
)}
@@ -18,9 +18,7 @@ import {
import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Input } from "@calcom/ui";
import { Button, Dialog, DialogContent, DialogFooter, Form, PhoneInput } from "@calcom/ui";
import { MapPin } from "@calcom/ui/components/icon";
import { Button, Icon, Input, Dialog, DialogContent, DialogFooter, Form, PhoneInput } from "@calcom/ui";
import { QueryCell } from "@lib/QueryCell";
@@ -237,7 +235,7 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
<DialogContent>
<div className="flex flex-row space-x-3">
<div className="bg-subtle mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full sm:mx-0 sm:h-10 sm:w-10">
<MapPin className="text-emphasis h-6 w-6" />
<Icon name="map-pin" className="text-emphasis h-6 w-6" />
</div>
<div className="w-full">
<div className="mt-3 text-center sm:mt-0 sm:text-left">
@@ -10,10 +10,10 @@ import {
DialogContent,
DialogFooter,
DialogHeader,
Icon,
showToast,
TextArea,
} from "@calcom/ui";
import { Clock } from "@calcom/ui/components/icon";
interface IRescheduleDialog {
isOpenDialog: boolean;
@@ -44,7 +44,7 @@ export const RescheduleDialog = (props: IRescheduleDialog) => {
<DialogContent enableOverflow>
<div className="flex flex-row space-x-3">
<div className="bg-subtle flex h-10 w-10 flex-shrink-0 justify-center rounded-full ">
<Clock className="m-auto h-6 w-6" />
<Icon name="clock" className="m-auto h-6 w-6" />
</div>
<div className="pt-1">
<DialogHeader title={t("send_reschedule_request")} />
@@ -21,7 +21,6 @@ import {
PhoneInput,
showToast,
} from "@calcom/ui";
import { Sparkles } from "@calcom/ui/components/icon";
type AIEventControllerProps = {
eventType: EventTypeSetup;
@@ -44,7 +43,7 @@ export default function AIEventController({ eventType, isTeamEvent }: AIEventCon
{!isOrg || !isTeamEvent ? (
<EmptyScreen
headline={t("Cal.ai")}
Icon={Sparkles}
Icon="sparkles"
description={t("upgrade_to_cal_ai_phone_number_description")}
buttonRaw={<Button href="/enterprise">{t("upgrade")}</Button>}
/>
@@ -191,12 +190,28 @@ const AISettings = ({ eventType }: { eventType: EventTypeSetup }) => {
<TextField
type="text"
hint="Variable: {name}"
hint="Variable: {{name}}"
label={t("guest_name")}
placeholder="Jane Doe"
{...formMethods.register("aiPhoneCallConfig.guestName")}
/>
<TextField
type="text"
hint="Variable: {{email}}"
label={t("guest_email")}
placeholder="jane@acme.com"
{...formMethods.register("aiPhoneCallConfig.guestEmail")}
/>
<TextField
type="text"
hint="Variable: {{company}}"
label={t("guest_company")}
placeholder="Acme"
{...formMethods.register("aiPhoneCallConfig.guestCompany")}
/>
<TextField
type="text"
hint="For eg:- cal_live_0123.."
@@ -1,4 +1,3 @@
import { InfoIcon } from "lucide-react";
import dynamic from "next/dynamic";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
import { useEffect, useState } from "react";
@@ -30,15 +29,15 @@ import {
Button,
Badge,
CheckboxField,
Icon,
Label,
SelectField,
SettingsToggle,
showToast,
Switch,
TextField,
Tooltip,
showToast,
} from "@calcom/ui";
import { Copy, Edit, Info } from "@calcom/ui/components/icon";
import RequiresConfirmationController from "./RequiresConfirmationController";
@@ -180,7 +179,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
aria-label="edit custom name"
className="hover:stroke-3 hover:text-emphasis min-w-fit !py-0 px-0 hover:bg-transparent"
onClick={() => setShowEventNameTip((old) => !old)}>
<Edit className="h-4 w-4" />
<Icon name="pencil" className="h-4 w-4" />
</Button>
}
/>
@@ -194,7 +193,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
label={
<>
{t("display_add_to_calendar_organizer")}
<InfoIcon className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md" />
<Icon
name="info"
className="text-default hover:text-attention hover:bg-attention ms-1 inline h-4 w-4 rounded-md"
/>
</>
}
checked={useEventTypeDestinationCalendarEmail}
@@ -348,7 +350,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
target="_blank"
rel="noreferrer"
href="https://cal.com/docs/core-features/event-types/single-use-private-links">
<Info className="ml-1.5 h-4 w-4 cursor-pointer" />
<Icon name="info" className="ml-1.5 h-4 w-4 cursor-pointer" />
</a>
}
{...shouldLockDisableProps("hashedLink")}
@@ -389,7 +391,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
showToast(t("enabled_after_update_description"), "warning");
}
}}>
<Copy className="h-4 w-4" />
<Icon name="copy" className="h-4 w-4" />
</Button>
</Tooltip>
}
@@ -11,8 +11,7 @@ import useLockedFieldsManager from "@calcom/features/ee/managed-event-types/hook
import type { FormValues } from "@calcom/features/eventtypes/lib/types";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Button, EmptyScreen, Alert } from "@calcom/ui";
import { Grid, Lock } from "@calcom/ui/components/icon";
import { Alert, Button, EmptyScreen } from "@calcom/ui";
export type EventType = Pick<EventTypeSetupProps, "eventType">["eventType"] &
EventTypeAppCardComponentProps["eventType"];
@@ -161,12 +160,12 @@ export const EventAppsTab = ({ eventType }: { eventType: EventType }) => {
)}
{!isPending && !installedApps?.length ? (
<EmptyScreen
Icon={Grid}
Icon="grid-3x3"
headline={t("empty_installed_apps_headline")}
description={t("empty_installed_apps_description")}
buttonRaw={
appsDisableProps.disabled ? (
<Button StartIcon={Lock} color="secondary" disabled>
<Button StartIcon="lock" color="secondary" disabled>
{t("locked_by_team_admin")}
</Button>
) : (
@@ -13,8 +13,7 @@ import { weekdayNames } from "@calcom/lib/weekday";
import { SchedulingType } from "@calcom/prisma/enums";
import { trpc } from "@calcom/trpc/react";
import useMeQuery from "@calcom/trpc/react/hooks/useMeQuery";
import { Badge, Button, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { ExternalLink, Globe } from "@calcom/ui/components/icon";
import { Badge, Button, Icon, Select, SettingsToggle, SkeletonText } from "@calcom/ui";
import { SelectSkeletonLoader } from "@components/availability/SkeletonLoader";
@@ -130,7 +129,7 @@ const EventTypeScheduleDetails = memo(
</div>
<div className="bg-muted border-subtle flex flex-col justify-center gap-2 rounded-b-md border p-6 sm:flex-row sm:justify-between">
<span className="text-default flex items-center justify-center text-sm sm:justify-start">
<Globe className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
<Icon name="globe" className="h-3.5 w-3.5 ltr:mr-2 rtl:ml-2" />
{schedule?.timeZone || <SkeletonText className="block h-5 w-32" />}
</span>
{!!schedule?.id && !schedule.isManaged && !schedule.readOnly && (
@@ -138,7 +137,7 @@ const EventTypeScheduleDetails = memo(
href={`/availability/${schedule.id}`}
disabled={isPending}
color="minimal"
EndIcon={ExternalLink}
EndIcon="external-link"
target="_blank"
rel="noopener noreferrer">
{t("edit_availability")}
@@ -18,7 +18,6 @@ import { ascendingLimitKeys, intervalLimitKeyToUnit } from "@calcom/lib/interval
import type { PeriodType } from "@calcom/prisma/enums";
import type { IntervalLimit } from "@calcom/types/Calendar";
import { Button, DateRangePicker, InputField, Label, Select, SettingsToggle, TextField } from "@calcom/ui";
import { Plus, Trash2 } from "@calcom/ui/components/icon";
const MinimumBookingNoticeInput = React.forwardRef<
HTMLInputElement,
@@ -598,7 +597,7 @@ const IntervalLimitItem = ({
{hasDeleteButton && !disabled && (
<Button
variant="icon"
StartIcon={Trash2}
StartIcon="trash-2"
color="destructive"
className="border-none"
onClick={() => onDelete(limitKey)}
@@ -708,7 +707,7 @@ const IntervalLimitsManager = <K extends "durationLimits" | "bookingLimits">({
);
})}
{currentIntervalLimits && Object.keys(currentIntervalLimits).length <= 3 && !disabled && (
<Button color="minimal" StartIcon={Plus} onClick={addLimit}>
<Button color="minimal" StartIcon="plus" onClick={addLimit}>
{t("add_limit")}
</Button>
)}
@@ -23,6 +23,7 @@ import {
SettingsToggle,
Skeleton,
TextField,
Icon,
Editor,
SkeletonContainer,
SkeletonText,
@@ -31,7 +32,6 @@ import {
Button,
showToast,
} from "@calcom/ui";
import { Plus, X, Check, CornerDownRight } from "@calcom/ui/components/icon";
import CheckboxField from "@components/ui/form/CheckboxField";
import type { SingleValueLocationOption } from "@components/ui/form/LocationSelect";
@@ -313,7 +313,7 @@ export const EventSetupTab = (
onClick={() => remove(index)}
aria-label={t("remove")}>
<div className="h-4 w-4">
<X className="border-l-1 hover:text-emphasis text-subtle h-4 w-4" />
<Icon name="x" className="border-l-1 hover:text-emphasis text-subtle h-4 w-4" />
</div>
</button>
)}
@@ -324,7 +324,7 @@ export const EventSetupTab = (
<div className="w-full">
<div className="flex gap-2">
<div className="flex items-center justify-center">
<CornerDownRight className="h-4 w-4" />
<Icon name="corner-down-right" className="h-4 w-4" />
</div>
<LocationInput
defaultValue={
@@ -414,7 +414,7 @@ export const EventSetupTab = (
) && (
<div className="text-default flex items-center text-sm">
<div className="mr-1.5 h-3 w-3">
<Check className="h-3 w-3" />
<Icon name="check" className="h-3 w-3" />
</div>
<Trans i18nKey="event_type_requres_google_cal">
<p>
@@ -442,7 +442,7 @@ export const EventSetupTab = (
<li>
<Button
data-testid="add-location"
StartIcon={Plus}
StartIcon="plus"
color="minimal"
onClick={() => setShowEmptyLocationSelect(true)}>
{t("add_location")}
@@ -1,4 +1,3 @@
import { Webhook as TbWebhook } from "lucide-react";
import type { TFunction } from "next-i18next";
import { Trans } from "next-i18next";
import { useRouter } from "next/navigation";
@@ -15,7 +14,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { HttpError } from "@calcom/lib/http-error";
import { SchedulingType } from "@calcom/prisma/enums";
import { trpc, TRPCClientError } from "@calcom/trpc/react";
import type { DialogProps } from "@calcom/ui";
import type { DialogProps, VerticalTabItemProps } from "@calcom/ui";
import {
Button,
ButtonGroup,
@@ -29,6 +28,7 @@ import {
DropdownMenuTrigger,
HorizontalTabs,
Label,
Icon,
showToast,
Skeleton,
Switch,
@@ -36,23 +36,6 @@ import {
VerticalDivider,
VerticalTabs,
} from "@calcom/ui";
import {
Link as LinkIcon,
Calendar,
Clock,
Sliders,
Repeat,
Grid,
Zap,
Sparkles,
Users,
ExternalLink,
Code,
Trash,
PhoneCall,
MoreHorizontal,
Loader,
} from "@calcom/ui/components/icon";
type Props = {
children: React.ReactNode;
@@ -97,41 +80,41 @@ function getNavigation({
{
name: "event_setup_tab_title",
href: `/event-types/${id}?tabName=setup`,
icon: LinkIcon,
icon: "link",
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
},
{
name: "event_limit_tab_title",
href: `/event-types/${id}?tabName=limits`,
icon: Clock,
icon: "clock",
info: `event_limit_tab_description`,
},
{
name: "event_advanced_tab_title",
href: `/event-types/${id}?tabName=advanced`,
icon: Sliders,
icon: "sliders-vertical",
info: `event_advanced_tab_description`,
},
{
name: "recurring",
href: `/event-types/${id}?tabName=recurring`,
icon: Repeat,
icon: "repeat",
info: `recurring_event_tab_description`,
},
{
name: "apps",
href: `/event-types/${id}?tabName=apps`,
icon: Grid,
icon: "grid-3x3",
//TODO: Handle proper translation with count handling
info: `${installedAppsNumber} apps, ${enabledAppsNumber} ${t("active")}`,
},
{
name: "workflows",
href: `/event-types/${id}?tabName=workflows`,
icon: Zap,
icon: "zap",
info: `${enabledWorkflowsNumber} ${t("active")}`,
},
];
] satisfies VerticalTabItemProps[];
}
function DeleteDialog({
@@ -228,7 +211,7 @@ function EventTypeSingleLayout({
const watchChildrenCount = formMethods.watch("children").length;
// Define tab navigation here
const EventTypeTabs = useMemo(() => {
const navigation = getNavigation({
const navigation: VerticalTabItemProps[] = getNavigation({
t,
length,
multipleDuration,
@@ -242,7 +225,7 @@ function EventTypeSingleLayout({
navigation.splice(1, 0, {
name: "availability",
href: `/event-types/${formMethods.getValues("id")}?tabName=availability`,
icon: Calendar,
icon: "calendar",
info:
isManagedEventType || isChildrenManagedEventType
? formMethods.getValues("schedule") === null
@@ -261,7 +244,7 @@ function EventTypeSingleLayout({
navigation.splice(2, 0, {
name: "assignment",
href: `/event-types/${formMethods.getValues("id")}?tabName=team`,
icon: Users,
icon: "users",
info: `${t(watchSchedulingType?.toLowerCase() ?? "")}${
isManagedEventType ? ` - ${t("number_member", { count: watchChildrenCount || 0 })}` : ""
}`,
@@ -273,14 +256,14 @@ function EventTypeSingleLayout({
navigation.push({
name: "instant_tab_title",
href: `/event-types/${eventType.id}?tabName=instant`,
icon: PhoneCall,
icon: "phone-call",
info: `instant_event_tab_description`,
});
}
navigation.push({
name: "webhooks",
href: `/event-types/${formMethods.getValues("id")}?tabName=webhooks`,
icon: TbWebhook,
icon: "webhook",
info: `${activeWebhooksNumber} ${t("active")}`,
});
}
@@ -289,7 +272,7 @@ function EventTypeSingleLayout({
navigation.push({
name: "Cal.ai",
href: `/event-types/${eventType.id}?tabName=ai`,
icon: Sparkles,
icon: "sparkles",
info: "cal_ai_event_tab_description", // todo `cal_ai_event_tab_description`,
});
}
@@ -378,14 +361,14 @@ function EventTypeSingleLayout({
variant="icon"
href={permalink}
rel="noreferrer"
StartIcon={ExternalLink}
StartIcon="external-link"
/>
</Tooltip>
<Button
color="secondary"
variant="icon"
StartIcon={LinkIcon}
StartIcon="link"
tooltip={t("copy_link")}
tooltipSide="bottom"
tooltipOffset={4}
@@ -396,7 +379,7 @@ function EventTypeSingleLayout({
/>
<EventTypeEmbedButton
embedUrl={encodeURIComponent(embedLink)}
StartIcon={Code}
StartIcon="code"
color="secondary"
variant="icon"
namespace=""
@@ -411,7 +394,7 @@ function EventTypeSingleLayout({
<Button
color="destructive"
variant="icon"
StartIcon={Trash}
StartIcon="trash"
tooltip={t("delete")}
tooltipSide="bottom"
tooltipOffset={4}
@@ -425,14 +408,14 @@ function EventTypeSingleLayout({
<Dropdown>
<DropdownMenuTrigger asChild>
<Button className="lg:hidden" StartIcon={MoreHorizontal} variant="icon" color="secondary" />
<Button className="lg:hidden" StartIcon="ellipsis" variant="icon" color="secondary" />
</DropdownMenuTrigger>
<DropdownMenuContent style={{ minWidth: "200px" }}>
<DropdownMenuItem className="focus:ring-muted">
<DropdownItem
target="_blank"
type="button"
StartIcon={ExternalLink}
StartIcon="external-link"
href={permalink}
rel="noreferrer">
{t("preview")}
@@ -441,7 +424,7 @@ function EventTypeSingleLayout({
<DropdownMenuItem className="focus:ring-muted">
<DropdownItem
type="button"
StartIcon={LinkIcon}
StartIcon="link"
onClick={() => {
navigator.clipboard.writeText(permalink);
showToast("Link copied!", "success");
@@ -453,7 +436,7 @@ function EventTypeSingleLayout({
<DropdownItem
type="button"
color="destructive"
StartIcon={Trash}
StartIcon="trash"
disabled={!hasPermsToDelete}
onClick={() => setDeleteDialogOpen(true)}>
{t("delete")}
@@ -489,7 +472,7 @@ function EventTypeSingleLayout({
</Button>
</div>
}>
<Suspense fallback={<Loader />}>
<Suspense fallback={<Icon name="loader" />}>
<div className="flex flex-col xl:flex-row xl:space-x-6">
<div className="hidden xl:block">
<VerticalTabs
@@ -1,5 +1,4 @@
import type { Webhook } from "@prisma/client";
import { Webhook as TbWebhook } from "lucide-react";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps } from "pages/event-types/[type]";
@@ -16,7 +15,6 @@ import { APP_NAME } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { Alert, Button, Dialog, DialogContent, EmptyScreen, showToast } from "@calcom/ui";
import { Plus, Lock } from "@calcom/ui/components/icon";
export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
const { t } = useLocale();
@@ -92,7 +90,7 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
<Button
color="secondary"
data-testid="new_webhook"
StartIcon={Plus}
StartIcon="plus"
onClick={() => setCreateModalOpen(true)}>
{t("new_webhook")}
</Button>
@@ -160,12 +158,12 @@ export const EventWebhooksTab = ({ eventType }: Pick<EventTypeSetupProps, "event
</>
) : (
<EmptyScreen
Icon={TbWebhook}
Icon="webhook"
headline={t("create_your_first_webhook")}
description={t("first_event_type_webhook_description")}
buttonRaw={
isChildrenManagedEventType && !isManagedEventType ? (
<Button StartIcon={Lock} color="secondary" disabled>
<Button StartIcon="lock" color="secondary" disabled>
{t("locked_by_team_admin")}
</Button>
) : (
@@ -16,7 +16,6 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
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;
@@ -48,7 +47,7 @@ export default function InstantEventController({
{!isOrg || !isTeamEvent ? (
<EmptyScreen
headline={t("instant_tab_title")}
Icon={PhoneCall}
Icon="phone-call"
description={t("uprade_to_create_instant_bookings")}
buttonRaw={<Button href="/enterprise">{t("upgrade")}</Button>}
/>
@@ -171,7 +170,7 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
<Button
color="secondary"
data-testid="new_webhook"
StartIcon={Plus}
StartIcon="plus"
onClick={() => setCreateModalOpen(true)}>
{t("new_webhook")}
</Button>
@@ -218,12 +217,12 @@ const InstantMeetingWebhooks = ({ eventType }: { eventType: EventTypeSetup }) =>
{t("warning_payment_instant_meeting_event")}
</p>
<EmptyScreen
Icon={TbWebhook}
Icon="webhook"
headline={t("create_your_first_webhook")}
description={t("create_instant_meeting_webhook_description")}
buttonRaw={
isChildrenManagedEventType && !isManagedEventType ? (
<Button StartIcon={Lock} color="secondary" disabled>
<Button StartIcon="lock" color="secondary" disabled>
{t("locked_by_admin")}
</Button>
) : (
@@ -1,5 +1,5 @@
import { SkeletonAvatar, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { Clock, User } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
function SkeletonLoader() {
return (
@@ -32,11 +32,11 @@ function SkeletonItem() {
<div className="">
<ul className="mt-2 flex space-x-4 rtl:space-x-reverse ">
<li className="flex items-center whitespace-nowrap">
<Clock className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<Icon name="clock" className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<SkeletonText className="h-4 w-12" />
</li>
<li className="flex items-center whitespace-nowrap">
<User className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<Icon name="user" className="text-subtle mr-1.5 mt-0.5 inline h-4 w-4" />
<SkeletonText className="h-4 w-12" />
</li>
</ul>
@@ -1,8 +1,7 @@
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { List } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
import { Icon, List } from "@calcom/ui";
import { AppConnectionItem } from "../components/AppConnectionItem";
import { ConnectedCalendarItem } from "../components/ConnectedCalendarItem";
@@ -88,7 +87,7 @@ const ConnectedCalendars = (props: IConnectCalendarsProps) => {
onClick={() => nextStep()}
disabled={disabledNextButton}>
{firstCalendar ? `${t("continue")}` : `${t("next_step_text")}`}
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
<Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</button>
</>
);
@@ -1,8 +1,7 @@
import classNames from "@calcom/lib/classNames";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import { List } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
import { Icon, List } from "@calcom/ui";
import { AppConnectionItem } from "../components/AppConnectionItem";
import { StepConnectionLoader } from "../components/StepConnectionLoader";
@@ -59,7 +58,7 @@ const ConnectedVideoStep = (props: ConnectedAppStepProps) => {
disabled={!hasAnyInstalledVideoApps}
onClick={() => nextStep()}>
{t("next_step_text")}
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
<Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</button>
</>
);
@@ -6,8 +6,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { TRPCClientErrorLike } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Form } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
import { Button, Form, Icon } from "@calcom/ui";
interface ISetupAvailabilityProps {
nextStep: () => void;
@@ -78,7 +77,7 @@ const SetupAvailability = (props: ISetupAvailabilityProps) => {
className="mt-2 w-full justify-center p-2 text-sm sm:mt-8"
loading={availabilityForm.formState.isSubmitting}
disabled={availabilityForm.formState.isSubmitting}>
{t("next_step_text")} <ArrowRight className="ml-2 h-4 w-4 self-center" />
{t("next_step_text")} <Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" />
</Button>
</div>
</Form>
@@ -10,7 +10,6 @@ import turndown from "@calcom/lib/turndownService";
import { trpc } from "@calcom/trpc/react";
import { Button, Editor, ImageUploader, Label, showToast } from "@calcom/ui";
import { UserAvatar } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
type FormData = {
bio: string;
@@ -147,7 +146,7 @@ const UserProfile = () => {
</fieldset>
<Button
loading={mutation.isPending}
EndIcon={ArrowRight}
EndIcon="arrow-right"
type="submit"
className="mt-8 w-full items-center justify-center">
{t("finish")}
@@ -9,8 +9,7 @@ import { FULL_NAME_LENGTH_MAX_LIMIT } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { trpc } from "@calcom/trpc/react";
import { Button, TimezoneSelect, Input } from "@calcom/ui";
import { ArrowRight } from "@calcom/ui/components/icon";
import { Button, TimezoneSelect, Icon, Input } from "@calcom/ui";
import { UsernameAvailabilityField } from "@components/ui/UsernameAvailability";
@@ -116,7 +115,7 @@ const UserSettings = (props: IUserSettingsProps) => {
loading={mutation.isPending}
disabled={mutation.isPending}>
{t("next_step_text")}
<ArrowRight className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
<Icon name="arrow-right" className="ml-2 h-4 w-4 self-center" aria-hidden="true" />
</Button>
</form>
);
@@ -1,10 +1,10 @@
import { Shield } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
const TwoFactorModalHeader = ({ title, description }: { title: string; description: string }) => {
return (
<div className="mb-4 sm:flex sm:items-start">
<div className="bg-brand text-brandcontrast dark:bg-darkmodebrand dark:text-darkmodebrandcontrast mx-auto flex h-12 w-12 flex-shrink-0 items-center justify-center rounded-full bg-opacity-5 sm:mx-0 sm:h-10 sm:w-10">
<Shield className="text-inverted h-6 w-6" />
<Icon name="shield" className="text-inverted h-6 w-6" />
</div>
<div className="mt-3 text-center sm:ml-4 sm:mt-0 sm:text-left">
<h3 className="font-cal text-emphasis text-lg font-medium leading-6" id="modal-title">
@@ -14,7 +14,6 @@ import {
Button,
InputError,
} from "@calcom/ui";
import { MoreHorizontal, Flag, Trash, Send } from "@calcom/ui/components/icon";
type CustomEmailTextFieldProps = {
formMethods: UseFormReturn<FormValues>;
@@ -71,7 +70,7 @@ const CustomEmailTextField = ({
<Dropdown>
<DropdownMenuTrigger asChild>
<Button
StartIcon={MoreHorizontal}
StartIcon="ellipsis"
variant="icon"
size="sm"
color="secondary"
@@ -82,7 +81,7 @@ const CustomEmailTextField = ({
<DropdownMenuContent>
<DropdownMenuItem>
<DropdownItem
StartIcon={Flag}
StartIcon="flag"
color="secondary"
className="disabled:opacity-40"
onClick={handleChangePrimary}
@@ -94,7 +93,7 @@ const CustomEmailTextField = ({
{!emailVerified && (
<DropdownMenuItem>
<DropdownItem
StartIcon={Send}
StartIcon="send"
color="secondary"
className="disabled:opacity-40"
onClick={handleVerifyEmail}
@@ -106,7 +105,7 @@ const CustomEmailTextField = ({
)}
<DropdownMenuItem>
<DropdownItem
StartIcon={Trash}
StartIcon="trash"
color="destructive"
className="disabled:opacity-40"
onClick={handleItemDelete}
@@ -1,10 +1,9 @@
import { Asterisk, Clipboard } from "lucide-react";
import React from "react";
import { classNames } from "@calcom/lib";
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants";
import type { Avatar } from "@calcom/prisma/client";
import { Button, showToast } from "@calcom/ui";
import { Button, Icon, showToast } from "@calcom/ui";
import { hasPermission } from "../../../../../../../packages/platform/utils/permissions";
@@ -75,7 +74,8 @@ export const OAuthClientCard = ({
<div className="flex flex-row items-center gap-2">
<div className="font-semibold">Client Id:</div>
<div>{id}</div>
<Clipboard
<Icon
name="clipboard"
type="button"
className="h-4 w-4 cursor-pointer"
onClick={() => {
@@ -89,9 +89,10 @@ export const OAuthClientCard = ({
<div className="font-semibold">Client Secret:</div>
<div className="flex items-center justify-center rounded-md">
{[...new Array(20)].map((_, index) => (
<Asterisk key={`${index}asterisk`} className="h-2 w-2" />
<Icon name="asterisk" key={`${index}asterisk`} className="h-2 w-2" />
))}
<Clipboard
<Icon
name="clipboard"
type="button"
className="ml-2 h-4 w-4 cursor-pointer"
onClick={() => {
@@ -7,7 +7,6 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import { PERMISSIONS_GROUPED_MAP } from "@calcom/platform-constants/permissions";
import { showToast } from "@calcom/ui";
import { Meta, Button, TextField, Label } from "@calcom/ui";
import { Plus, Trash } from "@calcom/ui/components/icon";
import { useCreateOAuthClient } from "@lib/hooks/settings/organizations/platform/oauth-clients/usePersistOAuthClient";
@@ -156,7 +155,7 @@ export const OAuthClientForm: FC = () => {
type="button"
color="minimal"
variant="icon"
StartIcon={Plus}
StartIcon="plus"
className="text-default mx-2 mb-2"
onClick={() => {
append({ uri: "" });
@@ -168,7 +167,7 @@ export const OAuthClientForm: FC = () => {
type="button"
color="destructive"
variant="icon"
StartIcon={Trash}
StartIcon="trash"
className="text-default mx-2 mb-2"
onClick={() => {
remove(index);
@@ -191,7 +190,7 @@ export const OAuthClientForm: FC = () => {
<Avatar
alt=""
imageSrc={value}
fallback={<Plus className="text-subtle h-4 w-4" />}
fallback={<Icon name="plus" className="text-subtle h-4 w-4" />}
size="sm"
/>
<div className="ms-4">
+1 -2
View File
@@ -9,7 +9,6 @@ import { isPasswordValid } from "@calcom/features/auth/lib/isPasswordValid";
import { WEBSITE_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { EmailField, EmptyScreen, Label, PasswordField, TextField } from "@calcom/ui";
import { UserCheck } from "@calcom/ui/components/icon";
export const AdminUserContainer = (props: React.ComponentProps<typeof AdminUser> & { userCount: number }) => {
const { t } = useLocale();
@@ -24,7 +23,7 @@ export const AdminUserContainer = (props: React.ComponentProps<typeof AdminUser>
props.onSuccess();
}}>
<EmptyScreen
Icon={UserCheck}
Icon="user-check"
headline={t("admin_user_created")}
description={t("admin_user_created_description")}
/>
@@ -11,7 +11,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
import type { RouterInputs, RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import { Button, TextField } from "@calcom/ui";
import { Check, ExternalLink, Loader } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
type EnterpriseLicenseFormValues = {
licenseKey: string;
@@ -92,7 +92,7 @@ const EnterpriseLicense = (
<div>
<Button
className="w-full justify-center text-lg"
EndIcon={ExternalLink}
EndIcon="external-link"
href="https://console.cal.com"
target="_blank">
{t("purchase_license")}
@@ -122,9 +122,9 @@ const EnterpriseLicense = (
)}
addOnSuffix={
checkLicenseLoading ? (
<Loader className="h-5 w-5 animate-spin" />
<Icon name="loader" className="h-5 w-5 animate-spin" />
) : errors.licenseKey === undefined && isDirty ? (
<Check className="h-5 w-5 text-green-700" />
<Icon name="check" className="h-5 w-5 text-green-700" />
) : undefined
}
color={errors.licenseKey ? "warn" : ""}
+5 -2
View File
@@ -2,7 +2,7 @@ import { useRouter } from "next/navigation";
import type { Dispatch, SetStateAction } from "react";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Check } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
const StepDone = (props: {
currentStep: number;
@@ -24,7 +24,10 @@ const StepDone = (props: {
}}>
<div className="min-h-36 my-6 flex flex-col items-center justify-center">
<div className="dark:bg-default flex h-[72px] w-[72px] items-center justify-center rounded-full bg-gray-600">
<Check className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10" />
<Icon
name="check"
className="text-inverted dark:bg-default dark:text-default inline-block h-10 w-10"
/>
</div>
<div className="max-w-[420px] text-center">
<h2 className="mb-1 mt-6 text-lg font-medium dark:text-gray-300">{t("all_done")}</h2>
+3 -5
View File
@@ -1,11 +1,9 @@
import React from "react";
import type { LucideIcon } from "@calcom/ui/components/icon";
import type { SVGComponent } from "@lib/types/SVGComponent";
import { Icon } from "@calcom/ui";
interface LinkIconButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement> {
Icon: SVGComponent | LucideIcon;
Icon: React.ComponentProps<typeof Icon>["name"];
}
export default function LinkIconButton(props: LinkIconButtonProps) {
@@ -15,7 +13,7 @@ export default function LinkIconButton(props: LinkIconButtonProps) {
type="button"
{...props}
className="text-md hover:bg-emphasis hover:text-emphasis text-default flex items-center rounded-sm px-2 py-1 text-sm font-medium">
<props.Icon className="text-subtle h-4 w-4 ltr:mr-2 rtl:ml-2" />
<Icon name={props.Icon} className="text-subtle h-4 w-4 ltr:mr-2 rtl:ml-2" />
{props.children}
</button>
</div>
@@ -16,7 +16,7 @@ import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Dialog, DialogClose, DialogContent, DialogFooter, Input, Label } from "@calcom/ui";
import { Check, Edit2, ExternalLink, Star as StarSolid } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
export enum UsernameChangeStatusEnum {
UPGRADE = "UPGRADE",
@@ -239,9 +239,13 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
isInputUsernamePremium ? "text-transparent" : "",
usernameIsAvailable ? "" : ""
)}>
{isInputUsernamePremium ? <StarSolid className="mt-[2px] h-4 w-4 fill-orange-400" /> : <></>}
{isInputUsernamePremium ? (
<Icon name="star" className="mt-[2px] h-4 w-4 fill-orange-400" />
) : (
<></>
)}
{!isInputUsernamePremium && usernameIsAvailable ? (
<Check className="mt-[2px] h-4 w-4" />
<Icon name="check" className="mt-[2px] h-4 w-4" />
) : (
<></>
)}
@@ -260,7 +264,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<Dialog open={openDialogSaveUsername}>
<DialogContent
Icon={Edit2}
Icon="pencil"
title={t("confirm_username_change_dialog_title")}
description={
<>
@@ -297,7 +301,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
data-testid="go-to-billing"
href={paymentLink}>
<>
{t("go_to_stripe_billing")} <ExternalLink className="ml-1 h-4 w-4" />
{t("go_to_stripe_billing")} <Icon name="external-link" className="ml-1 h-4 w-4" />
</>
</Button>
)}
@@ -11,7 +11,7 @@ import type { TRPCClientErrorLike } from "@calcom/trpc/client";
import { trpc } from "@calcom/trpc/react";
import type { AppRouter } from "@calcom/trpc/server/routers/_app";
import { Button, Dialog, DialogClose, DialogContent, TextField, DialogFooter, Tooltip } from "@calcom/ui";
import { Check, Edit2 } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
interface ICustomUsernameProps {
currentUsername: string | undefined;
@@ -137,7 +137,11 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
{currentUsername !== inputUsernameValue && (
<div className="absolute right-[2px] top-6 flex h-7 flex-row">
<span className={classNames("bg-default mx-0 p-3")}>
{usernameIsAvailable ? <Check className="relative bottom-[6px] h-4 w-4" /> : <></>}
{usernameIsAvailable ? (
<Icon name="check" className="relative bottom-[6px] h-4 w-4" />
) : (
<></>
)}
</span>
</div>
)}
@@ -154,7 +158,7 @@ const UsernameTextfield = (props: ICustomUsernameProps & Partial<React.Component
</div>
)}
<Dialog open={openDialogSaveUsername}>
<DialogContent type="confirmation" Icon={Edit2} title={t("confirm_username_change_dialog_title")}>
<DialogContent type="confirmation" Icon="pencil" title={t("confirm_username_change_dialog_title")}>
<div className="flex flex-row">
<div className="mb-4 w-full pt-1">
<div className="bg-subtle flex w-full flex-wrap justify-between gap-6 rounded-sm px-4 py-3 text-sm">
@@ -3,7 +3,7 @@ import type { Props } from "react-select";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Avatar } from "@calcom/ui";
import { X } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
import Select from "@components/ui/form/Select";
@@ -43,7 +43,8 @@ export const CheckedSelect = ({
alt={option.label}
/>
{option.label}
<X
<Icon
name="x"
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
className="text-subtle float-right mt-0.5 h-5 w-5 cursor-pointer"
/>
+2 -2
View File
@@ -3,7 +3,7 @@ import "react-calendar/dist/Calendar.css";
import "react-date-picker/dist/DatePicker.css";
import PrimitiveDatePicker from "react-date-picker/dist/entry.nostyle";
import { Calendar } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
import classNames from "@lib/classNames";
@@ -23,7 +23,7 @@ export const DatePicker = ({ minDate, disabled, date, onDatesChange, className }
className
)}
clearIcon={null}
calendarIcon={<Calendar className="text-subtle h-5 w-5" />}
calendarIcon={<Icon name="calendar" className="text-subtle h-5 w-5" />}
value={date}
minDate={minDate}
disabled={disabled}
+4 -2
View File
@@ -24,8 +24,10 @@ const querySchema = z.object({
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const emailVerificationEnabled = await getFeatureFlag(prisma, "email-verification");
await ssrInit(ctx);
const signupDisabled = await getFeatureFlag(prisma, "disable-signup");
const ssr = await ssrInit(ctx);
const token = z.string().optional().parse(ctx.query.token);
const redirectUrlData = z
.string()
@@ -42,8 +44,8 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
redirectUrl,
isGoogleLoginEnabled: IS_GOOGLE_LOGIN_ENABLED,
isSAMLLoginEnabled,
trpcState: ssr.dehydrate(),
prepopulateFormValues: undefined,
emailVerificationEnabled,
};
// username + email prepopulated from query params
-1
View File
@@ -1 +0,0 @@
export type SVGComponent = React.FunctionComponent<React.SVGProps<SVGSVGElement>>;
@@ -17,7 +17,6 @@ import type { RouterOutputs } from "@calcom/trpc/react";
import { trpc } from "@calcom/trpc/react";
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
import { Alert, Button, EmptyScreen, HorizontalTabs } from "@calcom/ui";
import { Calendar } from "@calcom/ui/components/icon";
import { useInViewObserver } from "@lib/hooks/useInViewObserver";
import useMeQuery from "@lib/hooks/useMeQuery";
@@ -241,7 +240,7 @@ export default function Bookings() {
{query.status === "success" && isEmpty && (
<div className="flex items-center justify-center pt-2 xl:pt-0">
<EmptyScreen
Icon={Calendar}
Icon="calendar"
headline={t("no_status_bookings_yet", { status: t(status).toLowerCase() })}
description={t("no_status_bookings_yet_description", {
status: t(status).toLowerCase(),
File diff suppressed because it is too large Load Diff
@@ -50,6 +50,7 @@ import {
EmptyScreen,
HeadSeo,
HorizontalTabs,
Icon,
Label,
showToast,
Skeleton,
@@ -58,20 +59,6 @@ import {
ArrowButton,
UserAvatarGroup,
} from "@calcom/ui";
import {
Clipboard,
Code,
Copy,
Edit,
Edit2,
ExternalLink,
Link as LinkIcon,
MoreHorizontal,
Trash,
Upload,
Users,
VenetianMask,
} from "@calcom/ui/components/icon";
import type { AppProps } from "@lib/app-providers";
import useMeQuery from "@lib/hooks/useMeQuery";
@@ -476,7 +463,7 @@ export const EventTypeList = ({
target="_blank"
variant="icon"
href={calLink}
StartIcon={ExternalLink}
StartIcon="external-link"
/>
</Tooltip>
@@ -484,7 +471,7 @@ export const EventTypeList = ({
<Button
color="secondary"
variant="icon"
StartIcon={LinkIcon}
StartIcon="link"
onClick={() => {
showToast(t("link_copied"), "success");
navigator.clipboard.writeText(calLink);
@@ -497,7 +484,7 @@ export const EventTypeList = ({
<Button
color="secondary"
variant="icon"
StartIcon={VenetianMask}
StartIcon="venetian-mask"
onClick={() => {
showToast(t("private_link_copied"), "success");
navigator.clipboard.writeText(placeholderHashedLink);
@@ -513,7 +500,7 @@ export const EventTypeList = ({
type="button"
variant="icon"
color="secondary"
StartIcon={MoreHorizontal}
StartIcon="ellipsis"
className="ltr:radix-state-open:rounded-r-md rtl:radix-state-open:rounded-l-md"
/>
</DropdownMenuTrigger>
@@ -523,7 +510,7 @@ export const EventTypeList = ({
<DropdownItem
type="button"
data-testid={`event-type-edit-${type.id}`}
StartIcon={Edit2}
StartIcon="pencil"
onClick={() => router.push(`/event-types/${type.id}`)}>
{t("edit")}
</DropdownItem>
@@ -535,7 +522,7 @@ export const EventTypeList = ({
<DropdownItem
type="button"
data-testid={`event-type-duplicate-${type.id}`}
StartIcon={Copy}
StartIcon="copy"
onClick={() => openDuplicateModal(type, group)}>
{t("duplicate")}
</DropdownItem>
@@ -548,7 +535,7 @@ export const EventTypeList = ({
namespace=""
as={DropdownItem}
type="button"
StartIcon={Code}
StartIcon="code"
className="w-full rounded-none"
embedUrl={encodeURIComponent(embedLink)}
eventId={type.id}>
@@ -569,7 +556,7 @@ export const EventTypeList = ({
setDeleteDialogTypeId(type.id);
setDeleteDialogSchedulingType(type.schedulingType);
}}
StartIcon={Trash}
StartIcon="trash"
className="w-full rounded-none">
{t("delete")}
</DropdownItem>
@@ -586,7 +573,7 @@ export const EventTypeList = ({
<div className="min-w-9 mx-5 flex sm:hidden">
<Dropdown>
<DropdownMenuTrigger asChild data-testid={`event-type-options-${type.id}`}>
<Button type="button" variant="icon" color="secondary" StartIcon={MoreHorizontal} />
<Button type="button" variant="icon" color="secondary" StartIcon="ellipsis" />
</DropdownMenuTrigger>
<DropdownMenuPortal>
<DropdownMenuContent>
@@ -596,7 +583,7 @@ export const EventTypeList = ({
<DropdownItem
href={calLink}
target="_blank"
StartIcon={ExternalLink}
StartIcon="external-link"
className="w-full rounded-none">
{t("preview")}
</DropdownItem>
@@ -608,7 +595,7 @@ export const EventTypeList = ({
navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success");
}}
StartIcon={Clipboard}
StartIcon="clipboard"
className="w-full rounded-none text-left">
{t("copy_link")}
</DropdownItem>
@@ -629,7 +616,7 @@ export const EventTypeList = ({
.then(() => showToast(t("link_shared"), "success"))
.catch(() => showToast(t("failed"), "error"));
}}
StartIcon={Upload}
StartIcon="upload"
className="w-full rounded-none">
{t("share")}
</DropdownItem>
@@ -639,7 +626,7 @@ export const EventTypeList = ({
<DropdownMenuItem className="outline-none">
<DropdownItem
onClick={() => router.push(`/event-types/${type.id}`)}
StartIcon={Edit}
StartIcon="pencil"
className="w-full rounded-none">
{t("edit")}
</DropdownItem>
@@ -649,7 +636,7 @@ export const EventTypeList = ({
<DropdownMenuItem className="outline-none">
<DropdownItem
onClick={() => openDuplicateModal(type, group)}
StartIcon={Copy}
StartIcon="copy"
data-testid={`event-type-duplicate-${type.id}`}>
{t("duplicate")}
</DropdownItem>
@@ -667,7 +654,7 @@ export const EventTypeList = ({
setDeleteDialogTypeId(type.id);
setDeleteDialogSchedulingType(type.schedulingType);
}}
StartIcon={Trash}
StartIcon="trash"
className="w-full rounded-none">
{t("delete")}
</DropdownItem>
@@ -768,7 +755,7 @@ const EventTypeListHeading = ({
<span className="text-subtle relative -top-px me-2 ms-2 text-xs">
<Link href={`/settings/teams/${teamId}/members`}>
<Badge variant="gray">
<Users className="-mt-px mr-1 inline h-3 w-3" />
<Icon name="users" className="-mt-px mr-1 inline h-3 w-3" />
{membershipCount}
</Badge>
</Link>
@@ -796,7 +783,7 @@ const CreateFirstEventTypeView = ({ slug }: { slug: string }) => {
return (
<EmptyScreen
Icon={LinkIcon}
Icon="link"
headline={t("new_event_type_heading")}
description={t("new_event_type_description")}
className="mb-16"
@@ -34,10 +34,41 @@ import { EventTypeSingleLayout } from "@components/eventtype/EventTypeSingleLayo
import { type PageProps } from "~/event-types/views/event-types-single-view.getServerSideProps";
const DEFAULT_PROMPT_VALUE =
"## You are helping user set up a call with the support team. The appointment is 15 min long. You are a pleasant and friendly.\n\n## Style Guardrails\nBe Concise: Respond succinctly, addressing one topic at most.\nEmbrace Variety: Use diverse language and rephrasing to enhance clarity without repeating content.\nBe Conversational: Use everyday language, making the chat feel like talking to a friend.\nBe Proactive: Lead the conversation, often wrapping up with a question or next-step suggestion.\nAvoid multiple questions in a single response.\nGet clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.\nUse a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).\nIf you are saying a time like 8:00 AM, just say 8 AM and emit the trailing zeros.\n\n## Response Guideline\nAdapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.\nStay in Character: Keep conversations within your role'''s scope, guiding them back creatively without repeating.\nEnsure Fluid Dialogue: Respond in a role-appropriate, direct manner to maintain a smooth conversation flow.\n\n## Schedule Rule\nCurrent time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.\n\n## Task Steps\n1. I am here to learn more about your issue and help schedule an appointment with our support team.\n2. Ask for user name and email. Confirm the name and email with user by reading it back to user.\n3. Ask user for \"When would you want to meet with one of our representive\".\n4. Call function check_availability to check for availability in the user provided time range.\n - if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.\n - if availability does not exist, ask user to select another time range for the appointment, repeat this step 3.\n4. Confirm the date and time selected by user: \"Just to confirm, you want to book the appointment at ...\".\n6. Once confirmed, call function book_appointment to book the appointment.\n - if booking returned booking detail, it means booking is successful, proceed to step 7.\n - if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3.\n7. Inform the user booking is successful, and ask if user have any questions. Answer them if there are any.\n8. After all questions answered, call function end_call to hang up.";
const DEFAULT_PROMPT_VALUE = `## You are helping user set up a call with the support team. The appointment is 15 min long. You are a pleasant and friendly.
const DEFAULT_BEGIN_MESSAGE = "Hi. How are you doing?";
## Style Guardrails
Be Concise: Respond succinctly, addressing one topic at most.
Embrace Variety: Use diverse language and rephrasing to enhance clarity without repeating content.
Be Conversational: Use everyday language, making the chat feel like talking to a friend.
Be Proactive: Lead the conversation, often wrapping up with a question or next-step suggestion.
Avoid multiple questions in a single response.
Get clarity: If the user only partially answers a question, or if the answer is unclear, keep asking to get clarity.
Use a colloquial way of referring to the date (like Friday, Jan 14th, or Tuesday, Jan 12th, 2024 at 8am).
If you are saying a time like 8:00 AM, just say 8 AM and emit the trailing zeros.
## Response Guideline
Adapt and Guess: Try to understand transcripts that may contain transcription errors. Avoid mentioning \"transcription error\" in the response.
Stay in Character: Keep conversations within your role'''s scope, guiding them back creatively without repeating.
Ensure Fluid Dialogue: Respond in a role-appropriate, direct manner to maintain a smooth conversation flow.
## Schedule Rule
Current time is {{current_time}}. You only schedule time in current calendar year, you cannot schedule time that'''s in the past.
## Task Steps
1. I am here to learn more about your issue and help schedule an appointment with our support team.
2. Use name {{name}} and email {{email}} for creating booking.
3. Ask user for \"When would you want to meet with one of our representive\".
4. Call function check_availability to check for availability in the user provided time range.
- if availability exists, inform user about the availability range (do not repeat the detailed available slot) and ask user to choose from it. Make sure user chose a slot within detailed available slot.
- if availability does not exist, ask user to select another time range for the appointment, repeat this step 3.
5. Confirm the date and time selected by user: \"Just to confirm, you want to book the appointment at ...\".
6. Once confirmed, call function book_appointment to book the appointment.
- if booking returned booking detail, it means booking is successful, proceed to step 7.
- if booking returned error message, let user know why the booking was not successful, and maybe start over with step 3.
7. Inform the user booking is successful, and ask if user have any questions. Answer them if there are any.
8. After all questions answered, call function end_call to hang up.`;
const DEFAULT_BEGIN_MESSAGE = "Hi {{name}} from {{company}}. How are you doing?";
// These can't really be moved into calcom/ui due to the fact they use infered getserverside props typings;
const EventSetupTab = dynamic(() =>
@@ -256,6 +287,8 @@ const EventTypePage = (props: EventTypeSetupProps) => {
enabled: eventType.aiPhoneCallConfig?.enabled,
beginMessage: eventType.aiPhoneCallConfig?.beginMessage ?? DEFAULT_BEGIN_MESSAGE,
guestName: eventType.aiPhoneCallConfig?.guestName,
guestEmail: eventType.aiPhoneCallConfig?.guestEmail,
guestCompany: eventType.aiPhoneCallConfig?.guestCompany,
yourPhoneNumber: eventType.aiPhoneCallConfig?.yourPhoneNumber,
numberToCall: eventType.aiPhoneCallConfig?.numberToCall,
},
@@ -17,8 +17,7 @@ import EmptyPage from "@calcom/features/eventtypes/components/EmptyPage";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useRouterQuery } from "@calcom/lib/hooks/useRouterQuery";
import useTheme from "@calcom/lib/hooks/useTheme";
import { HeadSeo, UnpublishedEntity, UserAvatar } from "@calcom/ui";
import { ArrowRight, Verified } from "@calcom/ui/components/icon";
import { HeadSeo, Icon, UnpublishedEntity, UserAvatar } from "@calcom/ui";
import { type getServerSideProps } from "./users-public-view.getServerSideProps";
@@ -121,10 +120,16 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
{profile.name}
{!isOrg && user.verified && (
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
<Icon
name="badge-check"
className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black"
/>
)}
{isOrg && (
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-yellow-500 text-white dark:text-black" />
<Icon
name="badge-check"
className=" mx-1 -mt-1 inline h-6 w-6 fill-yellow-500 text-white dark:text-black"
/>
)}
</h1>
{!isBioEmpty && (
@@ -153,7 +158,10 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
key={type.id}
style={{ display: "flex", ...eventTypeListItemEmbedStyles }}
className="bg-default border-subtle dark:bg-muted dark:hover:bg-emphasis hover:bg-muted group relative border-b first:rounded-t-md last:rounded-b-md last:border-b-0">
<ArrowRight className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100" />
<Icon
name="arrow-right"
className="text-emphasis absolute right-4 top-4 h-4 w-4 opacity-0 transition-opacity group-hover:opacity-100"
/>
{/* Don't prefetch till the time we drop the amount of javascript in [user][type] page which is impacting score for [user] page */}
<div className="block w-full p-5">
<Link
@@ -3,8 +3,7 @@
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import { Button, HeadSeo } from "@calcom/ui";
import { ArrowRight, Calendar, X } from "@calcom/ui/components/icon";
import { Button, HeadSeo, Icon } from "@calcom/ui";
import { type PageProps } from "./videos-meeting-ended-single-view.getServerSideProps";
@@ -28,7 +27,7 @@ export default function MeetingUnavailable(props: PageProps) {
aria-labelledby="modal-headline">
<div>
<div className="bg-error mx-auto flex h-12 w-12 items-center justify-center rounded-full">
<X className="h-6 w-6 text-red-600" />
<Icon name="x" className="h-6 w-6 text-red-600" />
</div>
<div className="mt-3 text-center sm:mt-5">
<h3 className="text-emphasis text-lg font-medium leading-6" id="modal-headline">
@@ -40,14 +39,14 @@ export default function MeetingUnavailable(props: PageProps) {
{props.booking.title}
</h2>
<p className="text-subtle text-center">
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
<Icon name="calendar" className="-mt-1 mr-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(`${detectBrowserTimeFormat}, dddd DD MMMM YYYY`)}
</p>
</div>
</div>
<div className="mt-5 text-center sm:mt-6">
<div className="mt-5">
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
<Button data-testid="return-home" href="/event-types" EndIcon="arrow-right">
{t("go_back")}
</Button>
</div>
@@ -3,8 +3,7 @@
import dayjs from "@calcom/dayjs";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { detectBrowserTimeFormat } from "@calcom/lib/timeFormat";
import { Button, HeadSeo, EmptyScreen } from "@calcom/ui";
import { ArrowRight, Calendar, Clock } from "@calcom/ui/components/icon";
import { Button, HeadSeo, Icon, EmptyScreen } from "@calcom/ui";
import { type PageProps } from "./videos-meeting-not-started-single-view.getServerSideProps";
@@ -15,19 +14,19 @@ export default function MeetingNotStarted(props: PageProps) {
<HeadSeo title={t("this_meeting_has_not_started_yet")} description={props.booking.title} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon={Clock}
Icon="clock"
headline={t("this_meeting_has_not_started_yet")}
description={
<>
<h2 className="mb-2 text-center font-medium">{props.booking.title}</h2>
<p className="text-subtle text-center">
<Calendar className="-mt-1 mr-1 inline-block h-4 w-4" />
<Icon name="calendar" className="-mt-1 mr-1 inline-block h-4 w-4" />
{dayjs(props.booking.startTime).format(`${detectBrowserTimeFormat}, dddd DD MMMM YYYY`)}
</p>
</>
}
buttonRaw={
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
<Button data-testid="return-home" href="/event-types" EndIcon="arrow-right">
{t("go_back")}
</Button>
}
@@ -2,7 +2,6 @@
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, EmptyScreen, HeadSeo } from "@calcom/ui";
import { X, ArrowRight } from "@calcom/ui/components/icon";
export default function NoMeetingFound() {
const { t } = useLocale();
@@ -12,11 +11,11 @@ export default function NoMeetingFound() {
<HeadSeo title={t("no_meeting_found")} description={t("no_meeting_found")} />
<main className="mx-auto my-24 max-w-3xl">
<EmptyScreen
Icon={X}
Icon="x"
headline={t("no_meeting_found")}
description={t("no_meeting_found_description")}
buttonRaw={
<Button data-testid="return-home" href="/event-types" EndIcon={ArrowRight}>
<Button data-testid="return-home" href="/event-types" EndIcon="arrow-right">
{t("go_back_home")}
</Button>
}
@@ -10,7 +10,7 @@ import { APP_NAME, SEO_IMG_OGIMG_VIDEO, WEBSITE_URL } from "@calcom/lib/constant
import { formatToLocalizedDate, formatToLocalizedTime } from "@calcom/lib/date-fns";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { markdownToSafeHTML } from "@calcom/lib/markdownToSafeHTML";
import { ChevronRight } from "@calcom/ui/components/icon";
import { Icon } from "@calcom/ui";
import { type PageProps } from "./videos-single-view.getServerSideProps";
@@ -231,7 +231,8 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
aria-label={`${open ? "close" : "open"} booking description sidebar`}
className="h-20 w-6 rounded-r-md border border-l-0 border-gray-300/20 bg-black/60 text-white shadow-sm backdrop-blur-lg"
onClick={() => setOpen(!open)}>
<ChevronRight
<Icon
name="chevron-right"
aria-hidden
className={classNames(open && "rotate-180", "w-5 transition-all duration-300 ease-in-out")}
/>
+4 -4
View File
@@ -50,6 +50,10 @@ if (!process.env.EMAIL_FROM) {
if (!process.env.NEXTAUTH_URL) throw new Error("Please set NEXTAUTH_URL");
if (!process.env.NEXT_PUBLIC_API_V2_URL) {
console.error("Please set NEXT_PUBLIC_API_V2_URL");
}
const validJson = (jsonString) => {
try {
const o = JSON.parse(jsonString);
@@ -187,10 +191,6 @@ const nextConfig = {
"lucide-react",
],
modularizeImports: {
"@calcom/ui/components/icon": {
transform: "lucide-react/dist/esm/icons/{{ kebabCase member }}",
preventFullImport: true,
},
"@calcom/features/insights/components": {
transform: "@calcom/features/insights/components/{{member}}",
skipDefaultConversion: true,
+2 -1
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.9.4",
"version": "3.9.5",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -119,6 +119,7 @@
"react-schemaorg": "^2.0.0",
"react-select": "^5.7.0",
"react-timezone-select": "^1.4.0",
"react-turnstile": "^1.1.3",
"react-use-intercom": "1.5.1",
"remove-markdown": "^0.5.0",
"rrule": "^2.7.1",

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