Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4933295810 | ||
|
|
ed0cd30092 | ||
|
|
1132d9d0dc | ||
|
|
9bd3a362a2 | ||
|
|
844076badb | ||
|
|
c0523a31a7 | ||
|
|
1fff642860 | ||
|
|
89fe383c9f | ||
|
|
4a173c48b8 | ||
|
|
a72a3e45e9 | ||
|
|
a8d8c992a4 | ||
|
|
99f9a121cb | ||
|
|
6a0e2392fe | ||
|
|
addec46feb | ||
|
|
a4fe9a51b5 | ||
|
|
78b8bbb5aa | ||
|
|
d7ece4f583 | ||
|
|
8fc9b9ae9d | ||
|
|
d1175bf7de | ||
|
|
3900453482 | ||
|
|
68e7e76b31 | ||
|
|
986d196a37 | ||
|
|
d30fb213d2 | ||
|
|
3f02de8214 | ||
|
|
7421cea0da | ||
|
|
507daa6a0c | ||
|
|
61f68af435 | ||
|
|
b3f4cfab32 | ||
|
|
c3b1de498d | ||
|
|
cdf059361e | ||
|
|
2aada9a7da |
+5
-2
@@ -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
|
||||
|
||||
|
||||
@@ -4,3 +4,4 @@ node_modules
|
||||
**/**/public
|
||||
packages/prisma/zod
|
||||
apps/web/public/embed
|
||||
packages/ui/components/icon/dynamicIconImports.tsx
|
||||
|
||||
@@ -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
@@ -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,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({
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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],
|
||||
|
||||
@@ -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`;
|
||||
|
||||
@@ -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.");
|
||||
}
|
||||
|
||||
+15
@@ -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}`)
|
||||
|
||||
+23
-1
@@ -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(
|
||||
|
||||
+6
-4
@@ -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);
|
||||
|
||||
|
||||
@@ -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");
|
||||
}
|
||||
|
||||
@@ -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 });
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
) : (
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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") : ""}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
+6
-5
@@ -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={() => {
|
||||
|
||||
+3
-4
@@ -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">
|
||||
|
||||
@@ -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" : ""}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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"
|
||||
/>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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 +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")}
|
||||
/>
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user