Merge branch 'main' into bugfix/include-zoom-location

pull/638/head
Nico 2021-09-20 09:23:55 +02:00 committed by GitHub
commit 942066819a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 389 additions and 233 deletions

View File

@ -8,7 +8,7 @@ BASE_URL='http://localhost:3000'
# Required for Vercel hosting - set NEXTAUTH_URL to equal your BASE_URL
# NEXTAUTH_URL='http://localhost:3000'
# Remove this var if you don't want Calendso to collect anonymous usage
# Remove this var if you don't want Cal to collect anonymous usage
NEXT_PUBLIC_TELEMETRY_KEY=js.2pvs2bbpqq1zxna97wcml.oi2jzirnbj1ev4tc57c5r
# Used for the Office 365 / Outlook.com Calendar integration
@ -21,11 +21,11 @@ ZOOM_CLIENT_SECRET=
# E-mail settings
# Calendso uses nodemailer (@see https://nodemailer.com/about/) to provide email sending. As such we are trying to
# Cal uses nodemailer (@see https://nodemailer.com/about/) to provide email sending. As such we are trying to
# allow access to the nodemailer transports from the .env file. E-mail templates are accessible within lib/emails/
# Configures the global From: header whilst sending emails.
EMAIL_FROM='notifications@yourselfhostedcalendso.com'
EMAIL_FROM='notifications@yourselfhostedcal.com'
# Configure SMTP settings (@see https://nodemailer.com/smtp/).
# Note: The below configuration for Office 365 has been verified to work.

View File

