Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c8b577fb7a | ||
|
|
7f97d27c20 | ||
|
|
9ff10877e7 | ||
|
|
eb0768917c | ||
|
|
ec65cd6333 | ||
|
|
563141b295 | ||
|
|
7b24972900 | ||
|
|
6d2ec71df3 | ||
|
|
492304fe1d | ||
|
|
c7c5cdf133 |
@@ -392,7 +392,37 @@ Currently Vercel Pro Plan is required to be able to Deploy this application with
|
||||
|
||||
See the [roadmap project](https://cal.com/roadmap) for a list of proposed features (and known issues). You can change the view to see planned tagged releases.
|
||||
|
||||
<!-- RORADMAP -->
|
||||
<!-- LICENSE -->
|
||||
|
||||
## License
|
||||
|
||||
Cal.com, Inc. is a commercial open source company, which means some parts of this open source repository require a commercial license. The concept is called "Open Core" where the core technology (99%) is fully open source, licensed under [AGPLv3](https://opensource.org/license/agpl-v3) and the last 1% is covered under a commercial license (["/ee" Enterprise Edition](https://github.com/calcom/cal.com/tree/main/packages/features/ee)) which we believe is entirely relevant for larger organisations that require enterprise features. Enterprise features are built by the core engineering team of Cal.com, Inc. which is hired in full-time. Find their compensation on https://cal.com/open.
|
||||
|
||||
> [!NOTE]
|
||||
> Our philosophy is simple, all "Singleplayer APIs" are open-source under AGPLv3. All commercial "Multiplayer APIs" are under a commercial license.
|
||||
|
||||
| | AGPLv3 | EE |
|
||||
|---|---|---|
|
||||
| Self-host for commercial purposes | ✅ | ✅ |
|
||||
| Clone privately | ✅ | ✅ |
|
||||
| Fork publicly | ✅ | ✅ |
|
||||
| Requires CLA | ✅ | ✅ |
|
||||
| Official Support| ❌ | ✅ |
|
||||
| Derivative work privately | ❌ | ✅ |
|
||||
| SSO | ❌ | ✅ |
|
||||
| Admin Panel | ❌ | ✅ |
|
||||
| Impersonation | ❌ | ✅ |
|
||||
| Managed Event Types | ❌ | ✅ |
|
||||
| Organizations | ❌ | ✅ |
|
||||
| Payments | ❌ | ✅ |
|
||||
| Platform | ❌ | ✅ |
|
||||
| Teams | ❌ | ✅ |
|
||||
| Users | ❌ | ✅ |
|
||||
| Video | ❌ | ✅ |
|
||||
| Workflows | ❌ | ✅ |
|
||||
|
||||
> [!TIP]
|
||||
> We work closely with the community and always invite feedback about what should be open and what is fine to be commercial. This list is not set and stone and we have moved things from commercial to open in the past. Please open a [discussion](https://github.com/calcom/cal.com/discussions) if you feel like something is wrong.
|
||||
|
||||
## Repo Activity
|
||||
|
||||
|
||||
+1
-17
@@ -21,8 +21,6 @@ import {
|
||||
Query,
|
||||
} from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsNumber, Min, Max, IsOptional } from "class-validator";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import {
|
||||
@@ -34,21 +32,7 @@ import {
|
||||
UpdateScheduleInput_2024_06_11,
|
||||
UpdateScheduleOutput_2024_06_11,
|
||||
} from "@calcom/platform-types";
|
||||
|
||||
class SkipTakePagination {
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@Max(250)
|
||||
@IsOptional()
|
||||
take?: number;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@IsOptional()
|
||||
skip?: number;
|
||||
}
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
|
||||
@Controller({
|
||||
path: "/v2/organizations/:orgId",
|
||||
|
||||
+17
-8
@@ -6,7 +6,11 @@ import { IsOrgGuard } from "@/modules/auth/guards/organizations/is-org.guard";
|
||||
import { RolesGuard } from "@/modules/auth/guards/roles/roles.guard";
|
||||
import { IsTeamInOrg } from "@/modules/auth/guards/teams/is-team-in-org.guard";
|
||||
import { CreateOrgTeamDto } from "@/modules/organizations/inputs/create-organization-team.input";
|
||||
import { OrgTeamOutputDto } from "@/modules/organizations/outputs/organization-team.output";
|
||||
import {
|
||||
OrgTeamOutputDto,
|
||||
OrgTeamOutputResponseDto,
|
||||
OrgTeamsOutputResponseDto,
|
||||
} from "@/modules/organizations/outputs/organization-team.output";
|
||||
import { OrganizationsTeamsService } from "@/modules/organizations/services/organizations-teams.service";
|
||||
import {
|
||||
Controller,
|
||||
@@ -20,11 +24,11 @@ import {
|
||||
Post,
|
||||
Body,
|
||||
} from "@nestjs/common";
|
||||
import { ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { ApiOperation, ApiTags as DocsTags } from "@nestjs/swagger";
|
||||
import { plainToClass } from "class-transformer";
|
||||
|
||||
import { SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
import { ApiResponse, SkipTakePagination } from "@calcom/platform-types";
|
||||
import { SkipTakePagination } from "@calcom/platform-types";
|
||||
import { Team } from "@calcom/prisma/client";
|
||||
|
||||
@Controller({
|
||||
@@ -37,12 +41,13 @@ export class OrganizationsTeamsController {
|
||||
constructor(private organizationsTeamsService: OrganizationsTeamsService) {}
|
||||
|
||||
@Get()
|
||||
@ApiOperation({ summary: "Get all the teams of an organization." })
|
||||
@UseGuards()
|
||||
@Roles("ORG_ADMIN")
|
||||
async getAllTeams(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Query() queryParams: SkipTakePagination
|
||||
): Promise<ApiResponse<OrgTeamOutputDto[]>> {
|
||||
): Promise<OrgTeamsOutputResponseDto> {
|
||||
const { skip, take } = queryParams;
|
||||
const teams = await this.organizationsTeamsService.getPaginatedOrgTeams(orgId, skip ?? 0, take ?? 250);
|
||||
return {
|
||||
@@ -54,7 +59,8 @@ export class OrganizationsTeamsController {
|
||||
@UseGuards(IsTeamInOrg)
|
||||
@Roles("TEAM_ADMIN")
|
||||
@Get("/:teamId")
|
||||
async getTeam(@GetTeam() team: Team): Promise<ApiResponse<OrgTeamOutputDto>> {
|
||||
@ApiOperation({ summary: "Get a team of the organization by ID." })
|
||||
async getTeam(@GetTeam() team: Team): Promise<OrgTeamOutputResponseDto> {
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
data: plainToClass(OrgTeamOutputDto, team, { strategy: "excludeAll" }),
|
||||
@@ -64,10 +70,11 @@ export class OrganizationsTeamsController {
|
||||
@UseGuards(IsTeamInOrg)
|
||||
@Roles("ORG_ADMIN")
|
||||
@Delete("/:teamId")
|
||||
@ApiOperation({ summary: "Delete a team of the organization by ID." })
|
||||
async deleteTeam(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Param("teamId", ParseIntPipe) teamId: number
|
||||
): Promise<ApiResponse<OrgTeamOutputDto>> {
|
||||
): Promise<OrgTeamOutputResponseDto> {
|
||||
const team = await this.organizationsTeamsService.deleteOrgTeam(orgId, teamId);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
@@ -78,11 +85,12 @@ export class OrganizationsTeamsController {
|
||||
@UseGuards(IsTeamInOrg)
|
||||
@Roles("ORG_ADMIN")
|
||||
@Patch("/:teamId")
|
||||
@ApiOperation({ summary: "Update a team of the organization by ID." })
|
||||
async updateTeam(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Param("teamId", ParseIntPipe) teamId: number,
|
||||
@Body() body: CreateOrgTeamDto
|
||||
): Promise<ApiResponse<OrgTeamOutputDto>> {
|
||||
): Promise<OrgTeamOutputResponseDto> {
|
||||
const team = await this.organizationsTeamsService.updateOrgTeam(orgId, teamId, body);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
@@ -93,10 +101,11 @@ export class OrganizationsTeamsController {
|
||||
@Post()
|
||||
@UseGuards()
|
||||
@Roles("ORG_ADMIN")
|
||||
@ApiOperation({ summary: "Create a team for an organization." })
|
||||
async createTeam(
|
||||
@Param("orgId", ParseIntPipe) orgId: number,
|
||||
@Body() body: CreateOrgTeamDto
|
||||
): Promise<ApiResponse<OrgTeamOutputDto>> {
|
||||
): Promise<OrgTeamOutputResponseDto> {
|
||||
const team = await this.organizationsTeamsService.createOrgTeam(orgId, body);
|
||||
return {
|
||||
status: SUCCESS_STATUS,
|
||||
|
||||
@@ -1,5 +1,17 @@
|
||||
import { Expose } from "class-transformer";
|
||||
import { IsBoolean, IsInt, IsOptional, IsString, IsUrl, Length } from "class-validator";
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Expose, Type } from "class-transformer";
|
||||
import {
|
||||
IsBoolean,
|
||||
IsEnum,
|
||||
IsInt,
|
||||
IsOptional,
|
||||
IsString,
|
||||
IsUrl,
|
||||
Length,
|
||||
ValidateNested,
|
||||
} from "class-validator";
|
||||
|
||||
import { ERROR_STATUS, SUCCESS_STATUS } from "@calcom/platform-constants";
|
||||
|
||||
export class OrgTeamOutputDto {
|
||||
@IsInt()
|
||||
@@ -105,3 +117,25 @@ export class OrgTeamOutputDto {
|
||||
@Expose()
|
||||
readonly weekStart?: string = "Sunday";
|
||||
}
|
||||
|
||||
export class OrgTeamsOutputResponseDto {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => OrgTeamOutputDto)
|
||||
data!: OrgTeamOutputDto[];
|
||||
}
|
||||
|
||||
export class OrgTeamOutputResponseDto {
|
||||
@ApiProperty({ example: SUCCESS_STATUS, enum: [SUCCESS_STATUS, ERROR_STATUS] })
|
||||
@IsEnum([SUCCESS_STATUS, ERROR_STATUS])
|
||||
status!: typeof SUCCESS_STATUS | typeof ERROR_STATUS;
|
||||
|
||||
@Expose()
|
||||
@ValidateNested()
|
||||
@Type(() => OrgTeamOutputDto)
|
||||
data!: OrgTeamOutputDto;
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The number of items to return",
|
||||
"example": 10,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -417,6 +427,16 @@
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "limit",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The number of items to return",
|
||||
"example": 10,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -809,6 +829,7 @@
|
||||
"/v2/organizations/{orgId}/teams": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsController_getAllTeams",
|
||||
"summary": "Get all the teams of an organization.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orgId",
|
||||
@@ -817,6 +838,26 @@
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The number of items to return",
|
||||
"example": 10,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The number of items to skip",
|
||||
"example": 0,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -825,7 +866,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/OrgTeamsOutputResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -837,6 +878,7 @@
|
||||
},
|
||||
"post": {
|
||||
"operationId": "OrganizationsTeamsController_createTeam",
|
||||
"summary": "Create a team for an organization.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orgId",
|
||||
@@ -863,7 +905,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -877,6 +919,7 @@
|
||||
"/v2/organizations/{orgId}/teams/{teamId}": {
|
||||
"get": {
|
||||
"operationId": "OrganizationsTeamsController_getTeam",
|
||||
"summary": "Get a team of the organization by ID.",
|
||||
"parameters": [],
|
||||
"responses": {
|
||||
"200": {
|
||||
@@ -884,7 +927,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -896,6 +939,7 @@
|
||||
},
|
||||
"delete": {
|
||||
"operationId": "OrganizationsTeamsController_deleteTeam",
|
||||
"summary": "Delete a team of the organization by ID.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orgId",
|
||||
@@ -920,7 +964,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -932,6 +976,7 @@
|
||||
},
|
||||
"patch": {
|
||||
"operationId": "OrganizationsTeamsController_updateTeam",
|
||||
"summary": "Update a team of the organization by ID.",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "orgId",
|
||||
@@ -966,7 +1011,7 @@
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"type": "object"
|
||||
"$ref": "#/components/schemas/OrgTeamOutputResponseDto"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -988,6 +1033,26 @@
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "take",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The number of items to return",
|
||||
"example": 10,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
{
|
||||
"name": "skip",
|
||||
"required": false,
|
||||
"in": "query",
|
||||
"description": "The number of items to skip",
|
||||
"example": 0,
|
||||
"schema": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
],
|
||||
"responses": {
|
||||
@@ -1902,6 +1967,46 @@
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/bookings/{bookingUid}/mark-no-show": {
|
||||
"post": {
|
||||
"operationId": "BookingsController_markNoShow",
|
||||
"parameters": [
|
||||
{
|
||||
"name": "bookingUid",
|
||||
"required": true,
|
||||
"in": "path",
|
||||
"schema": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
],
|
||||
"requestBody": {
|
||||
"required": true,
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MarkNoShowInput"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"responses": {
|
||||
"201": {
|
||||
"description": "",
|
||||
"content": {
|
||||
"application/json": {
|
||||
"schema": {
|
||||
"$ref": "#/components/schemas/MarkNoShowOutput"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"tags": [
|
||||
"Bookings"
|
||||
]
|
||||
}
|
||||
},
|
||||
"/v2/bookings/recurring": {
|
||||
"post": {
|
||||
"operationId": "BookingsController_createRecurringBooking",
|
||||
@@ -3700,6 +3805,125 @@
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"OrgTeamOutputDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "number"
|
||||
},
|
||||
"parentId": {
|
||||
"type": "number"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"slug": {
|
||||
"type": "string"
|
||||
},
|
||||
"logoUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"calVideoLogo": {
|
||||
"type": "string"
|
||||
},
|
||||
"appLogo": {
|
||||
"type": "string"
|
||||
},
|
||||
"appIconLogo": {
|
||||
"type": "string"
|
||||
},
|
||||
"bio": {
|
||||
"type": "string"
|
||||
},
|
||||
"hideBranding": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isOrganization": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"isPrivate": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hideBookATeamMember": {
|
||||
"type": "boolean",
|
||||
"default": false
|
||||
},
|
||||
"metadata": {
|
||||
"type": "string"
|
||||
},
|
||||
"theme": {
|
||||
"type": "string"
|
||||
},
|
||||
"brandColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"darkBrandColor": {
|
||||
"type": "string"
|
||||
},
|
||||
"bannerUrl": {
|
||||
"type": "string"
|
||||
},
|
||||
"timeFormat": {
|
||||
"type": "number"
|
||||
},
|
||||
"timeZone": {
|
||||
"type": "string",
|
||||
"default": "Europe/London"
|
||||
},
|
||||
"weekStart": {
|
||||
"type": "string",
|
||||
"default": "Sunday"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"id",
|
||||
"name"
|
||||
]
|
||||
},
|
||||
"OrgTeamsOutputResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/OrgTeamOutputDto"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"OrgTeamOutputResponseDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/OrgTeamOutputDto"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"CreateOrgTeamDto": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
@@ -5461,6 +5685,63 @@
|
||||
"seatReferenceUid"
|
||||
]
|
||||
},
|
||||
"MarkNoShowInput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"noShowHost": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"attendees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Attendee"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"HandleMarkNoShowData": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"message": {
|
||||
"type": "string"
|
||||
},
|
||||
"noShowHost": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"messageKey": {
|
||||
"type": "string"
|
||||
},
|
||||
"attendees": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/components/schemas/Attendee"
|
||||
}
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"message"
|
||||
]
|
||||
},
|
||||
"MarkNoShowOutput": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"status": {
|
||||
"type": "string",
|
||||
"example": "success",
|
||||
"enum": [
|
||||
"success",
|
||||
"error"
|
||||
]
|
||||
},
|
||||
"data": {
|
||||
"$ref": "#/components/schemas/HandleMarkNoShowData"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"status",
|
||||
"data"
|
||||
]
|
||||
},
|
||||
"ReserveSlotInput": {
|
||||
"type": "object",
|
||||
"properties": {}
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { withAppDirSsr } from "app/WithAppDirSsr";
|
||||
import { _generateMetadata } from "app/_utils";
|
||||
import { WithLayout } from "app/layoutHOC";
|
||||
|
||||
import { getServerSideProps } from "@lib/event-types/getServerSideProps";
|
||||
|
||||
import EventTypes from "~/event-types/views/event-types-listing-view";
|
||||
|
||||
export const generateMetadata = async () =>
|
||||
@@ -9,4 +12,6 @@ export const generateMetadata = async () =>
|
||||
(t) => t("event_types_page_subtitle")
|
||||
);
|
||||
|
||||
export default WithLayout({ getLayout: null, Page: EventTypes })<"P">;
|
||||
const getData = withAppDirSsr(getServerSideProps);
|
||||
|
||||
export default WithLayout({ getLayout: null, getData, Page: EventTypes })<"P">;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@calcom/web",
|
||||
"version": "4.2.5",
|
||||
"version": "4.3.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"analyze": "ANALYZE=true next build",
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
**/
|
||||
import { appKeysSchema as alby_zod_ts } from "./alby/zod";
|
||||
import { appKeysSchema as basecamp3_zod_ts } from "./basecamp3/zod";
|
||||
import { appKeysSchema as campsite_zod_ts } from "./campsite/zod";
|
||||
import { appKeysSchema as closecom_zod_ts } from "./closecom/zod";
|
||||
import { appKeysSchema as dailyvideo_zod_ts } from "./dailyvideo/zod";
|
||||
import { appKeysSchema as fathom_zod_ts } from "./fathom/zod";
|
||||
@@ -50,6 +51,7 @@ import { appKeysSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
|
||||
export const appKeysSchemas = {
|
||||
alby: alby_zod_ts,
|
||||
basecamp3: basecamp3_zod_ts,
|
||||
campsite: campsite_zod_ts,
|
||||
closecom: closecom_zod_ts,
|
||||
dailyvideo: dailyvideo_zod_ts,
|
||||
fathom: fathom_zod_ts,
|
||||
|
||||
@@ -12,6 +12,7 @@ import basecamp3_config_json from "./basecamp3/config.json";
|
||||
import cal_ai_config_json from "./cal-ai/config.json";
|
||||
import { metadata as caldavcalendar__metadata_ts } from "./caldavcalendar/_metadata";
|
||||
import campfire_config_json from "./campfire/config.json";
|
||||
import campsite_config_json from "./campsite/config.json";
|
||||
import clic_config_json from "./clic/config.json";
|
||||
import closecom_config_json from "./closecom/config.json";
|
||||
import cron_config_json from "./cron/config.json";
|
||||
@@ -103,6 +104,7 @@ export const appStoreMetadata = {
|
||||
"cal-ai": cal_ai_config_json,
|
||||
caldavcalendar: caldavcalendar__metadata_ts,
|
||||
campfire: campfire_config_json,
|
||||
campsite: campsite_config_json,
|
||||
clic: clic_config_json,
|
||||
closecom: closecom_config_json,
|
||||
cron: cron_config_json,
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
**/
|
||||
import { appDataSchema as alby_zod_ts } from "./alby/zod";
|
||||
import { appDataSchema as basecamp3_zod_ts } from "./basecamp3/zod";
|
||||
import { appDataSchema as campsite_zod_ts } from "./campsite/zod";
|
||||
import { appDataSchema as closecom_zod_ts } from "./closecom/zod";
|
||||
import { appDataSchema as dailyvideo_zod_ts } from "./dailyvideo/zod";
|
||||
import { appDataSchema as fathom_zod_ts } from "./fathom/zod";
|
||||
@@ -50,6 +51,7 @@ import { appDataSchema as zoomvideo_zod_ts } from "./zoomvideo/zod";
|
||||
export const appDataSchemas = {
|
||||
alby: alby_zod_ts,
|
||||
basecamp3: basecamp3_zod_ts,
|
||||
campsite: campsite_zod_ts,
|
||||
closecom: closecom_zod_ts,
|
||||
dailyvideo: dailyvideo_zod_ts,
|
||||
fathom: fathom_zod_ts,
|
||||
|
||||
@@ -13,6 +13,7 @@ export const apiHandlers = {
|
||||
"cal-ai": import("./cal-ai/api"),
|
||||
caldavcalendar: import("./caldavcalendar/api"),
|
||||
campfire: import("./campfire/api"),
|
||||
campsite: import("./campsite/api"),
|
||||
clic: import("./clic/api"),
|
||||
closecom: import("./closecom/api"),
|
||||
cron: import("./cron/api"),
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
**/
|
||||
import around_config_json from "./around/config.json";
|
||||
import campfire_config_json from "./campfire/config.json";
|
||||
import campsite_config_json from "./campsite/config.json";
|
||||
import { metadata as dailyvideo__metadata_ts } from "./dailyvideo/_metadata";
|
||||
import demodesk_config_json from "./demodesk/config.json";
|
||||
import discord_config_json from "./discord/config.json";
|
||||
@@ -45,6 +46,7 @@ import { metadata as zoomvideo__metadata_ts } from "./zoomvideo/_metadata";
|
||||
export const appStoreMetadata = {
|
||||
around: around_config_json,
|
||||
campfire: campfire_config_json,
|
||||
campsite: campsite_config_json,
|
||||
dailyvideo: dailyvideo__metadata_ts,
|
||||
demodesk: demodesk_config_json,
|
||||
discord: discord_config_json,
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
items:
|
||||
- 1.png
|
||||
- 2.png
|
||||
- 3.png
|
||||
---
|
||||
|
||||
{DESCRIPTION}
|
||||
@@ -0,0 +1,45 @@
|
||||
import type { NextApiRequest } from "next";
|
||||
import { stringify } from "querystring";
|
||||
import { z } from "zod";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import { encodeOAuthState } from "../../_utils/oauth/encodeOAuthState";
|
||||
|
||||
const campsiteAppKeysSchema = z.object({
|
||||
client_id: z.string(),
|
||||
client_secret: z.string(),
|
||||
});
|
||||
|
||||
export const getCampsiteAppKeys = async () => {
|
||||
const appKeys = await getAppKeysFromSlug("campsite");
|
||||
return campsiteAppKeysSchema.parse(appKeys);
|
||||
};
|
||||
|
||||
async function handler(req: NextApiRequest) {
|
||||
// Get user
|
||||
const user = req?.session?.user;
|
||||
if (!user) {
|
||||
return { status: 401, body: { error: "Unauthorized" } };
|
||||
}
|
||||
|
||||
const { client_id } = await getCampsiteAppKeys();
|
||||
const state = encodeOAuthState(req);
|
||||
|
||||
const params = {
|
||||
response_type: "code",
|
||||
client_id,
|
||||
redirect_uri: `${WEBAPP_URL}/api/integrations/campsite/callback`,
|
||||
state,
|
||||
scope: "read_user write_call_room",
|
||||
};
|
||||
const query = stringify(params);
|
||||
const url = `https://auth.campsite.co/oauth/authorize?${query}`;
|
||||
return { url };
|
||||
}
|
||||
|
||||
export default defaultHandler({
|
||||
GET: Promise.resolve({ default: defaultResponder(handler) }),
|
||||
});
|
||||
@@ -0,0 +1,73 @@
|
||||
import type { NextApiRequest, NextApiResponse } from "next";
|
||||
|
||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||
import { getSafeRedirectUrl } from "@calcom/lib/getSafeRedirectUrl";
|
||||
import prisma from "@calcom/prisma";
|
||||
|
||||
import getAppKeysFromSlug from "../../_utils/getAppKeysFromSlug";
|
||||
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
|
||||
import createOAuthAppCredential from "../../_utils/oauth/createOAuthAppCredential";
|
||||
import { decodeOAuthState } from "../../_utils/oauth/decodeOAuthState";
|
||||
|
||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||
const state = decodeOAuthState(req);
|
||||
const userId = req.session?.user.id;
|
||||
if (!userId) {
|
||||
return res.status(404).json({ message: "No user found" });
|
||||
}
|
||||
const { code } = req.query;
|
||||
const { client_id, client_secret } = await getAppKeysFromSlug("campsite");
|
||||
|
||||
const result = await fetch(`https://auth.campsite.co/oauth/token`, {
|
||||
method: "POST",
|
||||
body: JSON.stringify({
|
||||
grant_type: "authorization_code",
|
||||
code,
|
||||
client_id,
|
||||
client_secret,
|
||||
redirect_uri: `${WEBAPP_URL}/api/integrations/campsite/callback`,
|
||||
}),
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
|
||||
if (result.status !== 200) {
|
||||
let errorMessage = "Something is wrong with the Campsite API";
|
||||
try {
|
||||
const responseBody = await result.json();
|
||||
errorMessage = responseBody.error;
|
||||
} catch (e) {}
|
||||
|
||||
res.status(400).json({ message: errorMessage });
|
||||
return;
|
||||
}
|
||||
|
||||
const responseBody = await result.json();
|
||||
if (responseBody.error) {
|
||||
res.status(400).json({ message: responseBody.error });
|
||||
return;
|
||||
}
|
||||
|
||||
/**
|
||||
* With this we take care of no duplicate Campsite keys for a single user
|
||||
* when creating a room using deleteMany if there is already a Campsite key
|
||||
* */
|
||||
await prisma.credential.deleteMany({
|
||||
where: {
|
||||
type: "campsite_conferencing",
|
||||
userId,
|
||||
appId: "campsite",
|
||||
},
|
||||
});
|
||||
|
||||
await createOAuthAppCredential(
|
||||
{ appId: "campsite", type: "campsite_conferencing" },
|
||||
{ access_token: responseBody.access_token },
|
||||
req
|
||||
);
|
||||
|
||||
res.redirect(
|
||||
getSafeRedirectUrl(state?.returnTo) ?? getInstalledAppPath({ variant: "conferencing", slug: "campsite" })
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { default as add } from "./add";
|
||||
export { default as callback } from "./callback";
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"/*": "Don't modify slug - If required, do it using cli edit command",
|
||||
"name": "Campsite",
|
||||
"slug": "campsite",
|
||||
"type": "campsite_conferencing",
|
||||
"logo": "icon.svg",
|
||||
"url": "https://campsite.co",
|
||||
"variant": "conferencing",
|
||||
"categories": ["conferencing"],
|
||||
"publisher": "Campsite",
|
||||
"email": "support@campsite.co",
|
||||
"appData": {
|
||||
"location": {
|
||||
"type": "integrations:{SLUG}_conferencing",
|
||||
"label": "{TITLE}",
|
||||
"linkType": "dynamic"
|
||||
}
|
||||
},
|
||||
"description": "Campsite is an all-in-one communication app that helps async teams move faster. Posts, docs, calls, and chat in one place — finally. Everything is searchable and easy to find.",
|
||||
"isTemplate": false,
|
||||
"__createdUsingCli": true,
|
||||
"__template": "event-type-location-video-static"
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * as api from "./api";
|
||||
export * as lib from "./lib";
|
||||
@@ -0,0 +1,47 @@
|
||||
import type { CalendarEvent, EventBusyDate } from "@calcom/types/Calendar";
|
||||
import type { CredentialPayload } from "@calcom/types/Credential";
|
||||
import type { PartialReference } from "@calcom/types/EventManager";
|
||||
import type { VideoApiAdapter, VideoCallData } from "@calcom/types/VideoApiAdapter";
|
||||
|
||||
type CampsiteToken = {
|
||||
access_token: string;
|
||||
};
|
||||
const CampsiteVideoApiAdapter = (credential: CredentialPayload): VideoApiAdapter => {
|
||||
return {
|
||||
createMeeting: async (_event: CalendarEvent): Promise<VideoCallData> => {
|
||||
const keys = credential.key as CampsiteToken;
|
||||
const { access_token } = keys;
|
||||
const response = await fetch("https://api.campsite.co/v1/integrations/cal_dot_com/call_rooms", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
});
|
||||
const callRoom = await response.json();
|
||||
|
||||
return {
|
||||
type: "campsite_conferencing",
|
||||
id: callRoom.id,
|
||||
password: "",
|
||||
url: callRoom.url,
|
||||
};
|
||||
},
|
||||
updateMeeting: async (bookingRef: PartialReference, _event: CalendarEvent): Promise<VideoCallData> => {
|
||||
return {
|
||||
type: "campsite_conferencing",
|
||||
id: bookingRef.externalCalendarId ? bookingRef.externalCalendarId : "",
|
||||
password: "",
|
||||
url: bookingRef.meetingUrl ? bookingRef.meetingUrl : "",
|
||||
};
|
||||
},
|
||||
deleteMeeting: async (_uid: string): Promise<unknown> => {
|
||||
return {};
|
||||
},
|
||||
getAvailability: async (_dateFrom?: string, _dateTo?: string): Promise<EventBusyDate[]> => {
|
||||
return [];
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
export default CampsiteVideoApiAdapter;
|
||||
@@ -0,0 +1 @@
|
||||
export { default as VideoApiAdapter } from "./VideoApiAdapter";
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"$schema": "https://json.schemastore.org/package.json",
|
||||
"private": true,
|
||||
"name": "@calcom/campsite",
|
||||
"version": "0.0.0",
|
||||
"main": "./index.ts",
|
||||
"dependencies": {
|
||||
"@calcom/lib": "*"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@calcom/types": "*"
|
||||
},
|
||||
"description": "Campsite is an all-in-one communication app that helps async teams move faster. Posts, docs, calls, and chat in one place — finally. Everything is searchable and easy to find."
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 1.9 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.0 MiB |
Binary file not shown.
|
After Width: | Height: | Size: 1.0 MiB |
@@ -0,0 +1,157 @@
|
||||
<svg width="64" height="64" viewBox="0 0 64 64" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_2575_4550)">
|
||||
<g filter="url(#filter0_di_2575_4550)">
|
||||
<path
|
||||
d="M48 0H16C7.16344 0 0 7.16344 0 16V48C0 56.8366 7.16344 64 16 64H48C56.8366 64 64 56.8366 64 48V16C64 7.16344 56.8366 0 48 0Z"
|
||||
fill="url(#paint0_radial_2575_4550)"
|
||||
/>
|
||||
</g>
|
||||
<g filter="url(#filter1_dddii_2575_4550)">
|
||||
<path
|
||||
d="M9.52012 41.9532L22.0953 20.6545C22.3348 20.2489 22.7709 19.9999 23.2421 19.9999H43.7279C44.7587 19.9999 45.3987 21.1203 44.8747 22.0077L32.2996 43.3064C32.0601 43.7121 31.624 43.961 31.1528 43.961H10.667C9.63615 43.961 8.99619 42.8406 9.52012 41.9532Z"
|
||||
fill="url(#paint1_linear_2575_4550)"
|
||||
fill-opacity="0.96"
|
||||
/>
|
||||
<path
|
||||
d="M38.9673 43.9609H53.2769C54.2811 43.9609 54.924 42.8921 54.4531 42.0055L47.6958 29.2815C47.2086 28.3639 45.9046 28.333 45.3743 29.2264L37.8221 41.9505C37.2954 42.8378 37.9352 43.9609 38.9673 43.9609Z"
|
||||
fill="url(#paint2_linear_2575_4550)"
|
||||
fill-opacity="0.96"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter0_di_2575_4550"
|
||||
x="-40"
|
||||
y="0"
|
||||
width="144"
|
||||
height="144"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="40" />
|
||||
<feGaussianBlur stdDeviation="20" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2575_4550" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2575_4550" result="shape" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="16" />
|
||||
<feGaussianBlur stdDeviation="8" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.25 0" />
|
||||
<feBlend mode="normal" in2="shape" result="effect2_innerShadow_2575_4550" />
|
||||
</filter>
|
||||
<filter
|
||||
id="filter1_dddii_2575_4550"
|
||||
x="1.33325"
|
||||
y="15.9999"
|
||||
width="61.2772"
|
||||
height="39.9611"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="1" />
|
||||
<feGaussianBlur stdDeviation="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.733333 0 0 0 0 0.0745098 0 0 0 0 0.0745098 0 0 0 0.316416 0" />
|
||||
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2575_4550" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="2" />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.733333 0 0 0 0 0.0745098 0 0 0 0 0.0745098 0 0 0 0.358691 0" />
|
||||
<feBlend mode="normal" in2="effect1_dropShadow_2575_4550" result="effect2_dropShadow_2575_4550" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="4" />
|
||||
<feGaussianBlur stdDeviation="4" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 0.733333 0 0 0 0 0.0745098 0 0 0 0 0.0745098 0 0 0 0.31 0" />
|
||||
<feBlend mode="normal" in2="effect2_dropShadow_2575_4550" result="effect3_dropShadow_2575_4550" />
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="effect3_dropShadow_2575_4550" result="shape" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset dy="-2" />
|
||||
<feGaussianBlur stdDeviation="12" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 0.4 0" />
|
||||
<feBlend mode="normal" in2="shape" result="effect4_innerShadow_2575_4550" />
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
type="matrix"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
result="hardAlpha"
|
||||
/>
|
||||
<feOffset />
|
||||
<feGaussianBlur stdDeviation="2" />
|
||||
<feComposite in2="hardAlpha" operator="arithmetic" k2="-1" k3="1" />
|
||||
<feColorMatrix type="matrix" values="0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0 0 0 1 0" />
|
||||
<feBlend mode="normal" in2="effect4_innerShadow_2575_4550" result="effect5_innerShadow_2575_4550" />
|
||||
</filter>
|
||||
<radialGradient
|
||||
id="paint0_radial_2575_4550"
|
||||
cx="0"
|
||||
cy="0"
|
||||
r="1"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
gradientTransform="translate(13.5534 65.4757) rotate(-74.2658) scale(68.0246 308.191)"
|
||||
>
|
||||
<stop stop-color="#FA4438" />
|
||||
<stop offset="1" stop-color="#FF6D23" />
|
||||
</radialGradient>
|
||||
<linearGradient
|
||||
id="paint1_linear_2575_4550"
|
||||
x1="46.122"
|
||||
y1="28.5745"
|
||||
x2="46.122"
|
||||
y2="43.9609"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.86" />
|
||||
</linearGradient>
|
||||
<linearGradient
|
||||
id="paint2_linear_2575_4550"
|
||||
x1="46.122"
|
||||
y1="28.5745"
|
||||
x2="46.122"
|
||||
y2="43.9609"
|
||||
gradientUnits="userSpaceOnUse"
|
||||
>
|
||||
<stop stop-color="white" />
|
||||
<stop offset="1" stop-color="white" stop-opacity="0.86" />
|
||||
</linearGradient>
|
||||
<clipPath id="clip0_2575_4550">
|
||||
<rect width="64" height="64" rx="16" fill="white" />
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 6.0 KiB |
@@ -0,0 +1,8 @@
|
||||
import { z } from "zod";
|
||||
|
||||
export const appDataSchema = z.object({});
|
||||
|
||||
export const appKeysSchema = z.object({
|
||||
client_id: z.string().min(1),
|
||||
client_secret: z.string().min(1),
|
||||
});
|
||||
@@ -4,6 +4,7 @@ const appStore = {
|
||||
applecalendar: () => import("./applecalendar"),
|
||||
aroundvideo: () => import("./around"),
|
||||
caldavcalendar: () => import("./caldavcalendar"),
|
||||
campsiteconferencing: () => import("./campsite"),
|
||||
closecom: () => import("./closecom"),
|
||||
dailyvideo: () => import("./dailyvideo"),
|
||||
googlecalendar: () => import("./googlecalendar"),
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { LRUCache } from "lru-cache";
|
||||
import * as lruCache from "lru-cache";
|
||||
import type { GetServerSidePropsContext, NextApiRequest, NextApiResponse } from "next";
|
||||
import type { AuthOptions, Session } from "next-auth";
|
||||
import { getToken } from "next-auth/jwt";
|
||||
@@ -15,7 +15,7 @@ const log = logger.getSubLogger({ prefix: ["getServerSession"] });
|
||||
* Stores the session in memory using the stringified token as the key.
|
||||
*
|
||||
*/
|
||||
const CACHE = new LRUCache<string, Session>({ max: 1000 });
|
||||
const CACHE = new lruCache.LRUCache<string, Session>({ max: 1000 });
|
||||
|
||||
/**
|
||||
* This is a slimmed down version of the `getServerSession` function from
|
||||
|
||||
@@ -93,7 +93,7 @@ export const useBookings = ({ event, hashedLink, bookingForm, metadata, teamMemb
|
||||
function refactorMeWithoutEffect() {
|
||||
const data = _instantBooking.data;
|
||||
|
||||
if (!data) return;
|
||||
if (!data || !data.booking) return;
|
||||
try {
|
||||
const locationVideoCallUrl: string | undefined = bookingMetadataSchema.parse(
|
||||
data.booking?.metadata || {}
|
||||
|
||||
@@ -2,6 +2,7 @@ import { useState } from "react";
|
||||
import type { UseFormReturn } from "react-hook-form";
|
||||
|
||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||
import { TimeUnit } from "@calcom/prisma/enums";
|
||||
import {
|
||||
Dropdown,
|
||||
DropdownItem,
|
||||
@@ -12,21 +13,50 @@ import {
|
||||
TextField,
|
||||
} from "@calcom/ui";
|
||||
|
||||
import { getWorkflowTimeUnitOptions } from "../lib/getOptions";
|
||||
import type { FormValues } from "../pages/workflow";
|
||||
|
||||
const TIME_UNITS = [TimeUnit.DAY, TimeUnit.HOUR, TimeUnit.MINUTE] as const;
|
||||
|
||||
type Props = {
|
||||
form: UseFormReturn<FormValues>;
|
||||
disabled: boolean;
|
||||
};
|
||||
|
||||
const TimeUnitAddonSuffix = ({
|
||||
DropdownItems,
|
||||
timeUnitOptions,
|
||||
form,
|
||||
}: {
|
||||
form: UseFormReturn<FormValues>;
|
||||
DropdownItems: JSX.Element;
|
||||
timeUnitOptions: { [x: string]: string };
|
||||
}) => {
|
||||
// because isDropdownOpen already triggers a render cycle we can use getValues()
|
||||
// instead of watch() function
|
||||
const timeUnit = form.getValues("timeUnit");
|
||||
const [isDropdownOpen, setIsDropdownOpen] = useState(false);
|
||||
return (
|
||||
<Dropdown onOpenChange={setIsDropdownOpen}>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="flex items-center">
|
||||
<div className="mr-1 w-3/5">{timeUnit ? timeUnitOptions[timeUnit] : "undefined"}</div>
|
||||
<div className="w-1/4 pt-1">
|
||||
{isDropdownOpen ? <Icon name="chevron-up" /> : <Icon name="chevron-down" />}
|
||||
</div>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>{DropdownItems}</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
);
|
||||
};
|
||||
|
||||
export const TimeTimeUnitInput = (props: Props) => {
|
||||
const { form } = props;
|
||||
const { t } = useLocale();
|
||||
const timeUnitOptions = getWorkflowTimeUnitOptions(t);
|
||||
|
||||
const [timeUnit, setTimeUnit] = useState(form.getValues("timeUnit"));
|
||||
|
||||
const timeUnitOptions = TIME_UNITS.reduce((acc, option) => {
|
||||
acc[option] = t(`${option.toLowerCase()}_timeUnit`);
|
||||
return acc;
|
||||
}, {} as { [x: string]: string });
|
||||
return (
|
||||
<div className="flex">
|
||||
<div className="grow">
|
||||
@@ -39,33 +69,26 @@ export const TimeTimeUnitInput = (props: Props) => {
|
||||
className="-mt-2 rounded-r-none text-sm focus:ring-0"
|
||||
{...form.register("time", { valueAsNumber: true })}
|
||||
addOnSuffix={
|
||||
<Dropdown>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button className="flex items-center">
|
||||
<div className="mr-1 w-3/4">
|
||||
{timeUnit ? t(`${timeUnit.toLowerCase()}_timeUnit`) : "undefined"}{" "}
|
||||
</div>
|
||||
<div className="w-1/4 pt-1">
|
||||
<Icon name="chevron-down" />
|
||||
</div>
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent>
|
||||
{timeUnitOptions.map((option, index) => (
|
||||
<DropdownMenuItem key={index} className="outline-none">
|
||||
<DropdownItem
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
setTimeUnit(option.value);
|
||||
form.setValue("timeUnit", option.value);
|
||||
}}>
|
||||
{option.label}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</Dropdown>
|
||||
<TimeUnitAddonSuffix
|
||||
form={form}
|
||||
timeUnitOptions={timeUnitOptions}
|
||||
DropdownItems={
|
||||
<>
|
||||
{TIME_UNITS.map((timeUnit, index) => (
|
||||
<DropdownMenuItem key={index} className="outline-none">
|
||||
<DropdownItem
|
||||
key={index}
|
||||
type="button"
|
||||
onClick={() => {
|
||||
form.setValue("timeUnit", timeUnit);
|
||||
}}>
|
||||
{timeUnitOptions[timeUnit]}
|
||||
</DropdownItem>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -4,7 +4,6 @@ import type { WorkflowActions } from "@calcom/prisma/enums";
|
||||
|
||||
import { isSMSOrWhatsappAction, isWhatsappAction, isEmailToAttendeeAction } from "./actionHelperFunctions";
|
||||
import {
|
||||
TIME_UNIT,
|
||||
WHATSAPP_WORKFLOW_TEMPLATES,
|
||||
WORKFLOW_ACTIONS,
|
||||
BASIC_WORKFLOW_TEMPLATES,
|
||||
@@ -32,12 +31,6 @@ export function getWorkflowTriggerOptions(t: TFunction) {
|
||||
});
|
||||
}
|
||||
|
||||
export function getWorkflowTimeUnitOptions(t: TFunction) {
|
||||
return TIME_UNIT.map((timeUnit) => {
|
||||
return { label: t(`${timeUnit.toLowerCase()}_timeUnit`), value: timeUnit };
|
||||
});
|
||||
}
|
||||
|
||||
export function getWorkflowTemplateOptions(t: TFunction, action: WorkflowActions | undefined) {
|
||||
const TEMPLATES =
|
||||
action && isWhatsappAction(action)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
VITE_BOOKER_EMBED_OAUTH_CLIENT_ID="clye3x2f6000133strp0tf85t"
|
||||
|
||||
VITE_BOOKER_EMBED_API_URL="http://localhost:5555/api/v2"
|
||||
@@ -0,0 +1,15 @@
|
||||
import type { BookerPlatformWrapperAtomProps } from "../booker/BookerPlatformWrapper";
|
||||
import { BookerPlatformWrapper } from "../booker/BookerPlatformWrapper";
|
||||
import { CalProvider } from "../cal-provider/CalProvider";
|
||||
|
||||
export const BookerEmbed = (props: BookerPlatformWrapperAtomProps) => {
|
||||
return (
|
||||
<CalProvider
|
||||
clientId={import.meta.env.VITE_BOOKER_EMBED_OAUTH_CLIENT_ID}
|
||||
options={{
|
||||
apiUrl: import.meta.env.VITE_BOOKER_EMBED_API_URL,
|
||||
}}>
|
||||
<BookerPlatformWrapper {...props} />
|
||||
</CalProvider>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1 @@
|
||||
export { BookerEmbed } from "./BookerEmbed";
|
||||
@@ -41,7 +41,7 @@ import { useMe } from "../hooks/useMe";
|
||||
import { useSlots } from "../hooks/useSlots";
|
||||
import { AtomsWrapper } from "../src/components/atoms-wrapper";
|
||||
|
||||
type BookerPlatformWrapperAtomProps = Omit<BookerProps, "username" | "entity"> & {
|
||||
export type BookerPlatformWrapperAtomProps = Omit<BookerProps, "username" | "entity"> & {
|
||||
rescheduleUid?: string;
|
||||
bookingUid?: string;
|
||||
username: string | string[];
|
||||
|
||||
@@ -14,3 +14,4 @@ export { useGetBookings } from "./hooks/useGetBookings";
|
||||
export { useMe } from "./hooks/useMe";
|
||||
export { OutlookConnect } from "./connect/outlook/OutlookConnect";
|
||||
export * as Connect from "./connect";
|
||||
export { BookerEmbed } from "./booker-embed";
|
||||
|
||||
@@ -28,6 +28,9 @@ export function Navbar({ username }: { username?: string }) {
|
||||
<li>
|
||||
<Link href="/bookings">My Bookings</Link>
|
||||
</li>
|
||||
<li>
|
||||
<Link href="/embed">Embed</Link>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
import "@/styles/globals.css";
|
||||
import type { AppProps } from "next/app";
|
||||
import { Poppins } from "next/font/google";
|
||||
import { usePathname } from "next/navigation";
|
||||
import { useRouter } from "next/navigation";
|
||||
import { useEffect, useState } from "react";
|
||||
|
||||
import { CalProvider } from "@calcom/atoms";
|
||||
import { CalProvider, BookerEmbed } from "@calcom/atoms";
|
||||
import "@calcom/atoms/globals.min.css";
|
||||
|
||||
const poppins = Poppins({ subsets: ["latin"], weight: ["400", "800"] });
|
||||
@@ -25,6 +27,8 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
const [accessToken, setAccessToken] = useState("");
|
||||
const [email, setUserEmail] = useState("");
|
||||
const [username, setUsername] = useState("");
|
||||
const router = useRouter();
|
||||
const pathname = usePathname();
|
||||
|
||||
useEffect(() => {
|
||||
const randomEmail = generateRandomEmail();
|
||||
@@ -59,6 +63,30 @@ export default function App({ Component, pageProps }: AppProps) {
|
||||
</>
|
||||
)}
|
||||
</CalProvider>{" "}
|
||||
{pathname === "/embed" && (
|
||||
<div>
|
||||
<BookerEmbed
|
||||
customClassNames={{
|
||||
bookerContainer: "!bg-[#F5F2FE] [&_button:!rounded-full] border-subtle border",
|
||||
datePickerCustomClassNames: {
|
||||
datePickerDatesActive: "!bg-[#D7CEF5]",
|
||||
},
|
||||
eventMetaCustomClassNames: {
|
||||
eventMetaTitle: "text-[#7151DC]",
|
||||
},
|
||||
availableTimeSlotsCustomClassNames: {
|
||||
availableTimeSlotsHeaderContainer: "!bg-[#F5F2FE]",
|
||||
availableTimes: "!bg-[#D7CEF5]",
|
||||
},
|
||||
}}
|
||||
username={username}
|
||||
eventSlug="thirty-minutes"
|
||||
onCreateBookingSuccess={(data) => {
|
||||
router.push(`/${data.data.uid}`);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,15 @@
|
||||
import { Navbar } from "@/components/Navbar";
|
||||
import { Inter } from "next/font/google";
|
||||
|
||||
const inter = Inter({ subsets: ["latin"] });
|
||||
|
||||
export default function Embed(props: { calUsername: string; calEmail: string }) {
|
||||
return (
|
||||
<main className={`flex ${inter.className} text-default flex flex-col`}>
|
||||
<Navbar username={props.calUsername} />
|
||||
<div>
|
||||
<h1 className="mx-8 my-4 text-2xl font-bold">This is the booker embed</h1>
|
||||
</div>
|
||||
</main>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
# How to work with platform libraries in Dev
|
||||
|
||||
whenever you want to change anything in platform libraries, or if you modify the code of one of the functions imported in libraries you will need to build the code again.
|
||||
|
||||
first thing to know is that we version this package using NPM
|
||||
|
||||
https://www.npmjs.com/package/@calcom/platform-libraries?activeTab=code
|
||||
|
||||
In order to work using the locally built platform libraries you need to:
|
||||
|
||||
- in packages/platform/libraries/package.json set the version to for example 1.2.3
|
||||
|
||||
- in apps/api/v2/package.json add to dependencies:
|
||||
"@calcom/platform-libraries-1.2.3": "npm:@calcom/platform-libraries@1.2.3"
|
||||
|
||||
- in api v2 code simply import using the new alias:
|
||||
import {
|
||||
getAllUserBookings as getAllUserBookings1.2.3,
|
||||
} from "@calcom/platform-libraries-1.2.3";
|
||||
|
||||
|
||||
since the versions are matching in both package.json yarn will try to use the locally built code
|
||||
|
||||
now go to packages/platform/libraries and do
|
||||
|
||||
- yarn build
|
||||
|
||||
then go back to /apps/api/v2 and run
|
||||
|
||||
- yarn
|
||||
|
||||
- yarn dev
|
||||
|
||||
# Before Merging to main
|
||||
- Publish Your Version of Libraries on NPM:
|
||||
- To publish, ensure you are a contributor to the platform libraries' NPM package.
|
||||
- Authenticate yourself via the CLI using npm auth.
|
||||
- Increment the version number accordingly.
|
||||
- Run yarn publish to publish your version.
|
||||
- Once it's published, change back the version in packages/platform/libraries/package.json back to 0.0.0
|
||||
- Run yarn
|
||||
- You should now be using the npm package instead of the locally built version
|
||||
@@ -1,3 +1,4 @@
|
||||
import { ApiProperty } from "@nestjs/swagger";
|
||||
import { Transform } from "class-transformer";
|
||||
import { IsNumber, Min, Max, IsOptional } from "class-validator";
|
||||
import type { Response as BaseResponse } from "express";
|
||||
@@ -41,6 +42,7 @@ export type ApiResponse<T = undefined> = T extends undefined
|
||||
export type ApiResponseMaybeRedirect<T = undefined> = ApiResponse<T> | ApiRedirectResponseType;
|
||||
|
||||
export class Pagination {
|
||||
@ApiProperty({ required: false, description: "The number of items to return", example: 10 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@@ -49,6 +51,7 @@ export class Pagination {
|
||||
limit?: number;
|
||||
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@ApiProperty({ required: false, description: "The number of items to skip", example: 0 })
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
@Max(100)
|
||||
@@ -57,6 +60,7 @@ export class Pagination {
|
||||
}
|
||||
|
||||
export class SkipTakePagination {
|
||||
@ApiProperty({ required: false, description: "The number of items to return", example: 10 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(1)
|
||||
@@ -64,6 +68,7 @@ export class SkipTakePagination {
|
||||
@IsOptional()
|
||||
take?: number;
|
||||
|
||||
@ApiProperty({ required: false, description: "The number of items to skip", example: 0 })
|
||||
@Transform(({ value }: { value: string }) => value && parseInt(value))
|
||||
@IsNumber()
|
||||
@Min(0)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
/*
|
||||
Warnings:
|
||||
|
||||
- A unique constraint covering the columns `[username,organizationId]` on the table `Profile` will be added. If there are existing duplicate values, this will fail.
|
||||
|
||||
*/
|
||||
-- CreateIndex
|
||||
CREATE UNIQUE INDEX "Profile_username_organizationId_key" ON "Profile"("username", "organizationId");
|
||||
@@ -349,7 +349,11 @@ model Profile {
|
||||
createdAt DateTime @default(now())
|
||||
updatedAt DateTime @updatedAt
|
||||
|
||||
// A user can have multiple profiles in different organizations
|
||||
@@unique([userId, organizationId])
|
||||
// Allow username reuse only across different organizations
|
||||
@@unique([username, organizationId])
|
||||
|
||||
@@index([uid])
|
||||
@@index([userId])
|
||||
@@index([organizationId])
|
||||
|
||||
@@ -7,9 +7,9 @@ type NoShowOptions = {
|
||||
};
|
||||
|
||||
export const noShowHandler = async ({ input }: NoShowOptions) => {
|
||||
const { bookingUid, attendees } = input;
|
||||
const { bookingUid, attendees, noShowHost } = input;
|
||||
|
||||
return handleMarkNoShow({ bookingUid, attendees });
|
||||
return handleMarkNoShow({ bookingUid, attendees, noShowHost });
|
||||
};
|
||||
|
||||
export default noShowHandler;
|
||||
|
||||
Reference in New Issue
Block a user