Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8143816c18 | ||
|
|
8243ac3c00 | ||
|
|
207e120d66 | ||
|
|
b25d04da1c | ||
|
|
7064acf756 | ||
|
|
716407ad90 | ||
|
|
af0b08bb2b | ||
|
|
3e27735ccb | ||
|
|
76c087f29c | ||
|
|
e05bcf98c2 | ||
|
|
636e88d467 | ||
|
|
34a61214d3 | ||
|
|
b1af30e026 | ||
|
|
870b59d617 | ||
|
|
e25094ccab | ||
|
|
1376f0991f | ||
|
|
443329b99f | ||
|
|
a1fc4ec26d | ||
|
|
d462298e79 | ||
|
|
965cff38c2 | ||
|
|
f96e909dce | ||
|
|
a4d6f7da62 | ||
|
|
ce9413e0dd | ||
|
|
8fc0dfb763 | ||
|
|
690edcf0a7 | ||
|
|
b7dbf10068 | ||
|
|
83b4ba94fc | ||
|
|
62a09e7098 | ||
|
|
d2c4e2894d | ||
|
|
070d314897 | ||
|
|
0a76368e98 | ||
|
|
99287d9eb4 | ||
|
|
100035cb89 | ||
|
|
1750dbead6 | ||
|
|
4c8ed3d11f | ||
|
|
f21c4eb11e | ||
|
|
3f71a41d7e | ||
|
|
1ba191c4ed | ||
|
|
6414903567 | ||
|
|
e62d8b4596 | ||
|
|
e27b1b1bc2 | ||
|
|
9507138f29 | ||
|
|
e5fa202a09 | ||
|
|
447098bc97 | ||
|
|
dc5e042cdd | ||
|
|
92fb365b54 | ||
|
|
161f5c7000 | ||
|
|
c8744768c4 | ||
|
|
b34a568a93 | ||
|
|
a669403604 | ||
|
|
7b2bfc2a64 | ||
|
|
7655c0c867 | ||
|
|
6fbabb4b26 | ||
|
|
edfc9ff082 | ||
|
|
59a9a77e3f | ||
|
|
8e756d7648 | ||
|
|
5763c1c2f1 | ||
|
|
6c5ae0b4d9 | ||
|
|
9cb9ce2e42 | ||
|
|
018be43017 | ||
|
|
4f720c8c9b | ||
|
|
06e1e113f0 | ||
|
|
3fc6f38da9 | ||
|
|
8e8c96c267 | ||
|
|
fab765c5ee | ||
|
|
89ed07a097 | ||
|
|
d7d73d2caf | ||
|
|
aa92730e28 | ||
|
|
430430e0cd | ||
|
|
62713940b0 | ||
|
|
75aef09338 | ||
|
|
4e65c30e18 | ||
|
|
8b873e2828 | ||
|
|
e70d179628 | ||
|
|
ca9a836300 | ||
|
|
abdf72dc0f | ||
|
|
227cd165f6 | ||
|
|
a0135cdc59 | ||
|
|
112d504148 | ||
|
|
d275930732 | ||
|
|
97e33bdd7e | ||
|
|
3f13791faf | ||
|
|
359cfc4be0 | ||
|
|
1ad6bba0ab | ||
|
|
6dc4211d81 | ||
|
|
e5f0452728 | ||
|
|
d95bba63f4 | ||
|
|
6f48869021 | ||
|
|
845bfae887 | ||
|
|
3fe02fba26 | ||
|
|
4ca5ca9fe2 | ||
|
|
364e187080 | ||
|
|
b1193eef1e | ||
|
|
e110f12cf1 | ||
|
|
022b513935 | ||
|
|
010f39a7a4 | ||
|
|
6b6eed66eb | ||
|
|
c214e01f03 | ||
|
|
a28ac3634d | ||
|
|
a0709fac88 | ||
|
|
e5b6a3332e | ||
|
|
979686920a | ||
|
|
793850f3aa | ||
|
|
fa9d72fc0a | ||
|
|
0d485ffed6 | ||
|
|
9a2e55d528 | ||
|
|
81ecbb86a5 | ||
|
|
2cd816932b | ||
|
|
d83411162d | ||
|
|
c9c54db3a1 | ||
|
|
d100600789 |
No files matched your search
@@ -1 +0,0 @@
|
|||||||
🚀 Welcome to Cal.com! Try typing 'yarn dx' to get a quick dev envionment.
|
|
||||||
+9
-1
@@ -31,11 +31,14 @@ NEXT_PUBLIC_CONSOLE_URL='http://localhost:3004'
|
|||||||
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
|
NEXT_PUBLIC_EMBED_LIB_URL='http://localhost:3000/embed/embed.js'
|
||||||
|
|
||||||
# To enable SAML login, set both these variables
|
# To enable SAML login, set both these variables
|
||||||
# @see https://github.com/calcom/cal.com/tree/main/packages/ee#setting-up-saml-login
|
# @see https://github.com/calcom/cal.com/tree/main/packages/features/ee#setting-up-saml-login
|
||||||
# SAML_DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml"
|
# SAML_DATABASE_URL="postgresql://postgres:@localhost:5450/cal-saml"
|
||||||
SAML_DATABASE_URL=
|
SAML_DATABASE_URL=
|
||||||
# SAML_ADMINS='pro@example.com'
|
# SAML_ADMINS='pro@example.com'
|
||||||
SAML_ADMINS=
|
SAML_ADMINS=
|
||||||
|
# NEXT_PUBLIC_HOSTED_CAL_FEATURES=1
|
||||||
|
NEXT_PUBLIC_HOSTED_CAL_FEATURES=
|
||||||
|
|
||||||
# If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then uncomment the follow line.
|
# If you use Heroku to deploy Postgres (or use self-signed certs for Postgres) then uncomment the follow line.
|
||||||
# @see https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
|
# @see https://devcenter.heroku.com/articles/connecting-heroku-postgres#connecting-in-node-js
|
||||||
# PGSSLMODE='no-verify'
|
# PGSSLMODE='no-verify'
|
||||||
@@ -102,6 +105,8 @@ STRIPE_TEAM_MONTHLY_PRICE_ID=
|
|||||||
STRIPE_WEBHOOK_SECRET=
|
STRIPE_WEBHOOK_SECRET=
|
||||||
STRIPE_PRIVATE_KEY=
|
STRIPE_PRIVATE_KEY=
|
||||||
STRIPE_CLIENT_ID=
|
STRIPE_CLIENT_ID=
|
||||||
|
PAYMENT_FEE_FIXED=
|
||||||
|
PAYMENT_FEE_PERCENTAGE=
|
||||||
|
|
||||||
# Use for internal Public API Keys and optional
|
# Use for internal Public API Keys and optional
|
||||||
API_KEY_PREFIX=cal_
|
API_KEY_PREFIX=cal_
|
||||||
@@ -147,3 +152,6 @@ SENTRY_IGNORE_API_RESOLUTION_ERROR=
|
|||||||
NEXT_PUBLIC_APP_NAME="Cal.com"
|
NEXT_PUBLIC_APP_NAME="Cal.com"
|
||||||
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
|
NEXT_PUBLIC_SUPPORT_MAIL_ADDRESS="help@cal.com"
|
||||||
NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc."
|
NEXT_PUBLIC_COMPANY_NAME="Cal.com, Inc."
|
||||||
|
# Set this to true in to disable new signups
|
||||||
|
# NEXT_PUBLIC_DISABLE_SIGNUP=true
|
||||||
|
NEXT_PUBLIC_DISABLE_SIGNUP=
|
||||||
@@ -1,9 +1,3 @@
|
|||||||
"♻️ autoupdate":
|
|
||||||
- apps/web/public/static/locales/**/*.json
|
|
||||||
|
|
||||||
automerge:
|
|
||||||
- apps/web/public/static/locales/**/*.json
|
|
||||||
|
|
||||||
"❗️ migrations":
|
"❗️ migrations":
|
||||||
- packages/prisma/migrations/**/migration.sql
|
- packages/prisma/migrations/**/migration.sql
|
||||||
|
|
||||||
|
|||||||
@@ -9,6 +9,8 @@ concurrency:
|
|||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
|
required-checks:
|
||||||
|
uses: calcom/cal.com/.github/workflows/required-checks.yml@main
|
||||||
synchronize-with-crowdin:
|
synchronize-with-crowdin:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
@@ -21,6 +23,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
upload_translations: true
|
upload_translations: true
|
||||||
download_translations: true
|
download_translations: true
|
||||||
|
pull_request_labels: "♻️ autoupdate, automerge"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
CROWDIN_PROJECT_ID: ${{ secrets.CROWDIN_PROJECT_ID }}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ on:
|
|||||||
paths:
|
paths:
|
||||||
- apps/web/**
|
- apps/web/**
|
||||||
- packages/embeds/**
|
- packages/embeds/**
|
||||||
|
workflow_call:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
name: E2E test
|
name: E2E test
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
|
|
||||||
pull_request_target: # So we can test on forks
|
pull_request_target: # So we can test on forks
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- apps/web/**
|
- apps/web/**
|
||||||
- packages/**
|
- packages/**
|
||||||
|
workflow_call:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|||||||
@@ -14,3 +14,5 @@ jobs:
|
|||||||
- uses: actions/labeler@v4
|
- uses: actions/labeler@v4
|
||||||
with:
|
with:
|
||||||
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
repo-token: "${{ secrets.GITHUB_TOKEN }}"
|
||||||
|
# https://github.com/actions/labeler/issues/442#issuecomment-1297359481
|
||||||
|
sync-labels: ""
|
||||||
@@ -1,10 +1,9 @@
|
|||||||
name: "Meta Workflow: Require Conditional Status Checks"
|
name: "Meta Workflow: Require Conditional Status Checks"
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [fixes/e2e-consolidation] # TODO: Remove this after merged in main
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
workflow_call:
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|||||||
@@ -1,13 +1,14 @@
|
|||||||
name: Unit tests
|
name: Unit tests
|
||||||
on:
|
on:
|
||||||
push:
|
|
||||||
branches: [ feat/routing-forms/reporting ] # TODO: Remove this after merged in main
|
|
||||||
pull_request_target: # So we can test on forks
|
pull_request_target: # So we can test on forks
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
paths:
|
paths:
|
||||||
- apps/web/**
|
- apps/web/**
|
||||||
- packages/**
|
- packages/**
|
||||||
|
workflow_run:
|
||||||
|
workflows: [Crowdin Action]
|
||||||
|
types: [completed]
|
||||||
concurrency:
|
concurrency:
|
||||||
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }}
|
||||||
cancel-in-progress: true
|
cancel-in-progress: true
|
||||||
|
|||||||
@@ -315,6 +315,17 @@ Please see our [contributing guide](/CONTRIBUTING.md).
|
|||||||
|
|
||||||
We have a list of [help wanted](https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22) that contain small features and bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process.
|
We have a list of [help wanted](https://github.com/calcom/cal.com/issues?q=is:issue+is:open+label:%22%F0%9F%99%8B%F0%9F%8F%BB%E2%80%8D%E2%99%82%EF%B8%8Fhelp+wanted%22) that contain small features and bugs which have a relatively limited scope. This is a great place to get started, gain experience, and get familiar with our contribution process.
|
||||||
|
|
||||||
|
|
||||||
|
<!-- TRANSLATIONS -->
|
||||||
|
|
||||||
|
### Translations
|
||||||
|
|
||||||
|
Don't code but still want to contribute? Join our [slack](https://cal.com/slack) and join the [#i18n channel](https://calendso.slack.com/archives/C02BY67GMMW) and let us know what language you want to translate.
|
||||||
|
|
||||||
|
                       => {
|
|
||||||
return <>{typeof window !== "undefined" ? <Suspense {...props} /> : props.fallback}</>;
|
|
||||||
};
|
|
||||||
@@ -504,7 +504,7 @@ const tabs = [
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div>
|
<div>
|
||||||
<small className="flex py-4 text-neutral-500">
|
<small className="flex py-4 text-gray-500">
|
||||||
{t("place_where_cal_widget_appear", { appName: APP_NAME })}
|
{t("place_where_cal_widget_appear", { appName: APP_NAME })}
|
||||||
</small>
|
</small>
|
||||||
</div>
|
</div>
|
||||||
@@ -556,7 +556,7 @@ ${getEmbedTypeSpecificString({ embedFramework: "HTML", embedType, calLink, previ
|
|||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<small className="flex py-4 text-neutral-500">{t("create_update_react_component")}</small>
|
<small className="flex py-4 text-gray-500">{t("create_update_react_component")}</small>
|
||||||
<TextArea
|
<TextArea
|
||||||
data-testid="embed-react"
|
data-testid="embed-react"
|
||||||
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
|
ref={ref as typeof ref & MutableRefObject<HTMLTextAreaElement>}
|
||||||
@@ -650,7 +650,7 @@ const ChooseEmbedTypesDialogContent = () => {
|
|||||||
<div className="order-none box-border flex-none rounded-sm border border-solid bg-white">
|
<div className="order-none box-border flex-none rounded-sm border border-solid bg-white">
|
||||||
{embed.illustration}
|
{embed.illustration}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 font-medium text-neutral-900">{embed.title}</div>
|
<div className="mt-2 font-medium text-gray-900">{embed.title}</div>
|
||||||
<p className="text-sm text-gray-500">{embed.subtitle}</p>
|
<p className="text-sm text-gray-500">{embed.subtitle}</p>
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
@@ -827,7 +827,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
|
onOpenChange={() => setIsEmbedCustomizationOpen((val) => !val)}>
|
||||||
<CollapsibleTrigger
|
<CollapsibleTrigger
|
||||||
type="button"
|
type="button"
|
||||||
className="flex w-full items-center text-base font-medium text-neutral-900">
|
className="flex w-full items-center text-base font-medium text-gray-900">
|
||||||
<div>
|
<div>
|
||||||
{embedType === "inline"
|
{embedType === "inline"
|
||||||
? "Inline Embed Customization"
|
? "Inline Embed Customization"
|
||||||
@@ -838,7 +838,7 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
<Icon.FiChevronRight
|
<Icon.FiChevronRight
|
||||||
className={`${
|
className={`${
|
||||||
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
|
isEmbedCustomizationOpen ? "rotate-90 transform" : ""
|
||||||
} ml-auto h-5 w-5 text-neutral-500`}
|
} ml-auto h-5 w-5 text-gray-500`}
|
||||||
/>
|
/>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent className="text-sm">
|
<CollapsibleContent className="text-sm">
|
||||||
@@ -1001,11 +1001,11 @@ const EmbedTypeCodeAndPreviewDialogContent = ({
|
|||||||
open={isBookingCustomizationOpen}
|
open={isBookingCustomizationOpen}
|
||||||
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
onOpenChange={() => setIsBookingCustomizationOpen((val) => !val)}>
|
||||||
<CollapsibleTrigger className="flex w-full" type="button">
|
<CollapsibleTrigger className="flex w-full" type="button">
|
||||||
<div className="text-base font-medium text-neutral-900">Cal Booking Customization</div>
|
<div className="text-base font-medium text-gray-900">Cal Booking Customization</div>
|
||||||
<Icon.FiChevronRight
|
<Icon.FiChevronRight
|
||||||
className={`${
|
className={`${
|
||||||
isBookingCustomizationOpen ? "rotate-90 transform" : ""
|
isBookingCustomizationOpen ? "rotate-90 transform" : ""
|
||||||
} ml-auto h-5 w-5 text-neutral-500`}
|
} ml-auto h-5 w-5 text-gray-500`}
|
||||||
/>
|
/>
|
||||||
</CollapsibleTrigger>
|
</CollapsibleTrigger>
|
||||||
<CollapsibleContent>
|
<CollapsibleContent>
|
||||||
|
|||||||
@@ -62,12 +62,12 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
|||||||
const className = tab.className || "";
|
const className = tab.className || "";
|
||||||
return (
|
return (
|
||||||
<Component key={tab.name}>
|
<Component key={tab.name}>
|
||||||
<Link key={tab.name} href={href} {...linkProps}>
|
<Link key={tab.name} href={href} {...linkProps} legacyBehavior>
|
||||||
<a
|
<a
|
||||||
onClick={onClick}
|
onClick={onClick}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
isCurrent
|
isCurrent
|
||||||
? "border-neutral-900 text-neutral-900"
|
? "border-neutral-900 text-gray-900"
|
||||||
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
: "border-transparent text-gray-500 hover:border-gray-300 hover:text-gray-700",
|
||||||
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
|
"group inline-flex items-center border-b-2 py-4 px-1 text-sm font-medium",
|
||||||
className
|
className
|
||||||
@@ -76,7 +76,7 @@ const NavTabs: FC<NavTabProps> = ({ tabs, linkProps, ...props }) => {
|
|||||||
{tab.icon && (
|
{tab.icon && (
|
||||||
<tab.icon
|
<tab.icon
|
||||||
className={classNames(
|
className={classNames(
|
||||||
isCurrent ? "text-neutral-900" : "text-gray-400 group-hover:text-gray-500",
|
isCurrent ? "text-gray-900" : "text-gray-400 group-hover:text-gray-500",
|
||||||
"-ml-0.5 hidden h-4 w-4 ltr:mr-2 rtl:ml-2 sm:inline-block"
|
"-ml-0.5 hidden h-4 w-4 ltr:mr-2 rtl:ml-2 sm:inline-block"
|
||||||
)}
|
)}
|
||||||
aria-hidden="true"
|
aria-hidden="true"
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import React, { ComponentProps } from "react";
|
import React, { ComponentProps } from "react";
|
||||||
|
|
||||||
import { ErrorBoundary, Icon, Shell } from "@calcom/ui";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
|
import { ErrorBoundary, Icon } from "@calcom/ui";
|
||||||
|
|
||||||
import NavTabs from "./NavTabs";
|
import NavTabs from "./NavTabs";
|
||||||
|
|
||||||
|
|||||||
@@ -6,12 +6,13 @@ import useAddAppMutation from "@calcom/app-store/_utils/useAddAppMutation";
|
|||||||
import { InstallAppButton } from "@calcom/app-store/components";
|
import { InstallAppButton } from "@calcom/app-store/components";
|
||||||
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
|
||||||
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
import LicenseRequired from "@calcom/features/ee/common/components/v2/LicenseRequired";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import classNames from "@calcom/lib/classNames";
|
import classNames from "@calcom/lib/classNames";
|
||||||
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
import { APP_NAME, COMPANY_NAME, SUPPORT_MAIL_ADDRESS } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { App as AppType } from "@calcom/types/App";
|
import { App as AppType } from "@calcom/types/App";
|
||||||
import { Button, Icon, Shell, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui";
|
import { Button, Icon, showToast, SkeletonButton, SkeletonText, HeadSeo } from "@calcom/ui";
|
||||||
|
|
||||||
const Component = ({
|
const Component = ({
|
||||||
name,
|
name,
|
||||||
@@ -98,8 +99,10 @@ const Component = ({
|
|||||||
<h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1>
|
<h1 className="font-cal ml-4 text-3xl text-gray-900">{name}</h1>
|
||||||
</div>
|
</div>
|
||||||
<h2 className="text-sm font-medium text-gray-600">
|
<h2 className="text-sm font-medium text-gray-600">
|
||||||
<Link href={`categories/${categories[0]}`}>
|
<Link
|
||||||
<a className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">{categories[0]}</a>
|
href={`categories/${categories[0]}`}
|
||||||
|
className="rounded-md bg-gray-100 p-1 text-xs capitalize text-gray-800">
|
||||||
|
{categories[0]}
|
||||||
</Link>{" "}
|
</Link>{" "}
|
||||||
• {t("published_by", { author })}
|
• {t("published_by", { author })}
|
||||||
</h2>
|
</h2>
|
||||||
|
|||||||
@@ -13,10 +13,10 @@ import {
|
|||||||
EmptyScreen,
|
EmptyScreen,
|
||||||
Icon,
|
Icon,
|
||||||
List,
|
List,
|
||||||
ShellSubHeading,
|
|
||||||
showToast,
|
showToast,
|
||||||
AppSkeletonLoader as SkeletonLoader,
|
AppSkeletonLoader as SkeletonLoader,
|
||||||
Switch,
|
Switch,
|
||||||
|
ShellSubHeading,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
|
|
||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
@@ -184,9 +184,7 @@ function ConnectedCalendarsList(props: Props) {
|
|||||||
<div className="border-t border-gray-200">
|
<div className="border-t border-gray-200">
|
||||||
{!fromOnboarding && (
|
{!fromOnboarding && (
|
||||||
<>
|
<>
|
||||||
<p className="px-4 pt-4 text-sm text-neutral-500">
|
<p className="px-4 pt-4 text-sm text-gray-500">{t("toggle_calendars_conflict")}</p>
|
||||||
{t("toggle_calendars_conflict")}
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-2 p-4">
|
<ul className="space-y-2 p-4">
|
||||||
{item.calendars.map((cal) => (
|
{item.calendars.map((cal) => (
|
||||||
<CalendarSwitch
|
<CalendarSwitch
|
||||||
@@ -330,7 +328,10 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
|
|||||||
})}
|
})}
|
||||||
description={t(`no_category_apps_description_calendar`)}
|
description={t(`no_category_apps_description_calendar`)}
|
||||||
buttonRaw={
|
buttonRaw={
|
||||||
<Button color="secondary" href="/apps/categories/calendar">
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
data-testid="connect-calendar-apps"
|
||||||
|
href="/apps/categories/calendar">
|
||||||
{t(`connect_calendar_apps`)}
|
{t(`connect_calendar_apps`)}
|
||||||
</Button>
|
</Button>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,9 @@ import { useSession } from "next-auth/react";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import React, { ComponentProps } from "react";
|
import React, { ComponentProps } from "react";
|
||||||
|
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { EmptyScreen, Icon, Shell, HorizontalTabs, HorizontalTabItemProps } from "@calcom/ui";
|
import { EmptyScreen, Icon } from "@calcom/ui";
|
||||||
|
|
||||||
const tabs: HorizontalTabItemProps[] = [
|
|
||||||
{
|
|
||||||
name: "app_store",
|
|
||||||
href: "/apps",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "installed_apps",
|
|
||||||
href: "/apps/installed",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
type AppsLayoutProps = {
|
type AppsLayoutProps = {
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
@@ -30,11 +20,8 @@ export default function AppsLayout({ children, actions, emptyStore, ...rest }: A
|
|||||||
if (session.status === "loading") return <></>;
|
if (session.status === "loading") return <></>;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Shell {...rest} actions={actions?.("hidden sm:block")}>
|
<Shell {...rest} actions={actions?.("block")}>
|
||||||
<div className="flex flex-col xl:flex-row">
|
<div className="flex flex-col xl:flex-row">
|
||||||
<div className="block lg:hidden">
|
|
||||||
<HorizontalTabs tabs={tabs} actions={actions?.("ml-6 mr-4 mt-3 block sm:hidden")} />
|
|
||||||
</div>
|
|
||||||
<main className="w-full">
|
<main className="w-full">
|
||||||
{emptyStore ? (
|
{emptyStore ? (
|
||||||
<EmptyScreen
|
<EmptyScreen
|
||||||
|
|||||||
@@ -2,10 +2,10 @@ import React, { ComponentProps } from "react";
|
|||||||
|
|
||||||
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
|
import AppCategoryNavigation from "@calcom/app-store/_components/AppCategoryNavigation";
|
||||||
import { InstalledAppVariants } from "@calcom/app-store/utils";
|
import { InstalledAppVariants } from "@calcom/app-store/utils";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import { Icon, Shell } from "@calcom/ui";
|
import { Icon } from "@calcom/ui";
|
||||||
import type { HorizontalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/HorizontalTabItem";
|
import type { HorizontalTabItemProps, VerticalTabItemProps } from "@calcom/ui";
|
||||||
import type { VerticalTabItemProps } from "@calcom/ui/v2/core/navigation/tabs/VerticalTabItem";
|
|
||||||
|
|
||||||
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
|
const tabs: (VerticalTabItemProps | HorizontalTabItemProps)[] = [
|
||||||
{
|
{
|
||||||
@@ -44,13 +44,15 @@ export default function InstalledAppsLayout({
|
|||||||
children,
|
children,
|
||||||
...rest
|
...rest
|
||||||
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
}: { children: React.ReactNode } & ComponentProps<typeof Shell>) {
|
||||||
|
const variant: typeof InstalledAppVariants[number] = "payment";
|
||||||
|
|
||||||
const query = trpc.viewer.integrations.useQuery({
|
const query = trpc.viewer.integrations.useQuery({
|
||||||
variant: InstalledAppVariants.payment,
|
variant,
|
||||||
onlyInstalled: true,
|
onlyInstalled: true,
|
||||||
});
|
});
|
||||||
let actualTabs = tabs;
|
let actualTabs = tabs;
|
||||||
if (query.data?.items.length === 0) {
|
if (query.data?.items.length === 0) {
|
||||||
actualTabs = tabs.filter((tab) => tab.name !== InstalledAppVariants.payment);
|
actualTabs = tabs.filter((tab) => tab.name !== variant);
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -3,7 +3,8 @@ import { useRouter } from "next/router";
|
|||||||
import React, { ComponentProps, useEffect } from "react";
|
import React, { ComponentProps, useEffect } from "react";
|
||||||
|
|
||||||
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
|
import SettingsLayout from "@calcom/features/settings/layouts/SettingsLayout";
|
||||||
import { ErrorBoundary, Shell } from "@calcom/ui";
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
|
import { ErrorBoundary } from "@calcom/ui";
|
||||||
|
|
||||||
import { UserPermissionRole } from ".prisma/client";
|
import { UserPermissionRole } from ".prisma/client";
|
||||||
|
|
||||||
|
|||||||
@@ -87,7 +87,7 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
slug: eventTypeSlug,
|
slug: eventTypeSlug,
|
||||||
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
|
/** Treat as recurring only when a count exist and it's not a rescheduling workflow */
|
||||||
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
|
count: recurringCount && !rescheduleUid ? recurringCount : undefined,
|
||||||
ethSignature,
|
...(ethSignature ? { ethSignature } : {}),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -114,11 +114,12 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
|
{!!seatsPerTimeSlot && <p className="text-sm">{t("booking_full")}</p>}
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
<Link href={bookingUrl} prefetch={false}>
|
<Link
|
||||||
<a
|
href={bookingUrl}
|
||||||
|
prefetch={false}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"text-primary-500 hover:border-gray-900 hover:bg-gray-50",
|
"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",
|
"dark:bg-darkgray-200 dark:hover:bg-darkgray-300 dark:hover:border-darkmodebrand dark:text-darkgray-800 mb-2 block rounded-md border bg-white py-2 text-sm font-medium dark:border-transparent",
|
||||||
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
brand === "#fff" || brand === "#ffffff" ? "" : ""
|
||||||
)}
|
)}
|
||||||
data-testid="time">
|
data-testid="time">
|
||||||
@@ -136,7 +137,6 @@ const AvailableTimes: FC<AvailableTimesProps> = ({
|
|||||||
{seatsPerTimeSlot} {t("seats_available")}
|
{seatsPerTimeSlot} {t("seats_available")}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -76,7 +76,7 @@ const BookingDescription: FC<Props> = (props) => {
|
|||||||
profile={profile}
|
profile={profile}
|
||||||
users={eventType.users}
|
users={eventType.users}
|
||||||
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
|
showMembers={eventType.schedulingType !== SchedulingType.ROUND_ROBIN}
|
||||||
size={10}
|
size="sm"
|
||||||
truncateAfter={3}
|
truncateAfter={3}
|
||||||
/>
|
/>
|
||||||
<h2 className="mt-2 break-words text-sm font-medium text-gray-600 dark:text-gray-300">
|
<h2 className="mt-2 break-words text-sm font-medium text-gray-600 dark:text-gray-300">
|
||||||
|
|||||||
@@ -356,7 +356,7 @@ function BookingListItem(booking: BookingItemProps) {
|
|||||||
<div
|
<div
|
||||||
title={booking.title}
|
title={booking.title}
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"max-w-10/12 sm:max-w-56 text-sm font-medium leading-6 text-neutral-900 md:max-w-full",
|
"max-w-10/12 sm:max-w-56 text-sm font-medium leading-6 text-gray-900 md:max-w-full",
|
||||||
isCancelled ? "line-through" : ""
|
isCancelled ? "line-through" : ""
|
||||||
)}>
|
)}>
|
||||||
{booking.title}
|
{booking.title}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ export default function CancelBooking(props: Props) {
|
|||||||
placeholder={t("cancellation_reason_placeholder")}
|
placeholder={t("cancellation_reason_placeholder")}
|
||||||
value={cancellationReason}
|
value={cancellationReason}
|
||||||
onChange={(e) => setCancellationReason(e.target.value)}
|
onChange={(e) => setCancellationReason(e.target.value)}
|
||||||
className="mt-2 mb-4 w-full dark:border-gray-900 dark:bg-gray-700 dark:text-white "
|
className="dark:bg-darkgray-100 dark:border-darkgray-400 mt-2 mb-4 w-full dark:text-white "
|
||||||
rows={3}
|
rows={3}
|
||||||
/>
|
/>
|
||||||
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
<div className="flex flex-col-reverse rtl:space-x-reverse ">
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { CAL_URL } from "@calcom/lib/constants";
|
||||||
|
import { AvatarGroup, AvatarGroupProps } from "@calcom/ui";
|
||||||
import AvatarGroup, { AvatarGroupProps } from "@components/ui/AvatarGroup";
|
|
||||||
|
|
||||||
export const UserAvatars = ({
|
export const UserAvatars = ({
|
||||||
profile,
|
profile,
|
||||||
@@ -14,7 +13,6 @@ export const UserAvatars = ({
|
|||||||
const showMembers = !users.find((user) => user.name === profile.name) && props.showMembers;
|
const showMembers = !users.find((user) => user.name === profile.name) && props.showMembers;
|
||||||
return (
|
return (
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
border="border-2 dark:border-darkgray-100 border-white"
|
|
||||||
items={
|
items={
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
@@ -38,7 +36,7 @@ export const UserAvatars = ({
|
|||||||
href?: string;
|
href?: string;
|
||||||
}[]
|
}[]
|
||||||
}
|
}
|
||||||
size={props.size}
|
size="sm"
|
||||||
truncateAfter={props.truncateAfter}
|
truncateAfter={props.truncateAfter}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ export const AvailabilityTab = ({ isTeamEvent }: { isTeamEvent: boolean }) => {
|
|||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
<div>
|
<div>
|
||||||
<div className="min-w-4 mb-2">
|
<div className="min-w-4 mb-2">
|
||||||
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-neutral-700">
|
<label htmlFor="availability" className="mt-0 flex text-sm font-medium text-gray-700">
|
||||||
{t("availability")}
|
{t("availability")}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -93,10 +93,11 @@ export const EventAdvancedTab = ({ eventType, team }: Pick<EventTypeSetupProps,
|
|||||||
<div className="flex flex-col">
|
<div className="flex flex-col">
|
||||||
<div className="flex justify-between">
|
<div className="flex justify-between">
|
||||||
<Label>{t("add_to_calendar")}</Label>
|
<Label>{t("add_to_calendar")}</Label>
|
||||||
<Link href="/apps/categories/calendar">
|
<Link
|
||||||
<a target="_blank" className="text-sm text-gray-600 hover:text-gray-900">
|
href="/apps/categories/calendar"
|
||||||
|
target="_blank"
|
||||||
|
className="text-sm text-gray-600 hover:text-gray-900">
|
||||||
{t("add_another_calendar")}
|
{t("add_another_calendar")}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="-mt-1 w-full">
|
<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*/}
|
{/*TODO: Extract it out into a component when used more than once*/}
|
||||||
{!IS_SELF_HOSTED && (
|
{!IS_SELF_HOSTED && (
|
||||||
<Link href="https://cal.com/pricing">
|
<Link href="https://cal.com/pricing" target="_blank">
|
||||||
<a target="_blank">
|
|
||||||
<Badge variant="orange" className="ml-2">
|
<Badge variant="orange" className="ml-2">
|
||||||
Platform Only
|
Platform Only
|
||||||
</Badge>
|
</Badge>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import * as RadioGroup from "@radix-ui/react-radio-group";
|
import * as RadioGroup from "@radix-ui/react-radio-group";
|
||||||
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||||
import { useMemo, useRef, useState } from "react";
|
import React, { useEffect, useState } from "react";
|
||||||
import { Controller, useFormContext, useWatch } from "react-hook-form";
|
import { Controller, useFormContext, UseFormRegisterReturn, useWatch } from "react-hook-form";
|
||||||
|
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
|
import convertToNewDurationType, { DurationType } from "@calcom/lib/convertToNewDurationType";
|
||||||
@@ -12,13 +12,95 @@ import { PeriodType } from "@calcom/prisma/client";
|
|||||||
import type { BookingLimit } from "@calcom/types/Calendar";
|
import type { BookingLimit } from "@calcom/types/Calendar";
|
||||||
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
|
import { Button, DateRangePicker, Icon, Input, InputField, Label, Select, SettingsToggle } from "@calcom/ui";
|
||||||
|
|
||||||
|
const MinimumBookingNoticeInput = React.forwardRef<
|
||||||
|
HTMLInputElement,
|
||||||
|
Omit<UseFormRegisterReturn<"minimumBookingNotice">, "ref">
|
||||||
|
>(function MinimumBookingNoticeInput({ ...passThroughProps }, ref) {
|
||||||
|
const { t } = useLocale();
|
||||||
|
const { setValue, getValues } = useFormContext<FormValues>();
|
||||||
|
const durationTypeOptions: {
|
||||||
|
value: DurationType;
|
||||||
|
label: string;
|
||||||
|
}[] = [
|
||||||
|
{
|
||||||
|
label: t("minutes"),
|
||||||
|
value: "minutes",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("hours"),
|
||||||
|
value: "hours",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: t("days"),
|
||||||
|
value: "days",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const [minimumBookingNoticeDisplayValues, setMinimumBookingNoticeDisplayValues] = useState<{
|
||||||
|
type: DurationType;
|
||||||
|
value: number;
|
||||||
|
}>({
|
||||||
|
type: findDurationType(getValues(passThroughProps.name)),
|
||||||
|
value: convertToNewDurationType(
|
||||||
|
"minutes",
|
||||||
|
findDurationType(getValues(passThroughProps.name)),
|
||||||
|
getValues(passThroughProps.name)
|
||||||
|
),
|
||||||
|
});
|
||||||
|
// keep hidden field in sync with minimumBookingNoticeDisplayValues
|
||||||
|
useEffect(() => {
|
||||||
|
setValue(
|
||||||
|
passThroughProps.name,
|
||||||
|
convertToNewDurationType(
|
||||||
|
minimumBookingNoticeDisplayValues.type,
|
||||||
|
"minutes",
|
||||||
|
minimumBookingNoticeDisplayValues.value
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}, [minimumBookingNoticeDisplayValues, setValue, passThroughProps.name]);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="flex items-end justify-end">
|
||||||
|
<div className="w-1/2 md:w-full">
|
||||||
|
<InputField
|
||||||
|
required
|
||||||
|
defaultValue={minimumBookingNoticeDisplayValues.value}
|
||||||
|
onChange={(e) =>
|
||||||
|
setMinimumBookingNoticeDisplayValues({
|
||||||
|
...minimumBookingNoticeDisplayValues,
|
||||||
|
value: parseInt(e.target.value || "0", 10),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
label={t("minimum_booking_notice")}
|
||||||
|
type="number"
|
||||||
|
placeholder="0"
|
||||||
|
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
|
||||||
|
/>
|
||||||
|
<input type="hidden" ref={ref} {...passThroughProps} />
|
||||||
|
</div>
|
||||||
|
<Select
|
||||||
|
isSearchable={false}
|
||||||
|
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
||||||
|
defaultValue={durationTypeOptions.find(
|
||||||
|
(option) => option.value === minimumBookingNoticeDisplayValues.type
|
||||||
|
)}
|
||||||
|
onChange={(input) => {
|
||||||
|
if (input) {
|
||||||
|
setMinimumBookingNoticeDisplayValues({
|
||||||
|
...minimumBookingNoticeDisplayValues,
|
||||||
|
type: input.value,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
options={durationTypeOptions}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventType">) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
const minimumBookingNoticeType = useRef<DurationType>(findDurationType(eventType.minimumBookingNotice));
|
|
||||||
const prevBookingNoticeType = useRef<DurationType>(minimumBookingNoticeType.current);
|
|
||||||
|
|
||||||
const minimumBookingNoticeInDurationTypeFormValue = formMethods.watch("minimumBookingNoticeInDurationType");
|
|
||||||
|
|
||||||
const PERIOD_TYPES = [
|
const PERIOD_TYPES = [
|
||||||
{
|
{
|
||||||
@@ -49,59 +131,8 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
defaultValue: periodType?.type,
|
defaultValue: periodType?.type,
|
||||||
});
|
});
|
||||||
|
|
||||||
const onMinimumNoticeDurationTypeChange = useMemo(
|
|
||||||
() => (durationType?: DurationType) => {
|
|
||||||
if (typeof durationType === "undefined") return;
|
|
||||||
|
|
||||||
// Store current selected type in ref to use in previous run for comparrison.
|
|
||||||
minimumBookingNoticeType.current = durationType;
|
|
||||||
|
|
||||||
// Uses the previous selected type and converts the value to the new type.
|
|
||||||
// Eg day was selected before, user selects hours now, so we multiple * 24 and round up.
|
|
||||||
const minimumBookingNoticeInDurationType = convertToNewDurationType(
|
|
||||||
prevBookingNoticeType.current,
|
|
||||||
durationType,
|
|
||||||
Number(minimumBookingNoticeInDurationTypeFormValue)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Uses the round up value in the new type to calculate the value in minutes.
|
|
||||||
// We should NOT use minimumBookingNoticeInDurationTypeFormValue here, since that
|
|
||||||
// would break in the case that the user enters 90 minutes, changes the type to
|
|
||||||
// hours (that will show 2 hours), but the value in minutes would then remain 90,
|
|
||||||
// which is something else than the user things they save.
|
|
||||||
const minimumBookingNoticeInMinutes = convertToNewDurationType(
|
|
||||||
durationType,
|
|
||||||
"minutes",
|
|
||||||
Number(minimumBookingNoticeInDurationType)
|
|
||||||
);
|
|
||||||
|
|
||||||
// Updates both form values as well as stores the current type as previous type in ref.
|
|
||||||
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
|
|
||||||
formMethods.setValue("minimumBookingNoticeInDurationType", minimumBookingNoticeInDurationType);
|
|
||||||
prevBookingNoticeType.current = durationType;
|
|
||||||
},
|
|
||||||
[formMethods, minimumBookingNoticeInDurationTypeFormValue]
|
|
||||||
);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* When the user inputs a new value for minimumBookingNoticeInDurationType,
|
|
||||||
* we calculate the value in minutes, and update this hidden field as well.
|
|
||||||
*/
|
|
||||||
const onMinimumNoticeChange = useMemo(
|
|
||||||
() => (duration: string) => {
|
|
||||||
const minimumBookingNoticeInMinutes = convertToNewDurationType(
|
|
||||||
minimumBookingNoticeType.current,
|
|
||||||
"minutes",
|
|
||||||
Number(duration)
|
|
||||||
);
|
|
||||||
|
|
||||||
formMethods.setValue("minimumBookingNotice", minimumBookingNoticeInMinutes);
|
|
||||||
},
|
|
||||||
[formMethods]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div className="space-y-8">
|
||||||
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
<div className="flex flex-col space-y-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Label htmlFor="beforeBufferTime">{t("before_event")} </Label>
|
<Label htmlFor="beforeBufferTime">{t("before_event")} </Label>
|
||||||
@@ -168,56 +199,10 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex flex-col space-y-4 pt-4 lg:flex-row lg:space-y-0 lg:space-x-4">
|
<div className="flex flex-col lg:flex-row lg:space-y-0 lg:space-x-4">
|
||||||
<div className="flex w-full items-end justify-end">
|
<div className="w-full">
|
||||||
<Controller
|
<Label htmlFor="minimumBookingNotice">{t("minimum_booking_notice")} </Label>
|
||||||
name="minimumBookingNotice"
|
<MinimumBookingNoticeInput {...formMethods.register("minimumBookingNotice")} />
|
||||||
control={formMethods.control}
|
|
||||||
render={() => {
|
|
||||||
const durationTypeOptions = [
|
|
||||||
{
|
|
||||||
label: t("minutes"),
|
|
||||||
value: "minutes",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t("hours"),
|
|
||||||
value: "hours",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: t("days"),
|
|
||||||
value: "days",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
return (
|
|
||||||
<>
|
|
||||||
<div className="w-1/2 md:w-3/4">
|
|
||||||
<InputField
|
|
||||||
required
|
|
||||||
label={t("minimum_booking_notice")}
|
|
||||||
type="number"
|
|
||||||
placeholder="120"
|
|
||||||
className="mb-0 h-[38px] rounded-[4px] ltr:mr-2 rtl:ml-2"
|
|
||||||
{...formMethods.register("minimumBookingNoticeInDurationType", {
|
|
||||||
onChange: (event: React.ChangeEvent<HTMLInputElement>) =>
|
|
||||||
onMinimumNoticeChange(event.target.value),
|
|
||||||
})}
|
|
||||||
/>
|
|
||||||
<input type="hidden" {...formMethods.register("minimumBookingNotice")} />
|
|
||||||
</div>
|
|
||||||
<Select
|
|
||||||
isSearchable={false}
|
|
||||||
className="mb-0 ml-2 h-[38px] w-full capitalize md:min-w-[150px] md:max-w-[200px]"
|
|
||||||
defaultValue={durationTypeOptions.find(
|
|
||||||
(option) => option.value === minimumBookingNoticeType.current
|
|
||||||
)}
|
|
||||||
onChange={(input) => onMinimumNoticeDurationTypeChange(input?.value as DurationType)}
|
|
||||||
options={durationTypeOptions}
|
|
||||||
/>
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<Label htmlFor="slotInterval">{t("slot_interval")} </Label>
|
<Label htmlFor="slotInterval">{t("slot_interval")} </Label>
|
||||||
@@ -252,9 +237,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<hr />
|
||||||
<hr className="my-8" />
|
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="bookingLimits"
|
name="bookingLimits"
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
@@ -276,8 +259,7 @@ export const EventLimitsTab = ({ eventType }: Pick<EventTypeSetupProps, "eventTy
|
|||||||
</SettingsToggle>
|
</SettingsToggle>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
|
<hr />
|
||||||
<hr className="my-8" />
|
|
||||||
<Controller
|
<Controller
|
||||||
name="periodType"
|
name="periodType"
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
|
|||||||
@@ -1,16 +1,18 @@
|
|||||||
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
import { useAutoAnimate } from "@formkit/auto-animate/react";
|
||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { isValidPhoneNumber } from "libphonenumber-js";
|
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 type { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { Controller, useForm, useFormContext } from "react-hook-form";
|
import { Controller, useForm, useFormContext } from "react-hook-form";
|
||||||
import { MultiValue } from "react-select";
|
import { MultiValue } from "react-select";
|
||||||
import { z } from "zod";
|
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 { CAL_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
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";
|
import { slugify } from "@lib/slugify";
|
||||||
|
|
||||||
@@ -195,6 +197,23 @@ export const EventSetupTab = (
|
|||||||
</li>
|
</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 && (
|
{validLocations.length > 0 && validLocations.length !== locationOptions.length && (
|
||||||
<li>
|
<li>
|
||||||
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
|
<Button StartIcon={Icon.FiPlus} color="minimal" onClick={() => setShowLocationModal(true)}>
|
||||||
@@ -326,6 +345,7 @@ export const EventSetupTab = (
|
|||||||
<Skeleton as={Label} loadingClassName="w-16">
|
<Skeleton as={Label} loadingClassName="w-16">
|
||||||
{t("location")}
|
{t("location")}
|
||||||
</Skeleton>
|
</Skeleton>
|
||||||
|
|
||||||
<Controller
|
<Controller
|
||||||
name="locations"
|
name="locations"
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
import { SchedulingType } from "@prisma/client/";
|
import { SchedulingType } from "@prisma/client";
|
||||||
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
||||||
import { useMemo } from "react";
|
import { ComponentProps } from "react";
|
||||||
import { Controller, useFormContext } from "react-hook-form";
|
import { Controller, useFormContext, useWatch, Control } from "react-hook-form";
|
||||||
|
import { Options } from "react-select";
|
||||||
|
|
||||||
import CheckedTeamSelect from "@calcom/features/eventtypes/components/CheckedTeamSelect";
|
import CheckedTeamSelect, {
|
||||||
|
CheckedSelectOption,
|
||||||
|
} from "@calcom/features/eventtypes/components/CheckedTeamSelect";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Label, Select } from "@calcom/ui";
|
import { Label, Select } from "@calcom/ui";
|
||||||
@@ -22,14 +25,66 @@ const mapUserToValue = ({ id, name, username, email }: IMemberToValue) => ({
|
|||||||
email,
|
email,
|
||||||
});
|
});
|
||||||
|
|
||||||
export const EventTeamTab = ({
|
const FixedHosts = ({
|
||||||
eventType,
|
control,
|
||||||
team,
|
labelText,
|
||||||
teamMembers,
|
placeholder,
|
||||||
}: Pick<EventTypeSetupProps, "eventType" | "teamMembers" | "team">) => {
|
options = [],
|
||||||
|
...rest
|
||||||
|
}: {
|
||||||
|
control: Control<FormValues>;
|
||||||
|
labelText: string;
|
||||||
|
placeholder: string;
|
||||||
|
options?: Options<CheckedSelectOption>;
|
||||||
|
} & Partial<ComponentProps<typeof CheckedTeamSelect>>) => {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col space-y-5 bg-gray-50 p-4">
|
||||||
|
<div>
|
||||||
|
<Label>{labelText}</Label>
|
||||||
|
<Controller
|
||||||
|
name="hostsFixed"
|
||||||
|
control={control}
|
||||||
|
render={({ field: { onChange, value } }) => {
|
||||||
|
return (
|
||||||
|
<CheckedTeamSelect
|
||||||
|
isDisabled={false}
|
||||||
|
onChange={(options) => {
|
||||||
|
onChange(
|
||||||
|
options.map((option) => ({
|
||||||
|
isFixed: true,
|
||||||
|
userId: parseInt(option.value, 10),
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
value={value
|
||||||
|
.map(
|
||||||
|
(host) =>
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
|
options.find((member) => member.value === host.userId.toString())!
|
||||||
|
)
|
||||||
|
.filter(Boolean)}
|
||||||
|
controlShouldRenderValue={false}
|
||||||
|
options={options}
|
||||||
|
placeholder={placeholder}
|
||||||
|
{...rest}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const EventTeamTab = ({ team, teamMembers }: Pick<EventTypeSetupProps, "teamMembers" | "team">) => {
|
||||||
const formMethods = useFormContext<FormValues>();
|
const formMethods = useFormContext<FormValues>();
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
|
const schedulingType = useWatch({
|
||||||
|
control: formMethods.control,
|
||||||
|
name: "schedulingType",
|
||||||
|
});
|
||||||
|
|
||||||
const schedulingTypeOptions: {
|
const schedulingTypeOptions: {
|
||||||
value: SchedulingType;
|
value: SchedulingType;
|
||||||
label: string;
|
label: string;
|
||||||
@@ -47,14 +102,13 @@ export const EventTeamTab = ({
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const teamMembersToValues = useMemo(() => {
|
const teamMembersOptions = teamMembers.map(mapUserToValue);
|
||||||
return teamMembers.map(mapUserToValue);
|
|
||||||
}, [teamMembers]);
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{team && (
|
{team && (
|
||||||
<div className="space-y-3">
|
<div className="space-y-5">
|
||||||
<div className="flex flex-col pb-8">
|
<div className="flex flex-col">
|
||||||
<Label>{t("scheduling_type")}</Label>
|
<Label>{t("scheduling_type")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="schedulingType"
|
name="schedulingType"
|
||||||
@@ -72,30 +126,75 @@ export const EventTeamTab = ({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col">
|
{schedulingType === SchedulingType.COLLECTIVE && (
|
||||||
<Label>{t("team")}</Label>
|
<FixedHosts
|
||||||
|
options={teamMembersOptions}
|
||||||
|
placeholder={t("add_attendees")}
|
||||||
|
labelText={t("team")}
|
||||||
|
control={formMethods.control}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{schedulingType === SchedulingType.ROUND_ROBIN && (
|
||||||
|
<>
|
||||||
|
<FixedHosts
|
||||||
|
options={teamMembersOptions}
|
||||||
|
isOptionDisabled={(option) =>
|
||||||
|
!!formMethods.getValues("hosts").find((host) => host.userId.toString() === option.value)
|
||||||
|
}
|
||||||
|
placeholder={t("add_fixed_hosts")}
|
||||||
|
labelText={t("fixed_hosts")}
|
||||||
|
control={formMethods.control}
|
||||||
|
/>
|
||||||
|
<div className="flex flex-col space-y-5 bg-gray-50 p-4">
|
||||||
|
<div>
|
||||||
|
<Label>{t("round_robin_hosts")}</Label>
|
||||||
<Controller
|
<Controller
|
||||||
name="users"
|
name="hosts"
|
||||||
control={formMethods.control}
|
control={formMethods.control}
|
||||||
defaultValue={eventType.users.map((user) => user.id.toString())}
|
|
||||||
render={({ field: { onChange, value } }) => (
|
render={({ field: { onChange, value } }) => (
|
||||||
<CheckedTeamSelect
|
<CheckedTeamSelect
|
||||||
isDisabled={false}
|
isDisabled={false}
|
||||||
onChange={(options) => onChange(options.map((user) => user.value))}
|
onChange={(options) =>
|
||||||
|
onChange(
|
||||||
|
options.map((option) => ({
|
||||||
|
isFixed: false,
|
||||||
|
userId: parseInt(option.value, 10),
|
||||||
|
}))
|
||||||
|
)
|
||||||
|
}
|
||||||
value={value
|
value={value
|
||||||
.map(
|
.map(
|
||||||
(userId) =>
|
(host) =>
|
||||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||||
teamMembers.map(mapUserToValue).find((member) => member.value === userId)!
|
teamMembers
|
||||||
|
.map(mapUserToValue)
|
||||||
|
.find((member) => member.value === host.userId.toString())!
|
||||||
)
|
)
|
||||||
.filter(Boolean)}
|
.filter(Boolean)}
|
||||||
controlShouldRenderValue={false}
|
controlShouldRenderValue={false}
|
||||||
options={teamMembersToValues}
|
options={teamMembers.map(mapUserToValue)}
|
||||||
|
isOptionDisabled={(option) =>
|
||||||
|
!!formMethods
|
||||||
|
.getValues("hostsFixed")
|
||||||
|
.find((host) => host.userId.toString() === option.value)
|
||||||
|
}
|
||||||
placeholder={t("add_attendees")}
|
placeholder={t("add_attendees")}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{/*<TextField
|
||||||
|
required
|
||||||
|
type="number"
|
||||||
|
label={t("minimum_round_robin_hosts_count")}
|
||||||
|
defaultValue={1}
|
||||||
|
{...formMethods.register("minimumHostCount")}
|
||||||
|
addOnSuffix={<>{t("hosts")}</>}
|
||||||
|
/>*/}
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import { TFunction } from "next-i18next";
|
import { TFunction } from "next-i18next";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { EventTypeSetupProps, FormValues } from "pages/event-types/[type]";
|
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 { UseFormReturn } from "react-hook-form";
|
||||||
import { TbWebhook } from "react-icons/tb";
|
import { TbWebhook } from "react-icons/tb";
|
||||||
|
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { classNames } from "@calcom/lib";
|
import { classNames } from "@calcom/lib";
|
||||||
import { CAL_URL } from "@calcom/lib/constants";
|
import { CAL_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -15,15 +16,15 @@ import {
|
|||||||
ButtonGroup,
|
ButtonGroup,
|
||||||
ConfirmationDialogContent,
|
ConfirmationDialogContent,
|
||||||
Dialog,
|
Dialog,
|
||||||
Divider,
|
DropdownMenuSeparator,
|
||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
HorizontalTabs,
|
HorizontalTabs,
|
||||||
Icon,
|
Icon,
|
||||||
Label,
|
Label,
|
||||||
Shell,
|
|
||||||
showToast,
|
showToast,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
Switch,
|
Switch,
|
||||||
@@ -32,7 +33,6 @@ import {
|
|||||||
VerticalTabs,
|
VerticalTabs,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
|
|
||||||
import { ClientSuspense } from "@components/ClientSuspense";
|
|
||||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
@@ -56,12 +56,15 @@ function getNavigation(props: {
|
|||||||
installedAppsNumber: number;
|
installedAppsNumber: number;
|
||||||
}) {
|
}) {
|
||||||
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
|
const { eventType, t, enabledAppsNumber, installedAppsNumber, enabledWorkflowsNumber } = props;
|
||||||
|
const duration =
|
||||||
|
eventType.metadata?.multipleDuration?.map((duration) => ` ${duration}`) || eventType.length;
|
||||||
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
name: "event_setup_tab_title",
|
name: "event_setup_tab_title",
|
||||||
href: `/event-types/${eventType.id}?tabName=setup`,
|
href: `/event-types/${eventType.id}?tabName=setup`,
|
||||||
icon: Icon.FiLink,
|
icon: Icon.FiLink,
|
||||||
info: `${eventType.length} Mins`, // TODO: Get this from props
|
info: `${duration} ${t("minute_timeUnit")}`, // TODO: Get this from props
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "availability",
|
name: "availability",
|
||||||
@@ -152,7 +155,7 @@ function EventTypeSingleLayout({
|
|||||||
// If there is a team put this navigation item within the tabs
|
// If there is a team put this navigation item within the tabs
|
||||||
if (team) {
|
if (team) {
|
||||||
navigation.splice(2, 0, {
|
navigation.splice(2, 0, {
|
||||||
name: "scheduling_type",
|
name: "assignment",
|
||||||
href: `/event-types/${eventType.id}?tabName=team`,
|
href: `/event-types/${eventType.id}?tabName=team`,
|
||||||
icon: Icon.FiUsers,
|
icon: Icon.FiUsers,
|
||||||
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
|
info: eventType.schedulingType === "COLLECTIVE" ? "collective" : "round_robin",
|
||||||
@@ -247,32 +250,36 @@ function EventTypeSingleLayout({
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem className="focus:ring-gray-100">
|
<DropdownMenuItem className="focus:ring-gray-100">
|
||||||
<Button
|
<DropdownItem
|
||||||
color="minimal"
|
|
||||||
StartIcon={Icon.FiExternalLink}
|
|
||||||
target="_blank"
|
target="_blank"
|
||||||
|
type="button"
|
||||||
|
StartIcon={Icon.FiExternalLink}
|
||||||
href={permalink}
|
href={permalink}
|
||||||
rel="noreferrer"
|
rel="noreferrer">
|
||||||
className="min-w-full">
|
|
||||||
{t("preview")}
|
{t("preview")}
|
||||||
</Button>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="focus:ring-gray-100">
|
<DropdownMenuItem className="focus:ring-gray-100">
|
||||||
<Button color="minimal" StartIcon={Icon.FiLink}>
|
<DropdownItem
|
||||||
|
type="button"
|
||||||
|
StartIcon={Icon.FiLink}
|
||||||
|
onClick={() => {
|
||||||
|
navigator.clipboard.writeText(permalink);
|
||||||
|
showToast("Link copied!", "success");
|
||||||
|
}}>
|
||||||
{t("copy_link")}
|
{t("copy_link")}
|
||||||
</Button>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="focus:ring-gray-100">
|
<DropdownMenuItem className="focus:ring-gray-100">
|
||||||
<Button
|
<DropdownItem
|
||||||
color="minimal"
|
type="button"
|
||||||
StartIcon={Icon.FiTrash}
|
StartIcon={Icon.FiTrash}
|
||||||
disabled={!hasPermsToDelete}
|
disabled={!hasPermsToDelete}
|
||||||
onClick={() => setDeleteDialogOpen(true)}
|
onClick={() => setDeleteDialogOpen(true)}>
|
||||||
className="min-w-full">
|
|
||||||
{t("delete")}
|
{t("delete")}
|
||||||
</Button>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<Divider />
|
<DropdownMenuSeparator className="block sm:hidden" />
|
||||||
<div className="flex items-center rounded-md py-1.5 px-4 sm:hidden sm:hover:bg-gray-100">
|
<div className="flex items-center rounded-md py-1.5 px-4 sm:hidden sm:hover:bg-gray-100">
|
||||||
<Skeleton
|
<Skeleton
|
||||||
as={Label}
|
as={Label}
|
||||||
@@ -301,7 +308,7 @@ function EventTypeSingleLayout({
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</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="-mt-2 flex flex-col xl:flex-row xl:space-x-8">
|
||||||
<div className="hidden xl:block">
|
<div className="hidden xl:block">
|
||||||
<VerticalTabs
|
<VerticalTabs
|
||||||
@@ -324,7 +331,7 @@ function EventTypeSingleLayout({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</ClientSuspense>
|
</Suspense>
|
||||||
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
<Dialog open={deleteDialogOpen} onOpenChange={setDeleteDialogOpen}>
|
||||||
<ConfirmationDialogContent
|
<ConfirmationDialogContent
|
||||||
isLoading={deleteMutation.isLoading}
|
isLoading={deleteMutation.isLoading}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ export default function RecurringEventController({
|
|||||||
{recurringEventState && (
|
{recurringEventState && (
|
||||||
<div data-testid="recurring-event-collapsible" className="text-sm">
|
<div data-testid="recurring-event-collapsible" className="text-sm">
|
||||||
<div className="flex items-center">
|
<div className="flex items-center">
|
||||||
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
|
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">{t("repeats_every")}</p>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -89,7 +89,7 @@ export default function RecurringEventController({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-4 flex items-center">
|
<div className="mt-4 flex items-center">
|
||||||
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
|
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">{t("for_a_maximum_of")}</p>
|
||||||
<input
|
<input
|
||||||
type="number"
|
type="number"
|
||||||
min="1"
|
min="1"
|
||||||
@@ -105,7 +105,7 @@ export default function RecurringEventController({
|
|||||||
setRecurringEventState(newVal);
|
setRecurringEventState(newVal);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<p className="text-neutral-900 ltr:mr-2 rtl:ml-2">
|
<p className="text-gray-900 ltr:mr-2 rtl:ml-2">
|
||||||
{t("events", {
|
{t("events", {
|
||||||
count: recurringEventState.count,
|
count: recurringEventState.count,
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ const CreateEventsOnCalendarSelect = (props: ICreateEventsOnCalendarSelectProps)
|
|||||||
<>
|
<>
|
||||||
<div className="mt-6 flex flex-row">
|
<div className="mt-6 flex flex-row">
|
||||||
<div className="w-full">
|
<div className="w-full">
|
||||||
<label htmlFor="createEventsOn" className="flex text-sm font-medium text-neutral-700">
|
<label htmlFor="createEventsOn" className="flex text-sm font-medium text-gray-700">
|
||||||
{t("create_events_on")}
|
{t("create_events_on")}
|
||||||
</label>
|
</label>
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
|
|||||||
@@ -19,7 +19,8 @@ interface IUserSettingsProps {
|
|||||||
const UserSettings = (props: IUserSettingsProps) => {
|
const UserSettings = (props: IUserSettingsProps) => {
|
||||||
const { user, nextStep } = props;
|
const { user, nextStep } = props;
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const [selectedTimeZone, setSelectedTimeZone] = useState(user.timeZone ?? dayjs.tz.guess());
|
const [selectedTimeZone, setSelectedTimeZone] = useState(dayjs.tz.guess());
|
||||||
|
|
||||||
const {
|
const {
|
||||||
register,
|
register,
|
||||||
handleSubmit,
|
handleSubmit,
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ const Member = ({ member, teamName }: { member: MemberType; teamName: string | n
|
|||||||
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
imageSrc={WEBAPP_URL + "/" + member.username + "/avatar.png"}
|
||||||
/>
|
/>
|
||||||
<section className="line-clamp-4 mt-2 w-full space-y-1">
|
<section className="line-clamp-4 mt-2 w-full space-y-1">
|
||||||
<p className="font-medium text-neutral-900 dark:text-white">{member.name}</p>
|
<p className="font-medium text-gray-900 dark:text-white">{member.name}</p>
|
||||||
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-neutral-500 dark:text-white">
|
<p className="line-clamp-3 overflow-ellipsis text-sm font-normal text-gray-500 dark:text-white">
|
||||||
{member.bio || t("user_from_team", { user: member.name, team: teamName })}
|
{member.bio || t("user_from_team", { user: member.name, team: teamName })}
|
||||||
</p>
|
</p>
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
|||||||
)}
|
)}
|
||||||
|
|
||||||
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
|
<div className={classNames(props.showLogo ? "text-center" : "", "sm:mx-auto sm:w-full sm:max-w-md")}>
|
||||||
{props.heading && <h2 className="font-cal text-center text-3xl text-neutral-900">{props.heading}</h2>}
|
{props.heading && <h2 className="font-cal text-center text-3xl text-gray-900">{props.heading}</h2>}
|
||||||
</div>
|
</div>
|
||||||
{props.loading && (
|
{props.loading && (
|
||||||
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
|
<div className="absolute z-50 flex h-screen w-full items-center bg-gray-50">
|
||||||
@@ -35,7 +35,7 @@ export default function AuthContainer(props: React.PropsWithChildren<Props>) {
|
|||||||
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-10 sm:px-10">
|
<div className="border-1 mx-2 rounded-md border-gray-200 bg-white px-4 py-10 sm:px-10">
|
||||||
{props.children}
|
{props.children}
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-8 text-center text-sm text-neutral-600">
|
<div className="mt-8 text-center text-sm text-gray-600">
|
||||||
{props.footerText}
|
{props.footerText}
|
||||||
<Credits />
|
<Credits />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -35,13 +35,7 @@ export const AvatarGroup = function AvatarGroup(props: AvatarGroupProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
|
<li key={idx} className="-ltr:mr-2 inline-block rtl:ml-2">
|
||||||
{item.href ? (
|
{item.href ? <Link href={item.href}>{avatar}</Link> : avatar}
|
||||||
<Link href={item.href}>
|
|
||||||
<a>{avatar}</a>
|
|
||||||
</Link>
|
|
||||||
) : (
|
|
||||||
avatar
|
|
||||||
)}
|
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ export default function LinkIconButton(props: LinkIconButtonProps) {
|
|||||||
type="button"
|
type="button"
|
||||||
{...props}
|
{...props}
|
||||||
className="text-md flex items-center rounded-sm px-2 py-1 text-sm font-medium text-gray-700 hover:bg-gray-200 hover:text-gray-900">
|
className="text-md flex items-center rounded-sm px-2 py-1 text-sm font-medium text-gray-700 hover:bg-gray-200 hover:text-gray-900">
|
||||||
<props.Icon className="h-4 w-4 text-neutral-500 ltr:mr-2 rtl:ml-2" />
|
<props.Icon className="h-4 w-4 text-gray-500 ltr:mr-2 rtl:ml-2" />
|
||||||
{props.children}
|
{props.children}
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
|
||||||
);
|
|
||||||
};
|
|
||||||
@@ -9,26 +9,21 @@ const PoweredByCal = () => {
|
|||||||
const isEmbed = useIsEmbed();
|
const isEmbed = useIsEmbed();
|
||||||
return (
|
return (
|
||||||
<div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}>
|
<div className={"p-2 text-center text-xs sm:text-right" + (isEmbed ? " max-w-3xl" : "")}>
|
||||||
<Link href={POWERED_BY_URL}>
|
<Link
|
||||||
<a target="_blank" className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
|
href={POWERED_BY_URL}
|
||||||
|
target="_blank"
|
||||||
|
className="text-bookinglight opacity-50 hover:opacity-100 dark:text-white">
|
||||||
{t("powered_by")}{" "}
|
{t("powered_by")}{" "}
|
||||||
{
|
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
|
className="relative -mt-px inline h-[10px] w-auto dark:hidden"
|
||||||
src="/cal-logo-word.svg"
|
src="/cal-logo-word.svg"
|
||||||
alt="Cal.com Logo"
|
alt="Cal.com Logo"
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
{
|
|
||||||
// eslint-disable-next-line @next/next/no-img-element
|
|
||||||
<img
|
<img
|
||||||
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
|
className="relativ -mt-px hidden h-[10px] w-auto dark:inline"
|
||||||
src="/cal-logo-word-dark.svg"
|
src="/cal-logo-word-dark.svg"
|
||||||
alt="Cal.com Logo"
|
alt="Cal.com Logo"
|
||||||
/>
|
/>
|
||||||
}
|
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ export default function SettingInputContainer({
|
|||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="block sm:flex">
|
<div className="block sm:flex">
|
||||||
<div className="min-w-48 mb-4 sm:mb-0">
|
<div className="min-w-48 mb-4 sm:mb-0">
|
||||||
<label htmlFor={htmlFor} className="mt-1 flex text-sm font-medium text-neutral-700">
|
<label htmlFor={htmlFor} className="mt-1 flex text-sm font-medium text-gray-700">
|
||||||
<Icon className="mt-0.5 h-4 w-4 text-neutral-500 ltr:mr-2 rtl:ml-2" />
|
<Icon className="mt-0.5 h-4 w-4 text-gray-500 ltr:mr-2 rtl:ml-2" />
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -187,7 +187,7 @@ const PremiumTextfield = (props: ICustomUsernameProps) => {
|
|||||||
<div className="flex justify-items-center">
|
<div className="flex justify-items-center">
|
||||||
<Label htmlFor="username">{t("username")}</Label>
|
<Label htmlFor="username">{t("username")}</Label>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-2 flex rounded-md">
|
<div className="flex rounded-md">
|
||||||
<span
|
<span
|
||||||
className={classNames(
|
className={classNames(
|
||||||
isInputUsernamePremium ? "border-1 border-orange-400 " : "",
|
isInputUsernamePremium ? "border-1 border-orange-400 " : "",
|
||||||
|
|||||||
@@ -118,7 +118,7 @@ const UsernameTextfield = (props: ICustomUsernameProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<div className="mt-2 flex rounded-md">
|
<div className="flex rounded-md">
|
||||||
<div className="relative w-full">
|
<div className="relative w-full">
|
||||||
<TextField
|
<TextField
|
||||||
ref={usernameRef}
|
ref={usernameRef}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
|
|||||||
{React.createElement(
|
{React.createElement(
|
||||||
descriptionAsLabel ? "div" : "label",
|
descriptionAsLabel ? "div" : "label",
|
||||||
{
|
{
|
||||||
className: "flex text-sm font-medium text-neutral-700",
|
className: "flex text-sm font-medium text-gray-700",
|
||||||
...(!descriptionAsLabel
|
...(!descriptionAsLabel
|
||||||
? {
|
? {
|
||||||
htmlFor: rest.id,
|
htmlFor: rest.id,
|
||||||
@@ -39,7 +39,7 @@ const CheckboxField = forwardRef<HTMLInputElement, Props>(
|
|||||||
{
|
{
|
||||||
className: classNames(
|
className: classNames(
|
||||||
"relative flex items-start",
|
"relative flex items-start",
|
||||||
descriptionAsLabel ? "text-neutral-700" : "text-neutral-900"
|
descriptionAsLabel ? "text-gray-700" : "text-gray-900"
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
<>
|
<>
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ export const CheckedSelect = ({
|
|||||||
{option.label}
|
{option.label}
|
||||||
<Icon.FiX
|
<Icon.FiX
|
||||||
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
|
onClick={() => props.onChange(value.filter((item) => item.value !== option.value))}
|
||||||
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-neutral-500"
|
className="float-right mt-0.5 h-5 w-5 cursor-pointer text-gray-500"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ const MinutesField = forwardRef<HTMLInputElement, Props>(({ label, ...rest }, re
|
|||||||
<div className="block sm:flex">
|
<div className="block sm:flex">
|
||||||
{!!label && (
|
{!!label && (
|
||||||
<div className="min-w-48 mb-4 sm:mb-0">
|
<div className="min-w-48 mb-4 sm:mb-0">
|
||||||
<label htmlFor={rest.id} className="flex h-full items-center text-sm font-medium text-neutral-700">
|
<label htmlFor={rest.id} className="flex h-full items-center text-sm font-medium text-gray-700">
|
||||||
{label}
|
{label}
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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";
|
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
// lets refactor and move this into packages/lib/hooks/
|
||||||
import { useState, useEffect } from "react";
|
import { useState, useEffect } from "react";
|
||||||
|
|
||||||
const useMediaQuery = (query: string) => {
|
const useMediaQuery = (query: string) => {
|
||||||
|
|||||||
@@ -89,6 +89,12 @@ const nextConfig = {
|
|||||||
eslint: {
|
eslint: {
|
||||||
ignoreDuringBuilds: !!process.env.CI,
|
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: {
|
images: {
|
||||||
unoptimized: true,
|
unoptimized: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@calcom/web",
|
"name": "@calcom/web",
|
||||||
"version": "2.4.4",
|
"version": "2.5.0",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"analyze": "ANALYZE=true next build",
|
"analyze": "ANALYZE=true next build",
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
"bcryptjs": "^2.4.3",
|
"bcryptjs": "^2.4.3",
|
||||||
"classnames": "^2.3.1",
|
"classnames": "^2.3.1",
|
||||||
"dotenv-cli": "^6.0.0",
|
"dotenv-cli": "^6.0.0",
|
||||||
|
"eslint-config-next": "^13.1.1",
|
||||||
"googleapis": "^84.0.0",
|
"googleapis": "^84.0.0",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"handlebars": "^4.7.7",
|
"handlebars": "^4.7.7",
|
||||||
@@ -80,16 +81,14 @@
|
|||||||
"memory-cache": "^0.2.0",
|
"memory-cache": "^0.2.0",
|
||||||
"micro": "^9.4.1",
|
"micro": "^9.4.1",
|
||||||
"mime-types": "^2.1.35",
|
"mime-types": "^2.1.35",
|
||||||
"next": "^12.3.1",
|
"next": "^13.1.1",
|
||||||
"next-auth": "^4.10.3",
|
"next-auth": "^4.10.3",
|
||||||
"next-axiom": "^0.10.0",
|
"next-axiom": "^0.16.0",
|
||||||
"next-collect": "^0.2.1",
|
"next-collect": "^0.2.1",
|
||||||
"next-i18next": "^11.3.0",
|
"next-i18next": "^11.3.0",
|
||||||
"next-mdx-remote": "^4.1.0",
|
|
||||||
"next-seo": "^4.26.0",
|
"next-seo": "^4.26.0",
|
||||||
"next-themes": "^0.2.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",
|
"nodemailer": "^6.7.8",
|
||||||
"otplib": "^12.0.1",
|
"otplib": "^12.0.1",
|
||||||
"qrcode": "^1.5.1",
|
"qrcode": "^1.5.1",
|
||||||
|
|||||||
+10
-12
@@ -98,8 +98,9 @@ export default function Custom404() {
|
|||||||
|
|
||||||
<ul role="list" className="divide-y divide-gray-200 border-gray-200">
|
<ul role="list" className="divide-y divide-gray-200 border-gray-200">
|
||||||
<li className="px-4 py-2">
|
<li className="px-4 py-2">
|
||||||
<Link href={`${DEVELOPER_DOCS}/self-hosting/installation`}>
|
<Link
|
||||||
<a className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
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">
|
<div className="flex-shrink-0">
|
||||||
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
|
<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" />
|
<Icon.FiFileText className="h-6 w-6 text-gray-700" aria-hidden="true" />
|
||||||
@@ -117,7 +118,6 @@ export default function Custom404() {
|
|||||||
<div className="flex-shrink-0 self-center">
|
<div className="flex-shrink-0 self-center">
|
||||||
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
<li className="px-4 py-2">
|
<li className="px-4 py-2">
|
||||||
@@ -167,11 +167,11 @@ export default function Custom404() {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<Link href={`${WEBSITE_URL}/enterprise`}>
|
<Link
|
||||||
<a className="text-base font-medium text-black hover:text-gray-500">
|
href={`${WEBSITE_URL}/enterprise`}
|
||||||
|
className="text-base font-medium text-black hover:text-gray-500">
|
||||||
{t("contact_sales")}
|
{t("contact_sales")}
|
||||||
<span aria-hidden="true"> →</span>
|
<span aria-hidden="true"> →</span>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -243,8 +243,9 @@ export default function Custom404() {
|
|||||||
<ul role="list" className="mt-4 divide-y divide-gray-200 border-gray-200">
|
<ul role="list" className="mt-4 divide-y divide-gray-200 border-gray-200">
|
||||||
{links.map((link, linkIdx) => (
|
{links.map((link, linkIdx) => (
|
||||||
<li key={linkIdx} className="px-4 py-2">
|
<li key={linkIdx} className="px-4 py-2">
|
||||||
<Link href={link.href}>
|
<Link
|
||||||
<a className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
href={link.href}
|
||||||
|
className="relative flex items-start space-x-4 py-6 rtl:space-x-reverse">
|
||||||
<div className="flex-shrink-0">
|
<div className="flex-shrink-0">
|
||||||
<span className="flex h-12 w-12 items-center justify-center rounded-lg bg-gray-50">
|
<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.icon className="h-6 w-6 text-gray-700" aria-hidden="true" />
|
||||||
@@ -262,7 +263,6 @@ export default function Custom404() {
|
|||||||
<div className="flex-shrink-0 self-center">
|
<div className="flex-shrink-0 self-center">
|
||||||
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
<Icon.FiChevronRight className="h-5 w-5 text-gray-400" aria-hidden="true" />
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -313,11 +313,9 @@ export default function Custom404() {
|
|||||||
</li>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<div className="mt-8">
|
<div className="mt-8">
|
||||||
<Link href="/">
|
<Link href="/" className="text-base font-medium text-black hover:text-gray-500">
|
||||||
<a className="text-base font-medium text-black hover:text-gray-500">
|
|
||||||
{t("or_go_back_home")}
|
{t("or_go_back_home")}
|
||||||
<span aria-hidden="true"> →</span>
|
<span aria-hidden="true"> →</span>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+10
-14
@@ -1,6 +1,5 @@
|
|||||||
import classNames from "classnames";
|
import classNames from "classnames";
|
||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
import dynamic from "next/dynamic";
|
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
import { useEffect } from "react";
|
import { useEffect } from "react";
|
||||||
@@ -27,12 +26,11 @@ import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calco
|
|||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
import { baseEventTypeSelect } from "@calcom/prisma/selects";
|
||||||
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { Icon, HeadSeo } from "@calcom/ui";
|
import { Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
|
||||||
|
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
import { EmbedProps } from "@lib/withEmbedSsr";
|
import { EmbedProps } from "@lib/withEmbedSsr";
|
||||||
|
|
||||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
|
||||||
import { AvatarSSR } from "@components/ui/AvatarSSR";
|
import { AvatarSSR } from "@components/ui/AvatarSSR";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -60,25 +58,25 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
key={index}
|
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">
|
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" />
|
<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 })}>
|
<Link
|
||||||
<a className="flex justify-between px-6 py-4" data-testid="event-type-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">
|
<div className="flex-shrink">
|
||||||
<p className="dark:text-darkgray-700 text-sm font-semibold text-neutral-900">{type.title}</p>
|
<p className="dark:text-darkgray-700 text-sm font-semibold text-gray-900">{type.title}</p>
|
||||||
<EventTypeDescription className="text-sm" eventType={type} />
|
<EventTypeDescription className="text-sm" eventType={type} />
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-1 self-center">
|
<div className="mt-1 self-center">
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
border="border-2 border-white dark:border-darkgray-100"
|
|
||||||
truncateAfter={4}
|
truncateAfter={4}
|
||||||
className="flex flex-shrink-0"
|
className="flex flex-shrink-0"
|
||||||
size={10}
|
size="sm"
|
||||||
items={props.users.map((user) => ({
|
items={props.users.map((user) => ({
|
||||||
alt: user.name || "",
|
alt: user.name || "",
|
||||||
image: user.avatar || "",
|
image: user.avatar || "",
|
||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -135,13 +133,13 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
{isSingleUser && ( // When we deal with a single user, not dynamic group
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} />
|
<AvatarSSR user={user} className="mx-auto mb-4 h-24 w-24" alt={nameOrUsername} />
|
||||||
<h1 className="font-cal mb-1 text-3xl text-neutral-900 dark:text-white">
|
<h1 className="font-cal mb-1 text-3xl text-gray-900 dark:text-white">
|
||||||
{nameOrUsername}
|
{nameOrUsername}
|
||||||
{user.verified && (
|
{user.verified && (
|
||||||
<Icon.BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
|
<Icon.BadgeCheckIcon className="mx-1 -mt-1 inline h-6 w-6 text-blue-500 dark:text-white" />
|
||||||
)}
|
)}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="dark:text-darkgray-600 text-s text-neutral-500">{user.bio}</p>
|
<p className="dark:text-darkgray-600 text-s text-gray-500">{user.bio}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
<div
|
<div
|
||||||
@@ -175,8 +173,7 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
href={{
|
href={{
|
||||||
pathname: `/${user.username}/${type.slug}`,
|
pathname: `/${user.username}/${type.slug}`,
|
||||||
query,
|
query,
|
||||||
}}>
|
}}
|
||||||
<a
|
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
sdkActionManager?.fire("eventTypeSelected", {
|
sdkActionManager?.fire("eventTypeSelected", {
|
||||||
eventType: type,
|
eventType: type,
|
||||||
@@ -190,7 +187,6 @@ export default function User(props: inferSSRProps<typeof getServerSideProps> & E
|
|||||||
</h2>
|
</h2>
|
||||||
</div>
|
</div>
|
||||||
<EventTypeDescription eventType={type} />
|
<EventTypeDescription eventType={type} />
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
))
|
))
|
||||||
|
|||||||
@@ -53,7 +53,6 @@ class MyDocument extends Document<Props> {
|
|||||||
return (
|
return (
|
||||||
<Html lang={locale} dir={dir}>
|
<Html lang={locale} dir={dir}>
|
||||||
<Head>
|
<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="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="32x32" href="/favicon-32x32.png" />
|
||||||
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png" />
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
import { IdentityProvider } from "@prisma/client";
|
import { IdentityProvider } from "@prisma/client";
|
||||||
import { NextApiRequest, NextApiResponse } from "next";
|
import { NextApiRequest, NextApiResponse } from "next";
|
||||||
|
|
||||||
|
import { hashPassword } from "@calcom/lib/auth";
|
||||||
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { hashPassword } from "@lib/auth";
|
|
||||||
import slugify from "@lib/slugify";
|
import slugify from "@lib/slugify";
|
||||||
|
|
||||||
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
|
||||||
@@ -12,6 +12,11 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
|
||||||
|
res.status(403).json({ message: "Signup is disabled" });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
const data = req.body;
|
const data = req.body;
|
||||||
const { email, password } = data;
|
const { email, password } = data;
|
||||||
const username = slugify(data.username);
|
const username = slugify(data.username);
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
location: true,
|
location: true,
|
||||||
user: {
|
user: {
|
||||||
select: {
|
select: {
|
||||||
|
id: true,
|
||||||
email: true,
|
email: true,
|
||||||
name: true,
|
name: true,
|
||||||
username: true,
|
username: true,
|
||||||
@@ -100,6 +101,7 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
|
|||||||
startTime: booking.startTime.toISOString(),
|
startTime: booking.startTime.toISOString(),
|
||||||
endTime: booking.endTime.toISOString(),
|
endTime: booking.endTime.toISOString(),
|
||||||
organizer: {
|
organizer: {
|
||||||
|
id: user.id,
|
||||||
email: user.email,
|
email: user.email,
|
||||||
name,
|
name,
|
||||||
timeZone: user.timeZone,
|
timeZone: user.timeZone,
|
||||||
|
|||||||
@@ -35,6 +35,24 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
uid: "xxyPr4cg2xx4XoS2KeMEQy",
|
uid: "xxyPr4cg2xx4XoS2KeMEQy",
|
||||||
metadata: {},
|
metadata: {},
|
||||||
recurringEvent: null,
|
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;
|
req.statusCode = 200;
|
||||||
@@ -42,9 +60,9 @@ const handler = async (req: NextApiRequest, res: NextApiResponse) => {
|
|||||||
res.setHeader("Content-Type", "text/html");
|
res.setHeader("Content-Type", "text/html");
|
||||||
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
|
res.setHeader("Cache-Control", "no-cache, no-store, private, must-revalidate");
|
||||||
res.write(
|
res.write(
|
||||||
renderEmail("OrganizerRequestEmail", {
|
renderEmail("OrganizerScheduledEmail", {
|
||||||
attendee: evt.attendees[0],
|
|
||||||
calEvent: evt,
|
calEvent: evt,
|
||||||
|
attendee: evt.organizer,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
res.end();
|
res.end();
|
||||||
|
|||||||
@@ -2,11 +2,11 @@ import { NextApiRequest, NextApiResponse } from "next";
|
|||||||
import type { Session } from "next-auth";
|
import type { Session } from "next-auth";
|
||||||
|
|
||||||
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
|
import getInstalledAppPath from "@calcom/app-store/_utils/getInstalledAppPath";
|
||||||
|
import { getSession } from "@calcom/lib/auth";
|
||||||
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
|
import { deriveAppDictKeyFromType } from "@calcom/lib/deriveAppDictKeyFromType";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
|
import type { AppDeclarativeHandler, AppHandler } from "@calcom/types/AppHandler";
|
||||||
|
|
||||||
import { getSession } from "@lib/auth";
|
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
|
|
||||||
const defaultIntegrationAddHandler = async ({
|
const defaultIntegrationAddHandler = async ({
|
||||||
|
|||||||
@@ -1,34 +1,18 @@
|
|||||||
import fs from "fs";
|
import fs from "fs";
|
||||||
import matter from "gray-matter";
|
import matter from "gray-matter";
|
||||||
|
import MarkdownIt from "markdown-it";
|
||||||
import { GetStaticPaths, GetStaticPropsContext } from "next";
|
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 path from "path";
|
||||||
|
|
||||||
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
import { getAppWithMetadata } from "@calcom/app-store/_appRegistry";
|
||||||
|
import ExisitingGoogleCal from "@calcom/app-store/googlevideo/components/ExistingGoogleCal";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
|
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import App from "@components/apps/App";
|
import App from "@components/apps/App";
|
||||||
|
|
||||||
const components = {
|
const md = new MarkdownIt("default", { html: true, breaks: true });
|
||||||
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: () => <></>,
|
|
||||||
};
|
|
||||||
|
|
||||||
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
||||||
return (
|
return (
|
||||||
@@ -50,10 +34,15 @@ function SingleAppPage({ data, source }: inferSSRProps<typeof getStaticProps>) {
|
|||||||
email={data.email}
|
email={data.email}
|
||||||
licenseRequired={data.licenseRequired}
|
licenseRequired={data.licenseRequired}
|
||||||
isProOnly={data.isProOnly}
|
isProOnly={data.isProOnly}
|
||||||
images={source?.scope?.items as string[] | undefined}
|
images={source.data?.items as string[] | undefined}
|
||||||
// tos="https://zoom.us/terms"
|
// tos="https://zoom.us/terms"
|
||||||
// privacy="https://zoom.us/privacy"
|
// 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 };
|
if (!singleApp) return { notFound: true };
|
||||||
|
|
||||||
const appDirname = app.dirName;
|
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);
|
const postFilePath = path.join(README_PATH);
|
||||||
let source = "";
|
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 */
|
/* If the app doesn't have a README we fallback to the package description */
|
||||||
source = fs.readFileSync(postFilePath).toString();
|
source = fs.readFileSync(postFilePath).toString();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(`No README.mdx provided for: ${appDirname}`);
|
console.log(`No DESCRIPTION.md provided for: ${appDirname}`);
|
||||||
source = singleApp.description;
|
source = singleApp.description;
|
||||||
}
|
}
|
||||||
|
|
||||||
const { content, data } = matter(source);
|
const { content, data } = matter(source);
|
||||||
const mdxSource = await serialize(content, { scope: data });
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
source: mdxSource,
|
source: { content, data },
|
||||||
data: singleApp,
|
data: singleApp,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -4,9 +4,10 @@ import Link from "next/link";
|
|||||||
import { useRouter } from "next/router";
|
import { useRouter } from "next/router";
|
||||||
|
|
||||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { AppCard, Shell, SkeletonText } from "@calcom/ui";
|
import { AppCard, SkeletonText } from "@calcom/ui";
|
||||||
|
|
||||||
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
|
export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const { t, isLocaleReady } = useLocale();
|
const { t, isLocaleReady } = useLocale();
|
||||||
@@ -20,10 +21,10 @@ export default function Apps({ apps }: InferGetStaticPropsType<typeof getStaticP
|
|||||||
backPath="/apps"
|
backPath="/apps"
|
||||||
heading={
|
heading={
|
||||||
<>
|
<>
|
||||||
<Link href="/apps">
|
<Link
|
||||||
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
|
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" />}{" "}
|
{isLocaleReady ? t("app_store") : <SkeletonText className="h-4 w-24" />}{" "}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
{category && (
|
{category && (
|
||||||
<span className="gap-1 text-gray-600">
|
<span className="gap-1 text-gray-600">
|
||||||
|
|||||||
@@ -2,8 +2,9 @@ import { InferGetStaticPropsType } from "next";
|
|||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
|
||||||
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
import { getAppRegistry } from "@calcom/app-store/_appRegistry";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Icon, Shell, SkeletonText } from "@calcom/ui";
|
import { Icon, SkeletonText } from "@calcom/ui";
|
||||||
|
|
||||||
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
|
export default function Apps({ categories }: InferGetStaticPropsType<typeof getStaticProps>) {
|
||||||
const { t, isLocaleReady } = useLocale();
|
const { t, isLocaleReady } = useLocale();
|
||||||
@@ -11,18 +12,19 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
|
|||||||
return (
|
return (
|
||||||
<Shell isPublic large>
|
<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">
|
<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">
|
<Link
|
||||||
<a className="inline-flex items-center justify-start gap-1 rounded-sm py-2 text-gray-900">
|
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" />
|
<Icon.FiArrowLeft className="h-4 w-4" />
|
||||||
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
|
{isLocaleReady ? t("app_store") : <SkeletonText className="h-6 w-24" />}{" "}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<div className="mb-16">
|
<div className="mb-16">
|
||||||
<div className="grid h-auto w-full grid-cols-5 gap-3">
|
<div className="grid h-auto w-full grid-cols-5 gap-3">
|
||||||
{categories.map((category) => (
|
{categories.map((category) => (
|
||||||
<Link key={category.name} href={"/apps/categories/" + category.name}>
|
<Link
|
||||||
<a
|
key={category.name}
|
||||||
|
href={"/apps/categories/" + category.name}
|
||||||
data-testid={`app-store-category-${category.name}`}
|
data-testid={`app-store-category-${category.name}`}
|
||||||
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
|
className="relative flex rounded-sm bg-gray-100 px-6 py-4 sm:block">
|
||||||
<div className="self-center">
|
<div className="self-center">
|
||||||
@@ -32,7 +34,6 @@ export default function Apps({ categories }: InferGetStaticPropsType<typeof getS
|
|||||||
<Icon.FiArrowRight className="inline-block h-4 w-4" />
|
<Icon.FiArrowRight className="inline-block h-4 w-4" />
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -7,12 +7,31 @@ import { getSession } from "@calcom/lib/auth";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import type { AppCategories } from "@calcom/prisma/client";
|
import type { AppCategories } from "@calcom/prisma/client";
|
||||||
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
import { inferSSRProps } from "@calcom/types/inferSSRProps";
|
||||||
import { AllApps, AppStoreCategories, Icon, TextField, TrendingAppsSlider } from "@calcom/ui";
|
import {
|
||||||
|
AllApps,
|
||||||
|
AppStoreCategories,
|
||||||
|
HorizontalTabItemProps,
|
||||||
|
HorizontalTabs,
|
||||||
|
Icon,
|
||||||
|
TextField,
|
||||||
|
TrendingAppsSlider,
|
||||||
|
} from "@calcom/ui";
|
||||||
|
|
||||||
import AppsLayout from "@components/apps/layouts/AppsLayout";
|
import AppsLayout from "@components/apps/layouts/AppsLayout";
|
||||||
|
|
||||||
import { ssgInit } from "@server/lib/ssg";
|
import { ssgInit } from "@server/lib/ssg";
|
||||||
|
|
||||||
|
const tabs: HorizontalTabItemProps[] = [
|
||||||
|
{
|
||||||
|
name: "app_store",
|
||||||
|
href: "/apps",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "installed_apps",
|
||||||
|
href: "/apps/installed",
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
function AppsSearch({
|
function AppsSearch({
|
||||||
onChange,
|
onChange,
|
||||||
className,
|
className,
|
||||||
@@ -43,9 +62,18 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
|
|||||||
heading={t("app_store")}
|
heading={t("app_store")}
|
||||||
subtitle={t("app_store_description")}
|
subtitle={t("app_store_description")}
|
||||||
actions={(className) => (
|
actions={(className) => (
|
||||||
|
<div className="flex w-full flex-col md:flex-row md:justify-between lg:w-auto">
|
||||||
|
<div className="lg:hidden">
|
||||||
|
<HorizontalTabs tabs={tabs} />
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
|
<AppsSearch className={className} onChange={(e) => setSearchText(e.target.value)} />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
)}
|
)}
|
||||||
|
headerClassName="sm:hidden lg:block hidden"
|
||||||
emptyStore={!appStore.length}>
|
emptyStore={!appStore.length}>
|
||||||
|
<div className="flex flex-col gap-y-8">
|
||||||
{!searchText && (
|
{!searchText && (
|
||||||
<>
|
<>
|
||||||
<AppStoreCategories categories={categories} />
|
<AppStoreCategories categories={categories} />
|
||||||
@@ -57,6 +85,7 @@ export default function Apps({ categories, appStore }: inferSSRProps<typeof getS
|
|||||||
searchText={searchText}
|
searchText={searchText}
|
||||||
categories={categories.map((category) => category.name)}
|
categories={categories.map((category) => category.name)}
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
</AppsLayout>
|
</AppsLayout>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,8 +15,8 @@ import {
|
|||||||
EmptyScreen,
|
EmptyScreen,
|
||||||
Icon,
|
Icon,
|
||||||
List,
|
List,
|
||||||
ShellSubHeading,
|
|
||||||
AppSkeletonLoader as SkeletonLoader,
|
AppSkeletonLoader as SkeletonLoader,
|
||||||
|
ShellSubHeading,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
|
|
||||||
import { QueryCell } from "@lib/QueryCell";
|
import { QueryCell } from "@lib/QueryCell";
|
||||||
@@ -92,8 +92,8 @@ function ConnectOrDisconnectIntegrationButton(props: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface IntegrationsContainerProps {
|
interface IntegrationsContainerProps {
|
||||||
variant?: keyof typeof InstalledAppVariants;
|
variant?: typeof InstalledAppVariants[number];
|
||||||
exclude?: (keyof typeof InstalledAppVariants)[];
|
exclude?: typeof InstalledAppVariants[number][];
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IntegrationsListProps {
|
interface IntegrationsListProps {
|
||||||
@@ -143,8 +143,10 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
|
|||||||
automation: Icon.FiShare2,
|
automation: Icon.FiShare2,
|
||||||
analytics: Icon.FiBarChart,
|
analytics: Icon.FiBarChart,
|
||||||
payment: Icon.FiCreditCard,
|
payment: Icon.FiCreditCard,
|
||||||
|
web3: Icon.FiBarChart,
|
||||||
other: Icon.FiGrid,
|
other: Icon.FiGrid,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryCell
|
<QueryCell
|
||||||
query={query}
|
query={query}
|
||||||
@@ -183,6 +185,7 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
|
|||||||
buttonRaw={
|
buttonRaw={
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
|
data-testid={`connect-${variant || "other"}-apps`}
|
||||||
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
|
href={variant ? `/apps/categories/${variant}` : "/apps/categories/other"}>
|
||||||
{t(`connect_${variant || "other"}_apps`)}
|
{t(`connect_${variant || "other"}_apps`)}
|
||||||
</Button>
|
</Button>
|
||||||
@@ -197,31 +200,29 @@ const IntegrationsContainer = ({ variant, exclude }: IntegrationsContainerProps)
|
|||||||
};
|
};
|
||||||
|
|
||||||
const querySchema = z.object({
|
const querySchema = z.object({
|
||||||
category: z.nativeEnum(InstalledAppVariants),
|
category: z.enum(InstalledAppVariants),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
type querySchemaType = z.infer<typeof querySchema>;
|
||||||
|
|
||||||
export default function InstalledApps() {
|
export default function InstalledApps() {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const category = router.query.category;
|
const category = router.query.category as querySchemaType["category"];
|
||||||
|
const categoryList: querySchemaType["category"][] = [
|
||||||
|
"payment",
|
||||||
|
"conferencing",
|
||||||
|
"automation",
|
||||||
|
"analytics",
|
||||||
|
"web3",
|
||||||
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
|
<InstalledAppsLayout heading={t("installed_apps")} subtitle={t("manage_your_connected_apps")}>
|
||||||
{(category === InstalledAppVariants.payment || category === InstalledAppVariants.conferencing) && (
|
{categoryList.includes(category) && <IntegrationsContainer variant={category} />}
|
||||||
<IntegrationsContainer variant={category} />
|
{category === "calendar" && <CalendarListContainer />}
|
||||||
)}
|
{category === "other" && (
|
||||||
{(category === InstalledAppVariants.automation || category === InstalledAppVariants.analytics) && (
|
<IntegrationsContainer variant={category} exclude={[...categoryList, "calendar"]} />
|
||||||
<IntegrationsContainer variant={category} />
|
|
||||||
)}
|
|
||||||
{category === InstalledAppVariants.calendar && <CalendarListContainer />}
|
|
||||||
{category === InstalledAppVariants.other && (
|
|
||||||
<IntegrationsContainer
|
|
||||||
exclude={[
|
|
||||||
InstalledAppVariants.conferencing,
|
|
||||||
InstalledAppVariants.calendar,
|
|
||||||
InstalledAppVariants.analytics,
|
|
||||||
InstalledAppVariants.automation,
|
|
||||||
]}
|
|
||||||
/>
|
|
||||||
)}
|
)}
|
||||||
</InstalledAppsLayout>
|
</InstalledAppsLayout>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ export default function Error() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="mt-5 sm:mt-6">
|
<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>
|
<Button className="flex w-full justify-center">{t("go_back_login")}</Button>
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -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>
|
<h2 className="text-center text-3xl font-extrabold text-gray-900">{t("request_is_expired")}</h2>
|
||||||
</div>
|
</div>
|
||||||
<p>{t("request_is_expired_instructions")}</p>
|
<p>{t("request_is_expired_instructions")}</p>
|
||||||
<Link href="/auth/forgot-password" passHref>
|
<Link href="/auth/forgot-password" passHref legacyBehavior>
|
||||||
<button
|
<button
|
||||||
type="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">
|
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={
|
footerText={
|
||||||
!success && (
|
!success && (
|
||||||
<>
|
<>
|
||||||
<Link href="/auth/login">
|
<Link href="/auth/login" className="font-medium text-gray-900">
|
||||||
<a className="font-medium text-neutral-900">{t("back_to_signin")}</a>
|
{t("back_to_signin")}
|
||||||
</Link>
|
</Link>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -115,7 +115,13 @@ export default function Login({
|
|||||||
description={t("login")}
|
description={t("login")}
|
||||||
showLogo
|
showLogo
|
||||||
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
|
heading={twoFactorRequired ? t("2fa_code") : t("welcome_back")}
|
||||||
footerText={twoFactorRequired ? TwoFactorFooter : LoginFooter}>
|
footerText={
|
||||||
|
twoFactorRequired
|
||||||
|
? TwoFactorFooter
|
||||||
|
: process.env.NEXT_PUBLIC_DISABLE_SIGNUP !== "true"
|
||||||
|
? LoginFooter
|
||||||
|
: null
|
||||||
|
}>
|
||||||
<FormProvider {...methods}>
|
<FormProvider {...methods}>
|
||||||
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
|
<form onSubmit={methods.handleSubmit(onSubmit)} data-testid="login-form">
|
||||||
<div>
|
<div>
|
||||||
@@ -133,10 +139,11 @@ export default function Login({
|
|||||||
/>
|
/>
|
||||||
<div className="relative">
|
<div className="relative">
|
||||||
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
|
<div className="absolute -top-[6px] z-10 ltr:right-0 rtl:left-0">
|
||||||
<Link href="/auth/forgot-password">
|
<Link
|
||||||
<a tabIndex={-1} className="text-sm font-medium text-gray-600">
|
href="/auth/forgot-password"
|
||||||
|
tabIndex={-1}
|
||||||
|
className="text-sm font-medium text-gray-600">
|
||||||
{t("forgot")}
|
{t("forgot")}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
<PasswordField
|
<PasswordField
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ export default function Setup(props: inferSSRProps<typeof getServerSideProps>) {
|
|||||||
{
|
{
|
||||||
title: t("enable_apps"),
|
title: t("enable_apps"),
|
||||||
description: t("enable_apps_description"),
|
description: t("enable_apps_description"),
|
||||||
content: <AdminAppsList baseURL="/auth/setup?step=2" />,
|
content: <AdminAppsList baseURL="/auth/setup?step=2" useQueryParam={true} />,
|
||||||
isLoading: false,
|
isLoading: false,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { z } from "zod";
|
|||||||
|
|
||||||
import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules";
|
import { DateOverrideInputDialog, DateOverrideList } from "@calcom/features/schedules";
|
||||||
import Schedule from "@calcom/features/schedules/components/Schedule";
|
import Schedule from "@calcom/features/schedules/components/Schedule";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { availabilityAsString } from "@calcom/lib/availability";
|
import { availabilityAsString } from "@calcom/lib/availability";
|
||||||
import { yyyymmdd } from "@calcom/lib/date-fns";
|
import { yyyymmdd } from "@calcom/lib/date-fns";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
@@ -16,7 +17,6 @@ import {
|
|||||||
Form,
|
Form,
|
||||||
Icon,
|
Icon,
|
||||||
Label,
|
Label,
|
||||||
Shell,
|
|
||||||
showToast,
|
showToast,
|
||||||
Skeleton,
|
Skeleton,
|
||||||
SkeletonText,
|
SkeletonText,
|
||||||
@@ -60,7 +60,7 @@ const DateOverride = ({ workingHours }: { workingHours: WorkingHours[] }) => {
|
|||||||
</span>
|
</span>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
</h3>
|
</h3>
|
||||||
<p className="mb-4 text-sm text-neutral-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
|
<p className="mb-4 text-sm text-gray-500 ltr:mr-4 rtl:ml-4">{t("date_overrides_subtitle")}</p>
|
||||||
<div className="mt-1 space-y-2">
|
<div className="mt-1 space-y-2">
|
||||||
<DateOverrideList
|
<DateOverrideList
|
||||||
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
|
excludedDates={fields.map((field) => yyyymmdd(field.ranges[0].start))}
|
||||||
|
|||||||
@@ -2,9 +2,10 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
|||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
import { NewScheduleButton, ScheduleListItem } from "@calcom/features/schedules";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||||
import { EmptyScreen, Icon, Shell, showToast } from "@calcom/ui";
|
import { EmptyScreen, Icon, showToast } from "@calcom/ui";
|
||||||
|
|
||||||
import { withQuery } from "@lib/QueryCell";
|
import { withQuery } from "@lib/QueryCell";
|
||||||
import { HttpError } from "@lib/core/http/error";
|
import { HttpError } from "@lib/core/http/error";
|
||||||
|
|||||||
@@ -1,7 +1,8 @@
|
|||||||
import dayjs from "@calcom/dayjs";
|
import dayjs from "@calcom/dayjs";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
import { RouterOutputs, trpc } from "@calcom/trpc/react";
|
||||||
import { Shell, SkeletonText } from "@calcom/ui";
|
import { SkeletonText } from "@calcom/ui";
|
||||||
|
|
||||||
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
import useRouterQuery from "@lib/hooks/useRouterQuery";
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ const AvailabilityView = ({ user }: { user: User }) => {
|
|||||||
if (e.target.value) setSelectedDate(e.target.value);
|
if (e.target.value) setSelectedDate(e.target.value);
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<small className="block text-neutral-400">{t("hover_over_bold_times_tip")}</small>
|
<small className="block text-gray-400">{t("hover_over_bold_times_tip")}</small>
|
||||||
<div className="mt-4 space-y-4">
|
<div className="mt-4 space-y-4">
|
||||||
<div className="bg-brand dark:bg-darkmodebrand overflow-hidden rounded-md">
|
<div className="bg-brand dark:bg-darkmodebrand overflow-hidden rounded-md">
|
||||||
<div className="text-brandcontrast dark:text-darkmodebrandcontrast px-4 py-2 sm:px-6">
|
<div className="text-brandcontrast dark:text-darkmodebrandcontrast px-4 py-2 sm:px-6">
|
||||||
@@ -81,13 +82,11 @@ const AvailabilityView = ({ user }: { user: User }) => {
|
|||||||
data-testid="troubleshooter-busy-time">
|
data-testid="troubleshooter-busy-time">
|
||||||
<div className="px-4 py-5 text-black sm:p-6">
|
<div className="px-4 py-5 text-black sm:p-6">
|
||||||
{t("calendar_shows_busy_between")}{" "}
|
{t("calendar_shows_busy_between")}{" "}
|
||||||
<span
|
<span className="font-medium text-gray-800" title={dayjs(slot.start).format("HH:mm")}>
|
||||||
className="font-medium text-neutral-800"
|
|
||||||
title={dayjs(slot.start).format("HH:mm")}>
|
|
||||||
{dayjs(slot.start).format("HH:mm")}
|
{dayjs(slot.start).format("HH:mm")}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
{t("and")}{" "}
|
{t("and")}{" "}
|
||||||
<span className="font-medium text-neutral-800" title={dayjs(slot.end).format("HH:mm")}>
|
<span className="font-medium text-gray-800" title={dayjs(slot.end).format("HH:mm")}>
|
||||||
{dayjs(slot.end).format("HH:mm")}
|
{dayjs(slot.end).format("HH:mm")}
|
||||||
</span>{" "}
|
</span>{" "}
|
||||||
{t("on")} {dayjs(slot.start).format("D")}{" "}
|
{t("on")} {dayjs(slot.start).format("D")}{" "}
|
||||||
|
|||||||
@@ -32,7 +32,7 @@ import { getEveryFreqFor } from "@calcom/lib/recurringStrings";
|
|||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
import { getIs24hClockFromLocalStorage, isBrowserLocale24h } from "@calcom/lib/timeFormat";
|
||||||
import { localStorage } from "@calcom/lib/webstorage";
|
import { localStorage } from "@calcom/lib/webstorage";
|
||||||
import prisma, { baseUserSelect } from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { Prisma } from "@calcom/prisma/client";
|
import { Prisma } from "@calcom/prisma/client";
|
||||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
import { Button, EmailInput, Icon, HeadSeo } from "@calcom/ui";
|
import { Button, EmailInput, Icon, HeadSeo } from "@calcom/ui";
|
||||||
@@ -322,7 +322,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
}
|
}
|
||||||
return t("emailed_you_and_attendees" + titleSuffix);
|
return t("emailed_you_and_attendees" + titleSuffix);
|
||||||
}
|
}
|
||||||
const userIsOwner = !!(session?.user?.id && eventType.users.find((user) => (user.id = session.user.id)));
|
const userIsOwner = !!(session?.user?.id && eventType.owner?.id === session.user.id);
|
||||||
useTheme(isSuccessBookingPage ? props.profile.theme : "light");
|
useTheme(isSuccessBookingPage ? props.profile.theme : "light");
|
||||||
const title = t(
|
const title = t(
|
||||||
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
`booking_${needsConfirmation ? "submitted" : "confirmed"}${props.recurringBookings ? "_recurring" : ""}`
|
||||||
@@ -353,10 +353,10 @@ export default function Success(props: SuccessProps) {
|
|||||||
)}
|
)}
|
||||||
{userIsOwner && !isEmbed && (
|
{userIsOwner && !isEmbed && (
|
||||||
<div className="mt-2 ml-4 -mb-4">
|
<div className="mt-2 ml-4 -mb-4">
|
||||||
<Link href={allRemainingBookings ? "/bookings/recurring" : "/bookings/upcoming"}>
|
<Link
|
||||||
<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">
|
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")}
|
<Icon.FiChevronLeft className="h-5 w-5" /> {t("back_to_bookings")}
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
@@ -379,7 +379,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
"main inline-block transform overflow-hidden rounded-lg border sm:my-8 sm:max-w-xl",
|
||||||
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 bg-white dark:border-neutral-700",
|
isBackgroundTransparent ? "" : "dark:bg-darkgray-100 dark:border-darkgray-200 bg-white",
|
||||||
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
"px-8 pt-5 pb-4 text-left align-bottom transition-all sm:w-full sm:py-8 sm:align-middle"
|
||||||
)}
|
)}
|
||||||
role="dialog"
|
role="dialog"
|
||||||
@@ -408,7 +408,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-6 mb-8 text-center last:mb-0">
|
<div className="mt-6 mb-8 text-center last:mb-0">
|
||||||
<h3
|
<h3
|
||||||
className="text-2xl font-semibold leading-6 text-neutral-900 dark:text-white"
|
className="text-2xl font-semibold leading-6 text-gray-900 dark:text-white"
|
||||||
data-testid={isCancelled ? "cancelled-headline" : ""}
|
data-testid={isCancelled ? "cancelled-headline" : ""}
|
||||||
id="modal-headline">
|
id="modal-headline">
|
||||||
{needsConfirmation && !isCancelled
|
{needsConfirmation && !isCancelled
|
||||||
@@ -422,9 +422,10 @@ export default function Success(props: SuccessProps) {
|
|||||||
: t("meeting_is_scheduled")}
|
: t("meeting_is_scheduled")}
|
||||||
</h3>
|
</h3>
|
||||||
<div className="mt-3">
|
<div className="mt-3">
|
||||||
<p className="text-neutral-600 dark:text-gray-300">{getTitle()}</p>
|
<p className="text-gray-600 dark:text-gray-300">{getTitle()}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-300 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
|
|
||||||
|
<div className="border-bookinglightest text-bookingdark dark:border-darkgray-200 mt-8 grid grid-cols-3 border-t pt-8 text-left dark:text-gray-300">
|
||||||
{(isCancelled || reschedule) && cancellationReason && (
|
{(isCancelled || reschedule) && cancellationReason && (
|
||||||
<>
|
<>
|
||||||
<div className="font-medium">
|
<div className="font-medium">
|
||||||
@@ -499,7 +500,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
<>
|
<>
|
||||||
<div className="mt-9 font-medium">{t("additional_notes")}</div>
|
<div className="mt-9 font-medium">{t("additional_notes")}</div>
|
||||||
<div className="col-span-2 mb-2 mt-9">
|
<div className="col-span-2 mb-2 mt-9">
|
||||||
<p>{bookingInfo.description}</p>
|
<p className="break-words">{bookingInfo.description}</p>
|
||||||
</div>
|
</div>
|
||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
@@ -573,7 +574,9 @@ export default function Success(props: SuccessProps) {
|
|||||||
{!props.recurringBookings && (
|
{!props.recurringBookings && (
|
||||||
<span className="text-bookinglight inline text-gray-700">
|
<span className="text-bookinglight inline text-gray-700">
|
||||||
<span className="underline">
|
<span className="underline">
|
||||||
<Link href={`/reschedule/${bookingInfo?.uid}`}>{t("reschedule")}</Link>
|
<Link href={`/reschedule/${bookingInfo?.uid}`} legacyBehavior>
|
||||||
|
{t("reschedule")}
|
||||||
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
<span className="mx-2">{t("or_lowercase")}</span>
|
<span className="mx-2">{t("or_lowercase")}</span>
|
||||||
</span>
|
</span>
|
||||||
@@ -592,7 +595,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<hr className="border-bookinglightest dark:border-darkgray-300" />
|
<hr className="border-bookinglightest dark:border-darkgray-200" />
|
||||||
<CancelBooking
|
<CancelBooking
|
||||||
booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id }}
|
booking={{ uid: bookingInfo?.uid, title: bookingInfo?.title, id: bookingInfo?.id }}
|
||||||
profile={{ name: props.profile.name, slug: props.profile.slug }}
|
profile={{ name: props.profile.name, slug: props.profile.slug }}
|
||||||
@@ -633,8 +636,8 @@ export default function Success(props: SuccessProps) {
|
|||||||
? "&recur=" +
|
? "&recur=" +
|
||||||
encodeURIComponent(new RRule(props.eventType.recurringEvent).toString())
|
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">
|
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
|
<svg
|
||||||
className="-mt-1.5 inline-block h-4 w-4"
|
className="-mt-1.5 inline-block h-4 w-4"
|
||||||
fill="currentColor"
|
fill="currentColor"
|
||||||
@@ -643,7 +646,6 @@ export default function Success(props: SuccessProps) {
|
|||||||
<title>Google</title>
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={
|
||||||
@@ -657,8 +659,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
"&subject=" +
|
"&subject=" +
|
||||||
eventName
|
eventName
|
||||||
) + (location ? "&location=" + location : "")
|
) + (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"
|
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">
|
target="_blank">
|
||||||
<svg
|
<svg
|
||||||
@@ -669,7 +670,6 @@ export default function Success(props: SuccessProps) {
|
|||||||
<title>Microsoft Outlook</title>
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link
|
<Link
|
||||||
href={
|
href={
|
||||||
@@ -683,8 +683,7 @@ export default function Success(props: SuccessProps) {
|
|||||||
"&subject=" +
|
"&subject=" +
|
||||||
eventName
|
eventName
|
||||||
) + (location ? "&location=" + location : "")
|
) + (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"
|
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">
|
target="_blank">
|
||||||
<svg
|
<svg
|
||||||
@@ -695,10 +694,9 @@ export default function Success(props: SuccessProps) {
|
|||||||
<title>Microsoft Office</title>
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
<Link href={"data:text/calendar," + eventLink()}>
|
<Link
|
||||||
<a
|
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"
|
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"}>
|
download={props.eventType.title + ".ics"}>
|
||||||
<svg
|
<svg
|
||||||
@@ -710,7 +708,6 @@ export default function Success(props: SuccessProps) {
|
|||||||
<title>{t("other")}</title>
|
<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" />
|
<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>
|
</svg>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -849,6 +846,17 @@ export function RecurringBookings({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getEventTypesFromDB = async (id: number) => {
|
const getEventTypesFromDB = async (id: number) => {
|
||||||
|
const userSelect = {
|
||||||
|
id: true,
|
||||||
|
name: true,
|
||||||
|
username: true,
|
||||||
|
hideBranding: true,
|
||||||
|
theme: true,
|
||||||
|
brandColor: true,
|
||||||
|
darkBrandColor: true,
|
||||||
|
email: true,
|
||||||
|
timeZone: true,
|
||||||
|
};
|
||||||
const eventType = await prisma.eventType.findUnique({
|
const eventType = await prisma.eventType.findUnique({
|
||||||
where: {
|
where: {
|
||||||
id,
|
id,
|
||||||
@@ -867,17 +875,17 @@ const getEventTypesFromDB = async (id: number) => {
|
|||||||
locations: true,
|
locations: true,
|
||||||
price: true,
|
price: true,
|
||||||
currency: true,
|
currency: true,
|
||||||
|
owner: {
|
||||||
|
select: userSelect,
|
||||||
|
},
|
||||||
users: {
|
users: {
|
||||||
|
select: userSelect,
|
||||||
|
},
|
||||||
|
hosts: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
user: {
|
||||||
name: true,
|
select: userSelect,
|
||||||
username: true,
|
},
|
||||||
hideBranding: true,
|
|
||||||
theme: true,
|
|
||||||
brandColor: true,
|
|
||||||
darkBrandColor: true,
|
|
||||||
email: true,
|
|
||||||
timeZone: true,
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
team: {
|
team: {
|
||||||
@@ -921,6 +929,7 @@ const schema = z.object({
|
|||||||
uid: z.string(),
|
uid: z.string(),
|
||||||
email: z.string().optional(),
|
email: z.string().optional(),
|
||||||
eventTypeSlug: z.string().optional(),
|
eventTypeSlug: z.string().optional(),
|
||||||
|
cancel: stringToBoolean,
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSeatsEventTypeOnBooking = (
|
const handleSeatsEventTypeOnBooking = (
|
||||||
@@ -954,7 +963,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
const ssr = await ssrInit(context);
|
const ssr = await ssrInit(context);
|
||||||
const parsedQuery = schema.safeParse(context.query);
|
const parsedQuery = schema.safeParse(context.query);
|
||||||
if (!parsedQuery.success) return { notFound: true };
|
if (!parsedQuery.success) return { notFound: true };
|
||||||
const { uid, email, eventTypeSlug } = parsedQuery.data;
|
const { uid, email, eventTypeSlug, cancel } = parsedQuery.data;
|
||||||
|
|
||||||
const bookingInfo = await prisma.booking.findFirst({
|
const bookingInfo = await prisma.booking.findFirst({
|
||||||
where: {
|
where: {
|
||||||
@@ -1017,27 +1026,18 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!eventTypeRaw.users.length && eventTypeRaw.userId) {
|
eventTypeRaw.users = !!eventTypeRaw.hosts?.length
|
||||||
// TODO we should add `user User` relation on `EventType` so this extra query isn't needed
|
? eventTypeRaw.hosts.map((host) => host.user)
|
||||||
const user = await prisma.user.findUnique({
|
: eventTypeRaw.users;
|
||||||
where: {
|
|
||||||
id: eventTypeRaw.userId,
|
|
||||||
},
|
|
||||||
select: baseUserSelect,
|
|
||||||
});
|
|
||||||
if (user) {
|
|
||||||
eventTypeRaw.users.push({
|
|
||||||
...user,
|
|
||||||
avatar: "",
|
|
||||||
allowDynamicBooking: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!eventTypeRaw.users.length) {
|
if (!eventTypeRaw.users.length) {
|
||||||
|
if (!eventTypeRaw.owner)
|
||||||
return {
|
return {
|
||||||
notFound: true,
|
notFound: true,
|
||||||
};
|
};
|
||||||
|
eventTypeRaw.users.push({
|
||||||
|
...eventTypeRaw.owner,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const eventType = {
|
const eventType = {
|
||||||
@@ -1049,7 +1049,7 @@ export async function getServerSideProps(context: GetServerSidePropsContext) {
|
|||||||
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
customInputs: customInputSchema.array().parse(eventTypeRaw.customInputs),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl) {
|
if (eventType.metadata?.disableSuccessPage && eventType.successRedirectUrl && !cancel) {
|
||||||
return {
|
return {
|
||||||
redirect: {
|
redirect: {
|
||||||
destination: eventType.successRedirectUrl,
|
destination: eventType.successRedirectUrl,
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ import { useAutoAnimate } from "@formkit/auto-animate/react";
|
|||||||
import { zodResolver } from "@hookform/resolvers/zod";
|
import { zodResolver } from "@hookform/resolvers/zod";
|
||||||
import { PeriodType, SchedulingType } from "@prisma/client";
|
import { PeriodType, SchedulingType } from "@prisma/client";
|
||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
import { useRouter } from "next/router";
|
|
||||||
import { useState } from "react";
|
import { useState } from "react";
|
||||||
import { useForm } from "react-hook-form";
|
import { useForm } from "react-hook-form";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
@@ -15,6 +14,7 @@ import convertToNewDurationType from "@calcom/lib/convertToNewDurationType";
|
|||||||
import findDurationType from "@calcom/lib/findDurationType";
|
import findDurationType from "@calcom/lib/findDurationType";
|
||||||
import getEventTypeById from "@calcom/lib/getEventTypeById";
|
import getEventTypeById from "@calcom/lib/getEventTypeById";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
|
import { useTypedQuery } from "@calcom/lib/hooks/useTypedQuery";
|
||||||
import { HttpError } from "@calcom/lib/http-error";
|
import { HttpError } from "@calcom/lib/http-error";
|
||||||
import prisma from "@calcom/prisma";
|
import prisma from "@calcom/prisma";
|
||||||
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
import { customInputSchema, EventTypeMetaDataSchema } from "@calcom/prisma/zod-utils";
|
||||||
@@ -64,7 +64,6 @@ export type FormValues = {
|
|||||||
phone?: string;
|
phone?: string;
|
||||||
}[];
|
}[];
|
||||||
customInputs: CustomInputParsed[];
|
customInputs: CustomInputParsed[];
|
||||||
users: string[];
|
|
||||||
schedule: number;
|
schedule: number;
|
||||||
periodType: PeriodType;
|
periodType: PeriodType;
|
||||||
periodDays: number;
|
periodDays: number;
|
||||||
@@ -85,6 +84,8 @@ export type FormValues = {
|
|||||||
};
|
};
|
||||||
successRedirectUrl: string;
|
successRedirectUrl: string;
|
||||||
bookingLimits?: BookingLimit;
|
bookingLimits?: BookingLimit;
|
||||||
|
hosts: { userId: number }[];
|
||||||
|
hostsFixed: { userId: number }[];
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CustomInputParsed = typeof customInputSchema._output;
|
export type CustomInputParsed = typeof customInputSchema._output;
|
||||||
@@ -111,15 +112,15 @@ export type EventTypeSetupProps = RouterOutputs["viewer"]["eventTypes"]["get"];
|
|||||||
const EventTypePage = (props: EventTypeSetupProps) => {
|
const EventTypePage = (props: EventTypeSetupProps) => {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const router = useRouter();
|
const {
|
||||||
const { tabName } = querySchema.parse(router.query);
|
data: { tabName },
|
||||||
|
} = useTypedQuery(querySchema);
|
||||||
|
|
||||||
const { data: eventTypeApps } = trpc.viewer.apps.useQuery({
|
const { data: eventTypeApps } = trpc.viewer.apps.useQuery({
|
||||||
extendsFeature: "EventType",
|
extendsFeature: "EventType",
|
||||||
});
|
});
|
||||||
|
|
||||||
const { eventType, locationOptions, team, teamMembers, currentUserMembership } = props;
|
const { eventType, locationOptions, team, teamMembers, currentUserMembership } = props;
|
||||||
|
|
||||||
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
|
const [animationParentRef] = useAutoAnimate<HTMLDivElement>();
|
||||||
|
|
||||||
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
const updateMutation = trpc.viewer.eventTypes.update.useMutation({
|
||||||
@@ -185,6 +186,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
description: eventType.description ?? undefined,
|
description: eventType.description ?? undefined,
|
||||||
schedule: eventType.schedule || undefined,
|
schedule: eventType.schedule || undefined,
|
||||||
bookingLimits: eventType.bookingLimits || undefined,
|
bookingLimits: eventType.bookingLimits || undefined,
|
||||||
|
length: eventType.length,
|
||||||
hidden: eventType.hidden,
|
hidden: eventType.hidden,
|
||||||
periodDates: {
|
periodDates: {
|
||||||
startDate: periodDates.startDate,
|
startDate: periodDates.startDate,
|
||||||
@@ -194,12 +196,17 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
|
periodCountCalendarDays: eventType.periodCountCalendarDays ? "1" : "0",
|
||||||
schedulingType: eventType.schedulingType,
|
schedulingType: eventType.schedulingType,
|
||||||
minimumBookingNotice: eventType.minimumBookingNotice,
|
minimumBookingNotice: eventType.minimumBookingNotice,
|
||||||
minimumBookingNoticeInDurationType: convertToNewDurationType(
|
|
||||||
"minutes",
|
|
||||||
findDurationType(eventType.minimumBookingNotice),
|
|
||||||
eventType.minimumBookingNotice
|
|
||||||
),
|
|
||||||
metadata,
|
metadata,
|
||||||
|
hosts: !!eventType.hosts?.length
|
||||||
|
? eventType.hosts.filter((host) => !host.isFixed)
|
||||||
|
: eventType.users
|
||||||
|
.filter(() => eventType.schedulingType === SchedulingType.ROUND_ROBIN)
|
||||||
|
.map((user) => ({ userId: user.id })),
|
||||||
|
hostsFixed: !!eventType.hosts?.length
|
||||||
|
? eventType.hosts.filter((host) => host.isFixed)
|
||||||
|
: eventType.users
|
||||||
|
.filter(() => eventType.schedulingType === SchedulingType.COLLECTIVE)
|
||||||
|
.map((user) => ({ userId: user.id })),
|
||||||
},
|
},
|
||||||
resolver: zodResolver(
|
resolver: zodResolver(
|
||||||
z
|
z
|
||||||
@@ -237,7 +244,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
availability: <AvailabilityTab isTeamEvent={!!team} />,
|
availability: <AvailabilityTab isTeamEvent={!!team} />,
|
||||||
team: <EventTeamTab eventType={eventType} teamMembers={teamMembers} team={team} />,
|
team: <EventTeamTab teamMembers={teamMembers} team={team} />,
|
||||||
limits: <EventLimitsTab eventType={eventType} />,
|
limits: <EventLimitsTab eventType={eventType} />,
|
||||||
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
|
advanced: <EventAdvancedTab eventType={eventType} team={team} />,
|
||||||
recurring: <EventRecurringTab eventType={eventType} />,
|
recurring: <EventRecurringTab eventType={eventType} />,
|
||||||
@@ -278,6 +285,8 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
locations,
|
locations,
|
||||||
metadata,
|
metadata,
|
||||||
customInputs,
|
customInputs,
|
||||||
|
hosts: hostsInput,
|
||||||
|
hostsFixed,
|
||||||
// We don't need to send send these values to the backend
|
// We don't need to send send these values to the backend
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
seatsPerTimeSlotEnabled,
|
seatsPerTimeSlotEnabled,
|
||||||
@@ -286,6 +295,11 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
...input
|
...input
|
||||||
} = values;
|
} = values;
|
||||||
|
|
||||||
|
const hosts: (typeof hostsInput[number] & { isFixed?: boolean })[] = [];
|
||||||
|
if (hostsInput || hostsFixed) {
|
||||||
|
hosts.push(...hostsInput.concat(hostsFixed.map((host) => ({ isFixed: true, ...host }))));
|
||||||
|
}
|
||||||
|
|
||||||
if (bookingLimits) {
|
if (bookingLimits) {
|
||||||
const isValid = validateBookingLimitOrder(bookingLimits);
|
const isValid = validateBookingLimitOrder(bookingLimits);
|
||||||
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
|
if (!isValid) throw new Error(t("event_setup_booking_limits_error"));
|
||||||
@@ -303,6 +317,7 @@ const EventTypePage = (props: EventTypeSetupProps) => {
|
|||||||
|
|
||||||
updateMutation.mutate({
|
updateMutation.mutate({
|
||||||
...input,
|
...input,
|
||||||
|
hosts,
|
||||||
locations,
|
locations,
|
||||||
recurringEvent,
|
recurringEvent,
|
||||||
periodStartDate: periodDates.startDate,
|
periodStartDate: periodDates.startDate,
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import {
|
|||||||
CreateEventTypeButton,
|
CreateEventTypeButton,
|
||||||
EventTypeDescriptionLazy as EventTypeDescription,
|
EventTypeDescriptionLazy as EventTypeDescription,
|
||||||
} from "@calcom/features/eventtypes/components";
|
} from "@calcom/features/eventtypes/components";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
import { APP_NAME, CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
|
import { RouterOutputs, trpc, TRPCClientError } from "@calcom/trpc/react";
|
||||||
@@ -26,9 +27,10 @@ import {
|
|||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
EmptyScreen,
|
EmptyScreen,
|
||||||
Icon,
|
Icon,
|
||||||
Shell,
|
|
||||||
showToast,
|
showToast,
|
||||||
Switch,
|
Switch,
|
||||||
|
Avatar,
|
||||||
|
AvatarGroup,
|
||||||
Tooltip,
|
Tooltip,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
|
|
||||||
@@ -37,8 +39,6 @@ import { HttpError } from "@lib/core/http/error";
|
|||||||
|
|
||||||
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
import { EmbedButton, EmbedDialog } from "@components/Embed";
|
||||||
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
import SkeletonLoader from "@components/eventtype/SkeletonLoader";
|
||||||
import Avatar from "@components/ui/Avatar";
|
|
||||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
@@ -64,8 +64,8 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
|
|||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Link href={`/event-types/${type.id}?tabName=setup`}>
|
<Link
|
||||||
<a
|
href={`/event-types/${type.id}?tabName=setup`}
|
||||||
className="flex-1 overflow-hidden pr-4 text-sm"
|
className="flex-1 overflow-hidden pr-4 text-sm"
|
||||||
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
title={`${type.title} ${type.description ? `– ${type.description}` : ""}`}>
|
||||||
<div>
|
<div>
|
||||||
@@ -79,12 +79,11 @@ const Item = ({ type, group, readOnly }: { type: EventType; group: EventTypeGrou
|
|||||||
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
|
data-testid={"event-type-slug-" + type.id}>{`/${group.profile.slug}/${type.slug}`}</small>
|
||||||
{readOnly && (
|
{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">
|
<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}
|
{t("readonly")}
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
<EventTypeDescription eventType={type} />
|
<EventTypeDescription eventType={type} />
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
@@ -280,9 +279,8 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
<div className="flex justify-between space-x-2 rtl:space-x-reverse">
|
||||||
{type.users?.length > 1 && (
|
{type.users?.length > 1 && (
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
border="border-2 border-white"
|
|
||||||
className="relative top-1 right-3"
|
className="relative top-1 right-3"
|
||||||
size={8}
|
size="sm"
|
||||||
truncateAfter={4}
|
truncateAfter={4}
|
||||||
items={type.users.map((organizer) => ({
|
items={type.users.map((organizer) => ({
|
||||||
alt: organizer.name || "",
|
alt: organizer.name || "",
|
||||||
@@ -297,7 +295,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
{t("hidden")}
|
{t("hidden")}
|
||||||
</Badge>
|
</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">
|
<div className="self-center rounded-md p-2 hover:bg-gray-200">
|
||||||
<Switch
|
<Switch
|
||||||
name="Hidden"
|
name="Hidden"
|
||||||
@@ -310,7 +308,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<ButtonGroup combined>
|
<ButtonGroup combined>
|
||||||
<Tooltip content={t("preview") as string}>
|
<Tooltip content={t("preview")}>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
target="_blank"
|
target="_blank"
|
||||||
@@ -320,7 +318,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
/>
|
/>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
|
|
||||||
<Tooltip content={t("copy_link") as string}>
|
<Tooltip content={t("copy_link")}>
|
||||||
<Button
|
<Button
|
||||||
color="secondary"
|
color="secondary"
|
||||||
size="icon"
|
size="icon"
|
||||||
@@ -350,7 +348,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
data-testid={"event-type-edit-" + type.id}
|
data-testid={"event-type-edit-" + type.id}
|
||||||
StartIcon={Icon.FiEdit2}
|
StartIcon={Icon.FiEdit2}
|
||||||
onClick={() => router.push("/event-types/" + type.id)}>
|
onClick={() => router.push("/event-types/" + type.id)}>
|
||||||
{t("edit") as string}
|
{t("edit")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
@@ -359,7 +357,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
data-testid={"event-type-duplicate-" + type.id}
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
StartIcon={Icon.FiCopy}
|
StartIcon={Icon.FiCopy}
|
||||||
onClick={() => openDuplicateModal(type)}>
|
onClick={() => openDuplicateModal(type)}>
|
||||||
{t("duplicate") as string}
|
{t("duplicate")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
@@ -372,18 +370,19 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
{t("embed")}
|
{t("embed")}
|
||||||
</EmbedButton>
|
</EmbedButton>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
<DropdownMenuSeparator />
|
||||||
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
|
{/* readonly is only set when we are on a team - if we are on a user event type null will be the value. */}
|
||||||
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) && (
|
{(group.metadata?.readOnly === false || group.metadata.readOnly === null) && (
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<DropdownItem
|
<DropdownItem
|
||||||
|
color="destructive"
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteDialogOpen(true);
|
setDeleteDialogOpen(true);
|
||||||
setDeleteDialogTypeId(type.id);
|
setDeleteDialogTypeId(type.id);
|
||||||
}}
|
}}
|
||||||
StartIcon={Icon.FiTrash}
|
StartIcon={Icon.FiTrash}
|
||||||
className="w-full rounded-none">
|
className="w-full rounded-none">
|
||||||
{t("delete") as string}
|
{t("delete")}
|
||||||
</DropdownItem>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
)}
|
)}
|
||||||
@@ -402,15 +401,13 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
<DropdownMenuPortal>
|
<DropdownMenuPortal>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
<Link href={calLink}>
|
<Link href={calLink} target="_blank">
|
||||||
<a target="_blank">
|
|
||||||
<Button
|
<Button
|
||||||
color="minimal"
|
color="minimal"
|
||||||
StartIcon={Icon.FiExternalLink}
|
StartIcon={Icon.FiExternalLink}
|
||||||
className="w-full rounded-none">
|
className="w-full rounded-none">
|
||||||
{t("preview") as string}
|
{t("preview")}
|
||||||
</Button>
|
</Button>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
@@ -424,7 +421,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
navigator.clipboard.writeText(calLink);
|
navigator.clipboard.writeText(calLink);
|
||||||
showToast(t("link_copied"), "success");
|
showToast(t("link_copied"), "success");
|
||||||
}}>
|
}}>
|
||||||
{t("copy_link") as string}
|
{t("copy_link")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
{isNativeShare ? (
|
{isNativeShare ? (
|
||||||
@@ -445,7 +442,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
.then(() => showToast(t("link_shared"), "success"))
|
.then(() => showToast(t("link_shared"), "success"))
|
||||||
.catch(() => showToast(t("failed"), "error"));
|
.catch(() => showToast(t("failed"), "error"));
|
||||||
}}>
|
}}>
|
||||||
{t("share") as string}
|
{t("share")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -456,7 +453,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
color="minimal"
|
color="minimal"
|
||||||
className="w-full rounded-none"
|
className="w-full rounded-none"
|
||||||
StartIcon={Icon.FiEdit}>
|
StartIcon={Icon.FiEdit}>
|
||||||
{t("edit") as string}
|
{t("edit")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
@@ -467,10 +464,10 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
data-testid={"event-type-duplicate-" + type.id}
|
data-testid={"event-type-duplicate-" + type.id}
|
||||||
StartIcon={Icon.FiCopy}
|
StartIcon={Icon.FiCopy}
|
||||||
onClick={() => openDuplicateModal(type)}>
|
onClick={() => openDuplicateModal(type)}>
|
||||||
{t("duplicate") as string}
|
{t("duplicate")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
<DropdownMenuSeparator className="h-px bg-gray-200" />
|
<DropdownMenuSeparator />
|
||||||
<DropdownMenuItem className="outline-none">
|
<DropdownMenuItem className="outline-none">
|
||||||
<Button
|
<Button
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
@@ -480,7 +477,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
color="destructive"
|
color="destructive"
|
||||||
StartIcon={Icon.FiTrash}
|
StartIcon={Icon.FiTrash}
|
||||||
className="w-full rounded-none">
|
className="w-full rounded-none">
|
||||||
{t("delete") as string}
|
{t("delete")}
|
||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
@@ -502,7 +499,7 @@ export const EventTypeList = ({ group, groupIndex, readOnly, types }: EventTypeL
|
|||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
deleteEventTypeHandler(deleteDialogTypeId);
|
deleteEventTypeHandler(deleteDialogTypeId);
|
||||||
}}>
|
}}>
|
||||||
{t("delete_event_type_description") as string}
|
{t("delete_event_type_description")}
|
||||||
</ConfirmationDialogContent>
|
</ConfirmationDialogContent>
|
||||||
</Dialog>
|
</Dialog>
|
||||||
</div>
|
</div>
|
||||||
@@ -515,38 +512,34 @@ const EventTypeListHeading = ({
|
|||||||
teamId,
|
teamId,
|
||||||
}: EventTypeListHeadingProps): JSX.Element => {
|
}: EventTypeListHeadingProps): JSX.Element => {
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 flex">
|
<div className="mb-4 flex items-center space-x-2">
|
||||||
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
|
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
|
||||||
<a>
|
|
||||||
<Avatar
|
<Avatar
|
||||||
alt={profile?.name || ""}
|
alt={profile?.name || ""}
|
||||||
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
|
imageSrc={`${WEBAPP_URL}/${profile.slug}/avatar.png` || undefined}
|
||||||
size={8}
|
size="sm"
|
||||||
className="mt-1 inline ltr:mr-2 rtl:ml-2"
|
className="mt-1 inline ltr:mr-2 rtl:ml-2"
|
||||||
/>
|
/>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
<div>
|
<div>
|
||||||
<Link href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}>
|
<Link
|
||||||
<a className="font-bold">{profile?.name || ""}</a>
|
href={teamId ? `/settings/teams/${teamId}/profile` : "/settings/my-account/profile"}
|
||||||
|
className="font-bold">
|
||||||
|
{profile?.name || ""}
|
||||||
</Link>
|
</Link>
|
||||||
{membershipCount && teamId && (
|
{membershipCount && teamId && (
|
||||||
<span className="relative -top-px text-xs text-neutral-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
|
<span className="relative -top-px text-xs text-gray-500 ltr:ml-2 ltr:mr-2 rtl:ml-2">
|
||||||
<Link href={`/settings/teams/${teamId}/members`}>
|
<Link href={`/settings/teams/${teamId}/members`}>
|
||||||
<a>
|
|
||||||
<Badge variant="gray">
|
<Badge variant="gray">
|
||||||
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
|
<Icon.FiUsers className="mr-1 -mt-px inline h-3 w-3" />
|
||||||
{membershipCount}
|
{membershipCount}
|
||||||
</Badge>
|
</Badge>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</span>
|
</span>
|
||||||
)}
|
)}
|
||||||
{profile?.slug && (
|
{profile?.slug && (
|
||||||
<Link href={`${CAL_URL}/${profile.slug}`}>
|
<Link href={`${CAL_URL}/${profile.slug}`} className="block text-xs text-gray-500">
|
||||||
<a className="block text-xs text-neutral-500">{`${CAL_URL?.replace("https://", "")}/${
|
{`${CAL_URL?.replace("https://", "")}/${profile.slug}`}
|
||||||
profile.slug
|
|
||||||
}`}</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
@@ -581,10 +574,7 @@ const EventTypesPage = () => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div>
|
<div>
|
||||||
<Shell
|
<Shell heading={t("event_types_page_title")} subtitle={t("event_types_page_subtitle")} CTA={<CTA />}>
|
||||||
heading={t("event_types_page_title") as string}
|
|
||||||
subtitle={t("event_types_page_subtitle") as string}
|
|
||||||
CTA={<CTA />}>
|
|
||||||
<WithQuery
|
<WithQuery
|
||||||
customLoader={<SkeletonLoader />}
|
customLoader={<SkeletonLoader />}
|
||||||
success={({ data }) => (
|
success={({ data }) => (
|
||||||
|
|||||||
@@ -178,6 +178,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
throw new Error("User from session not found");
|
throw new Error("User from session not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (user.completedOnboarding) {
|
||||||
|
return { redirect: { permanent: false, destination: "/event-types" } };
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
props: {
|
props: {
|
||||||
...(await serverSideTranslations(context.locale ?? "", ["common"])),
|
...(await serverSideTranslations(context.locale ?? "", ["common"])),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
|
import Shell, { MobileNavigationMoreItems } from "@calcom/features/shell/Shell";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { MobileNavigationMoreItems, Shell } from "@calcom/ui";
|
|
||||||
|
|
||||||
export default function MorePage() {
|
export default function MorePage() {
|
||||||
const { t } = useLocale();
|
const { t } = useLocale();
|
||||||
|
|||||||
@@ -7,7 +7,6 @@ import { APP_NAME } from "@calcom/lib/constants";
|
|||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { trpc } from "@calcom/trpc/react";
|
import { trpc } from "@calcom/trpc/react";
|
||||||
import {
|
import {
|
||||||
Badge,
|
|
||||||
Button,
|
Button,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
Form,
|
Form,
|
||||||
@@ -17,6 +16,7 @@ import {
|
|||||||
SkeletonContainer,
|
SkeletonContainer,
|
||||||
SkeletonText,
|
SkeletonText,
|
||||||
Switch,
|
Switch,
|
||||||
|
UpgradeTeamsBadge,
|
||||||
} from "@calcom/ui";
|
} from "@calcom/ui";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
@@ -49,6 +49,7 @@ const AppearanceView = () => {
|
|||||||
const session = useSession();
|
const session = useSession();
|
||||||
const utils = trpc.useContext();
|
const utils = trpc.useContext();
|
||||||
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
const { data: user, isLoading } = trpc.viewer.me.useQuery();
|
||||||
|
const { data: dataHasTeamPlan, isLoading: isLoadingHasTeamPlan } = trpc.viewer.teams.hasTeamPlan.useQuery();
|
||||||
|
|
||||||
const formMethods = useForm({
|
const formMethods = useForm({
|
||||||
defaultValues: {
|
defaultValues: {
|
||||||
@@ -73,7 +74,8 @@ const AppearanceView = () => {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isLoading) return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
|
if (isLoading || isLoadingHasTeamPlan)
|
||||||
|
return <SkeletonLoader title={t("appearance")} description={t("appearance_description")} />;
|
||||||
|
|
||||||
if (!user) return null;
|
if (!user) return null;
|
||||||
|
|
||||||
@@ -180,18 +182,18 @@ const AppearanceView = () => {
|
|||||||
<p className="font-semibold ltr:mr-2 rtl:ml-2">
|
<p className="font-semibold ltr:mr-2 rtl:ml-2">
|
||||||
{t("disable_cal_branding", { appName: APP_NAME })}
|
{t("disable_cal_branding", { appName: APP_NAME })}
|
||||||
</p>
|
</p>
|
||||||
<Badge variant="gray">{t("pro")}</Badge>
|
{!dataHasTeamPlan?.hasTeamPlan && <UpgradeTeamsBadge />}
|
||||||
</div>
|
</div>
|
||||||
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
|
<p className="mt-0.5 text-gray-600">{t("removes_cal_branding", { appName: APP_NAME })}</p>
|
||||||
</div>
|
</div>
|
||||||
<div className="flex-none">
|
<div className="flex-none">
|
||||||
<Switch
|
<Switch
|
||||||
id="hideBranding"
|
id="hideBranding"
|
||||||
disabled={!session.data?.user.belongsToActiveTeam}
|
disabled={!dataHasTeamPlan?.hasTeamPlan}
|
||||||
onCheckedChange={(checked) =>
|
onCheckedChange={(checked) =>
|
||||||
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
|
formMethods.setValue("hideBranding", checked, { shouldDirty: true })
|
||||||
}
|
}
|
||||||
checked={!session.data?.user.belongsToActiveTeam ? false : value}
|
checked={!dataHasTeamPlan?.hasTeamPlan ? false : value}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -175,9 +175,7 @@ const CalendarsView = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="w-full border-t border-gray-200">
|
<div className="w-full border-t border-gray-200">
|
||||||
<p className="px-2 pt-4 text-sm text-neutral-500">
|
<p className="px-2 pt-4 text-sm text-gray-500">{t("toggle_calendars_conflict")}</p>
|
||||||
{t("toggle_calendars_conflict")}
|
|
||||||
</p>
|
|
||||||
<ul className="space-y-2 p-4">
|
<ul className="space-y-2 p-4">
|
||||||
{item.calendars.map((cal) => (
|
{item.calendars.map((cal) => (
|
||||||
<CalendarSwitch
|
<CalendarSwitch
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ import {
|
|||||||
Dropdown,
|
Dropdown,
|
||||||
DropdownMenuContent,
|
DropdownMenuContent,
|
||||||
DropdownMenuItem,
|
DropdownMenuItem,
|
||||||
|
DropdownItem,
|
||||||
DropdownMenuTrigger,
|
DropdownMenuTrigger,
|
||||||
Icon,
|
Icon,
|
||||||
List,
|
List,
|
||||||
@@ -83,7 +84,7 @@ const ConferencingLayout = () => {
|
|||||||
}
|
}
|
||||||
<div className="flex-grow truncate pl-2">
|
<div className="flex-grow truncate pl-2">
|
||||||
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
|
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
|
||||||
<h3 className="truncate text-sm font-medium text-neutral-900">{app.title}</h3>
|
<h3 className="truncate text-sm font-medium text-gray-900">{app.title}</h3>
|
||||||
</ListItemTitle>
|
</ListItemTitle>
|
||||||
<ListItemText component="p">{app.description}</ListItemText>
|
<ListItemText component="p">{app.description}</ListItemText>
|
||||||
</div>
|
</div>
|
||||||
@@ -94,16 +95,17 @@ const ConferencingLayout = () => {
|
|||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent>
|
<DropdownMenuContent>
|
||||||
<DropdownMenuItem>
|
<DropdownMenuItem>
|
||||||
<Button
|
<DropdownItem
|
||||||
|
type="button"
|
||||||
color="destructive"
|
color="destructive"
|
||||||
StartIcon={Icon.FiTrash}
|
|
||||||
disabled={app.isGlobal}
|
disabled={app.isGlobal}
|
||||||
|
StartIcon={Icon.FiTrash}
|
||||||
onClick={() => {
|
onClick={() => {
|
||||||
setDeleteCredentialId(app.credentialIds[0]);
|
setDeleteCredentialId(app.credentialIds[0]);
|
||||||
setDeleteAppModal(true);
|
setDeleteAppModal(true);
|
||||||
}}>
|
}}>
|
||||||
{t("remove_app")}
|
{t("remove_app")}
|
||||||
</Button>
|
</DropdownItem>
|
||||||
</DropdownMenuItem>
|
</DropdownMenuItem>
|
||||||
</DropdownMenuContent>
|
</DropdownMenuContent>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -181,7 +181,9 @@ const ProfileView = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
if (isLoading || !user || isLoadingAvatar || !avatar)
|
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 = {
|
const defaultValues = {
|
||||||
username: user.username || "",
|
username: user.username || "",
|
||||||
@@ -227,11 +229,7 @@ const ProfileView = () => {
|
|||||||
{/* Delete account Dialog */}
|
{/* Delete account Dialog */}
|
||||||
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
|
<Dialog open={deleteAccountOpen} onOpenChange={setDeleteAccountOpen}>
|
||||||
<DialogTrigger asChild>
|
<DialogTrigger asChild>
|
||||||
<Button
|
<Button data-testid="delete-account" color="destructive" className="mt-1" StartIcon={Icon.FiTrash2}>
|
||||||
data-testid="delete-account"
|
|
||||||
color="destructive"
|
|
||||||
className="mt-1 border-2"
|
|
||||||
StartIcon={Icon.FiTrash2}>
|
|
||||||
{t("delete_account")}
|
{t("delete_account")}
|
||||||
</Button>
|
</Button>
|
||||||
</DialogTrigger>
|
</DialogTrigger>
|
||||||
|
|||||||
@@ -92,7 +92,11 @@ export default function Signup({ prepopulateFormValues }: inferSSRProps<typeof g
|
|||||||
{...register("username")}
|
{...register("username")}
|
||||||
required
|
required
|
||||||
/>
|
/>
|
||||||
<EmailField {...register("email")} />
|
<EmailField
|
||||||
|
{...register("email")}
|
||||||
|
disabled={prepopulateFormValues?.email}
|
||||||
|
className="disabled:bg-gray-200 disabled:hover:cursor-not-allowed"
|
||||||
|
/>
|
||||||
<PasswordField
|
<PasswordField
|
||||||
labelProps={{
|
labelProps={{
|
||||||
className: "block text-sm font-medium text-gray-700",
|
className: "block text-sm font-medium text-gray-700",
|
||||||
@@ -145,6 +149,12 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
|
|||||||
prepopulateFormValues: undefined,
|
prepopulateFormValues: undefined,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
|
||||||
|
return {
|
||||||
|
notFound: true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
// no token given, treat as a normal signup without verification token
|
// no token given, treat as a normal signup without verification token
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -12,13 +12,12 @@ import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|||||||
import useTheme from "@calcom/lib/hooks/useTheme";
|
import useTheme from "@calcom/lib/hooks/useTheme";
|
||||||
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
|
import { getTeamWithMembers } from "@calcom/lib/server/queries/teams";
|
||||||
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
|
||||||
import { Avatar, Button, Icon, HeadSeo } from "@calcom/ui";
|
import { Avatar, Button, Icon, HeadSeo, AvatarGroup } from "@calcom/ui";
|
||||||
|
|
||||||
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
import { useToggleQuery } from "@lib/hooks/useToggleQuery";
|
||||||
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
import { inferSSRProps } from "@lib/types/inferSSRProps";
|
||||||
|
|
||||||
import Team from "@components/team/screens/Team";
|
import Team from "@components/team/screens/Team";
|
||||||
import AvatarGroup from "@components/ui/AvatarGroup";
|
|
||||||
|
|
||||||
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
export type TeamPageProps = inferSSRProps<typeof getServerSideProps>;
|
||||||
function TeamPage({ team }: TeamPageProps) {
|
function TeamPage({ team }: TeamPageProps) {
|
||||||
@@ -45,8 +44,10 @@ 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",
|
"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"
|
!isEmbed && "bg-white"
|
||||||
)}>
|
)}>
|
||||||
<Link href={`/team/${team.slug}/${type.slug}`}>
|
<Link
|
||||||
<a className="flex justify-between px-6 py-4" data-testid="event-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-shrink">
|
||||||
<div className="flex flex-wrap items-center space-x-2 rtl:space-x-reverse">
|
<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>
|
<h2 className="dark:text-darkgray-700 text-sm font-semibold text-gray-700">{type.title}</h2>
|
||||||
@@ -55,10 +56,9 @@ function TeamPage({ team }: TeamPageProps) {
|
|||||||
</div>
|
</div>
|
||||||
<div className="mt-1 self-center">
|
<div className="mt-1 self-center">
|
||||||
<AvatarGroup
|
<AvatarGroup
|
||||||
border="border-2 border-white dark:border-darkgray-100"
|
|
||||||
truncateAfter={4}
|
truncateAfter={4}
|
||||||
className="flex flex-shrink-0"
|
className="flex flex-shrink-0"
|
||||||
size={10}
|
size="sm"
|
||||||
items={type.users.map((user) => ({
|
items={type.users.map((user) => ({
|
||||||
alt: user.name || "",
|
alt: user.name || "",
|
||||||
title: user.name || "",
|
title: user.name || "",
|
||||||
@@ -66,7 +66,6 @@ function TeamPage({ team }: TeamPageProps) {
|
|||||||
}))}
|
}))}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
|
||||||
</Link>
|
</Link>
|
||||||
</li>
|
</li>
|
||||||
))}
|
))}
|
||||||
@@ -97,6 +96,9 @@ function TeamPage({ team }: TeamPageProps) {
|
|||||||
{!showMembers.isOn && team.eventTypes.length > 0 && (
|
{!showMembers.isOn && team.eventTypes.length > 0 && (
|
||||||
<div className="mx-auto max-w-3xl ">
|
<div className="mx-auto max-w-3xl ">
|
||||||
<EventTypes />
|
<EventTypes />
|
||||||
|
|
||||||
|
{!team.hideBookATeamMember && (
|
||||||
|
<div>
|
||||||
<div className="relative mt-12">
|
<div className="relative mt-12">
|
||||||
<div className="absolute inset-0 flex items-center" aria-hidden="true">
|
<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 className="dark:border-darkgray-300 w-full border-t border-gray-200" />
|
||||||
@@ -120,6 +122,8 @@ function TeamPage({ team }: TeamPageProps) {
|
|||||||
</aside>
|
</aside>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -51,7 +51,10 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
id: true,
|
id: true,
|
||||||
slug: true,
|
slug: true,
|
||||||
hidden: true,
|
hidden: true,
|
||||||
users: {
|
hosts: {
|
||||||
|
select: {
|
||||||
|
isFixed: true,
|
||||||
|
user: {
|
||||||
select: {
|
select: {
|
||||||
id: true,
|
id: true,
|
||||||
name: true,
|
name: true,
|
||||||
@@ -63,6 +66,8 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
darkBrandColor: true,
|
darkBrandColor: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
title: true,
|
title: true,
|
||||||
availability: true,
|
availability: true,
|
||||||
description: true,
|
description: true,
|
||||||
@@ -124,11 +129,11 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
|
|||||||
periodEndDate: eventType.periodEndDate?.toString() ?? null,
|
periodEndDate: eventType.periodEndDate?.toString() ?? null,
|
||||||
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
|
recurringEvent: parseRecurringEvent(eventType.recurringEvent),
|
||||||
locations: privacyFilteredLocations(locations),
|
locations: privacyFilteredLocations(locations),
|
||||||
users: eventType.users.map((user) => ({
|
users: eventType.hosts.map(({ user: { name, username, hideBranding, timeZone } }) => ({
|
||||||
name: user.name,
|
name,
|
||||||
username: user.username,
|
username,
|
||||||
hideBranding: user.hideBranding,
|
hideBranding,
|
||||||
timeZone: user.timeZone,
|
timeZone,
|
||||||
})),
|
})),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import { GetServerSidePropsContext } from "next";
|
import { GetServerSidePropsContext } from "next";
|
||||||
|
|
||||||
import { TeamsListing } from "@calcom/features/ee/teams/components";
|
import { TeamsListing } from "@calcom/features/ee/teams/components";
|
||||||
|
import Shell from "@calcom/features/shell/Shell";
|
||||||
import { WEBAPP_URL } from "@calcom/lib/constants";
|
import { WEBAPP_URL } from "@calcom/lib/constants";
|
||||||
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
||||||
import { Button, Icon, Shell } from "@calcom/ui";
|
import { Button, Icon } from "@calcom/ui";
|
||||||
|
|
||||||
import { ssrInit } from "@server/lib/ssr";
|
import { ssrInit } from "@server/lib/ssr";
|
||||||
|
|
||||||
|
|||||||
@@ -14,6 +14,17 @@ test.describe("App Store - Authed", () => {
|
|||||||
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
|
await expect(page.locator(`text=Connect to Apple Server`)).toBeVisible();
|
||||||
await pro.delete();
|
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", () => {
|
test.describe("App Store - Unauthed", () => {
|
||||||
|
|||||||
@@ -5,7 +5,8 @@ import { bookTimeSlot, selectFirstAvailableTimeSlotNextMonth } from "./lib/testU
|
|||||||
|
|
||||||
test.describe.configure({ mode: "parallel" });
|
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 }) => {
|
test.beforeEach(async ({ users }) => {
|
||||||
const user = await users.create();
|
const user = await users.create();
|
||||||
await user.login();
|
await user.login();
|
||||||
|
|||||||
@@ -1077,13 +1077,13 @@
|
|||||||
"new_seat_subject": "حضور جديد {{name}} لـ {{eventType}} في {{date}}",
|
"new_seat_subject": "حضور جديد {{name}} لـ {{eventType}} في {{date}}",
|
||||||
"new_seat_title": "شخص ما أضاف نفسه إلى حدث",
|
"new_seat_title": "شخص ما أضاف نفسه إلى حدث",
|
||||||
"variable": "متغير",
|
"variable": "متغير",
|
||||||
"event_name_workflow": "اسم الحدث",
|
"event_name_variable": "اسم الحدث",
|
||||||
"organizer_name_workflow": "المنظِّم",
|
"organizer_name_variable": "المنظِّم",
|
||||||
"attendee_name_workflow": "الحاضر",
|
"attendee_name_variable": "الحاضر",
|
||||||
"event_date_workflow": "تاريخ الحدث",
|
"event_date_variable": "تاريخ الحدث",
|
||||||
"event_time_workflow": "وقت الحدث",
|
"event_time_variable": "وقت الحدث",
|
||||||
"location_workflow": "الموقع",
|
"location_variable": "الموقع",
|
||||||
"additional_notes_workflow": "ملاحظات إضافية",
|
"additional_notes_variable": "ملاحظات إضافية",
|
||||||
"app_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.",
|
"app_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.",
|
||||||
"invalid_number": "رقم الهاتف غير صالح",
|
"invalid_number": "رقم الهاتف غير صالح",
|
||||||
"navigate": "تنقّل",
|
"navigate": "تنقّل",
|
||||||
@@ -1387,7 +1387,7 @@
|
|||||||
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
|
"team_url_taken": "عنوان URL هذا مأخوذ بالفعل",
|
||||||
"team_publish": "نشر فريق",
|
"team_publish": "نشر فريق",
|
||||||
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
|
"number_sms_notifications": "رقم الهاتف (إشعارات الرسائل النصية)",
|
||||||
"attendee_email_workflow": "اسم الحاضر",
|
"attendee_email_variable": "اسم الحاضر",
|
||||||
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
|
"attendee_email_info": "البريد الإلكتروني للشخص الحجز",
|
||||||
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
|
"kbar_search_placeholder": "اكتب أمرًا أو بحثًا...",
|
||||||
"invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.",
|
"invalid_credential": "لا! يبدو أن الصلاحية انتهت أو تم إلغاؤها. يرجى إعادة التثبيت مرة أخرى.",
|
||||||
|
|||||||
@@ -68,7 +68,7 @@
|
|||||||
"event_still_awaiting_approval": "Událost stále čeká na schválení",
|
"event_still_awaiting_approval": "Událost stále čeká na schválení",
|
||||||
"booking_submitted_subject": "Rezervace odeslána: {{eventType}} s {{name}} během {{date}}",
|
"booking_submitted_subject": "Rezervace odeslána: {{eventType}} s {{name}} během {{date}}",
|
||||||
"your_meeting_has_been_booked": "Vaše schůzka byla rezervována",
|
"your_meeting_has_been_booked": "Vaše schůzka byla rezervována",
|
||||||
"event_type_has_been_rescheduled_on_time_date": "Váš {{eventType}} s {{name}} byl přesunut na {{date}}.",
|
"event_type_has_been_rescheduled_on_time_date": "Váš {{title}} byl přeplánován na {{date}}.",
|
||||||
"event_has_been_rescheduled": "Změna - Vaše událost byla přesunuta na jindy",
|
"event_has_been_rescheduled": "Změna - Vaše událost byla přesunuta na jindy",
|
||||||
"request_reschedule_title_attendee": "Žádost o přeplánování rezervace",
|
"request_reschedule_title_attendee": "Žádost o přeplánování rezervace",
|
||||||
"request_reschedule_subtitle": "Organizátor {{organizer}} zrušil rezervaci a požádal vás, abyste si vybrali jiný čas.",
|
"request_reschedule_subtitle": "Organizátor {{organizer}} zrušil rezervaci a požádal vás, abyste si vybrali jiný čas.",
|
||||||
@@ -1076,13 +1076,13 @@
|
|||||||
"new_seat_subject": "Nový účastník {{name}} události {{eventType}} dne {{date}}",
|
"new_seat_subject": "Nový účastník {{name}} události {{eventType}} dne {{date}}",
|
||||||
"new_seat_title": "Někdo se přidal k události",
|
"new_seat_title": "Někdo se přidal k události",
|
||||||
"variable": "Proměnná",
|
"variable": "Proměnná",
|
||||||
"event_name_workflow": "Název události",
|
"event_name_variable": "Název události",
|
||||||
"organizer_name_workflow": "Jméno organizátora",
|
"organizer_name_variable": "Jméno organizátora",
|
||||||
"attendee_name_workflow": "Jméno účastníka",
|
"attendee_name_variable": "Jméno účastníka",
|
||||||
"event_date_workflow": "Datum události",
|
"event_date_variable": "Datum události",
|
||||||
"event_time_workflow": "Čas události",
|
"event_time_variable": "Čas události",
|
||||||
"location_workflow": "Místo",
|
"location_variable": "Místo",
|
||||||
"additional_notes_workflow": "Doplňující poznámky",
|
"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.",
|
"app_upgrade_description": "Pokud chcete použít tuto funkci, musíte provést aktualizaci na účet Pro.",
|
||||||
"invalid_number": "Neplatné telefonní číslo",
|
"invalid_number": "Neplatné telefonní číslo",
|
||||||
"navigate": "Navigace",
|
"navigate": "Navigace",
|
||||||
@@ -1386,7 +1386,7 @@
|
|||||||
"team_url_taken": "Tato adresa URL je už obsazená",
|
"team_url_taken": "Tato adresa URL je už obsazená",
|
||||||
"team_publish": "Zveřejnit tým",
|
"team_publish": "Zveřejnit tým",
|
||||||
"number_sms_notifications": "Číslo telefonu (SMS oznámení)",
|
"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",
|
"attendee_email_info": "E-mail osoby provádějící rezervaci",
|
||||||
"kbar_search_placeholder": "Zadejte příkaz nebo vyhledejte...",
|
"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.",
|
"invalid_credential": "Ale ne! Zdá se, že platnost oprávnění vypršela nebo byla zrušena. Proveďte novou instalaci.",
|
||||||
|
|||||||
@@ -67,7 +67,7 @@
|
|||||||
"event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung",
|
"event_still_awaiting_approval": "Ein Termin wartet noch auf Ihre Bestätigung",
|
||||||
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
|
"booking_submitted_subject": "Buchung eingereicht: {{eventType}} mit {{name}} am {{date}}",
|
||||||
"your_meeting_has_been_booked": "Dein Termin wurde gebucht",
|
"your_meeting_has_been_booked": "Dein Termin wurde gebucht",
|
||||||
"event_type_has_been_rescheduled_on_time_date": "Ihr {{eventType}} mit {{name}} wurde auf {{time}} ({{timeZone}}) am {{date}} verschoben.",
|
"event_type_has_been_rescheduled_on_time_date": "Ihr {{title}} wurde auf den {{date}} verschoben.",
|
||||||
"event_has_been_rescheduled": "Ihr Termin wurde verschoben.",
|
"event_has_been_rescheduled": "Ihr Termin wurde verschoben.",
|
||||||
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
|
"request_reschedule_title_attendee": "Anfrage zur Neuplanung Ihrer Buchung",
|
||||||
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
|
"request_reschedule_subtitle": "{{organizer}} hat die Buchung storniert und Sie gebeten, eine andere Zeit auszuwählen.",
|
||||||
@@ -1078,13 +1078,13 @@
|
|||||||
"new_seat_subject": "Neuer Teilnehmer {{name}} beim {{eventType}} am {{date}}",
|
"new_seat_subject": "Neuer Teilnehmer {{name}} beim {{eventType}} am {{date}}",
|
||||||
"new_seat_title": "Jemand hat sich zu einem Ereignis hinzugefügt",
|
"new_seat_title": "Jemand hat sich zu einem Ereignis hinzugefügt",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
"event_name_workflow": "Event Name",
|
"event_name_variable": "Event Name",
|
||||||
"organizer_name_workflow": "Organisator",
|
"organizer_name_variable": "Organisator",
|
||||||
"attendee_name_workflow": "Teilnehmer",
|
"attendee_name_variable": "Teilnehmer",
|
||||||
"event_date_workflow": "Event Datum",
|
"event_date_variable": "Event Datum",
|
||||||
"event_time_workflow": "Event Zeit",
|
"event_time_variable": "Event Zeit",
|
||||||
"location_workflow": "Ort",
|
"location_variable": "Ort",
|
||||||
"additional_notes_workflow": "Zusätzliche Notizen",
|
"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.",
|
"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",
|
"invalid_number": "Ungültige Telefonnummer",
|
||||||
"navigate": "Navigieren",
|
"navigate": "Navigieren",
|
||||||
@@ -1388,7 +1388,7 @@
|
|||||||
"team_url_taken": "Diese URL ist bereits vergeben",
|
"team_url_taken": "Diese URL ist bereits vergeben",
|
||||||
"team_publish": "Team veröffentlichen",
|
"team_publish": "Team veröffentlichen",
|
||||||
"number_sms_notifications": "Telefonnummer (SMS-Benachrichtigungen)",
|
"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",
|
"attendee_email_info": "Die E-Mail-Adresse der buchenden Person",
|
||||||
"kbar_search_placeholder": "Geben Sie einen Befehl ein oder suchen Sie...",
|
"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.",
|
"invalid_credential": "Oh nein! Es sieht so aus, als ob die Berechtigung abgelaufen ist oder entzogen wurden. Bitte erneut installieren.",
|
||||||
|
|||||||
@@ -38,20 +38,37 @@
|
|||||||
"confirm_or_reject_request": "Επιβεβαιώστε ή απορρίψτε το αίτημα",
|
"confirm_or_reject_request": "Επιβεβαιώστε ή απορρίψτε το αίτημα",
|
||||||
"check_bookings_page_to_confirm_or_reject": "Ελέγξτε τη σελίδα κρατήσεων για να επιβεβαιώσετε ή να απορρίψετε την κράτηση.",
|
"check_bookings_page_to_confirm_or_reject": "Ελέγξτε τη σελίδα κρατήσεων για να επιβεβαιώσετε ή να απορρίψετε την κράτηση.",
|
||||||
"event_awaiting_approval": "Ένα γεγονός περιμένει την έγκρισή σας",
|
"event_awaiting_approval": "Ένα γεγονός περιμένει την έγκρισή σας",
|
||||||
|
"ics_event_title": "{{eventType}} με {{name}}",
|
||||||
|
"join_by_entrypoint": "Συμμετάσχετε από {{entryPoint}}",
|
||||||
|
"notes": "Σημειώσεις",
|
||||||
|
"manage_my_bookings": "Διαχείριση κρατήσεων",
|
||||||
"event_type": "Τύπος Συμβάντος",
|
"event_type": "Τύπος Συμβάντος",
|
||||||
"meeting_password": "Συνθηματικό Συνάντησης",
|
"meeting_password": "Συνθηματικό Συνάντησης",
|
||||||
"meeting_url": "URL Συνάντησης",
|
"meeting_url": "URL Συνάντησης",
|
||||||
"meeting_request_rejected": "Το αίτημα συνάντησής σας απορρίφθηκε",
|
"meeting_request_rejected": "Το αίτημα συνάντησής σας απορρίφθηκε",
|
||||||
"hi": "Γεια",
|
"hi": "Γεια",
|
||||||
|
"join_team": "Γίνε μέλος της ομάδας",
|
||||||
"manage_this_team": "Διαχείριση αυτής της ομάδας",
|
"manage_this_team": "Διαχείριση αυτής της ομάδας",
|
||||||
"team_info": "Πληροφορίες Ομάδας",
|
"team_info": "Πληροφορίες Ομάδας",
|
||||||
|
"request_another_invitation_email": "Αν προτιμάτε να μην χρησιμοποιείτε το {{toEmail}} ως {{appName}} email σας ή να έχετε ήδη λογαριασμό {{appName}} , παρακαλώ ζητήστε μια άλλη πρόσκληση σε αυτό το email.",
|
||||||
|
"you_have_been_invited": "Έχετε προσκληθεί να γίνετε μέλος της ομάδας {{teamName}}",
|
||||||
|
"user_invited_you": "Ο χρήστης {{user}} σας προσκάλεσε να συμμετάσχετε στην ομάδα {{team}} στο {{appName}}",
|
||||||
"hidden_team_member_title": "Είστε κρυμμένοι σε αυτήν την ομάδα",
|
"hidden_team_member_title": "Είστε κρυμμένοι σε αυτήν την ομάδα",
|
||||||
|
"link_expires": "Υ.Γ. Λήγει σε {{expiresIn}} ώρες.",
|
||||||
|
"team_upgrade_banner_action": "Αναβάθμισε εδώ",
|
||||||
|
"team_upgraded_successfully": "Η ομάδα σας αναβαθμίστηκε επιτυχώς!",
|
||||||
|
"use_link_to_reset_password": "Χρησιμοποιήστε τον παρακάτω σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασης",
|
||||||
|
"hey_there": "Γεια σας,",
|
||||||
|
"forgot_your_password_calcom": "Ξεχάσατε τον κωδικό πρόσβασης? - {{appName}}",
|
||||||
|
"confirm_delete_webhook": "Ναι, διαγραφή webhook",
|
||||||
"edit_webhook": "Επεξεργασία Webhook",
|
"edit_webhook": "Επεξεργασία Webhook",
|
||||||
"delete_webhook": "Διαγραφή Webhook",
|
"delete_webhook": "Διαγραφή Webhook",
|
||||||
|
"webhook_status": "Κατάσταση Webhook",
|
||||||
"webhook_enabled": "Ενεργοποιημένο Webhook",
|
"webhook_enabled": "Ενεργοποιημένο Webhook",
|
||||||
"webhook_disabled": "Απενεργοποιημένο Webhook",
|
"webhook_disabled": "Απενεργοποιημένο Webhook",
|
||||||
"webhook_response": "Απάντηση Webhook",
|
"webhook_response": "Απάντηση Webhook",
|
||||||
"webhook_test": "Έλεγχος Webhook",
|
"webhook_test": "Έλεγχος Webhook",
|
||||||
|
"manage_your_webhook": "Διαχείριση του webhook σας",
|
||||||
"webhook_created_successfully": "Το Webhook δημιουργήθηκε επιτυχώς!",
|
"webhook_created_successfully": "Το Webhook δημιουργήθηκε επιτυχώς!",
|
||||||
"webhook_updated_successfully": "Το Webhook ενημερώθηκε επιτυχώς!",
|
"webhook_updated_successfully": "Το Webhook ενημερώθηκε επιτυχώς!",
|
||||||
"webhook_removed_successfully": "Το Webhook αφαιρέθηκε επιτυχώς!",
|
"webhook_removed_successfully": "Το Webhook αφαιρέθηκε επιτυχώς!",
|
||||||
@@ -62,6 +79,14 @@
|
|||||||
"past": "Παρελθοντικά",
|
"past": "Παρελθοντικά",
|
||||||
"choose_a_file": "Επιλογή αρχείου...",
|
"choose_a_file": "Επιλογή αρχείου...",
|
||||||
"upload_image": "Μεταφόρτωση εικόνας",
|
"upload_image": "Μεταφόρτωση εικόνας",
|
||||||
|
"view_notifications": "Εμφάνιση ειδοποιήσεων",
|
||||||
|
"view_public_page": "Προβολή δημόσιας σελίδας",
|
||||||
|
"copy_public_page_link": "Αντιγραφή συνδέσμου δημόσιας σελίδας",
|
||||||
|
"sign_out": "Αποσύνδεση",
|
||||||
|
"2fa_confirm_current_password": "Επιβεβαιώστε τον κωδικό πρόσβασης σας για να ξεκινήσετε.",
|
||||||
|
"required": "Υποχρεωτικό",
|
||||||
|
"optional": "Προαιρετικό",
|
||||||
|
"guests": "Επισκέπτες",
|
||||||
"username": "Όνομα χρήστη",
|
"username": "Όνομα χρήστη",
|
||||||
"is_still_available": "είναι ακόμα διαθέσιμο.",
|
"is_still_available": "είναι ακόμα διαθέσιμο.",
|
||||||
"documentation": "Τεκμηρίωση",
|
"documentation": "Τεκμηρίωση",
|
||||||
@@ -88,17 +113,37 @@
|
|||||||
"dont_have_an_account": "Δεν έχετε λογαριασμό;",
|
"dont_have_an_account": "Δεν έχετε λογαριασμό;",
|
||||||
"sign_in_account": "Συνδεθείτε στο λογαριασμό σας",
|
"sign_in_account": "Συνδεθείτε στο λογαριασμό σας",
|
||||||
"sign_in": "Είσοδος",
|
"sign_in": "Είσοδος",
|
||||||
|
"error_during_login": "Παρουσιάστηκε σφάλμα κατά τη σύνδεσή σας. Επιστρέψτε στην οθόνη σύνδεσης και προσπαθήστε ξανά.",
|
||||||
|
"request_password_reset": "Αποστολή email επαναφοράς",
|
||||||
|
"forgot_password": "Ξεχάσατε τον κωδικό πρόσβασης?",
|
||||||
|
"all_apps": "Όλα",
|
||||||
|
"check_email_reset_password": "Ελέγξτε το email σας. Σας στείλαμε ένα σύνδεσμο για να επαναφέρετε τον κωδικό πρόσβασής σας.",
|
||||||
|
"few_sentences_about_yourself": "Μερικά λόγια για τον εσάς. Αυτό θα εμφανιστεί στην προσωπική σελίδα σας.",
|
||||||
|
"set_availability": "Όρισε τη διαθεσιμότητά σου",
|
||||||
|
"details": "Λεπτομέρειες",
|
||||||
|
"welcome": "Καλωσορίσατε",
|
||||||
|
"welcome_back": "Καλωσορίσατε πίσω",
|
||||||
|
"welcome_to_calcom": "Καλωσορίσατε στο {{appName}}",
|
||||||
|
"connect_caldav": "Σύνδεση στο CalDav (Beta)",
|
||||||
|
"credentials_stored_and_encrypted": "Τα διαπιστευτήριά σας θα αποθηκευτούν και θα κρυπτογραφηθούν.",
|
||||||
"connect": "Σύνδεση",
|
"connect": "Σύνδεση",
|
||||||
"try_for_free": "Δοκιμάστε δωρεάν",
|
"try_for_free": "Δοκιμάστε δωρεάν",
|
||||||
"add_to_calendar": "Προσθήκη στο ημερολόγιο",
|
"add_to_calendar": "Προσθήκη στο ημερολόγιο",
|
||||||
"add_another_calendar": "Προσθήκη άλλου ημερολογίου",
|
"add_another_calendar": "Προσθήκη άλλου ημερολογίου",
|
||||||
|
"email_sign_in_subject": "Ο σύνδεσμος εισόδου για {{appName}}",
|
||||||
"meeting_is_scheduled": "Η συνάντηση έχει προγραμματιστεί",
|
"meeting_is_scheduled": "Η συνάντηση έχει προγραμματιστεί",
|
||||||
"submitted": "Η κράτησή σας έχει υποβληθεί",
|
"submitted": "Η κράτησή σας έχει υποβληθεί",
|
||||||
|
"booking_submitted": "Η κράτησή σας έχει υποβληθεί",
|
||||||
|
"booking_confirmed": "Η κράτησή σας έχει επιβεβαιωθεί",
|
||||||
|
"booking_confirmed_recurring": "Η επαναλαμβανόμενη συνάντησή σας έχει επιβεβαιωθεί",
|
||||||
|
"warning_recurring_event_payment": "Οι πληρωμές δεν υποστηρίζονται ακόμη στις επαναλαμβανόμενες εκδηλώσεις",
|
||||||
|
"enter_new_password": "Εισάγετε τον νέο κωδικό πρόσβασης που θέλετε για το λογαριασμό σας.",
|
||||||
"reset_password": "Επαναφορά Κωδικού Πρόσβασης",
|
"reset_password": "Επαναφορά Κωδικού Πρόσβασης",
|
||||||
"change_your_password": "Αλλαγή κωδικού πρόσβασης",
|
"change_your_password": "Αλλαγή κωδικού πρόσβασης",
|
||||||
"show_password": "Εμφάνιση κωδικού πρόσβασης",
|
"show_password": "Εμφάνιση κωδικού πρόσβασης",
|
||||||
"hide_password": "Απόκρυψη κωδικού πρόσβασης",
|
"hide_password": "Απόκρυψη κωδικού πρόσβασης",
|
||||||
"try_again": "Δοκιμάστε ξανά",
|
"try_again": "Δοκιμάστε ξανά",
|
||||||
|
"request_is_expired": "Το αίτημα έχει λήξει.",
|
||||||
"sunday_time_error": "Μη έγκυρη ώρα την Κυριακή",
|
"sunday_time_error": "Μη έγκυρη ώρα την Κυριακή",
|
||||||
"monday_time_error": "Μη έγκυρη ώρα τη Δευτέρα",
|
"monday_time_error": "Μη έγκυρη ώρα τη Δευτέρα",
|
||||||
"tuesday_time_error": "Μη έγκυρη ώρα την Τρίτη",
|
"tuesday_time_error": "Μη έγκυρη ώρα την Τρίτη",
|
||||||
@@ -106,6 +151,127 @@
|
|||||||
"thursday_time_error": "Μη έγκυρη ώρα την Πέμπτη",
|
"thursday_time_error": "Μη έγκυρη ώρα την Πέμπτη",
|
||||||
"friday_time_error": "Μη έγκυρη ώρα την Παρασκευή",
|
"friday_time_error": "Μη έγκυρη ώρα την Παρασκευή",
|
||||||
"saturday_time_error": "Μη έγκυρη ώρα το Σάββατο",
|
"saturday_time_error": "Μη έγκυρη ώρα το Σάββατο",
|
||||||
|
"error_with_status_code_occured": "Παρουσιάστηκε ένα σφάλμα με κωδικό κατάστασης {{status}}.",
|
||||||
|
"booking_already_cancelled": "Αυτή η κράτηση είχε ήδη ακυρωθεί",
|
||||||
|
"booking_already_accepted_rejected": "Αυτή η κράτηση έγινε ήδη αποδεκτή ή απορρίφθηκε",
|
||||||
|
"go_back_home": "Επιστροφή στην αρχική σελίδα",
|
||||||
|
"or_go_back_home": "Ή επιστροφή στην αρχική σελίδα",
|
||||||
"no_meeting_found": "Δε βρέθηκε συνάντηση",
|
"no_meeting_found": "Δε βρέθηκε συνάντηση",
|
||||||
"bookings": "Κρατήσεις"
|
"no_status_bookings_yet": "Δεν υπάρχουν {{status}} κρατήσεις",
|
||||||
|
"no_status_bookings_yet_description": "Δεν έχετε {{status}} κρατήσεις. {{description}}",
|
||||||
|
"bookings": "Κρατήσεις",
|
||||||
|
"unconfirmed_bookings_tooltip": "Μη επιβεβαιωμένες κρατήσεις",
|
||||||
|
"start_time": "Ώρα έναρξης",
|
||||||
|
"end_time": "Ώρα λήξης",
|
||||||
|
"your_day_starts_at": "Η ημέρα σας ξεκινάει στις",
|
||||||
|
"your_day_ends_at": "Η ημέρα σας τελειώνει στις",
|
||||||
|
"change_available_times": "Αλλαγή διαθέσιμων ωρών",
|
||||||
|
"change_your_available_times": "Αλλάξτε τις διαθέσιμες ώρες σας",
|
||||||
|
"change_start_end": "Αλλάξτε την ώρα έναρξης και λήξης της ημέρας σας",
|
||||||
|
"current_start_date": "Αυτή τη στιγμή, η ημέρα σας έχει οριστεί να ξεκινήσει στις",
|
||||||
|
"email": "Email",
|
||||||
|
"email_placeholder": "jdoe@example.com",
|
||||||
|
"loading": "Φόρτωση...",
|
||||||
|
"deleting": "Διαγραφή...",
|
||||||
|
"developer": "Προγραμματιστές",
|
||||||
|
"schedule_a_meeting": "Προγραμματισμός συνάντησης",
|
||||||
|
"view_and_manage_billing_details": "Προβολή και διαχείριση των στοιχείων χρέωσης",
|
||||||
|
"view_and_edit_billing_details": "Προβολή και επεξεργασία των στοιχείων χρέωσης σας, καθώς και ακύρωση της συνδρομής σας.",
|
||||||
|
"need_anything_else": "Χρειάζεστε οτιδήποτε άλλο;",
|
||||||
|
"further_billing_help": "Αν χρειάζεστε περαιτέρω βοήθεια με την τιμολόγηση, η ομάδα υποστήριξής μας είναι εδώ για να βοηθήσει.",
|
||||||
|
"contact": "Επικοινωνία",
|
||||||
|
"contact_our_support_team": "Επικοινωνήστε με την ομάδα υποστήριξης",
|
||||||
|
"no_event_types_have_been_setup": "Αυτός ο χρήστης δεν έχει ορίσει ακόμα τύπους γεγονότων.",
|
||||||
|
"edit_logo": "Επεξεργασία λογοτύπου",
|
||||||
|
"upload_a_logo": "Ανεβάστε ένα λογότυπο",
|
||||||
|
"remove_logo": "Αφαίρεση λογότυπου",
|
||||||
|
"enable": "Ενεργοποίηση",
|
||||||
|
"2fa": "Ταυτοποίηση Δύο Παραμέτρων",
|
||||||
|
"security": "Ασφάλεια",
|
||||||
|
"manage_account_security": "Διαχειριστείτε την ασφάλεια του λογαριασμού σας.",
|
||||||
|
"password": "Κωδικός",
|
||||||
|
"password_updated_successfully": "Ο κωδικός πρόσβασης ενημερώθηκε επιτυχώς",
|
||||||
|
"password_has_been_changed": "Ο κωδικός πρόσβασής σας έχει αλλάξει με επιτυχία.",
|
||||||
|
"error_changing_password": "Σφάλμα στην αλλαγή κωδικού πρόσβασης",
|
||||||
|
"something_went_wrong": "Κάτι πήγε στραβά.",
|
||||||
|
"something_doesnt_look_right": "Κάτι δεν φαίνεται σωστά;",
|
||||||
|
"please_try_again": "Παρακαλώ δοκιμάστε ξανά.",
|
||||||
|
"super_secure_new_password": "Ο σούπερ ασφαλής νέος κωδικός πρόσβασης σας",
|
||||||
|
"new_password": "Νέος Κωδικός",
|
||||||
|
"your_old_password": "Ο παλιός σας κωδικός",
|
||||||
|
"current_password": "Τρέχων Κωδικός",
|
||||||
|
"change_password": "Αλλαγή Κωδικού",
|
||||||
|
"change_secret": "Αλλαγή Μυστικού",
|
||||||
|
"new_password_matches_old_password": "Ο νέος κωδικός πρόσβασης ταιριάζει με τον παλιό σας κωδικό. Παρακαλώ επιλέξτε διαφορετικό κωδικό πρόσβασης.",
|
||||||
|
"your_name": "Το όνομά σας",
|
||||||
|
"email_address": "Διεύθυνση ηλεκτρονικού ταχυδρομείου",
|
||||||
|
"enter_valid_email": "Παρακαλώ εισάγετε ένα έγκυρο email",
|
||||||
|
"location": "Τοποθεσία",
|
||||||
|
"address": "Διεύθυνση",
|
||||||
|
"enter_address": "Εισάγετε διεύθυνση",
|
||||||
|
"yes": "ναι",
|
||||||
|
"no": "όχι",
|
||||||
|
"additional_notes": "Πρόσθετες σημειώσεις",
|
||||||
|
"your_number": "Ο αριθμός τηλεφώνου σας",
|
||||||
|
"phone_number": "Αριθμός Τηλεφώνου",
|
||||||
|
"attendee_phone_number": "Αριθμός Τηλεφώνου Συμμετέχοντα",
|
||||||
|
"host_phone_number": "Ο αριθμός τηλεφώνου σας",
|
||||||
|
"enter_phone_number": "Εισάγετε αριθμό τηλεφώνου",
|
||||||
|
"or": "Ή",
|
||||||
|
"go_back": "Επιστροφή",
|
||||||
|
"email_or_username": "Email ή Όνομα χρήστη",
|
||||||
|
"role": "Ρόλος",
|
||||||
|
"edit_role": "Επεξεργασία Ρόλου",
|
||||||
|
"edit_team": "Επεξεργασία ομάδας",
|
||||||
|
"reject": "Απορρίψτε",
|
||||||
|
"reject_all": "Απόρριψη όλων",
|
||||||
|
"accept": "Αποδοχή",
|
||||||
|
"leave": "Αποχώρηση",
|
||||||
|
"team_name": "Όνομα ομάδας",
|
||||||
|
"your_team_name": "Το όνομα της ομάδας σας",
|
||||||
|
"team_updated_successfully": "Επιτυχής ενημέρωση ομάδας",
|
||||||
|
"your_team_updated_successfully": "Η ομάδα σας ενημερώθηκε με επιτυχία.",
|
||||||
|
"about": "Σχετικά",
|
||||||
|
"confirm_remove_member": "Ναι, αφαίρεση μέλους",
|
||||||
|
"remove_member": "Αφαίρεση μέλους",
|
||||||
|
"manage_your_team": "Διαχείριση ομάδας",
|
||||||
|
"no_teams": "Δεν έχετε καμία ομάδα ακόμη.",
|
||||||
|
"submit": "Υποβολή",
|
||||||
|
"delete": "Διαγραφή",
|
||||||
|
"update": "Ενημέρωση",
|
||||||
|
"save": "Αποθήκευση",
|
||||||
|
"pending": "Εκκρεμεί",
|
||||||
|
"share": "Κοινοποίηση",
|
||||||
|
"leave_team": "Αποχώρηση από ομάδα",
|
||||||
|
"confirm_leave_team": "Ναι, αποχώρηση από την ομάδα",
|
||||||
|
"preview": "Προεπισκόπηση",
|
||||||
|
"link_copied": "Ο σύνδεσμος αντιγράφηκε!",
|
||||||
|
"private_link_copied": "Ο ιδιωτικός σύνδεσμος αντιγράφηκε!",
|
||||||
|
"title": "Τίτλος",
|
||||||
|
"description": "Περιγραφή",
|
||||||
|
"preview_team": "Προεπισκόπηση ομάδας",
|
||||||
|
"duration": "Διάρκεια",
|
||||||
|
"minutes": "Λεπτά",
|
||||||
|
"one_time_link": "Σύνδεσμος μιας χρήσης",
|
||||||
|
"first_day_of_week": "Πρώτη ημέρα της εβδομάδας",
|
||||||
|
"weekly_one": "εβδομάδα",
|
||||||
|
"weekly_other": "εβδομάδες",
|
||||||
|
"monthly_one": "μήνας",
|
||||||
|
"monthly_other": "μήνες",
|
||||||
|
"yearly_one": "έτος",
|
||||||
|
"yearly_other": "έτη",
|
||||||
|
"max": "Μέγιστο",
|
||||||
|
"create_team": "Δημιουργία ομάδας",
|
||||||
|
"name": "Όνομα",
|
||||||
|
"create_new_team": "Δημιουργία νέας ομάδας",
|
||||||
|
"new_team": "Νέα Ομάδα",
|
||||||
|
"teams": "Ομάδες",
|
||||||
|
"team": "Ομάδα",
|
||||||
|
"enabled": "Ενεργοποιημένο",
|
||||||
|
"disabled": "Απενεργοποιημένο",
|
||||||
|
"show_advanced_settings": "Εμφάνιση προχωρημένων ρυθμίσεων",
|
||||||
|
"event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια",
|
||||||
|
"label": "Ετικέτα",
|
||||||
|
"edit": "Επεξεργασία",
|
||||||
|
"disable_guests": "Απενεργοποίηση επισκεπτών"
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,8 @@
|
|||||||
"calcom_explained": "{{appName}} provides scheduling infrastructure for absolutely everyone.",
|
"calcom_explained": "{{appName}} provides scheduling infrastructure for absolutely everyone.",
|
||||||
"have_any_questions": "Have questions? We're here to help.",
|
"have_any_questions": "Have questions? We're here to help.",
|
||||||
"reset_password_subject": "{{appName}}: Reset password instructions",
|
"reset_password_subject": "{{appName}}: Reset password instructions",
|
||||||
"event_declined_subject": "Declined: {{eventType}} with {{name}} at {{date}}",
|
"event_declined_subject": "Declined: {{title}} at {{date}}",
|
||||||
"event_cancelled_subject": "Cancelled: {{eventType}} with {{name}} at {{date}}",
|
"event_cancelled_subject": "Cancelled: {{title}} at {{date}}",
|
||||||
"event_request_declined": "Your event request has been declined",
|
"event_request_declined": "Your event request has been declined",
|
||||||
"event_request_declined_recurring": "Your recurring event request has been declined",
|
"event_request_declined_recurring": "Your recurring event request has been declined",
|
||||||
"event_request_cancelled": "Your scheduled event was cancelled",
|
"event_request_cancelled": "Your scheduled event was cancelled",
|
||||||
@@ -32,7 +32,7 @@
|
|||||||
"refund_failed": "The refund for the event {{eventType}} with {{userName}} on {{date}} failed.",
|
"refund_failed": "The refund for the event {{eventType}} with {{userName}} on {{date}} failed.",
|
||||||
"check_with_provider_and_user": "Please check with your payment provider and {{user}} how to handle this.",
|
"check_with_provider_and_user": "Please check with your payment provider and {{user}} how to handle this.",
|
||||||
"a_refund_failed": "A refund failed",
|
"a_refund_failed": "A refund failed",
|
||||||
"awaiting_payment_subject": "Awaiting Payment: {{eventType}} with {{name}} on {{date}}",
|
"awaiting_payment_subject": "Awaiting Payment: {{title}} on {{date}}",
|
||||||
"meeting_awaiting_payment": "Your meeting is awaiting payment",
|
"meeting_awaiting_payment": "Your meeting is awaiting payment",
|
||||||
"help": "Help",
|
"help": "Help",
|
||||||
"price": "Price",
|
"price": "Price",
|
||||||
@@ -64,11 +64,11 @@
|
|||||||
"someone_requested_an_event": "Someone has requested to schedule an event on your calendar.",
|
"someone_requested_an_event": "Someone has requested to schedule an event on your calendar.",
|
||||||
"someone_requested_password_reset": "Someone has requested a link to change your password.",
|
"someone_requested_password_reset": "Someone has requested a link to change your password.",
|
||||||
"password_reset_instructions": "If you didn't request this, you can safely ignore this email and your password will not be changed.",
|
"password_reset_instructions": "If you didn't request this, you can safely ignore this email and your password will not be changed.",
|
||||||
"event_awaiting_approval_subject": "Awaiting Approval: {{eventType}} with {{name}} at {{date}}",
|
"event_awaiting_approval_subject": "Awaiting Approval: {{eventType}} at {{date}}",
|
||||||
"event_still_awaiting_approval": "An event is still waiting for your approval",
|
"event_still_awaiting_approval": "An event is still waiting for your approval",
|
||||||
"booking_submitted_subject": "Booking Submitted: {{eventType}} with {{name}} at {{date}}",
|
"booking_submitted_subject": "Booking Submitted: {{title}} at {{date}}",
|
||||||
"your_meeting_has_been_booked": "Your meeting has been booked",
|
"your_meeting_has_been_booked": "Your meeting has been booked",
|
||||||
"event_type_has_been_rescheduled_on_time_date": "Your {{eventType}} with {{name}} has been rescheduled to {{date}}.",
|
"event_type_has_been_rescheduled_on_time_date": "Your {{title}} has been rescheduled to {{date}}.",
|
||||||
"event_has_been_rescheduled": "Updated - Your event has been rescheduled",
|
"event_has_been_rescheduled": "Updated - Your event has been rescheduled",
|
||||||
"request_reschedule_title_attendee": "Request to reschedule your booking",
|
"request_reschedule_title_attendee": "Request to reschedule your booking",
|
||||||
"request_reschedule_subtitle": "{{organizer}} has cancelled the booking and requested you to pick another time.",
|
"request_reschedule_subtitle": "{{organizer}} has cancelled the booking and requested you to pick another time.",
|
||||||
@@ -521,6 +521,8 @@
|
|||||||
"change_member_role": "Change team member role",
|
"change_member_role": "Change team member role",
|
||||||
"disable_cal_branding": "Disable {{appName}} branding",
|
"disable_cal_branding": "Disable {{appName}} branding",
|
||||||
"disable_cal_branding_description": "Hide all {{appName}} branding from your public pages.",
|
"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",
|
"danger_zone": "Danger zone",
|
||||||
"back": "Back",
|
"back": "Back",
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
@@ -797,12 +799,14 @@
|
|||||||
"no_category_apps_description_analytics": "Add an analytics app for your booking pages",
|
"no_category_apps_description_analytics": "Add an analytics app for your booking pages",
|
||||||
"no_category_apps_description_automation": "Add an automation app to use",
|
"no_category_apps_description_automation": "Add an automation app to use",
|
||||||
"no_category_apps_description_other": "Add any other type of app to do all sorts of things",
|
"no_category_apps_description_other": "Add any other type of app to do all sorts of things",
|
||||||
|
"no_category_apps_description_web3": "Add a web3 app for your booking pages",
|
||||||
"installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.",
|
"installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.",
|
||||||
"installed_app_conferencing_description": "Add your favourite video conferencing apps for your meetings",
|
"installed_app_conferencing_description": "Add your favourite video conferencing apps for your meetings",
|
||||||
"installed_app_payment_description": "Configure which payment processing services to use when charging your clients.",
|
"installed_app_payment_description": "Configure which payment processing services to use when charging your clients.",
|
||||||
"installed_app_analytics_description": "Configure which analytics apps to use for your booking pages",
|
"installed_app_analytics_description": "Configure which analytics apps to use for your booking pages",
|
||||||
"installed_app_other_description": "All your installed apps from other categories.",
|
"installed_app_other_description": "All your installed apps from other categories.",
|
||||||
"installed_app_automation_description": "Configure which automation apps to use",
|
"installed_app_automation_description": "Configure which automation apps to use",
|
||||||
|
"installed_app_web3_description": "Configure which web3 apps to use for your booking pages",
|
||||||
"analytics": "Analytics",
|
"analytics": "Analytics",
|
||||||
"empty_installed_apps_headline": "No apps installed",
|
"empty_installed_apps_headline": "No apps installed",
|
||||||
"empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.",
|
"empty_installed_apps_description": "Apps enable you to enhance your workflow and improve your scheduling life significantly.",
|
||||||
@@ -1082,13 +1086,13 @@
|
|||||||
"new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}",
|
"new_seat_subject": "New Attendee {{name}} on {{eventType}} at {{date}}",
|
||||||
"new_seat_title": "Someone has added themselves to an event",
|
"new_seat_title": "Someone has added themselves to an event",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
"event_name_workflow": "Event name",
|
"event_name_variable": "Event name",
|
||||||
"organizer_name_workflow": "Organizer",
|
"organizer_name_variable": "Organizer",
|
||||||
"attendee_name_workflow": "Attendee",
|
"attendee_name_variable": "Attendee",
|
||||||
"event_date_workflow": "Event date",
|
"event_date_variable": "Event date",
|
||||||
"event_time_workflow": "Event time",
|
"event_time_variable": "Event time",
|
||||||
"location_workflow": "Location",
|
"location_variable": "Location",
|
||||||
"additional_notes_workflow": "Additional notes",
|
"additional_notes_variable": "Additional notes",
|
||||||
"app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
|
"app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
|
||||||
"invalid_number": "Invalid phone number",
|
"invalid_number": "Invalid phone number",
|
||||||
"navigate": "Navigate",
|
"navigate": "Navigate",
|
||||||
@@ -1215,6 +1219,7 @@
|
|||||||
"connect_automation_apps": "Connect automation apps",
|
"connect_automation_apps": "Connect automation apps",
|
||||||
"connect_analytics_apps": "Connect analytics apps",
|
"connect_analytics_apps": "Connect analytics apps",
|
||||||
"connect_other_apps": "Connect other apps",
|
"connect_other_apps": "Connect other apps",
|
||||||
|
"connect_web3_apps": "Connect web3 apps",
|
||||||
"current_step_of_total": "Step {{currentStep}} of {{maxSteps}}",
|
"current_step_of_total": "Step {{currentStep}} of {{maxSteps}}",
|
||||||
"add_variable": "Add variable",
|
"add_variable": "Add variable",
|
||||||
"custom_phone_number": "Custom phone number",
|
"custom_phone_number": "Custom phone number",
|
||||||
@@ -1402,7 +1407,7 @@
|
|||||||
"team_url_taken": "This URL is already taken",
|
"team_url_taken": "This URL is already taken",
|
||||||
"team_publish": "Publish team",
|
"team_publish": "Publish team",
|
||||||
"number_sms_notifications": "Phone number (SMS\u00a0notifications)",
|
"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",
|
"attendee_email_info": "The person booking's email",
|
||||||
"kbar_search_placeholder": "Type a command or search...",
|
"kbar_search_placeholder": "Type a command or search...",
|
||||||
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
|
"invalid_credential": "Oh no! Looks like permission expired or was revoked. Please reinstall again.",
|
||||||
@@ -1454,10 +1459,11 @@
|
|||||||
"enter_option": "Enter Option {{index}}",
|
"enter_option": "Enter Option {{index}}",
|
||||||
"add_an_option": "Add an option",
|
"add_an_option": "Add an option",
|
||||||
"radio": "Radio",
|
"radio": "Radio",
|
||||||
|
"google_meet_warning": "In order to use Google Meet you must set your destination calendar to a Google Calendar",
|
||||||
"individual":"Individual",
|
"individual":"Individual",
|
||||||
"all_bookings_filter_label":"All Bookings",
|
"all_bookings_filter_label":"All Bookings",
|
||||||
"all_users_filter_label":"All Users",
|
"all_users_filter_label":"All Users",
|
||||||
"meeting_url_workflow": "Meeting url",
|
"meeting_url_variable": "Meeting url",
|
||||||
"meeting_url_info": "The event meeting conference url",
|
"meeting_url_info": "The event meeting conference url",
|
||||||
"date_overrides": "Date overrides",
|
"date_overrides": "Date overrides",
|
||||||
"date_overrides_subtitle": "Add dates when your availability changes from your daily hours.",
|
"date_overrides_subtitle": "Add dates when your availability changes from your daily hours.",
|
||||||
@@ -1488,5 +1494,21 @@
|
|||||||
"disable_success_page":"Disable Success Page (only works if you have a redirect URL)",
|
"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",
|
"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",
|
"change_password_admin": "Change Password to gain admin access",
|
||||||
"username_already_taken": "Username is already taken"
|
"username_already_taken": "Username is already taken",
|
||||||
|
"assignment": "Assignment",
|
||||||
|
"fixed_hosts": "Fixed Hosts",
|
||||||
|
"add_fixed_hosts": "Add fixed hosts",
|
||||||
|
"round_robin_hosts": "Round-Robin Hosts",
|
||||||
|
"minimum_round_robin_hosts_count": "Number of hosts required to attend",
|
||||||
|
"hosts": "Hosts",
|
||||||
|
"upgrade_to_enable_feature": "You need to create a team to enable this feature. Click to create a team.",
|
||||||
|
"new_attendee": "New Attendee",
|
||||||
|
"awaiting_approval": "Awaiting Approval",
|
||||||
|
"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",
|
||||||
|
"no_recordings_found": "No recordings found"
|
||||||
}
|
}
|
||||||
@@ -68,7 +68,7 @@
|
|||||||
"event_still_awaiting_approval": "Un evento aún está esperando su aprobación",
|
"event_still_awaiting_approval": "Un evento aún está esperando su aprobación",
|
||||||
"booking_submitted_subject": "Reserva enviada: {{eventType}} con {{name}} el {{date}}",
|
"booking_submitted_subject": "Reserva enviada: {{eventType}} con {{name}} el {{date}}",
|
||||||
"your_meeting_has_been_booked": "Su reunión ha sido reservada",
|
"your_meeting_has_been_booked": "Su reunión ha sido reservada",
|
||||||
"event_type_has_been_rescheduled_on_time_date": "Tu {{eventType}} con {{name}} se reprogramó para el {{date}}.",
|
"event_type_has_been_rescheduled_on_time_date": "Tu {{title}} ha sido reprogramado para {{date}}.",
|
||||||
"event_has_been_rescheduled": "Tu evento ha sido reprogramado.",
|
"event_has_been_rescheduled": "Tu evento ha sido reprogramado.",
|
||||||
"request_reschedule_title_attendee": "Solicitud para reprogramar su reserva",
|
"request_reschedule_title_attendee": "Solicitud para reprogramar su reserva",
|
||||||
"request_reschedule_subtitle": "{{organizer}} ha cancelado la reserva y le ha solicitado que elija otra hora.",
|
"request_reschedule_subtitle": "{{organizer}} ha cancelado la reserva y le ha solicitado que elija otra hora.",
|
||||||
@@ -511,7 +511,7 @@
|
|||||||
"add_team_members_description": "Invitar a otros a unirse a tu equipo",
|
"add_team_members_description": "Invitar a otros a unirse a tu equipo",
|
||||||
"add_team_member": "Añadir miembro del equipo",
|
"add_team_member": "Añadir miembro del equipo",
|
||||||
"invite_new_member": "Invitar a un nuevo miembro",
|
"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.",
|
"invite_new_team_member": "Invita a alguien a tu equipo.",
|
||||||
"change_member_role": "Cambiar rol del miembro del equipo",
|
"change_member_role": "Cambiar rol del miembro del equipo",
|
||||||
"disable_cal_branding": "Desactivar marca de {{appName}}",
|
"disable_cal_branding": "Desactivar marca de {{appName}}",
|
||||||
@@ -1076,13 +1076,13 @@
|
|||||||
"new_seat_subject": "Nuevo asistente {{name}} en {{eventType}} el {{date}}",
|
"new_seat_subject": "Nuevo asistente {{name}} en {{eventType}} el {{date}}",
|
||||||
"new_seat_title": "Alguien se ha añadido a un evento",
|
"new_seat_title": "Alguien se ha añadido a un evento",
|
||||||
"variable": "Variable",
|
"variable": "Variable",
|
||||||
"event_name_workflow": "Nombre del evento",
|
"event_name_variable": "Nombre del evento",
|
||||||
"organizer_name_workflow": "Nombre del organizador",
|
"organizer_name_variable": "Nombre del organizador",
|
||||||
"attendee_name_workflow": "Nombre del asistente",
|
"attendee_name_variable": "Nombre del asistente",
|
||||||
"event_date_workflow": "Fecha del evento",
|
"event_date_variable": "Fecha del evento",
|
||||||
"event_time_workflow": "Hora del evento",
|
"event_time_variable": "Hora del evento",
|
||||||
"location_workflow": "Lugar",
|
"location_variable": "Lugar",
|
||||||
"additional_notes_workflow": "Notas Adicionales",
|
"additional_notes_variable": "Notas Adicionales",
|
||||||
"app_upgrade_description": "Para poder usar esta función, necesita actualizarse a una cuenta Pro.",
|
"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",
|
"invalid_number": "Número de teléfono no válido",
|
||||||
"navigate": "Navegar",
|
"navigate": "Navegar",
|
||||||
@@ -1386,7 +1386,7 @@
|
|||||||
"team_url_taken": "Esta URL ya está en uso",
|
"team_url_taken": "Esta URL ya está en uso",
|
||||||
"team_publish": "Publicar equipo",
|
"team_publish": "Publicar equipo",
|
||||||
"number_sms_notifications": "Número de teléfono (notificaciones por SMS)",
|
"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",
|
"attendee_email_info": "El correo electrónico de la persona que reserva",
|
||||||
"kbar_search_placeholder": "Escriba un comando o búsqueda...",
|
"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.",
|
"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": "ID del remitente",
|
||||||
"sender_id_error_message": "Sólo se permiten letras, números y espacios (máx. 11 caracteres)",
|
"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_routing_form": "Prueba de formulario de ruta",
|
||||||
"test_preview": "Vista previa de la prueba",
|
"test_preview": "Probar vista previa",
|
||||||
"route_to": "Ruta hacia",
|
"route_to": "Ruta hacia",
|
||||||
"test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato",
|
"test_preview_description": "Pruebe su formulario de ruta sin enviar ningún dato",
|
||||||
"test_routing": "Prueba de ruta",
|
"test_routing": "Prueba de ruta",
|
||||||
|
|||||||
@@ -0,0 +1,79 @@
|
|||||||
|
{
|
||||||
|
"day_one": "egun {{count}}",
|
||||||
|
"day_other": "{{count}} egun",
|
||||||
|
"second_one": "segundo {{count}}",
|
||||||
|
"second_other": "{{count}} segundo",
|
||||||
|
"accept_invitation": "Onartu gonbidapena",
|
||||||
|
"have_any_questions": "Galderarik? Laguntzeko gaude.",
|
||||||
|
"reset_password_subject": "{{appName}}: Pasahitza berrezartzeko argibideak",
|
||||||
|
"event_declined_subject": "Baztertua: {{title}} {{date}}(e)an",
|
||||||
|
"event_cancelled_subject": "Bertan behera: {{title}} {{date}}(e)an",
|
||||||
|
"event_request_declined": "Zure gertaera-eskaera baztertua izan da",
|
||||||
|
"event_request_declined_recurring": "Zure gertaera errepikari-eskaera baztertua izan da",
|
||||||
|
"event_request_cancelled": "Zure programatutako gertaera baztertua izan da",
|
||||||
|
"organizer": "Antolatzailea",
|
||||||
|
"need_to_reschedule_or_cancel": "Programazioa aldatu edo bertan behera utzi behar duzu?",
|
||||||
|
"cancellation_reason": "Bertan behera uztearen arrazoia (aukerakoa)",
|
||||||
|
"cancellation_reason_placeholder": "Zergatik utzi duzu bertan behera?",
|
||||||
|
"rejection_reason": "Errefusatzeko arrazoia",
|
||||||
|
"rejection_reason_title": "Errefusatu erreserba-eskaera?",
|
||||||
|
"rejection_reason_description": "Ziur zaude erreserba errefusatu nahi duzula? Erreserba-eskaera egin duen pertsonari jakinaraziko zaio. Arrazoi bat adieraz dezakezu behean.",
|
||||||
|
"rejection_confirmation": "Errefusatu erreserba",
|
||||||
|
"manage_this_event": "Kudeatu gertaera hau",
|
||||||
|
"your_event_has_been_scheduled": "Zure gertaera programatu da",
|
||||||
|
"error_message": "Errore-mezua honakoa ian da: '{{errorMessage}}'",
|
||||||
|
"refund_failed_subject": "Itzulketak huts egin du: {{name}} - {{date}} - {{eventType}}",
|
||||||
|
"refund_failed": "Huts egin du itzulketak {{eventType}} gertaerarako, {{userName}}(r)ekin {{date}}(e)an.",
|
||||||
|
"a_refund_failed": "Itzulketa batek huts egin du",
|
||||||
|
"awaiting_payment_subject": "Ordainketaren zain: {{title}} {{date}}(e)an",
|
||||||
|
"help": "Laguntza",
|
||||||
|
"price": "Prezioa",
|
||||||
|
"paid": "Ordainduta",
|
||||||
|
"refunded": "Itzulita",
|
||||||
|
"payment": "Ordainketa",
|
||||||
|
"pay_now": "Ordaindu orain",
|
||||||
|
"no_more_results": "Emaitza gehiagorik ez",
|
||||||
|
"no_results": "Emaitzarik ez",
|
||||||
|
"load_more_results": "Kargatu emaitza gehiago",
|
||||||
|
"confirmed_event_type_subject": "Baieztatua: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||||
|
"confirm_or_reject_request": "Baieztatu edo errefusatu eskaera",
|
||||||
|
"check_bookings_page_to_confirm_or_reject": "Begiratu zure erreserba-orrialdea erreserba baieztatu edo errefusatzeko.",
|
||||||
|
"event_awaiting_approval": "Gertaera bat zure onarpenaren zain dago",
|
||||||
|
"booking_submitted_subject": "Erreserba bidalita: {{title}} {{date}}(e)an",
|
||||||
|
"event_type_has_been_rescheduled_on_time_date": "Zure {{title}} getaeraren programazioa aldatu egin da {{date}}(e)ra.",
|
||||||
|
"hi_user_name": "Kaixo {{name}}",
|
||||||
|
"ics_event_title": "{{eventType}} {{name}}(r)ekin",
|
||||||
|
"notes": "Oharrak",
|
||||||
|
"manage_my_bookings": "Kudeatu nire erreserbak",
|
||||||
|
"rejected_event_type_with_organizer": "Errefusatua: {{eventType}} {{organizer}}(r)ekin {{date}}(e)an",
|
||||||
|
"hi": "Kaixo",
|
||||||
|
"change_bookings_availability": "Aldatu noiz zauden prest erreserbak jasotzeko",
|
||||||
|
"create_booking_link_with_calcom": "Sor ezazu zeure erreserba-esteka {{appName}}(e)kin",
|
||||||
|
"user_needs_to_confirm_or_reject_booking": "{{user}}(e)k erreserba baieztatu edo errefusatu behar du oraindik.",
|
||||||
|
"booking_submitted": "Zure erreserba bidali da",
|
||||||
|
"booking_confirmed": "Zure erreserba baieztatu da",
|
||||||
|
"back_to_bookings": "Itzuli erreserbatara",
|
||||||
|
"really_cancel_booking": "Benetan bertan behera utzi nahi duzu zure erreserba?",
|
||||||
|
"cannot_cancel_booking": "Ezin duzu erreserba hau bertan behera utzi",
|
||||||
|
"booking_already_cancelled": "Erreserba hau bertan behera utzi da dagoeneko",
|
||||||
|
"booking_already_accepted_rejected": "Erreserba hau onartu edo errefusatu da dagoeneko",
|
||||||
|
"bookings": "Erreserbak",
|
||||||
|
"past_bookings": "Zure iraganeko erreserbak agertuko dira hemen.",
|
||||||
|
"cancelled_bookings": "Zure bertan behera utzitako erreserbak agertuko dira hemen.",
|
||||||
|
"unconfirmed_bookings": "Zure baieztatu gabeko erreserbak agertuko dira hemen.",
|
||||||
|
"unconfirmed_bookings_tooltip": "Baieztatu gabeko erreserbak",
|
||||||
|
"booking_cancelled": "Erreserba bertan behera",
|
||||||
|
"booking_rescheduled": "Erreserbaren programazioa aldatuta",
|
||||||
|
"booking_created": "Erreserba sortuta",
|
||||||
|
"edit_booking": "Aldatu erreserba",
|
||||||
|
"reschedule_booking": "Aldatu erreserbaren programazioa",
|
||||||
|
"location_changed_event_type_subject": "Kokapena aldatu da: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||||
|
"booking_details": "Erreserbaren xehetasunak",
|
||||||
|
"reminder_email": "Gogorarazpena: {{eventType}} {{name}}(r)ekin {{date}}(e)an",
|
||||||
|
"new_seat_subject": "{{name}} parte-hartzaile berria {{eventType}}(e)n {{date}}(e)an",
|
||||||
|
"booking_appearance": "Erreserba-orriaren itxura",
|
||||||
|
"limit_booking_frequency": "Mugatu erreserbatzeko maiztasuna",
|
||||||
|
"booking_tentative": "Erreserba hau behin-behinekoa da",
|
||||||
|
"limit_future_bookings": "Mugatu etorkizuneko erreserbak",
|
||||||
|
"all_bookings_filter_label": "Erreserba guztiak"
|
||||||
|
}
|
||||||
File diff suppressed because it is too large.
Load diff
Loaded 100 of 413 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user