Compare commits

..
48 Commits
Author SHA1 Message Date
Omar López 8e8c96c267 v2.4.6
release.yml / release (push) Canceled after 0s
2023-01-09 20:26:21 -07:00
github-actions[bot]andCrowdin Bot fab765c5ee New Crowdin translations by Github Action (#6349)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-09 20:25:22 -07:00
zomars 89ed07a097 Update crowdin.yml 2023-01-09 20:17:03 -07:00
zomars d7d73d2caf Update crowdin.yml 2023-01-09 20:15:53 -07:00
zomars aa92730e28 Fix labeler 2023-01-09 19:52:02 -07:00
430430e0cd Feedback of reusable wysiwyg editor (#6357)
* make text editor reusable

* use dropdown component for block type

* remove ring when clicking on block type

* allow excluding items in toolbar

* refactor code

* make AddVariableDropdown reusable

* fixed missed translations

* fixes variable translation issue

* change missed translation variables

* make AddVariablesDropdown reusable

* reorder block types in dropdown

* remove not needed prop

* code clean up

* center add variable dropwdown icon

* remove rounded borders in dropwdown on hover and active

* center icons

* fix tool bar items dropdown

* add constant for dynamic text variables

* remove not needed variables (added from merge)

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: alannnc <alannnc@gmail.com>
2023-01-09 19:22:17 -07:00
62713940b0 Chor/v2 more tidyup - Card - link icon button - tooltip (#6312)
* Card

* Step card

* Already in components

* Link Icon Button

* Tooltip

* Fix types

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 19:22:04 -07:00
75aef09338 Google Meet - installable app (#5904)
* Abstract app category navigation

* Send key schema to frontend

Co-authored-by: Omar López <zomars@users.noreply.github.com>

* Render keys for apps on admin

* Add enabled col for apps

* Save app keys to DB

* Add checks for admin role

* Abstract setup components

* Add AdminAppsList to setup wizard

* Migrate to v10 tRPC

* Default hide keys

* Display enabled apps

* Merge branch 'main' into admin-apps-ui

* Toggle calendars

* WIP

* Add params and include AppCategoryNavigation

* Refactor getEnabledApps

* Add warning for disabling apps

* Fallback to cal video when a video app is disabled

* WIP send disabled email

* Send email to all users of  event types with payment app

* Disable Stripe when app is disabled

* Disable apps in event types

* Send email to users on disabled apps

* Send email based on what app was disabled

* WIP type fix

* Disable navigation to apps list if already setup

* UI import fixes

* Waits for session data before redirecting

* Updates admin seeded password

To comply with admin password requirements

* Update yarn.lock

* Flex fixes

* Adds admin middleware

* Clean up

* WIP

* WIP

* NTS

* Add dirName to app metadata

* Upsert app if not in db

* Upsert app if not in db

* Add dirName to app metadata

* Add keys to app packages w/ keys

* Merge with main

* Toggle show keys & on enable

* Fix empty keys

* Fix lark calendar metadata

* Fix some type errors

* Fix Lark metadata & check for category when upserting

* More type fixes

* Fix types & add keys to google cal

* WIP

* WIP

* WIP

* More type fixes

* Fix type errors

* Fix type errors

* More type fixes

* More type fixes

* More type fixes

* Feedback

* Fixes default value

* Feedback

* Migrate credential invalid col default value "false"

* Upsert app on saving keys

* Clean up

* Validate app keys on frontend

* Add nonempty to app keys schemas

* Add web3

* Listlocale filter on categories / category

* Grab app metadata via category or categories

* Show empty screen if no apps are enabled

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Fix type checks

* Replace .nonempty() w/ .min(1)

* Fix type error

* Address feedback

* Draft Google Meet install button

* Add install button and warning dialog

* WIP

* WIP

* Display warning when Meet is selected

* Display Google Meet warning on email to organizer

* Fix email

* Fix type errors

* Fix type error

* Add connected account component

* Add warning message

* Address comments

* Address feedback

* Clean up & add MeetLocationType

* Use useApp hook

* Translate to new API approach

* Remove console.log

* Refactor

* Fix missing backup Cal video link

* WIP

* Address feedback

* Update submodules

* Feedback

* Submodule sync

Co-authored-by: Omar López <zomars@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: zomars <zomars@me.com>
Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-10 02:01:57 +00:00
4e65c30e18 fix: Parameterize Stripe Payment Fee configs (#6024)
* Parameterize Stripe Payment Fee configs

* Restore constant values for Payment Fee params

* Restore constant values for all Payment Fee params

* Remove STRIPE_ prefix on payment fee vars

* Cast Payment Fee params as Number

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
Co-authored-by: Omar López <zomars@me.com>
2023-01-09 18:50:49 -07:00
8b873e2828 Adding a bunch of performance markers (#6303)
* Adding a bunch of performance markers

* Update packages/lib/server/perfObserver.ts

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 18:42:46 -07:00
zomars e70d179628 Update website 2023-01-09 18:31:38 -07:00
Nafees Nazik ca9a836300 fix: remove padding from username field (#6356) 2023-01-09 19:35:00 +01:00
Alex van Andel abdf72dc0f Fixup yarn.lock 2023-01-09 14:36:49 +00:00
dependabot[bot]andBailey Pumfleet 227cd165f6 Bump json5 from 1.0.1 to 1.0.2 (#6351)
Bumps [json5](https://github.com/json5/json5) from 1.0.1 to 1.0.2.
- [Release notes](https://github.com/json5/json5/releases)
- [Changelog](https://github.com/json5/json5/blob/main/CHANGELOG.md)
- [Commits](https://github.com/json5/json5/compare/v1.0.1...v1.0.2)

---
updated-dependencies:
- dependency-name: json5
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Bailey Pumfleet <bailey@pumfleet.co.uk>
2023-01-09 14:19:51 +00:00
a0135cdc59 Make WYSIWYG editor reusable (#6293)
* make text editor reusable

* use dropdown component for block type

* remove ring when clicking on block type

* allow excluding items in toolbar

* refactor code

* make AddVariableDropdown reusable

* fixed missed translations

* fixes variable translation issue

* change missed translation variables

* make AddVariablesDropdown reusable

* reorder block types in dropdown

* remove not needed prop

* code clean up

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-09 14:15:11 +00:00
112d504148 Chor/v2 UI tidy - Dialog & Toasts (#6307)
* Dialog Tidyup

* Move toasts

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
Co-authored-by: Peer Richelsen <peer@cal.com>
2023-01-09 13:57:12 +00:00
Peer Richelsen d275930732 link to event-types in settings to fix cal.com usage (#6348) 2023-01-09 13:15:10 +01:00
Alex van Andel 97e33bdd7e Set bg-white to secondary variant (#6339) 2023-01-08 17:50:37 +01:00
Hariom Balhara 3f13791faf Add app-store installed page navigationt ests (#6333) 2023-01-08 09:29:20 +01:00
Omar LópezandAlex van Andel 359cfc4be0 Replaces mdx-remote with markdown-it (#6326)
* Replaces mdx-remote with react-markdown

* Replaces react-markdown with already available markdown-it

* Renames readme to description

Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-01-08 00:05:35 +00:00
Alex van AndelandPeer Richelsen 1ad6bba0ab set useQueryParam to false for app categories (#6330)
* set useQueryParam to false for app categories

* New param 'useQueryParam' for AppCategoryNavigation/AdminAppsList

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 23:21:21 +00:00
Alex van Andel 6dc4211d81 Fixes hydration error, uses useTypedQuery for tabName (#6327) 2023-01-07 22:42:30 +00:00
Alex van Andel e5f0452728 Don't initialize KBar when unauthed (#6328)
* Don't initialize KBar when unauthed

* Don't render trigger when query is undefined
2023-01-07 22:31:15 +00:00
zomars d95bba63f4 Submodule sync 2023-01-07 11:19:16 -07:00
zomars 6f48869021 Add missing slash to custom import 2023-01-07 10:17:38 -07:00
Jeff Loiselle 845bfae887 adds event types to kbar (#6210) 2023-01-07 10:06:53 -07:00
zomars 3fe02fba26 Update basic.e2e.ts 2023-01-07 09:55:30 -07:00
zomars 4ca5ca9fe2 Update hash-my-url.e2e.ts 2023-01-07 09:50:02 -07:00
Hariom BalharaandPeer Richelsen 364e187080 Fix/Next.js-13/Circular Dependency in Routing Forms (#6321)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 15:50:28 +00:00
Peer Richelsen b1193eef1e Update README.md 2023-01-07 16:27:56 +01:00
Peer Richelsen e110f12cf1 Update README.md 2023-01-07 16:26:47 +01:00
Peer Richelsen 022b513935 Update package.json 2023-01-07 11:55:50 +01:00
Jeff LoiselleandPeer Richelsen 010f39a7a4 adds render.com url constant (#6287)
* adds render.com url constant

* fixes webapp_url constant

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-07 10:28:02 +00:00
Peer Richelsen 6b6eed66eb fixed mobile sidebar in settings (#6317) 2023-01-07 10:10:35 +00:00
Udit Takkar c214e01f03 fix: use overflow-auto (#6295)
* fix: use overflow-auto

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* fix: scroll issues with too many filters

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

* chore: merge conflict

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
2023-01-07 11:00:11 +01:00
Carina WollendorferandCarinaWolli a28ac3634d Add start and end time of rescheduled booking to webhook payload (#6297)
* add start and end time of rescheduled booking

* fix type error

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-01-06 13:37:06 -07:00
zomars a0709fac88 Circular dependecy hotfix 2023-01-06 13:15:34 -07:00
Alex van Andel e5b6a3332e Feature/new design radio area group (#6289)
* Refactor of the RadioAreaGroup component

* Move RadioAreaGroup outside of v2/core

* Use useTypedQuery

* Removed unused (broken) imports

* Fix type for Select
2023-01-06 20:05:23 +00:00
Jeff Loiselle 979686920a fixes codespace login msg spelling (#6288) 2023-01-06 12:37:03 -07:00
zomars 793850f3aa Update console 2023-01-06 11:10:17 -07:00
Nafees NazikandPeer Richelsen fa9d72fc0a fix: flash of untranslated title in settings/my-account/profile (#6163)
* fix: flash of untranslated content

* feat: extract loading into a variable

* fix: use optional chaining because typescript complaining

* feat: render Meta component even if not loading for seo purpose

* fix: received an array with more than 1 element as
children warning

* fix: changes

Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-06 10:35:32 -07:00
zomars 0d485ffed6 Updates website 2023-01-06 10:18:18 -07:00
Peer Richelsen 9a2e55d528 revert version change 2023-01-06 16:59:26 +01:00
github-actions[bot]andCrowdin Bot 81ecbb86a5 New Crowdin translations by Github Action (#6308)
Co-authored-by: Crowdin Bot <support+bot@crowdin.com>
2023-01-06 16:58:09 +01:00
Peer Richelsen 2cd816932b Update package.json 2023-01-06 16:55:01 +01:00
Peer Richelsenandzomars d83411162d Upgrade to next 13.1 (#6265)
Co-authored-by: zomars <zomars@me.com>
2023-01-06 12:13:56 +00:00
Nafees NazikandPeer Richelsen c9c54db3a1 fix: remove duplicate font preload (#6291)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-01-06 12:35:48 +01:00
Udit Takkarandkodiakhq[bot] d100600789 feat: update team schema, update team query (#6294)
team appearance section

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>

Signed-off-by: Udit Takkar <udit.07814802719@cse.mait.ac.in>
Co-authored-by: kodiakhq[bot] <49736102+kodiakhq[bot]@users.noreply.github.com>
2023-01-06 10:55:57 +00:00
247 changed files with 3175 additions and 2662 deletions
-1
View File
@@ -1 +0,0 @@
🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev envionment.
+2
View File
@@ -102,6 +102,8 @@ STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
PAYMENT_FEE_PERCENTAGE=
# Use for internal Public API Keys and optional
API_KEY_PREFIX=cal_
+2 -1
View File
@@ -22,6 +22,7 @@ jobs:
upload_translations: true
download_translations: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
# So required checks run on Crowdin PRs
GITHUB_TOKEN: ${{ secrets.GH_ACCESS_TOKEN }}
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
CROWDIN_PERSONAL_TOKEN: ${{ secrets.CROWDIN_PERSONAL_TOKEN }}
+2
View File
@@ -14,3 +14,5 @@ jobs:
- uses: actions/labeler@v4
with:
repo-token: "${{ secrets.GITHUB_TOKEN }}"
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
sync-labels: ""
+11
View File
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -17,7 +17,7 @@
"license": "MIT",
"dependencies": {
"iframe-resizer-react": "^1.1.0",
"next": "^12.3.1",
"next": "^13.1.1",
"nextra": "^1.1.0",
"nextra-theme-docs": "^1.2.2",
"react": "^18.2.0",
+3 -3
View File
@@ -31,7 +31,7 @@ All apps can be found under `packages/app-store`. In this folder is `_example` w
| ├──index.ts
| ├──package.json
| ├──.env.example
| ├──README.mdx
| ├──DESCRIPTION.md
```
## Getting Started
@@ -48,9 +48,9 @@ Under the `/components` folder, this is where the install button for your app sh
The `/lib` folder is where the functions of your app live. For example, when creating a booking with a MS Teams link the function to make the call to grab the link lives in the `/lib` folder. Export your endpoints in an `index.ts` file under `/lib` folder and import them in your main `index.ts` file.
On the app store page you can customize your apps description by adding a markdown file called `README.mdx`. If you do not add one then the description from you `package.json` will be used instead.
On the app store page you can customize your apps description by adding a markdown file called `DESCRIPTION.md`. If you do not add one then the description from you `package.json` will be used instead.
The `/static` folder is where you can store your app icon and any images that your `README.mdx` may use.
The `/static` folder is where you can store your app icon and any images that your `DESCRIPTION.md` may use.
## Adding Your App to the App Store
To render your app on the app store page, go to `packages/app-store/index.ts`. Import your app into the file and add it to the `appStore` object.
+1 -1
View File
@@ -20,7 +20,7 @@
"@radix-ui/react-slider": "^1.0.0",
"@radix-ui/react-switch": "^1.0.0",
"@radix-ui/react-tooltip": "^1.0.0",
"next": "^12.3.1",
"next": "^13.1.1",
"react": "^18.2.0",
"react-dom": "^18.2.0"
},
+1 -1
View File
@@ -14,7 +14,7 @@
"dependencies": {
"highlight.js": "^11.6.0",
"isarray": "2.0.5",
"next": "^12.3.1",
"next": "^13.1.1",
"openapi-snippet": "^0.13.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
-9
View File
@@ -1,9 +0,0 @@
import { Suspense, SuspenseProps } from "react";
/**
* Wrapper around `<Suspense />` which will render the `fallback` when on server
* Can be simply replaced by `<Suspense />` once React 18 is ready.
*/
export const ClientSuspense = (props: SuspenseProps) => {
return <>{typeof window !== "undefined" ? <Suspense {...props} /> : props.fallback}</>;
};
+1 -1
View File
@@ -62,7 +62,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
const className = tab.className || "";
return (
<Component key={tab.name}>
<Link key={tab.name} href={href} {...linkProps}>
<Link key={tab.name} href={href} {...linkProps} legacyBehavior>
<a
onClick={onClick}
className={classNames(
+4 -2
View File
@@ -98,8 +98,10 @@ const Component = ({
<h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1>
</div>
<h2 className="text-sm font-medium text-gray-600">
<Link href={`categories/${categories[0]}`}>
<a className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">{categories[0]}</a>
<Link
href={`categories/${categories[0]}`}
className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">
{categories[0]}
</Link>{" "}
• {t("published_by", { author })}
</h2>
@@ -330,7 +330,10 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
})}
description={t(`no_category_apps_description_calendar`)}
buttonRaw={
<Button color="secondary" href="/apps/categories/calendar">
<Button
color="secondary"
data-testid="connect-calendar-apps"
href="/apps/categories/calendar">
{t(`connect_calendar_apps`)}
</Button>
}
+23 -23
View File
@@ -114,29 +114,29 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
</div>
) : (
<Link href={bookingUrl} prefetch={false}>
<a
className={classNames(
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent dark:text-neutral-200",
brand === "#fff" || brand === "#ffffff" ? "" : ""
)}
data-testid="time">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && (
<p
className={`${
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
? "text-rose-600"
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} text-sm`}>
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
{seatsPerTimeSlot} {t("seats_available")}
</p>
)}
</a>
<Link
href={bookingUrl}
prefetch={false}
className={classNames(
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent dark:text-neutral-200",
brand === "#fff" || brand === "#ffffff" ? "" : ""
)}
data-testid="time">
{dayjs(slot.time).tz(timeZone()).format(timeFormat)}
{!!seatsPerTimeSlot && (
<p
className={`${
slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.8
? "text-rose-600"
: slot.attendees && slot.attendees / seatsPerTimeSlot >= 0.33
? "text-yellow-500"
: "text-emerald-400"
} text-sm`}>
{slot.attendees ? seatsPerTimeSlot - slot.attendees : seatsPerTimeSlot} /{" "}
{seatsPerTimeSlot} {t("seats_available")}
</p>
)}
</Link>
)}
</div>
@@ -93,10 +93,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
<div className="flex flex-col">
<div className="flex justify-between">
<Label>{t("add_to_calendar")}</Label>
<Link href="/apps/categories/calendar">
<a target="_blank" className="text-sm text-gray-600 hover:text-gray-900">
{t("add_another_calendar")}
</a>
<Link
href="/apps/categories/calendar"
target="_blank"
className="text-sm text-gray-600 hover:text-gray-900">
{t("add_another_calendar")}
</Link>
</div>
<div className="-mt-1 w-full">
@@ -266,12 +267,10 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
/>
{/*TODO: Extract it out into a component when used more than once*/}
{!IS_SELF_HOSTED && (
<Link href="https://cal.com/pricing">
<a target="_blank">
<Badge variant="orange" className="ml-2">
Platform Only
</Badge>
</a>
<Link href="https://cal.com/pricing" target="_blank">
<Badge variant="orange" className="ml-2">
Platform Only
</Badge>
</Link>
)}
</div>
@@ -1,16 +1,18 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { isValidPhoneNumber } from "libphonenumber-js";
import { Trans } from "next-i18next";
import Link from "next/link";
import type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useState } from "react";
import { Controller, useForm, useFormContext } from "react-hook-form";
import { MultiValue } from "react-select";
import { z } from "zod";
import { EventLocationType, getEventLocationType } from "@calcom/app-store/locations";
import { EventLocationType, getEventLocationType, MeetLocationType } from "@calcom/app-store/locations";
import { CAL_URL } from "@calcom/lib/constants";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { Button, Icon, Label, Select, Skeleton, TextField, SettingsToggle } from "@calcom/ui";
import { Button, Icon, Label, Select, SettingsToggle, Skeleton, TextField } from "@calcom/ui";
import { slugify } from "@lib/slugify";
@@ -195,6 +197,23 @@ export const EventSetupTab = (
</li>
);
})}
{validLocations.some((location) => location.type === MeetLocationType) && (
<div className="flex text-sm text-gray-600">
<Icon.FiCheck className="mt-0.5 mr-1.5 h-2 w-2.5" />
<Trans i18nKey="event_type_requres_google_cal">
<p>
The “Add to calendar” for this event type needs to be a Google Calendar for Meet to work.
Change it{" "}
<Link
href={`${CAL_URL}/event-types/${eventType.id}?tabName=advanced`}
className="underline">
here.
</Link>{" "}
We will fall back to Cal video if you do not change it.
</p>
</Trans>
</div>
)}
{validLocations.length > 0 && validLocations.length !== locationOptions.length && (
<li>
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
@@ -326,6 +345,7 @@ export const EventSetupTab = (
<Skeleton as={Label} loadingClassName="w-16">
{t("location")}
</Skeleton>
<Controller
name="locations"
control={formMethods.control}
@@ -1,7 +1,7 @@
import { TFunction } from "next-i18next";
import { useRouter } from "next/router";
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
import { useMemo, useState } from "react";
import { useMemo, useState, Suspense } from "react";
import { UseFormReturn } from "react-hook-form";
import { TbWebhook } from "react-icons/tb";
@@ -32,7 +32,6 @@ import {
VerticalTabs,
} from "@calcom/ui";
import { ClientSuspense } from "@components/ClientSuspense";
import { EmbedButton, EmbedDialog } from "@components/Embed";
type Props = {
@@ -301,7 +300,7 @@ function EventTypeSingleLayout({
</Button>
</div>
}>
<ClientSuspense fallback={<Icon.FiLoader />}>
<Suspense fallback={<Icon.FiLoader />}>
<div className="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
<div className="hidden xl:block">
<VerticalTabs
@@ -324,7 +323,7 @@ function EventTypeSingleLayout({
</div>
</div>
</div>
</ClientSuspense>
</Suspense>
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
<ConfirmationDialogContent
isLoading={deleteMutation.isLoading}
+1 -7
View File
@@ -35,13 +35,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
return (
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
{item.href ? (
<Link href={item.href}>
<a>{avatar}</a>
</Link>
) : (
avatar
)}
{item.href ? <Link href={item.href}>{avatar}</Link> : avatar}
</li>
);
}
-22
View File
@@ -1,22 +0,0 @@
import Link from "next/link";
interface ILinkTextProps {
href: string;
children: React.ReactNode;
classNameChildren?: string;
target?: string;
}
/**
* This component had to be made in order to make i18n work with next/link
* @see https://github.com/i18next/react-i18next/issues/1090#issuecomment-615426145
**/
export const LinkText = (props: ILinkTextProps) => {
const { target, href, children, classNameChildren, ...moreProps } = props;
return (
<Link href={href || ""} {...moreProps}>
<a target={target || ""} className={classNameChildren}>
{children}
</a>
</Link>
);
};
+15 -20
View File
@@ -9,26 +9,21 @@ const PoweredByCal = () => {
const isEmbed = useIsEmbed();
return (
<div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}>
<Link href={POWERED_BY_URL}>
<a target="_blank" className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
{t("powered_by")}{" "}
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg"
alt="Cal.com Logo"
/>
}
{
// eslint-disable-next-line @next/next/no-img-element
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
}
</a>
<Link
href={POWERED_BY_URL}
target="_blank"
className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
{t("powered_by")}{" "}
<img
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
src="/cal-logo-word.svg"
alt="Cal.com Logo"
/>
<img
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
src="/cal-logo-word-dark.svg"
alt="Cal.com Logo"
/>
</Link>
</div>
);
@@ -187,7 +187,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
<div className="flex justify-items-center">
<Label htmlFor="username">{t("username")}</Label>
</div>
<div className="mt-2 flex rounded-md">
<div className="flex rounded-md">
<span
className={classNames(
isInputUsernamePremium ? "border-1 border-orange-400 " : "",
@@ -118,7 +118,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
return (
<div>
<div className="mt-2 flex rounded-md">
<div className="flex rounded-md">
<div className="relative w-full">
<TextField
ref={usernameRef}
@@ -1,79 +0,0 @@
import React, { ReactNode, useState } from "react";
import classNames from "@lib/classNames";
type RadioAreaProps = Omit<React.InputHTMLAttributes<HTMLInputElement>, "onChange"> & {
onChange?: (value: string) => void;
defaultChecked?: boolean;
};
const RadioArea = (props: RadioAreaProps) => {
return (
<label
className={classNames(
"border-1 block border p-4 focus:outline-none focus:ring focus:ring-neutral-500",
props.checked && "border-brand",
props.className
)}>
<input
onChange={(e) => {
if (typeof props.onChange === "function") {
props.onChange(e.target.value);
}
}}
checked={props.checked}
className="float-right ml-3 text-neutral-900 focus:ring-neutral-500"
name={props.name}
value={props.value}
type="radio"
/>
{props.children}
</label>
);
};
type ChildrenProps = {
props: RadioAreaProps;
children?: ReactNode;
};
interface RadioAreaGroupProps extends Omit<React.ComponentPropsWithoutRef<"div">, "onChange" | "children"> {
children: ChildrenProps | ChildrenProps[];
name?: string;
onChange?: (value: string) => void;
}
const RadioAreaGroup = ({ children, name, onChange, ...passThroughProps }: RadioAreaGroupProps) => {
const [checkedIdx, setCheckedIdx] = useState<number | null>(null);
const changeHandler = (value: string, idx: number) => {
if (onChange) {
onChange(value);
}
setCheckedIdx(idx);
};
return (
<div {...passThroughProps}>
{(Array.isArray(children) ? children : [children]).map((child, idx: number) => {
if (checkedIdx === null && child.props.defaultChecked) {
setCheckedIdx(idx);
}
return (
<Item
{...child.props}
key={idx}
name={name}
checked={idx === checkedIdx}
onChange={(value: string) => changeHandler(value, idx)}>
{child.props.children}
</Item>
);
})}
</div>
);
};
const Item = RadioArea;
const Group = RadioAreaGroup;
export { RadioArea, RadioAreaGroup, Item, Group };
@@ -1,61 +0,0 @@
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from "@radix-ui/react-collapsible";
import React from "react";
import { Icon } from "@calcom/ui";
import classNames from "@lib/classNames";
import { useLocale } from "@lib/hooks/useLocale";
import { RadioArea, RadioAreaGroup } from "@components/ui/form/radio-area/RadioAreaGroup";
interface OptionProps
extends Pick<React.OptionHTMLAttributes<HTMLOptionElement>, "value" | "label" | "className"> {
description?: string;
}
interface RadioAreaSelectProps extends Omit<React.SelectHTMLAttributes<HTMLSelectElement>, "onChange"> {
options: OptionProps[]; // allow options to be passed programmatically, like options={}
onChange?: (value: string) => void;
}
export const Select = function RadioAreaSelect(props: RadioAreaSelectProps) {
const { t } = useLocale();
const {
options,
disabled = !options.length, // if not explicitly disabled and the options length is empty, disable anyway
placeholder = t("select"),
} = props;
const getLabel = (value: string | ReadonlyArray<string> | number | undefined) =>
options.find((option: OptionProps) => option.value === value)?.label;
return (
<Collapsible className={classNames("w-full", props.className)}>
<CollapsibleTrigger
type="button"
disabled={disabled}
className={classNames(
"border-1 focus:ring-primary-500 mb-1 block w-full cursor-pointer rounded-sm border border-gray-300 bg-white p-2 text-left text-sm",
disabled && "cursor-default bg-gray-200 focus:ring-0 "
)}>
{getLabel(props.value) ?? placeholder}
<Icon.FiChevronDown className="float-right h-5 w-5 text-neutral-500" />
</CollapsibleTrigger>
<CollapsibleContent>
<RadioAreaGroup className="space-y-2 text-sm" name={props.name} onChange={props.onChange}>
{options.map((option) => (
<RadioArea
{...option}
key={Array.isArray(option.value) ? option.value.join(",") : `${option.value}`}
defaultChecked={props.value === option.value}>
<strong className="mb-1 block">{option.label}</strong>
<p>{option.description}</p>
</RadioArea>
))}
</RadioAreaGroup>
</CollapsibleContent>
</Collapsible>
);
};
export default Select;
@@ -1,2 +0,0 @@
export * from "./RadioAreaGroup";
export * from "./Select";
+6
View File
@@ -89,6 +89,12 @@ const nextConfig = {
eslint: {
ignoreDuringBuilds: !!process.env.CI,
},
// TODO: We need to have all components in `@calcom/ui/components` in order to use this
// modularizeImports: {
// "@calcom/ui": {
// transform: "@calcom/ui/components/{{member}}",
// },
// },
images: {
unoptimized: true,
},
+5 -5
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "2.4.4",
"version": "2.4.6",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -67,6 +67,7 @@
"bcryptjs": "^2.4.3",
"classnames": "^2.3.1",
"dotenv-cli": "^6.0.0",
"eslint-config-next": "^13.1.1",
"googleapis": "^84.0.0",
"gray-matter": "^4.0.3",
"handlebars": "^4.7.7",
@@ -80,15 +81,14 @@
"memory-cache": "^0.2.0",
"micro": "^9.4.1",
"mime-types": "^2.1.35",
"next": "^12.3.1",
"next": "^13.1.1",
"next-auth": "^4.10.3",
"next-axiom": "^0.10.0",
"next-axiom": "^0.16.0",
"next-collect": "^0.2.1",
"next-i18next": "^11.3.0",
"next-mdx-remote": "^4.1.0",
"next-seo": "^4.26.0",
"next-themes": "^0.2.0",
"next-transpile-modules": "^9.0.0",
"next-transpile-modules": "^10.0.0",
"nock": "^13.2.8",
"nodemailer": "^6.7.8",
"otplib": "^12.0.1",
+46 -48
View File
@@ -98,26 +98,26 @@ export default function Custom404() {
<ul role="list" className="divide-y divide-gray-200 border-gray-200">
<li className="px-4 py-2">
<Link href={`${DEVELOPER_DOCS}/self-hosting/installation`}>
<a className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
<Link
href={`${DEVELOPER_DOCS}/self-hosting/installation`}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{t("prisma_studio_tip")}
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{t("prisma_studio_tip")}
</span>
</h3>
<p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</h3>
<p className="text-base text-gray-500">{t("prisma_studio_tip_description")}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</Link>
</li>
<li className="px-4 py-2">
@@ -167,11 +167,11 @@ export default function Custom404() {
</li>
</ul>
<div className="mt-8">
<Link href={`${WEBSITE_URL}/enterprise`}>
<a className="text-base font-medium text-black hover:text-gray-500">
{t("contact_sales")}
<span aria-hidden="true"> &rarr;</span>
</a>
<Link
href={`${WEBSITE_URL}/enterprise`}
className="text-base font-medium text-black hover:text-gray-500">
{t("contact_sales")}
<span aria-hidden="true"> &rarr;</span>
</Link>
</div>
</div>
@@ -243,26 +243,26 @@ export default function Custom404() {
<ul role="list" className="mt-4 divide-y divide-gray-200 border-gray-200">
{links.map((link, linkIdx) => (
<li key={linkIdx} className="px-4 py-2">
<Link href={link.href}>
<a className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
<Link
href={link.href}
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
<div className="flex-shrink-0">
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
<link.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</div>
<div className="min-w-0 flex-1">
<h3 className="text-base font-medium text-gray-900">
<span className="rounded-sm focus-within:ring-2 focus-within:ring-gray-500 focus-within:ring-offset-2">
<span className="absolute inset-0" aria-hidden="true" />
{link.title}
</span>
</h3>
<p className="text-base text-gray-500">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</a>
</h3>
<p className="text-base text-gray-500">{link.description}</p>
</div>
<div className="flex-shrink-0 self-center">
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
</div>
</Link>
</li>
))}
@@ -313,11 +313,9 @@ export default function Custom404() {
</li>
</ul>
<div className="mt-8">
<Link href="/">
<a className="text-base font-medium text-black hover:text-gray-500">
{t("or_go_back_home")}
<span aria-hidden="true"> &rarr;</span>
</a>
<Link href="/" className="text-base font-medium text-black hover:text-gray-500">
{t("or_go_back_home")}
<span aria-hidden="true"> &rarr;</span>
</Link>
</div>
</div>
+34 -36
View File
@@ -1,6 +1,5 @@
import classNames from "classnames";
import { GetServerSidePropsContext } from "next";
import dynamic from "next/dynamic";
import Link from "next/link";
import { useRouter } from "next/router";
import { useEffect } from "react";
@@ -60,25 +59,26 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
key={index}
className="dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600">
<Icon.FiArrowRight className="absolute right-3 top-3 h-4 w-4 text-black opacity-0 transition-opacity group-hover:opacity-100 dark:text-white" />
<Link href={getUsernameSlugLink({ users: props.users, slug: type.slug })}>
<a className="flex justify-between px-6 py-4" data-testid="event-type-link">
<div className="flex-shrink">
<p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p>
<EventTypeDescription className="text-sm" eventType={type} />
</div>
<div className="mt-1 self-center">
<AvatarGroup
border="border-2 border-white dark:border-darkgray-100"
truncateAfter={4}
className="flex flex-shrink-0"
size={10}
items={props.users.map((user) => ({
alt: user.name || "",
image: user.avatar || "",
}))}
/>
</div>
</a>
<Link
href={getUsernameSlugLink({ users: props.users, slug: type.slug })}
className="flex justify-between px-6 py-4"
data-testid="event-type-link">
<div className="flex-shrink">
<p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p>
<EventTypeDescription className="text-sm" eventType={type} />
</div>
<div className="mt-1 self-center">
<AvatarGroup
border="border-2 border-white dark:border-darkgray-100"
truncateAfter={4}
className="flex flex-shrink-0"
size={10}
items={props.users.map((user) => ({
alt: user.name || "",
image: user.avatar || "",
}))}
/>
</div>
</Link>
</li>
))}
@@ -175,22 +175,20 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
href={{
pathname: `/${user.username}/${type.slug}`,
query,
}}>
<a
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
className="block w-full p-5"
data-testid="event-type-link">
<div className="flex flex-wrap items-center">
<h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700">
{type.title}
</h2>
</div>
<EventTypeDescription eventType={type} />
</a>
}}
onClick={async () => {
sdkActionManager?.fire("eventTypeSelected", {
eventType: type,
});
}}
className="block w-full p-5"
data-testid="event-type-link">
<div className="flex flex-wrap items-center">
<h2 className="dark:text-darkgray-700 pr-2 text-sm font-semibold text-gray-700">
{type.title}
</h2>
</div>
<EventTypeDescription eventType={type} />
</Link>
</div>
))
-1
View File
@@ -53,7 +53,6 @@ class MyDocument extends Document<Props> {
return (
<Html lang={locale} dir={dir}>
<Head>
<link rel="preload" href="/cal.ttf" as="font" type="font/ttf" crossOrigin="anonymous" />
<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png" />
<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png" />
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
+20 -2
View File
@@ -35,6 +35,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
uid: "xxyPr4cg2xx4XoS2KeMEQy",
metadata: {},
recurringEvent: null,
appsStatus: [
{
appName: "Outlook Calendar",
type: "office365_calendar",
success: 1,
failures: 0,
errors: [],
warnings: [],
},
{
appName: "Google Meet",
type: "conferencing",
success: 0,
failures: 1,
errors: [],
warnings: ["In order to use Google Meet you must set your destination calendar to a Google Calendar"],
},
],
};
req.statusCode = 200;
@@ -42,9 +60,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
res.setHeader("Content-Type", "text/html");
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
res.write(
renderEmail("OrganizerRequestEmail", {
attendee: evt.attendees[0],
renderEmail("OrganizerScheduledEmail", {
calEvent: evt,
attendee: evt.organizer,
})
);
res.end();
+1 -1
View File
@@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from "next";
import type { Session } from "next-auth";
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
import { getSession } from "@calcom/lib/auth";
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
import prisma from "@calcom/prisma";
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
import { getSession } from "@lib/auth";
import { HttpError } from "@lib/core/http/error";
const defaultIntegrationAddHandler = async ({
+13 -25
View File
@@ -1,34 +1,18 @@
import fs from "fs";
import matter from "gray-matter";
import MarkdownIt from "markdown-it";
import { GetStaticPaths, GetStaticPropsContext } from "next";
import { MDXRemote } from "next-mdx-remote";
import { serialize } from "next-mdx-remote/serialize";
import Image from "next/image";
import Link from "next/link";
import path from "path";
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
import ExisitingGoogleCal from "@calcom/app-store/googlevideo/components/ExistingGoogleCal";
import prisma from "@calcom/prisma";
import { inferSSRProps } from "@lib/types/inferSSRProps";
import App from "@components/apps/App";
const components = {
a: ({ href = "", ...otherProps }: JSX.IntrinsicElements["a"]) => (
<Link href={href}>
<a {...otherProps} />
</Link>
),
img: ({ src = "", alt = "", placeholder, ...rest }: JSX.IntrinsicElements["img"]) => (
<Image src={src} alt={alt} {...rest} />
),
// @TODO: In v2 the slider isn't shown anymore. However, to ensure the v1 pages keep
// working, this component is still rendered in the MDX content. To skip them in the v2
// content we have to render null here. In v2 the gallery is shown by directly
// using the `items` property from the MDX's meta data.
Slider: () => <></>,
};
const md = new MarkdownIt("default", { html: true, breaks: true });
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
return (
@@ -50,10 +34,15 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
email={data.email}
licenseRequired={data.licenseRequired}
isProOnly={data.isProOnly}
images={source?.scope?.items as string[] | undefined}
images={source.data?.items as string[] | undefined}
// tos="https://zoom.us/terms"
// privacy="https://zoom.us/privacy"
body={<MDXRemote {...source} components={components} />}
body={
<>
{data.slug === "google-meet" && <ExisitingGoogleCal />}
<div dangerouslySetInnerHTML={{ __html: md.render(source.content) }} />
</>
}
/>
);
}
@@ -82,7 +71,7 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
if (!singleApp) return { notFound: true };
const appDirname = app.dirName;
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/README.mdx`);
const README_PATH = path.join(process.cwd(), "..", "..", `packages/app-store/${appDirname}/DESCRIPTION.md`);
const postFilePath = path.join(README_PATH);
let source = "";
@@ -90,16 +79,15 @@ export const getStaticProps = async (ctx: GetStaticPropsContext) => {
/* If the app doesn't have a README we fallback to the package description */
source = fs.readFileSync(postFilePath).toString();
} catch (error) {
console.log(`No README.mdx provided for: ${appDirname}`);
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
source = singleApp.description;
}
const { content, data } = matter(source);
const mdxSource = await serialize(content, { scope: data });
return {
props: {
source: mdxSource,
source: { content, data },
data: singleApp,
},
};
@@ -20,10 +20,10 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
backPath="/apps"
heading={
<>
<Link href="/apps">
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
{isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "}
</a>
<Link
href="/apps"
className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
{isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "}
</Link>
{category && (
<span className="gap-1 text-gray-600">
+17 -17
View File
@@ -11,28 +11,28 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
return (
<Shell isPublic large>
<div className="text-md flex items-center gap-1 px-4 pb-3 pt-3 font-normal md:px-8 lg:px-0 lg:pt-0">
<Link href="/apps">
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" />
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</a>
<Link
href="/apps"
className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
<Icon.FiArrowLeft className="h-4 w-4" />
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
</Link>
</div>
<div className="mb-16">
<div className="grid h-auto w-full grid-cols-5 gap-3">
{categories.map((category) => (
<Link key={category.name} href={"/apps/categories/" + category.name}>
<a
data-testid={`app-store-category-${category.name}`}
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<div className="self-center">
<h3 className="font-medium capitalize">{category.name}</h3>
<p className="text-sm text-gray-500">
{t("number_apps", { count: category.count })}{" "}
<Icon.FiArrowRight className="inline-block h-4 w-4" />
</p>
</div>
</a>
<Link
key={category.name}
href={"/apps/categories/" + category.name}
data-testid={`app-store-category-${category.name}`}
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
<div className="self-center">
<h3 className="font-medium capitalize">{category.name}</h3>
<p className="text-sm text-gray-500">
{t("number_apps", { count: category.count })}{" "}
<Icon.FiArrowRight className="inline-block h-4 w-4" />
</p>
</div>
</Link>
))}
</div>
@@ -183,6 +183,7 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
buttonRaw={
<Button
color="secondary"
data-testid={`connect-${variant || "other"}-apps`}
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
{t(`connect_${variant || "other"}_apps`)}
</Button>
+1 -1
View File
@@ -40,7 +40,7 @@ export default function Error() {
</div>
</div>
<div className="mt-5 sm:mt-6">
<Link href="/auth/login" passHref>
<Link href="/auth/login" passHref legacyBehavior>
<Button className="flex w-full justify-center">{t("go_back_login")}</Button>
</Link>
</div>
+1 -1
View File
@@ -81,7 +81,7 @@ export default function Page({ resetPasswordRequest, csrfToken }: Props) {
<h2 className="text-center text-3xl font-extrabold text-gray-900">{t("request_is_expired")}</h2>
</div>
<p>{t("request_is_expired_instructions")}</p>
<Link href="/auth/forgot-password" passHref>
<Link href="/auth/forgot-password" passHref legacyBehavior>
<button
type="button"
className="flex w-full justify-center px-4 py-2 text-sm font-medium text-blue-600 focus:outline-none focus:ring-2 focus:ring-black focus:ring-offset-2">
@@ -94,8 +94,8 @@ export default function ForgotPassword({ csrfToken }: { csrfToken: string }) {
footerText={
!success && (
<>
<Link href="/auth/login">
<a className="font-medium text-neutral-900">{t("back_to_signin")}</a>
<Link href="/auth/login" className="font-medium text-neutral-900">
{t("back_to_signin")}
</Link>
</>
)
+5 -4
View File
@@ -133,10 +133,11 @@ export default function Login({
/>
<div className="relative">
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
<Link href="/auth/forgot-password">
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
{t("forgot")}
</a>
<Link
href="/auth/forgot-password"
tabIndex={-1}
className="text-sm font-medium text-gray-600">
{t("forgot")}
</Link>
</div>
<PasswordField
+1 -1
View File
@@ -27,7 +27,7 @@ export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
{
title: t("enable_apps"),
description: t("enable_apps_description"),
content: <AdminAppsList baseURL="/auth/setup?step=2" />,
content: <AdminAppsList baseURL="/auth/setup?step=2" useQueryParam={true} />,
isLoading: false,
},
];
+52 -56
View File
@@ -353,10 +353,10 @@ export default function Success(props: SuccessProps) {
)}
{userIsOwner && !isEmbed && (
<div className="mt-2 ml-4 -mb-4">
<Link href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}>
<a className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
</a>
<Link
href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}
className="mt-2 inline-flex px-1 py-2 text-sm text-gray-500 hover:bg-gray-100 hover:text-gray-800 dark:hover:bg-transparent dark:hover:text-white">
<Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
</Link>
</div>
)}
@@ -573,7 +573,9 @@ export default function Success(props: SuccessProps) {
{!props.recurringBookings && (
<span className="text-bookinglight inline text-gray-700">
<span className="underline">
<Link href={`/reschedule/${bookingInfo?.uid}`}>{t("reschedule")}</Link>
<Link href={`/reschedule/${bookingInfo?.uid}`} legacyBehavior>
{t("reschedule")}
</Link>
</span>
<span className="mx-2">{t("or_lowercase")}</span>
</span>
@@ -633,17 +635,16 @@ export default function Success(props: SuccessProps) {
? "&recur=" +
encodeURIComponent(new RRule(props.eventType.recurringEvent).toString())
: "")
}>
<a className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white">
<svg
className="-mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Google</title>
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
</svg>
</a>
}
className="h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 ltr:mr-2 rtl:ml-2 dark:border-neutral-700 dark:text-white">
<svg
className="-mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Google</title>
<path d="M12.48 10.92v3.28h7.84c-.24 1.84-.853 3.187-1.787 4.133-1.147 1.147-2.933 2.4-6.053 2.4-4.827 0-8.6-3.893-8.6-8.72s3.773-8.72 8.6-8.72c2.6 0 4.507 1.027 5.907 2.347l2.307-2.307C18.747 1.44 16.133 0 12.48 0 5.867 0 .307 5.387.307 12s5.56 12 12.173 12c3.573 0 6.267-1.173 8.373-3.36 2.16-2.16 2.84-5.213 2.84-7.667 0-.76-.053-1.467-.173-2.053H12.48z" />
</svg>
</Link>
<Link
href={
@@ -657,19 +658,17 @@ export default function Success(props: SuccessProps) {
"&subject=" +
eventName
) + (location ? "&location=" + location : "")
}>
<a
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Outlook</title>
<path d="M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z" />
</svg>
</a>
}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Outlook</title>
<path d="M7.88 12.04q0 .45-.11.87-.1.41-.33.74-.22.33-.58.52-.37.2-.87.2t-.85-.2q-.35-.21-.57-.55-.22-.33-.33-.75-.1-.42-.1-.86t.1-.87q.1-.43.34-.76.22-.34.59-.54.36-.2.87-.2t.86.2q.35.21.57.55.22.34.31.77.1.43.1.88zM24 12v9.38q0 .46-.33.8-.33.32-.8.32H7.13q-.46 0-.8-.33-.32-.33-.32-.8V18H1q-.41 0-.7-.3-.3-.29-.3-.7V7q0-.41.3-.7Q.58 6 1 6h6.5V2.55q0-.44.3-.75.3-.3.75-.3h12.9q.44 0 .75.3.3.3.3.75V10.85l1.24.72h.01q.1.07.18.18.07.12.07.25zm-6-8.25v3h3v-3zm0 4.5v3h3v-3zm0 4.5v1.83l3.05-1.83zm-5.25-9v3h3.75v-3zm0 4.5v3h3.75v-3zm0 4.5v2.03l2.41 1.5 1.34-.8v-2.73zM9 3.75V6h2l.13.01.12.04v-2.3zM5.98 15.98q.9 0 1.6-.3.7-.32 1.19-.86.48-.55.73-1.28.25-.74.25-1.61 0-.83-.25-1.55-.24-.71-.71-1.24t-1.15-.83q-.68-.3-1.55-.3-.92 0-1.64.3-.71.3-1.2.85-.5.54-.75 1.3-.25.74-.25 1.63 0 .85.26 1.56.26.72.74 1.23.48.52 1.17.81.69.3 1.56.3zM7.5 21h12.39L12 16.08V17q0 .41-.3.7-.29.3-.7.3H7.5zm15-.13v-7.24l-5.9 3.54Z" />
</svg>
</Link>
<Link
href={
@@ -683,34 +682,31 @@ export default function Success(props: SuccessProps) {
"&subject=" +
eventName
) + (location ? "&location=" + location : "")
}>
<a
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Office</title>
<path d="M21.53 4.306v15.363q0 .807-.472 1.433-.472.627-1.253.85l-6.888 1.974q-.136.037-.29.055-.156.019-.293.019-.396 0-.72-.105-.321-.106-.656-.292l-4.505-2.544q-.248-.137-.391-.366-.143-.23-.143-.515 0-.434.304-.738.304-.305.739-.305h5.831V4.964l-4.38 1.563q-.533.187-.856.658-.322.472-.322 1.03v8.078q0 .496-.248.912-.25.416-.683.651l-2.072 1.13q-.286.148-.571.148-.497 0-.844-.347-.348-.347-.348-.844V6.563q0-.62.33-1.19.328-.571.874-.881L11.07.285q.248-.136.534-.21.285-.075.57-.075.211 0 .38.031.166.031.364.093l6.888 1.899q.384.11.7.329.317.217.547.52.23.305.353.67.125.367.125.764zm-1.588 15.363V4.306q0-.273-.16-.478-.163-.204-.423-.28l-3.388-.93q-.397-.111-.794-.23-.397-.117-.794-.216v19.68l4.976-1.427q.26-.074.422-.28.161-.204.161-.477z" />
</svg>
</a>
}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
target="_blank">
<svg
className="mr-1 -mt-1.5 inline-block h-4 w-4"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24">
<title>Microsoft Office</title>
<path d="M21.53 4.306v15.363q0 .807-.472 1.433-.472.627-1.253.85l-6.888 1.974q-.136.037-.29.055-.156.019-.293.019-.396 0-.72-.105-.321-.106-.656-.292l-4.505-2.544q-.248-.137-.391-.366-.143-.23-.143-.515 0-.434.304-.738.304-.305.739-.305h5.831V4.964l-4.38 1.563q-.533.187-.856.658-.322.472-.322 1.03v8.078q0 .496-.248.912-.25.416-.683.651l-2.072 1.13q-.286.148-.571.148-.497 0-.844-.347-.348-.347-.348-.844V6.563q0-.62.33-1.19.328-.571.874-.881L11.07.285q.248-.136.534-.21.285-.075.57-.075.211 0 .38.031.166.031.364.093l6.888 1.899q.384.11.7.329.317.217.547.52.23.305.353.67.125.367.125.764zm-1.588 15.363V4.306q0-.273-.16-.478-.163-.204-.423-.28l-3.388-.93q-.397-.111-.794-.23-.397-.117-.794-.216v19.68l4.976-1.427q.26-.074.422-.28.161-.204.161-.477z" />
</svg>
</Link>
<Link href={"data:text/calendar," + eventLink()}>
<a
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
download={props.eventType.title + ".ics"}>
<svg
version="1.1"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
className="mr-1 -mt-1.5 inline-block h-4 w-4">
<title>{t("other")}</title>
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
</svg>
</a>
<Link
href={"data:text/calendar," + eventLink()}
className="mx-2 h-10 w-10 rounded-sm border border-neutral-200 px-3 py-2 dark:border-neutral-700 dark:text-white"
download={props.eventType.title + ".ics"}>
<svg
version="1.1"
fill="currentColor"
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 1000 1000"
className="mr-1 -mt-1.5 inline-block h-4 w-4">
<title>{t("other")}</title>
<path d="M971.3,154.9c0-34.7-28.2-62.9-62.9-62.9H611.7c-1.3,0-2.6,0.1-3.9,0.2V10L28.7,87.3v823.4L607.8,990v-84.6c1.3,0.1,2.6,0.2,3.9,0.2h296.7c34.7,0,62.9-28.2,62.9-62.9V154.9z M607.8,636.1h44.6v-50.6h-44.6v-21.9h44.6v-50.6h-44.6v-92h277.9v230.2c0,3.8-3.1,7-7,7H607.8V636.1z M117.9,644.7l-50.6-2.4V397.5l50.6-2.2V644.7z M288.6,607.3c17.6,0.6,37.3-2.8,49.1-7.2l9.1,48c-11,5.1-35.6,9.9-66.9,8.3c-85.4-4.3-127.5-60.7-127.5-132.6c0-86.2,57.8-136.7,133.2-140.1c30.3-1.3,53.7,4,64.3,9.2l-12.2,48.9c-12.1-4.9-28.8-9.2-49.5-8.6c-45.3,1.2-79.5,30.1-79.5,87.4C208.8,572.2,237.8,605.7,288.6,607.3z M455.5,665.2c-32.4-1.6-63.7-11.3-79.1-20.5l12.6-50.7c16.8,9.1,42.9,18.5,70.4,19.4c30.1,1,46.3-10.7,46.3-29.3c0-17.8-14-28.1-48.8-40.6c-46.9-16.4-76.8-41.7-76.8-81.5c0-46.6,39.3-84.1,106.8-87.1c33.3-1.5,58.3,4.2,76.5,11.2l-15.4,53.3c-12.1-5.3-33.5-12.8-62.3-12c-28.3,0.8-41.9,13.6-41.9,28.1c0,17.8,16.1,25.5,53.6,39c52.9,18.5,78.4,45.3,78.4,86.4C575.6,629.7,536.2,669.2,455.5,665.2z M935.3,842.7c0,14.9-12.1,27-27,27H611.7c-1.3,0-2.6-0.2-3.9-0.4V686.2h270.9c19.2,0,34.9-15.6,34.9-34.9V398.4c0-19.2-15.6-34.9-34.9-34.9h-47.1v-32.3H808v32.3h-44.8v-32.3h-22.7v32.3h-43.3v-32.3h-22.7v32.3H628v-32.3h-20.2v-203c1.31.2,2.6-0.4,3.9-0.4h296.7c14.9,0,27,12.1,27,27L935.3,842.7L935.3,842.7z" />
</svg>
</Link>
</div>
</div>
+4 -3
View File
@@ -3,7 +3,6 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
import { zodResolver } from "@hookform/resolvers/zod";
import { PeriodType, SchedulingType } from "@prisma/client";
import { GetServerSidePropsContext } from "next";
import { useRouter } from "next/router";
import { useState } from "react";
import { useForm } from "react-hook-form";
import { z } from "zod";
@@ -15,6 +14,7 @@ import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
import findDurationType from "@calcom/lib/findDurationType";
import getEventTypeById from "@calcom/lib/getEventTypeById";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
import { HttpError } from "@calcom/lib/http-error";
import prisma from "@calcom/prisma";
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
@@ -111,8 +111,9 @@ export type EventTypeSetupProps = RouterOutputs["viewer"]["eventTypes"]["get"];
const EventTypePage = (props: EventTypeSetupProps) => {
const { t } = useLocale();
const utils = trpc.useContext();
const router = useRouter();
const { tabName } = querySchema.parse(router.query);
const {
data: { tabName },
} = useTypedQuery(querySchema);
const { data: eventTypeApps } = trpc.viewer.apps.useQuery({
extendsFeature: "EventType",
+55 -65
View File
@@ -64,27 +64,26 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
const { t } = useLocale();
return (
<Link href={`/event-types/${type.id}?tabName=setup`}>
<a
className="flex-1 overflow-hidden pr-4 text-sm"
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
<div>
<span
className="font-semibold text-gray-700 ltr:mr-1 rtl:ml-1"
data-testid={"event-type-title-" + type.id}>
{type.title}
<Link
href={`/event-types/${type.id}?tabName=setup`}
className="flex-1 overflow-hidden pr-4 text-sm"
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
<div>
<span
className="font-semibold text-gray-700 ltr:mr-1 rtl:ml-1"
data-testid={"event-type-title-" + type.id}>
{type.title}
</span>
<small
className="hidden font-normal leading-4 text-gray-600 sm:inline"
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
{readOnly && (
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
{t("readonly")}
</span>
<small
className="hidden font-normal leading-4 text-gray-600 sm:inline"
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
{readOnly && (
<span className="rtl:ml-2inline items-center rounded-sm bg-gray-100 px-1.5 py-0.5 text-xs font-medium text-gray-800 ltr:ml-2 ltr:mr-2">
{t("readonly") as string}
</span>
)}
</div>
<EventTypeDescription eventType={type} />
</a>
)}
</div>
<EventTypeDescription eventType={type} />
</Link>
);
};
@@ -297,7 +296,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
{t("hidden")}
</Badge>
)}
<Tooltip content={t("show_eventtype_on_profile") as string}>
<Tooltip content={t("show_eventtype_on_profile")}>
<div className="self-center rounded-md p-2 hover:bg-gray-200">
<Switch
name="Hidden"
@@ -310,7 +309,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
</Tooltip>
<ButtonGroup combined>
<Tooltip content={t("preview") as string}>
<Tooltip content={t("preview")}>
<Button
color="secondary"
target="_blank"
@@ -320,7 +319,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
/>
</Tooltip>
<Tooltip content={t("copy_link") as string}>
<Tooltip content={t("copy_link")}>
<Button
color="secondary"
size="icon"
@@ -350,7 +349,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-edit-" + type.id}
StartIcon={Icon.FiEdit2}
onClick={() => router.push("/event-types/" + type.id)}>
{t("edit") as string}
{t("edit")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -359,7 +358,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string}
{t("duplicate")}
</DropdownItem>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -383,7 +382,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
}}
StartIcon={Icon.FiTrash}
className="w-full rounded-none">
{t("delete") as string}
{t("delete")}
</DropdownItem>
</DropdownMenuItem>
)}
@@ -402,15 +401,13 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
<DropdownMenuPortal>
<DropdownMenuContent>
<DropdownMenuItem className="outline-none">
<Link href={calLink}>
<a target="_blank">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
className="w-full rounded-none">
{t("preview") as string}
</Button>
</a>
<Link href={calLink} target="_blank">
<Button
color="minimal"
StartIcon={Icon.FiExternalLink}
className="w-full rounded-none">
{t("preview")}
</Button>
</Link>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -424,7 +421,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
navigator.clipboard.writeText(calLink);
showToast(t("link_copied"), "success");
}}>
{t("copy_link") as string}
{t("copy_link")}
</Button>
</DropdownMenuItem>
{isNativeShare ? (
@@ -445,7 +442,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
.then(() => showToast(t("link_shared"), "success"))
.catch(() => showToast(t("failed"), "error"));
}}>
{t("share") as string}
{t("share")}
</Button>
</DropdownMenuItem>
) : null}
@@ -456,7 +453,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="minimal"
className="w-full rounded-none"
StartIcon={Icon.FiEdit}>
{t("edit") as string}
{t("edit")}
</Button>
</DropdownMenuItem>
<DropdownMenuItem className="outline-none">
@@ -467,7 +464,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
data-testid={"event-type-duplicate-" + type.id}
StartIcon={Icon.FiCopy}
onClick={() => openDuplicateModal(type)}>
{t("duplicate") as string}
{t("duplicate")}
</Button>
</DropdownMenuItem>
<DropdownMenuSeparator className="h-px bg-gray-200" />
@@ -480,7 +477,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
color="destructive"
StartIcon={Icon.FiTrash}
className="w-full rounded-none">
{t("delete") as string}
{t("delete")}
</Button>
</DropdownMenuItem>
</DropdownMenuContent>
@@ -502,7 +499,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
e.preventDefault();
deleteEventTypeHandler(deleteDialogTypeId);
}}>
{t("delete_event_type_description") as string}
{t("delete_event_type_description")}
</ConfirmationDialogContent>
</Dialog>
</div>
@@ -517,36 +514,32 @@ const EventTypeListHeading = ({
return (
<div className="mb-4 flex">
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a>
<Avatar
alt={profile?.name || ""}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size={8}
className="mt-1 inline ltr:mr-2 rtl:ml-2"
/>
</a>
<Avatar
alt={profile?.name || ""}
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
size={8}
className="mt-1 inline ltr:mr-2 rtl:ml-2"
/>
</Link>
<div>
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
<a className="font-bold">{profile?.name || ""}</a>
<Link
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
className="font-bold">
{profile?.name || ""}
</Link>
{membershipCount && teamId && (
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
<Link href={`/settings/teams/${teamId}/members`}>
<a>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
{membershipCount}
</Badge>
</a>
<Badge variant="gray">
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
{membershipCount}
</Badge>
</Link>
</span>
)}
{profile?.slug && (
<Link href={`${CAL_URL}/${profile.slug}`}>
<a className="block text-xs text-neutral-500">{`${CAL_URL?.replace("https://", "")}/${
profile.slug
}`}</a>
<Link href={`${CAL_URL}/${profile.slug}`} className="block text-xs text-neutral-500">
{`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
</Link>
)}
</div>
@@ -581,10 +574,7 @@ const EventTypesPage = () => {
return (
<div>
<Shell
heading={t("event_types_page_title") as string}
subtitle={t("event_types_page_subtitle") as string}
CTA={<CTA />}>
<Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
<WithQuery
customLoader={<SkeletonLoader />}
success={({ data }) => (
@@ -181,7 +181,9 @@ const ProfileView = () => {
};
if (isLoading || !user || isLoadingAvatar || !avatar)
return <SkeletonLoader title={t("profile")} description={t("profile_description")} />;
return (
<SkeletonLoader title={t("profile")} description={t("profile_description", { appName: APP_NAME })} />
);
const defaultValues = {
username: user.username || "",
+47 -41
View File
@@ -45,28 +45,29 @@ function TeamPage({ team }: TeamPageProps) {
"dark:bg-darkgray-100 group relative border-b border-neutral-200 bg-white first:rounded-t-md last:rounded-b-md last:border-b-0 hover:bg-gray-50 dark:border-neutral-700 dark:hover:border-neutral-600",
!isEmbed && "bg-white"
)}>
<Link href={`/team/${team.slug}/${type.slug}`}>
<a className="flex justify-between px-6 py-4" data-testid="event-type-link">
<div className="flex-shrink">
<div className="flex flex-wrap items-center space-x-2 rtl:space-x-reverse">
<h2 className="dark:text-darkgray-700 text-sm font-semibold text-gray-700">{type.title}</h2>
</div>
<EventTypeDescription className="text-sm" eventType={type} />
<Link
href={`/team/${team.slug}/${type.slug}`}
className="flex justify-between px-6 py-4"
data-testid="event-type-link">
<div className="flex-shrink">
<div className="flex flex-wrap items-center space-x-2 rtl:space-x-reverse">
<h2 className="dark:text-darkgray-700 text-sm font-semibold text-gray-700">{type.title}</h2>
</div>
<div className="mt-1 self-center">
<AvatarGroup
border="border-2 border-white dark:border-darkgray-100"
truncateAfter={4}
className="flex flex-shrink-0"
size={10}
items={type.users.map((user) => ({
alt: user.name || "",
title: user.name || "",
image: CAL_URL + "/" + user.username + "/avatar.png" || "",
}))}
/>
</div>
</a>
<EventTypeDescription className="text-sm" eventType={type} />
</div>
<div className="mt-1 self-center">
<AvatarGroup
border="border-2 border-white dark:border-darkgray-100"
truncateAfter={4}
className="flex flex-shrink-0"
size={10}
items={type.users.map((user) => ({
alt: user.name || "",
title: user.name || "",
image: CAL_URL + "/" + user.username + "/avatar.png" || "",
}))}
/>
</div>
</Link>
</li>
))}
@@ -97,27 +98,32 @@ function TeamPage({ team }: TeamPageProps) {
{!showMembers.isOn && team.eventTypes.length > 0 && (
<div className="mx-auto max-w-3xl ">
<EventTypes />
<div className="relative mt-12">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="dark:border-darkgray-300 w-full border-t border-gray-200" />
</div>
<div className="relative flex justify-center">
<span className="dark:bg-darkgray-50 bg-gray-100 px-2 text-sm text-gray-500 dark:text-white">
{t("or")}
</span>
</div>
</div>
<aside className="mt-8 flex justify-center text-center dark:text-white">
<Button
color="minimal"
EndIcon={Icon.FiArrowRight}
className="dark:hover:bg-darkgray-200"
href={`/team/${team.slug}?members=1`}
shallow={true}>
{t("book_a_team_member")}
</Button>
</aside>
{!team.hideBookATeamMember && (
<div>
<div className="relative mt-12">
<div className="absolute inset-0 flex items-center" aria-hidden="true">
<div className="dark:border-darkgray-300 w-full border-t border-gray-200" />
</div>
<div className="relative flex justify-center">
<span className="dark:bg-darkgray-50 bg-gray-100 px-2 text-sm text-gray-500 dark:text-white">
{t("or")}
</span>
</div>
</div>
<aside className="mt-8 flex justify-center text-center dark:text-white">
<Button
color="minimal"
EndIcon={Icon.FiArrowRight}
className="dark:hover:bg-darkgray-200"
href={`/team/${team.slug}?members=1`}
shallow={true}>
{t("book_a_team_member")}
</Button>
</aside>
</div>
)}
</div>
)}
</main>
+11
View File
@@ -14,6 +14,17 @@ test.describe("App Store - Authed", () => {
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
await pro.delete();
});
test("Installed Apps - Navigation", async ({ page, users }) => {
const user = await users.create();
await user.login();
await page.goto("/apps/installed");
await page.waitForSelector('[data-testid="connect-calendar-apps"]');
await page.click('[data-testid="vertical-tab-payment"]');
await page.waitForSelector('[data-testid="connect-payment-apps"]');
await page.click('[data-testid="vertical-tab-automation"]');
await page.waitForSelector('[data-testid="connect-automation-apps"]');
});
});
test.describe("App Store - Unauthed", () => {
+2 -1
View File
@@ -5,7 +5,8 @@ import { bookTimeSlot, selectFirstAvailableTimeSlotNextMonth } from "./lib/testU
test.describe.configure({ mode: "parallel" });
test.describe("hash my url", () => {
// TODO: This test is very flaky. Feels like tossing a coin and hope that it won't fail. Needs to be revisited.
test.fixme("hash my url", () => {
test.beforeEach(async ({ users }) => {
const user = await users.create();
await user.login();
@@ -1077,13 +1077,13 @@
"new_seat_subject": "حضور جديد {{name}} لـ {{eventType}} في {{date}}",
"new_seat_title": "شخص ما أضاف نفسه إلى حدث",
"variable": "متغير",
"event_name_workflow": "اسم الحدث",
"organizer_name_workflow": "المنظِّم",
"attendee_name_workflow": "الحاضر",
"event_date_workflow": "تاريخ الحدث",
"event_time_workflow": "وقت الحدث",
"location_workflow": "الموقع",
"additional_notes_workflow": "ملاحظات إضافية",
"event_name_variable": "اسم الحدث",
"organizer_name_variable": "المنظِّم",
"attendee_name_variable": "الحاضر",
"event_date_variable": "تاريخ الحدث",
"event_time_variable": "وقت الحدث",
"location_variable": "الموقع",
"additional_notes_variable": "ملاحظات إضافية",
"app_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.",
"invalid_number": "رقم الهاتف غير صالح",
"navigate": "تنقّل",
@@ -1387,7 +1387,7 @@
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
"team_publish": "نشر فريق",
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
"attendee_email_workflow": "اسم الحاضر",
"attendee_email_variable": "اسم الحاضر",
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
"invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nový účastník {{name}} události {{eventType}} dne {{date}}",
"new_seat_title": "Někdo se přidal k události",
"variable": "Proměnná",
"event_name_workflow": "Název události",
"organizer_name_workflow": "Jméno organizátora",
"attendee_name_workflow": "Jméno účastníka",
"event_date_workflow": "Datum události",
"event_time_workflow": "Čas události",
"location_workflow": "Místo",
"additional_notes_workflow": "Doplňující poznámky",
"event_name_variable": "Název události",
"organizer_name_variable": "Jméno organizátora",
"attendee_name_variable": "Jméno účastníka",
"event_date_variable": "Datum události",
"event_time_variable": "Čas události",
"location_variable": "Místo",
"additional_notes_variable": "Doplňující poznámky",
"app_upgrade_description": "Pokud chcete použít tuto funkci, musíte provést aktualizaci na účet Pro.",
"invalid_number": "Neplatné telefonní číslo",
"navigate": "Navigace",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Tato adresa URL je už obsazená",
"team_publish": "Zveřejnit tým",
"number_sms_notifications": "Číslo telefonu (SMS oznámení)",
"attendee_email_workflow": "E-mail účastníka",
"attendee_email_variable": "E-mail účastníka",
"attendee_email_info": "E-mail osoby provádějící rezervaci",
"kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...",
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte novou instalaci.",
@@ -1078,13 +1078,13 @@
"new_seat_subject": "Neuer Teilnehmer {{name}} beim {{eventType}} am {{date}}",
"new_seat_title": "Jemand hat sich zu einem Ereignis hinzugefügt",
"variable": "Variable",
"event_name_workflow": "Event Name",
"organizer_name_workflow": "Organisator",
"attendee_name_workflow": "Teilnehmer",
"event_date_workflow": "Event Datum",
"event_time_workflow": "Event Zeit",
"location_workflow": "Ort",
"additional_notes_workflow": "Zusätzliche Notizen",
"event_name_variable": "Event Name",
"organizer_name_variable": "Organisator",
"attendee_name_variable": "Teilnehmer",
"event_date_variable": "Event Datum",
"event_time_variable": "Event Zeit",
"location_variable": "Ort",
"additional_notes_variable": "Zusätzliche Notizen",
"app_upgrade_description": "Um diese Funktion nutzen zu können, müssen Sie ein Upgrade auf einen Pro-Account durchführen.",
"invalid_number": "Ungültige Telefonnummer",
"navigate": "Navigieren",
@@ -1388,7 +1388,7 @@
"team_url_taken": "Diese URL ist bereits vergeben",
"team_publish": "Team veröffentlichen",
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
"attendee_email_workflow": "Teilnehmer E-Mail",
"attendee_email_variable": "Teilnehmer E-Mail",
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen ist oder entzogen wurden. Bitte erneut installieren.",
+19 -10
View File
@@ -521,6 +521,8 @@
"change_member_role": "Change team member role",
"disable_cal_branding": "Disable {{appName}} branding",
"disable_cal_branding_description": "Hide all {{appName}} branding from your public pages.",
"hide_book_a_team_member": "Hide Book a Team Member Button",
"hide_book_a_team_member_description": "Hide Book a Team Member Button from your public pages.",
"danger_zone": "Danger zone",
"back": "Back",
"cancel": "Cancel",
@@ -1082,13 +1084,13 @@
"new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}",
"new_seat_title": "Someone has added themselves to an event",
"variable": "Variable",
"event_name_workflow": "Event name",
"organizer_name_workflow": "Organizer",
"attendee_name_workflow": "Attendee",
"event_date_workflow": "Event date",
"event_time_workflow": "Event time",
"location_workflow": "Location",
"additional_notes_workflow": "Additional notes",
"event_name_variable": "Event name",
"organizer_name_variable": "Organizer",
"attendee_name_variable": "Attendee",
"event_date_variable": "Event date",
"event_time_variable": "Event time",
"location_variable": "Location",
"additional_notes_variable": "Additional notes",
"app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
"invalid_number": "Invalid phone number",
"navigate": "Navigate",
@@ -1402,7 +1404,7 @@
"team_url_taken": "This URL is already taken",
"team_publish": "Publish team",
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
"attendee_email_workflow": "Attendee email",
"attendee_email_variable": "Attendee email",
"attendee_email_info": "The person booking's email",
"kbar_search_placeholder": "Type a command or search...",
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
@@ -1454,10 +1456,11 @@
"enter_option": "Enter Option {{index}}",
"add_an_option": "Add an option",
"radio": "Radio",
"google_meet_warning": "In order to use Google Meet you must set your destination calendar to a Google Calendar",
"individual":"Individual",
"all_bookings_filter_label":"All Bookings",
"all_users_filter_label":"All Users",
"meeting_url_workflow": "Meeting url",
"meeting_url_variable": "Meeting url",
"meeting_url_info": "The event meeting conference url",
"date_overrides": "Date overrides",
"date_overrides_subtitle": "Add dates when your availability changes from your daily hours.",
@@ -1488,5 +1491,11 @@
"disable_success_page":"Disable Success Page (only works if you have a redirect URL)",
"invalid_admin_password": "You are set as an admin but you do not have a password length of at least 15 characters",
"change_password_admin": "Change Password to gain admin access",
"username_already_taken": "Username is already taken"
"username_already_taken": "Username is already taken",
"requires_google_calendar": "This app requires a Google Calendar connection",
"connected_google_calendar": "You have connected a Google Calendar account.",
"using_meet_requires_calendar": "Using Google Meet requires a connected Google Calendar",
"continue_to_install_google_calendar": "Continue to install Google Calendar",
"install_google_meet": "Install Google Meet",
"install_google_calendar": "Install Google Calendar"
}
+10 -10
View File
@@ -511,7 +511,7 @@
"add_team_members_description": "Invitar a otros a unirse a tu equipo",
"add_team_member": "Añadir miembro del equipo",
"invite_new_member": "Invitar a un nuevo miembro",
"invite_new_member_description": "Nota: Esto costará <1>un cupo adicional ($15/m)</1> en su suscripción.",
"invite_new_member_description": "Nota: Esto costará <1>una plaza adicional ($15/m)</1> en su suscripción.",
"invite_new_team_member": "Invita a alguien a tu equipo.",
"change_member_role": "Cambiar rol del miembro del equipo",
"disable_cal_branding": "Desactivar marca de {{appName}}",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nuevo asistente {{name}} en {{eventType}} el {{date}}",
"new_seat_title": "Alguien se ha añadido a un evento",
"variable": "Variable",
"event_name_workflow": "Nombre del evento",
"organizer_name_workflow": "Nombre del organizador",
"attendee_name_workflow": "Nombre del asistente",
"event_date_workflow": "Fecha del evento",
"event_time_workflow": "Hora del evento",
"location_workflow": "Lugar",
"additional_notes_workflow": "Notas Adicionales",
"event_name_variable": "Nombre del evento",
"organizer_name_variable": "Nombre del organizador",
"attendee_name_variable": "Nombre del asistente",
"event_date_variable": "Fecha del evento",
"event_time_variable": "Hora del evento",
"location_variable": "Lugar",
"additional_notes_variable": "Notas Adicionales",
"app_upgrade_description": "Para poder usar esta función, necesita actualizarse a una cuenta Pro.",
"invalid_number": "Número de teléfono no válido",
"navigate": "Navegar",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Esta URL ya está en uso",
"team_publish": "Publicar equipo",
"number_sms_notifications": "Número de teléfono (notificaciones por SMS)",
"attendee_email_workflow": "Correo electrónico del asistente",
"attendee_email_variable": "Correo electrónico del asistente",
"attendee_email_info": "El correo electrónico de la persona que reserva",
"kbar_search_placeholder": "Escriba un comando o búsqueda...",
"invalid_credential": "¡Oh no! Parece que el permiso ha caducado o se ha revocado. Vuelva a instalarlo.",
@@ -1396,7 +1396,7 @@
"sender_id": "ID del remitente",
"sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)",
"test_routing_form": "Prueba de formulario de ruta",
"test_preview": "Vista previa de la prueba",
"test_preview": "Probar vista previa",
"route_to": "Ruta hacia",
"test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato",
"test_routing": "Prueba de ruta",
File diff suppressed because it is too large Load Diff
@@ -1076,13 +1076,13 @@
"new_seat_subject": "משתתף/ת חדש/ה {{name}} ב{{eventType}} בתאריך {{date}}",
"new_seat_title": "מישהו הוסיף את עצמו לאירוע",
"variable": "משתנה",
"event_name_workflow": "שם האירוע",
"organizer_name_workflow": "שם המארגן/ת",
"attendee_name_workflow": "שם המשתתף/ת",
"event_date_workflow": "תאריך האירוע",
"event_time_workflow": "שעת האירוע",
"location_workflow": "מיקום",
"additional_notes_workflow": "הערות נוספות",
"event_name_variable": "שם האירוע",
"organizer_name_variable": "שם המארגן/ת",
"attendee_name_variable": "שם המשתתף/ת",
"event_date_variable": "תאריך האירוע",
"event_time_variable": "שעת האירוע",
"location_variable": "מיקום",
"additional_notes_variable": "הערות נוספות",
"app_upgrade_description": "כדי להשתמש בתכונה זו, עליך לשדרג לחשבון Pro.",
"invalid_number": "מספר טלפון לא תקין",
"navigate": "ניווט",
@@ -1386,7 +1386,7 @@
"team_url_taken": "כתובת ה-URL הזו כבר תפוסה",
"team_publish": "פרסום צוות",
"number_sms_notifications": "מספר טלפון (הודעות SMS)",
"attendee_email_workflow": "כתובת הדוא\"ל של המשתתף/ת",
"attendee_email_variable": "כתובת הדוא\"ל של המשתתף/ת",
"attendee_email_info": "כתובת הדוא\"ל של האדם שביצע את ההזמנה",
"kbar_search_placeholder": "הקלד/י פקודה או חפש/י...",
"invalid_credential": "נראה שתוקף ההרשאה פג או שההרשאה בוטלה. יש להתקין מחדש שוב.",
@@ -212,9 +212,9 @@
"remove_app": "Alkalmazás eltávolítása",
"yes_remove_app": "Igen, távolítsd el az alkalmazást",
"web_conference": "Online konferencia",
"organizer_name_workflow": "Szervező",
"location_workflow": "Helyszín",
"additional_notes_workflow": "Egyéb jegyzetek",
"organizer_name_variable": "Szervező",
"location_variable": "Helyszín",
"additional_notes_variable": "Egyéb jegyzetek",
"organizer_name_info": "Neved",
"theme_light": "Világos",
"theme_dark": "Sötét"
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Nuovo partecipante {{name}} a {{eventType}} il {{date}}",
"new_seat_title": "Qualcuno ha aggiunto se stesso a un evento",
"variable": "Variabile",
"event_name_workflow": "Nome evento",
"organizer_name_workflow": "Organizzatore",
"attendee_name_workflow": "Partecipante",
"event_date_workflow": "Data evento",
"event_time_workflow": "Ora evento",
"location_workflow": "Luogo",
"additional_notes_workflow": "Note aggiuntive",
"event_name_variable": "Nome evento",
"organizer_name_variable": "Organizzatore",
"attendee_name_variable": "Partecipante",
"event_date_variable": "Data evento",
"event_time_variable": "Ora evento",
"location_variable": "Luogo",
"additional_notes_variable": "Note aggiuntive",
"app_upgrade_description": "Per poter utilizzare questa funzionalità, è necessario passare a un account Pro.",
"invalid_number": "Numero di telefono non valido",
"navigate": "Esplora",
@@ -1387,7 +1387,7 @@
"team_url_taken": "URL già occupato",
"team_publish": "Pubblica team",
"number_sms_notifications": "Numero di telefono (notifiche SMS)",
"attendee_email_workflow": "E-mail partecipante",
"attendee_email_variable": "E-mail partecipante",
"attendee_email_info": "E-mail della persona che prenota",
"kbar_search_placeholder": "Digitare un comando o eseguire una ricerca...",
"invalid_credential": "Errore! Sembra che l'autorizzazione sia scaduta o che sia stata revocata. Prova a reinstallare.",
@@ -612,7 +612,7 @@
"monthly_other": "月",
"yearly_one": "年",
"yearly_other": "年",
"plus_more": "あと {{count}} 回",
"plus_more": "あと {{count}} 件",
"max": "最大",
"single_theme": "シングルテーマ",
"brand_color": "ブランドカラー",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "{{date}} の {{eventType}} への新規参加者 {{name}}",
"new_seat_title": "別のユーザーがイベントに自身を追加しました",
"variable": "変数",
"event_name_workflow": "イベントの名前",
"organizer_name_workflow": "主催者の名前",
"attendee_name_workflow": "出席者の名前",
"event_date_workflow": "イベントの日付",
"event_time_workflow": "イベントの時間",
"location_workflow": "場所",
"additional_notes_workflow": "備考",
"event_name_variable": "イベントの名前",
"organizer_name_variable": "主催者の名前",
"attendee_name_variable": "出席者の名前",
"event_date_variable": "イベントの日付",
"event_time_variable": "イベントの時間",
"location_variable": "場所",
"additional_notes_variable": "備考",
"app_upgrade_description": "この機能を利用するには、Pro アカウントへのアップグレードが必要です。",
"invalid_number": "電話番号が無効です",
"navigate": "ナビゲート",
@@ -1386,7 +1386,7 @@
"team_url_taken": "この URL は既に使用されています",
"team_publish": "チームを公開",
"number_sms_notifications": "電話番号(SMS 通知)",
"attendee_email_workflow": "出席者のメールアドレス",
"attendee_email_variable": "出席者のメールアドレス",
"attendee_email_info": "予約者のメールアドレス",
"kbar_search_placeholder": "コマンドを入力するか、検索...",
"invalid_credential": "アクセス許可の有効期限が切れたか、取り消されたようです。もう一度インストールしてください。",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "신규 참석자 {{name}}님이 {{date}}, {{eventType}}에 참석",
"new_seat_title": "누군가가 이벤트에 자신을 추가했습니다",
"variable": "변수",
"event_name_workflow": "이벤트 이름",
"organizer_name_workflow": "주최자",
"attendee_name_workflow": "참석자",
"event_date_workflow": "이벤트 날짜",
"event_time_workflow": "이벤트 시간",
"location_workflow": "위치",
"additional_notes_workflow": "추가 참고 사항",
"event_name_variable": "이벤트 이름",
"organizer_name_variable": "주최자",
"attendee_name_variable": "참석자",
"event_date_variable": "이벤트 날짜",
"event_time_variable": "이벤트 시간",
"location_variable": "위치",
"additional_notes_variable": "추가 참고 사항",
"app_upgrade_description": "이 기능을 사용하려면 Pro 계정으로 업그레이드해야 합니다.",
"invalid_number": "유효하지 않은 전화 번호",
"navigate": "탐색하기",
@@ -1387,7 +1387,7 @@
"team_url_taken": "이미 사용 중인 URL입니다",
"team_publish": "팀 게시",
"number_sms_notifications": "전화번호 (SMS notifications)",
"attendee_email_workflow": "참석자 이메일",
"attendee_email_variable": "참석자 이메일",
"attendee_email_info": "예약자 이메일",
"kbar_search_placeholder": "명령어를 입력하거나 검색하세요...",
"invalid_credential": "죄송합니다! 권한이 만료되었거나 취소된 것 같습니다. 다시 설치하십시오.",
+10 -10
View File
@@ -612,7 +612,7 @@
"monthly_other": "maanden",
"yearly_one": "jaar",
"yearly_other": "jaar",
"plus_more": "{{count}} meer",
"plus_more": "Nog {{count}}",
"max": "Max.",
"single_theme": "Enkel Thema",
"brand_color": "Merkkleur",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nieuwe deelnemer {{name}} op {{eventType}} om {{date}}",
"new_seat_title": "Iemand heeft zichzelf toegevoegd aan een evenement",
"variable": "Variabel",
"event_name_workflow": "Naam gebeurtenis",
"organizer_name_workflow": "Naam organisator",
"attendee_name_workflow": "Deelnemer",
"event_date_workflow": "Datum gebeurtenis",
"event_time_workflow": "Tijd gebeurtenis",
"location_workflow": "Locatie",
"additional_notes_workflow": "Aanvullende opmerkingen",
"event_name_variable": "Naam gebeurtenis",
"organizer_name_variable": "Naam organisator",
"attendee_name_variable": "Deelnemer",
"event_date_variable": "Datum gebeurtenis",
"event_time_variable": "Tijd gebeurtenis",
"location_variable": "Locatie",
"additional_notes_variable": "Aanvullende opmerkingen",
"app_upgrade_description": "Om deze functie te gebruiken, moet u upgraden naar een Pro-account.",
"invalid_number": "Ongeldig telefoonnummer",
"navigate": "Navigeren",
@@ -1386,11 +1386,11 @@
"team_url_taken": "Deze URL is al in gebruik",
"team_publish": "Team publiceren",
"number_sms_notifications": "Telefoonnummer (sms-meldingen)",
"attendee_email_workflow": "E-mailadres deelnemer",
"attendee_email_variable": "E-mailadres deelnemer",
"attendee_email_info": "Het e-mailadres van de persoon die boekt",
"kbar_search_placeholder": "Typ een opdracht of zoek...",
"invalid_credential": "Oh nee! Het lijkt erop dat de machtiging is verlopen of is ingetrokken. Installeer het opnieuw.",
"choose_common_schedule_team_event": "Kies een gemeenschappelijke planning",
"choose_common_schedule_team_event": "Kies een gezamenlijke planning",
"choose_common_schedule_team_event_description": "Schakel dit in als u een gemeenschappelijke planning tussen organisatoren wilt gebruiken. Indien uitgeschakeld, wordt elke organisator geboekt op basis van zijn standaardplanning.",
"reason": "Reden",
"sender_id": "Afzender-ID",
@@ -1080,13 +1080,13 @@
"new_seat_subject": "Ny Deltaker {{name}} på {{eventType}} kl. {{date}}",
"new_seat_title": "Noen har lagt seg til en hendelse",
"variable": "Variabel",
"event_name_workflow": "Hendelsesnavn",
"organizer_name_workflow": "Arrangør",
"attendee_name_workflow": "Deltaker",
"event_date_workflow": "Hendelsesdato",
"event_time_workflow": "Tidspunkt for hendelse",
"location_workflow": "Sted",
"additional_notes_workflow": "Tilleggsinformasjon",
"event_name_variable": "Hendelsesnavn",
"organizer_name_variable": "Arrangør",
"attendee_name_variable": "Deltaker",
"event_date_variable": "Hendelsesdato",
"event_time_variable": "Tidspunkt for hendelse",
"location_variable": "Sted",
"additional_notes_variable": "Tilleggsinformasjon",
"app_upgrade_description": "For å bruke denne funksjonen må du oppgradere til en Pro-konto.",
"invalid_number": "Ugyldig telefonnummer",
"navigate": "Naviger",
@@ -1400,7 +1400,7 @@
"team_url_taken": "Denne URL'en er allerede tatt",
"team_publish": "Publiser team",
"number_sms_notifications": "Telefonnummer (SMS varsler)",
"attendee_email_workflow": "Deltakerens e-post",
"attendee_email_variable": "Deltakerens e-post",
"attendee_email_info": "Den som booker sin e-post",
"kbar_search_placeholder": "Skriv inn en kommando eller søk...",
"invalid_credential": "Å nei! Det ser ut til at tillatelsen er utløpt eller tilbakekalt. Vennligst installer på nytt.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Nowy uczestnik ({{name}}) wydarzenia {{eventType}} odbywającego się {{date}}",
"new_seat_title": "Ktoś dodał się do wydarzenia",
"variable": "Zmienna",
"event_name_workflow": "Nazwa wydarzenia",
"organizer_name_workflow": "Organizator",
"attendee_name_workflow": "Uczestnik",
"event_date_workflow": "Data wydarzenia",
"event_time_workflow": "Godzina wydarzenia",
"location_workflow": "Lokalizacja",
"additional_notes_workflow": "Dodatkowe uwagi",
"event_name_variable": "Nazwa wydarzenia",
"organizer_name_variable": "Organizator",
"attendee_name_variable": "Uczestnik",
"event_date_variable": "Data wydarzenia",
"event_time_variable": "Godzina wydarzenia",
"location_variable": "Lokalizacja",
"additional_notes_variable": "Dodatkowe uwagi",
"app_upgrade_description": "Aby korzystać z tej funkcji, musisz uaktualnić do konta Pro.",
"invalid_number": "Nieprawidłowy numer telefonu",
"navigate": "Nawigacja",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Ten adres URL jest już zajęty",
"team_publish": "Opublikuj zespół",
"number_sms_notifications": "Numer telefonu (powiadomienia SMS)",
"attendee_email_workflow": "Adres e-mail uczestnika",
"attendee_email_variable": "Adres e-mail uczestnika",
"attendee_email_info": "Adres e-mail osoby rezerwującej",
"kbar_search_placeholder": "Wpisz polecenie lub wyszukaj...",
"invalid_credential": "O nie! Wygląda na to, że uprawnienia wygasły lub zostały cofnięte. Zainstaluj aplikację ponownie.",
@@ -511,7 +511,7 @@
"add_team_members_description": "Convidar outros para se juntar à sua equipe",
"add_team_member": "Adicionar membro da equipe",
"invite_new_member": "Convidar um novo membro",
"invite_new_member_description": "Observação: isso <1>custará um assento extra ($15/m)</1> na sua assinatura.",
"invite_new_member_description": "Observação: isso <1>custará um lugar extra ($15/m)</1> na sua assinatura.",
"invite_new_team_member": "Convidar alguém para seu time.",
"change_member_role": "Alterar função de membro da equipe",
"disable_cal_branding": "Remover a marca {{appName}}",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Novo participante {{name}} no {{eventType}} em {{date}}",
"new_seat_title": "Alguém se adicionou a um evento",
"variable": "Variável",
"event_name_workflow": "Nome do evento",
"organizer_name_workflow": "Nome do organizador",
"attendee_name_workflow": "Nome do participante",
"event_date_workflow": "Data do evento",
"event_time_workflow": "Horário do evento",
"location_workflow": "Local",
"additional_notes_workflow": "Observações adicionais",
"event_name_variable": "Nome do evento",
"organizer_name_variable": "Nome do organizador",
"attendee_name_variable": "Nome do participante",
"event_date_variable": "Data do evento",
"event_time_variable": "Horário do evento",
"location_variable": "Local",
"additional_notes_variable": "Observações adicionais",
"app_upgrade_description": "Para usar este recurso, atualize para uma conta Pro.",
"invalid_number": "Número de telefone inválido",
"navigate": "Navegar",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Este URL já está em uso",
"team_publish": "Publicar equipe",
"number_sms_notifications": "Número de telefone (notificações de SMS)",
"attendee_email_workflow": "E-mail do participante",
"attendee_email_variable": "E-mail do participante",
"attendee_email_info": "O e-mail da pessoa que fez a reserva",
"kbar_search_placeholder": "Digite um comando ou procure...",
"invalid_credential": "Ah, não! Parece que a permissão expirou ou foi revogada. Reinstale novamente.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Novo participante {{name}} em {{eventType}} em {{date}}",
"new_seat_title": "Alguém se adicionou a um evento",
"variable": "Variável",
"event_name_workflow": "Nome do evento",
"organizer_name_workflow": "Organizador",
"attendee_name_workflow": "Participante",
"event_date_workflow": "Data do evento",
"event_time_workflow": "Hora do evento",
"location_workflow": "Localização",
"additional_notes_workflow": "Notas Adicionais",
"event_name_variable": "Nome do evento",
"organizer_name_variable": "Organizador",
"attendee_name_variable": "Participante",
"event_date_variable": "Data do evento",
"event_time_variable": "Hora do evento",
"location_variable": "Localização",
"additional_notes_variable": "Notas Adicionais",
"app_upgrade_description": "Para usar esta funcionalidade, tem de actualizar para uma conta Pro.",
"invalid_number": "Número de telefone inválido",
"navigate": "Navegar",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Este endereço já está a ser utilizado",
"team_publish": "Publicar equipa",
"number_sms_notifications": "Número de telefone (notificações SMS)",
"attendee_email_workflow": "E-mail do participante",
"attendee_email_variable": "E-mail do participante",
"attendee_email_info": "O e-mail do responsável pela reserva",
"kbar_search_placeholder": "Digite um comando ou pesquise...",
"invalid_credential": "Oh não! Parece que a permissão expirou ou foi revogada. Por favor, reinstale novamente.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Participant nou, {{name}}, la {{eventType}} de pe {{date}}",
"new_seat_title": "Cineva s-a adăugat pe sine la un eveniment",
"variable": "Variabilă",
"event_name_workflow": "Denumire eveniment",
"organizer_name_workflow": "Organizator",
"attendee_name_workflow": "Participant",
"event_date_workflow": "Dată eveniment",
"event_time_workflow": "Oră eveniment",
"location_workflow": "Loc",
"additional_notes_workflow": "Note suplimentare",
"event_name_variable": "Denumire eveniment",
"organizer_name_variable": "Organizator",
"attendee_name_variable": "Participant",
"event_date_variable": "Dată eveniment",
"event_time_variable": "Oră eveniment",
"location_variable": "Loc",
"additional_notes_variable": "Note suplimentare",
"app_upgrade_description": "Pentru a utiliza această caracteristică, trebuie să faceți upgrade la un cont Pro.",
"invalid_number": "Număr de telefon nevalid",
"navigate": "Navigare",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Acest URL este folosit deja",
"team_publish": "Publicați echipa",
"number_sms_notifications": "Număr de telefon (notificări SMS)",
"attendee_email_workflow": "E-mail participant",
"attendee_email_variable": "E-mail participant",
"attendee_email_info": "E-mailul persoanei care efectuează rezervarea",
"kbar_search_placeholder": "Scrie o comandă sau caută...",
"invalid_credential": "Vai, nu! Se pare că permisiunea a expirat sau a fost revocată. Reinstalați din nou.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "{{eventType}}, {{date}}: новый участник {{name}}",
"new_seat_title": "Кто-то добавился к событию",
"variable": "Переменная",
"event_name_workflow": "Название события",
"organizer_name_workflow": "Организатор",
"attendee_name_workflow": "Участник",
"event_date_workflow": "Дата события",
"event_time_workflow": "Время события",
"location_workflow": "Местоположение",
"additional_notes_workflow": "Дополнительная информация",
"event_name_variable": "Название события",
"organizer_name_variable": "Организатор",
"attendee_name_variable": "Участник",
"event_date_variable": "Дата события",
"event_time_variable": "Время события",
"location_variable": "Местоположение",
"additional_notes_variable": "Дополнительная информация",
"app_upgrade_description": "Чтобы использовать эту функцию, необходимо перейти на аккаунт Pro.",
"invalid_number": "Неверный номер телефона",
"navigate": "Перейти",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Этот URL-адрес уже занят",
"team_publish": "Опубликовать команду",
"number_sms_notifications": "Номер телефона (SMS-уведомления)",
"attendee_email_workflow": "Электронная почта участника",
"attendee_email_variable": "Электронная почта участника",
"attendee_email_info": "Электронная почта участника, на которого оформляется бронирование",
"kbar_search_placeholder": "Введите команду или начните поиск...",
"invalid_credential": "Похоже, разрешение истекло или было отозвано. Необходима переустановка.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Novi učesnik {{name}} na {{eventType}} dana {{date}}",
"new_seat_title": "Neko se dodao na događaj",
"variable": "Promenljiva",
"event_name_workflow": "Naziv događaja",
"organizer_name_workflow": "Organizator",
"attendee_name_workflow": "Učesnik",
"event_date_workflow": "Datum događaja",
"event_time_workflow": "Vreme događaja",
"location_workflow": "Lokacija",
"additional_notes_workflow": "Dodatne beleške",
"event_name_variable": "Naziv događaja",
"organizer_name_variable": "Organizator",
"attendee_name_variable": "Učesnik",
"event_date_variable": "Datum događaja",
"event_time_variable": "Vreme događaja",
"location_variable": "Lokacija",
"additional_notes_variable": "Dodatne beleške",
"app_upgrade_description": "Da biste koristili ovu funkciju, morate izvršiti nadogradnju na Pro nalog.",
"invalid_number": "Neispravan broj telefona",
"navigate": "Navigacija",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Ovaj URL je zauzet",
"team_publish": "Objavite tim",
"number_sms_notifications": "Broj telefona (obaveštenja putem SMS-a)",
"attendee_email_workflow": "Imejl polaznika",
"attendee_email_variable": "Imejl polaznika",
"attendee_email_info": "Imejl osobe koja rezerviše",
"kbar_search_placeholder": "Unesite komandu ili pretražite...",
"invalid_credential": "O, ne! Izgleda da je dozvola istekla ili je povučena. Instalirajte ponovo.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "Ny deltagare {{name}} i {{eventType}} {{date}}",
"new_seat_title": "Någon har lagt till sig själv i en händelse",
"variable": "Variabel",
"event_name_workflow": "Händelsenamn",
"organizer_name_workflow": "Arrangör",
"attendee_name_workflow": "Deltagare",
"event_date_workflow": "Händelsedatum",
"event_time_workflow": "Händelsetid",
"location_workflow": "Plats",
"additional_notes_workflow": "Ytterligare inmatning",
"event_name_variable": "Händelsenamn",
"organizer_name_variable": "Arrangör",
"attendee_name_variable": "Deltagare",
"event_date_variable": "Händelsedatum",
"event_time_variable": "Händelsetid",
"location_variable": "Plats",
"additional_notes_variable": "Ytterligare inmatning",
"app_upgrade_description": "För att kunna använda den här funktionen måste du uppgradera till ett Pro-konto.",
"invalid_number": "Ogiltigt telefonnummer",
"navigate": "Navigera",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Denna URL används redan",
"team_publish": "Publicera team",
"number_sms_notifications": "Telefonnummer (SMS-notiser)",
"attendee_email_workflow": "Deltagarens e-postadress",
"attendee_email_variable": "Deltagarens e-postadress",
"attendee_email_info": "Personens e-postadress för bokning",
"kbar_search_placeholder": "Skriv ett kommando eller sök...",
"invalid_credential": "Åh nej! Det verkar som om behörigheten löpt ut eller återkallats. Installera om igen.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "{{date}} tarihindeki {{eventType}} etkinliği için Yeni Katılımcı ({{name}})",
"new_seat_title": "Biri kendini bir etkinliğe ekledi",
"variable": "Değişken",
"event_name_workflow": "Etkinlik adı",
"organizer_name_workflow": "Düzenleyen Kişi",
"attendee_name_workflow": "Katılımcı",
"event_date_workflow": "Etkinlik tarihi",
"event_time_workflow": "Etkinlik saati",
"location_workflow": "Konum",
"additional_notes_workflow": "Ek notlar",
"event_name_variable": "Etkinlik adı",
"organizer_name_variable": "Düzenleyen Kişi",
"attendee_name_variable": "Katılımcı",
"event_date_variable": "Etkinlik tarihi",
"event_time_variable": "Etkinlik saati",
"location_variable": "Konum",
"additional_notes_variable": "Ek notlar",
"app_upgrade_description": "Bu özelliği kullanmak için Pro hesabına geçmeniz gerekiyor.",
"invalid_number": "Geçersiz telefon numarası",
"navigate": "Gezin",
@@ -1387,7 +1387,7 @@
"team_url_taken": "Bu URL zaten kullanılıyor",
"team_publish": "Ekibi yayınla",
"number_sms_notifications": "Telefon numarası (SMS bildirimleri)",
"attendee_email_workflow": "Katılımcı e-postası",
"attendee_email_variable": "Katılımcı e-postası",
"attendee_email_info": "Rezervasyon yaptıran kişinin e-postası",
"kbar_search_placeholder": "Bir komut girin veya arayın...",
"invalid_credential": "Hata! İznin süresi dolmuş veya iptal edilmiş gibi görünüyor. Lütfen yeniden yükleyin.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Новий учасник: {{name}} – {{eventType}}, {{date}}",
"new_seat_title": "Хтось додав себе в захід",
"variable": "Змінна",
"event_name_workflow": "Назва заходу",
"organizer_name_workflow": "Організатор",
"attendee_name_workflow": "Учасник",
"event_date_workflow": "Дата заходу",
"event_time_workflow": "Час заходу",
"location_workflow": "Розташування",
"additional_notes_workflow": "Додаткові примітки",
"event_name_variable": "Назва заходу",
"organizer_name_variable": "Організатор",
"attendee_name_variable": "Учасник",
"event_date_variable": "Дата заходу",
"event_time_variable": "Час заходу",
"location_variable": "Розташування",
"additional_notes_variable": "Додаткові примітки",
"app_upgrade_description": "Щоб користуватися цією функцією, потрібен обліковий запис Pro.",
"invalid_number": "Недійсний номер телефону",
"navigate": "Перейти",
@@ -1386,7 +1386,7 @@
"team_url_taken": "Цю URL-адресу вже зайнято",
"team_publish": "Опублікувати команду",
"number_sms_notifications": "Номер телефону (SMS-сповіщення)",
"attendee_email_workflow": "Адреса ел. пошти учасника",
"attendee_email_variable": "Адреса ел. пошти учасника",
"attendee_email_info": "Адреса ел. пошти особи, яка бронює",
"kbar_search_placeholder": "Введіть команду або пошуковий запис…",
"invalid_credential": "Отакої! Схоже, дозвіл більше не дійсний або його відкликано. Перевстановіть додаток знову.",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "Người tham gia mới tên {{name}} ở {{eventType}} vào {{date}}",
"new_seat_title": "Ai đó đã tự thêm chính họ vào một sự kiện",
"variable": "Biến số",
"event_name_workflow": "Tên sự kiện",
"organizer_name_workflow": "Tên nhà tổ chức",
"attendee_name_workflow": "Tên người tham dự",
"event_date_workflow": "Ngày sự kiện",
"event_time_workflow": "Thời gian sự kiện",
"location_workflow": "Vị trí",
"additional_notes_workflow": "Ghi chú bổ sung",
"event_name_variable": "Tên sự kiện",
"organizer_name_variable": "Tên nhà tổ chức",
"attendee_name_variable": "Tên người tham dự",
"event_date_variable": "Ngày sự kiện",
"event_time_variable": "Thời gian sự kiện",
"location_variable": "Vị trí",
"additional_notes_variable": "Ghi chú bổ sung",
"app_upgrade_description": "Để sử dụng tính năng này, bạn cần nâng cấp lên tài khoản Pro.",
"invalid_number": "Số điện thoại không hợp lệ",
"navigate": "Điều hướng",
@@ -1386,7 +1386,7 @@
"team_url_taken": "URL này đã có người lấy rồi",
"team_publish": "Đăng nhóm",
"number_sms_notifications": "Số điện thoại (thông báo SMS)",
"attendee_email_workflow": "Email người tham dự",
"attendee_email_variable": "Email người tham dự",
"attendee_email_info": "Email người tham gia lịch hẹn",
"kbar_search_placeholder": "Nhập một lệnh hoặc tìm kiếm...",
"invalid_credential": "Ôi không! Có vẻ như quyền đã hết hạn hoặc đã bị thu hồi. Vui lòng cài đặt lại.",
@@ -1077,13 +1077,13 @@
"new_seat_subject": "{{date}} 的 {{eventType}} 有新参与者 {{name}}",
"new_seat_title": "有人将自己添加到活动中",
"variable": "变量",
"event_name_workflow": "活动名称",
"organizer_name_workflow": "组织者",
"attendee_name_workflow": "参与者",
"event_date_workflow": "活动日期",
"event_time_workflow": "活动时间",
"location_workflow": "位置",
"additional_notes_workflow": "附加备注",
"event_name_variable": "活动名称",
"organizer_name_variable": "组织者",
"attendee_name_variable": "参与者",
"event_date_variable": "活动日期",
"event_time_variable": "活动时间",
"location_variable": "位置",
"additional_notes_variable": "附加备注",
"app_upgrade_description": "要使用此功能,您需要升级到专业版帐户。",
"invalid_number": "电话号码无效",
"navigate": "导航",
@@ -1387,7 +1387,7 @@
"team_url_taken": "此链接已被占用",
"team_publish": "发布团队",
"number_sms_notifications": "电话号码(短信通知)",
"attendee_email_workflow": "参与者电子邮件",
"attendee_email_variable": "参与者电子邮件",
"attendee_email_info": "预约人的电子邮件",
"kbar_search_placeholder": "输入命令或搜索...",
"invalid_credential": "哦不!看起来权限已过期或被撤销。请重新安装。",
@@ -1076,13 +1076,13 @@
"new_seat_subject": "{{date}} 的 {{eventType}} 有新與會者 {{name}}",
"new_seat_title": "有人將自己加入活動中",
"variable": "變數",
"event_name_workflow": "活動名稱",
"organizer_name_workflow": "主辦者姓名",
"attendee_name_workflow": "與會者姓名",
"event_date_workflow": "活動日期",
"event_time_workflow": "活動時間",
"location_workflow": "地點",
"additional_notes_workflow": "備註",
"event_name_variable": "活動名稱",
"organizer_name_variable": "主辦者姓名",
"attendee_name_variable": "與會者姓名",
"event_date_variable": "活動日期",
"event_time_variable": "活動時間",
"location_variable": "地點",
"additional_notes_variable": "備註",
"app_upgrade_description": "您必須升級至專業版帳號才能使用此功能。",
"invalid_number": "電話號碼無效",
"navigate": "導覽",
@@ -1386,7 +1386,7 @@
"team_url_taken": "已有人使用此網址",
"team_publish": "發佈團隊",
"number_sms_notifications": "電話號碼 (簡訊通知)",
"attendee_email_workflow": "與會者電子郵件",
"attendee_email_variable": "與會者電子郵件",
"attendee_email_info": "預約人電子郵件",
"kbar_search_placeholder": "輸入指令或搜尋…",
"invalid_credential": "糟糕!權限似乎過期或遭到撤銷。請重新安裝。",
+1 -1
View File
@@ -6,4 +6,4 @@ if [ -z "${CODESPACES}" ]; then
exit 0
fi
echo "echo \"🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev envionment.\"" >> ~/.bashrc
echo "printf \"\n🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev environment.\n\"" >> ~/.bashrc
+3 -3
View File
@@ -114,9 +114,9 @@ const BaseAppFork = {
};
fs.writeFileSync(`${appDirPath}/config.json`, JSON.stringify(config, null, 2));
fs.writeFileSync(
`${appDirPath}/README.mdx`,
`${appDirPath}/DESCRIPTION.md`,
fs
.readFileSync(`${appDirPath}/README.mdx`)
.readFileSync(`${appDirPath}/DESCRIPTION.md`)
.toString()
.replace(/_DESCRIPTION_/g, appDescription)
.replace(/_APP_DIR_/g, slug)
@@ -189,7 +189,7 @@ const CreateApp = ({ noDbUpdate, slug = null, editMode = false }) => {
name: "appDescription",
type: "text",
explainer:
"A detailed description of your app. You can later modify README.mdx to add slider and other components",
"A detailed description of your app. You can later modify DESCRIPTION.md to add slider and other components",
},
{
label: "Category of App",
@@ -6,5 +6,4 @@ items:
- /api/app-store/_APP_DIR_/3.jpg
---
{description}
_DESCRIPTION_
+2 -4
View File
@@ -32,10 +32,8 @@ export default function AppCard({
<div className="p-4 text-sm sm:p-8">
<div className="flex w-full flex-col gap-2 sm:flex-row sm:gap-0">
{/* Don't know why but w-[42px] isn't working, started happening when I started using next/dynamic */}
<Link href={"/apps/" + app.slug}>
<a className="mr-3 h-auto w-10 rounded-sm">
<img className="w-full" src={app?.logo} alt={app?.name} />
</a>
<Link href={"/apps/" + app.slug} className="mr-3 h-auto w-10 rounded-sm">
<img className="w-full" src={app?.logo} alt={app?.name} />
</Link>
<div className="flex flex-col">
<span className="text-base font-semibold leading-4 text-black">{app?.name}</span>
@@ -12,15 +12,17 @@ const AppCategoryNavigation = ({
containerClassname,
className,
fromAdmin,
useQueryParam = false,
}: {
baseURL: string;
children: React.ReactNode;
containerClassname: string;
className?: string;
fromAdmin?: boolean;
useQueryParam?: boolean;
}) => {
const [animationRef] = useAutoAnimate<HTMLDivElement>();
const appCategories = useMemo(() => getAppCategories(baseURL), [baseURL]);
const appCategories = useMemo(() => getAppCategories(baseURL, useQueryParam), [baseURL, useQueryParam]);
return (
<div className={classNames("flex flex-col p-2 md:p-0 xl:flex-row", className)}>
@@ -7,7 +7,7 @@ function getHref(baseURL: string, category: string, useQueryParam: boolean) {
return useQueryParam ? `${baseUrlParsed.toString()}` : `${baseURL}/${category}`;
}
const getAppCategories = (baseURL: string, useQueryParam = true) => {
const getAppCategories = (baseURL: string, useQueryParam: boolean) => {
return [
{
name: "calendar",
@@ -20,7 +20,8 @@ type CustomUseMutationOptions =
type AddAppMutationData = { setupPending: boolean } | void;
type UseAddAppMutationOptions = CustomUseMutationOptions & {
onSuccess: (data: AddAppMutationData) => void;
onSuccess?: (data: AddAppMutationData) => void;
installGoogleVideo?: boolean;
returnTo?: string;
};
@@ -42,6 +43,10 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
if (type?.endsWith("_other_calendar")) {
type = type.split("_other_calendar")[0];
}
if (options?.installGoogleVideo && type !== "google_calendar")
throw new Error("Could not install Google Meet");
const state: IntegrationOAuthCallbackState = {
returnTo:
returnTo ||
@@ -50,6 +55,7 @@ function useAddAppMutation(_type: App["type"] | null, allOptions?: UseAddAppMuta
{ variant: variables && variables.variant, slug: variables && variables.slug },
location.search
),
...(type === "google_calendar" && { installGoogleVideo: options?.installGoogleVideo }),
};
const stateStr = encodeURIComponent(JSON.stringify(state));
const searchParams = `?state=${stateStr}`;
@@ -13,6 +13,7 @@ export const InstallAppButtonMap = {
exchange2016calendar: dynamic(() => import("./exchange2016calendar/components/InstallAppButton")),
exchangecalendar: dynamic(() => import("./exchangecalendar/components/InstallAppButton")),
googlecalendar: dynamic(() => import("./googlecalendar/components/InstallAppButton")),
googlevideo: dynamic(() => import("./googlevideo/components/InstallAppButton")),
hubspot: dynamic(() => import("./hubspot/components/InstallAppButton")),
huddle01video: dynamic(() => import("./huddle01video/components/InstallAppButton")),
jitsivideo: dynamic(() => import("./jitsivideo/components/InstallAppButton")),
@@ -17,6 +17,7 @@ export const apiHandlers = {
ga4: import("./ga4/api"),
giphy: import("./giphy/api"),
googlecalendar: import("./googlecalendar/api"),
googlevideo: import("./googlevideo/api"),
hubspot: import("./hubspot/api"),
huddle01video: import("./huddle01video/api"),
jitsivideo: import("./jitsivideo/api"),
@@ -1,8 +1,8 @@
import { App_RoutingForms_Form } from "@prisma/client";
import { Dispatch, SetStateAction } from "react";
import { getQueryBuilderConfig } from "../lib/getQueryBuilderConfig";
import isRouterLinkedField from "../lib/isRouterLinkedField";
import { getQueryBuilderConfig } from "../pages/route-builder/[...appPages]";
import { SerializableForm, Response } from "../types/types";
type Props = {
@@ -14,8 +14,8 @@ import {
} from "@calcom/types/AppGetServerSideProps";
import {
Alert,
Button,
Badge,
Button,
ButtonGroup,
Dialog,
DialogClose,
@@ -35,9 +35,9 @@ import {
VerticalDivider,
} from "@calcom/ui";
import { RoutingPages } from "../lib/RoutingPages";
import { getSerializableForm } from "../lib/getSerializableForm";
import { processRoute } from "../lib/processRoute";
import { RoutingPages } from "../pages/route-builder/[...appPages]";
import { Response, Route, SerializableForm } from "../types/types";
import { FormAction, FormActionsDropdown, FormActionsProvider } from "./FormActions";
import FormInputFields from "./FormInputFields";
@@ -318,9 +318,7 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
return (
<div key={router.id} className="mr-2">
<Link href={`/${appUrl}/route-builder/${router.id}`}>
<a>
<Badge variant="gray">{router.name}</Badge>
</a>
<Badge variant="gray">{router.name}</Badge>
</Link>
</div>
);
@@ -342,9 +340,7 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
return (
<div key={router.id} className="mr-2">
<Link href={`/${appUrl}/route-builder/${router.id}`}>
<a>
<Badge variant="default">{router.name}</Badge>
</a>
<Badge variant="default">{router.name}</Badge>
</Link>
</div>
);
@@ -399,9 +395,12 @@ function SingleForm({ form, appUrl, Page }: SingleFormComponentProps) {
<div className="font-bold ">{t("route_to")}:</div>
<div className="mt-2">
{RoutingPages.map((page) => {
if (page.value === decidedAction.type) {
return <div data-testid="test-routing-result-type">{page.label}</div>;
}
if (page.value !== decidedAction.type) return null;
return (
<div key={page.value} data-testid="test-routing-result-type">
{page.label}
</div>
);
})}
:{" "}
{decidedAction.type === "customPageMessage" ? (
@@ -0,0 +1,3 @@
import QueryBuilderInitialConfig from "../components/react-awesome-query-builder/config/config";
export const InitialConfig = QueryBuilderInitialConfig;
@@ -0,0 +1,16 @@
import { LocalRoute } from "../types/types";
export const RoutingPages: { label: string; value: NonNullable<LocalRoute["action"]>["type"] }[] = [
{
label: "Custom Page",
value: "customPageMessage",
},
{
label: "External Redirect",
value: "externalRedirectUrl",
},
{
label: "Event Redirect",
value: "eventTypeRedirectUrl",
},
];
@@ -0,0 +1,63 @@
import { FieldTypes } from "../pages/form-edit/[...appPages]";
import { QueryBuilderUpdatedConfig, RoutingForm } from "../types/types";
import { InitialConfig } from "./InitialConfig";
export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) {
const fields: Record<
string,
{
label: string;
type: string;
valueSources: ["value"];
fieldSettings: {
listValues?: {
value: string;
title: string;
}[];
};
}
> = {};
form.fields?.forEach((field) => {
if ("routerField" in field) {
field = field.routerField;
}
if (FieldTypes.map((f) => f.value).includes(field.type)) {
const optionValues = field.selectText?.trim().split("\n");
const options = optionValues?.map((value) => {
const title = value;
return {
value,
title,
};
});
const widget = InitialConfig.widgets[field.type];
const widgetType = widget.type;
fields[field.id] = {
label: field.label,
type: widgetType,
valueSources: ["value"],
fieldSettings: {
listValues: options,
},
// preferWidgets: field.type === "textarea" ? ["textarea"] : [],
};
} else {
throw new Error("Unsupported field type:" + field.type);
}
});
const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } };
if (forReporting) {
delete initialConfigCopy.operators.is_empty;
delete initialConfigCopy.operators.is_not_empty;
initialConfigCopy.operators.__calReporting = true;
}
// You need to provide your own config. See below 'Config format'
const config: QueryBuilderUpdatedConfig = {
...initialConfigCopy,
fields: fields,
};
return config;
}
@@ -2,9 +2,9 @@ import jsonLogic from "json-logic-js";
import { Utils as QbUtils } from "react-awesome-query-builder";
import { z } from "zod";
import { getQueryBuilderConfig } from "../pages/route-builder/[...appPages]";
import { Response, Route, SerializableForm } from "../types/types";
import { zodNonRouterRoute } from "../zod";
import { getQueryBuilderConfig } from "./getQueryBuilderConfig";
import { isFallbackRoute } from "./isFallbackRoute";
import isRouter from "./isRouter";
import { App_RoutingForms_Form } from ".prisma/client";
@@ -24,7 +24,7 @@ import SingleForm, {
import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config";
import "../../components/react-awesome-query-builder/styles.css";
import { JsonLogicQuery } from "../../jsonLogicToPrisma";
import { getQueryBuilderConfig } from "../route-builder/[...appPages]";
import { getQueryBuilderConfig } from "../../lib/getQueryBuilderConfig";
export { getServerSideProps };
@@ -1,8 +1,7 @@
import { useAutoAnimate } from "@formkit/auto-animate/react";
import { App_RoutingForms_Form } from "@prisma/client";
import Link from "next/link";
import React, { useCallback, useState } from "react";
import { Query, Config, Builder, Utils as QbUtils } from "react-awesome-query-builder";
import { Query, Builder, Utils as QbUtils } from "react-awesome-query-builder";
// types
import { JsonTree, ImmutableTree, BuilderProps } from "react-awesome-query-builder";
@@ -21,83 +20,19 @@ import {
import SingleForm, {
getServerSidePropsForSingleFormView as getServerSideProps,
} from "../../components/SingleForm";
import QueryBuilderInitialConfig from "../../components/react-awesome-query-builder/config/config";
import "../../components/react-awesome-query-builder/styles.css";
import { RoutingPages } from "../../lib/RoutingPages";
import { createFallbackRoute } from "../../lib/createFallbackRoute";
import { getQueryBuilderConfig } from "../../lib/getQueryBuilderConfig";
import isRouter from "../../lib/isRouter";
import { GlobalRoute, LocalRoute, SerializableForm, SerializableRoute } from "../../types/types";
import { FieldTypes } from "../form-edit/[...appPages]";
import { GlobalRoute, LocalRoute, QueryBuilderUpdatedConfig, SerializableRoute } from "../../types/types";
export { getServerSideProps };
type RoutingForm = SerializableForm<App_RoutingForms_Form>;
const InitialConfig = QueryBuilderInitialConfig;
const hasRules = (route: Route) => {
if (isRouter(route)) return false;
route.queryValue.children1 && Object.keys(route.queryValue.children1).length;
};
type QueryBuilderUpdatedConfig = typeof QueryBuilderInitialConfig & { fields: Config["fields"] };
export function getQueryBuilderConfig(form: RoutingForm, forReporting = false) {
const fields: Record<
string,
{
label: string;
type: string;
valueSources: ["value"];
fieldSettings: {
listValues?: {
value: string;
title: string;
}[];
};
}
> = {};
form.fields?.forEach((field) => {
if ("routerField" in field) {
field = field.routerField;
}
if (FieldTypes.map((f) => f.value).includes(field.type)) {
const optionValues = field.selectText?.trim().split("\n");
const options = optionValues?.map((value) => {
const title = value;
return {
value,
title,
};
});
const widget = InitialConfig.widgets[field.type];
const widgetType = widget.type;
fields[field.id] = {
label: field.label,
type: widgetType,
valueSources: ["value"],
fieldSettings: {
listValues: options,
},
// preferWidgets: field.type === "textarea" ? ["textarea"] : [],
};
} else {
throw new Error("Unsupported field type:" + field.type);
}
});
const initialConfigCopy = { ...InitialConfig, operators: { ...InitialConfig.operators } };
if (forReporting) {
delete initialConfigCopy.operators.is_empty;
delete initialConfigCopy.operators.is_not_empty;
initialConfigCopy.operators.__calReporting = true;
}
// You need to provide your own config. See below 'Config format'
const config: QueryBuilderUpdatedConfig = {
...initialConfigCopy,
fields: fields,
};
return config;
}
const getEmptyRoute = (): Exclude<SerializableRoute, GlobalRoute> => {
const uuid = QbUtils.uuid();
return {
@@ -122,21 +57,6 @@ type Route =
})
| GlobalRoute;
export const RoutingPages: { label: string; value: NonNullable<LocalRoute["action"]>["type"] }[] = [
{
label: "Custom Page",
value: "customPageMessage",
},
{
label: "External Redirect",
value: "externalRedirectUrl",
},
{
label: "Event Redirect",
value: "eventTypeRedirectUrl",
},
];
const Route = ({
route,
routes,
@@ -213,11 +133,9 @@ const Route = ({
className="mb-6">
<div className="-mt-3">
<Link href={`/${appUrl}/route-builder/${route.id}`}>
<a>
<Badge variant="gray">
<span className="font-semibold">{route.name}</span>
</Badge>
</a>
<Badge variant="gray">
<span className="font-semibold">{route.name}</span>
</Badge>
</Link>
<p className="mt-2 text-sm text-gray-500">
Fields available in <span className="font-bold">{route.name}</span> will be added to this form.

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