@ -7,7 +7,7 @@ assignees: ''
---
Please do not use GitHub for asking questions, as this unnecessarily pollutes it. Instead, if you have a general question about Calendso or about Calendso's features we encourage you to post on our Slack workspace instead: [Calendso's Slack](https://calendso.com/slack). The maintainers and other community members can provide help and answer your questions there.
Please do not use GitHub for asking questions, as this unnecessarily pollutes it. Instead, if you have a general question about Calendso or about Calendso's features we encourage you to post on our Slack workspace instead: [Calendso's Slack](https://cal.com/slack). The maintainers and other community members can provide help and answer your questions there.
If you've discovered a bug or would like to propose a change/new feature please use one of the other issue templates.

View File

@ -1,49 +1,57 @@
<!-- PROJECT LOGO -->
<p align="center">
<a href="https://github.com/calendso/calendso">
<img src="https://calendso.com/calendso-logo.svg" alt="Logo" width="160" height="160">
<img src="https://user-images.githubusercontent.com/8019099/133430653-24422d2a-3c8d-4052-9ad6-0580597151ee.png" alt="Logo">
</a>
<h3 align="center">Calendso</h3>
<h3 align="center">Cal.com (formerly Calendso)</h3>
<p align="center">
The open-source Calendly alternative.
<br />
<a href="https://calendso.com"><strong>Learn more »</strong></a>
<a href="https://cal.com"><strong>Learn more »</strong></a>
<br />
<br />
<a href="https://join.slack.com/t/calendso/shared_invite/zt-mem978vn-RgOEELhA5bcnoGONxDCiHw">Slack</a>
·
<a href="https://calendso.com">Website</a>
<a href="https://cal.com">Website</a>
·
<a href="https://github.com/calendso/calendso/issues">Issues</a>
</p>
</p>
<p align="center">
<a href="https://calendso.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Calendso Slack"></a>
<a href="https://cal.com/slack"><img src="https://img.shields.io/badge/Slack-calendso.slack.com-%234A154B" alt="Join Cal.com Slack"></a>
<a href="https://www.producthunt.com/posts/calendso"><img src="https://img.shields.io/badge/Product%20Hunt-%231%20Product%20of%20the%20Month-%23DA552E" alt="Product Hunt"></a>
<a href="https://github.com/calendso/calendso/stargazers"><img src="https://img.shields.io/github/stars/calendso/calendso" alt="Github Stars"></a>
<a href="https://news.ycombinator.com/item?id=26817795"><img src="https://img.shields.io/badge/Hacker%20News-311-%23FF6600" alt="Hacker News"></a>
<img src="https://img.shields.io/github/license/calendso/calendso" alt="License">
<img src="https://img.shields.io/github/package-json/v/calendso/calendso">
<a href="https://github.com/calendso/calendso/pulse"><img src="https://img.shields.io/github/commit-activity/m/calendso/calendso" alt="Commits-per-month"></a>
<a href="https://calendso.com/pricing"><img src="https://img.shields.io/badge/Pricing-%2412%2Fmonth-brightgreen" alt="Pricing"></a>
<a href="https://cal.com/pricing"><img src="https://img.shields.io/badge/Pricing-%2412%2Fmonth-brightgreen" alt="Pricing"></a>
</p>
<!-- ABOUT THE PROJECT -->
## About The Project
<img width="937" alt="calendso-screenshot" src="https://user-images.githubusercontent.com/8019099/117973912-d9405a80-b324-11eb-8b35-4262e472909c.png">
<img width="100%" alt="booking-screen" src="https://user-images.githubusercontent.com/8019099/133429837-69ac8554-4c9c-43f9-90dd-c3337002d8ff.png">
Let's face it: Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customisations. That's where Calendso comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data. Calendso is to Calendly what GitLab is to GitHub.
# Scheduling infrastructure for absolutely everyone.
The open source Calendly alternative. You are in charge
of your own data, workflow and appearance.
Calendly and other scheduling tools are awesome. It made our lives massively easier. We're using it for business meetings, seminars, yoga classes and even calls with our families. However, most tools are very limited in terms of control and customisations.
That's where Cal.com comes in. Self-hosted or hosted by us. White-label by design. API-driven and ready to be deployed on your own domain. Full control of your events and data.
### Product of the Month: April
#### Support us on [Product Hunt](https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso)
<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://calendso.com/maker-grant.svg" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/posts/calendso?utm_source=badge-top-post-badge&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/top-post-badge.svg?post_id=291910&theme=light&period=monthly" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/posts/calendso?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-calendso" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=291910&theme=light" alt="Calendso - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a> <a href="https://www.producthunt.com/stories/how-this-open-source-calendly-alternative-rocketed-to-product-of-the-day" target="_blank"><img src="https://cal.com/maker-grant.svg" alt="Cal.com - The open source Calendly alternative | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
### Built With
@ -54,9 +62,9 @@ Let's face it: Calendly and other scheduling tools are awesome. It made our live
## Stay Up-to-Date
Calendso is currently in alpha. Watch **releases** of this repository to be notified for future updates:
Cal officially launched as v.1.0 on 15th of September, however a lot of new features are coming. Watch **releases** of this repository to be notified for future updates:
![calendso-star-github](https://user-images.githubusercontent.com/8019099/116010176-5d9c9900-a615-11eb-92d0-aa0e892f7056.gif)
![cal-star-github](https://user-images.githubusercontent.com/8019099/116010176-5d9c9900-a615-11eb-92d0-aa0e892f7056.gif)
<!-- GETTING STARTED -->
@ -66,7 +74,7 @@ To get a local copy up and running, please follow these simple steps.
### Prerequisites
Here is what you need to be able to run Calendso.
Here is what you need to be able to run Cal.
- Node.js
- PostgreSQL
@ -81,12 +89,11 @@ You will also need Google API credentials. You can get this from the [Google API
> - **Requires Docker to be installed**
> - Will start a local Postgres instance with a few test users - the credentials will be logged in the console
```bash
git clone git@github.com:calendso/calendso.git
cd calendso
yarn
yarn dx
yarn dx
```
#### Manual
@ -113,7 +120,7 @@ yarn dx
1. Create a free account with [Heroku](https://www.heroku.com/).
2. Create a new app.
<img width="306" alt="Google Chrome — CleanShotX | 2021-04-20 at 02 01 56" src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png">
<img width="306" alt="Create an App" src="https://user-images.githubusercontent.com/16905768/115322780-b3d58c00-a17e-11eb-8a52-b758fb0ea942.png">
3. In your new app, go to `Overview` and next to `Installed add-ons`, click `Configure Add-ons`. We need this to set up our database.
![image](https://user-images.githubusercontent.com/16905768/115323232-a53ba480-a17f-11eb-98db-58e2f8c52426.png)
@ -122,7 +129,7 @@ yarn dx
![image](https://user-images.githubusercontent.com/16905768/115323126-5beb5500-a17f-11eb-8030-7380310807a9.png)
5. Once the pop-up appears, click `Submit Order Form` - plan name should be `Hobby Dev - Free`.
<img width="512" alt="Google Chrome — CleanShotX | 2021-04-20 at 02 04 29" src="https://user-images.githubusercontent.com/16905768/115323265-b4baed80-a17f-11eb-99f0-d67f019aa6df.png">
<img width="512" alt="Submit Order Form" src="https://user-images.githubusercontent.com/16905768/115323265-b4baed80-a17f-11eb-99f0-d67f019aa6df.png">
6. Once you completed the above steps, click on your newly created `Heroku Postgres` and go to its `Settings`.
![image](https://user-images.githubusercontent.com/16905768/115323367-e92ea980-a17f-11eb-9ff4-dec95f2ec349.png)
@ -198,7 +205,7 @@ yarn dx
### Docker
The Docker configuration for Calendso is an effort powered by people within the community. Calendso does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.
The Docker configuration for Cal is an effort powered by people within the community. Cal.com, Inc. does not provide official support for Docker, but we will accept fixes and documentation. Use at your own risk.
The Docker configuration can be found [in our docker repository](https://github.com/calendso/docker).
@ -206,7 +213,7 @@ The Docker configuration can be found [in our docker repository](https://github.
[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template?template=https%3A%2F%2Fgithub.com%2Fcalendso%2Fcalendso&plugins=postgresql&envs=GOOGLE_API_CREDENTIALS%2CBASE_URL%2CNEXTAUTH_URL%2CPORT&BASE_URLDefault=http%3A%2F%2Flocalhost%3A3000&NEXTAUTH_URLDefault=http%3A%2F%2Flocalhost%3A3000&PORTDefault=3000)
You can deploy Calendso on [Railway](https://railway.app/) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Calendso on their platform.
You can deploy Cal on [Railway](https://railway.app/) using the button above. The team at Railway also have a [detailed blog post](https://blog.railway.app/p/calendso) on deploying Cal on their platform.
<!-- ROADMAP -->
@ -264,7 +271,7 @@ Contributions are what make the open source community such an amazing place to b
10. Also add the redirect URL given above as a whitelist URL and enable "Subdomain check". Make sure, it says "saved" below the form.
11. You don't need to provide basic information about your app. Instead click at "Scopes" and then at "+ Add Scopes". On the left, click the category "Meeting" and check the scope `meeting:write`.
12. Click "Done".
13. You're good to go. Now you can easily add your Zoom integration in the Calendso settings.
13. You're good to go. Now you can easily add your Zoom integration in the Cal.com settings.
<!-- LICENSE -->
@ -276,14 +283,12 @@ Distributed under the MIT License. See `LICENSE` for more information.
## Acknowledgements
Special thanks to these amazing projects which help power Calendso:
Special thanks to these amazing projects which help power Cal.com:
[<img src="https://calendso.com/powered-by-vercel.svg">](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
[<img src="https://cal.com/powered-by-vercel.svg">](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
- [Vercel](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
- [Next.js](https://nextjs.org/)
- [Day.js](https://day.js.org/)
- [Tailwind CSS](https://tailwindcss.com/)
- [Prisma](https://prisma.io/)
[product-screenshot]: https://i.imgur.com/4yvFj2E.png

View File

@ -4,12 +4,12 @@ info:
description: The open source Calendly alternative.
contact:
name: Support
email: support@calendso.com
email: support@cal.com
license:
name: MIT License
url: 'https://opensource.org/licenses/MIT'
version: 1.0.0
termsOfService: 'https://calendso.com/terms'
termsOfService: 'https://cal.com/terms'
server:
url: 'http://localhost:{port}'
description: Local Development Server
@ -205,7 +205,7 @@ paths:
integration: google_calendar
name: Birthdays
- selected: true
externalId: bailey@calendso.com
externalId: bailey@cal.com
integration: google_calendar
name: Calendso
primary: true
@ -1009,7 +1009,7 @@ paths:
required: true
description: The team which you wish to list members of
servers:
- url: 'https://app.calendso.com'
- url: 'https://app.cal.com'
description: Production
components:
securitySchemes: {}

View File

@ -1,6 +1,6 @@
import { GiftIcon } from "@heroicons/react/outline";
export default function DonateBanner() {
if (location.hostname.endsWith(".calendso.com")) {
if (location.hostname.endsWith(".cal.com")) {
return null;
}
@ -27,7 +27,7 @@ export default function DonateBanner() {
<a
target="_blank"
rel="noreferrer"
href="https://calendso.com/donate"
href="https://cal.com/donate"
className="flex items-center justify-center px-4 py-2 border border-transparent rounded-md shadow-sm text-sm font-medium text-blue-600 bg-white hover:bg-blue-50">
Donate
</a>

View File

@ -1,5 +1,5 @@
import Cropper from "react-easy-crop";
import { useState, useCallback, useRef } from "react";
import { useCallback, useRef, useState } from "react";
import Slider from "./Slider";
export default function ImageUploader({ target, id, buttonMsg, handleAvatarChange, imageRef }) {

View File

@ -4,8 +4,8 @@ export default function Logo({ small }: { small?: boolean }) {
<strong>
<img
className={small ? "h-4 w-auto" : "h-5 w-auto"}
alt="Calendso"
title="Calendso"
alt="Cal"
title="Cal"
src="/calendso-logo-white-word.svg"
/>
</strong>

View File

@ -276,7 +276,7 @@ function UserDropdown({ small, bottom }: { small?: boolean; bottom?: boolean })
<Menu.Item>
{({ active }) => (
<a
href="https://calendso.com/slack"
href="https://cal.com/slack"
target="_blank"
rel="noreferrer"
className={classNames(
@ -312,7 +312,7 @@ function UserDropdown({ small, bottom }: { small?: boolean; bottom?: boolean })
<Menu.Item>
{({ active }) => (
<a
href="mailto:feedback@calendso.com"
href="mailto:feedback@cal.com"
className={classNames(
active ? "bg-gray-100 text-gray-900" : "text-neutral-700",
"flex px-4 py-2 text-sm font-medium"

View File

@ -48,7 +48,7 @@ const BookingPage = (props: any): JSX.Element => {
// TODO: Move to translations
const locationLabels = {
[LocationType.InPerson]: "In-person meeting",
[LocationType.InPerson]: "Link or In-person meeting",
[LocationType.Phone]: "Phone call",
[LocationType.GoogleMeet]: "Google Meet",
[LocationType.Zoom]: "Zoom Video",
@ -148,7 +148,7 @@ const BookingPage = (props: any): JSX.Element => {
<Head>
<title>
{rescheduleUid ? "Reschedule" : "Confirm"} your {props.eventType.title} with {props.profile.name}{" "}
| Calendso
| Cal.com
</title>
<link rel="icon" href="/favicon.ico" />
</Head>

View File

@ -68,7 +68,7 @@ const buildSeoMeta = (pageProps: {
const constructImage = (name: string, avatar: string, description: string): string => {
return (
encodeURIComponent("Meet **" + name + "** <br>" + description).replace(/'/g, "%27") +
".png?md=1&images=https%3A%2F%2Fcalendso.com%2Fcalendso-logo-white.svg&images=" +
".png?md=1&images=https%3A%2F%2Fcal.com%2Fcalendso-logo-white.svg&images=" +
encodeURIComponent(avatar)
);
};
@ -87,7 +87,7 @@ export const HeadSeo: React.FC<HeadSeoProps & { children?: never }> = (props) =>
nextSeoProps = {},
} = props;
const pageTitle = title + " | Calendso";
const pageTitle = title + " | Cal.com";
let seoObject = buildSeoMeta({ title: pageTitle, image, description, canonical, siteName });
if (name && avatar) {

View File

@ -1,4 +1,4 @@
import React, { useEffect, useState, useRef } from "react";
import React, { useEffect, useRef, useState } from "react";
import { ArrowLeftIcon, PlusIcon, TrashIcon } from "@heroicons/react/outline";
import ErrorAlert from "@components/ui/alerts/Error";
import { UsernameInput } from "@components/ui/UsernameInput";
@ -242,9 +242,9 @@ export default function EditTeam(props: { team: Team | undefined | null; onClose
</div>
<div className="ml-3 text-sm">
<label htmlFor="hide-branding" className="font-medium text-gray-700">
Disable Calendso branding
Disable Cal.com branding
</label>
<p className="text-gray-500">Hide all Calendso branding from your public pages.</p>
<p className="text-gray-500">Hide all Cal.com branding from your public pages.</p>
</div>
</div>
<hr className="mt-6" />

View File

@ -1,9 +1,9 @@
import {
TrashIcon,
DotsHorizontalIcon,
ExternalLinkIcon,
LinkIcon,
PencilAltIcon,
ExternalLinkIcon,
TrashIcon,
} from "@heroicons/react/outline";
import Dropdown, { DropdownMenuContent, DropdownMenuItem, DropdownMenuTrigger } from "../ui/Dropdown";
import { useState } from "react";

View File

@ -1,4 +1,4 @@
import { XCircleIcon, InformationCircleIcon, CheckCircleIcon } from "@heroicons/react/solid";
import { CheckCircleIcon, InformationCircleIcon, XCircleIcon } from "@heroicons/react/solid";
import classNames from "classnames";
import { ReactNode } from "react";

View File

@ -29,7 +29,6 @@ export const DropdownMenuContent = forwardRef<HTMLDivElement, DropdownMenuConten
className="z-10 mt-1 text-sm origin-top-right bg-white rounded-sm shadow-lg w-44 ring-1 ring-black ring-opacity-5 focus:outline-none"
ref={forwardedRef}>
{children}
<DropdownMenuPrimitive.Arrow />
</DropdownMenuPrimitive.Content>
);
}

View File

@ -2,20 +2,20 @@ import Link from "next/link";
const PoweredByCalendso = () => (
<div className="text-xs text-center sm:text-right p-1">
<Link href={`https://calendso.com?utm_source=embed&utm_medium=powered-by-button`}>
<Link href={`https://cal.com?utm_source=embed&utm_medium=powered-by-button`}>
<a target="_blank" className="dark:text-white text-gray-500 opacity-50 hover:opacity-100">
powered by{" "}
<img
style={{ top: -2 }}
className="dark:hidden w-auto inline h-3 relative"
src="/calendso-logo-word.svg"
alt="Calendso Logo"
alt="Cal.com Logo"
/>
<img
style={{ top: -2 }}
className="hidden dark:inline w-auto h-3 relative"
src="/calendso-logo-word-dark.svg"
alt="Calendso Logo"
alt="Cal.com Logo"
/>
</a>
</Link>

View File

@ -3,6 +3,7 @@ import Text from "@components/ui/Text";
import { PlusIcon, TrashIcon } from "@heroicons/react/outline";
import dayjs, { Dayjs } from "dayjs";
import classnames from "classnames";
export const SCHEDULE_FORM_ID = "SCHEDULE_FORM_ID";
export const toCalendsoAvailabilityFormat = (schedule: Schedule) => {
return schedule;

View File

@ -1,7 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
describe("booking pages", () => {
describe("free user", () => {
it("only one visibile event", () => {
it("only one visible event", () => {
cy.visit("/free");
cy.get("[data-testid=event-types]").children().should("have.length", 1);
cy.get('[href="/free/30min"]').should("exist");
@ -18,7 +18,7 @@ describe("booking pages", () => {
});
});
it("/free/60min is not bookable", () => {
it.skip("/free/60min is not bookable", () => {
cy.request({
method: "GET",
url: "/free/60min",
@ -28,7 +28,7 @@ describe("booking pages", () => {
});
});
});
it("pro user's page has at least 2 visibile events", () => {
it("pro user's page has at least 2 visible events", () => {
cy.visit("/pro");
cy.get("[data-testid=event-types]").children().should("have.length.at.least", 2);
});
@ -49,7 +49,7 @@ describe("booking pages", () => {
});
});
it("/free-first-hidden/60min is not bookable", () => {
it.skip("/free-first-hidden/60min is not bookable", () => {
cy.request({
method: "GET",
url: "/free-first-hidden/60min",

View File

@ -6,7 +6,7 @@ With regard to the Calendso Software:
This software and associated documentation files (the "Software") may only be
used in production, if you (and any entity that you represent) have agreed to,
and are in compliance with, the Calendso Subscription Terms available
at https://calendso.com/terms (the “EE Terms”), or other agreements governing
at https://cal.com/terms (the “EE Terms”), or other agreements governing
the use of the Software, as mutually agreed by you and Meet Technologies, Inc ("Calendso"),
and otherwise have a valid Calendso Enterprise Edition subscription ("EE Subscription")
for the correct number of hosts as defined in the EE Terms ("Hosts"). Subject to the foregoing sentence,

View File

@ -1,15 +1,16 @@
<!-- PROJECT LOGO -->
<div align="right">
<a href="https://github.com/calendso/calendso">
<img src="https://calendso.com/calendso-logo.svg" alt="Logo" width="160" height="65">
</a><br/>
<a href="https://calendso.com">Website</a>
<div align="center">
<a href="https://cal.com/enterprise">
<img src="https://user-images.githubusercontent.com/8019099/133430653-24422d2a-3c8d-4052-9ad6-0580597151ee.png" alt="Logo">
</a>
<a href="https://cal.com/enterprise">Get Started with Enterprise</a>
</div>
# Enterprise Edition
Welcome to the Enterprise Edition ("/ee") of Calendso.com.
The [/ee](https://github.com/calendso/calendso/tree/main/ee) subfolder is the place for all the **Pro** features from our [hosted](https://calendso.com/pricing) plan and [enterprise-grade](https://calendso.com/enterprise) features such as SSO, SAML, ADFS, OIDC, SCIM, SIEM, HRIS and much more.
The [/ee](https://github.com/calendso/calendso/tree/main/ee) subfolder is the place for all the **Pro** features from our [hosted](https://cal.com/pricing) plan and [enterprise-grade](https://cal.com/enterprise) features such as SSO, SAML, ADFS, OIDC, SCIM, SIEM, HRIS and much more.
> _❗ WARNING: This repository is copyrighted (unlike our [main repo](https://github.com/calendso/calendso)). You are not allowed to use this code to host your own version of app.calendso.com without obtaining a proper [license](https://calendso.com/enterprise) first❗_
> _❗ WARNING: This repository is copyrighted (unlike our [main repo](https://github.com/calendso/calendso)). You are not allowed to use this code to host your own version of app.cal.com without obtaining a proper [license](https://cal.com/enterprise) first❗_

View File

@ -5,12 +5,12 @@ import { Credential } from "@prisma/client";
import CalEventParser from "./CalEventParser";
import { EventResult } from "@lib/events/EventManager";
import logger from "@lib/logger";
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
import { CalDavCalendar } from "./integrations/CalDav/CalDavCalendarAdapter";
import { AppleCalendar } from "./integrations/Apple/AppleCalendarAdapter";
import { VideoCallData } from "@lib/videoClient";
const log = logger.getChildLogger({ prefix: ["[lib] calendarClient"] });
// eslint-disable-next-line @typescript-eslint/no-var-requires
const { google } = require("googleapis");
@ -215,7 +215,9 @@ const MicrosoftOffice365Calendar = (credential): CalendarApiAdapter => {
return {
getAvailability: (dateFrom, dateTo, selectedCalendars) => {
const filter = "?startdatetime=" + dateFrom + "&enddatetime=" + dateTo;
const filter = `?startdatetime=${encodeURIComponent(dateFrom)}&enddatetime=${encodeURIComponent(
dateTo
)}`;
return auth
.getToken()
.then((accessToken) => {

View File

@ -15,7 +15,7 @@ export const seoConfig: {
defaultNextSeo: DefaultSeoProps;
} = {
headSeo: {
siteName: "Calendso",
siteName: "Cal.com",
},
defaultNextSeo: {
twitter: {

View File

@ -92,7 +92,7 @@ export default class EventAttendeeMail extends EventMail {
`
</div>
<div style="text-align: center; margin-top: 20px; color: #ccc; font-size: 12px;">
<img style="opacity: 0.25; width: 120px;" src="https://app.calendso.com/calendso-logo-word.svg" alt="Calendso Logo"></div>
<img style="opacity: 0.25; width: 120px;" src="https://app.cal.com/cal-logo-word.svg" alt="Calendso Logo"></div>
</body>
`
);

View File

@ -137,7 +137,7 @@ export default class EventOrganizerMail extends EventMail {
`
</div>
<div style="text-align: center; margin-top: 20px; color: #ccc; font-size: 12px;">
<img style="opacity: 0.25; width: 120px;" src="https://app.calendso.com/calendso-logo-word.svg" alt="Calendso Logo"></div>
<img style="opacity: 0.25; width: 120px;" src="https://app.cal.com/cal-logo-word.svg" alt="Calendso Logo"></div>
</body>
`
);

View File

@ -52,7 +52,7 @@ export default class EventRejectionMail extends EventMail {
`
</div>
<div style="text-align: center; margin-top: 20px; color: #ccc; font-size: 12px;">
<img style="opacity: 0.25; width: 120px;" src="https://app.calendso.com/calendso-logo-word.svg" alt="Calendso Logo"></div>
<img style="opacity: 0.25; width: 120px;" src="https://app.cal.com/cal-logo-word.svg" alt="Calendso Logo"></div>
</body>
`
);

View File

@ -1,23 +1,23 @@
import { IntegrationCalendar, CalendarApiAdapter, CalendarEvent } from "../../calendarClient";
import { CalendarApiAdapter, CalendarEvent, IntegrationCalendar } from "../../calendarClient";
import { symmetricDecrypt } from "@lib/crypto";
import {
createAccount,
fetchCalendars,
fetchCalendarObjects,
getBasicAuthHeaders,
createCalendarObject,
updateCalendarObject,
deleteCalendarObject,
fetchCalendarObjects,
fetchCalendars,
getBasicAuthHeaders,
updateCalendarObject,
} from "tsdav";
import { Credential } from "@prisma/client";
import ICAL from "ical.js";
import { createEvent, DurationObject, Attendee, Person } from "ics";
import { Attendee, createEvent, DurationObject, Person } from "ics";
import dayjs from "dayjs";
import { v4 as uuidv4 } from "uuid";
import { stripHtml } from "../../emails/helpers";
import logger from "@lib/logger";
const log = logger.getChildLogger({ prefix: ["[[lib] caldav"] });
const log = logger.getChildLogger({ prefix: ["[lib] caldav"] });
type EventBusyDate = Record<"start" | "end", Date>;
@ -111,7 +111,7 @@ export class CalDavCalendar implements CalendarApiAdapter {
id: uid,
};
} catch (reason) {
console.error(reason);
log.error(reason);
throw reason;
}
}
@ -161,7 +161,7 @@ export class CalDavCalendar implements CalendarApiAdapter {
})
);
} catch (reason) {
console.error(reason);
log.error(reason);
throw reason;
}
}
@ -193,7 +193,7 @@ export class CalDavCalendar implements CalendarApiAdapter {
})
);
} catch (reason) {
console.error(reason);
log.error(reason);
throw reason;
}
}
@ -260,7 +260,7 @@ export class CalDavCalendar implements CalendarApiAdapter {
integration: this.integrationName,
}));
} catch (reason) {
console.error(reason);
log.error(reason);
throw reason;
}
}
@ -281,58 +281,59 @@ export class CalDavCalendar implements CalendarApiAdapter {
headers: this.headers,
});
const events =
objects &&
objects?.length > 0 &&
objects
.map((object) => {
if (object?.data) {
const jcalData = ICAL.parse(object.data);
const vcalendar = new ICAL.Component(jcalData);
const vevent = vcalendar.getFirstSubcomponent("vevent");
const event = new ICAL.Event(vevent);
if (!objects || objects?.length === 0) {
return [];
}
const calendarTimezone = vcalendar.getFirstSubcomponent("vtimezone")
? vcalendar.getFirstSubcomponent("vtimezone").getFirstPropertyValue("tzid")
: "";
const events = objects
.map((object) => {
if (object?.data) {
const jcalData = ICAL.parse(object.data);
const vcalendar = new ICAL.Component(jcalData);
const vevent = vcalendar.getFirstSubcomponent("vevent");
const event = new ICAL.Event(vevent);
const startDate = calendarTimezone
? dayjs(event.startDate).tz(calendarTimezone)
: new Date(event.startDate.toUnixTime() * 1000);
const endDate = calendarTimezone
? dayjs(event.endDate).tz(calendarTimezone)
: new Date(event.endDate.toUnixTime() * 1000);
const calendarTimezone = vcalendar.getFirstSubcomponent("vtimezone")
? vcalendar.getFirstSubcomponent("vtimezone").getFirstPropertyValue("tzid")
: "";
return {
uid: event.uid,
etag: object.etag,
url: object.url,
summary: event.summary,
description: event.description,
location: event.location,
sequence: event.sequence,
startDate,
endDate,
duration: {
weeks: event.duration.weeks,
days: event.duration.days,
hours: event.duration.hours,
minutes: event.duration.minutes,
seconds: event.duration.seconds,
isNegative: event.duration.isNegative,
},
organizer: event.organizer,
attendees: event.attendees.map((a) => a.getValues()),
recurrenceId: event.recurrenceId,
timezone: calendarTimezone,
};
}
})
.filter((e) => e != null);
const startDate = calendarTimezone
? dayjs(event.startDate).tz(calendarTimezone)
: new Date(event.startDate.toUnixTime() * 1000);
const endDate = calendarTimezone
? dayjs(event.endDate).tz(calendarTimezone)
: new Date(event.endDate.toUnixTime() * 1000);
return {
uid: event.uid,
etag: object.etag,
url: object.url,
summary: event.summary,
description: event.description,
location: event.location,
sequence: event.sequence,
startDate,
endDate,
duration: {
weeks: event.duration.weeks,
days: event.duration.days,
hours: event.duration.hours,
minutes: event.duration.minutes,
seconds: event.duration.seconds,
isNegative: event.duration.isNegative,
},
organizer: event.organizer,
attendees: event.attendees.map((a) => a.getValues()),
recurrenceId: event.recurrenceId,
timezone: calendarTimezone,
};
}
})
.filter((e) => e != null);
return events;
} catch (reason) {
console.error(reason);
log.error(reason);
throw reason;
}
}

View File

@ -1,5 +1,5 @@
export const slugify = (str: string) => {
return str.replace(/\s+/g, "-").toLowerCase();
return str.replace(/[^a-zA-Z0-9-]/g, "-").toLowerCase();
};
export default slugify;

View File

@ -63,7 +63,7 @@
"react-select": "^4.3.1",
"react-timezone-select": "^1.0.7",
"short-uuid": "^4.2.0",
"tsdav": "^1.0.6",
"tsdav": "1.0.6",
"tslog": "^3.2.1",
"uuid": "^8.3.2"
},

View File

@ -10,19 +10,19 @@ const links = [
title: "Documentation",
description: "Learn how to integrate our tools with your app",
icon: DocumentTextIcon,
href: "https://docs.calendso.com",
href: "https://docs.cal.com",
},
{
title: "API Reference",
description: "A complete API reference for our libraries",
icon: CodeIcon,
href: "https://api.docs.calendso.com",
href: "https://api.docs.cal.com",
},
{
title: "Blog",
description: "Read our latest news and articles",
icon: BookOpenIcon,
href: "https://calendso.com/blog",
href: "https://cal.com/blog",
},
];
@ -54,9 +54,9 @@ export default function Custom404() {
Check for spelling mistakes or go back to the previous page.
</span>
) : (
<a href="https://checkout.calendso.com" className="inline-block mt-2 text-lg ">
The username <strong className="text-blue-500">calendso.com{username}</strong> is still
available. <span className="text-blue-500">Register now</span>.
<a href="https://cal.com/signup" className="inline-block mt-2 text-lg ">
The username <strong className="text-blue-500">cal.com{username}</strong> is still available.{" "}
<span className="text-blue-500">Register now</span>.
</a>
)}
</div>
@ -65,9 +65,7 @@ export default function Custom404() {
{!isEventType404 && (
<ul role="list" className="mt-4">
<li className="border-2 border-green-500 px-4 py-2">
<a
href="https://checkout.calendso.com"
className="relative py-6 flex items-start space-x-4">
<a href="https://cal.com/signup" className="relative py-6 flex items-start space-x-4">
<div className="flex-shrink-0">
<span className="flex items-center justify-center h-12 w-12 rounded-lg bg-green-50">
<CheckIcon className="h-6 w-6 text-green-500" aria-hidden="true" />
@ -119,7 +117,7 @@ export default function Custom404() {
</li>
))}
<li className="px-4 py-2">
<a href="https://calendso.com/slack" className="relative py-6 flex items-start space-x-4">
<a href="https://cal.com/slack" className="relative py-6 flex items-start space-x-4">
<div className="flex-shrink-0">
<span className="flex items-center justify-center h-12 w-12 rounded-lg bg-gray-50">
<svg viewBox="0 0 2447.6 2452.5" className="h-6 w-6" xmlns="http://www.w3.org/2000/svg">

View File

@ -117,7 +117,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
const eventType = user.eventTypes[0];
// check this is the first event
if (user.plan === "FREE") {
// TEMPORARILY disabled because of a bug during event create - during which users were able
// to create event types >n1.
/*if (user.plan === "FREE") {
const firstEventType = await prisma.eventType.findFirst({
where: {
OR: [
@ -142,7 +145,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
notFound: true,
} as const;
}
}
}*/
const getWorkingHours = (providesAvailability: { availability: Availability[] }) =>
providesAvailability.availability && providesAvailability.availability.length
? providesAvailability.availability

View File

@ -69,7 +69,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
text: message,
});
return res.status(201).json({ message: "Reset Requested", data: passwordRequest });
return res.status(201).json({ message: "Reset Requested" });
} catch (reason) {
console.error(reason);
return res.status(500).json({ message: "Unable to create password reset request" });

View File

@ -1,13 +1,16 @@
import prisma from "../../../lib/prisma";
import { hashPassword } from "../../../lib/auth";
import { hashPassword } from "@lib/auth";
import prisma from "@lib/prisma";
import slugify from "@lib/slugify";
import { NextApiRequest, NextApiResponse } from "next";
export default async function handler(req, res) {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return;
}
const data = req.body;
const { username, email, password } = data;
const { email, password } = data;
const username = slugify(data.username);
if (!username) {
res.status(422).json({ message: "Invalid username" });
@ -34,13 +37,6 @@ export default async function handler(req, res) {
email: email,
},
],
AND: [
{
emailVerified: {
not: null,
},
},
],
},
});

View File

@ -10,6 +10,35 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
if (!session.user?.id) {
console.error("Session is missing a user id");
return res.status(500).json({ message: "Something went wrong" });
}
if (req.method !== "POST") {
const event = await prisma.eventType.findUnique({
where: { id: req.body.id },
include: {
users: true,
},
});
if (!event) {
return res.status(404).json({ message: "No event exists matching that id." });
}
const isAuthorized =
event.userId === session.user.id ||
event.users.find((user) => {
return user.id === session.user?.id;
});
if (!isAuthorized) {
console.warn(`User ${session.user.id} attempted to an access an event ${event.id} they do not own.`);
return res.status(404).json({ message: "No event exists matching that id." });
}
}
if (req.method == "PATCH" || req.method == "POST") {
const data = {
title: req.body.title,

View File

@ -242,13 +242,16 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}; // used for invitee emails
}
// Initialize EventManager with credentials
const rescheduleUid = req.body.rescheduleUid;
const bookingCreateInput: Prisma.BookingCreateInput = {
uid,
title: evt.title,
startTime: dayjs(evt.startTime).toDate(),
endTime: dayjs(evt.endTime).toDate(),
description: evt.description,
confirmed: !eventType.requiresConfirmation,
confirmed: !eventType.requiresConfirmation || !!rescheduleUid,
location: evt.location,
eventType: {
connect: {
@ -375,9 +378,8 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
// Initialize EventManager with credentials
const eventManager = new EventManager(user.credentials);
const rescheduleUid = req.body.rescheduleUid;
if (rescheduleUid) {
// Use EventManager to conditionally use all needed integrations.
const updateResults: CreateUpdateResult = await eventManager.update(evt, rescheduleUid);
@ -410,7 +412,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
}
if (eventType.requiresConfirmation) {
if (eventType.requiresConfirmation && !rescheduleUid) {
await new EventOrganizerRequestMail(evt, uid).sendEmail();
}
@ -429,6 +431,6 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
},
});
// booking succesfull
// booking successful
return res.status(201).json(booking);
}

View File

@ -1,6 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { getSession } from "@lib/auth";
import prisma from "../../../../lib/prisma";
const scopes = ["offline_access", "Calendars.Read", "Calendars.ReadWrite"];
export default async function handler(req: NextApiRequest, res: NextApiResponse) {

View File

@ -37,6 +37,15 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
in: memberships.map((membership) => membership.userId),
},
},
select: {
id: true,
username: true,
name: true,
email: true,
bio: true,
avatar: true,
timeZone: true,
},
});
members = members.map((member) => {

View File

@ -1,6 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@lib/prisma";
import { getSession } from "@lib/auth";
import { pick } from "lodash";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req: req });
@ -34,13 +35,41 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
}
if (req.method === "PATCH") {
const data = req.body.data;
const updatedUser = await prisma.user.update({
where: {
id: authenticatedUser.id,
},
data: {
...data,
...pick(req.body.data, [
"username",
"name",
"avatar",
"timeZone",
"weekStart",
"hideBranding",
"theme",
"completedOnboarding",
]),
bio: req.body.description,
},
select: {
id: true,
username: true,
name: true,
email: true,
emailVerified: true,
bio: true,
avatar: true,
timeZone: true,
weekStart: true,
startTime: true,
endTime: true,
bufferTime: true,
hideBranding: true,
theme: true,
createdDate: true,
plan: true,
completedOnboarding: true,
},
});
return res.status(200).json({ message: "User Updated", data: updatedUser });

View File

@ -1,6 +1,7 @@
import type { NextApiRequest, NextApiResponse } from "next";
import prisma from "@lib/prisma";
import { getSession } from "@lib/auth";
import { pick } from "lodash";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const session = await getSession({ req: req });
@ -10,17 +11,23 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
return;
}
const description = req.body.description;
delete req.body.description;
try {
await prisma.user.update({
where: {
id: session.user.id,
},
data: {
...req.body,
bio: description,
...pick(req.body, [
"username",
"name",
"avatar",
"timeZone",
"weekStart",
"hideBranding",
"theme",
"completedOnboarding",
]),
bio: req.body.description,
},
});
} catch (e) {

View File

@ -76,7 +76,7 @@ export default function Login({ csrfToken }) {
</div>
<div className="mt-4 text-neutral-600 text-center text-sm">
Don&apos;t have an account? {/* replace this with your account creation flow */}
<a href="https://checkout.calendso.com" className="font-medium text-neutral-900">
<a href="https://cal.com/signup" className="font-medium text-neutral-900">
Create an account
</a>
</div>

View File

@ -8,7 +8,7 @@ import { Fragment } from "react";
import { Menu, Transition } from "@headlessui/react";
import { DotsHorizontalIcon } from "@heroicons/react/solid";
import classNames from "@lib/classNames";
import { ClockIcon, XIcon } from "@heroicons/react/outline";
import { ClockIcon, XIcon, CheckIcon, BanIcon } from "@heroicons/react/outline";
import Loader from "@components/Loader";
import { Button } from "@components/ui/Button";
import { getSession } from "@lib/auth";
@ -89,33 +89,103 @@ export default function Bookings({ bookings }) {
<td className="px-6 py-4 whitespace-nowrap text-right text-sm font-medium">
{!booking.confirmed && !booking.rejected && (
<>
<button
onClick={() => confirmBookingHandler(booking, true)}
className="text-xs sm:text-sm inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
Confirm
</button>
<button
onClick={() => confirmBookingHandler(booking, false)}
className="text-xs sm:text-sm inline-flex items-center px-4 py-2 border-transparent font-medium rounded-sm shadow-sm text-neutral-700 bg-white hover:bg-neutral-100 border border-neutral-300 focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-black ml-2">
Reject
</button>
<div className="space-x-2 hidden lg:block">
<Button
onClick={() => confirmBookingHandler(booking, true)}
StartIcon={CheckIcon}
color="secondary">
Confirm
</Button>
<Button
onClick={() => confirmBookingHandler(booking, false)}
StartIcon={BanIcon}
color="secondary">
Reject
</Button>
</div>
<Menu as="div" className="inline-block lg:hidden text-left ">
{({ open }) => (
<>
<div>
<Menu.Button className="text-neutral-400 mt-1 p-2 border border-transparent hover:border-gray-200">
<span className="sr-only">Open options</span>
<DotsHorizontalIcon className="h-5 w-5" aria-hidden="true" />
</Menu.Button>
</div>
<Transition
show={open}
as={Fragment}
enter="transition ease-out duration-100"
enterFrom="transform opacity-0 scale-95"
enterTo="transform opacity-100 scale-100"
leave="transition ease-in duration-75"
leaveFrom="transform opacity-100 scale-100"
leaveTo="transform opacity-0 scale-95">
<Menu.Items
static
className="origin-top-right absolute right-0 mt-2 w-56 rounded-sm shadow-lg bg-white ring-1 ring-black ring-opacity-5 focus:outline-none divide-y divide-neutral-100">
<div className="py-1">
<Menu.Item>
{({ active }) => (
<span
onClick={() => confirmBookingHandler(booking, true)}
className={classNames(
active
? "bg-neutral-100 text-neutral-900"
: "text-neutral-700",
"group flex items-center px-4 py-2 text-sm font-medium"
)}>
<CheckIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Confirm
</span>
)}
</Menu.Item>
<Menu.Item>
{({ active }) => (
<span
onClick={() => confirmBookingHandler(booking, false)}
className={classNames(
active
? "bg-neutral-100 text-neutral-900"
: "text-neutral-700",
"group flex items-center px-4 py-2 text-sm w-full font-medium"
)}>
<BanIcon
className="mr-3 h-5 w-5 text-neutral-400 group-hover:text-neutral-500"
aria-hidden="true"
/>
Reject
</span>
)}
</Menu.Item>
</div>
</Menu.Items>
</Transition>
</>
)}
</Menu>
</>
)}
{booking.confirmed && !booking.rejected && (
<div className="space-x-2">
<Button
data-testid="cancel"
href={"/cancel/" + booking.uid}
StartIcon={XIcon}
color="secondary">
Cancel
</Button>
<Button
href={"reschedule/" + booking.uid}
StartIcon={ClockIcon}
color="secondary">
Reschedule
</Button>
<>
<div className="space-x-2 hidden lg:block">
<Button
data-testid="cancel"
href={"/cancel/" + booking.uid}
StartIcon={XIcon}
color="secondary">
Cancel
</Button>
<Button
href={"reschedule/" + booking.uid}
StartIcon={ClockIcon}
color="secondary">
Reschedule
</Button>
</div>
<Menu as="div" className="inline-block lg:hidden text-left ">
{({ open }) => (
<>
@ -183,7 +253,7 @@ export default function Bookings({ bookings }) {
</>
)}
</Menu>
</div>
</>
)}
{!booking.confirmed && booking.rejected && (
<div className="text-sm text-gray-500">Rejected</div>

View File

@ -3,7 +3,7 @@ import Modal from "@components/Modal";
import React, { useEffect, useRef, useState } from "react";
import Select, { OptionTypeBase } from "react-select";
import prisma from "@lib/prisma";
import { EventTypeCustomInput, EventTypeCustomInputType, SchedulingType } from "@prisma/client";
import { Availability, EventTypeCustomInput, EventTypeCustomInputType, SchedulingType } from "@prisma/client";
import { LocationType } from "@lib/location";
import Shell from "@components/Shell";
import { getSession } from "@lib/auth";
@ -28,7 +28,6 @@ import {
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
import { Availability } from "@prisma/client";
import { validJson } from "@lib/jsonUtils";
import throttle from "lodash.throttle";
import "react-dates/initialize";
@ -1238,7 +1237,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
];
const locationOptions: OptionTypeBase[] = [
{ value: LocationType.InPerson, label: "In-person meeting" },
{ value: LocationType.InPerson, label: "Link or In-person meeting" },
{ value: LocationType.Phone, label: "Phone call" },
{ value: LocationType.Zoom, label: "Zoom Video", disabled: true },
];

View File

@ -274,7 +274,7 @@ const EventTypesPage = (props: PageProps) => {
<CreateNewEventDialog canAddEvents={props.canAddEvents} profiles={props.profiles} />
)
}>
{props.user.plan === "FREE" && typeof window !== "undefined" && (
{props.user.plan === "FREE" && !props.canAddEvents && typeof window !== "undefined" && (
<Alert
severity="warning"
title={<>You need to upgrade your plan to have more than one active event type.</>}
@ -357,11 +357,7 @@ const CreateNewEventDialog = ({ profiles, canAddEvents }: { profiles: Profile[];
<Button EndIcon={ChevronDownIcon}>New event type</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>
Create an event type under
<br />
your name or a team.
</DropdownMenuLabel>
<DropdownMenuLabel>Create an event type under your name or a team.</DropdownMenuLabel>
<DropdownMenuSeparator className="h-px bg-gray-200" />
{profiles.map((profile) => (
<DropdownMenuItem
@ -720,7 +716,7 @@ export async function getServerSideProps(context) {
createdDate: user.createdDate.toString(),
});
const canAddEvents = user.plan !== "FREE" || eventTypes.length < 1;
const canAddEvents = user.plan !== "FREE" || eventTypes[0].eventTypes.length < 1;
return {
props: {

View File

@ -2,15 +2,15 @@ import Head from "next/head";
import prisma from "@lib/prisma";
import { useSession } from "next-auth/client";
import {
EventType,
EventTypeCreateInput,
Schedule,
ScheduleCreateInput,
User,
UserUpdateInput,
EventType,
Schedule,
} from "@prisma/client";
import { NextPageContext } from "next";
import React, { useState, useEffect, useRef } from "react";
import React, { useEffect, useRef, useState } from "react";
import { validJson } from "@lib/jsonUtils";
import TimezoneSelect from "react-timezone-select";
import Text from "@components/ui/Text";
@ -18,8 +18,6 @@ import ErrorAlert from "@components/ui/alerts/Error";
import dayjs from "dayjs";
import utc from "dayjs/plugin/utc";
import timezone from "dayjs/plugin/timezone";
dayjs.extend(utc);
dayjs.extend(timezone);
import AddCalDavIntegration, {
ADD_CALDAV_INTEGRATION_FORM_TITLE,
} from "@lib/integrations/CalDav/components/AddCalDavIntegration";
@ -33,6 +31,9 @@ import { ArrowRightIcon } from "@heroicons/react/outline";
import { getSession } from "@lib/auth";
import Button from "@components/ui/Button";
dayjs.extend(utc);
dayjs.extend(timezone);
const DEFAULT_EVENT_TYPES = [
{
title: "15 Min Meeting",
@ -347,7 +348,7 @@ export default function Onboarding(props: OnboardingProps) {
const steps = [
{
id: "welcome",
title: "Welcome to Calendso",
title: "Welcome to Cal.com",
description:
"Tell us what to call you and let us know what timezone youre in. Youll be able to edit this later.",
Component: (

View File

@ -1,7 +1,7 @@
import Link from "next/link";
import prisma from "@lib/prisma";
import Shell from "@components/Shell";
import { useEffect, useState, useRef, useCallback } from "react";
import { useCallback, useEffect, useRef, useState } from "react";
import { useSession } from "next-auth/client";
import { CheckCircleIcon, ChevronRightIcon, PlusIcon, XCircleIcon } from "@heroicons/react/solid";
import { InformationCircleIcon } from "@heroicons/react/outline";
@ -462,7 +462,7 @@ export default function Home({ integrations }: Props) {
<p>If you want to add your own App here, get in touch with us.</p>
</div>
<div className="mt-5">
<a href="mailto:apps@calendso.com" className="btn btn-white">
<a href="mailto:apps@cal.com" className="btn btn-white">
Contact us
</a>
</div>

View File

@ -69,7 +69,7 @@ export default function Embed(props) {
Leverage our API for full control and customizability.
</p>
</div>
<a href="https://developer.calendso.com/api" className="btn btn-primary">
<a href="https://developer.cal.com/api" className="btn btn-primary">
Browse our API documentation
</a>
</div>

View File

@ -58,15 +58,15 @@ function HideBrandingInput(props: {
description={
<div className="flex flex-col space-y-3">
<p>
In order to remove the Calendso branding from your booking pages, you need to upgrade to a paid
In order to remove the Cal branding from your booking pages, you need to upgrade to a paid
account.
</p>
<p>
{" "}
To upgrade go to{" "}
<a href="https://calendso.com/upgrade" className="underline">
calendso.com/upgrade
<a href="https://cal.com/upgrade" className="underline">
cal.com/upgrade
</a>
.
</p>

View File

@ -1,7 +1,7 @@
import { GetServerSideProps } from "next";
import Shell from "@components/Shell";
import SettingsShell from "@components/Settings";
import { useEffect, useState, useRef } from "react";
import { useEffect, useRef, useState } from "react";
import type { Session } from "next-auth";
import { useSession } from "next-auth/client";
import { UsersIcon } from "@heroicons/react/outline";

View File

@ -23,7 +23,7 @@ dayjs.extend(timezone);
export default function Success(props: inferSSRProps<typeof getServerSideProps>) {
const router = useRouter();
const { location, name } = router.query;
const { location, name, reschedule } = router.query;
const [is24h, setIs24h] = useState(false);
const [date, setDate] = useState(dayjs.utc(asStringOrNull(router.query.date)));
@ -62,12 +62,14 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
return encodeURIComponent(event.value);
}
const needsConfirmation = props.eventType.requiresConfirmation && reschedule != "true";
return (
isReady && (
<div className="bg-neutral-50 dark:bg-neutral-900 h-screen">
<HeadSeo
title={`Booking ${props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}`}
description={`Booking ${props.eventType.requiresConfirmation ? "Submitted" : "Confirmed"}`}
title={`Booking ${needsConfirmation ? "Submitted" : "Confirmed"}`}
description={`Booking ${needsConfirmation ? "Submitted" : "Confirmed"}`}
/>
<main className="max-w-3xl mx-auto py-24">
<div className="fixed z-50 inset-0 overflow-y-auto">
@ -83,22 +85,18 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
aria-labelledby="modal-headline">
<div>
<div className="mx-auto flex items-center justify-center h-12 w-12 rounded-full bg-green-100">
{!props.eventType.requiresConfirmation && (
<CheckIcon className="h-8 w-8 text-green-600" />
)}
{props.eventType.requiresConfirmation && (
<ClockIcon className="h-8 w-8 text-green-600" />
)}
{!needsConfirmation && <CheckIcon className="h-8 w-8 text-green-600" />}
{needsConfirmation && <ClockIcon className="h-8 w-8 text-green-600" />}
</div>
<div className="mt-3 text-center sm:mt-5">
<h3
className="text-2xl leading-6 font-semibold dark:text-white text-neutral-900"
id="modal-headline">
{props.eventType.requiresConfirmation ? "Submitted" : "This meeting is scheduled"}
{needsConfirmation ? "Submitted" : "This meeting is scheduled"}
</h3>
<div className="mt-3">
<p className="text-sm text-neutral-600 dark:text-gray-300">
{props.eventType.requiresConfirmation
{needsConfirmation
? props.profile.name !== null
? `${props.profile.name} still needs to confirm or reject the booking.`
: "Your booking still needs to be confirmed or rejected."
@ -126,7 +124,7 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
</div>
</div>
</div>
{!props.eventType.requiresConfirmation && (
{!needsConfirmation && (
<div className="mt-5 sm:mt-0 sm:pt-4 pt-2 text-center flex">
<span className="font-medium text-gray-700 dark:text-gray-50 flex self-center mr-6">
Add to calendar
@ -226,7 +224,7 @@ export default function Success(props: inferSSRProps<typeof getServerSideProps>)
)}
{!props.hideBranding && (
<div className="mt-4 pt-4 border-t dark:border-gray-900 text-gray-400 text-center text-xs dark:text-white">
<a href="https://checkout.calendso.com">Create your own booking link with Calendso</a>
<a href="https://cal.com/signup">Create your own booking link with Calendso</a>
</div>
)}
</div>

9
public/cal-logo-word.svg Normal file
View File

@ -0,0 +1,9 @@
<svg width="101" height="22" viewBox="0 0 101 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M10.0582 20.817C4.32115 20.817 0 16.2763 0 10.6704C0 5.04589 4.1005 0.467773 10.0582 0.467773C13.2209 0.467773 15.409 1.43945 17.1191 3.66311L14.3609 5.96151C13.2025 4.72822 11.805 4.11158 10.0582 4.11158C6.17833 4.11158 4.04533 7.08268 4.04533 10.6704C4.04533 14.2582 6.38059 17.1732 10.0582 17.1732C11.7866 17.1732 13.2577 16.5566 14.4161 15.3233L17.1375 17.7151C15.501 19.8453 13.2577 20.817 10.0582 20.817Z" fill="#292929"/>
<path d="M29.0161 5.88601H32.7304V20.4612H29.0161V18.331C28.2438 19.8446 26.9566 20.8536 24.4927 20.8536C20.5577 20.8536 17.4133 17.4341 17.4133 13.2297C17.4133 9.02528 20.5577 5.60571 24.4927 5.60571C26.9383 5.60571 28.2438 6.61477 29.0161 8.12835V5.88601ZM29.1264 13.2297C29.1264 10.95 27.5634 9.06266 25.0995 9.06266C22.7274 9.06266 21.1828 10.9686 21.1828 13.2297C21.1828 15.4346 22.7274 17.3967 25.0995 17.3967C27.5451 17.3967 29.1264 15.4907 29.1264 13.2297Z" fill="#292929"/>
<path d="M35.3599 0H39.0742V20.4427H35.3599V0Z" fill="#292929"/>
<path d="M40.7291 18.5182C40.7291 17.3223 41.6853 16.3132 42.9908 16.3132C44.2964 16.3132 45.2158 17.3223 45.2158 18.5182C45.2158 19.7515 44.278 20.7605 42.9908 20.7605C41.7037 20.7605 40.7291 19.7515 40.7291 18.5182Z" fill="#292929"/>
<path d="M59.4296 18.1068C58.0505 19.7885 55.9543 20.8536 53.4719 20.8536C49.0404 20.8536 45.7858 17.4341 45.7858 13.2297C45.7858 9.02528 49.0404 5.60571 53.4719 5.60571C55.8623 5.60571 57.9402 6.61477 59.3193 8.20309L56.4508 10.6136C55.7336 9.71667 54.7958 9.04397 53.4719 9.04397C51.0999 9.04397 49.5553 10.95 49.5553 13.211C49.5553 15.472 51.0999 17.378 53.4719 17.378C54.9062 17.378 55.8991 16.6306 56.6346 15.6215L59.4296 18.1068Z" fill="#292929"/>
<path d="M59.7422 13.2297C59.7422 9.02528 62.9968 5.60571 67.4283 5.60571C71.8598 5.60571 75.1144 9.02528 75.1144 13.2297C75.1144 17.4341 71.8598 20.8536 67.4283 20.8536C62.9968 20.8349 59.7422 17.4341 59.7422 13.2297ZM71.3449 13.2297C71.3449 10.95 69.8003 9.06266 67.4283 9.06266C65.0563 9.04397 63.5117 10.95 63.5117 13.2297C63.5117 15.4907 65.0563 17.3967 67.4283 17.3967C69.8003 17.3967 71.3449 15.4907 71.3449 13.2297Z" fill="#292929"/>
<path d="M100.232 11.5482V20.4428H96.518V12.4638C96.518 9.94119 95.3412 8.85739 93.576 8.85739C91.921 8.85739 90.7442 9.67958 90.7442 12.4638V20.4428H87.0299V12.4638C87.0299 9.94119 85.8346 8.85739 84.0878 8.85739C82.4329 8.85739 80.9802 9.67958 80.9802 12.4638V20.4428H77.2659V5.8676H80.9802V7.88571C81.7525 6.31607 83.15 5.53125 85.3014 5.53125C87.3425 5.53125 89.0525 6.5403 89.9903 8.24074C90.9281 6.50293 92.3072 5.53125 94.8079 5.53125C97.8603 5.54994 100.232 7.86702 100.232 11.5482Z" fill="#292929"/>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -7506,10 +7506,10 @@ ts-pnp@^1.1.6:
resolved "https://registry.yarnpkg.com/ts-pnp/-/ts-pnp-1.2.0.tgz#a500ad084b0798f1c3071af391e65912c86bca92"
integrity sha512-csd+vJOb/gkzvcCHgTGSChYpy5f1/XKNsmvBGO4JXS+z1v2HobugDz4s1IeFXM3wZB44uczs+eazB5Q/ccdhQw==
tsdav@^1.0.6:
version "1.1.0"
resolved "https://registry.yarnpkg.com/tsdav/-/tsdav-1.1.0.tgz#48fe17df07f852ff8dfddf71fae2396e79ed6735"
integrity sha512-Cc/VTFgUC/fOOFkGT/xuwrrIrG0+it7WA5ywNM4QyYWAzXVw+WmbM1pr7/7fzHA/OOVQ0a8RyPi4K1C+mFBGJw==
tsdav@1.0.6:
version "1.0.6"
resolved "https://registry.yarnpkg.com/tsdav/-/tsdav-1.0.6.tgz#65c22fc77d6516db234c1288ff9c9fed6730fef5"
integrity sha512-xnKE39wZtyLoyW1UClYAb2Eilx6tHzbqhO2v29P1zrmwLAsKNYUHwHb+lrmJHfZZLsbbzKWRg5dPLYRuEXmMJA==
dependencies:
base-64 "^1.0.0"
cross-fetch "^3.1.4"