Compare commits

...
58 Commits
Author SHA1 Message Date
Omar López 2e50625165 v3.5.4
release.yml / release (push) Canceled after 0s
2023-11-30 15:47:15 -07:00
sean-brydonandMorgan c78eb752a7 feat: rate limit removeMember (#12570)
* feat: rate limit removeMember

* Remove optional type as its always there

* fix

---------

Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2023-11-30 22:16:40 +00:00
Somay Chauhan 1c20bdcecf fix: rescheduling recurring events (#12567) 2023-11-30 13:27:37 -05:00
Amit Sharma 338756439f fix: meeting ended incorrect content type (#12484) 2023-11-30 17:52:55 +00:00
56050b994d fix 'attempt booking in the past' error (#12597)
Co-authored-by: CarinaWolli <wollencarina@gmail.com>
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-30 17:31:59 +00:00
sean-brydon c11ecbb323 Fix:use typed query initially fill default paramaters (#12568) 2023-11-29 17:51:47 -05:00
Peer RichelsenandOmar López 7f23ae156b fix: improved team upgrade screen to also show unpublished teams (#12492)
* improved team upgrade screen to also show unpublished teams

* Update TeamsListing.tsx

* bunch of stuff

---------

Co-authored-by: Omar López <zomars@me.com>
2023-11-29 14:48:26 -07:00
zomars 33250652b3 Double e2e shards 2023-11-29 14:40:21 -07:00
Morgan a65e18d92d chore: improve invitation form validation (#12594)
* chore: improve invitation form validation

* fixup! chore: improve invitation form validation

* fixup! fixup! chore: improve invitation form validation
2023-11-29 12:44:48 -07:00
Carina WollendorferandCarinaWolli d0f7085cb8 fix: updating workflow with new step and new active event type (#12592)
* correctly filter event types to avoid null values

* clean up all filters

* re-add reverted fix

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-29 23:51:12 +05:30
Morgan ea8437b4f7 fix: invite member by username (#12591)
* fix: invite member by username

* fixup! fix: invite member by username
2023-11-29 19:57:51 +02:00
Joe Au-YeungandOmar López 877cd4cdff refactor: Team Creation Flow [CAL-2751] (#12501)
* Create new endpoint for creating a team

* Generate a team checkout session

* Create team navigate to checkout

* Clean up

* UI changes

* Add comments

* Fix

* Type fix

* Type fix

* Type fix

* Type fixes

* Set telemetry

* Import fix

* Type fix

* Update tests

* Type fix

* fix: e2e

* fix: e2e

* fix: e2e

* fix: e2e

* Update teams.e2e.ts

* fix: e2e

---------

Co-authored-by: Omar López <zomars@me.com>
2023-11-29 09:39:21 -07:00
Igor Perzic bae3bd76e5 docs: update byte size for openssl command (#12587) 2023-11-29 15:15:04 +00:00
Joe Au-Yeung 8b24995d52 refactor: Abstract createBooking in handleNewBooking [CAL-2619] (#11959)
## What does this PR do?

<!-- Please include a summary of the change and which issue is fixed. Please also include relevant motivation and context. List any dependencies that are required for this change. -->

In anticipation of refactoring `handleSeats` we need to abstract `createBooking` in order to get the return type. This PR is purposely aiming to do one thing so nothing is missed while refactoring `handleNewBooking`

Fixes # (issue)

<!-- Please provide a loom video for visual changes to speed up reviews
 Loom Video: https://www.loom.com/
-->

## Requirement/Documentation

<!-- Please provide all documents that are important to understand the reason of that PR. -->

- If there is a requirement document, please, share it here.
- If there is ab UI/UX design document, please, share it here.

## Type of change

<!-- Please delete bullets that are not relevant. -->

- [x] Chore (refactoring code, technical debt, workflow improvements)


## How should this be tested?

<!-- Please describe the tests that you ran to verify your changes. Provide instructions so we can reproduce. Please also list any relevant details for your test configuration. Write details that help to start the tests -->

- Are there environment variables that should be set?
- What are the minimal test data to have?
- What is expected (happy path) to have (input and output)?
- Any other important info that could help to test that PR

## Mandatory Tasks

- [ ] Make sure you have self-reviewed the code. A decent size PR without self-review might be rejected.

## Checklist

<!-- Please remove all the irrelevant bullets to your PR -->

- I haven't read the [contributing guide](https://github.com/calcom/cal.com/blob/main/CONTRIBUTING.md)
- My code doesn't follow the style guidelines of this project
- I haven't commented my code, particularly in hard-to-understand areas
- I haven't checked if my PR needs changes to the documentation
- I haven't checked if my changes generate no new warnings
- I haven't added tests that prove my fix is effective or that my feature works
- I haven't checked if new and existing unit tests pass locally with my changes
2023-11-29 08:11:09 -07:00
Udit Takkarandsean-brydon 7dc6df2ad6 chore: improve logs (#12467)
* chore: improve logs

* fix: import

* chore: use safe stringify

* chore: add more logs to google calendar

* chore: use this.log

---------

Co-authored-by: sean-brydon <55134778+sean-brydon@users.noreply.github.com>
2023-11-29 19:39:25 +05:30
Peer RichelsenandUdit Takkar 6c4b1154b8 chore: removed last web3 references (#12540)
* removed last web3 references

* updated yarn

* Update schema.prisma

* readded icon

---------

Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-29 11:57:50 +00:00
Pratik KumarandUdit Takkar 1099bad930 fix:selecting behaviour fixed (#12578)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-29 11:53:31 +00:00
Crowdin Bot dbd5d27d9d New Crowdin translations by Github Action 2023-11-29 09:57:23 +00:00
Udit TakkarandMorgan 45c4f4789b chore: add check for recording (#12584)
Co-authored-by: Morgan <33722304+ThyMinimalDev@users.noreply.github.com>
2023-11-29 09:54:14 +00:00
Morgan 07f42057fc fix: increase maxWait/Timeout invite members transaction (#12583) 2023-11-29 09:07:03 +00:00
Crowdin Bot 79ee7c41b2 New Crowdin translations by Github Action 2023-11-29 08:03:18 +00:00
Joe Au-Yeung 57e6971942 chore: Refactor <CalendarListContainer /> (#12388)
* Import calendarlistcontainer in settings

* Change CalendarListComponent to Figma design

* Add border subtle

* Address feedback
2023-11-29 13:29:51 +05:30
Alex van Andel bd6ca21e02 fix: Toggling really fast throws internal errors (#12580) 2023-11-28 23:04:54 +00:00
Carina Wollendorfer f19a1926b4 chore: refactor reschedule EventManager with changed organizer (#12574)
## What does this PR do?

Follow up for https://github.com/calcom/cal.com/pull/12469

Event creation is now already handled in the reschedule function of EventManager. 

## Type of change

- [x] Chore (refactoring code, technical debt, workflow improvements)

## How should this be tested?

- Same as in https://github.com/calcom/cal.com/pull/12469
2023-11-28 11:37:31 -07:00
Erik 477ee1c526 v3.5.3
release.yml / release (push) Canceled after 0s
2023-11-28 15:32:02 -03:00
Udit TakkarandJoe Au-Yeung 6704e18e32 fix: cal video issues (#12546)
Co-authored-by: Joe Au-Yeung <65426560+joeauyeung@users.noreply.github.com>
2023-11-28 18:28:35 +00:00
Crowdin Bot 20f17903b9 New Crowdin translations by Github Action 2023-11-28 14:56:47 +00:00
8dabbbd09f feat: signup refactor (#11421)
Co-authored-by: alannnc <alannnc@gmail.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-28 14:52:53 +00:00
sean-brydon 32933ed6ef fix: premium username to premium username (#12569) 2023-11-28 12:55:35 +00:00
Omar López c765ea9b31 fix: team and org invite e2e tests (#12566) 2023-11-28 10:49:06 +00:00
Matt NicollsandOmar López d3ddbf418f fix: fetch event type with a team permissions (#12448)
Co-authored-by: Omar López <zomars@me.com>
2023-11-27 23:14:59 -07:00
Joe Au-Yeung 79d0513428 Change method to getOrgMembership (#12563) 2023-11-27 14:46:55 -05:00
Carina WollendorferandCarinaWolli 230b82d3bf fix: rescheduling round robin events (emails and calendar events) (#12469)
* send the correct booking email for round robin rescheduling

* fixing originalBookingMemberEmails

* fix calendar event (needs some code refactoring)

* refactor rescheduling code

* code clean up

* add comment

* fix event name if host changes

* add tests for rr rescheduling emails

* fix videoCallUrl of google meet

* code clean up

* fix destinationCalendar for new booking

---------

Co-authored-by: CarinaWolli <wollencarina@gmail.com>
2023-11-27 13:09:33 -05:00
Hariom Balhara c9b50ffb78 fix: Provide calOrigin for organization embeds (#12380) 2023-11-27 10:38:27 -07:00
Somay Chauhan ada0ef242b fix: crash on other team members and profile page (#12539) 2023-11-27 16:59:03 +00:00
Shashank KaulandPeer Richelsen b26e688950 fix(api): Added correct include for GET /teams/{teamId}/event-types (#12459)
Co-authored-by: Peer Richelsen <peeroke@gmail.com>
2023-11-27 16:44:13 +00:00
Crowdin Bot d9041d03b7 New Crowdin translations by Github Action 2023-11-27 16:10:27 +00:00
Peer RichelsenandSean Brydon f60b3c14e1 fix: small UI improvements for troubleshooter (#12535)
Co-authored-by: Sean Brydon <sean@brydon.io>
2023-11-27 16:06:54 +00:00
Crowdin Bot fbcc43931e New Crowdin translations by Github Action 2023-11-27 13:57:45 +00:00
Udit Takkar a48e7cb39a fix: remove duplicate key in common.json (#12543)
* fix: remove duplicate key in common.json

* chore: add  both keys in same log
2023-11-27 19:23:35 +05:30
sean-brydon db3f718ba6 fix: removing next steps (#12549)
* Removing next steps

* Removing next steps
2023-11-27 16:49:40 +05:30
Morgan ca5dd4626d fix: event-types post api endpoint not showing hosts in response data (#12550) 2023-11-27 12:35:39 +02:00
Amit Sharma 4524d722f6 fix: phone-number-input (#12266) 2023-11-27 10:27:01 +00:00
Morgan 2094d59856 refactor: invite members handler (#12442)
* refactor: invite members handler

* fixup! refactor: invite members handler

* fixup! fixup! refactor: invite members handler

* refactor: promise all settled send emails

* fixup! refactor: promise all settled send emails

* fixup! fixup! refactor: promise all settled send emails

* fixup! fixup! fixup! refactor: promise all settled send emails

* fix: opening team invite link in email throws error on signup page

* fixup! Merge branch 'main' into cal-2698-refactor-invitemember-handler

* fix: centralize validation if invitee can be invited

* fix: improve select query and fix tests

* fixup! Merge branch 'main' into cal-2698-refactor-invitemember-handler

* rename functions and add some tests
2023-11-27 09:27:27 +00:00
Lauris Skraucis 7b350a5b8f revert: sync platform branch with main (#12548) 2023-11-27 08:35:58 +00:00
Brijendra Singh 5a869228d3 typo (#12541) 2023-11-26 14:34:40 +05:30
Peer Richelsen dabd5eae73 chore: fixed order of tips (#12529) 2023-11-25 13:55:40 -03:00
Somay ChauhanandUdit Takkar cb78de231a feat: if profile only has one public event-type, redirect to it (#12158)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
2023-11-24 21:54:14 +00:00
DmytroHryshyn b0a1ef8a49 chore: remove dynamic loading of EventTypeDescription on event-types page (#12524) 2023-11-24 19:12:43 +00:00
Joe Au-Yeung 0a43aa3351 OAuth URL account for dev environment (#12530) 2023-11-24 17:38:21 +00:00
Lauris Skraucis ed4cce1c7e fix: platform branch sync with main (#12528) 2023-11-24 17:46:33 +01:00
Lauris Skraucis d595aa23c0 fix: platform branch sync with main (#12526) 2023-11-24 20:30:01 +05:30
Lauris Skraucis a6df49235f chore: sync platform branch with main (#12525) 2023-11-24 15:18:08 +01:00
Erik 5df41e37a0 fix: Stripe webhook event mismatch (#12522) 2023-11-24 13:18:06 +00:00
Peer Richelsen fca5778b6d chore: avatar padding (#12515) 2023-11-23 23:43:14 +00:00
Alex van Andel 0ddefaa141 Remove cache-control and disable cache (#12517) 2023-11-23 23:42:18 +00:00
c11f7aeffc chore: insights UI refresher (#12498)
Co-authored-by: Udit Takkar <53316345+Udit-takkar@users.noreply.github.com>
Co-authored-by: Alex van Andel <me@alexvanandel.com>
2023-11-23 21:25:27 +00:00
Erik 9903fcaa05 chore: Remove free use of Cal.AI (#12489) 2023-11-23 21:07:08 +00:00
192 changed files with 6490 additions and 5828 deletions
+2 -1
View File
@@ -155,6 +155,7 @@ NEXT_PUBLIC_STRIPE_PREMIUM_NEW_PLAN_PRICE=
STRIPE_TEAM_MONTHLY_PRICE_ID=
STRIPE_ORG_MONTHLY_PRICE_ID=
STRIPE_WEBHOOK_SECRET=
STRIPE_WEBHOOK_SECRET_APPS=
STRIPE_PRIVATE_KEY=
STRIPE_CLIENT_ID=
PAYMENT_FEE_FIXED=
@@ -265,7 +266,7 @@ CALCOM_WEBHOOK_SECRET=""
CALCOM_WEBHOOK_HEADER_NAME="calcom-webhook-secret"
CALCOM_CREDENTIAL_SYNC_ENDPOINT=""
# Key should match on Cal.com and your application
# must be 32 bytes for AES256 encryption algorithm
# must be 24 bytes for AES256 encryption algorithm
# You can use: `openssl rand -base64 24` to generate one
CALCOM_APP_CREDENTIAL_ENCRYPTION_KEY=""
+1 -1
View File
@@ -24,7 +24,7 @@ jobs:
strategy:
fail-fast: false
matrix:
shard: [1, 2, 3, 4, 5]
shard: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
steps:
- uses: actions/checkout@v3
- uses: ./.github/actions/dangerous-git-checkout
+4 -1
View File
@@ -2,6 +2,7 @@ import type { NextApiRequest } from "next";
import { HttpError } from "@calcom/lib/http-error";
import { defaultResponder } from "@calcom/lib/server";
import { MembershipRole } from "@calcom/prisma/enums";
import { schemaEventTypeReadPublic } from "~/lib/validations/event-type";
import { schemaQueryIdParseInt } from "~/lib/validations/shared/queryIdTransformParseInt";
@@ -89,7 +90,9 @@ async function checkPermissions<T extends BaseEventTypeCheckPermissions>(
if (req.isAdmin) return true;
if (eventType?.teamId) {
req.query.teamId = String(eventType.teamId);
await canAccessTeamEventOrThrow(req, "MEMBER");
await canAccessTeamEventOrThrow(req, {
in: [MembershipRole.OWNER, MembershipRole.ADMIN, MembershipRole.MEMBER],
});
}
if (eventType?.userId === req.userId) return true; // is owner.
throw new HttpError({ statusCode: 403, message: "Forbidden" });
+1 -1
View File
@@ -299,7 +299,7 @@ async function postHandler(req: NextApiRequest) {
data.hosts = { createMany: { data: hosts } };
}
const eventType = await prisma.eventType.create({ data });
const eventType = await prisma.eventType.create({ data, include: { hosts: true } });
return {
event_type: schemaEventTypeReadPublic.parse(eventType),
@@ -22,7 +22,11 @@ export async function checkPermissions(
role: Prisma.MembershipWhereInput["role"] = MembershipRole.OWNER
) {
const { userId, prisma, isAdmin } = req;
const { teamId } = schemaQueryTeamId.parse(req.query);
const { teamId } = schemaQueryTeamId.parse({
teamId: req.query.teamId,
version: req.query.version,
apiKey: req.query.apiKey,
});
const args: Prisma.TeamFindFirstArgs = { where: { id: teamId } };
/** If not ADMIN then we check if the actual user belongs to team and matches the required role */
if (!isAdmin) args.where = { ...args.where, members: { some: { userId, role } } };
@@ -56,6 +56,14 @@ async function getHandler(req: NextApiRequest) {
members: { some: { userId } },
},
},
include: {
customInputs: true,
team: { select: { slug: true } },
users: true,
hosts: { select: { userId: true, isFixed: true } },
owner: { select: { username: true, id: true } },
children: { select: { id: true, userId: true } },
},
};
const data = await prisma.eventType.findMany(args);
+3 -1
View File
@@ -32,6 +32,7 @@ type AppListCardProps = {
invalidCredential?: boolean;
children?: ReactNode;
credentialOwner?: CredentialOwner;
className?: string;
} & ShouldHighlight;
const schema = z.object({ hl: z.string().optional() });
@@ -50,6 +51,7 @@ export default function AppListCard(props: AppListCardProps) {
invalidCredential,
children,
credentialOwner,
className,
} = props;
const {
data: { hl },
@@ -83,7 +85,7 @@ export default function AppListCard(props: AppListCardProps) {
}, [highlight, pathname, router, searchParams, shouldHighlight]);
return (
<div className={classNames(highlight && "dark:bg-muted bg-yellow-100")}>
<div className={classNames(highlight && "dark:bg-muted bg-yellow-100", className)}>
<div className="flex items-center gap-x-3 px-4 py-4 sm:px-6">
{logo ? (
<img
+115 -105
View File
@@ -14,8 +14,9 @@ import {
List,
AppSkeletonLoader as SkeletonLoader,
ShellSubHeading,
Label,
} from "@calcom/ui";
import { Calendar, Plus } from "@calcom/ui/components/icon";
import { Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
@@ -27,6 +28,7 @@ type Props = {
onChanged: () => unknown | Promise<unknown>;
fromOnboarding?: boolean;
destinationCalendarId?: string;
isLoading?: boolean;
};
function CalendarList(props: Props) {
@@ -70,8 +72,9 @@ function ConnectedCalendarsList(props: Props) {
const { t } = useLocale();
const query = trpc.viewer.connectedCalendars.useQuery(undefined, {
suspense: true,
refetchOnWindowFocus: false,
});
const { fromOnboarding } = props;
const { fromOnboarding, isLoading } = props;
return (
<QueryCell
query={query}
@@ -82,74 +85,94 @@ function ConnectedCalendarsList(props: Props) {
}
return (
<List>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<AppListCard
shouldHighlight
slug={item.integration.slug}
title={item.integration.name}
logo={item.integration.logo}
description={item.primary?.email ?? item.integration.description}
actions={
<div className="flex w-32 justify-end">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
</div>
}>
<div className="border-subtle border-t">
{!fromOnboarding && (
<>
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-4 px-5 py-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
isChecked={cal.isSelected}
destination={cal.externalId === props.destinationCalendarId}
credentialId={cal.credentialId}
/>
))}
</ul>
</>
)}
<div className="border-subtle mt-6 rounded-lg border">
<div className="border-subtle border-b p-6">
<div className="flex items-center justify-between">
<div>
<h4 className="text-emphasis text-base font-semibold leading-5">
{t("check_for_conflicts")}
</h4>
<p className="text-default text-sm leading-tight">{t("select_calendars")}</p>
</div>
<div className="flex flex-col xl:flex-row xl:space-x-5">
{!!data.connectedCalendars.length && (
<div className="flex items-center">
<AdditionalCalendarSelector isLoading={isLoading} />
</div>
</AppListCard>
) : (
<Alert
severity="warning"
title={t("something_went_wrong")}
message={
<span>
<Link href={`/apps/${item.integration.slug}`}>{item.integration.name}</Link>:{" "}
{t("calendar_error")}
</span>
}
iconClassName="h-10 w-10 ml-2 mr-1 mt-0.5"
actions={
<div className="flex w-32 justify-end md:pr-1">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
)}
</div>
</div>
</div>
<List noBorderTreatment className="p-6 pt-2">
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.calendars ? (
<AppListCard
shouldHighlight
slug={item.integration.slug}
title={item.integration.name}
logo={item.integration.logo}
description={item.primary?.email ?? item.integration.description}
className="border-subtle mt-4 rounded-lg border"
actions={
<div className="flex w-32 justify-end">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
</div>
}>
<div className="border-subtle border-t">
{!fromOnboarding && (
<>
<p className="text-subtle px-5 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-4 px-5 py-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
isChecked={cal.isSelected}
destination={cal.externalId === props.destinationCalendarId}
credentialId={cal.credentialId}
/>
))}
</ul>
</>
)}
</div>
}
/>
)}
</Fragment>
))}
</List>
</AppListCard>
) : (
<Alert
severity="warning"
title={t("something_went_wrong")}
message={
<span>
<Link href={`/apps/${item.integration.slug}`}>{item.integration.name}</Link>:{" "}
{t("calendar_error")}
</span>
}
iconClassName="h-10 w-10 ml-2 mr-1 mt-0.5"
actions={
<div className="flex w-32 justify-end md:pr-1">
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={props.onChanged}
buttonProps={{ className: "border border-default" }}
/>
</div>
}
/>
)}
</Fragment>
))}
</List>
</div>
);
}}
/>
@@ -187,42 +210,28 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
{!!data.connectedCalendars.length || !!installedCalendars.data?.items.length ? (
<>
{heading && (
<div className="border-default flex flex-col gap-6 rounded-md border p-7">
<ShellSubHeading
title={t("calendar")}
subtitle={t("installed_app_calendar_description")}
className="mb-0 flex flex-wrap items-center gap-4 sm:flex-nowrap md:mb-3 md:gap-0"
actions={
<div className="flex flex-col xl:flex-row xl:space-x-5">
{!!data.connectedCalendars.length && (
<div className="flex items-center">
<AdditionalCalendarSelector isLoading={mutation.isLoading} />
</div>
)}
</div>
}
/>
<div className="bg-muted border-subtle flex justify-between rounded-md border p-4">
<div className="flex w-full flex-col items-start gap-4 md:flex-row md:items-center">
<div className="bg-default border-subtle relative rounded-md border p-1.5">
<Calendar className="text-default h-8 w-8" strokeWidth="1" />
<Plus
className="text-emphasis absolute left-4 top-1/2 ml-0.5 mt-[1px] h-2 w-2"
strokeWidth="4"
/>
</div>
<div className="md:w-6/12">
<h1 className="text-emphasis text-sm font-semibold">{t("create_events_on")}</h1>
<p className="text-default text-sm font-normal">{t("set_calendar")}</p>
</div>
<div className="justify-end md:w-6/12">
<DestinationCalendarSelector
onChange={mutation.mutate}
hidePlaceholder
isLoading={mutation.isLoading}
value={data.destinationCalendar?.externalId}
hideAdvancedText
/>
<>
<div className="border-subtle mb-6 mt-8 rounded-lg border">
<div className="p-6">
<h2 className="text-emphasis mb-1 text-base font-bold leading-5 tracking-wide">
{t("add_to_calendar")}
</h2>
<p className="text-subtle text-sm leading-tight">
{t("add_to_calendar_description")}
</p>
</div>
<div className="border-t">
<div className="border-subtle flex w-full flex-col space-y-3 border-y-0 p-6">
<div>
<Label className="text-default mb-0 font-medium">{t("add_events_to")}</Label>
<DestinationCalendarSelector
hidePlaceholder
value={data.destinationCalendar?.externalId}
onChange={mutation.mutate}
isLoading={mutation.isLoading}
/>
</div>
</div>
</div>
</div>
@@ -230,8 +239,9 @@ export function CalendarListContainer(props: { heading?: boolean; fromOnboarding
onChanged={onChanged}
fromOnboarding={fromOnboarding}
destinationCalendarId={data.destinationCalendar?.externalId}
isLoading={mutation.isLoading}
/>
</div>
</>
)}
</>
) : fromOnboarding ? (
@@ -60,7 +60,7 @@ export const InstallAppButtonChild = ({
disabled={shouldDisableInstallation}
color="primary"
size="base">
{paid.trial ? t("start_paid_trial") : t("install_paid_app")}
{paid.trial ? t("start_paid_trial") : t("subscribe")}
</Button>
);
}
@@ -262,15 +262,21 @@ function BookingListItem(booking: BookingItemProps) {
const title = booking.title;
const showRecordingsButtons = !!(booking.isRecorded && isPast && isConfirmed);
const showViewRecordingsButton = !!(booking.isRecorded && isPast && isConfirmed);
const showCheckRecordingButton =
isPast &&
isConfirmed &&
!booking.isRecorded &&
(!booking.location || booking.location === "integrations:daily" || booking?.location?.trim() === "");
const showRecordingActions: ActionType[] = [
{
id: "view_recordings",
label: t("view_recordings"),
label: showCheckRecordingButton ? t("check_for_recordings") : t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
color: showCheckRecordingButton ? "secondary" : "primary",
disabled: mutation.isLoading,
},
];
@@ -298,7 +304,7 @@ function BookingListItem(booking: BookingItemProps) {
paymentCurrency={booking.payment[0].currency}
/>
)}
{showRecordingsButtons && (
{(showViewRecordingsButton || showCheckRecordingButton) && (
<ViewRecordingsDialog
booking={booking}
isOpenDialog={viewRecordingsDialogIsOpen}
@@ -478,7 +484,9 @@ function BookingListItem(booking: BookingItemProps) {
</>
) : null}
{isPast && isPending && !isConfirmed ? <TableActions actions={bookedActions} /> : null}
{showRecordingsButtons && <TableActions actions={showRecordingActions} />}
{(showViewRecordingsButton || showCheckRecordingButton) && (
<TableActions actions={showRecordingActions} />
)}
{isCancelled && booking.rescheduled && (
<div className="hidden h-full items-center md:flex">
<RequestSentMessage />
@@ -517,8 +525,8 @@ const RecurringBookingsTooltip = ({
return (
recurringDate >= now &&
!booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(recurringDate.toDateString())
.map((date) => date.toString())
.includes(recurringDate.toString())
);
}).length;
@@ -535,8 +543,8 @@ const RecurringBookingsTooltip = ({
const pastOrCancelled =
aDate < now ||
booking.recurringInfo?.bookings[BookingStatus.CANCELLED]
.map((date) => date.toDateString())
.includes(aDate.toDateString());
.map((date) => date.toString())
.includes(aDate.toString());
return (
<p key={key} className={classNames(pastOrCancelled && "line-through")}>
{formatTime(aDate, userTimeFormat, userTimeZone)}
@@ -1,3 +1,4 @@
import { CAL_URL } from "@calcom/lib/constants";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import type { User } from "@calcom/prisma/client";
import { AvatarGroup } from "@calcom/ui";
@@ -11,6 +12,7 @@ export function UserAvatarGroup(props: UserAvatarProps) {
<AvatarGroup
{...rest}
items={users.map((user) => ({
href: `${CAL_URL}/${user.username}?redirect=false`,
alt: user.name || "",
title: user.name || "",
image: getUserAvatarUrl(user),
@@ -1,4 +1,5 @@
import { WEBAPP_URL } from "@calcom/lib/constants";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import { CAL_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { getUserAvatarUrl } from "@calcom/lib/getAvatarUrl";
import type { Team, User } from "@calcom/prisma/client";
import { AvatarGroup } from "@calcom/ui";
@@ -10,8 +11,11 @@ type UserAvatarProps = Omit<React.ComponentProps<typeof AvatarGroup>, "items"> &
export function UserAvatarGroupWithOrg(props: UserAvatarProps) {
const { users, organization, ...rest } = props;
const orgBranding = useOrgBranding();
const baseUrl = `${orgBranding?.fullDomain ?? CAL_URL}`;
const items = [
{
href: baseUrl,
image: `${WEBAPP_URL}/team/${organization.slug}/avatar.png`,
alt: organization.name || undefined,
title: organization.name,
@@ -19,6 +23,7 @@ export function UserAvatarGroupWithOrg(props: UserAvatarProps) {
].concat(
users.map((user) => {
return {
href: `${baseUrl}/${user.username}/?redirect=false`,
image: getUserAvatarUrl(user),
alt: user.name || undefined,
title: user.name || user.username || "",
+12 -4
View File
@@ -71,13 +71,21 @@ if (process.env.GOOGLE_API_CREDENTIALS && !validJson(process.env.GOOGLE_API_CRED
}
const informAboutDuplicateTranslations = () => {
const valueSet = new Set();
const valueMap = {};
for (const key in englishTranslation) {
if (valueSet.has(englishTranslation[key])) {
console.warn("\x1b[33mDuplicate value found in:", "\x1b[0m", key);
const value = englishTranslation[key];
if (valueMap[value]) {
console.warn(
"\x1b[33mDuplicate value found in common.json keys:",
"\x1b[0m ",
key,
"and",
valueMap[value]
);
} else {
valueSet.add(englishTranslation[key]);
valueMap[value] = key;
}
}
};
+1 -2
View File
@@ -1,6 +1,6 @@
{
"name": "@calcom/web",
"version": "3.5.2",
"version": "3.5.4",
"private": true,
"scripts": {
"analyze": "ANALYZE=true next build",
@@ -130,7 +130,6 @@
"tailwindcss-radix": "^2.6.0",
"turndown": "^7.1.1",
"uuid": "^8.3.2",
"web3": "^1.7.5",
"zod": "^3.22.2"
},
"devDependencies": {
+12 -1
View File
@@ -54,6 +54,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
// So it doesn't display in the Link (and make tests fail)
user: _user,
orgSlug: _orgSlug,
redirect: _redirect,
...query
} = useRouterQuery();
@@ -116,7 +117,7 @@ export function UserPage(props: InferGetServerSidePropsType<typeof getServerSide
: null
}
/>
<h1 className="font-cal text-emphasis mb-1 text-3xl" data-testid="name-title">
<h1 className="font-cal text-emphasis my-1 text-3xl" data-testid="name-title">
{profile.name}
{user.verified && (
<Verified className=" mx-1 -mt-1 inline h-6 w-6 fill-blue-500 text-white dark:text-black" />
@@ -388,6 +389,16 @@ export const getServerSideProps: GetServerSideProps<UserPageProps> = async (cont
descriptionAsSafeHTML: markdownToSafeHTML(eventType.description),
}));
// if profile only has one public event-type, redirect to it
if (eventTypes.length === 1 && context.query.redirect !== "false") {
return {
redirect: {
permanent: false,
destination: `/${user.username}/${eventTypes[0].slug}`,
},
};
}
const safeBio = markdownToSafeHTML(user.bio) || "";
const markdownStrippedBio = stripMarkdown(user?.bio || "");
+47 -212
View File
@@ -1,218 +1,53 @@
import type { NextApiRequest, NextApiResponse } from "next";
import type { NextApiResponse } from "next";
import dayjs from "@calcom/dayjs";
import { checkPremiumUsername } from "@calcom/ee/common/lib/checkPremiumUsername";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { IS_CALCOM } from "@calcom/lib/constants";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateUsernameInTeam, validateUsername } from "@calcom/lib/validateUsername";
import prisma from "@calcom/prisma";
import { IdentityProvider } from "@calcom/prisma/enums";
import { MembershipRole } from "@calcom/prisma/enums";
import { signupSchema } from "@calcom/prisma/zod-utils";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== "POST") {
return res.status(405).end();
}
import calcomSignupHandler from "@calcom/feature-auth/signup/handlers/calcomHandler";
import selfHostedSignupHandler from "@calcom/feature-auth/signup/handlers/selfHostedHandler";
import { type RequestWithUsernameStatus } from "@calcom/features/auth/signup/username";
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import { HttpError } from "@calcom/lib/http-error";
import logger from "@calcom/lib/logger";
function ensureSignupIsEnabled() {
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true") {
res.status(403).json({ message: "Signup is disabled" });
return;
}
const data = req.body;
const { email, password, language, token } = signupSchema.parse(data);
const username = slugify(data.username);
const userEmail = email.toLowerCase();
if (!username) {
res.status(422).json({ message: "Invalid username" });
return;
}
let foundToken: { id: number; teamId: number | null; expires: Date } | null = null;
if (token) {
foundToken = await prisma.verificationToken.findFirst({
where: {
token,
},
select: {
id: true,
expires: true,
teamId: true,
},
});
if (!foundToken) {
return res.status(401).json({ message: "Invalid Token" });
}
if (dayjs(foundToken?.expires).isBefore(dayjs())) {
return res.status(401).json({ message: "Token expired" });
}
if (foundToken?.teamId) {
const teamUserValidation = await validateUsernameInTeam(username, userEmail, foundToken?.teamId);
if (!teamUserValidation.isValid) {
return res.status(409).json({ message: "Username or email is already taken" });
}
}
} else {
const userValidation = await validateUsername(username, userEmail);
if (!userValidation.isValid) {
return res.status(409).json({ message: "Username or email is already taken" });
}
}
const hashedPassword = await hashPassword(password);
if (foundToken && foundToken?.teamId) {
const team = await prisma.team.findUnique({
where: {
id: foundToken.teamId,
},
});
if (team) {
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
if (IS_CALCOM && (!teamMetadata?.isOrganization || !!team.parentId)) {
const checkUsername = await checkPremiumUsername(username);
if (checkUsername.premium) {
// This signup page is ONLY meant for team invites and local setup. Not for every day users.
// In singup redesign/refactor coming up @sean will tackle this to make them the same API/page instead of two.
return res.status(422).json({
message: "Sign up from https://cal.com/signup to claim your premium username",
});
}
}
// Identify the org id in an org context signup, either the invited team is an org
// or has a parentId, otherwise parentId will be null, making orgId null
const orgId = teamMetadata?.isOrganization ? team.id : team.parentId;
const user = await prisma.user.upsert({
where: { email: userEmail },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
organizationId: orgId,
},
create: {
username,
email: userEmail,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
organizationId: orgId,
},
});
const membership = await prisma.membership.upsert({
where: {
userId_teamId: { userId: user.id, teamId: team.id },
},
update: {
accepted: true,
},
create: {
userId: user.id,
teamId: team.id,
accepted: true,
role: MembershipRole.MEMBER,
},
});
closeComUpsertTeamUser(team, user, membership.role);
// Accept any child team invites for orgs and create a membership for the org itself
if (team.parentId) {
// Create (when invite link is used) or Update (when regular email invitation is used) membership for the organization itself
await prisma.membership.upsert({
where: {
userId_teamId: { userId: user.id, teamId: team.parentId },
},
update: {
accepted: true,
},
create: {
userId: user.id,
teamId: team.parentId,
accepted: true,
role: MembershipRole.MEMBER,
},
});
// We do a membership update twice so we can join the ORG invite if the user is invited to a team witin a ORG
await prisma.membership.updateMany({
where: {
userId: user.id,
team: {
id: team.parentId,
},
accepted: false,
},
data: {
accepted: true,
},
});
// Join any other invites
await prisma.membership.updateMany({
where: {
userId: user.id,
team: {
parentId: team.parentId,
},
accepted: false,
},
data: {
accepted: true,
},
});
}
}
// Cleanup token after use
await prisma.verificationToken.delete({
where: {
id: foundToken.id,
},
});
} else {
if (IS_CALCOM) {
const checkUsername = await checkPremiumUsername(username);
if (checkUsername.premium) {
res.status(422).json({
message: "Sign up from https://cal.com/signup to claim your premium username",
});
return;
}
}
await prisma.user.upsert({
where: { email: userEmail },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
},
create: {
username,
email: userEmail,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
},
});
await sendEmailVerification({
email: userEmail,
username,
language,
throw new HttpError({
statusCode: 403,
message: "Signup is disabled",
});
}
res.status(201).json({ message: "Created user" });
}
function ensureReqIsPost(req: RequestWithUsernameStatus) {
if (req.method !== "POST") {
throw new HttpError({
statusCode: 405,
message: "Method not allowed",
});
}
}
export default async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
// Use a try catch instead of returning res every time
try {
ensureReqIsPost(req);
ensureSignupIsEnabled();
/**
* Im not sure its worth merging these two handlers. They are different enough to be separate.
* Calcom handles things like creating a stripe customer - which we don't need to do for self hosted.
* It also handles things like premium username.
* TODO: (SEAN) - Extract a lot of the logic from calcomHandler into a separate file and import it into both handlers.
* @zomars: We need to be able to test this with E2E. They way it's done RN it will never run on CI.
*/
if (IS_PREMIUM_USERNAME_ENABLED) {
return await calcomSignupHandler(req, res);
}
return await selfHostedSignupHandler(req, res);
} catch (e) {
if (e instanceof HttpError) {
return res.status(e.statusCode).json({ message: e.message });
}
logger.error(e);
return res.status(500).json({ message: "Internal server error" });
}
}
@@ -83,13 +83,13 @@ export default async function handler(req: NextApiRequest, res: NextApiResponse)
throw new HttpCode({ statusCode: 400, message: "Missing stripe-signature" });
}
if (!process.env.STRIPE_WEBHOOK_SECRET) {
throw new HttpCode({ statusCode: 500, message: "Missing process.env.STRIPE_WEBHOOK_SECRET" });
if (!process.env.STRIPE_WEBHOOK_SECRET_APPS) {
throw new HttpCode({ statusCode: 500, message: "Missing process.env.STRIPE_WEBHOOK_SECRET_APPS" });
}
const requestBuffer = await buffer(req);
const payload = requestBuffer.toString();
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET);
const event = stripe.webhooks.constructEvent(payload, sig, process.env.STRIPE_WEBHOOK_SECRET_APPS);
const handler = webhookHandlers[event.type];
if (handler) {
+92
View File
@@ -0,0 +1,92 @@
import type { NextApiRequest, NextApiResponse } from "next";
import type Stripe from "stripe";
import { z } from "zod";
import stripe from "@calcom/features/ee/payments/server/stripe";
import { HttpError } from "@calcom/lib/http-error";
import { defaultHandler, defaultResponder } from "@calcom/lib/server";
import prisma from "@calcom/prisma";
import { MembershipRole } from "@calcom/prisma/enums";
const querySchema = z.object({
session_id: z.string().min(1),
});
const checkoutSessionMetadataSchema = z.object({
teamName: z.string(),
teamSlug: z.string(),
userId: z.string().transform(Number),
});
const generateRandomString = () => {
return Math.random().toString(36).substring(2, 10);
};
async function handler(req: NextApiRequest, res: NextApiResponse) {
const { session_id } = querySchema.parse(req.query);
const checkoutSession = await stripe.checkout.sessions.retrieve(session_id, {
expand: ["subscription"],
});
if (!checkoutSession) throw new HttpError({ statusCode: 404, message: "Checkout session not found" });
const subscription = checkoutSession.subscription as Stripe.Subscription;
if (checkoutSession.payment_status !== "paid")
throw new HttpError({ statusCode: 402, message: "Payment required" });
// Let's query to ensure that the team metadata carried over from the checkout session.
const parseCheckoutSessionMetadata = checkoutSessionMetadataSchema.safeParse(checkoutSession.metadata);
if (!parseCheckoutSessionMetadata.success) {
console.error(
"Team metadata not found in checkout session",
parseCheckoutSessionMetadata.error,
checkoutSession.id
);
}
if (!checkoutSession.metadata?.userId) {
throw new HttpError({
statusCode: 400,
message: "Can't publish team/org without userId",
});
}
const checkoutSessionMetadata = parseCheckoutSessionMetadata.success
? parseCheckoutSessionMetadata.data
: {
teamName: checkoutSession?.metadata?.teamName ?? generateRandomString(),
teamSlug: checkoutSession?.metadata?.teamSlug ?? generateRandomString(),
userId: checkoutSession.metadata.userId,
};
const team = await prisma.team.create({
data: {
name: checkoutSessionMetadata.teamName,
slug: checkoutSessionMetadata.teamSlug,
members: {
create: {
userId: checkoutSessionMetadata.userId as number,
role: MembershipRole.OWNER,
accepted: true,
},
},
metadata: {
paymentId: checkoutSession.id,
subscriptionId: subscription.id || null,
subscriptionItemId: subscription.items.data[0].id || null,
},
},
});
// Sync Services: Close.com
// closeComUpdateTeam(prevTeam, team);
// redirect to team screen
res.redirect(302, `/settings/teams/${team.id}/onboard-members?event=team_created`);
}
export default defaultHandler({
GET: Promise.resolve({ default: defaultResponder(handler) }),
});
-2
View File
@@ -105,8 +105,6 @@ async function getIdentityData(req: NextApiRequest) {
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const identity = await getIdentityData(req);
const img = identity?.avatar;
// We cache for one day
res.setHeader("Cache-Control", "s-maxage=86400, stale-while-revalidate=60");
// If image isn't set or links to this route itself, use default avatar
if (!img) {
if (identity?.org) {
+1 -1
View File
@@ -54,8 +54,8 @@ const IntegrationsContainer = ({
automation: Share2,
analytics: BarChart,
payment: CreditCard,
web3: BarChart, // deprecated
other: Grid,
web3: CreditCard, // deprecated
video: Video, // deprecated
messaging: Mail,
crm: Contact,
+2 -2
View File
@@ -98,9 +98,9 @@ inferSSRProps<typeof _getServerSideProps> & WithNonceProps<{}>) {
callbackUrl = safeCallbackUrl || "";
const LoginFooter = (
<a href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
<Link href={`${WEBSITE_URL}/signup`} className="text-brand-500 font-medium">
{t("dont_have_an_account")}
</a>
</Link>
);
const TwoFactorFooter = (
+2 -1
View File
@@ -9,6 +9,7 @@ import { orgDomainConfig } from "@calcom/features/ee/organizations/lib/orgDomain
import stripe from "@calcom/features/ee/payments/server/stripe";
import { hostedCal, isSAMLLoginEnabled, samlProductID, samlTenantID } from "@calcom/features/ee/sso/lib/saml";
import { ssoTenantProduct } from "@calcom/features/ee/sso/lib/sso";
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { checkUsername } from "@calcom/lib/server/checkUsername";
import prisma from "@calcom/prisma";
@@ -72,7 +73,7 @@ export const getServerSideProps = async (context: GetServerSidePropsContext) =>
// Validating if username is Premium, while this is true an email its required for stripe user confirmation
if (usernameParam && session.user.email) {
const availability = await checkUsername(usernameParam, currentOrgDomain);
if (availability.available && availability.premium) {
if (availability.available && availability.premium && IS_PREMIUM_USERNAME_ENABLED) {
const stripePremiumUrl = await getStripePremiumUsernameUrl({
userEmail: session.user.email,
username: usernameParam,
+1
View File
@@ -1244,6 +1244,7 @@ async function getRecurringBookings(recurringEventId: string | null) {
const recurringBookings = await prisma.booking.findMany({
where: {
recurringEventId,
status: BookingStatus.ACCEPTED,
},
select: {
startTime: true,
+1 -1
View File
@@ -97,7 +97,7 @@ export default function Bookings() {
}
);
// Animate page (tab) tranistions to look smoothing
// Animate page (tab) transitions to look smoothing
const buttonInView = useInViewObserver(() => {
if (!query.isFetching && query.hasNextPage && query.status === "success") {
+1 -1
View File
@@ -13,7 +13,7 @@ import { getLayout } from "@calcom/features/MainLayout";
import { useOrgBranding } from "@calcom/features/ee/organizations/context/provider";
import useIntercom from "@calcom/features/ee/support/lib/intercom/useIntercom";
import { EventTypeEmbedButton, EventTypeEmbedDialog } from "@calcom/features/embed/EventTypeEmbed";
import { EventTypeDescriptionLazy as EventTypeDescription } from "@calcom/features/eventtypes/components";
import { EventTypeDescription } from "@calcom/features/eventtypes/components";
import CreateEventTypeDialog from "@calcom/features/eventtypes/components/CreateEventTypeDialog";
import { DuplicateDialog } from "@calcom/features/eventtypes/components/DuplicateDialog";
import { TeamsFilter } from "@calcom/features/filters/components/TeamsFilter";
+3 -3
View File
@@ -69,17 +69,17 @@ export default function InsightsPage() {
<FiltersProvider>
<Filters />
<div className="mb-4 space-y-6">
<div className="mb-4 space-y-4">
<BookingKPICards />
<BookingStatusLineChart />
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<PopularEventsTable />
<AverageEventDurationChart />
</div>
<div className="grid grid-cols-1 gap-5 md:grid-cols-2">
<div className="grid grid-cols-1 gap-4 md:grid-cols-2">
<MostBookedTeamMembersTable />
<LeastBookedTeamMembersTable />
</div>
@@ -26,7 +26,7 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
if (team) {
return GSSTeamPage({ ...ctx, query: { slug: ctx.query.user } });
}
return GSSUserPage({ ...ctx, query: { user: ctx.query.user } });
return GSSUserPage({ ...ctx, query: { user: ctx.query.user, redirect: ctx.query.redirect } });
};
type Props = UserPageProps | TeamPageProps;
@@ -1,37 +1,12 @@
import { Trans } from "next-i18next";
import Link from "next/link";
import { useRouter } from "next/navigation";
import { Fragment, useState, useEffect } from "react";
import { Fragment } from "react";
import DisconnectIntegration from "@calcom/features/apps/components/DisconnectIntegration";
import { CalendarSwitch } from "@calcom/features/calendars/CalendarSwitch";
import DestinationCalendarSelector from "@calcom/features/calendars/DestinationCalendarSelector";
import SectionBottomActions from "@calcom/features/settings/SectionBottomActions";
import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout";
import { classNames } from "@calcom/lib";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import { trpc } from "@calcom/trpc/react";
import {
Alert,
Badge,
Button,
EmptyScreen,
List,
ListItem,
ListItemText,
ListItemTitle,
Meta,
SkeletonButton,
SkeletonContainer,
SkeletonText,
showToast,
Label,
} from "@calcom/ui";
import { Plus, Calendar } from "@calcom/ui/components/icon";
import { QueryCell } from "@lib/QueryCell";
import { Button, Meta, SkeletonButton, SkeletonContainer, SkeletonText } from "@calcom/ui";
import { Plus } from "@calcom/ui/components/icon";
import PageWrapper from "@components/PageWrapper";
import { CalendarListContainer } from "@components/apps/CalendarListContainer";
const SkeletonLoader = () => {
return (
@@ -62,37 +37,6 @@ const AddCalendarButton = () => {
const CalendarsView = () => {
const { t } = useLocale();
const router = useRouter();
const utils = trpc.useContext();
const query = trpc.viewer.connectedCalendars.useQuery();
const [selectedDestinationCalendarOption, setSelectedDestinationCalendar] = useState<{
integration: string;
externalId: string;
} | null>(null);
useEffect(() => {
if (query?.data?.destinationCalendar) {
setSelectedDestinationCalendar({
integration: query.data.destinationCalendar.integration,
externalId: query.data.destinationCalendar.externalId,
});
}
}, [query?.isLoading, query?.data?.destinationCalendar]);
const mutation = trpc.viewer.setDestinationCalendar.useMutation({
async onSettled() {
await utils.viewer.connectedCalendars.invalidate();
},
onSuccess: async () => {
showToast(t("calendar_updated_successfully"), "success");
},
onError: () => {
showToast(t("unexpected_error_try_again"), "error");
},
});
return (
<>
@@ -102,167 +46,9 @@ const CalendarsView = () => {
CTA={<AddCalendarButton />}
borderInShellHeader={false}
/>
<QueryCell
query={query}
customLoader={<SkeletonLoader />}
success={({ data }) => {
const isDestinationUpdateBtnDisabled =
selectedDestinationCalendarOption?.externalId === query?.data?.destinationCalendar?.externalId;
return data.connectedCalendars.length ? (
<div>
<div className="border-subtle mt-8 rounded-t-lg border px-4 py-6 sm:px-6">
<h2 className="text-emphasis mb-1 text-base font-bold leading-5 tracking-wide">
{t("add_to_calendar")}
</h2>
<p className="text-subtle text-sm leading-tight">{t("add_to_calendar_description")}</p>
</div>
<div className="border-subtle flex w-full flex-col space-y-3 border border-x border-y-0 px-4 py-6 sm:px-6">
<div>
<Label className="text-default mb-0 font-medium">{t("add_events_to")}</Label>
<DestinationCalendarSelector
hidePlaceholder
value={selectedDestinationCalendarOption?.externalId}
onChange={(option) => {
setSelectedDestinationCalendar(option);
}}
isLoading={mutation.isLoading}
/>
</div>
</div>
<SectionBottomActions align="end">
<Button
loading={mutation.isLoading}
disabled={isDestinationUpdateBtnDisabled}
color="primary"
onClick={() => {
if (selectedDestinationCalendarOption) mutation.mutate(selectedDestinationCalendarOption);
}}>
{t("update")}
</Button>
</SectionBottomActions>
<div className="border-subtle mt-8 rounded-t-lg border px-4 py-6 sm:px-6">
<h4 className="text-emphasis text-base font-semibold leading-5">
{t("check_for_conflicts")}
</h4>
<p className="text-default text-sm leading-tight">{t("select_calendars")}</p>
</div>
<List
className="border-subtle flex flex-col gap-6 rounded-b-lg border border-t-0 p-6"
noBorderTreatment>
{data.connectedCalendars.map((item) => (
<Fragment key={item.credentialId}>
{item.error && item.error.message && (
<Alert
severity="warning"
key={item.credentialId}
title={t("calendar_connection_fail")}
message={item.error.message}
className="mb-4 mt-4"
actions={
<>
{/* @TODO: add a reconnect button, that calls add api and delete old credential */}
<DisconnectIntegration
credentialId={item.credentialId}
trashIcon
onSuccess={() => query.refetch()}
buttonProps={{
className: "border border-default py-[2px]",
color: "secondary",
}}
/>
</>
}
/>
)}
{item?.error === undefined && item.calendars && (
<ListItem className="flex-col rounded-lg">
<div className="flex w-full flex-1 items-center space-x-3 p-4 rtl:space-x-reverse">
{
// eslint-disable-next-line @next/next/no-img-element
item.integration.logo && (
<img
className={classNames(
"h-10 w-10",
item.integration.logo.includes("-dark") && "dark:invert"
)}
src={item.integration.logo}
alt={item.integration.title}
/>
)
}
<div className="flex-grow truncate pl-2">
<ListItemTitle component="h3" className="mb-1 space-x-2 rtl:space-x-reverse">
<Link href={`/apps/${item.integration.slug}`}>
{item.integration.name || item.integration.title}
</Link>
{data?.destinationCalendar?.credentialId === item.credentialId && (
<Badge variant="green">Default</Badge>
)}
</ListItemTitle>
<ListItemText component="p">{item.integration.description}</ListItemText>
</div>
<div>
<DisconnectIntegration
trashIcon
credentialId={item.credentialId}
buttonProps={{ className: "border border-default" }}
/>
</div>
</div>
<div className="border-subtle w-full border-t">
<p className="text-subtle px-2 pt-4 text-sm">{t("toggle_calendars_conflict")}</p>
<ul className="space-y-4 p-4">
{item.calendars.map((cal) => (
<CalendarSwitch
key={cal.externalId}
credentialId={cal.credentialId}
externalId={cal.externalId}
title={cal.name || "Nameless calendar"}
name={cal.name || "Nameless calendar"}
type={item.integration.type}
isChecked={
cal.isSelected || cal.externalId === data?.destinationCalendar?.externalId
}
/>
))}
</ul>
</div>
</ListItem>
)}
</Fragment>
))}
</List>
</div>
) : (
<EmptyScreen
Icon={Calendar}
headline={t("no_calendar_installed")}
description={t("no_calendar_installed_description")}
buttonText={t("add_a_calendar")}
buttonOnClick={() => router.push("/apps/categories/calendar")}
className="mt-6"
/>
);
}}
error={() => {
return (
<Alert
message={
<Trans i18nKey="fetching_calendars_error">
An error ocurred while fetching your Calendars.
<a className="cursor-pointer underline" onClick={() => query.refetch()}>
try again
</a>
.
</Trans>
}
severity="error"
/>
);
}}
/>
<div className="mt-8">
<CalendarListContainer />
</div>
</>
);
};
+390 -102
View File
@@ -1,25 +1,34 @@
import { zodResolver } from "@hookform/resolvers/zod";
import { CalendarHeart, Info, Link2, ShieldCheckIcon, StarIcon, Users } from "lucide-react";
import type { GetServerSidePropsContext } from "next";
import { signIn } from "next-auth/react";
import Link from "next/link";
import { useRouter } from "next/navigation";
import type { CSSProperties } from "react";
import { useState, useEffect } from "react";
import type { SubmitHandler } from "react-hook-form";
import { FormProvider, useForm } from "react-hook-form";
import { useForm, useFormContext } from "react-hook-form";
import { z } from "zod";
import getStripe from "@calcom/app-store/stripepayment/lib/client";
import { getPremiumPlanPriceValue } from "@calcom/app-store/stripepayment/lib/utils";
import { checkPremiumUsername } from "@calcom/features/ee/common/lib/checkPremiumUsername";
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
import { isSAMLLoginEnabled } from "@calcom/features/ee/sso/lib/saml";
import { useFlagMap } from "@calcom/features/flags/context/provider";
import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { IS_SELF_HOSTED, WEBAPP_URL } from "@calcom/lib/constants";
import { classNames } from "@calcom/lib";
import { APP_NAME, IS_CALCOM, IS_SELF_HOSTED, WEBAPP_URL, WEBSITE_URL } from "@calcom/lib/constants";
import { fetchUsername } from "@calcom/lib/fetchUsername";
import { useCompatSearchParams } from "@calcom/lib/hooks/useCompatSearchParams";
import { useDebounce } from "@calcom/lib/hooks/useDebounce";
import { useLocale } from "@calcom/lib/hooks/useLocale";
import slugify from "@calcom/lib/slugify";
import { collectPageParameters, telemetryEventTypes, useTelemetry } from "@calcom/lib/telemetry";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { signupSchema as apiSignupSchema } from "@calcom/prisma/zod-utils";
import type { inferSSRProps } from "@calcom/types/inferSSRProps";
import { Alert, Button, EmailField, HeadSeo, PasswordField, TextField } from "@calcom/ui";
import { Button, HeadSeo, PasswordField, TextField, Form, Alert } from "@calcom/ui";
import PageWrapper from "@components/PageWrapper";
@@ -34,7 +43,29 @@ type FormValues = z.infer<typeof signupSchema>;
type SignupProps = inferSSRProps<typeof getServerSideProps>;
const checkValidEmail = (email: string) => z.string().email().safeParse(email).success;
const FEATURES = [
{
title: "connect_all_calendars",
description: "connect_all_calendars_description",
i18nOptions: {
appName: APP_NAME,
},
icon: CalendarHeart,
},
{
title: "set_availability",
description: "set_availbility_description",
icon: Users,
},
{
title: "share_a_link_or_embed",
description: "share_a_link_or_embed_description",
icon: Link2,
i18nOptions: {
appName: APP_NAME,
},
},
];
const getOrgUsernameFromEmail = (email: string, autoAcceptEmailDomain: string) => {
const [emailUser, emailDomain = ""] = email.split("@");
@@ -46,31 +77,124 @@ const getOrgUsernameFromEmail = (email: string, autoAcceptEmailDomain: string) =
return username;
};
function UsernameField({
username,
setPremium,
premium,
setUsernameTaken,
usernameTaken,
...props
}: React.ComponentProps<typeof TextField> & {
username: string;
setPremium: (value: boolean) => void;
premium: boolean;
usernameTaken: boolean;
setUsernameTaken: (value: boolean) => void;
}) {
const { t } = useLocale();
const { register, formState } = useFormContext<FormValues>();
const debouncedUsername = useDebounce(username, 600);
useEffect(() => {
if (formState.isSubmitting || formState.isSubmitSuccessful) return;
async function checkUsername() {
if (!debouncedUsername) {
setPremium(false);
setUsernameTaken(false);
return;
}
fetchUsername(debouncedUsername).then(({ data }) => {
setPremium(data.premium);
setUsernameTaken(!data.available);
});
}
checkUsername();
}, [debouncedUsername, setPremium, setUsernameTaken, formState.isSubmitting, formState.isSubmitSuccessful]);
return (
<div>
<TextField
{...props}
{...register("username")}
data-testid="signup-usernamefield"
addOnFilled={false}
/>
{(!formState.isSubmitting || !formState.isSubmitted) && (
<div className="text-gray text-default flex items-center text-sm">
<p className="flex items-center text-sm ">
{usernameTaken ? (
<div className="text-error">
<Info className="mr-1 inline-block h-4 w-4" />
{t("already_in_use_error")}
</div>
) : premium ? (
<div data-testid="premium-username-warning">
<StarIcon className="mr-1 inline-block h-4 w-4" />
{t("premium_username", {
price: getPremiumPlanPriceValue(),
})}
</div>
) : null}
</p>
</div>
)}
</div>
);
}
const checkValidEmail = (email: string) => z.string().email().safeParse(email).success;
function addOrUpdateQueryParam(url: string, key: string, value: string) {
const separator = url.includes("?") ? "&" : "?";
const param = `${key}=${encodeURIComponent(value)}`;
return `${url}${separator}${param}`;
}
export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoAcceptEmail }: SignupProps) {
export default function Signup({
prepopulateFormValues,
token,
orgSlug,
isGoogleLoginEnabled,
isSAMLLoginEnabled,
orgAutoAcceptEmail,
}: SignupProps) {
const [premiumUsername, setPremiumUsername] = useState(false);
const [usernameTaken, setUsernameTaken] = useState(false);
const searchParams = useCompatSearchParams();
const telemetry = useTelemetry();
const { t, i18n } = useLocale();
const router = useRouter();
const flags = useFlagMap();
const methods = useForm<FormValues>({
mode: "onChange",
const formMethods = useForm<FormValues>({
resolver: zodResolver(signupSchema),
defaultValues: prepopulateFormValues,
defaultValues: prepopulateFormValues satisfies FormValues,
mode: "onChange",
});
const {
register,
formState: { errors, isSubmitting },
} = methods;
watch,
formState: { isSubmitting, errors, isSubmitSuccessful },
} = formMethods;
const handleErrors = async (resp: Response) => {
const loadingSubmitState = isSubmitSuccessful || isSubmitting;
const handleErrorsAndStripe = async (resp: Response) => {
if (!resp.ok) {
const err = await resp.json();
throw new Error(err.message);
if (err.checkoutSessionId) {
const stripe = await getStripe();
if (stripe) {
console.log("Redirecting to stripe checkout");
const { error } = await stripe.redirectToCheckout({
sessionId: err.checkoutSessionId,
});
console.warn(error.message);
}
} else {
throw new Error(err.message);
}
}
};
@@ -88,7 +212,7 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA
},
method: "POST",
})
.then(handleErrors)
.then(handleErrorsAndStripe)
.then(async () => {
telemetry.event(telemetryEventTypes.signup, collectPageParameters());
const verifyOrGettingStarted = flags["email-verification"] ? "auth/verify-email" : "getting-started";
@@ -106,109 +230,262 @@ export default function Signup({ prepopulateFormValues, token, orgSlug, orgAutoA
});
})
.catch((err) => {
methods.setError("apiError", { message: err.message });
formMethods.setError("apiError", { message: err.message });
});
};
return (
<>
<div
className="bg-muted flex min-h-screen flex-col justify-center "
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}
aria-labelledby="modal-title"
role="dialog"
aria-modal="true">
<div
className="light bg-muted 2xl:bg-default flex min-h-screen w-full flex-col items-center justify-center"
style={
{
"--cal-brand": "#111827",
"--cal-brand-emphasis": "#101010",
"--cal-brand-text": "white",
"--cal-brand-subtle": "#9CA3AF",
} as CSSProperties
}>
<div className="bg-muted 2xl:border-subtle grid max-h-[800px] w-full max-w-[1440px] grid-cols-1 grid-rows-1 lg:grid-cols-2 2xl:rounded-lg 2xl:border ">
<HeadSeo title={t("sign_up")} description={t("sign_up")} />
<div className="sm:mx-auto sm:w-full sm:max-w-md">
<h2 className="font-cal text-emphasis text-center text-3xl font-extrabold">
{t("create_your_account")}
</h2>
</div>
<div className="mt-8 sm:mx-auto sm:w-full sm:max-w-md">
<div className="bg-default mx-2 p-6 shadow sm:rounded-lg lg:p-8">
<FormProvider {...methods}>
<form
onSubmit={(event) => {
event.preventDefault();
event.stopPropagation();
<div className="flex w-full flex-col px-4 py-6 sm:px-16 md:px-24 2xl:px-28">
{/* Header */}
{errors.apiError && (
<Alert severity="error" message={errors.apiError?.message} data-testid="signup-error-message" />
)}
<div className="flex flex-col gap-1">
<h1 className="font-cal text-[28px] ">
{IS_CALCOM ? t("create_your_calcom_account") : t("create_your_account")}
</h1>
{IS_CALCOM ? (
<p className="text-subtle text-base font-medium leading-6">{t("cal_signup_description")}</p>
) : (
<p className="text-subtle text-base font-medium leading-6">
{t("calcom_explained", {
appName: APP_NAME,
})}
</p>
)}
</div>
{/* Form Container */}
<div className="mt-10">
<Form
className="flex flex-col gap-4"
form={formMethods}
handleSubmit={async (values) => {
let updatedValues = values;
if (!formMethods.getValues().username && isOrgInviteByLink && orgAutoAcceptEmail) {
updatedValues = {
...values,
username: getOrgUsernameFromEmail(values.email, orgAutoAcceptEmail),
};
}
await signUp(updatedValues);
}}>
{/* Username */}
<UsernameField
label={t("username")}
username={watch("username")}
premium={premiumUsername}
usernameTaken={usernameTaken}
setUsernameTaken={(value) => setUsernameTaken(value)}
data-testid="signup-usernamefield"
setPremium={(value) => setPremiumUsername(value)}
addOnLeading={
orgSlug
? `${getOrgFullOrigin(orgSlug, { protocol: true })}/`
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
}
/>
{/* Email */}
<TextField
{...register("email")}
label={t("email")}
type="email"
data-testid="signup-emailfield"
/>
if (methods.formState?.errors?.apiError) {
methods.clearErrors("apiError");
}
if (!methods.getValues().username && isOrgInviteByLink && orgAutoAcceptEmail) {
methods.setValue(
"username",
getOrgUsernameFromEmail(methods.getValues().email, orgAutoAcceptEmail)
);
}
methods.handleSubmit(signUp)(event);
}}
className="bg-default space-y-6">
{errors.apiError && <Alert severity="error" message={errors.apiError?.message} />}
{}
<div className="space-y-4">
{!isOrgInviteByLink && (
<TextField
addOnLeading={
orgSlug
? `${getOrgFullOrigin(orgSlug, { protocol: true })}/`
: `${process.env.NEXT_PUBLIC_WEBSITE_URL}/`
{/* Password */}
<PasswordField
data-testid="signup-passwordfield"
label={t("password")}
{...register("password")}
hintErrors={["caplow", "min", "num"]}
/>
<Button
type="submit"
className="my-2 w-full justify-center"
loading={loadingSubmitState}
disabled={
!!formMethods.formState.errors.username ||
!!formMethods.formState.errors.email ||
usernameTaken
}>
{premiumUsername && !usernameTaken
? `Create Account for ${getPremiumPlanPriceValue()}`
: t("create_account")}
</Button>
</Form>
{/* Continue with Social Logins */}
{token || (!isGoogleLoginEnabled && !isSAMLLoginEnabled) ? null : (
<div className="mt-6">
<div className="relative flex items-center">
<div className="border-subtle flex-grow border-t" />
<span className="text-subtle leadning-none mx-2 flex-shrink text-sm font-normal ">
{t("or_continue_with")}
</span>
<div className="border-subtle flex-grow border-t" />
</div>
</div>
)}
{/* Social Logins */}
{!token && (
<div className="mt-6 flex flex-col gap-2 md:flex-row">
{isGoogleLoginEnabled ? (
<Button
color="secondary"
disabled={!!formMethods.formState.errors.username || premiumUsername}
className={classNames(
"w-full justify-center rounded-md text-center",
formMethods.formState.errors.username ? "opacity-50" : ""
)}
onClick={async () => {
const username = formMethods.getValues("username");
const baseUrl = process.env.NEXT_PUBLIC_WEBAPP_URL;
const GOOGLE_AUTH_URL = `${baseUrl}/auth/sso/google`;
if (username) {
// If username is present we save it in query params to check for premium
const searchQueryParams = new URLSearchParams();
searchQueryParams.set("username", formMethods.getValues("username"));
localStorage.setItem("username", username);
router.push(`${GOOGLE_AUTH_URL}?${searchQueryParams.toString()}`);
return;
}
{...register("username")}
disabled={!!orgSlug}
required
router.push(GOOGLE_AUTH_URL);
}}>
<img
className={classNames("text-emphasis mr-2 h-5 w-5", premiumUsername && "opacity-50")}
src="/google-icon.svg"
alt=""
/>
)}
<EmailField
{...register("email")}
disabled={prepopulateFormValues?.email}
className="disabled:bg-emphasis disabled:hover:cursor-not-allowed"
/>
<PasswordField
labelProps={{
className: "block text-sm font-medium text-default",
}}
{...register("password")}
hintErrors={["caplow", "min", "num"]}
className="border-default mt-1 block w-full rounded-md border px-3 py-2 shadow-sm focus:border-black focus:outline-none focus:ring-black sm:text-sm"
/>
</div>
<div className="flex space-x-2 rtl:space-x-reverse">
<Button type="submit" loading={isSubmitting} className="w-full justify-center">
{t("create_account")}
Google
</Button>
{!token && (
<Button
color="secondary"
className="w-full justify-center"
onClick={() =>
signIn("Cal.com", {
callbackUrl: searchParams?.get("callbackUrl")
? `${WEBAPP_URL}/${searchParams.get("callbackUrl")}`
: `${WEBAPP_URL}/getting-started`,
})
}>
{t("login_instead")}
</Button>
)}
</div>
</form>
</FormProvider>
) : null}
{isSAMLLoginEnabled ? (
<Button
color="secondary"
disabled={
!!formMethods.formState.errors.username ||
!!formMethods.formState.errors.email ||
premiumUsername
}
className={classNames(
"w-full justify-center rounded-md text-center",
formMethods.formState.errors.username && formMethods.formState.errors.email
? "opacity-50"
: ""
)}
onClick={() => {
if (!formMethods.getValues("username")) {
formMethods.trigger("username");
}
if (!formMethods.getValues("email")) {
formMethods.trigger("email");
return;
}
const username = formMethods.getValues("username");
localStorage.setItem("username", username);
const sp = new URLSearchParams();
// @NOTE: don't remove username query param as it's required right now for stripe payment page
sp.set("username", formMethods.getValues("username"));
sp.set("email", formMethods.getValues("email"));
router.push(
`${process.env.NEXT_PUBLIC_WEBAPP_URL}/auth/sso/saml` + `?${sp.toString()}`
);
}}>
<ShieldCheckIcon className="mr-2 h-5 w-5" />
{t("saml_sso")}
</Button>
) : null}
</div>
)}
</div>
{/* Already have an account & T&C */}
<div className="mt-6">
<div className="flex flex-col text-sm">
<Link href="/auth/login" className="text-emphasis hover:underline">
{t("already_have_account")}
</Link>
<div className="text-subtle">
By signing up, you agree to our{" "}
<Link className="text-emphasis hover:underline" href={`${WEBSITE_URL}/terms`}>
Terms of Service{" "}
</Link>
<span>and</span>{" "}
<Link className="text-emphasis hover:underline" href={`${WEBSITE_URL}/privacy`}>
Privacy Policy.
</Link>
</div>
</div>
</div>
</div>
<div className="bg-subtle border-subtle hidden w-full flex-col justify-between rounded-l-2xl py-12 pl-12 lg:flex">
{IS_CALCOM && (
<div className="mb-12 mr-12 grid h-full w-full grid-cols-4 gap-4 ">
<div className="">
<img src="/product-cards/trustpilot.svg" className="h-[54px] w-full" alt="#" />
</div>
<div>
<img src="/product-cards/g2.svg" className="h-[54px] w-full" alt="#" />
</div>
<div>
<img src="/product-cards/producthunt.svg" className="h-[54px] w-full" alt="#" />
</div>
</div>
)}
<div
className="rounded-2xl border-y border-l border-dashed border-[#D1D5DB5A] py-[6px] pl-[6px]"
style={{
backgroundColor: "rgba(236,237,239,0.9)",
}}>
<img src="/mock-event-type-list.svg" alt="#" className="" />
</div>
<div className="mr-12 mt-8 grid h-full w-full grid-cols-3 gap-4 overflow-hidden">
{!IS_CALCOM &&
FEATURES.map((feature) => (
<>
<div className="flex flex-col leading-none">
<div className="text-emphasis items-center">
<feature.icon className="mb-1 h-4 w-4" />
<span className="text-sm font-medium">{t(feature.title)}</span>
</div>
<div className="text-subtle text-sm">
<p>
{t(
feature.description,
feature.i18nOptions && {
...feature.i18nOptions,
}
)}
</p>
</div>
</div>
</>
))}
</div>
</div>
</div>
</>
</div>
);
}
const querySchema = z.object({
username: z
.string()
.optional()
.transform((val) => val || ""),
email: z.string().email().optional(),
});
export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
const prisma = await import("@calcom/prisma").then((mod) => mod.default);
const flags = await getFeatureFlagMap(prisma);
@@ -222,6 +499,9 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
prepopulateFormValues: undefined,
};
// username + email prepopulated from query params
const { username: preFillusername, email: prefilEmail } = querySchema.parse(ctx.query);
if (process.env.NEXT_PUBLIC_DISABLE_SIGNUP === "true" || flags["disable-signup"]) {
return {
notFound: true,
@@ -231,7 +511,15 @@ export const getServerSideProps = async (ctx: GetServerSidePropsContext) => {
// no token given, treat as a normal signup without verification token
if (!token) {
return {
props: JSON.parse(JSON.stringify(props)),
props: JSON.parse(
JSON.stringify({
...props,
prepopulateFormValues: {
username: preFillusername || null,
email: prefilEmail || null,
},
})
),
};
}
+1 -1
View File
@@ -214,7 +214,7 @@ export function VideoMeetingInfo(props: VideoMeetingInfo) {
<h3>{t("invitee_timezone")}:</h3>
<p>{booking.user?.timeZone}</p>
<h3>{t("when")}:</h3>
<p>
<p suppressHydrationWarning={true}>
{formatToLocalizedDate(startTime)} <br />
{formatToLocalizedTime(startTime)}
</p>
@@ -24,7 +24,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// there should be one, otherwise we throw
const { id } = await prisma.resetPasswordRequest.findFirstOrThrow({
where: {
email: `${user.username}@example.com`,
email: user.email,
},
select: {
id: true,
@@ -37,7 +37,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// Test when a user changes his email after starting the password reset flow
await prisma.user.update({
where: {
email: `${user.username}@example.com`,
email: user.email,
},
data: {
email: `${user.username}-2@example.com`,
@@ -54,7 +54,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
email: `${user.username}-2@example.com`,
},
data: {
email: `${user.username}@example.com`,
email: user.email,
},
});
@@ -75,7 +75,7 @@ test("Can reset forgotten password", async ({ page, users }) => {
// we're not logging in to the UI to speed up test performance.
const updatedUser = await prisma.user.findUniqueOrThrow({
where: {
email: `${user.username}@example.com`,
email: user.email,
},
select: {
id: true,
@@ -84,10 +84,10 @@ test("Can reset forgotten password", async ({ page, users }) => {
});
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
await expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
expect(await verifyPassword(newPassword, updatedUser.password!)).toBeTruthy();
// finally, make sure the same URL cannot be used to reset the password again, as it should be expired.
await page.goto(`/auth/forgot-password/${id}`);
await page.waitForSelector("text=That request is expired.");
await expect(page.locator(`text=Whoops`)).toBeVisible();
});
+425 -140
View File
@@ -1,19 +1,337 @@
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { Linter } from "eslint";
import { parse } from "node-html-parser";
import { getOrgFullOrigin } from "@calcom/features/ee/organizations/lib/orgDomains";
import { EMBED_LIB_URL, WEBAPP_URL } from "@calcom/lib/constants";
import { MembershipRole } from "@calcom/prisma/client";
import { test } from "./lib/fixtures";
function chooseEmbedType(page: Page, embedType: string) {
const linter = new Linter();
const eslintRules = {
"no-undef": "error",
"no-unused-vars": "off",
} as const;
test.describe.configure({ mode: "parallel" });
test.afterEach(({ users }) => users.deleteAll());
test.describe("Embed Code Generator Tests", () => {
test.describe("Non-Organization", () => {
test.beforeEach(async ({ users }) => {
const pro = await users.create();
await pro.apiLogin();
});
test.describe("Event Types Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/event-types");
});
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "inline");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "inline",
basePage: "/event-types",
});
await expectToContainValidCode(page, {
language: "html",
embedType: "inline",
orgSlug: null,
});
await goToReactCodeTab(page);
await expectToContainValidCode(page, {
language: "react",
embedType: "inline",
orgSlug: null,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "inline",
calLink: `${pro.username}/30-min`,
});
});
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "floating-popup");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "floating-popup",
basePage: "/event-types",
});
await expectToContainValidCode(page, {
language: "html",
embedType: "floating-popup",
orgSlug: null,
});
await goToReactCodeTab(page);
await expectToContainValidCode(page, {
language: "react",
embedType: "floating-popup",
orgSlug: null,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "floating-popup",
calLink: `${pro.username}/30-min`,
});
});
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "element-click");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "element-click",
basePage: "/event-types",
});
await expectToContainValidCode(page, {
language: "html",
embedType: "element-click",
orgSlug: null,
});
await goToReactCodeTab(page);
await expectToContainValidCode(page, {
language: "react",
embedType: "element-click",
orgSlug: null,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "element-click",
calLink: `${pro.username}/30-min`,
});
});
});
test.describe("Event Type Edit Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto(`/event-types`);
await Promise.all([
page.locator('a[href*="/event-types/"]').first().click(),
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
]);
});
test("open Embed Dialog for the Event Type", async ({ page }) => {
const basePage = new URL(page.url()).pathname;
const embedUrl = await clickEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage,
});
chooseEmbedType(page, "inline");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
basePage,
embedType: "inline",
});
await expectToContainValidCode(page, {
language: "html",
embedType: "inline",
orgSlug: null,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "inline",
calLink: decodeURIComponent(embedUrl),
});
});
});
});
test.describe("Organization", () => {
test.beforeEach(async ({ users, orgs }) => {
const org = await orgs.create({
name: "TestOrg",
});
const user = await users.create({
organizationId: org.id,
roleInOrganization: MembershipRole.MEMBER,
});
await user.apiLogin();
});
test.describe("Event Types Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/event-types");
});
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
const [user] = users.get();
const { team: org } = await user.getOrgMembership();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "inline");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "inline",
basePage: "/event-types",
});
// Default tab is HTML code tab
await expectToContainValidCode(page, {
language: "html",
embedType: "inline",
orgSlug: org.slug,
});
await goToReactCodeTab(page);
await expectToContainValidCode(page, {
language: "react",
embedType: "inline",
orgSlug: org.slug,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "inline",
calLink: `${user.username}/30-min`,
bookerUrl: getOrgFullOrigin(org?.slug ?? ""),
});
});
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
const [user] = users.get();
const { team: org } = await user.getOrgMembership();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "floating-popup");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "floating-popup",
basePage: "/event-types",
});
await expectToContainValidCode(page, {
language: "html",
embedType: "floating-popup",
orgSlug: org.slug,
});
await goToReactCodeTab(page);
await expectToContainValidCode(page, {
language: "react",
embedType: "floating-popup",
orgSlug: org.slug,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "floating-popup",
calLink: `${user.username}/30-min`,
bookerUrl: getOrgFullOrigin(org?.slug ?? ""),
});
});
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
const [user] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
const { team: org } = await user.getOrgMembership();
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "element-click");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "element-click",
basePage: "/event-types",
});
await expectToContainValidCode(page, {
language: "html",
embedType: "element-click",
orgSlug: org.slug,
});
await goToReactCodeTab(page);
await expectToContainValidCode(page, {
language: "react",
embedType: "element-click",
orgSlug: org.slug,
});
await goToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "element-click",
calLink: `${user.username}/30-min`,
bookerUrl: getOrgFullOrigin(org?.slug ?? ""),
});
});
});
});
});
type EmbedType = "inline" | "floating-popup" | "element-click";
function chooseEmbedType(page: Page, embedType: EmbedType) {
page.locator(`[data-testid=${embedType}]`).click();
}
async function gotToPreviewTab(page: Page) {
async function goToPreviewTab(page: Page) {
// To prevent early timeouts
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
await page.locator("[data-testid=embed-tabs]").locator("text=Preview").click();
await page.locator("[data-testid=horizontal-tab-Preview]").click();
}
async function goToReactCodeTab(page: Page) {
// To prevent early timeouts
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(1000);
await page.locator("[data-testid=horizontal-tab-React]").click();
}
async function clickEmbedButton(page: Page) {
@@ -55,7 +373,7 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
basePage,
}: {
embedUrl: string | null;
embedType: string;
embedType: EmbedType;
basePage: string;
}
) {
@@ -73,10 +391,108 @@ async function expectToBeNavigatingToEmbedCodeAndPreviewDialog(
});
}
async function expectToContainValidCode(page: Page, { embedType }: { embedType: string }) {
async function expectToContainValidCode(
page: Page,
{
embedType,
language,
orgSlug,
}: { embedType: EmbedType; language: "html" | "react"; orgSlug: string | null }
) {
if (language === "react") {
return expectValidReactEmbedSnippet(page, { embedType, orgSlug });
}
if (language === "html") {
return expectValidHtmlEmbedSnippet(page, { embedType, orgSlug });
}
throw new Error("Unknown language");
}
async function expectValidHtmlEmbedSnippet(
page: Page,
{ embedType, orgSlug }: { embedType: EmbedType; orgSlug: string | null }
) {
const embedCode = await page.locator("[data-testid=embed-code]").inputValue();
expect(embedCode.includes("(function (C, A, L)")).toBe(true);
expect(embedCode.includes(`Cal ${embedType} embed code begins`)).toBe(true);
expect(embedCode).toContain("function (C, A, L)");
expect(embedCode).toContain(`Cal ${embedType} embed code begins`);
if (orgSlug) {
expect(embedCode).toContain(orgSlug);
}
const dom = parse(embedCode);
const scripts = dom.getElementsByTagName("script");
assertThatCodeIsValidVanillaJsCode(scripts[0].innerText);
return {
message: () => `passed`,
pass: true,
};
}
function assertThatCodeIsValidVanillaJsCode(code: string) {
const lintResult = linter.verify(code, {
env: {
browser: true,
},
parserOptions: {
ecmaVersion: 2021,
},
globals: {
Cal: "readonly",
},
rules: eslintRules,
});
if (lintResult.length) {
console.log(
JSON.stringify({
lintResult,
code,
})
);
}
expect(lintResult.length).toBe(0);
}
function assertThatCodeIsValidReactCode(code: string) {
const lintResult = linter.verify(code, {
env: {
browser: true,
},
parserOptions: {
ecmaVersion: 2021,
ecmaFeatures: {
jsx: true,
},
sourceType: "module",
},
rules: eslintRules,
});
if (lintResult.length) {
console.log(
JSON.stringify({
lintResult,
code,
})
);
}
expect(lintResult.length).toBe(0);
}
async function expectValidReactEmbedSnippet(
page: Page,
{ embedType, orgSlug }: { embedType: EmbedType; orgSlug: string | null }
) {
const embedCode = await page.locator("[data-testid=embed-react]").inputValue();
expect(embedCode).toContain("export default function MyApp(");
expect(embedCode).toContain(
embedType === "floating-popup" ? "floatingButton" : embedType === "inline" ? `<Cal` : "data-cal-link"
);
if (orgSlug) {
expect(embedCode).toContain(orgSlug);
}
assertThatCodeIsValidReactCode(embedCode);
return {
message: () => `passed`,
pass: true,
@@ -88,141 +504,10 @@ async function expectToContainValidCode(page: Page, { embedType }: { embedType:
*/
async function expectToContainValidPreviewIframe(
page: Page,
{ embedType, calLink }: { embedType: string; calLink: string }
{ embedType, calLink, bookerUrl }: { embedType: EmbedType; calLink: string; bookerUrl?: string }
) {
const bookerUrl = `${WEBAPP_URL}`;
bookerUrl = bookerUrl || `${WEBAPP_URL}`;
expect(await page.locator("[data-testid=embed-preview]").getAttribute("src")).toContain(
`/preview.html?embedType=${embedType}&calLink=${calLink}&embedLibUrl=${EMBED_LIB_URL}&bookerUrl=${bookerUrl}`
);
}
test.describe.configure({ mode: "parallel" });
test.afterEach(({ users }) => users.deleteAll());
test.describe("Embed Code Generator Tests", () => {
test.beforeEach(async ({ users }) => {
const pro = await users.create();
await pro.apiLogin();
});
test.describe("Event Types Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/event-types");
});
test("open Embed Dialog and choose Inline for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "inline");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "inline",
basePage: "/event-types",
});
await expectToContainValidCode(page, { embedType: "inline" });
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "inline",
calLink: `${pro.username}/30-min`,
});
});
test("open Embed Dialog and choose floating-popup for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "floating-popup");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "floating-popup",
basePage: "/event-types",
});
await expectToContainValidCode(page, { embedType: "floating-popup" });
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "floating-popup",
calLink: `${pro.username}/30-min`,
});
});
test("open Embed Dialog and choose element-click for First Event Type", async ({ page, users }) => {
const [pro] = users.get();
const embedUrl = await clickFirstEventTypeEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage: "/event-types",
});
chooseEmbedType(page, "element-click");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
embedType: "element-click",
basePage: "/event-types",
});
await expectToContainValidCode(page, { embedType: "element-click" });
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "element-click",
calLink: `${pro.username}/30-min`,
});
});
});
test.describe("Event Type Edit Page", () => {
test.beforeEach(async ({ page }) => {
await page.goto(`/event-types`);
await Promise.all([
page.locator('a[href*="/event-types/"]').first().click(),
page.waitForURL((url) => url.pathname.startsWith("/event-types/")),
]);
});
test("open Embed Dialog for the Event Type", async ({ page }) => {
const basePage = new URL(page.url()).pathname;
const embedUrl = await clickEmbedButton(page);
await expectToBeNavigatingToEmbedTypesDialog(page, {
embedUrl,
basePage,
});
chooseEmbedType(page, "inline");
await expectToBeNavigatingToEmbedCodeAndPreviewDialog(page, {
embedUrl,
basePage,
embedType: "inline",
});
await expectToContainValidCode(page, {
embedType: "inline",
});
await gotToPreviewTab(page);
await expectToContainValidPreviewIframe(page, {
embedType: "inline",
calLink: decodeURIComponent(embedUrl),
});
});
});
});
-34
View File
@@ -1,34 +0,0 @@
import type { Page } from "@playwright/test";
declare global {
interface Window {
E2E_CLIPBOARD_VALUE?: string;
}
}
export type Window = typeof window;
// creates the single server fixture
export const createClipboardFixture = (page: Page) => {
return {
reset: async () => {
await page.evaluate(() => {
delete window.E2E_CLIPBOARD_VALUE;
});
},
get: async () => {
return getClipboardValue({ page });
},
};
};
function getClipboardValue({ page }: { page: Page }) {
return page.evaluate(() => {
return new Promise<string>((resolve, reject) => {
setInterval(() => {
if (!window.E2E_CLIPBOARD_VALUE) return;
resolve(window.E2E_CLIPBOARD_VALUE);
}, 500);
setTimeout(() => reject(new Error("Timeout")), 1000);
});
});
}
+50
View File
@@ -0,0 +1,50 @@
import type { Page } from "@playwright/test";
import type { Feature } from "@prisma/client";
import type { AppFlags } from "@calcom/features/flags/config";
import { prisma } from "@calcom/prisma";
type FeatureSlugs = keyof AppFlags;
export const createFeatureFixture = (page: Page) => {
const store = { features: [], page } as { features: Feature[]; page: typeof page };
let initalFeatures: Feature[] = [];
// IIF to add all feautres to store on creation
return {
init: async () => {
const features = await prisma.feature.findMany();
store.features = features;
initalFeatures = features;
return features;
},
getAll: () => store.features,
get: (slug: FeatureSlugs) => store.features.find((b) => b.slug === slug),
deleteAll: async () => {
await prisma.feature.deleteMany({
where: { slug: { in: store.features.map((feature) => feature.slug) } },
});
store.features = [];
},
delete: async (slug: FeatureSlugs) => {
await prisma.feature.delete({ where: { slug } });
store.features = store.features.filter((b) => b.slug !== slug);
},
toggleFeature: async (slug: FeatureSlugs) => {
const feature = store.features.find((b) => b.slug === slug);
if (feature) {
const enabled = !feature.enabled;
await prisma.feature.update({ where: { slug }, data: { enabled } });
store.features = store.features.map((b) => (b.slug === slug ? { ...b, enabled } : b));
}
},
set: async (slug: FeatureSlugs, enabled: boolean) => {
const feature = store.features.find((b) => b.slug === slug);
if (feature) {
store.features = store.features.map((b) => (b.slug === slug ? { ...b, enabled } : b));
await prisma.feature.update({ where: { slug }, data: { enabled } });
}
},
reset: () => (store.features = initalFeatures),
};
};
@@ -73,10 +73,10 @@ const fillQuestion = async (eventTypePage: Page, questionType: string, customLoc
},
multiselect: async () => {
if (customLocators.shouldChangeMultiSelectLocator) {
await eventTypePage.locator("form svg").nth(1).click();
await eventTypePage.getByLabel("multi-select-dropdown").click();
await eventTypePage.getByTestId("select-option-Option 1").click();
} else {
await eventTypePage.locator("form svg").last().click();
await eventTypePage.getByLabel("multi-select-dropdown").last().click();
await eventTypePage.getByTestId("select-option-Option 1").click();
}
},
@@ -88,10 +88,10 @@ const fillQuestion = async (eventTypePage: Page, questionType: string, customLoc
},
select: async () => {
if (customLocators.shouldChangeSelectLocator) {
await eventTypePage.locator("form svg").nth(1).click();
await eventTypePage.getByLabel("select-dropdown").first().click();
await eventTypePage.getByTestId("select-option-Option 1").click();
} else {
await eventTypePage.locator("form svg").last().click();
await eventTypePage.getByLabel("select-dropdown").last().click();
await eventTypePage.getByTestId("select-option-Option 1").click();
}
},
@@ -138,11 +138,12 @@ const fillAllQuestions = async (eventTypePage: Page, questions: string[], option
await eventTypePage.getByPlaceholder("Textarea test").fill("This is a sample text for textarea.");
break;
case "select":
await eventTypePage.locator("form svg").last().click();
await eventTypePage.getByLabel("select-dropdown").last().click();
await eventTypePage.getByTestId("select-option-Option 1").click();
break;
case "multiselect":
await eventTypePage.locator("form svg").nth(4).click();
// select-dropdown
await eventTypePage.getByLabel("multi-select-dropdown").click();
await eventTypePage.getByTestId("select-option-Option 1").click();
break;
case "number":
+28 -2
View File
@@ -143,6 +143,17 @@ const createTeamAndAddUser = async (
export const createUsersFixture = (page: Page, emails: API | undefined, workerInfo: WorkerInfo) => {
const store = { users: [], page } as { users: UserFixture[]; page: typeof page };
return {
buildForSignup: (opts?: Pick<CustomUserOpts, "email" | "username" | "useExactUsername" | "password">) => {
const uname =
opts?.useExactUsername && opts?.username
? opts.username
: `${opts?.username || "user"}-${workerInfo.workerIndex}-${Date.now()}`;
return {
username: uname,
email: opts?.email ?? `${uname}@example.com`,
password: opts?.password ?? uname,
};
},
create: async (
opts?: CustomUserOpts | null,
scenario: {
@@ -396,6 +407,15 @@ export const createUsersFixture = (page: Page, emails: API | undefined, workerIn
await prisma.user.delete({ where: { id } });
store.users = store.users.filter((b) => b.id !== id);
},
deleteByEmail: async (email: string) => {
// Use deleteMany instead of delete to avoid the findUniqueOrThrow error that happens before the delete
await prisma.user.deleteMany({
where: {
email,
},
});
store.users = store.users.filter((b) => b.email !== email);
},
set: async (email: string) => {
const user = await prisma.user.findUniqueOrThrow({
where: { email },
@@ -460,7 +480,7 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
}
return membership;
},
getOrg: async () => {
getOrgMembership: async () => {
return prisma.membership.findFirstOrThrow({
where: {
userId: user.id,
@@ -471,7 +491,13 @@ const createUserFixture = (user: UserWithIncludes, page: Page) => {
},
},
},
include: { team: { select: { children: true, metadata: true, name: true } } },
include: {
team: {
include: {
children: true,
},
},
},
});
},
getFirstEventAsOwner: async () =>
+6 -5
View File
@@ -9,8 +9,8 @@ import prisma from "@calcom/prisma";
import type { ExpectedUrlDetails } from "../../../../playwright.config";
import { createBookingsFixture } from "../fixtures/bookings";
import { createClipboardFixture } from "../fixtures/clipboard";
import { createEmbedsFixture } from "../fixtures/embeds";
import { createFeatureFixture } from "../fixtures/features";
import { createOrgsFixture } from "../fixtures/orgs";
import { createPaymentsFixture } from "../fixtures/payments";
import { createBookingPageFixture } from "../fixtures/regularBookings";
@@ -30,7 +30,7 @@ export interface Fixtures {
emails?: API;
routingForms: ReturnType<typeof createRoutingFormsFixture>;
bookingPage: ReturnType<typeof createBookingPageFixture>;
clipboard: ReturnType<typeof createClipboardFixture>;
features: ReturnType<typeof createFeatureFixture>;
}
declare global {
@@ -97,8 +97,9 @@ export const test = base.extend<Fixtures>({
const bookingPage = createBookingPageFixture(page);
await use(bookingPage);
},
clipboard: async ({ page }, use) => {
const clipboard = createClipboardFixture(page);
await use(clipboard);
features: async ({ page }, use) => {
const features = createFeatureFixture(page);
await features.init();
await use(features);
},
});
+14
View File
@@ -203,6 +203,12 @@ export async function installAppleCalendar(page: Page) {
await page.click('[data-testid="install-app-button"]');
}
export async function getInviteLink(page: Page) {
const response = await page.waitForResponse("**/api/trpc/teams/createInvite?batch=1");
const json = await response.json();
return json[0].result.data.json.inviteLink as string;
}
export async function getEmailsReceivedByUser({
emails,
userEmail,
@@ -289,3 +295,11 @@ export function generateTotpCode(email: string) {
totp.options = { step: 90 };
return totp.generate(secret);
}
export async function fillStripeTestCheckout(page: Page) {
await page.fill("[name=cardNumber]", "4242424242424242");
await page.fill("[name=cardExpiry]", "12/30");
await page.fill("[name=cardCvc]", "111");
await page.fill("[name=billingName]", "Stripe Stripeson");
await page.click(".SubmitButton--complete-Shimmer");
}
+16 -8
View File
@@ -1,9 +1,15 @@
import { expect } from "@playwright/test";
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { test } from "./lib/fixtures";
import { selectFirstAvailableTimeSlotNextMonth, bookTimeSlot } from "./lib/testUtils";
import { localize } from "./lib/testUtils";
import {
bookTimeSlot,
fillStripeTestCheckout,
localize,
selectFirstAvailableTimeSlotNextMonth,
} from "./lib/testUtils";
test.afterEach(({ users }) => users.deleteAll());
@@ -21,18 +27,20 @@ test.describe("Managed Event Types tests", () => {
await test.step("Managed event option exists for team admin", async () => {
// Filling team creation form wizard
await page.locator('input[name="name"]').waitFor();
await page.locator('input[name="name"]').fill(`${adminUser.username}'s Team`);
await page.locator("text=Continue").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i);
await page.click("[type=submit]");
// TODO: Figure out a way to make this more reliable
// eslint-disable-next-line playwright/no-conditional-in-test
if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
await page.getByTestId("new-member-button").click();
await page.locator('[placeholder="email\\@example\\.com"]').fill(`${memberUser.username}@example.com`);
await page.getByTestId("invite-new-member-button").click();
// wait for the second member to be added to the pending-member-list.
await page.getByTestId("pending-member-list").locator("li:nth-child(2)").waitFor();
// and publish
await page.locator("text=Publish team").click();
await page.waitForURL("/settings/teams/**");
await page.locator("[data-testid=publish-button]").click();
await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
// Going to create an event type
await page.goto("/event-types");
await page.getByTestId("new-event-type").click();
@@ -1,20 +1,20 @@
import { expect } from "@playwright/test";
import { test } from "../lib/fixtures";
import { getInviteLink } from "../lib/testUtils";
import { expectInvitationEmailToBeReceived } from "./expects";
test.describe.configure({ mode: "parallel" });
test.afterEach(async ({ users, emails, clipboard }) => {
clipboard.reset();
test.afterEach(async ({ users, emails }) => {
await users.deleteAll();
emails?.deleteAll();
});
test.describe("Organization", () => {
test("Invitation (non verified)", async ({ browser, page, users, emails, clipboard }) => {
test("Invitation (non verified)", async ({ browser, page, users, emails }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true });
const { team: org } = await orgOwner.getOrg();
const { team: org } = await orgOwner.getOrgMembership();
await orgOwner.apiLogin();
await page.goto("/settings/organizations/members");
await page.waitForLoadState("networkidle");
@@ -69,9 +69,8 @@ test.describe("Organization", () => {
// Get the invite link
await page.locator('button:text("Add")').click();
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
const inviteLink = await clipboard.get();
await page.waitForLoadState("networkidle");
const inviteLink = await getInviteLink(page);
// Follow invite link in new window
const context = await browser.newContext();
const inviteLinkPage = await context.newPage();
@@ -92,13 +91,13 @@ test.describe("Organization", () => {
test("Invitation (verified)", async ({ browser, page, users, emails }) => {
const orgOwner = await users.create(undefined, { hasTeam: true, isOrg: true, isOrgVerified: true });
const { team: org } = await orgOwner.getOrg();
const { team: org } = await orgOwner.getOrgMembership();
await orgOwner.apiLogin();
await page.goto("/settings/organizations/members");
await page.waitForLoadState("networkidle");
await test.step("To the organization by email (internal user)", async () => {
const invitedUserEmail = `rick@example.com`;
const invitedUserEmail = `rick-${Date.now()}@example.com`;
await page.locator('button:text("Add")').click();
await page.locator('input[name="inviteUser"]').fill(invitedUserEmail);
await page.locator('button:text("Send invite")').click();
@@ -110,7 +109,7 @@ test.describe("Organization", () => {
`${org.name}'s admin invited you to join the organization ${org.name} on Cal.com`
);
// Check newly invited member exists and is pending
// Check newly invited member exists and is not pending
await expect(
page.locator(`[data-testid="email-${invitedUserEmail.replace("@", "")}-pending"]`)
).toHaveCount(0);
+231
View File
@@ -0,0 +1,231 @@
import { expect } from "@playwright/test";
import { randomBytes } from "crypto";
import { APP_NAME, IS_PREMIUM_USERNAME_ENABLED, IS_MAILHOG_ENABLED } from "@calcom/lib/constants";
import { test } from "./lib/fixtures";
import { getEmailsReceivedByUser } from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.describe("Signup Flow Test", async () => {
test.beforeEach(async ({ features }) => {
features.reset(); // This resets to the inital state not an empt yarray
});
test.afterAll(async ({ users }) => {
await users.deleteAll();
});
test("Username is taken", async ({ page, users }) => {
// log in trail user
await test.step("Sign up", async () => {
await users.create({
username: "pro",
});
await page.goto("/signup");
const alertMessage = "Username or email is already taken";
// Fill form
await page.locator('input[name="username"]').fill("pro");
await page.locator('input[name="email"]').fill("pro@example.com");
await page.locator('input[name="password"]').fill("Password99!");
// Submit form
await page.click('button[type="submit"]');
const alert = await page.waitForSelector('[data-testid="alert"]');
const alertMessageInner = await alert.innerText();
expect(alertMessage).toBeDefined();
expect(alertMessageInner).toContain(alertMessageInner);
});
});
test("Email is taken", async ({ page, users }) => {
// log in trail user
await test.step("Sign up", async () => {
const user = await users.create({
username: "pro",
});
await page.goto("/signup");
const alertMessage = "Username or email is already taken";
// Fill form
await page.locator('input[name="username"]').fill("randomuserwhodoesntexist");
await page.locator('input[name="email"]').fill(user.email);
await page.locator('input[name="password"]').fill("Password99!");
// Submit form
await page.click('button[type="submit"]');
const alert = await page.waitForSelector('[data-testid="alert"]');
const alertMessageInner = await alert.innerText();
expect(alertMessage).toBeDefined();
expect(alertMessageInner).toContain(alertMessageInner);
});
});
test("Premium Username Flow - creates stripe checkout", async ({ page, users, prisma }) => {
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!IS_PREMIUM_USERNAME_ENABLED, "Only run on Cal.com");
const userToCreate = users.buildForSignup({
username: "rock",
password: "Password99!",
});
// Ensure the premium username is available
await prisma.user.deleteMany({ where: { username: "rock" } });
// Signup with premium username name
await page.goto("/signup");
// Fill form
await page.locator('input[name="username"]').fill("rock");
await page.locator('input[name="email"]').fill(userToCreate.email);
await page.locator('input[name="password"]').fill(userToCreate.password);
await page.click('button[type="submit"]');
// Check that stripe checkout is present
const expectedUrl = "https://checkout.stripe.com";
await page.waitForURL((url) => url.href.startsWith(expectedUrl));
const url = page.url();
// Check that the URL matches the expected URL
expect(url).toContain(expectedUrl);
// TODO: complete the stripe checkout flow
});
test("Signup with valid (non premium) username", async ({ page, users, features }) => {
const userToCreate = users.buildForSignup({
username: "rick-jones",
password: "Password99!",
});
await page.goto("/signup");
// Fill form
await page.locator('input[name="username"]').fill(userToCreate.username);
await page.locator('input[name="email"]').fill(userToCreate.email);
await page.locator('input[name="password"]').fill(userToCreate.password);
await page.click('button[type="submit"]');
await page.waitForLoadState("networkidle");
// Find the newly created user and add it to the fixture store
const newUser = await users.set(userToCreate.email);
expect(newUser).not.toBeNull();
// Check that the URL matches the expected URL
expect(page.url()).toContain("/auth/verify-email");
});
test("Signup fields prefilled with query params", async ({ page, users }) => {
const signupUrlWithParams = "/signup?username=rick-jones&email=rick-jones%40example.com";
await page.goto(signupUrlWithParams);
// Fill form
const usernameInput = page.locator('input[name="username"]');
const emailInput = page.locator('input[name="email"]');
expect(await usernameInput.inputValue()).toBe("rick-jones");
expect(await emailInput.inputValue()).toBe("rick-jones@example.com");
});
test("Signup with token prefils correct fields", async ({ page, users, prisma }) => {
//Create a user and create a token
const token = randomBytes(32).toString("hex");
const userToCreate = users.buildForSignup({
username: "rick-team",
});
const createdtoken = await prisma.verificationToken.create({
data: {
identifier: userToCreate.email,
token,
expires: new Date(new Date().setHours(168)), // +1 week
team: {
create: {
name: "Rick's Team",
slug: `${userToCreate.username}-team`,
},
},
},
});
// create a user with the same email as the token
const rickTeamUser = await prisma.user.create({
data: {
email: userToCreate.email,
username: userToCreate.username,
},
});
// Create provitional membership
await prisma.membership.create({
data: {
teamId: createdtoken.teamId ?? -1,
userId: rickTeamUser.id,
role: "ADMIN",
accepted: false,
},
});
const signupUrlWithToken = `/signup?token=${token}`;
await page.goto(signupUrlWithToken);
const usernameField = page.locator('input[name="username"]');
const emailField = page.locator('input[name="email"]');
expect(await usernameField.inputValue()).toBe(userToCreate.username);
expect(await emailField.inputValue()).toBe(userToCreate.email);
// Cleanup specific to this test
// Clean up the user and token
await prisma.user.deleteMany({ where: { email: userToCreate.email } });
await prisma.verificationToken.deleteMany({ where: { identifier: createdtoken.identifier } });
await prisma.team.deleteMany({ where: { id: createdtoken.teamId! } });
});
test("Email verification sent if enabled", async ({ page, prisma, emails, users, features }) => {
const EmailVerifyFlag = features.get("email-verification")?.enabled;
// eslint-disable-next-line playwright/no-skipped-test
test.skip(!EmailVerifyFlag || !IS_MAILHOG_ENABLED, "Skipping check - Email verify disabled");
// Ensure email verification before testing (TODO: this could break other tests but we can fix that later)
await prisma.feature.update({
where: { slug: "email-verification" },
data: { enabled: true },
});
const userToCreate = users.buildForSignup({
username: "email-verify",
password: "Password99!",
});
await page.goto("/signup");
// Fill form
await page.locator('input[name="username"]').fill(userToCreate.username);
await page.locator('input[name="email"]').fill(userToCreate.email);
await page.locator('input[name="password"]').fill(userToCreate.password);
await page.click('button[type="submit"]');
await page.waitForURL((url) => url.pathname.includes("/auth/verify-email"));
// Find the newly created user and add it to the fixture store
const newUser = await users.set(userToCreate.email);
expect(newUser).not.toBeNull();
const receivedEmails = await getEmailsReceivedByUser({
emails,
userEmail: userToCreate.email,
});
// We need to wait for emails to be sent
// eslint-disable-next-line playwright/no-wait-for-timeout
await page.waitForTimeout(5000);
expect(receivedEmails?.total).toBe(1);
const verifyEmail = receivedEmails?.items[0];
expect(verifyEmail?.subject).toBe(`${APP_NAME}: Verify your account`);
});
});
@@ -3,19 +3,18 @@ import { expect } from "@playwright/test";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { test } from "../lib/fixtures";
import { localize } from "../lib/testUtils";
import { localize, getInviteLink } from "../lib/testUtils";
import { expectInvitationEmailToBeReceived } from "./expects";
test.describe.configure({ mode: "parallel" });
test.afterEach(async ({ users, emails, clipboard }) => {
clipboard.reset();
test.afterEach(async ({ users, emails }) => {
await users.deleteAll();
emails?.deleteAll();
});
test.describe("Team", () => {
test("Invitation (non verified)", async ({ browser, page, users, emails, clipboard }) => {
test("Invitation (non verified)", async ({ browser, page, users, emails }) => {
const t = await localize("en");
const teamOwner = await users.create(undefined, { hasTeam: true });
const { team } = await teamOwner.getFirstTeam();
@@ -76,8 +75,7 @@ test.describe("Team", () => {
});
await page.locator(`button:text("${t("add")}")`).click();
await page.locator(`[data-testid="copy-invite-link-button"]`).click();
const inviteLink = await clipboard.get();
await page.waitForLoadState("networkidle");
const inviteLink = await getInviteLink(page);
const context = await browser.newContext();
const inviteLinkPage = await context.newPage();
+86 -83
View File
@@ -1,34 +1,32 @@
import type { Page } from "@playwright/test";
import { expect } from "@playwright/test";
import { IS_TEAM_BILLING_ENABLED } from "@calcom/lib/constants";
import { prisma } from "@calcom/prisma";
import { MembershipRole, SchedulingType } from "@calcom/prisma/enums";
import { test } from "./lib/fixtures";
import { bookTimeSlot, selectFirstAvailableTimeSlotNextMonth, testName, todo } from "./lib/testUtils";
import {
bookTimeSlot,
fillStripeTestCheckout,
selectFirstAvailableTimeSlotNextMonth,
testName,
todo,
} from "./lib/testUtils";
test.describe.configure({ mode: "parallel" });
test.describe("Teams - NonOrg", () => {
test.afterEach(({ users }) => users.deleteAll());
test("Can create teams via Wizard", async ({ page, users }) => {
const user = await users.create();
const inviteeEmail = `${user.username}+invitee@example.com`;
await user.apiLogin();
await page.goto("/teams");
await test.step("Can create team", async () => {
// Click text=Create Team
await page.locator("text=Create Team").click();
await page.waitForURL("/settings/teams/new");
// Fill input[name="name"]
await page.locator('input[name="name"]').fill(`${user.username}'s Team`);
// Click text=Continue
await page.locator("text=Continue").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i);
await page.waitForSelector('[data-testid="pending-member-list"]');
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(1);
});
test("Team Onboarding Invite Members", async ({ page, users }) => {
const user = await users.create(undefined, { hasTeam: true });
const { team } = await user.getFirstTeam();
const inviteeEmail = `${user.username}+invitee@example.com`;
await user.apiLogin();
page.goto(`/settings/teams/${team.id}/onboard-members`);
await test.step("Can add members", async () => {
// Click [data-testid="new-member-button"]
@@ -50,9 +48,9 @@ test.describe("Teams - NonOrg", () => {
await prisma.user.delete({ where: { email: inviteeEmail } });
});
await test.step("Can publish team", async () => {
await page.locator("text=Publish team").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
await test.step("Finishing brings you to team profile page", async () => {
await page.locator("[data-testid=publish-button]").click();
await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
});
await test.step("Can disband team", async () => {
@@ -66,7 +64,6 @@ test.describe("Teams - NonOrg", () => {
});
test("Can create a booking for Collective EventType", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [
{ name: "teammate-1" },
{ name: "teammate-2" },
@@ -74,11 +71,14 @@ test.describe("Teams - NonOrg", () => {
{ name: "teammate-4" },
];
const owner = await users.create(ownerObj, {
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.COLLECTIVE,
});
const owner = await users.create(
{ username: "pro-user", name: "pro-user" },
{
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.COLLECTIVE,
}
);
const { team } = await owner.getFirstTeam();
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
@@ -102,18 +102,20 @@ test.describe("Teams - NonOrg", () => {
});
test("Can create a booking for Round Robin EventType", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [
{ name: "teammate-1" },
{ name: "teammate-2" },
{ name: "teammate-3" },
{ name: "teammate-4" },
];
const owner = await users.create(ownerObj, {
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.ROUND_ROBIN,
});
const owner = await users.create(
{ username: "pro-user", name: "pro-user" },
{
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.ROUND_ROBIN,
}
);
const { team } = await owner.getFirstTeam();
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
@@ -134,7 +136,7 @@ test.describe("Teams - NonOrg", () => {
// Anyone of the teammates could be the Host of the booking.
const chosenUser = await page.getByTestId("booking-host-name").textContent();
expect(chosenUser).not.toBeNull();
expect(teamMatesObj.concat([{ name: ownerObj.name }]).some(({ name }) => name === chosenUser)).toBe(true);
expect(teamMatesObj.concat([{ name: owner.name! }]).some(({ name }) => name === chosenUser)).toBe(true);
// TODO: Assert whether the user received an email
});
@@ -164,21 +166,18 @@ test.describe("Teams - NonOrg", () => {
await page.goto("/settings/teams/new");
// Fill input[name="name"]
await page.locator('input[name="name"]').fill(uniqueName);
await page.locator("text=Continue").click();
await expect(page.locator("[data-testid=alert]")).toBeVisible();
await page.click("[type=submit]");
// cleanup
const org = await owner.getOrg();
const org = await owner.getOrgMembership();
await prisma.team.delete({ where: { id: org.teamId } });
}
});
test("Can create team with same name as user", async ({ page, users }) => {
const user = await users.create();
// Name to be used for both user and team
const uniqueName = "test-unique-name";
const ownerObj = { username: uniqueName, name: uniqueName, useExactUsername: true };
const user = await users.create(ownerObj);
const uniqueName = user.username!;
await user.apiLogin();
await page.goto("/teams");
@@ -189,11 +188,14 @@ test.describe("Teams - NonOrg", () => {
// Fill input[name="name"]
await page.locator('input[name="name"]').fill(uniqueName);
// Click text=Continue
await page.locator("text=Continue").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i);
await page.click("[type=submit]");
// TODO: Figure out a way to make this more reliable
// eslint-disable-next-line playwright/no-conditional-in-test
if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
// Click text=Continue
await page.locator("text=Publish team").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
await page.locator("[data-testid=publish-button]").click();
await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
});
await test.step("Can access user and team with same slug", async () => {
@@ -210,13 +212,11 @@ test.describe("Teams - NonOrg", () => {
await expect(page.locator("[data-testid=name-title]")).toHaveText(uniqueName);
// cleanup team
const team = await prisma.team.findFirst({ where: { slug: uniqueName } });
await prisma.team.delete({ where: { id: team?.id } });
await prisma.team.deleteMany({ where: { slug: uniqueName } });
});
});
test("Can create a private team", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [
{ name: "teammate-1" },
{ name: "teammate-2" },
@@ -224,11 +224,14 @@ test.describe("Teams - NonOrg", () => {
{ name: "teammate-4" },
];
const owner = await users.create(ownerObj, {
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.COLLECTIVE,
});
const owner = await users.create(
{ username: "pro-user", name: "pro-user" },
{
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.COLLECTIVE,
}
);
await owner.apiLogin();
const { team } = await owner.getFirstTeam();
@@ -278,45 +281,43 @@ test.describe("Teams - Org", () => {
// Fill input[name="name"]
await page.locator('input[name="name"]').fill(`${user.username}'s Team`);
// Click text=Continue
await page.locator("text=Continue").click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/onboard-members$/i);
await page.click("[type=submit]");
// TODO: Figure out a way to make this more reliable
// eslint-disable-next-line playwright/no-conditional-in-test
if (IS_TEAM_BILLING_ENABLED) await fillStripeTestCheckout(page);
await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/onboard-members.*$/i);
await page.waitForSelector('[data-testid="pending-member-list"]');
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(1);
expect(await page.getByTestId("pending-member-item").count()).toBe(1);
});
await test.step("Can add members", async () => {
// Click [data-testid="new-member-button"]
await page.locator('[data-testid="new-member-button"]').click();
// Fill [placeholder="email\@example\.com"]
await page.getByTestId("new-member-button").click();
await page.locator('[placeholder="email\\@example\\.com"]').fill(inviteeEmail);
// Click [data-testid="invite-new-member-button"]
await page.locator('[data-testid="invite-new-member-button"]').click();
await page.getByTestId("invite-new-member-button").click();
await expect(page.locator(`li:has-text("${inviteeEmail}")`)).toBeVisible();
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(2);
expect(await page.getByTestId("pending-member-item").count()).toBe(2);
});
await test.step("Can remove members", async () => {
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(2);
const lastRemoveMemberButton = page.locator('[data-testid="remove-member-button"]').last();
expect(await page.getByTestId("pending-member-item").count()).toBe(2);
const lastRemoveMemberButton = page.getByTestId("remove-member-button").last();
await lastRemoveMemberButton.click();
await page.waitForLoadState("networkidle");
expect(await page.locator('[data-testid="pending-member-item"]').count()).toBe(1);
expect(await page.getByTestId("pending-member-item").count()).toBe(1);
// Cleanup here since this user is created without our fixtures.
await prisma.user.delete({ where: { email: inviteeEmail } });
});
await test.step("Can finish team creation", async () => {
await page.locator("text=Finish").click();
await page.waitForURL("/settings/teams");
await page.getByTestId("publish-button").click();
await expect(page).toHaveURL(/\/settings\/teams\/(\d+)\/profile$/i);
});
await test.step("Can disband team", async () => {
await page.locator('[data-testid="team-list-item-link"]').click();
await page.waitForURL(/\/settings\/teams\/(\d+)\/profile$/i);
await page.locator("text=Disband Team").click();
await page.locator("text=Yes, disband team").click();
await page.getByTestId("disband-team-button").click();
await page.getByTestId("dialog-confirmation").click();
await page.waitForURL("/teams");
expect(await page.locator(`text=${user.username}'s Team`).count()).toEqual(0);
});
@@ -361,13 +362,13 @@ test.describe("Teams - Org", () => {
await page.goto(`/team/${team.slug}/${teamEventSlug}`);
await selectFirstAvailableTimeSlotNextMonth(page);
await bookTimeSlot(page);
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
await expect(page.getByTestId("success-page")).toBeVisible();
// The title of the booking
const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`;
await expect(page.locator("[data-testid=booking-title]")).toHaveText(BookingTitle);
await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
// The booker should be in the attendee list
await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName);
await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
// All the teammates should be in the booking
for (const teammate of teamMatesObj.concat([{ name: owner.name || "" }])) {
@@ -380,18 +381,20 @@ test.describe("Teams - Org", () => {
});
test("Can create a booking for Round Robin EventType", async ({ page, users }) => {
const ownerObj = { username: "pro-user", name: "pro-user" };
const teamMatesObj = [
{ name: "teammate-1" },
{ name: "teammate-2" },
{ name: "teammate-3" },
{ name: "teammate-4" },
];
const owner = await users.create(ownerObj, {
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.ROUND_ROBIN,
});
const owner = await users.create(
{ username: "pro-user", name: "pro-user" },
{
hasTeam: true,
teammates: teamMatesObj,
schedulingType: SchedulingType.ROUND_ROBIN,
}
);
const { team } = await owner.getFirstTeam();
const { title: teamEventTitle, slug: teamEventSlug } = await owner.getFirstTeamEvent(team.id);
@@ -402,17 +405,17 @@ test.describe("Teams - Org", () => {
await expect(page.locator("[data-testid=success-page]")).toBeVisible();
// The person who booked the meeting should be in the attendee list
await expect(page.locator(`[data-testid="attendee-name-${testName}"]`)).toHaveText(testName);
await expect(page.getByTestId(`attendee-name-${testName}`)).toHaveText(testName);
// The title of the booking
const BookingTitle = `${teamEventTitle} between ${team.name} and ${testName}`;
await expect(page.locator("[data-testid=booking-title]")).toHaveText(BookingTitle);
await expect(page.getByTestId("booking-title")).toHaveText(BookingTitle);
// Since all the users have the same leastRecentlyBooked value
// Anyone of the teammates could be the Host of the booking.
const chosenUser = await page.getByTestId("booking-host-name").textContent();
expect(chosenUser).not.toBeNull();
expect(teamMatesObj.concat([{ name: ownerObj.name }]).some(({ name }) => name === chosenUser)).toBe(true);
expect(teamMatesObj.concat([{ name: owner.name! }]).some(({ name }) => name === chosenUser)).toBe(true);
// TODO: Assert whether the user received an email
});
});
+5 -5
View File
@@ -45,7 +45,7 @@ test.describe("Unpublished", () => {
test("Organization profile", async ({ users, page }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
const { team: org } = await owner.getOrg();
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
await page.goto(`/org/${requestedSlug}`);
await page.waitForLoadState("networkidle");
@@ -62,7 +62,7 @@ test.describe("Unpublished", () => {
isOrg: true,
hasSubteam: true,
});
const { team: org } = await owner.getOrg();
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
const [{ slug: subteamSlug }] = org.children as { slug: string }[];
await page.goto(`/org/${requestedSlug}/team/${subteamSlug}`);
@@ -80,7 +80,7 @@ test.describe("Unpublished", () => {
isOrg: true,
hasSubteam: true,
});
const { team: org } = await owner.getOrg();
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
const [{ slug: subteamSlug, id: subteamId }] = org.children as { slug: string; id: number }[];
const { slug: subteamEventSlug } = await owner.getFirstTeamEvent(subteamId);
@@ -95,7 +95,7 @@ test.describe("Unpublished", () => {
test("Organization user", async ({ users, page }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
const { team: org } = await owner.getOrg();
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
await page.goto(`/org/${requestedSlug}/${owner.username}`);
await page.waitForLoadState("networkidle");
@@ -107,7 +107,7 @@ test.describe("Unpublished", () => {
test("Organization user event-type", async ({ users, page }) => {
const owner = await users.create(undefined, { hasTeam: true, isUnpublished: true, isOrg: true });
const { team: org } = await owner.getOrg();
const { team: org } = await owner.getOrgMembership();
const { requestedSlug } = org.metadata as { requestedSlug: string };
const [{ slug: ownerEventType }] = owner.eventTypes;
await page.goto(`/org/${requestedSlug}/${owner.username}/${ownerEventType}`);
+3
View File
@@ -0,0 +1,3 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M8.31877 15.36C4.26002 15.36 0.95752 12.0588 0.95752 8.00001C0.95752 3.94126 4.26002 0.640015 8.31877 0.640015C10.1575 0.640015 11.9175 1.32126 13.2763 2.55876L13.5238 2.78501L11.0963 5.21251L10.8713 5.02001C10.1588 4.41001 9.25252 4.07376 8.31877 4.07376C6.15377 4.07376 4.39127 5.83501 4.39127 8.00001C4.39127 10.165 6.15377 11.9263 8.31877 11.9263C9.88002 11.9263 11.1138 11.1288 11.695 9.77001H7.99877V6.45626L15.215 6.46626L15.2688 6.72001C15.645 8.50626 15.3438 11.1338 13.8188 13.0138C12.5563 14.57 10.7063 15.36 8.31877 15.36Z" fill="currentColor"/>
</svg>

After

Width:  |  Height:  |  Size: 670 B

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 347 KiB

+17
View File
@@ -0,0 +1,17 @@
<svg width="115" height="54" viewBox="0 0 115 54" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_12158_115893)">
<path d="M8.95263 15.0632L3.41053 17.0526L3.55263 11.1553L0 6.53684L5.61316 4.83158L8.95263 0L12.2921 4.83158L17.9053 6.53684L14.3526 11.1553L14.4947 17.0526L8.95263 15.0632Z" fill="#FF492C"/>
<path d="M32.9684 15.0632L27.4263 17.0526L27.6394 11.1553L24.0157 6.53684L29.6289 4.83158L32.9684 0L36.3079 4.83158L41.921 6.53684L38.3684 11.1553L38.5105 17.0526L32.9684 15.0632Z" fill="#FF492C"/>
<path d="M56.9842 15.0632L51.4421 17.0526L51.6553 11.1553L48.0316 6.53684L53.7158 4.83158L56.9842 0L60.3237 4.83158L65.9369 6.53684L62.3843 11.1553L62.5264 17.0526L56.9842 15.0632Z" fill="#FF492C"/>
<path d="M81 15.0632L75.4579 17.0526L75.671 11.1553L72.0474 6.53684L77.7316 4.83158L81 0L84.3395 4.83158L90.0237 6.53684L86.4 11.1553L86.5421 17.0526L81 15.0632Z" fill="#FF492C"/>
<path d="M105.016 0L101.747 4.83158L96.0631 6.53684L99.6868 11.1553L99.4736 17.0526L105.016 15.0632V0Z" fill="#FF492C"/>
<path d="M110.416 11.1553L114.039 6.53684L108.355 4.83158L105.016 0V15.0632L110.558 17.0526L110.416 11.1553Z" fill="white"/>
<path d="M28.4682 40.2593C28.4682 47.4831 22.6148 53.3365 15.391 53.3365C8.1672 53.3365 2.31384 47.4831 2.31384 40.2593C2.31384 33.0355 8.1672 27.1821 15.391 27.1821C22.6148 27.1821 28.4682 33.0407 28.4682 40.2593Z" fill="#FF492C"/>
<path d="M21.0507 38.1252H17.6716V37.9683C17.6716 37.3929 17.7866 36.9168 18.0168 36.5454C18.247 36.1689 18.6445 35.8393 19.2199 35.5464L19.4815 35.4156C19.947 35.1802 20.0673 34.9762 20.0673 34.7356C20.0673 34.4479 19.8163 34.2386 19.4135 34.2386C18.9322 34.2386 18.5713 34.4897 18.3202 34.9971L17.6716 34.3485C17.8128 34.0451 18.043 33.8045 18.3464 33.611C18.655 33.4174 18.995 33.3233 19.3664 33.3233C19.832 33.3233 20.2347 33.4436 20.5642 33.6947C20.9043 33.9458 21.0716 34.2909 21.0716 34.7251C21.0716 35.4208 20.6793 35.8445 19.947 36.2212L19.5338 36.4304C19.0944 36.6501 18.8799 36.8488 18.8171 37.1993H21.0507V38.1252ZM20.7526 39.1923H17.0544L15.2079 42.3936H18.9061L20.7578 45.5948L22.6043 42.3936L20.7526 39.1923ZM15.527 44.533C13.173 44.533 11.2585 42.6185 11.2585 40.2646C11.2585 37.9107 13.173 35.9962 15.527 35.9962L16.9916 32.9362C16.5156 32.842 16.0291 32.7949 15.527 32.7949C11.3998 32.7949 8.052 36.1427 8.052 40.2646C8.052 44.3918 11.3945 47.7396 15.527 47.7396C17.1694 47.7396 18.6916 47.206 19.9261 46.3063L18.3045 43.5026C17.5618 44.1407 16.5888 44.533 15.527 44.533Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_12158_115893">
<rect width="114.395" height="54" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.6 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.3 KiB

@@ -0,0 +1,9 @@
<svg width="161" height="75" viewBox="0 0 161 75" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12.6 21.2L4.8 24L5 15.7L0 9.2L7.9 6.8L12.6 0L17.3 6.8L25.2 9.2L20.2 15.7L20.4 24L12.6 21.2Z" fill="#E9A944"/>
<path d="M46.4 21.2L38.6 24L38.9 15.7L33.8 9.2L41.7 6.8L46.4 0L51.1 6.8L59 9.2L54 15.7L54.2 24L46.4 21.2Z" fill="#E9A944"/>
<path d="M80.2 21.2L72.4 24L72.7 15.7L67.6 9.2L75.6 6.8L80.2 0L84.9 6.8L92.8 9.2L87.8 15.7L88 24L80.2 21.2Z" fill="#E9A944"/>
<path d="M114 21.2L106.2 24L106.5 15.7L101.4 9.2L109.4 6.8L114 0L118.7 6.8L126.7 9.2L121.6 15.7L121.8 24L114 21.2Z" fill="#E9A944"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M40 56.5C40 66.7175 31.7175 75 21.5 75C11.2825 75 3 66.7175 3 56.5C3 46.2825 11.2825 38 21.5 38C31.7175 38 40 46.2825 40 56.5Z" fill="#FF6154"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M23.967 56.5H18.725V50.95H23.967C24.703 50.95 25.4088 51.2424 25.9292 51.7628C26.4496 52.2832 26.742 52.989 26.742 53.725C26.742 54.461 26.4496 55.1668 25.9292 55.6872C25.4088 56.2076 24.703 56.5 23.967 56.5ZM23.967 47.25H15.025V65.75H18.725V60.2H23.967C25.6843 60.2 27.3312 59.5178 28.5455 58.3035C29.7598 57.0892 30.442 55.4423 30.442 53.725C30.442 52.0077 29.7598 50.3608 28.5455 49.1465C27.3312 47.9322 25.6843 47.25 23.967 47.25Z" fill="white"/>
<path d="M147.6 21.2L139.8 24L140.1 15.7L135 9.2L143 6.8L147.6 0L152.3 6.8L160.3 9.2L155.2 15.7L155.4 24L147.6 21.2Z" fill="#E9A944"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 9.8 KiB

+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "سيتم ملء \"اسم المستخدم\" باسم المستخدم للأعضاء المعينين",
"assign_to": "تعيين إلى",
"add_members": "إضافة أعضاء...",
"count_members_one": "{{count}} عضو",
"count_members_other": "{{count}} من الأعضاء",
"no_assigned_members": "لا يوجد أعضاء معينين",
"assigned_to": "تم التعيين إلى",
"start_assigning_members_above": "بدء تعيين الأعضاء أعلاه",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر Tandem.",
"cal_provide_video_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Daily.",
"cal_provide_jitsi_meeting_url": "سنوفر لك رابطًا للاجتماع عبر Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Huddle01 web3.",
"cal_provide_huddle01_meeting_url": "سيوفر {{appName}} رابطًا للاجتماع عبر الفيديو على Huddle01.",
"cal_provide_teams_meeting_url": "سيوفر {{appName}} عنوان URL لاجتماعات MS Teams. ملاحظة: يجب أن يكون لديك حساب عمل أو حساب مدرسة",
"require_payment": "يلزم الدفع",
"you_need_to_add_a_name": "تحتاج إلى إضافة اسم",
@@ -849,7 +847,6 @@
"next_step": "تخطي الخطوة",
"prev_step": "الخطوة السابقة",
"install": "تثبيت",
"install_paid_app": "اشتراك",
"installed": "تم التثبيت",
"active_install_one": "{{count}} تثبيت نشط",
"active_install_other": "{{count}} تثبيت نشط",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "إضافة تطبيق تحليلات لصفحات الحجز الخاصة بك",
"no_category_apps_description_automation": "إضافة تطبيق أتمتة لاستخدامه",
"no_category_apps_description_other": "أضف أي نوع آخر من التطبيقات للقيام بأي شيء",
"no_category_apps_description_web3": "إضافة تطبيق web3 لصفحات الحجز لديك",
"no_category_apps_description_messaging": "إضافة تطبيق مراسلة لإعداد الإشعارات والتذكيرات المخصصة",
"no_category_apps_description_crm": "إضافة تطبيق CRM لتتبع من قابلته",
"installed_app_calendar_description": "قم بتعيين التقويمات للتحقق من وجود تعارضات لمنع الحجوزات المزدوجة.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "جميع تطبيقاتك المثبتة من الفئات الأخرى.",
"installed_app_conferencing_description": "تكوين تطبيقات المؤتمرات المراد استخدامها",
"installed_app_automation_description": "تهيئة تطبيقات الأتمتة التي سيتم استخدامها",
"installed_app_web3_description": "تهيئة تطبيقات web3 التي يمكن استخدامها لصفحات الحجز لديك",
"installed_app_messaging_description": "تكوين تطبيقات المراسلة المراد استخدامها لإعداد الإشعارات والتذكيرات المخصصة",
"installed_app_crm_description": "تهيئة تطبيقات CRM التي يمكن استخدامها لتتبع من قابلته",
"analytics": "التحليلات",
@@ -1153,6 +1148,7 @@
"active_on": "إجراء في",
"workflow_updated_successfully": "تم تحديث سير العمل {{workflowName}} بنجاح",
"premium_to_standard_username_description": "هذا اسم مستخدم قياسي، سوف ينقلك هذا التحديث إلى صفحة الفوترة لخفض المستوى.",
"premium_username": "هذا اسم مستخدم مميز، احصل على اسم المستخدم لك مقابل {{price}}",
"current": "الحالي",
"premium": "مميز",
"standard": "قياسي",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "الحاضر",
"event_date_variable": "تاريخ الحدث",
"event_time_variable": "وقت الحدث",
"timezone_variable": "المنطقة الزمنية",
"location_variable": "الموقع",
"additional_notes_variable": "ملاحظات إضافية",
"organizer_name_variable": "اسم المنظم",
"app_upgrade_description": "لاستخدام هذه الميزة، تحتاج إلى الترقية إلى حساب Pro.",
"invalid_number": "رقم الهاتف غير صالح",
@@ -1309,7 +1302,6 @@
"profile_picture": "صورة الملف الشخصي",
"upload": "تحميل",
"add_profile_photo": "إضافة صورة الملف الشخصي",
"web3": "Web3",
"token_address": "عنوان الرمز المميز",
"blockchain": "سلسلة الكتل",
"old_password": "كلمة مرور قديمة",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "توصيل تطبيقات الأتمتة",
"connect_analytics_apps": "توصيل تطبيقات التحليلات",
"connect_other_apps": "ربط تطبيقات أخرى",
"connect_web3_apps": "ربط تطبيقات web3",
"connect_messaging_apps": "ربط تطبيقات المراسلة",
"connect_crm_apps": "ربط تطبيقات CRM",
"current_step_of_total": "الخطوة {{currentStep}} من {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "أنت",
"resend_email": "إعادة إرسال رسالة البريد الإلكتروني",
"member_already_invited": "تمت دعوة العضو بالفعل",
"already_in_use_error": "اسم المستخدم مستخدم مسبقاً",
"enter_email_or_username": "أدخل بريد إلكتروني أو اسم مستخدم",
"team_name_taken": "هذا الاسم مأخوذ بالفعل",
"must_enter_team_name": "يجب إدخال اسم فريق",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "يمكن أن تمنعك سياسة Google الجديدة للرسائل غير المرغوب فيها من تلقي أي إشعارات بالبريد الإلكتروني والتقويم حول هذا الحجز.",
"resolve": "حل",
"no_organization_slug": "حدث خطأ أثناء إنشاء فرق لهذه المنظمة. الكلمة اللطيفة للرابط مفقودة.",
"org_name": "اسم المنظمة",
"org_url": "رابط المنظمة",
"copy_link_org": "نسخ الرابط إلى المنظمة",
"404_the_org": "المنظمة",
"404_the_team": "الفريق",
"404_claim_entity_org": "المطالبة بنطاقك الفرعي لمنظمتك",
"404_claim_entity_team": "انضم لهذا الفريق وابدأ في إدارة الجداول الزمنية بشكل جماعي",
"insights_all_org_filter": "الكل",
"insights_team_filter": "الفريق: {{teamName}}",
"insights_user_filter": "المستخدم: {{userName}}",
"insights_subtitle": "عرض Insights الحجز عبر أحداثك",
@@ -2005,7 +1994,6 @@
"select_date": "اختيار التاريخ",
"see_all_available_times": "رؤية كل الأوقات المتاحة",
"org_team_names_example": "مثال، فريق التسويق",
"org_team_names_example_1": "مثال، فريق التسويق",
"org_team_names_example_2": "مثال، فريق المبيعات",
"org_team_names_example_3": "مثال، فريق التصميم",
"org_team_names_example_4": "مثال، الفريق الهندسي",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "إدراج فعاليات في التقويم",
"oAuth": "OAuth",
"recently_added": "تمت الإضافة مؤخراً",
"connect_all_calendars": "قم بربط جميع التقويمات",
"workflow_automation": "أتمتة سير العمل",
"scheduling_for_your_team": "أتمتة سير العمل",
"no_members_found": "لم يُعثر على أعضاء",
"event_setup_length_error": "إعداد الفعالية: يجب أن تكون المدة لدقيقة على الأقل.",
"availability_schedules": "جدولة التوافر",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "تعديل توافر المستخدم: {{username}}",
"resend_invitation": "إعادة إرسال الدعوة",
"invitation_resent": "تم إعادة إرسال الدعوة.",
"saml_sso": "SAML",
"add_client": "إضافة عميل",
"copy_client_secret_info": "لن تتمكن من مطالعة السر بعد نسخه بعد الآن",
"add_new_client": "إضافة عميل جديد",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "قفل المنطقة الزمنية في صفحة الحجز",
"description_lock_timezone_toggle_on_booking_page": "تقفل المنطقة الزمنية على صفحة الحجز، وهذا مفيد للأحداث وجهاً لوجه.",
"extensive_whitelabeling": "عملية انضمام ودعم هندسي مخصصين",
"need_help": "هل تحتاج إلى مساعدة؟",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ أضف السلاسل الجديدة أعلاه هنا ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "„uživatelské jméno“ bude vyplněno uživatelským jménem přiřazených členů",
"assign_to": "Přiřadit k",
"add_members": "Přidat členy...",
"count_members_one": "Počet členů: {{count}}",
"count_members_other": "Počet členů: {{count}}",
"no_assigned_members": "Žádní přiřazení členové",
"assigned_to": "Přiřazeno k",
"start_assigning_members_above": "Začněte přiřazovat členy výše",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} poskytne URL Tandem meetingu.",
"cal_provide_video_meeting_url": "{{appName}} poskytne URL Daily video meetingu.",
"cal_provide_jitsi_meeting_url": "{{appName}} poskytne URL Jitsi Meet video meetingu.",
"cal_provide_huddle01_meeting_url": "{{appName}} poskytne URL Huddle01 web3 video meetingu.",
"cal_provide_huddle01_meeting_url": "{{appName}} poskytne URL Huddle01 video meetingu.",
"cal_provide_teams_meeting_url": "{{appName}} poskytne URL schůzky aplikace MS Teams. POZNÁMKA: MUSÍ SE JEDNAT O PRACOVNÍ NEBO ŠKOLNÍ ÚČET",
"require_payment": "Vyžadovat platbu",
"you_need_to_add_a_name": "Musíte přidat název",
@@ -849,7 +847,6 @@
"next_step": "Přeskočit krok",
"prev_step": "Předchozí krok",
"install": "Nainstalovat",
"install_paid_app": "Odebírat",
"installed": "Nainstalováno",
"active_install_one": "Aktivní instalace: {{count}}",
"active_install_other": "Aktivní instalace: {{count}}",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Přidejte analytickou aplikaci pro vaše rezervační stránky",
"no_category_apps_description_automation": "Přidejte aplikaci pro automatizaci",
"no_category_apps_description_other": "Přidejte jakýkoli jiný typ aplikace pro nejrůznější činnosti",
"no_category_apps_description_web3": "Přidejte aplikaci web3 pro vaše rezervační stránky",
"no_category_apps_description_messaging": "Přidejte aplikaci pro zasílání zpráv a nastavte vlastní oznámení a připomenutí",
"no_category_apps_description_crm": "Přidejte aplikaci CRM, ať jste v obraze, s kým jste se setkali",
"installed_app_calendar_description": "Nastavte si kalendáře, ať můžete kontrolovat konflikty a zabránit tak dvojím rezervacím.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Všechny vaše nainstalované aplikace z ostatních kategorií.",
"installed_app_conferencing_description": "Nakonfigurujte konferenční aplikace, které chcete používat",
"installed_app_automation_description": "Konfigurovat aplikace pro automatizaci",
"installed_app_web3_description": "Nakonfigurujte aplikace web3, které se mají použít pro vaše rezervační stránky",
"installed_app_messaging_description": "Nakonfigurujte aplikace pro zasílání zpráv a nastavte vlastní oznámení a připomenutí",
"installed_app_crm_description": "Nakonfigurujte aplikace CRM, které se mají používat ke sledování toho, s kým jste se setkali",
"analytics": "Analytické nástroje",
@@ -1153,6 +1148,7 @@
"active_on": "Aktivní pro:",
"workflow_updated_successfully": "Pracovní postup {{workflowName}} byl aktualizován",
"premium_to_standard_username_description": "Toto je standardní uživatelské jméno a při aktualizaci přejdete k fakturaci, kde provedete snížení úrovně.",
"premium_username": "Toto je prémiové uživatelské jméno, získejte ho za {{price}}",
"current": "Aktuální",
"premium": "prémiové",
"standard": "standardní",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Jméno účastníka",
"event_date_variable": "Datum události",
"event_time_variable": "Čas události",
"timezone_variable": "Časová zóna",
"location_variable": "Místo",
"additional_notes_variable": "Doplňující poznámky",
"organizer_name_variable": "Jméno organizátora",
"app_upgrade_description": "Pokud chcete použít tuto funkci, musíte provést aktualizaci na účet Pro.",
"invalid_number": "Neplatné telefonní číslo",
@@ -1309,7 +1302,6 @@
"profile_picture": "Profilová fotka",
"upload": "Nahrát",
"add_profile_photo": "Přidat profilovou fotku",
"web3": "Web3",
"token_address": "Adresa tokenu",
"blockchain": "Blockchain",
"old_password": "Staré heslo",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Připojit aplikace pro automatizaci",
"connect_analytics_apps": "Připojit analytické aplikace",
"connect_other_apps": "Propojit další aplikace",
"connect_web3_apps": "Připojení aplikací web3",
"connect_messaging_apps": "Připojení aplikací pro zasílání zpráv",
"connect_crm_apps": "Připojení aplikací CRM",
"current_step_of_total": "Krok {{currentStep}} / {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Vy",
"resend_email": "Znovu odeslat e-mail",
"member_already_invited": "Člen byl už pozván",
"already_in_use_error": "Uživatelské jméno se již používá",
"enter_email_or_username": "Zadejte e-mail nebo uživatelské jméno",
"team_name_taken": "Toto jméno je už obsazeno",
"must_enter_team_name": "Musíte zadat název týmu",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Nové zásady společnosti Google týkající se nevyžádané pošty by mohly zabránit tomu, abyste dostávali e-mailová oznámení a oznámení kalendáře o této rezervaci.",
"resolve": "Vyřešit",
"no_organization_slug": "Při vytváření týmů pro tuto organizaci došlo k chybě. Chybějící slug adresy URL.",
"org_name": "Název organizace",
"org_url": "Adresa URL organizace",
"copy_link_org": "Zkopírovat odkaz na organizaci",
"404_the_org": "Organizace",
"404_the_team": "Tým",
"404_claim_entity_org": "Vyžádejte si subdoménu pro svou organizaci",
"404_claim_entity_team": "Vyžádejte si přístup k tomuto týmu a začněte kolektivně spravovat rozvrhy",
"insights_all_org_filter": "Všechny aplikace",
"insights_team_filter": "Tým: {{teamName}}",
"insights_user_filter": "Uživatel: {{userName}}",
"insights_subtitle": "Zobrazte si Insight rezervací napříč vašimi událostmi",
@@ -2005,7 +1994,6 @@
"select_date": "Vyberte datum",
"see_all_available_times": "Zobrazit všechny dostupné časy",
"org_team_names_example": "Např. marketingový tým",
"org_team_names_example_1": "Např. marketingový tým",
"org_team_names_example_2": "Např. obchodní tým",
"org_team_names_example_3": "Např. designérský tým",
"org_team_names_example_4": "Např. inženýrský tým",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Zahrnout událost kalendáře",
"oAuth": "OAuth",
"recently_added": "Nedávno přidáno",
"connect_all_calendars": "Propojte všechny své kalendáře",
"workflow_automation": "Automatizace pracovních postupů",
"scheduling_for_your_team": "Automatizace pracovních postupů",
"no_members_found": "Nenalezeni žádní členové",
"event_setup_length_error": "Nastavení události: Doba trvání musí být alespoň 1 minuta.",
"availability_schedules": "Plány dostupnosti",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Upravte dostupnost uživatele: {{username}}",
"resend_invitation": "Znovu odeslat pozvánku",
"invitation_resent": "Pozvánka byla odeslána znovu.",
"saml_sso": "SAML",
"add_client": "Přidat klienta",
"copy_client_secret_info": "Po zkopírování již nebude možné tajný klíč zobrazit",
"add_new_client": "Přidat nového klienta",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Uzamčení časového pásma na stránce rezervace",
"description_lock_timezone_toggle_on_booking_page": "Uzamčení časového pásma na stránce rezervace (užitečné pro osobní události).",
"extensive_whitelabeling": "Vyhrazená podpora zaškolovací a inženýrská podpora",
"need_help": "Potřebujete pomoc?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Přidejte své nové řetězce nahoru ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+4 -10
View File
@@ -681,7 +681,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} angiver en URL-adresse for Tandem-mødet.",
"cal_provide_video_meeting_url": "{{appName}} angiver en URL-adresse for videomødet.",
"cal_provide_jitsi_meeting_url": "Vi genererer en Jitsi Meet URL til dig.",
"cal_provide_huddle01_meeting_url": "{{appName}} vil levere en Huddle01 web3 videomøde-URL.",
"cal_provide_huddle01_meeting_url": "{{appName}} vil levere en Huddle01 videomøde-URL.",
"cal_provide_teams_meeting_url": "{{appName}} vil levere en MS Teams møde URL. BEMÆRK: SKAL HA EN ARBEJDS- ELLER SKOLE KONTO",
"require_payment": "Kræv Betaling",
"commission_per_transaction": "provision pr. transaktion",
@@ -717,7 +717,6 @@
"next_step": "Spring trin over",
"prev_step": "Forrige trin",
"install": "Installér",
"install_paid_app": "Abonnér",
"installed": "Installeret",
"active_install_one": "{{count}} aktiv installation",
"active_install_other": "{{count}} aktive installationer",
@@ -771,13 +770,11 @@
"no_category_apps_description_analytics": "Tilføj en analyseapp til dine bookingsider",
"no_category_apps_description_automation": "Tilføj en automatiseringsapp til brug",
"no_category_apps_description_other": "Tilføj enhver anden type app for at gøre alle mulige ting",
"no_category_apps_description_web3": "Tilføj en web3 app til dine bookingsider",
"installed_app_calendar_description": "Indstil kalenderne til at tjekke for konflikter, for at forhindre dobbeltbookinger.",
"installed_app_payment_description": "Indstil hvilke betalingsbehandlingstjenester der skal bruges, når du opkræver betaling fra dine kunder.",
"installed_app_analytics_description": "Indstil hvilke analyseapps der skal bruges til dine bookingsider",
"installed_app_other_description": "Alle dine installerede apps fra andre kategorier.",
"installed_app_automation_description": "Indstil hvilke automatiseringsapps der skal bruges",
"installed_app_web3_description": "Indstil hvilke web3 apps der skal bruges til dine bookingsider",
"analytics": "Analyser",
"empty_installed_apps_headline": "Ingen apps installeret",
"empty_installed_apps_description": "Apps giver dig mulighed for at forbedre din arbejdsgang og forbedre dit planlægningsliv betydeligt.",
@@ -1055,9 +1052,6 @@
"attendee_name_variable": "Deltager",
"event_date_variable": "Dato for begivenhed",
"event_time_variable": "Tidspunkt for begivenhed",
"timezone_variable": "Tidszone",
"location_variable": "Placering",
"additional_notes_variable": "Yderligere bemærkninger",
"app_upgrade_description": "For at bruge denne funktion skal du opgradere til en Pro-konto.",
"invalid_number": "Ugyldigt telefonnummer",
"navigate": "Navigér",
@@ -1136,7 +1130,6 @@
"profile_picture": "Profilbillede",
"upload": "Upload",
"add_profile_photo": "Tilføj profilbillede",
"web3": "Web3",
"token_address": "Token Adresse",
"blockchain": "Blockchain",
"old_password": "Gammel adgangskode",
@@ -1175,7 +1168,6 @@
"connect_automation_apps": "Tilslut automatiseringsapps",
"connect_analytics_apps": "Tilslut analyseapps",
"connect_other_apps": "Tilslut andre apps",
"connect_web3_apps": "Tilslut web3 apps",
"current_step_of_total": "Trin {{currentStep}} af {{maxSteps}}",
"add_variable": "Tilføj variabel",
"custom_phone_number": "Brugerdefineret telefonnummer",
@@ -1538,5 +1530,7 @@
"this_will_be_the_placeholder": "Dette vil være pladsholderen",
"verification_code": "Bekræftelseskode",
"verify": "Bekræft",
"insights_all_org_filter": "Alle"
"confirm_your_details": "Bekræft dine oplysninger",
"overlay_my_calendar": "Vis min kalender",
"need_help": "Brug for hjælp?"
}
+25 -32
View File
@@ -155,7 +155,7 @@
"webhook_updated_successfully": "Webhook erfolgreich aktualisiert!",
"webhook_removed_successfully": "Webhook erfolgreich entfernt!",
"payload_template": "Payload Vorlage",
"dismiss": "Abbrechen",
"dismiss": "Ignorieren",
"no_data_yet": "Noch keine Daten",
"ping_test": "Pingtest",
"add_to_homescreen": "Fügen Sie diese App Ihrem Startbildschirm für schnelleren Zugriff hinzu.",
@@ -295,7 +295,7 @@
"other": "Sonstige",
"email_sign_in_subject": "Ihr Anmelde-Link für {{appName}}",
"emailed_you_and_attendees": "Wir haben eine E-Mail mit einer Kalendereinladung mit den Details an alle gesendet.",
"emailed_you_and_attendees_recurring": "Wir haben eine E-Mail mit einer Kalendereinladung mit den Details für das erste dieser wiederkehrenden Ereignisse an alle gesendet.",
"emailed_you_and_attendees_recurring": "Wir haben eine E-Mail mit einer Kalendereinladung mit den Details für das erste dieser wiederkehrenden Termine an alle gesendet.",
"emailed_you_and_any_other_attendees": "Wir haben eine E-Mail mit diesen Informationen an alle gesendet.",
"needs_to_be_confirmed_or_rejected": "Ihr Termin muss noch bestätigt oder abgelehnt werden.",
"needs_to_be_confirmed_or_rejected_recurring": "Ihr wiederkehrender Termin muss noch bestätigt oder abgelehnt werden.",
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "„Benutzername“ wird mit dem Benutzernamen der zugewiesenen Mitglieder ausgefüllt",
"assign_to": "Zuordnen zu",
"add_members": "Mitglieder hinzufügen...",
"count_members_one": "{{count}} Mitglied",
"count_members_other": "{{count}} Mitglieder",
"no_assigned_members": "Keine zugeordneten Mitglieder",
"assigned_to": "Zugeordnet zu",
"start_assigning_members_above": "Mit der Zuordnung von Mitgliedern oben beginnen",
@@ -683,7 +681,7 @@
"plan_upgrade_teams": "Du musst deinen Plan upgraden, um ein Team zu erstellen.",
"plan_upgrade_instructions": "Sie können <1>hier upgraden</1>.",
"event_types_page_title": "Ereignistypen",
"event_types_page_subtitle": "Erstellen Sie teilbare Ereignisse, die andere Personen buchen können.",
"event_types_page_subtitle": "Erstellen Sie teilbare Termine, die andere Personen buchen können.",
"new": "Neu",
"new_event_type_btn": "Neuer Ereignistyp",
"new_event_type_heading": "Erstellen Sie Ihren ersten Ereignistyp",
@@ -703,7 +701,7 @@
"repeats_up_to_other": "Wiederholt sich bis zu {{count}} mal",
"every_for_freq": "Alle {{freq}} für",
"event_remaining_one": "{{count}} Ereignis übrig",
"event_remaining_other": "{{count}} Ereignisse übrig",
"event_remaining_other": "{{count}} Termine übrig",
"repeats_every": "Wiederholt sich alle",
"occurrence_one": "Vorkommnis",
"occurrence_other": "Vorkommnisse",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} wird eine Tandem Meeting URL zur Verfügung stellen.",
"cal_provide_video_meeting_url": "{{appName}} wird eine Video-Meeting URL zur Verfügung stellen.",
"cal_provide_jitsi_meeting_url": "Cal stellt eine Jitsi Meet URL zur Verfügung.",
"cal_provide_huddle01_meeting_url": "{{appName}} wird eine Huddle01 web3 Video-Meeting URL zur Verfügung stellen.",
"cal_provide_huddle01_meeting_url": "{{appName}} wird eine Huddle01 Video-Meeting URL zur Verfügung stellen.",
"cal_provide_teams_meeting_url": "{{appName}} wird eine MS Teams Meeting-URL zur Verfügung stellen. HINWEIS: SIE BRAUCHEN EIN BERUFS- ODER SCHULKONTO",
"require_payment": "Zahlung erforderlich",
"you_need_to_add_a_name": "Sie müssen einen Namen hinzufügen",
@@ -849,7 +847,6 @@
"next_step": "Schritt überspringen",
"prev_step": "Vorheriger Schritt",
"install": "Installieren",
"install_paid_app": "Abonnieren",
"installed": "Installiert",
"active_install_one": "{{count}} aktive Installation",
"active_install_other": "{{count}} aktive Installationen",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Fügen Sie eine Analyse-App für Ihre Buchungsseiten hinzu",
"no_category_apps_description_automation": "Fügen Sie eine Automatisierungs-App hinzu, die verwendet werden soll",
"no_category_apps_description_other": "Füge jede beliebige andere Art von App hinzu, um alle möglichen Dinge zu tun",
"no_category_apps_description_web3": "Fügen Sie eine web3-App für Ihre Buchungsseiten hinzu",
"no_category_apps_description_messaging": "Fügen Sie eine Nachrichten-App hinzu, um benutzerdefinierte Benachrichtigungen und Erinnerungen einzurichten",
"no_category_apps_description_crm": "Fügen Sie eine CRM-App hinzu, um einen Überblick über die Personen zu behalten, mit denen Sie sich getroffen haben",
"installed_app_calendar_description": "Legen Sie den/die Kalender in denen nach Konflikten gesucht werden sollen fest, um Doppelbuchungen zu vermeiden.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Alle von Ihnen installierten Apps aus anderen Kategorien.",
"installed_app_conferencing_description": "Konfigurieren Sie, welche Konferenz-Apps verwendet werden sollen",
"installed_app_automation_description": "Konfigurieren Sie, welche Automatisierungs-Apps verwendet werden sollen",
"installed_app_web3_description": "Konfigurieren Sie, welche web3-Apps für Ihre Buchungsseiten verwendet werden sollen",
"installed_app_messaging_description": "Konfigurieren Sie zur Einrichtung benutzerdefinierter Benachrichtigungen und Erinnerungen, welche Nachrichten-Apps verwendet werden sollen",
"installed_app_crm_description": "Konfigurieren Sie, welche CRM-Apps verwendet werden sollen, um einen Überblick über die Personen zu behalten, mit denen Sie sich getroffen haben",
"analytics": "Analyse",
@@ -1110,7 +1105,7 @@
"zapier_invite_link": "Zapier Einladungs-Link",
"meeting_url_provided_after_confirmed": "Eine Termin-URL wird angelegt, sobald der Termin bestätigt wurde.",
"dynamically_display_attendee_or_organizer": "Zeigt dynamisch entweder Ihnen den Namen Ihres Teilnehmers bzw. Ihrer Teilnehmerin an oder zeigt Ihrem/Ihrer Teilnehmerin Ihren Namen an",
"event_location": "Ort des Ereignisses",
"event_location": "Ort des Termins",
"reschedule_optional": "Grund für die Verschiebung (optional)",
"reschedule_placeholder": "Lassen Sie andere wissen, warum Sie den Termin verschieben müssen",
"event_cancelled": "Dieser Termin ist abgesagt",
@@ -1153,6 +1148,7 @@
"active_on": "Aktiv am",
"workflow_updated_successfully": "{{workflowName}} Workflow erfolgreich aktualisiert",
"premium_to_standard_username_description": "Dies ist ein Standard-Benutzername und die Aktualisierung führt Sie zur Rechnungsstellung, um ein Downgrade durchzuführen.",
"premium_username": "Dies ist ein Premium-Benutzername, holen Sie sich Ihren für {{price}}",
"current": "Aktuell",
"premium": "Premium",
"standard": "Standard",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Teilnehmer",
"event_date_variable": "Event Datum",
"event_time_variable": "Event Zeit",
"timezone_variable": "Zeitzone",
"location_variable": "Ort",
"additional_notes_variable": "Zusätzliche Notizen",
"organizer_name_variable": "Organisator Name",
"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",
@@ -1277,7 +1270,7 @@
"2fa_required": "Zwei-Faktor-Authentifizierung erforderlich",
"incorrect_2fa": "Falscher Zwei-Faktor-Authentifizierungscode",
"which_event_type_apply": "Auf welchen Termintyp wird dies angewandt?",
"no_workflows_description": "Workflows ermöglichen die einfache Automatisierung des Versands von Benachrichtigungen und Erinnerungen, so dass Sie Prozesse rund um Ihre Ereignisse erstellen können.",
"no_workflows_description": "Workflows ermöglichen die einfache Automatisierung des Versands von Benachrichtigungen und Erinnerungen, so dass Sie Prozesse rund um Ihre Termine erstellen können.",
"timeformat_profile_hint": "Dies ist eine interne Einstellung, die keinen Einfluss darauf hat, wie die Zeiten auf den öffentlichen Buchungsseiten für Sie oder jemanden, der Sie buchen möchte, angezeigt werden.",
"create_workflow": "Einen Workflow erstellen",
"do_this": "Mache dies",
@@ -1309,7 +1302,6 @@
"profile_picture": "Profilbild",
"upload": "Upload",
"add_profile_photo": "Profilbild hinzufügen",
"web3": "Web3",
"token_address": "Adresse des Token",
"blockchain": "Blockchain",
"old_password": "Altes Passwort",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Automatisierungs-Apps verbinden",
"connect_analytics_apps": "Analyse-Apps verbinden",
"connect_other_apps": "Andere Apps verbinden",
"connect_web3_apps": "web3-Apps verbinden",
"connect_messaging_apps": "Nachrichten-Apps verbinden",
"connect_crm_apps": "CRM-Apps verbinden",
"current_step_of_total": "Schritt {{currentStep}} von {{maxSteps}}",
@@ -1362,7 +1353,7 @@
"add_dynamic_variables": "Dynamische Textvariablen hinzufügen",
"event_name_info": "Name des Ereignistyps",
"event_date_info": "Das Datum der Veranstaltung",
"event_time_info": "Die Startzeit des Ereignisses",
"event_time_info": "Die Startzeit des Termins",
"location_info": "Der Ort des Events",
"additional_notes_info": "Die zusätzlichen Anmerkungen der Buchung",
"attendee_name_info": "Name der buchenden Person",
@@ -1380,7 +1371,7 @@
"create_your_first_webhook_description": "Mit Webhooks können Sie Meetingdaten in Echtzeit erhalten, sobald etwas in {{appName}} passiert.",
"for_a_maximum_of": "Für maximal",
"event_one": "Ereignis",
"event_other": "Ereignisse",
"event_other": "Termine",
"profile_team_description": "Einstellungen für Ihr Teamprofil verwalten",
"profile_org_description": "Einstellungen für Ihr Organization-Profil verwalten",
"members_team_description": "Benutzer in der Gruppe",
@@ -1502,13 +1493,13 @@
"require_additional_notes": "Zusätzliche Notizen erforderlich",
"require_additional_notes_description": "Zusätzliche Notizen bei der Buchung erforderlich machen",
"email_address_action": "eine E-Mail an eine bestimmte E-Mail-Adresse senden",
"after_event_trigger": "nach Ende des Ereignisses",
"how_long_after": "Wie lange nach Ende des Ereignisses?",
"after_event_trigger": "nach Ende des Termins",
"how_long_after": "Wie lange nach Ende des Termins?",
"no_available_slots": "Keine verfügbaren Plätze",
"time_available": "Zeit verfügbar",
"cant_find_the_right_video_app_visit_our_app_store": "Sie können die richtige Video-App nicht finden? Besuchen Sie unseren <1>App Store</1>.",
"install_new_calendar_app": "Neue Kalender-App installieren",
"make_phone_number_required": "Telefonnummer für die Buchung des Ereignisses erforderlich machen",
"make_phone_number_required": "Telefonnummer für die Buchung des Termins erforderlich machen",
"new_event_type_availability": "{{eventTypeTitle}} | Verfügbarkeit",
"error_editing_availability": "Fehler beim Bearbeiten der Verfügbarkeit",
"dont_have_permission": "Ihnen fehlt die Berechtigung, auf diese Ressource zuzugreifen.",
@@ -1536,6 +1527,7 @@
"you": "Sie",
"resend_email": "E-Mail erneut senden",
"member_already_invited": "Mitglied wurde bereits eingeladen",
"already_in_use_error": "Benutzername wird bereits verwendet",
"enter_email_or_username": "E-Mail oder Benutzername eingeben",
"team_name_taken": "Dieser Name ist bereits vergeben",
"must_enter_team_name": "Team-Name muss eingegeben werden",
@@ -1813,7 +1805,7 @@
"can_you_try_again": "Können Sie es zu einem anderen Zeitpunkt erneut versuchen?",
"verify": "Bestätigen",
"timezone_info": "Die Zeitzone der empfangenden Person",
"event_end_time_variable": "Endzeitpunkt des Ereignisses",
"event_end_time_variable": "Endzeitpunkt des Termins",
"event_end_time_info": "Der Endzeitpunkt des Termins",
"cancel_url_variable": "Absage-URL",
"cancel_url_info": "Die URL, um die Buchung abzusagen",
@@ -1849,15 +1841,15 @@
"select_user": "Benutzer auswählen",
"select_event_type": "Ereignistyp auswählen",
"select_date_range": "Datumsbereich auswählen",
"popular_events": "Beliebte Ereignisse",
"popular_events": "Beliebte Termine",
"no_event_types_found": "Keine Ereignistypen gefunden",
"average_event_duration": "Durchschnittliche Ereignisdauer",
"most_booked_members": "Meistgebuchte Mitglieder",
"least_booked_members": "Am wenigsten gebuchte Mitglieder",
"events_created": "Ereignisse erstellt",
"events_completed": "Ereignisse abgeschlossen",
"events_created": "Termine erstellt",
"events_completed": "Termine abgeschlossen",
"events_cancelled": "Termine abgesagt",
"events_rescheduled": "Ereignisse neu geplant",
"events_rescheduled": "Termine neu geplant",
"from_last_period": "seit dem letzten Zeitraum",
"from_to_date_period": "Von: {{startDate}} Bis: {{endDate}}",
"redirect_url_warning": "Das Hinzufügen einer Umleitung wird die Erfolgsseite deaktivieren. Erwähnen Sie \"Buchung bestätigt\" auf Ihrer benutzerdefinierten Erfolgsseite.",
@@ -1986,17 +1978,14 @@
"google_new_spam_policy": "Die neuen Spam-Richtlinien von Google könnten verhindern, dass Sie E-Mail- und Kalenderbenachrichtigungen zu dieser Buchung erhalten.",
"resolve": "Problem lösen",
"no_organization_slug": "Beim Erstellen von Teams für diese Organization ist ein Fehler aufgetreten. URL-Slug fehlt.",
"org_name": "Name der Organization",
"org_url": "URL der Organization",
"copy_link_org": "Link zur Organization kopieren",
"404_the_org": "Die Organization",
"404_the_team": "Das Team",
"404_claim_entity_org": "Beanspruchen Sie Ihre Subdomain für Ihre Organization",
"404_claim_entity_team": "Dieses Team beanspruchen und gemeinsam mit der Verwaltung von Verfügbarkeitsplänen beginnen",
"insights_all_org_filter": "Alle Apps",
"insights_team_filter": "Team: {{teamName}}",
"insights_user_filter": "Benutzer: {{userName}}",
"insights_subtitle": "Sehen Sie sich Buchungs-Insights zu Ihren Termine an",
"insights_subtitle": "Erfahren Sie mehr über Ihre Termine und Ihr Team",
"location_options": "{{locationCount}} Veranstaltungsort-Optionen",
"custom_plan": "Maßgeschneiderter Tarif",
"email_embed": "E-Mail Einbettung",
@@ -2005,7 +1994,6 @@
"select_date": "Datum auswählen",
"see_all_available_times": "Alle verfügbaren Zeiten ansehen",
"org_team_names_example": "z.B. Marketing-Team",
"org_team_names_example_1": "z.B. Marketing-Team",
"org_team_names_example_2": "z.B. Vertriebsteam",
"org_team_names_example_3": "z. B. Design-Team",
"org_team_names_example_4": "z.B. Engineering-Team",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Kalenderereignis hinzufügen",
"oAuth": "OAuth",
"recently_added": "Kürzlich hinzugefügt",
"connect_all_calendars": "Verbinden Sie alle Ihre Kalender",
"workflow_automation": "Workflow-Automatisierung",
"scheduling_for_your_team": "Workflow-Automatisierung",
"no_members_found": "Keine Mitglieder gefunden",
"event_setup_length_error": "Ereignis-Einrichtung: Die Dauer muss mindestens 1 Minute betragen.",
"availability_schedules": "Verfügbarkeitspläne",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Benutzerverfügbarkeit bearbeiten: {{username}}",
"resend_invitation": "Einladung erneut senden",
"invitation_resent": "Die Einladung wurde erneut gesendet.",
"saml_sso": "SAML",
"add_client": "Kunde hinzufügen",
"copy_client_secret_info": "Nach dem Kopieren des Geheimnisses können Sie es nicht mehr ansehen",
"add_new_client": "Neuen Kunden hinzufügen",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Zeitzone auf der Buchungsseite sperren",
"description_lock_timezone_toggle_on_booking_page": "Um die Zeitzone auf der Buchungsseite zu sperren, nützlich für Termine in Person.",
"extensive_whitelabeling": "Dedizierte Onboarding- und Engineeringsupport",
"need_help": "Brauchst du Hilfe?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Fügen Sie Ihre neuen Code-Zeilen über dieser hinzu ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -274,8 +274,5 @@
"event_name_tooltip": "Το όνομα που θα εμφανίζεται στα ημερολόγια",
"label": "Ετικέτα",
"edit": "Επεξεργασία",
"disable_guests": "Απενεργοποίηση επισκεπτών",
"location_variable": "Τοποθεσία",
"additional_notes_variable": "Πρόσθετες σημειώσεις",
"insights_all_org_filter": "Όλα"
"disable_guests": "Απενεργοποίηση επισκεπτών"
}
+26 -15
View File
@@ -78,6 +78,7 @@
"cannot_repackage_codebase": "You can not repackage or sell the codebase",
"acquire_license": "Acquire a commercial license to remove these terms by emailing",
"terms_summary": "Summary of terms",
"signing_up_terms":"By signing up, you agree to our <2>Terms of Service</2> and <3>Privacy Policy</3>.",
"open_env": "Open .env and agree to our License",
"env_changed": "I've changed my .env",
"accept_license": "Accept License",
@@ -240,6 +241,7 @@
"reset_your_password": "Set your new password with the instructions sent to your email address.",
"email_change": "Log back in with your new email address and password.",
"create_your_account": "Create your account",
"create_your_calcom_account": "Create your Cal.com account",
"sign_up": "Sign up",
"youve_been_logged_out": "You've been logged out",
"hope_to_see_you_soon": "We hope to see you again soon!",
@@ -277,6 +279,9 @@
"nearly_there_instructions": "Last thing, a brief description about you and a photo really helps you get bookings and let people know who they’re booking with.",
"set_availability_instructions": "Define ranges of time when you are available on a recurring basis. You can create more of these later and assign them to different calendars.",
"set_availability": "Set your availability",
"set_availbility_description":"Set schedules for the times you want to be booked.",
"share_a_link_or_embed":"Share a link or embed",
"share_a_link_or_embed_description":"Share your {{appName}} link or embed on your site.",
"availability_settings": "Availability Settings",
"continue_without_calendar": "Continue without calendar",
"continue_with": "Continue with {{appName}}",
@@ -678,8 +683,6 @@
"managed_event_url_clarification": "\"username\" will be filled by the username of the members assigned",
"assign_to": "Assign to",
"add_members": "Add members...",
"count_members_one": "{{count}} member",
"count_members_other": "{{count}} members",
"no_assigned_members": "No assigned members",
"assigned_to": "Assigned to",
"start_assigning_members_above": "Start assigning members above",
@@ -815,7 +818,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} will provide a Tandem meeting URL.",
"cal_provide_video_meeting_url": "{{appName}} will provide a video meeting URL.",
"cal_provide_jitsi_meeting_url": "We will generate a Jitsi Meet URL for you.",
"cal_provide_huddle01_meeting_url": "{{appName}} will provide a Huddle01 web3 video meeting URL.",
"cal_provide_huddle01_meeting_url": "{{appName}} will provide a Huddle01 video meeting URL.",
"cal_provide_teams_meeting_url": "{{appName}} will provide a MS Teams meeting URL. NOTE: MUST HAVE A WORK OR SCHOOL ACCOUNT",
"require_payment": "Require Payment",
"you_need_to_add_a_name": "You need to add a name",
@@ -861,7 +864,6 @@
"next_step": "Skip step",
"prev_step": "Prev step",
"install": "Install",
"install_paid_app": "Subscribe",
"start_paid_trial": "Start free Trial",
"installed": "Installed",
"active_install_one": "{{count}} active install",
@@ -920,7 +922,6 @@
"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_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",
"no_category_apps_description_messaging": "Add a messaging app to set up custom notifications & reminders",
"no_category_apps_description_crm": "Add a CRM app to keep track of who you've met with",
"installed_app_calendar_description": "Set the calendars to check for conflicts to prevent double bookings.",
@@ -929,7 +930,6 @@
"installed_app_other_description": "All your installed apps from other categories.",
"installed_app_conferencing_description": "Configure which conferencing 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",
"installed_app_messaging_description": "Configure which messaging apps to use for setting up custom notifications & reminders",
"installed_app_crm_description": "Configure which CRM apps to use for keeping track of who you've met with",
"analytics": "Analytics",
@@ -1052,6 +1052,7 @@
"user_impersonation_heading": "User Impersonation",
"user_impersonation_description": "Allows our support team to temporarily sign in as you to help us quickly resolve any issues you report to us.",
"team_impersonation_description": "Allows your team Owners/Admins to temporarily sign in as you.",
"cal_signup_description":"Free for individuals. Team plans for collaborative features.",
"make_team_private": "Make team private",
"make_team_private_description": "Your team members won't be able to see other team members when this is turned on.",
"you_cannot_see_team_members": "You cannot see all the team members of a private team.",
@@ -1167,6 +1168,7 @@
"active_on": "Active on",
"workflow_updated_successfully": "{{workflowName}} workflow updated successfully",
"premium_to_standard_username_description": "This is a standard username and updating will take you to billing to downgrade.",
"premium_username": "This is a premium username, get yours for {{price}}",
"current": "Current",
"premium": "premium",
"standard": "standard",
@@ -1232,9 +1234,6 @@
"attendee_name_variable": "Attendee",
"event_date_variable": "Event date",
"event_time_variable": "Event time",
"timezone_variable": "Timezone",
"location_variable": "Location",
"additional_notes_variable": "Additional notes",
"organizer_name_variable": "Organizer name",
"app_upgrade_description": "In order to use this feature, you need to upgrade to a Pro account.",
"invalid_number": "Invalid phone number",
@@ -1323,7 +1322,6 @@
"profile_picture": "Profile Picture",
"upload": "Upload",
"add_profile_photo": "Add profile photo",
"web3": "Web3",
"token_address": "Token Address",
"blockchain": "Blockchain",
"old_password": "Old password",
@@ -1365,7 +1363,6 @@
"connect_automation_apps": "Connect automation apps",
"connect_analytics_apps": "Connect analytics apps",
"connect_other_apps": "Connect other apps",
"connect_web3_apps": "Connect web3 apps",
"connect_messaging_apps": "Connect messaging apps",
"connect_crm_apps": "Connect CRM apps",
"current_step_of_total": "Step {{currentStep}} of {{maxSteps}}",
@@ -1550,9 +1547,14 @@
"your_org_disbanded_successfully": "Your organization has been disbanded successfully",
"error_creating_team": "Error creating team",
"you": "You",
"or_continue_with": "Or continue with",
"resend_email": "Resend email",
"member_already_invited": "Member has already been invited",
"already_in_use_error": "Username already in use",
"enter_email_or_username": "Enter an email or username",
"enter_email": "Enter an email",
"enter_emails": "Enter emails",
"too_many_invites": "You are limited to inviting a maximum of {{nbUsers}} users at once.",
"team_name_taken": "This name is already taken",
"must_enter_team_name": "Must enter a team name",
"team_url_required": "Must enter a team URL",
@@ -1605,6 +1607,7 @@
"enable_apps": "Enable Apps",
"enable_apps_description": "Enable apps that users can integrate with {{appName}}",
"purchase_license": "Purchase a License",
"already_have_account":"I already have an account",
"already_have_key": "I already have a key:",
"already_have_key_suggestion": "Please copy your existing CALCOM_LICENSE_KEY environment variable here.",
"app_is_enabled": "{{appName}} is enabled",
@@ -2002,14 +2005,11 @@
"google_new_spam_policy": "Google’s new spam policy could prevent you from receiving any email and calendar notifications about this booking.",
"resolve": "Resolve",
"no_organization_slug": "There was an error creating teams for this organization. Missing URL slug.",
"org_name": "Organization name",
"org_url": "Organization URL",
"copy_link_org": "Copy link to organization",
"404_the_org": "The organization",
"404_the_team": "The team",
"404_claim_entity_org": "Claim your subdomain for your organization",
"404_claim_entity_team": "Claim this team and start managing schedules collectively",
"insights_all_org_filter": "All",
"insights_team_filter": "Team: {{teamName}}",
"insights_user_filter": "User: {{userName}}",
"insights_subtitle": "View booking insights across your events",
@@ -2021,7 +2021,6 @@
"select_date": "Select Date",
"see_all_available_times": "See all available times",
"org_team_names_example": "e.g. Marketing Team",
"org_team_names_example_1": "e.g. Marketing Team",
"org_team_names_example_2": "e.g. Sales Team",
"org_team_names_example_3": "e.g. Design Team",
"org_team_names_example_4": "e.g. Engineering Team",
@@ -2083,6 +2082,12 @@
"include_calendar_event": "Include calendar event",
"oAuth": "OAuth",
"recently_added":"Recently added",
"connect_all_calendars":"Connect all your calendars",
"connect_all_calendars_description":"{{appName}} reads availability from all your existing calendars.",
"workflow_automation":"Workflow automation",
"workflow_automation_description":"Personalise your scheduling experience with workflows",
"scheduling_for_your_team":"Workflow automation",
"scheduling_for_your_team_description":"Schedule for your team with collective and round-robin scheduling",
"no_members_found": "No members found",
"event_setup_length_error":"Event Setup: The duration must be at least 1 minute.",
"availability_schedules":"Availability Schedules",
@@ -2106,6 +2111,7 @@
"edit_users_availability":"Edit user's availability: {{username}}",
"resend_invitation": "Resend invitation",
"invitation_resent": "The invitation was resent.",
"saml_sso": "SAML",
"add_client": "Add client",
"copy_client_secret_info": "After copying the secret you won't be able to view it anymore",
"add_new_client": "Add new Client",
@@ -2121,6 +2127,7 @@
"manage_availability_schedules":"Manage availability schedules",
"lock_timezone_toggle_on_booking_page": "Lock timezone on booking page",
"description_lock_timezone_toggle_on_booking_page" : "To lock the timezone on booking page, useful for in-person events.",
"number_in_international_format": "Please enter number in international format.",
"install_calendar":"Install Calendar",
"branded_subdomain": "Branded Subdomain",
"branded_subdomain_description": "Get your own branded subdomain, such as acme.cal.com",
@@ -2137,5 +2144,9 @@
"enterprise_description": "Upgrade to Enterprise to create your Organization",
"create_your_org": "Create your Organization",
"create_your_org_description": "Upgrade to Enterprise and receive a subdomain, unified billing, Insights, extensive whitelabeling and more",
"troubleshooter_tooltip": "Open the troubleshooter and figure out what is wrong with your schedule",
"need_help": "Need help?",
"troubleshooter": "Troubleshooter",
"please_install_a_calendar": "Please install a calendar",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+7 -14
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "El \"nombre de usuario\" se completará con el nombre de usuario de los miembros asignados",
"assign_to": "Asignar a",
"add_members": "Agregar miembros...",
"count_members_one": "{{count}} miembro",
"count_members_other": "{{count}} miembros",
"no_assigned_members": "No hay miembros asignados",
"assigned_to": "Asignado a",
"start_assigning_members_above": "Empezar a asignar miembros más arriba",
@@ -849,7 +847,6 @@
"next_step": "Saltar paso",
"prev_step": "Paso anterior",
"install": "Instalar",
"install_paid_app": "Suscribirse",
"installed": "Instalado",
"active_install_one": "{{count}} instalación activa",
"active_install_other": "{{count}} instalaciones activas",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Añada una aplicación de análisis para sus páginas de reserva",
"no_category_apps_description_automation": "Añada una aplicación de automatización para utilizar",
"no_category_apps_description_other": "Añade cualquier otro tipo de aplicación para hacer todo tipo de tareas",
"no_category_apps_description_web3": "Agregue una aplicación web3 para sus páginas de reserva",
"no_category_apps_description_messaging": "Agregue una aplicación de mensajería para configurar notificaciones y recordatorios personalizados",
"no_category_apps_description_crm": "Agregue una aplicación CRM para realizar un seguimiento de con quiénes se ha reunido",
"installed_app_calendar_description": "Configure el(los) calendario(s) para comprobar conflictos y evitar reservas duplicadas.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Todas tus aplicaciones instaladas de otras categorías.",
"installed_app_conferencing_description": "Configure qué aplicaciones de conferencia usará",
"installed_app_automation_description": "Configure qué aplicaciones de automatización va a utilizar",
"installed_app_web3_description": "Configure qué aplicaciones web3 usar para sus páginas de reserva",
"installed_app_messaging_description": "Configure qué aplicaciones de mensajería usar para configurar notificaciones y recordatorios personalizados",
"installed_app_crm_description": "Configure qué aplicaciones de CRM usar para realizar un seguimiento de las personas con las que se ha reunido",
"analytics": "Análisis",
@@ -1153,6 +1148,7 @@
"active_on": "Activo en",
"workflow_updated_successfully": "Flujo de trabajo {{workflowName}} actualizado correctamente",
"premium_to_standard_username_description": "Este es un nombre de usuario estándar y la actualización te llevará a la facturación para bajar de categoría.",
"premium_username": "Este es un nombre de usuario premium, obtenga el suyo por {{price}}",
"current": "Actual",
"premium": "premium",
"standard": "estándar",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Nombre del asistente",
"event_date_variable": "Fecha del evento",
"event_time_variable": "Hora del evento",
"timezone_variable": "Zona Horaria",
"location_variable": "Lugar",
"additional_notes_variable": "Notas Adicionales",
"organizer_name_variable": "Nombre del organizador",
"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",
@@ -1309,7 +1302,6 @@
"profile_picture": "Imagen de perfil",
"upload": "Cargar",
"add_profile_photo": "Agregar foto de perfil",
"web3": "Web3",
"token_address": "Dirección del token",
"blockchain": "Blockchain",
"old_password": "Contraseña anterior",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Conecte aplicaciones de automatización",
"connect_analytics_apps": "Conecte aplicaciones de análisis",
"connect_other_apps": "Conectar otras aplicaciones",
"connect_web3_apps": "Conectar aplicaciones web3",
"connect_messaging_apps": "Conectar aplicaciones de mensajería",
"connect_crm_apps": "Conectar aplicaciones CRM",
"current_step_of_total": "Paso {{currentStep}} de {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Usted",
"resend_email": "Reenviar correo electrónico",
"member_already_invited": "El miembro ya se ha invitado",
"already_in_use_error": "El nombre de usuario ya está en uso",
"enter_email_or_username": "Introduzca un correo electrónico o nombre de usuario",
"team_name_taken": "Este nombre ya está en uso",
"must_enter_team_name": "Debe introducir un nombre de equipo",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "La nueva política de spam de Google podría evitar que reciba notificaciones por correo electrónico y calendario sobre esta reserva.",
"resolve": "Resolver",
"no_organization_slug": "Hubo un error al crear equipos para esta organización. Falta el slug de URL.",
"org_name": "Nombre de la organización",
"org_url": "URL de la organización",
"copy_link_org": "Copiar enlace a la organización",
"404_the_org": "La organización",
"404_the_team": "El equipo",
"404_claim_entity_org": "Reclame su subdominio para su organización",
"404_claim_entity_team": "Reclame este equipo y empiece a gestionar los horarios de forma colectiva",
"insights_all_org_filter": "Todas las aplicaciones",
"insights_team_filter": "Equipo: {{teamName}}",
"insights_user_filter": "Usuario: {{userName}}",
"insights_subtitle": "Mire información de insights en todos sus eventos",
@@ -2005,7 +1994,6 @@
"select_date": "Seleccione la fecha",
"see_all_available_times": "Ver todas las horas disponibles",
"org_team_names_example": "ej. Equipo de marketing",
"org_team_names_example_1": "ej. Equipo de marketing",
"org_team_names_example_2": "ej. Equipo de ventas",
"org_team_names_example_3": "ej. Equipo de diseño",
"org_team_names_example_4": "ej. Equipo de ingeniería",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Incluir evento del calendario",
"oAuth": "OAuth",
"recently_added": "Añadido recientemente",
"connect_all_calendars": "Conecte todos sus calendarios",
"workflow_automation": "Automatización del flujo de trabajo",
"scheduling_for_your_team": "Automatización del flujo de trabajo",
"no_members_found": "No se encontraron miembros",
"event_setup_length_error": "Configuración del evento: la duración debe ser de al menos 1 minuto.",
"availability_schedules": "Horarios de disponibilidad",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Editar disponibilidad del usuario: {{username}}",
"resend_invitation": "Reenviar invitación",
"invitation_resent": "Se reenvió la invitación.",
"saml_sso": "SAML",
"add_client": "Agregar cliente",
"copy_client_secret_info": "Después de copiar el secreto, ya no podrá volver a verlo",
"add_new_client": "Agregar nuevo cliente",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Bloquear la zona horaria en la página de reserva",
"description_lock_timezone_toggle_on_booking_page": "Bloquear la zona horaria en la página de reserva, es útil para eventos en persona.",
"extensive_whitelabeling": "Asistencia dedicada en materia de incorporación e ingeniería",
"need_help": "¿Necesita ayuda?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Agregue sus nuevas cadenas arriba ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -439,8 +439,6 @@
"default_duration": "Lehenetsitako iraupena",
"minutes": "minutu",
"username_placeholder": "erabiltzaile izena",
"count_members_one": "kide {{count}}",
"count_members_other": "{{count}} kide",
"url": "URLa",
"hidden": "Ezkutuan",
"readonly": "Irakurtzeko bakarrik",
@@ -506,7 +504,6 @@
"next_step": "Saltatu pausoa",
"prev_step": "Aurreko pausoa",
"install": "Instalatu",
"install_paid_app": "Harpidetu",
"installed": "Instalatua",
"disconnect": "Deskonektatu",
"automation": "Automatizazioa",
@@ -644,9 +641,6 @@
"attendee_name_variable": "Partaidea",
"event_date_variable": "Gertaeraren data",
"event_time_variable": "Gertaeraren ordua",
"timezone_variable": "Ordu-eremua",
"location_variable": "Kokapena",
"additional_notes_variable": "Ohar gehigarriak",
"organizer_name_variable": "Antolatzailearen izena",
"invalid_number": "Telefono zenbaki baliogabea",
"navigate": "Nabigatu",
@@ -685,7 +679,6 @@
"profile_picture": "Profileko irudia",
"upload": "Kargatu",
"add_profile_photo": "Gehitu profileko argazkia",
"web3": "Web3",
"old_password": "Pasahitz zaharra",
"secure_password": "Zure pasahitz berri super segurua",
"error_updating_password": "Errorea pasahitza eguneratzean",
+19 -15
View File
@@ -67,6 +67,7 @@
"cannot_repackage_codebase": "Vous ne pouvez pas restructurer ou vendre la base de code",
"acquire_license": "Obtenez une licence commerciale pour supprimer ces termes en envoyant un e-mail",
"terms_summary": "Résumé des conditions",
"signing_up_terms": "En vous inscrivant, vous acceptez nos <2>Conditions d'utilisation</2> et notre <3>Politique de confidentialité</3>.",
"open_env": "Ouvrez le fichier .env et acceptez notre licence",
"env_changed": "J'ai modifié mon fichier .env",
"accept_license": "Accepter la licence",
@@ -229,6 +230,7 @@
"reset_your_password": "Définissez votre nouveau mot de passe avec les instructions envoyées à votre adresse e-mail.",
"email_change": "Reconnectez-vous avec votre nouvelle adresse e-mail et votre nouveau mot de passe.",
"create_your_account": "Créez votre compte",
"create_your_calcom_account": "Créez votre compte Cal.com",
"sign_up": "S'inscrire",
"youve_been_logged_out": "Vous avez été déconnecté",
"hope_to_see_you_soon": "Nous espérons vous revoir bientôt !",
@@ -266,6 +268,9 @@
"nearly_there_instructions": "Pour finir, une brève description de vous et une photo vous aideront vraiment à obtenir des réservations et à faire savoir aux gens avec qui ils prennent rendez-vous.",
"set_availability_instructions": "Définissez des plages de temps pendant lesquelles vous êtes disponible de manière récurrente. Vous pourrez en créer d'autres ultérieurement et les assigner à différents calendriers.",
"set_availability": "Définissez vos disponibilités",
"set_availbility_description": "Définissez des horaires pour les périodes où vous souhaitez être réservé.",
"share_a_link_or_embed": "Partagez un lien ou une intégration",
"share_a_link_or_embed_description": "Partagez votre lien ou intégration {{appName}} sur votre site.",
"availability_settings": "Paramètres de disponibilité",
"continue_without_calendar": "Continuer sans calendrier",
"continue_with": "Continuer avec {{appName}}",
@@ -665,8 +670,6 @@
"managed_event_url_clarification": "« nom d'utilisateur » sera rempli par le nom d'utilisateur des membres assignés",
"assign_to": "Assigner à",
"add_members": "Ajouter des membres...",
"count_members_one": "{{count}} membre",
"count_members_other": "{{count}} membres",
"no_assigned_members": "Aucun membre assigné",
"assigned_to": "Assigné à",
"start_assigning_members_above": "Commencez à assigner des membres ci-dessus",
@@ -802,7 +805,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} fournira un lien de rendez-vous Tandem.",
"cal_provide_video_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo.",
"cal_provide_jitsi_meeting_url": "Nous générerons un lien de réunion Jitsi Meet pour vous.",
"cal_provide_huddle01_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo Huddle01 web3.",
"cal_provide_huddle01_meeting_url": "{{appName}} fournira un lien de rendez-vous vidéo Huddle01.",
"cal_provide_teams_meeting_url": "{{appName}} fournira un lien de rendez-vous MS Teams. NOTE : IL FAUT AVOIR UN COMPTE PROFESSIONNEL OU SCOLAIRE",
"require_payment": "Exiger un paiement",
"you_need_to_add_a_name": "Vous devez ajouter un nom",
@@ -848,7 +851,6 @@
"next_step": "Passer l'étape",
"prev_step": "Étape précédente",
"install": "Installer",
"install_paid_app": "S'abonner",
"start_paid_trial": "Démarrer l'essai gratuit",
"installed": "Installée",
"active_install_one": "{{count}} installation active",
@@ -907,7 +909,6 @@
"no_category_apps_description_analytics": "Ajoutez une application d'analyse pour vos pages de réservation.",
"no_category_apps_description_automation": "Ajoutez une application d'automatisation.",
"no_category_apps_description_other": "Ajoutez n'importe quel autre type d'application pour faire toutes sortes de choses.",
"no_category_apps_description_web3": "Ajoutez une application Web3 pour vos pages de réservation.",
"no_category_apps_description_messaging": "Ajoutez une application de messagerie pour configurer des notifications et des rappels personnalisés.",
"no_category_apps_description_crm": "Ajoutez une application de CRM pour garder une trace des personnes que vous avez rencontrées.",
"installed_app_calendar_description": "Définissez les calendriers pour vérifier les conflits afin d'éviter les doubles réservations.",
@@ -916,7 +917,6 @@
"installed_app_other_description": "Toutes vos applications installées à partir d'autres catégories.",
"installed_app_conferencing_description": "Configurez les applications de conférence à utiliser.",
"installed_app_automation_description": "Configurez les applications d'automatisation à utiliser.",
"installed_app_web3_description": "Configurez les applications Web3 à utiliser pour vos pages de réservation.",
"installed_app_messaging_description": "Configurez les applications de messagerie à utiliser pour configurer des notifications et des rappels personnalisés.",
"installed_app_crm_description": "Configurez les applications de CRM à utiliser pour garder une trace des personnes que vous avez rencontrées.",
"analytics": "Analytiques",
@@ -1039,6 +1039,7 @@
"user_impersonation_heading": "Emprunt d'identité",
"user_impersonation_description": "Autorisez notre équipe d'assistance à se connecter temporairement à votre place pour nous aider à résoudre les problèmes que vous pourriez nous signaler.",
"team_impersonation_description": "Autorisez les propriétaires/administrateurs de votre équipe à se connecter temporairement à votre place.",
"cal_signup_description": "Gratuit pour les particuliers. Plans Équipes pour les fonctionnalités collaboratives.",
"make_team_private": "Rendre l'équipe privée",
"make_team_private_description": "Les membres de votre équipe ne pourront pas voir les autres membres de l'équipe si cette option est activée.",
"you_cannot_see_team_members": "Vous ne pouvez pas voir tous les membres d'une équipe privée.",
@@ -1154,6 +1155,7 @@
"active_on": "Actif sur",
"workflow_updated_successfully": "Workflow {{workflowName}} mis à jour avec succès",
"premium_to_standard_username_description": "Il s'agit d'un nom d'utilisateur standard et l'appliquer vous redirigera vers la facturation pour résilier votre plan.",
"premium_username": "Il s'agit d'un nom d'utilisateur premium, obtenez-le pour {{price}}",
"current": "Actuel",
"premium": "premium",
"standard": "standard",
@@ -1219,9 +1221,6 @@
"attendee_name_variable": "Nom du participant",
"event_date_variable": "Date de l'événement",
"event_time_variable": "Heure de l'événement",
"timezone_variable": "Fuseau horaire",
"location_variable": "Lieu",
"additional_notes_variable": "Notes supplémentaires",
"organizer_name_variable": "Nom de l'organisateur",
"app_upgrade_description": "Pour pouvoir utiliser cette fonctionnalité, vous devez passer à un compte Pro.",
"invalid_number": "Numéro de téléphone invalide",
@@ -1308,7 +1307,6 @@
"profile_picture": "Photo de profil",
"upload": "Télécharger",
"add_profile_photo": "Ajouter une photo de profil",
"web3": "Web3",
"token_address": "Adresse du token",
"blockchain": "Blockchain",
"old_password": "Ancien mot de passe",
@@ -1350,7 +1348,6 @@
"connect_automation_apps": "Connecter des apps d'automatisation",
"connect_analytics_apps": "Connecter des apps d'analytique",
"connect_other_apps": "Connecter d'autres applications",
"connect_web3_apps": "Connecter des apps Web3",
"connect_messaging_apps": "Connecter des apps de messagerie",
"connect_crm_apps": "Connecter des apps de CRM",
"current_step_of_total": "Étape {{currentStep}} sur {{maxSteps}}",
@@ -1533,8 +1530,10 @@
"your_org_disbanded_successfully": "Votre organisation a bien été dissoute",
"error_creating_team": "Erreur lors de la création de l'équipe",
"you": "Vous",
"or_continue_with": "Ou continuez avec",
"resend_email": "Renvoyer l’e-mail",
"member_already_invited": "Le membre a déjà été invité",
"already_in_use_error": "Nom d'utilisateur déjà utilisé",
"enter_email_or_username": "Saisissez une adresse e-mail ou un nom d'utilisateur",
"team_name_taken": "Ce nom est déjà pris",
"must_enter_team_name": "Vous devez saisir un nom d'équipe",
@@ -1588,6 +1587,7 @@
"enable_apps": "Activer les applications",
"enable_apps_description": "Activer les applications que les utilisateurs peuvent intégrer à {{appName}}",
"purchase_license": "Acheter une licence",
"already_have_account": "J'ai déjà un compte",
"already_have_key": "J'ai déjà une clé :",
"already_have_key_suggestion": "Veuillez copier votre variable d'environnement CALCOM_LICENSE_KEY existante ici.",
"app_is_enabled": "{{appName}} est activé",
@@ -1983,14 +1983,11 @@
"google_new_spam_policy": "La nouvelle politique anti-spam de Google peut vous empêcher de recevoir des notifications par e-mail ou sur votre calendrier concernant cette réservation.",
"resolve": "Résoudre",
"no_organization_slug": "Une erreur s'est produite lors de la création des équipes pour cette organisation. Le slug du lien est manquant.",
"org_name": "Nom de l'organisation",
"org_url": "Lien de l'organisation",
"copy_link_org": "Copier le lien vers l’organisation",
"404_the_org": "L'organisation",
"404_the_team": "L'équipe",
"404_claim_entity_org": "Réservez votre sous-domaine pour votre organisation",
"404_claim_entity_team": "Revendiquez cette équipe et commencez à gérer vos plannings collectivement",
"insights_all_org_filter": "Tous",
"insights_team_filter": "Équipe : {{teamName}}",
"insights_user_filter": "Utilisateur : {{userName}}",
"insights_subtitle": "Visualisez les statistiques de réservation à travers vos événements",
@@ -2002,7 +1999,6 @@
"select_date": "Sélectionner une date",
"see_all_available_times": "Voir tous les créneaux disponibles",
"org_team_names_example": "p. ex. Équipe marketing",
"org_team_names_example_1": "p. ex. Équipe marketing",
"org_team_names_example_2": "p. ex. Équipe de vente",
"org_team_names_example_3": "p. ex. Équipe de design",
"org_team_names_example_4": "p. ex. Équipe d'ingénierie",
@@ -2063,6 +2059,11 @@
"seat_options_doesnt_multiple_durations": "L'option par place ne prend pas en charge les durées multiples",
"include_calendar_event": "Inclure l'événement du calendrier",
"recently_added": "Ajoutées récemment",
"connect_all_calendars": "Connectez vos calendriers",
"connect_all_calendars_description": "{{appName}} relève la disponibilité de tous vos calendriers existants.",
"workflow_automation": "Automatisation de workflows",
"workflow_automation_description": "Personnalisez votre expérience de planification avec les workflows",
"scheduling_for_your_team": "Automatisation de workflows",
"no_members_found": "Aucun membre trouvé",
"event_setup_length_error": "Configuration de l'événement : la durée doit être d'au moins 1 minute.",
"availability_schedules": "Horaires de disponibilité",
@@ -2077,6 +2078,7 @@
"edit_users_availability": "Modifier la disponibilité de l'utilisateur : {{username}}",
"resend_invitation": "Renvoyer l'invitation",
"invitation_resent": "L'invitation a été renvoyée.",
"saml_sso": "SAML",
"add_client": "Ajouter un client",
"add_new_client": "Ajouter un nouveau client",
"as_csv": "au format CSV",
@@ -2085,7 +2087,9 @@
"view_overlay_calendar_events": "Consultez les événements de votre calendrier afin d'éviter les réservations incompatibles.",
"lock_timezone_toggle_on_booking_page": "Verrouiller le fuseau horaire sur la page de réservation",
"description_lock_timezone_toggle_on_booking_page": "Pour verrouiller le fuseau horaire sur la page de réservation, utile pour les événements en personne.",
"number_in_international_format": "Veuillez entrer le numéro au format international.",
"extensive_whitelabeling": "Marque blanche étendue",
"unlimited_teams": "Équipes illimitées",
"need_help": "Besoin d'aide ?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Ajoutez vos nouvelles chaînes ci-dessus ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "יש למלא את שדה \"username\" בשמות המשתמשים של החברים שהוקצו",
"assign_to": "הקצאה ל",
"add_members": "הוספת חברים...",
"count_members_one": "חבר {{count}}",
"count_members_other": "{{count}} חברים",
"no_assigned_members": "לא הוקצה אף חבר",
"assigned_to": "הוקצה ל",
"start_assigning_members_above": "התחל/י להקצות חברים למעלה",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} יספק כתובת קישור לפגישת Tandem.",
"cal_provide_video_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו.",
"cal_provide_jitsi_meeting_url": "אנחנו ניצור עבורך כתובת URL לפגישת Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו web3 ב- Huddle01.",
"cal_provide_huddle01_meeting_url": "{{appName}} יספק כתובת קישור לפגישת וידאו ב- Huddle01.",
"cal_provide_teams_meeting_url": "{{appName}} יספק כתובת קישור לפגישת MS Teams. הערה: חייב להיות חשבון של מקום עבודה או בית ספר",
"require_payment": "דרישת תשלום",
"you_need_to_add_a_name": "יש להוסיף שם",
@@ -849,7 +847,6 @@
"next_step": "לדלג על שלב זה",
"prev_step": "לשלב הקודם",
"install": "התקנה",
"install_paid_app": "הרשמה למינוי",
"installed": "מותקן",
"active_install_one": "התקנה פעילה {{count}}",
"active_install_other": "{{count}} התקנות פעילות",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "להוסיף אפליקציה לניתוח נתונים עבור דפי ההזמנות שלך",
"no_category_apps_description_automation": "להוסיף אפליקציית אוטומציה שברצונך להשתמש בה",
"no_category_apps_description_other": "הוסף/הוסיפי אפליקציה מכל סוג אחר לביצוע פעולות שונות",
"no_category_apps_description_web3": "הוסף אפליקצית web3 לעמודי תזמון הפגישות שלך",
"no_category_apps_description_messaging": "הוסף/הוסיפי אפליקציית מסרים מידיים כדי להגדיר התראות ותזכורות בהתאמה אישית",
"no_category_apps_description_crm": "הוסף/י אפליקציית CRM כדי לנהל מעקב אחר האנשים שנפגשת איתם",
"installed_app_calendar_description": "הגדר/הגדירח את לוחות השנה כדי לבדוק אם יש התנגשויות על מנת למנוע כפל הזמנות.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "כל האפליקציות המותקנות שלך מקטגוריות אחרות.",
"installed_app_conferencing_description": "הגדר/הגדירי את האפליקציות לשיחות ועידה שבהן ברצונך להשתמש",
"installed_app_automation_description": "הגדרת אפליקציות האוטומציה שבהן ברצונך להשתמש",
"installed_app_web3_description": "הגדר באיזה אפליקציות web3 להשתמש בעמודי תזמון הפגישות שלך",
"installed_app_messaging_description": "הגדר/הגדירי את אפליקציות המסרים המידיים שבהן ברצונך להשתמש להגדרת עדכונים ותזכורות",
"installed_app_crm_description": "הגדר/הגדירי את אפליקציות ה-CRM שבהן ברצונך להשתמש לניהול מעקב אחר האנשים שנפגשת איתם",
"analytics": "ניתוח נתונים",
@@ -1153,6 +1148,7 @@
"active_on": "פעיל ב",
"workflow_updated_successfully": "עדכון תהליך העבודה {{workflowName}} בוצע בהצלחה",
"premium_to_standard_username_description": "זהו שם משתמש סטנדרטי. אם תעדכן/י אותו, תועבר/י לדף החיוב לצורך שנמוך.",
"premium_username": "זהו שם משתמש פרימיום, קבל/י אחד משלך תמורת {{price}}",
"current": "נוכחי",
"premium": "פרימיום",
"standard": "רגיל",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "שם המשתתף",
"event_date_variable": "תאריך האירוע",
"event_time_variable": "מועד האירוע",
"timezone_variable": "אזור זמן",
"location_variable": "מיקום",
"additional_notes_variable": "הערות נוספות",
"organizer_name_variable": "שם המארגן/ת",
"app_upgrade_description": "כדי להשתמש בתכונה זו, עליך לשדרג לחשבון Pro.",
"invalid_number": "מספר טלפון לא תקין",
@@ -1309,7 +1302,6 @@
"profile_picture": "תמונת פרופיל",
"upload": "העלאה",
"add_profile_photo": "הוספת תמונת פרופיל",
"web3": "Web3",
"token_address": "כתובת הטוקן",
"blockchain": "בלוקצ'יין",
"old_password": "סיסמה ישנה",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "קישור אפליקציות אוטומציה",
"connect_analytics_apps": "קישור אפליקציות ניתוח נתונים",
"connect_other_apps": "חיבור אפליקציות אחרות",
"connect_web3_apps": "חבר אפליקציות web3",
"connect_messaging_apps": "קישור אפליקציות מסרים מידיים",
"connect_crm_apps": "קישור אפליקציות CRM",
"current_step_of_total": "שלב {{currentStep}} מתוך {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "את/ה",
"resend_email": "לשלוח שוב את הדוא״ל",
"member_already_invited": "החבר כבר הוזמן",
"already_in_use_error": "שם המשתמש כבר קיים",
"enter_email_or_username": "יש להזין כתובת דוא\"ל או שם משתמש",
"team_name_taken": "השם הזה כבר תפוס",
"must_enter_team_name": "יש להזין שם צוות",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "מדיניות הספאם החדשה של Google עלולה לגרום לכך שלא תקבל/י עדכונים בדוא״ל ובלוח השנה לגבי הפגישה הזו.",
"resolve": "פתרון",
"no_organization_slug": "אירעה שגיאה במהלך הניסיון ליצור צוותים עבור הארגון הזה. חסר רכיב slug של URL.",
"org_name": "שם הארגון",
"org_url": "כתובת ה-URL של הארגון",
"copy_link_org": "העתקת הקישור לארגון",
"404_the_org": "הארגון",
"404_the_team": "הצוות",
"404_claim_entity_org": "קבל/י את תת-הדומיין עבור הארגון שלך",
"404_claim_entity_team": "הצטרף/י לצוות הזה והתחל/התחילי לנהל קביעת מועדים ביחד",
"insights_all_org_filter": "כל האפליקציות",
"insights_team_filter": "צוות: {{teamName}}",
"insights_user_filter": "משתמש: {{userName}}",
"insights_subtitle": "הצגת insights לגבי ההזמנות באירועים שלך",
@@ -2005,7 +1994,6 @@
"select_date": "בחירת תאריך",
"see_all_available_times": "לצפייה בכל המועדים הפנויים",
"org_team_names_example": "לדוגמה, מחלקת שיווק",
"org_team_names_example_1": "לדוגמה, מחלקת שיווק",
"org_team_names_example_2": "לדוגמה, מחלקת מכירות",
"org_team_names_example_3": "לדוגמה, מחלקת עיצוב",
"org_team_names_example_4": "לדוגמה, מחלקת הנדסה",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "כלילת אירוע מלוח השנה",
"oAuth": "OAuth",
"recently_added": "נוספו לאחרונה",
"connect_all_calendars": "חבר את כל לוחות השנה שלך",
"workflow_automation": "אוטומצית תהליך עבודה",
"scheduling_for_your_team": "אוטומצית תהליך עבודה",
"no_members_found": "לא נמצא אף חבר",
"event_setup_length_error": "הגדרת אירוע: משך הזמן חייב להיות לפחות דקה אחת.",
"availability_schedules": "לוחות זמנים לזמינוּת",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "עריכת הזמינות של משתמש: {{username}}",
"resend_invitation": "שליחת ההזמנה מחדש",
"invitation_resent": "ההזמנה נשלחה מחדש.",
"saml_sso": "SAML",
"add_client": "הוספת לקוח",
"copy_client_secret_info": "לאחר העתקת הסוד, כבר לא תהיה לך אפשרות לראות אותו",
"add_new_client": "הוספת לקוח חדש",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "נעילת אזור הזמן בדף ההזמנות",
"description_lock_timezone_toggle_on_booking_page": "כדי לנעול את אזור הזמן בדף ההזמנות – שימושי לאירועים אישיים.",
"extensive_whitelabeling": "תהליך הטמעה והנדסת תמיכה אישי",
"need_help": "צריך עזרה?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -333,6 +333,5 @@
"dark": "Tamna",
"automatically_adjust_theme": "Automatski prilagodite temu na temelju preferencija pozvanih osoba",
"user_dynamic_booking_disabled": "Neki od korisnika u grupi trenutno su onemogućili dinamičke grupne rezervacije",
"full_name": "Puno ime",
"insights_all_org_filter": "Sve"
"full_name": "Puno ime"
}
@@ -205,9 +205,5 @@
"minute_timeUnit": "Perc",
"remove_app": "Alkalmazás eltávolítása",
"yes_remove_app": "Igen, távolítsd el az alkalmazást",
"web_conference": "Online konferencia",
"timezone_variable": "Időzóna",
"location_variable": "Helyszín",
"additional_notes_variable": "Egyéb jegyzetek",
"insights_all_org_filter": "Minden alkalmazás"
"web_conference": "Online konferencia"
}
@@ -125,5 +125,6 @@
"logged_out": "Telah keluar",
"please_try_again_and_contact_us": "Silakan coba lagi dan hubungi kami jika masalah terulang.",
"incorrect_2fa_code": "Kode dua faktor salah.",
"no_account_exists": "Tidak ada akun dengan email tersebut."
"no_account_exists": "Tidak ada akun dengan email tersebut.",
"need_help": "Butuh bantuan?"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "il campo \"username\" può essere compilato solo con il nome utente dei membri assegnati",
"assign_to": "Assegna a",
"add_members": "Aggiungi membri...",
"count_members_one": "{{count}} membro",
"count_members_other": "{{count}} membri",
"no_assigned_members": "Nessun membro assegnato",
"assigned_to": "Assegnato a",
"start_assigning_members_above": "Inizia ad assegnare membri in alto",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} fornirà un URL di riunione Tandem.",
"cal_provide_video_meeting_url": "{{appName}} fornirà un URL di riunione Daily video.",
"cal_provide_jitsi_meeting_url": "{{appName}} fornirà un URL di riunione Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} fornirà un URL di riunione Huddle01 web3 video.",
"cal_provide_huddle01_meeting_url": "{{appName}} fornirà un URL di riunione Huddle01 video.",
"cal_provide_teams_meeting_url": "{{appName}} fornirà un URL per la riunione MS Teams. NOTA: È NECESSARIO POSSEDERE UN ACCOUNT LAVORATIVO O SCOLASTICO",
"require_payment": "Richiedi Pagamento",
"you_need_to_add_a_name": "Indica un nome",
@@ -849,7 +847,6 @@
"next_step": "Salta passo",
"prev_step": "Passo precedente",
"install": "Installa",
"install_paid_app": "Abbonati",
"installed": "Installato",
"active_install_one": "{{count}} installazione attiva",
"active_install_other": "{{count}} installazioni attive",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Aggiungi un'app di analisi per le tue pagine di prenotazione",
"no_category_apps_description_automation": "Aggiungi un'app di automazione da usare",
"no_category_apps_description_other": "Aggiungi qualsiasi altra app per fare altre attività",
"no_category_apps_description_web3": "Aggiungi un'app Web3 per le tue pagine di prenotazione",
"no_category_apps_description_messaging": "Aggiungi un'app di messaggistica per impostare notifiche e promemoria personalizzati",
"no_category_apps_description_crm": "Aggiungi un'app CRM per tenere traccia delle persone che hai incontrato",
"installed_app_calendar_description": "Imposta uno o più calendari per controllare i conflitti ed evitare doppie prenotazioni.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Tutte le app installate appartenenti ad altre categorie.",
"installed_app_conferencing_description": "Scegli quali app di conferenza utilizzare",
"installed_app_automation_description": "Imposta quali app di automazione usare",
"installed_app_web3_description": "Imposta quali app Web3 usare per le tue pagine di prenotazione",
"installed_app_messaging_description": "Scegli le app di messaggistica da usare per impostare notifiche e promemoria personalizzati",
"installed_app_crm_description": "Scegli quali app CRM utilizzare per tenere traccia delle persone che hai incontrato",
"analytics": "Analisi",
@@ -1153,6 +1148,7 @@
"active_on": "Data attivazione",
"workflow_updated_successfully": "Flusso di lavoro {{workflowName}} aggiornato correttamente",
"premium_to_standard_username_description": "Questo è un nome utente standard e l'aggiornamento ti porterà alla fatturazione per il downgrade.",
"premium_username": "Questo è un nome utente premium, ottieni il tuo per {{price}}",
"current": "Corrente",
"premium": "premium",
"standard": "standard",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Partecipante",
"event_date_variable": "Data evento",
"event_time_variable": "Ora evento",
"timezone_variable": "Timezone",
"location_variable": "Luogo",
"additional_notes_variable": "Note aggiuntive",
"organizer_name_variable": "Nome organizzatore",
"app_upgrade_description": "Per poter utilizzare questa funzionalità, è necessario passare a un account Pro.",
"invalid_number": "Numero di telefono non valido",
@@ -1309,7 +1302,6 @@
"profile_picture": "Immagine del profilo",
"upload": "Carica",
"add_profile_photo": "Aggiungi foto del profilo",
"web3": "Web3",
"token_address": "Indirizzo token",
"blockchain": "Blockchain",
"old_password": "Vecchia password",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Connetti app di automazione",
"connect_analytics_apps": "Connetti app di analisi",
"connect_other_apps": "Connetti altre app",
"connect_web3_apps": "Connetti app Web3",
"connect_messaging_apps": "Connetti app di messaggistica",
"connect_crm_apps": "Connetti app CRM",
"current_step_of_total": "Passo {{currentStep}} di {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Tu",
"resend_email": "Invia di nuovo l'e-mail",
"member_already_invited": "Membro già invitato",
"already_in_use_error": "Nome utente già in uso",
"enter_email_or_username": "Immettere un indirizzo e-mail o un nome utente",
"team_name_taken": "Nome già utilizzato",
"must_enter_team_name": "Necessario immettere un nome per il team",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Le nuove norme sullo spam di Google potrebbero impedirti di ricevere email e notifiche di calendario in merito a questa prenotazione.",
"resolve": "Risolvi",
"no_organization_slug": "Si è verificato un errore durante la creazione dei team per questa organizzazione. Slug URL mancante.",
"org_name": "Nome dell'organizzazione",
"org_url": "URL dell'organizzazione",
"copy_link_org": "Copia link all'organizzazione",
"404_the_org": "L'organizzazione",
"404_the_team": "Il team",
"404_claim_entity_org": "Richiedi il sottodominio per la tua organizzazione",
"404_claim_entity_team": "Richiedi l'accesso a questo team per iniziare a gestire le pianificazioni collettivamente",
"insights_all_org_filter": "Tutte le app",
"insights_team_filter": "Team: {{teamName}}",
"insights_user_filter": "Utente: {{userName}}",
"insights_subtitle": "Visualizza insight sulle prenotazioni per tutti i tuoi eventi",
@@ -2005,7 +1994,6 @@
"select_date": "Seleziona la data",
"see_all_available_times": "Vedi tutti gli orari disponibili",
"org_team_names_example": "ad es., team di marketing",
"org_team_names_example_1": "ad es., team di marketing",
"org_team_names_example_2": "ad es., team vendite",
"org_team_names_example_3": "ad es., team di progettazione",
"org_team_names_example_4": "ad es., team di ingegneria",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Includi evento del calendario",
"oAuth": "OAuth",
"recently_added": "Aggiunti di recente",
"connect_all_calendars": "Collega tutti i tuoi calendari",
"workflow_automation": "Automazione dei flussi di lavoro",
"scheduling_for_your_team": "Automazione dei flussi di lavoro",
"no_members_found": "Nessun membro trovato",
"event_setup_length_error": "Impostazione evento: la durata deve essere di almeno 1 minuto.",
"availability_schedules": "Calendario disponibilità",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Modifica la disponibilità dell'utente: {{username}}",
"resend_invitation": "Invia di nuovo l'invito",
"invitation_resent": "L'invito è stato inviato di nuovo.",
"saml_sso": "SAML",
"add_client": "Aggiungi cliente",
"copy_client_secret_info": "Dopo aver copiato questa parola segreta non sarai più in grado di vederla",
"add_new_client": "Aggiungi nuovo cliente",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Blocca fuso orario nella pagina di prenotazione",
"description_lock_timezone_toggle_on_booking_page": "Per bloccare il fuso orario nella pagina di prenotazione, utile per gli eventi di persona.",
"extensive_whitelabeling": "Assistenza per l'onboarding e supporto tecnico dedicati",
"need_help": "Hai bisogno di aiuto?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Aggiungi le tue nuove stringhe qui sopra ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"ユーザー名\" には割り当てられたメンバーのユーザー名が入ります",
"assign_to": "割り当て先",
"add_members": "メンバーを追加…",
"count_members_one": "{{count}} 人のメンバー",
"count_members_other": "{{count}} 人のメンバー",
"no_assigned_members": "割り当てられたメンバーはいません",
"assigned_to": "割り当て先",
"start_assigning_members_above": "上記のメンバーに対する割り当てを開始する",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} は Tandem ミーティングの URL を提供する。",
"cal_provide_video_meeting_url": "{{appName}} はビデオミーティングの URL を提供する。",
"cal_provide_jitsi_meeting_url": "Cal は Jitsi Meet の URL を提供する。",
"cal_provide_huddle01_meeting_url": "{{appName}} は Huddle01 web3 ビデオミーティングの URL を提供する。",
"cal_provide_huddle01_meeting_url": "{{appName}} は Huddle01 ビデオミーティングの URL を提供する。",
"cal_provide_teams_meeting_url": "{{appName}} は、MS Teams のミーティングの URL を提供する。注: 職場や学校のアカウントが必要です。",
"require_payment": "有料",
"you_need_to_add_a_name": "名前を追加する必要があります",
@@ -849,7 +847,6 @@
"next_step": "手順をスキップ",
"prev_step": "前の手順",
"install": "インストール",
"install_paid_app": "サブスクライブ",
"installed": "インストール済み",
"active_install_one": "{{count}} 件のアクティブなインストール",
"active_install_other": "{{count}} 件のアクティブなインストール",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "予約ページに分析アプリを追加する",
"no_category_apps_description_automation": "使用する自動化アプリを追加する",
"no_category_apps_description_other": "その他のアプリを追加して、様々なことを実現しましょう",
"no_category_apps_description_web3": "予約ページに Web3 アプリを追加",
"no_category_apps_description_messaging": "カスタム通知とリマインダーを設定するには、メッセージングアプリを追加します",
"no_category_apps_description_crm": "会った人を記録するには、CRM アプリを追加します",
"installed_app_calendar_description": "ダブルブッキングを防ぐために、カレンダーの重複をチェックするように設定します。",
@@ -916,7 +912,6 @@
"installed_app_other_description": "その他のカテゴリーからインストールしたすべてのアプリ。",
"installed_app_conferencing_description": "どの会議アプリを使用するかを設定します",
"installed_app_automation_description": "どの自動化アプリを使用するかを構成する",
"installed_app_web3_description": "予約ページでどの Web3 アプリを使用するかを構成します",
"installed_app_messaging_description": "カスタム通知とリマインダーの設定にどのメッセージングアプリを使用するかを設定します",
"installed_app_crm_description": "会った人を記録するのにどの CRM アプリを使用するかを設定します",
"analytics": "分析",
@@ -1153,6 +1148,7 @@
"active_on": "有効日時",
"workflow_updated_successfully": "{{workflowName}} が正常に更新されました",
"premium_to_standard_username_description": "これは標準のユーザー名です。更新するとダウングレードを行うための請求画面に移動します。",
"premium_username": "これはプレミアムユーザー名ですので、{{price}} で入手してください",
"current": "現在",
"premium": "プレミアム",
"standard": "スタンダード",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "出席者の名前",
"event_date_variable": "イベントの日付",
"event_time_variable": "イベントの時間",
"timezone_variable": "タイムゾーン",
"location_variable": "場所",
"additional_notes_variable": "備考",
"organizer_name_variable": "主催者名",
"app_upgrade_description": "この機能を利用するには、Pro アカウントへのアップグレードが必要です。",
"invalid_number": "電話番号が無効です",
@@ -1309,7 +1302,6 @@
"profile_picture": "プロフィール写真",
"upload": "アップロード",
"add_profile_photo": "プロフィール写真を追加",
"web3": "Web3",
"token_address": "トークンアドレス",
"blockchain": "ブロックチェーン",
"old_password": "古いパスワード",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "自動化アプリを接続する",
"connect_analytics_apps": "分析アプリを接続する",
"connect_other_apps": "その他のアプリを接続",
"connect_web3_apps": "Web3 アプリを接続",
"connect_messaging_apps": "メッセージングアプリを接続する",
"connect_crm_apps": "CRM アプリを接続する",
"current_step_of_total": "ステップ {{currentStep}}/{{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "あなた",
"resend_email": "メールを再送する",
"member_already_invited": "メンバーは既に招待されています",
"already_in_use_error": "ユーザー名は既に使われています",
"enter_email_or_username": "メールアドレスまたはユーザー名を入力してください",
"team_name_taken": "この名前は既に使用されています",
"must_enter_team_name": "チーム名の入力は必須です",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Google の新しい迷惑メールポリシーにより、この予約に関するメールやカレンダー通知を受け取れなくなる可能性があります。",
"resolve": "解決する",
"no_organization_slug": "この組織でチームを作成する際にエラーが発生しました。URL スラッグがありません。",
"org_name": "組織名",
"org_url": "組織の URL",
"copy_link_org": "組織へのリンクをコピーする",
"404_the_org": "組織",
"404_the_team": "チーム",
"404_claim_entity_org": "組織のサブドメインを取得する",
"404_claim_entity_team": "このチームの一員になって、今後はスケジュールをまとめて管理しましょう",
"insights_all_org_filter": "すべて",
"insights_team_filter": "チーム: {{teamName}}",
"insights_user_filter": "ユーザー: {{userName}}",
"insights_subtitle": "イベント全体での予約に関する Insights を表示する",
@@ -2005,7 +1994,6 @@
"select_date": "日付を選ぶ",
"see_all_available_times": "出席できる時間帯をすべて表示",
"org_team_names_example": "例:マーケティングチーム",
"org_team_names_example_1": "例:マーケティングチーム",
"org_team_names_example_2": "例:営業チーム",
"org_team_names_example_3": "例:デザインチーム",
"org_team_names_example_4": "例:エンジニアリングチーム",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "カレンダーのイベントを含める",
"oAuth": "OAuth",
"recently_added": "最近追加されました",
"connect_all_calendars": "すべてのカレンダーに接続",
"workflow_automation": "ワークフローの自動化",
"scheduling_for_your_team": "ワークフローの自動化",
"no_members_found": "メンバーが見つかりません",
"event_setup_length_error": "イベント設定:時間は 1 分以上でなくてはいけません。",
"availability_schedules": "空き状況一覧",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "ユーザーの空き状況を編集:{{username}}",
"resend_invitation": "招待を再送",
"invitation_resent": "招待は再送されました。",
"saml_sso": "SAML",
"add_client": "顧客を追加",
"copy_client_secret_info": "このシークレットをコピーすると、もう表示できなくなります",
"add_new_client": "新しい顧客を追加",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "予約ページのタイムゾーンを固定する",
"description_lock_timezone_toggle_on_booking_page": "予約ページのタイムゾーンを固定するためのもので、対面のイベントに役立ちます。",
"extensive_whitelabeling": "専用のオンボーディングサポートとエンジニアリングサポート",
"need_help": "サポートが必要ですか?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ この上に新しい文字列を追加してください ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+7 -14
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"username\"은 할당된 회원의 사용자 이름으로 채워집니다",
"assign_to": "할당 대상",
"add_members": "회원 추가...",
"count_members_one": "회원 {{count}}명",
"count_members_other": "회원 {{count}}명",
"no_assigned_members": "할당된 회원 없음",
"assigned_to": "할당된 이벤트 유형",
"start_assigning_members_above": "위에서 회원 할당 시작",
@@ -849,7 +847,6 @@
"next_step": "건너뛰기",
"prev_step": "이전 단계",
"install": "설치",
"install_paid_app": "구독",
"installed": "설치됨",
"active_install_one": "{{count}}개 활성 설치",
"active_install_other": "{{count}}개 활성 설치",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "예약 페이지에 대한 분석 앱 추가",
"no_category_apps_description_automation": "사용할 자동화 앱 추가",
"no_category_apps_description_other": "모든 종류의 작업을 수행하는 다른 유형의 앱을 추가합니다",
"no_category_apps_description_web3": "예약 페이지에 web3 앱 추가",
"no_category_apps_description_messaging": "사용자 정의 알림 및 미리 알림을 설정하려면 메시징 앱을 추가하세요",
"no_category_apps_description_crm": "CRM 앱을 추가하여 만났던 사람을 추적하세요",
"installed_app_calendar_description": "중복 예약을 방지하기 위해 충돌을 확인하도록 캘린더를 설정합니다.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "기타 카테고리에서 설치된 모든 앱.",
"installed_app_conferencing_description": "사용할 회의 앱 구성",
"installed_app_automation_description": "사용할 자동화 앱 구성",
"installed_app_web3_description": "예약 페이지에 사용할 web3 앱 구성",
"installed_app_messaging_description": "사용자 정의 알림 및 미리 알림 설정에 사용할 메시징 앱 구성",
"installed_app_crm_description": "만났던 사람을 추적하는 데 사용할 CRM 앱 구성",
"analytics": "분석",
@@ -1153,6 +1148,7 @@
"active_on": "유효일",
"workflow_updated_successfully": "{{workflowName}} 워크플로 업데이트 완료",
"premium_to_standard_username_description": "이것은 표준 사용자 이름이며 업데이트하면 다운그레이드를 위한 청구로 이동합니다.",
"premium_username": "프리미엄 사용자 이름입니다. {{price}}에 구입하세요",
"current": "기존",
"premium": "프리미엄",
"standard": "표준",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "참석자",
"event_date_variable": "이벤트 날짜",
"event_time_variable": "이벤트 시간",
"timezone_variable": "시간대",
"location_variable": "위치",
"additional_notes_variable": "추가 참고 사항",
"organizer_name_variable": "주최자 이름",
"app_upgrade_description": "이 기능을 사용하려면 Pro 계정으로 업그레이드해야 합니다.",
"invalid_number": "유효하지 않은 전화 번호",
@@ -1309,7 +1302,6 @@
"profile_picture": "프로필 사진",
"upload": "업로드",
"add_profile_photo": "프로필 사진 추가",
"web3": "Web3",
"token_address": "토큰 주소",
"blockchain": "블록체인",
"old_password": "이전 비밀번호",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "자동화 앱 연결",
"connect_analytics_apps": "분석 앱 연결",
"connect_other_apps": "기타 앱 연결",
"connect_web3_apps": "web3 앱 연결",
"connect_messaging_apps": "메시징 앱 연결",
"connect_crm_apps": "CRM 앱 연결",
"current_step_of_total": "{{maxSteps}} 중 {{currentStep}} 단계",
@@ -1536,6 +1527,7 @@
"you": "귀하",
"resend_email": "이메일 다시 보내기",
"member_already_invited": "구성원이 이미 초대되었습니다",
"already_in_use_error": "이미 사용 중인 사용자명입니다.",
"enter_email_or_username": "이메일 또는 사용자 이름을 입력하세요",
"team_name_taken": "이미 사용 중인 이름입니다",
"must_enter_team_name": "팀 이름을 입력하세요",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Google의 새로운 스팸 정책으로 인해 이 예약에 대한 이메일 및 캘린더 알림을 받지 못할 수 있습니다.",
"resolve": "해결",
"no_organization_slug": "이 조직의 팀을 만드는 중 오류가 발생했습니다. URL 슬러그가 없습니다.",
"org_name": "조직 이름",
"org_url": "조직 URL",
"copy_link_org": "조직 링크 복사",
"404_the_org": "조직",
"404_the_team": "팀",
"404_claim_entity_org": "조직에 대한 하위 도메인 요청",
"404_claim_entity_team": "이 팀을 요청하고 일괄적으로 일정 관리 시작",
"insights_all_org_filter": "모두",
"insights_team_filter": "팀: {{teamName}}",
"insights_user_filter": "사용자: {{userName}}",
"insights_subtitle": "이벤트 전반에 걸친 예약 Insights 보기",
@@ -2005,7 +1994,6 @@
"select_date": "날짜 선택",
"see_all_available_times": "모든 사용 가능한 시간 보기",
"org_team_names_example": "예: 마케팅 팀",
"org_team_names_example_1": "예: 마케팅 팀",
"org_team_names_example_2": "예: 세일즈 팀",
"org_team_names_example_3": "예: 디자인 팀",
"org_team_names_example_4": "예: 엔지니어링 팀",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "캘린더 이벤트 포함",
"oAuth": "OAuth",
"recently_added": "최근 추가됨",
"connect_all_calendars": "모든 캘린더 연결",
"workflow_automation": "워크플로 자동화",
"scheduling_for_your_team": "워크플로 자동화",
"no_members_found": "구성원 없음",
"event_setup_length_error": "이벤트 설정: 지속 시간은 1분 이상이어야 합니다.",
"availability_schedules": "사용 가능한 일정",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "사용자 가용성 편집: {{username}}",
"resend_invitation": "초대장 다시 보내기",
"invitation_resent": "초대장을 다시 보냈습니다.",
"saml_sso": "SAML",
"add_client": "클라이언트 추가",
"copy_client_secret_info": "비밀글을 복사한 후에는 더 이상 볼 수 없습니다",
"add_new_client": "새 클라이언트 추가",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "예약 페이지의 시간대 잠금",
"description_lock_timezone_toggle_on_booking_page": "예약 페이지에서 시간대를 잠그는 기능은 대면 이벤트에 유용합니다.",
"extensive_whitelabeling": "전담 온보딩 및 엔지니어링 지원",
"need_help": "도움이 필요하세요?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ 여기에 새 문자열을 추가하세요 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"username\" wordt gevuld met de gebruikersnaam van de toegewezen leden",
"assign_to": "Toewijzen aan",
"add_members": "Leden toevoegen...",
"count_members_one": "{{count}} lid",
"count_members_other": "{{count}} leden",
"no_assigned_members": "Geen toegewezen leden",
"assigned_to": "Toegewezen aan",
"start_assigning_members_above": "Begin met het toewijzen van leden hierboven",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} zal een Tandem meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_video_meeting_url": "{{appName}} zal een Daily meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_jitsi_meeting_url": "{{appName}} zal een Jitsi Meet meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_huddle01_meeting_url": "{{appName}} zal een Huddle01 web3 meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_huddle01_meeting_url": "{{appName}} zal een Huddle01 meeting-URL meegeven in de afspraak bevestiging.",
"cal_provide_teams_meeting_url": "{{appName}} geeft een vergaderings-URL voor MS Teams. OPMERKING: MOET EEN WERK- OF SCHOOLACCOUNT HEBBEN",
"require_payment": "Betaling vereisen",
"you_need_to_add_a_name": "U moet een naam toevoegen",
@@ -849,7 +847,6 @@
"next_step": "Stap overslaan",
"prev_step": "Vorige stap",
"install": "Installeren",
"install_paid_app": "Abonneren",
"installed": "Geinstalleerd",
"active_install_one": "{{count}} actieve installatie",
"active_install_other": "{{count}} actieve installaties",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Voeg een analyse-app toe aan uw boekingspagina's",
"no_category_apps_description_automation": "Voeg een automatiseringsapp toe om te gebruiken",
"no_category_apps_description_other": "Voeg een ander type app toe om allerlei soorten dingen te doen",
"no_category_apps_description_web3": "Voeg een web3-app toe aan uw boekingspagina's",
"no_category_apps_description_messaging": "Voeg een berichtenapp toe om aangepaste meldingen en herinneringen in te stellen",
"no_category_apps_description_crm": "Voeg een CRM-app toe om bij te houden met wie u heeft ontmoet",
"installed_app_calendar_description": "Stel de agenda('s) in om te controleren op conflicten om dubbele boekingen te voorkomen.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Alle geïnstalleerde apps uit andere categorieën.",
"installed_app_conferencing_description": "Configureer welke conferentieapps moeten worden gebruikt",
"installed_app_automation_description": "Configureer welke automatiseringsapps moeten worden gebruikt",
"installed_app_web3_description": "Configureer welke web3-apps moeten worden gebruikt voor uw boekingspagina's",
"installed_app_messaging_description": "Configureer welke berichtenapps moeten worden gebruikt voor het instellen van aangepaste meldingen en herinneringen",
"installed_app_crm_description": "Configureer welke CRM-apps moeten worden gebruikt voor het bijhouden van wie u hebt ontmoet",
"analytics": "Analyse",
@@ -1153,6 +1148,7 @@
"active_on": "Actief op",
"workflow_updated_successfully": "Werkstroom {{workflowName}} bijgewerkt",
"premium_to_standard_username_description": "Dit is een standaard gebruikersnaam. Als u deze bijwerkt, gaat u naar facturering om te downgraden.",
"premium_username": "Dit is een premium gebruikersnaam, koop de uwe voor {{price}}",
"current": "Huidig",
"premium": "premium",
"standard": "standaard",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Deelnemer",
"event_date_variable": "Datum gebeurtenis",
"event_time_variable": "Tijd gebeurtenis",
"timezone_variable": "Tijdzone",
"location_variable": "Locatie",
"additional_notes_variable": "Aanvullende opmerkingen",
"organizer_name_variable": "Naam organisator",
"app_upgrade_description": "Om deze functie te gebruiken, moet u upgraden naar een Pro-account.",
"invalid_number": "Ongeldig telefoonnummer",
@@ -1309,7 +1302,6 @@
"profile_picture": "Profielafbeelding",
"upload": "Uploaden",
"add_profile_photo": "Profielfoto toevoegen",
"web3": "Web3",
"token_address": "Tokenadres",
"blockchain": "Blockchain",
"old_password": "Oude wachtwoord",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Koppel automatiseringsapps",
"connect_analytics_apps": "Koppel analyse-apps",
"connect_other_apps": "Andere apps koppelen",
"connect_web3_apps": "Web3-apps koppelen",
"connect_messaging_apps": "Berichtenapps koppelen",
"connect_crm_apps": "CRM-apps koppelen",
"current_step_of_total": "Stap {{currentStep}} van {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "U",
"resend_email": "E-mail opnieuw verzenden",
"member_already_invited": "Lid is al uitgenodigd",
"already_in_use_error": "Gebruikersnaam al in gebruik",
"enter_email_or_username": "Voer een e-mailadres of gebruikersnaam in",
"team_name_taken": "Deze naam is al in gebruik",
"must_enter_team_name": "Moet een teamnaam invoeren",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Het nieuwe spambeleid van Google kan ervoor zorgen dat u geen e-mail- en agendameldingen over deze boeking ontvangt.",
"resolve": "Oplossen",
"no_organization_slug": "Er is een fout opgetreden bij het maken van teams voor deze organisatie. Ontbrekende URL-slug.",
"org_name": "Organisatienaam",
"org_url": "Organisatie-URL",
"copy_link_org": "Link kopiëren naar organisatie",
"404_the_org": "De organisatie",
"404_the_team": "Het team",
"404_claim_entity_org": "Claim uw subdomein voor uw organisatie",
"404_claim_entity_team": "Claim dit team en begin met het gezamenlijk beheren van planningen",
"insights_all_org_filter": "Alle apps",
"insights_team_filter": "Team: {{teamName}}",
"insights_user_filter": "Gebruiker: {{userName}}",
"insights_subtitle": "Bekijk boekingsinsights voor al uw gebeurtenissen",
@@ -2005,7 +1994,6 @@
"select_date": "Datum selecteren",
"see_all_available_times": "Bekijk alle beschikbare tijden",
"org_team_names_example": "bijvoorbeeld Marketingteam",
"org_team_names_example_1": "bijvoorbeeld Marketingteam",
"org_team_names_example_2": "bijvoorbeeld Verkoopteam",
"org_team_names_example_3": "bijvoorbeeld Ontwerpteam",
"org_team_names_example_4": "bijvoorbeeld Engineeringteam",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Agendagebeurtenis opnemen",
"oAuth": "OAuth",
"recently_added": "Recent toegevoegd",
"connect_all_calendars": "Koppel al uw agenda's",
"workflow_automation": "Automatisering van de werkstroom",
"scheduling_for_your_team": "Automatisering van de werkstroom",
"no_members_found": "Geen leden gevonden",
"event_setup_length_error": "Gebeurtenisconfiguratie: de duur moet minimaal 1 minuut zijn.",
"availability_schedules": "Beschikbaarheidsschema's",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Beschikbaarheid van gebruiker bewerken: {{username}}",
"resend_invitation": "Uitnodiging opnieuw versturen",
"invitation_resent": "De uitnodiging is opnieuw verstuurd.",
"saml_sso": "SAML",
"add_client": "Klant toevoegen",
"copy_client_secret_info": "Na het kopiëren van het geheim kunt u het niet meer bekijken",
"add_new_client": "Nieuwe klant toevoegen",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Tijdzone vergrendelen op boekingspagina",
"description_lock_timezone_toggle_on_booking_page": "Om de tijdzone op de boekingspagina te vergrendelen, handig voor persoonlijke gebeurtenissen.",
"extensive_whitelabeling": "Speciale onboarding en technische ondersteuning",
"need_help": "Hulp nodig?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Voeg uw nieuwe strings hierboven toe ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -670,7 +670,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} vil angi en URL for Tandem-møtet.",
"cal_provide_video_meeting_url": "{{appName}} vil angi en videomøte-URL.",
"cal_provide_jitsi_meeting_url": "Vi vil generere en Jitsi Meet URL for deg.",
"cal_provide_huddle01_meeting_url": "{{appName}} vil angi en Huddle01 web3 videomøte-URL.",
"cal_provide_huddle01_meeting_url": "{{appName}} vil angi en Huddle01 videomøte-URL.",
"cal_provide_teams_meeting_url": "{{appName}} vil angi en URL for MS Teams møte. MERK: MÅ HA EN ARBEIDS- ELLER SKOLEKONTO",
"require_payment": "Krev Betaling",
"commission_per_transaction": "provisjon per transaksjon",
@@ -706,7 +706,6 @@
"next_step": "Hopp over trinn",
"prev_step": "Forrige trinn",
"install": "Installer",
"install_paid_app": "Abonner",
"installed": "Installert",
"active_install_one": "{{count}} aktiv installasjon",
"active_install_other": "{{count}} aktive installasjoner",
@@ -1035,9 +1034,6 @@
"attendee_name_variable": "Deltaker",
"event_date_variable": "Hendelsesdato",
"event_time_variable": "Tidspunkt for hendelse",
"timezone_variable": "Tidssone",
"location_variable": "Sted",
"additional_notes_variable": "Tilleggsinformasjon",
"app_upgrade_description": "For å bruke denne funksjonen må du oppgradere til en Pro-konto.",
"invalid_number": "Ugyldig telefonnummer",
"navigate": "Naviger",
@@ -1116,7 +1112,6 @@
"profile_picture": "Profilbilde",
"upload": "Last opp",
"add_profile_photo": "Legg til profilbilde",
"web3": "Web3",
"token_address": "Token Adresse",
"blockchain": "Blockchain",
"old_password": "Gammelt passord",
@@ -1391,5 +1386,5 @@
"configure": "Konfigurer",
"sso_configuration": "Enkel Pålogging",
"booking_confirmation_failed": "Booking-bekreftelse feilet",
"insights_all_org_filter": "Alt"
"need_help": "Trenger du hjelp?"
}
+7 -14
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "Pole „username” zostanie wypełnione nazwą przypisanego użytkownika",
"assign_to": "Przypisz do",
"add_members": "Dodaj członków...",
"count_members_one": "{{count}} członek",
"count_members_other": "{{count}} członków",
"no_assigned_members": "Brak przypisanych członków",
"assigned_to": "Przypisano do:",
"start_assigning_members_above": "Rozpocznij przypisywanie członków powyżej",
@@ -849,7 +847,6 @@
"next_step": "Pomiń krok",
"prev_step": "Poprzedni krok",
"install": "Zainstaluj",
"install_paid_app": "Subskrybuj",
"installed": "Zainstalowane",
"active_install_one": "Aktywne instalacje: {{count}}",
"active_install_other": "Aktywne instalacje: {{count}}",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Dodaj aplikację analityczną do stron rezerwacji",
"no_category_apps_description_automation": "Dodaj aplikację automatyzującą, która ma być używana",
"no_category_apps_description_other": "Dodaj dowolny typ aplikacji, aby uzyskać dostęp do różnych innych funkcji",
"no_category_apps_description_web3": "Dodaj aplikację web3 do stron rezerwacji",
"no_category_apps_description_messaging": "Dodaj aplikację do wysyłania wiadomości, aby skonfigurować własne powiadomienia i przypomnienia",
"no_category_apps_description_crm": "Dodaj aplikację CRM, aby monitorować to, z kim się spotykasz",
"installed_app_calendar_description": "Ustaw kalendarze, aby wykrywać konflikty i unikać podwójnych rezerwacji.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Wszystkie zainstalowane aplikacje z innych kategorii.",
"installed_app_conferencing_description": "Skonfiguruj, z których aplikacji konferencyjnych chcesz korzystać",
"installed_app_automation_description": "Skonfiguruj, które aplikacje automatyzujące mają być używane",
"installed_app_web3_description": "Skonfiguruj aplikacje web3, które mają być używane na stronach rezerwacji",
"installed_app_messaging_description": "Skonfiguruj aplikacje, które będą używane do ustawiania niestandardowych powiadomień i przypomnień",
"installed_app_crm_description": "Skonfiguruj aplikacje CRM, za pomocą których chcesz monitorować to, z kim się spotykasz",
"analytics": "Analityka",
@@ -1153,6 +1148,7 @@
"active_on": "Aktywny dnia",
"workflow_updated_successfully": "Pomyślnie zaktualizowano przepływ pracy {{workflowName}}",
"premium_to_standard_username_description": "To standardowa nazwa użytkownika i aktualizacja spowoduje przejście do rozliczenia za zmianę wersji na niższą.",
"premium_username": "To nazwa użytkownika premium, możesz ją mieć za {{price}}.",
"current": "Bieżąca",
"premium": "Premium",
"standard": "Standardowa",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Uczestnik",
"event_date_variable": "Data wydarzenia",
"event_time_variable": "Godzina wydarzenia",
"timezone_variable": "Strefa Czasowa",
"location_variable": "Lokalizacja",
"additional_notes_variable": "Dodatkowe uwagi",
"organizer_name_variable": "Nazwa organizatora",
"app_upgrade_description": "Aby korzystać z tej funkcji, musisz uaktualnić do konta Pro.",
"invalid_number": "Nieprawidłowy numer telefonu",
@@ -1309,7 +1302,6 @@
"profile_picture": "Zdjęcie profilowe",
"upload": "Prześlij",
"add_profile_photo": "Dodaj zdjęcie profilowe",
"web3": "Web3",
"token_address": "Adres tokena",
"blockchain": "Blockchain",
"old_password": "Stare hasło",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Połącz aplikacje automatyzujące",
"connect_analytics_apps": "Połącz aplikacje analityczne",
"connect_other_apps": "Połącz inne aplikacje",
"connect_web3_apps": "Połącz aplikacje web3",
"connect_messaging_apps": "Podłącz aplikacje do wysyłania wiadomości",
"connect_crm_apps": "Podłącz aplikacje CRM",
"current_step_of_total": "Krok {{currentStep}} z {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Ty",
"resend_email": "Wyślij ponownie wiadomość e-mail",
"member_already_invited": "Członek został już zaproszony",
"already_in_use_error": "Ta nazwa użytkownika jest już używana.",
"enter_email_or_username": "Wprowadź adres e-mail lub nazwę użytkownika",
"team_name_taken": "Ta nazwa jest już zajęta",
"must_enter_team_name": "Musisz wprowadzić nazwę zespołu",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Nowa polityka firmy Google dotycząca spamu może uniemożliwić Ci otrzymywanie powiadomień na temat tej rezerwacji w wiadomościach e-mail i z kalendarza.",
"resolve": "Rozwiąż problem",
"no_organization_slug": "Podczas tworzenia zespołów tej organizacji wystąpił błąd. Brakujący slug adresu URL.",
"org_name": "Nazwa organizacji",
"org_url": "Adres URL organizacji",
"copy_link_org": "Skopiuj link do organizacji",
"404_the_org": "Organizacja",
"404_the_team": "Zespół",
"404_claim_entity_org": "Zajmij subdomenę dla Twojej organizacji",
"404_claim_entity_team": "Dołącz do tego zespołu i zacznij wspólnie zarządzać harmonogramami",
"insights_all_org_filter": "Wszystkie",
"insights_team_filter": "Zespół: {{teamName}}",
"insights_user_filter": "Użytkownik: {{userName}}",
"insights_subtitle": "Wyświetl wskaźniki Insights rezerwacji z różnych wydarzeń",
@@ -2005,7 +1994,6 @@
"select_date": "Wybierz datę",
"see_all_available_times": "Zobacz wszystkie dostępne godziny",
"org_team_names_example": "np. zespół marketingowy",
"org_team_names_example_1": "np. zespół marketingowy",
"org_team_names_example_2": "np. zespół ds. sprzedaży",
"org_team_names_example_3": "np. zespół projektowy",
"org_team_names_example_4": "np. zespół ds. inżynierii",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Uwzględnij wydarzenie z kalendarza",
"oAuth": "OAuth",
"recently_added": "Niedawno dodane",
"connect_all_calendars": "Podłącz wszystkie swoje kalendarze",
"workflow_automation": "Automatyzacja przepływu pracy",
"scheduling_for_your_team": "Automatyzacja przepływu pracy",
"no_members_found": "Nie znaleziono członków",
"event_setup_length_error": "Konfiguracja wydarzenia: czas trwania musi wynosić co najmniej minutę.",
"availability_schedules": "Harmonogramy dostępności",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Edytuj dostępność użytkownika: {{username}}",
"resend_invitation": "Ponownie wyślij zaproszenie",
"invitation_resent": "Zaproszenie zostało wysłane ponownie.",
"saml_sso": "SAML",
"add_client": "Dodaj klienta",
"copy_client_secret_info": "Po skopiowaniu sekretu nie będzie można go już przeglądać",
"add_new_client": "Dodaj nowego klienta",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Zablokuj strefę czasową na stronie rezerwacji",
"description_lock_timezone_toggle_on_booking_page": "Aby zablokować strefę czasową na stronie rezerwacji (przydatne dla wydarzeń stacjonarnych).",
"extensive_whitelabeling": "Dedykowane wsparcie w zakresie wdrożenia i obsługi technicznej",
"need_help": "Potrzebujesz pomocy?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Dodaj nowe ciągi powyżej ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "o \"nome de usuário\" será preenchido com o nome de usuário dos membros atribuídos",
"assign_to": "Atribuir a",
"add_members": "Adicionar membros...",
"count_members_one": "{{count}} membro",
"count_members_other": "{{count}} membros",
"no_assigned_members": "Nenhum membro atribuído",
"assigned_to": "Atribuído a",
"start_assigning_members_above": "Começar a atribuir os membros acima",
@@ -849,7 +847,6 @@
"next_step": "Ignorar passo",
"prev_step": "Passo anterior",
"install": "Instalar",
"install_paid_app": "Assinar",
"installed": "Instalado",
"active_install_one": "{{count}} instalação ativa",
"active_install_other": "{{count}} instalações ativas",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Adicione um aplicativo de análise às suas páginas de reservas",
"no_category_apps_description_automation": "Adicione um aplicativo de automação para usar",
"no_category_apps_description_other": "Adicione qualquer outro tipo de aplicativo para fazer todos os tipos de coisas",
"no_category_apps_description_web3": "Adicione um aplicativo web3 para suas páginas de reservas",
"no_category_apps_description_messaging": "Adicione um aplicativo de mensagem para definir lembretes e notificações personalizados",
"no_category_apps_description_crm": "Adicione um aplicativo CRM para acompanhar todos os integrantes das suas reuniões",
"installed_app_calendar_description": "Defina o(s) calendário(s) para verificar se há conflitos e evitar reservas duplas.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Todos os seus aplicativos instalados de outras categorias.",
"installed_app_conferencing_description": "Configure quais aplicativos de conferência serão usados",
"installed_app_automation_description": "Configure quais aplicativos de automação serão usados",
"installed_app_web3_description": "Configure quais aplicativos web3 serão usados nas suas páginas de reservas",
"installed_app_messaging_description": "Configure quais aplicativos de mensagem usar para definir lembretes e notificações",
"installed_app_crm_description": "Configure quais aplicativos CRM usar para acompanhar os integrantes das suas reuniões",
"analytics": "Análise",
@@ -1153,6 +1148,7 @@
"active_on": "Ativar em",
"workflow_updated_successfully": "Fluxo de trabalho {{workflowName}} atualizado com sucesso",
"premium_to_standard_username_description": "Este é um nome de usuário padrão. Para atualizá-lo, levaremos você para fazer downgrade na cobrança.",
"premium_username": "Este é um nome de usuário premium, obtenha o seu por {{price}}",
"current": "Atual",
"premium": "premium",
"standard": "padrão",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Nome do participante",
"event_date_variable": "Data do evento",
"event_time_variable": "Horário do evento",
"timezone_variable": "Fuso Horário",
"location_variable": "Local",
"additional_notes_variable": "Observações adicionais",
"organizer_name_variable": "Nome do organizador",
"app_upgrade_description": "Para usar este recurso, atualize para uma conta Pro.",
"invalid_number": "Número de telefone inválido",
@@ -1309,7 +1302,6 @@
"profile_picture": "Imagem do perfil",
"upload": "Enviar",
"add_profile_photo": "Adicionar foto do perfil",
"web3": "Web3",
"token_address": "Endereço do Token",
"blockchain": "Blockchain",
"old_password": "Senha antiga",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Conecte aplicativos de automação",
"connect_analytics_apps": "Conecte aplicativos de análise",
"connect_other_apps": "Conectar outros aplicativos",
"connect_web3_apps": "Conecte aplicativos web3",
"connect_messaging_apps": "Conectar com aplicativos de mensagem",
"connect_crm_apps": "Conecte aplicativos CRM",
"current_step_of_total": "Passo {{currentStep}} de {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Você",
"resend_email": "Reenviar e-mail",
"member_already_invited": "O membro já foi convidado",
"already_in_use_error": "Este nome de usuário já está em uso",
"enter_email_or_username": "Insira um e-mail ou nome de usuário",
"team_name_taken": "Este nome já está em uso",
"must_enter_team_name": "É preciso inserir um nome de equipe",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "A nova política de spams do Google pode impedir o recebimento de notificações do calendário e e-mail sobre esta reserva.",
"resolve": "Resolver",
"no_organization_slug": "Houve um erro ao criar equipes para esta organizazção. Falta o campo de dados dinâmico do URL.",
"org_name": "Nome da organização",
"org_url": "URL da organização",
"copy_link_org": "Copiar link para a organização",
"404_the_org": "A organização",
"404_the_team": "A equipe",
"404_claim_entity_org": "Solicite o subdomínio para a sua organização",
"404_claim_entity_team": "Solicite esta equipe e comece a gerenciar agendamentos coletivamente",
"insights_all_org_filter": "Todos os apps",
"insights_team_filter": "Equipe: {{teamName}}",
"insights_user_filter": "Usuário: {{userName}}",
"insights_subtitle": "Veja insights de reserva em seus eventos",
@@ -2005,7 +1994,6 @@
"select_date": "Selecione a data",
"see_all_available_times": "Veja todos os horários disponíveis",
"org_team_names_example": "ex.: Time de Marketing",
"org_team_names_example_1": "ex.: Time de Marketing",
"org_team_names_example_2": "ex: Time de Vendas",
"org_team_names_example_3": "ex: Time de Design",
"org_team_names_example_4": "ex: Time de Engenharia",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Incluir evento do calendário",
"oAuth": "OAuth",
"recently_added": "Adicionou recentemente",
"connect_all_calendars": "Conecte todos os seus calendários",
"workflow_automation": "Automação de fluxo de trabalho",
"scheduling_for_your_team": "Automação de fluxo de trabalho",
"no_members_found": "Nenhum membro encontrado",
"event_setup_length_error": "Configuração do evento: deve ter pelo menos 1 minuto de duração.",
"availability_schedules": "Agendamentos de disponibilidade",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Editar disponibilidade do usuário: {{username}}",
"resend_invitation": "Reenviar convite",
"invitation_resent": "O convite foi reenviado.",
"saml_sso": "SAML",
"add_client": "Adicionar cliente",
"copy_client_secret_info": "Depois de copiar o segredo, você não poderá mais visualizá-lo",
"add_new_client": "Adicionar novo cliente",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Bloquear fuso horário na página de reserva",
"description_lock_timezone_toggle_on_booking_page": "Bloquear o fuso horário na página de reservas, útil para eventos presenciais.",
"extensive_whitelabeling": "Marca própria abrangente",
"need_help": "Precisa de ajuda?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Adicione suas novas strings aqui em cima ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+7 -14
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"nome-de-utilizador\" será substituído pelo nome de utilizador dos membros atribuídos",
"assign_to": "Atribuir a",
"add_members": "Adicionar membros...",
"count_members_one": "{{count}} membro",
"count_members_other": "{{count}} membros",
"no_assigned_members": "Sem membros atribuídos",
"assigned_to": "Atribuído a",
"start_assigning_members_above": "Comece a atribuir membros acima",
@@ -849,7 +847,6 @@
"next_step": "Ignorar passo",
"prev_step": "Passo anterior",
"install": "Instalar",
"install_paid_app": "Subscrever",
"installed": "Instalado",
"active_install_one": "{{count}} instalação activa",
"active_install_other": "{{count}} instalações activas",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Adicionar uma aplicação de análise às suas páginas de reservas",
"no_category_apps_description_automation": "Adicionar uma aplicação de automatização a utilizar",
"no_category_apps_description_other": "Adicione qualquer outro tipo de aplicações para fazer todos os tipos de coisas",
"no_category_apps_description_web3": "Adicione uma aplicação web3 às suas páginas de reservas",
"no_category_apps_description_messaging": "Adicione uma aplicação de mensagens para configurar notificações e lembretes personalizados",
"no_category_apps_description_crm": "Adicione uma aplicação de CRM para manter um registo das pessoas com quem se reuniu",
"installed_app_calendar_description": "Defina o(s) calendário(s) para verificar se existem conflitos e assim evitar marcações sobrepostas.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Todas as aplicações instaladas de outras categorias.",
"installed_app_conferencing_description": "Configurar as aplicações de conferência a utilizar",
"installed_app_automation_description": "Configurar as aplicações de automatização a utilizar",
"installed_app_web3_description": "Configure as aplicações web3 a utilizar nas suas páginas de reservas",
"installed_app_messaging_description": "Configurar as aplicações de mensagens a utilizar para definir notificações e lembretes personalizados",
"installed_app_crm_description": "Configurar as aplicações de CRM a utilizar para manter um registo das pessoas com quem se reuniu",
"analytics": "Estatísticas",
@@ -1153,6 +1148,7 @@
"active_on": "Activo em",
"workflow_updated_successfully": "Fluxo de trabalho {{workflowName}} actualizado com sucesso",
"premium_to_standard_username_description": "Este é um nome de utilizador padrão, e ao atualizar irá para a faturação para fazer o downgrade.",
"premium_username": "Este é um nome de utilizador premium. Obtenha o seu por {{price}}",
"current": "Atual",
"premium": "premium",
"standard": "padrão",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Participante",
"event_date_variable": "Data do evento",
"event_time_variable": "Hora do evento",
"timezone_variable": "Fuso Horário",
"location_variable": "Localização",
"additional_notes_variable": "Notas Adicionais",
"organizer_name_variable": "Nome do organizador",
"app_upgrade_description": "Para usar esta funcionalidade, tem de actualizar para uma conta Pro.",
"invalid_number": "Número de telefone inválido",
@@ -1309,7 +1302,6 @@
"profile_picture": "Imagem do perfil",
"upload": "Carregar",
"add_profile_photo": "Adicionar imagem de perfil",
"web3": "Web3",
"token_address": "Endereço do Token",
"blockchain": "Blockchain",
"old_password": "Palavra-passe antiga",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Associar aplicações de automatização",
"connect_analytics_apps": "Associar aplicações de estatística",
"connect_other_apps": "Associe outras aplicações",
"connect_web3_apps": "Associe aplicações web3",
"connect_messaging_apps": "Ligar aplicações de mensagens",
"connect_crm_apps": "Ligar aplicações CRM",
"current_step_of_total": "Passo {{currentStep}} de {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Você",
"resend_email": "Reenviar e-mail",
"member_already_invited": "O membro já foi convidado",
"already_in_use_error": "O nome de utilizador já está a ser utilizado",
"enter_email_or_username": "Especifique um e-mail ou nome de utilizador",
"team_name_taken": "Este nome já está a ser utilizado",
"must_enter_team_name": "Deve especificar um nome para a equipa",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "A nova política de SPAM do Google pode impedir que receba notificações de e-mail e de agenda sobre esta reserva.",
"resolve": "Resolver",
"no_organization_slug": "Ocorreu um erro ao criar equipas para esta organização. Falta o slug do URL.",
"org_name": "Nome da organização",
"org_url": "URL da Organização",
"copy_link_org": "Copiar a ligação para a organização",
"404_the_org": "A organização",
"404_the_team": "A equipa",
"404_claim_entity_org": "Reivindicar o seu subdomínio para a sua organização",
"404_claim_entity_team": "Reivindicar esta equipa e começar a gerir os horários em grupo",
"insights_all_org_filter": "Todos as aplicações",
"insights_team_filter": "Equipa: {{teamName}}",
"insights_user_filter": "Utilizador: {{userName}}",
"insights_subtitle": "Ver informações sobre reservas nos seus eventos",
@@ -2005,7 +1994,6 @@
"select_date": "Selecionar data",
"see_all_available_times": "Ver todos os horários disponíveis",
"org_team_names_example": "Por exemplo, Equipa de Marketing",
"org_team_names_example_1": "Por exemplo, Equipa de Marketing",
"org_team_names_example_2": "Por exemplo, Equipa de Vendas",
"org_team_names_example_3": "Por exemplo, Equipa de Design",
"org_team_names_example_4": "Por exemplo, Equipa de Engenharia",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Incluir evento no calendário",
"oAuth": "OAuth",
"recently_added": "Adicionados recentemente",
"connect_all_calendars": "Associe todos os seus calendários",
"workflow_automation": "Automação de fluxo de trabalho",
"scheduling_for_your_team": "Automação de fluxo de trabalho",
"no_members_found": "Nenhum membro encontrado",
"event_setup_length_error": "Configuração do Evento: a duração deve ser de, pelo menos, 1 minuto.",
"availability_schedules": "Horários de Disponibilidade",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Editar a disponibilidade do utilizador: {{username}}",
"resend_invitation": "Reenviar convite",
"invitation_resent": "O convite foi reenviado.",
"saml_sso": "SAML",
"add_client": "Adicionar cliente",
"copy_client_secret_info": "Após copiar o segredo, não será possível ver o mesmo novamente",
"add_new_client": "Adicionar novo Cliente",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Bloquear fuso horário na página de reserva",
"description_lock_timezone_toggle_on_booking_page": "Para bloquear o fuso horário na página de reservas. Útil para eventos presenciais.",
"extensive_whitelabeling": "Apoio dedicado à integração e engenharia",
"need_help": "Precisa de ajuda?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+7 -14
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "Câmpul „username” va fi completat cu numele de utilizator ale membrilor desemnați",
"assign_to": "Alocați la",
"add_members": "Adăugare membri...",
"count_members_one": "{{count}} membru",
"count_members_other": "{{count}} (de) membri",
"no_assigned_members": "Niciun membru alocat",
"assigned_to": "Alocat la",
"start_assigning_members_above": "Începeți să alocați membri mai sus",
@@ -849,7 +847,6 @@
"next_step": "Sari peste",
"prev_step": "Pas anterior",
"install": "Instalați",
"install_paid_app": "Abonare",
"installed": "Instalat",
"active_install_one": "{{count}} instalare activă",
"active_install_other": "{{count}} (de) instalări active",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Adăugați o aplicație de analiză pentru paginile dvs. de rezervări",
"no_category_apps_description_automation": "Adăugați o aplicație de automatizare de utilizat",
"no_category_apps_description_other": "Adăugați orice alt tip de aplicație pentru a întreprinde diverse acțiuni",
"no_category_apps_description_web3": "Adăugați o aplicație Web3 pentru paginile dvs. de rezervări",
"no_category_apps_description_messaging": "Adăugați o aplicație de mesagerie pentru a configura notificări și mementouri personalizate",
"no_category_apps_description_crm": "Adăugați o aplicație CRM pentru a ține evidența persoanelor cu care v-ați întâlnit",
"installed_app_calendar_description": "Pentru a evita rezervările suprapuse, configurează calendarele astfel încât să poată verifica existența conflictelor.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Toate aplicațiile instalate din alte categorii.",
"installed_app_conferencing_description": "Configurați ce aplicații de conferințe vor fi utilizate",
"installed_app_automation_description": "Configurați care aplicații de automatizare vor fi utilizate",
"installed_app_web3_description": "Configurați care aplicații Web3 vor fi utilizate pentru paginile dvs. de rezervări",
"installed_app_messaging_description": "Definiți ce aplicații de mesagerie vor fi utilizate pentru configurarea de notificări și mementouri personalizate",
"installed_app_crm_description": "Configurați ce aplicații CRM vor fi utilizate pentru a ține evidența persoanelor cu care v-ați întâlnit",
"analytics": "Analiză",
@@ -1153,6 +1148,7 @@
"active_on": "Activ pe",
"workflow_updated_successfully": "Fluxul de lucru {{workflowName}} a fost actualizat cu succes",
"premium_to_standard_username_description": "Acesta este un nume de utilizator standard, iar actualizarea vă va duce la facturare pentru retrogradare.",
"premium_username": "Acesta este un nume de utilizator premium. Puteți obține unul pentru {{price}}",
"current": "Actual",
"premium": "premium",
"standard": "standard",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Participant",
"event_date_variable": "Dată eveniment",
"event_time_variable": "Oră eveniment",
"timezone_variable": "Timezone",
"location_variable": "Loc",
"additional_notes_variable": "Note suplimentare",
"organizer_name_variable": "Nume organizator",
"app_upgrade_description": "Pentru a utiliza această caracteristică, trebuie să faceți upgrade la un cont Pro.",
"invalid_number": "Număr de telefon nevalid",
@@ -1309,7 +1302,6 @@
"profile_picture": "Fotografie de profil",
"upload": "Încărcare",
"add_profile_photo": "Adăugați poză de profil",
"web3": "Web3",
"token_address": "Adresă token",
"blockchain": "Blockchain",
"old_password": "Parola veche",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Conectați aplicații de automatizare",
"connect_analytics_apps": "Conectați aplicații de analiză",
"connect_other_apps": "Conectați alte aplicații",
"connect_web3_apps": "Conectați aplicații Web3",
"connect_messaging_apps": "Conectați aplicații de mesagerie",
"connect_crm_apps": "Conectați aplicații CRM",
"current_step_of_total": "Pasul {{currentStep}} din {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Dvs.",
"resend_email": "Retrimitere e-mail",
"member_already_invited": "Membrul a fost deja invitat",
"already_in_use_error": "Numele de utilizator există deja",
"enter_email_or_username": "Introduceți un e-mail sau un nume de utilizator",
"team_name_taken": "Acest nume este folosit deja",
"must_enter_team_name": "Trebuie să introduceți un nume de echipă",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Noua politică Google privind spamul vă poate împiedica să primiți orice notificare prin e-mail sau calendar cu privire la această rezervare.",
"resolve": "Rezolvați",
"no_organization_slug": "A survenit o eroare la crearea echipelor pentru această organizație. Slug URL lipsă.",
"org_name": "Denumire organizație",
"org_url": "Adresă URL organizație",
"copy_link_org": "Copiați linkul către organizație",
"404_the_org": "Organizația",
"404_the_team": "Echipa",
"404_claim_entity_org": "Revendicați subdomeniul pentru organizația dvs.",
"404_claim_entity_team": "Solicitați să faceți parte din această echipă și începeți să gestionați programe în mod colectiv",
"insights_all_org_filter": "Toate aplicațiile",
"insights_team_filter": "Echipă: {{teamName}}",
"insights_user_filter": "Utilizator: {{userName}}",
"insights_subtitle": "Vizualizați date Insights cu privire la rezervări pentru toate evenimentele dvs.",
@@ -2005,7 +1994,6 @@
"select_date": "Selectați data",
"see_all_available_times": "Vedeți toate orele disponibile",
"org_team_names_example": "de ex. echipa de marketing",
"org_team_names_example_1": "de ex. echipa de marketing",
"org_team_names_example_2": "de ex. echipa de vânzări",
"org_team_names_example_3": "de ex. echipa de design",
"org_team_names_example_4": "de ex. echipa de inginerie",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Includeți eveniment din calendar",
"oAuth": "OAuth",
"recently_added": "Adăugate recent",
"connect_all_calendars": "Conectați-vă toate calendarele",
"workflow_automation": "Automatizarea fluxului de lucru",
"scheduling_for_your_team": "Automatizarea fluxului de lucru",
"no_members_found": "Nu s-a găsit niciun membru",
"event_setup_length_error": "Configurare eveniment: Durata trebuie să fie de cel puțin 1 minut.",
"availability_schedules": "Programe de disponibilitate",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Editați disponibilitatea utilizatorului: {{username}}",
"resend_invitation": "Retrimitere invitație",
"invitation_resent": "Invitația a fost trimisă din nou.",
"saml_sso": "SAML",
"add_client": "Adăugare client",
"copy_client_secret_info": "După ce copiați secretul, nu îl veți mai putea vedea",
"add_new_client": "Adăugare client nou",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Blocare fus orar pe pagina de rezervare",
"description_lock_timezone_toggle_on_booking_page": "Pentru a bloca fusul orar pe pagina de rezervare, util pentru evenimentele în persoană.",
"extensive_whitelabeling": "Asistență dedicată pentru integrare și inginerie",
"need_help": "Aveți nevoie de ajutor?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Adăugați stringurile noi deasupra acestui rând ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+7 -14
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "в «username» указываются имена пользователей, которым будет назначен тип события",
"assign_to": "Назначить участнику",
"add_members": "Добавить участников...",
"count_members_one": "Участники: {{count}}",
"count_members_other": "Участники: {{count}}",
"no_assigned_members": "Нет назначенных участников",
"assigned_to": "Назначено участнику",
"start_assigning_members_above": "Начните назначать участников",
@@ -849,7 +847,6 @@
"next_step": "Пропустить шаг",
"prev_step": "Предыдущий шаг",
"install": "Установить",
"install_paid_app": "Подписаться",
"installed": "Установлено",
"active_install_one": "Активные установки: {{count}}",
"active_install_other": "Активные установки: {{count}}",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Добавьте приложение аналитики для использования на страницах бронирования",
"no_category_apps_description_automation": "Добавьте приложение для автоматизации",
"no_category_apps_description_other": "Добавляйте всевозможные приложения для решения своих задач",
"no_category_apps_description_web3": "Добавьте приложение web3 для использования на страницах бронирования",
"no_category_apps_description_messaging": "Добавьте мессенджер, чтобы настроить пользовательские уведомления и напоминания",
"no_category_apps_description_crm": "Добавьте приложение CRM, чтобы отслеживать, с кем у вас были встречи",
"installed_app_calendar_description": "Настройте проверку календарей на предмет конфликтов для избежания двойного бронирования.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Все установленные приложения из других категорий.",
"installed_app_conferencing_description": "Выберите приложения для конференц-связи",
"installed_app_automation_description": "Выберите приложения для автоматизации",
"installed_app_web3_description": "Выберите приложения web3 для страниц бронирования",
"installed_app_messaging_description": "Выберите мессенджеры, с помощью которых вы хотите отправлять пользовательские уведомления и напоминания",
"installed_app_crm_description": "Выберите CRM-приложения, в которых вы хотите отслеживать, с кем у вас были встречи",
"analytics": "Аналитика",
@@ -1153,6 +1148,7 @@
"active_on": "Активен с",
"workflow_updated_successfully": "Рабочий процесс {{workflowName}} обновлен",
"premium_to_standard_username_description": "Это стандартное имя пользователя. В случае изменения вы будете перенаправлены на страницу оплаты для смены тарифа.",
"premium_username": "Это имя пользователя уровня premium. Такое имя пользователя можно получить за {{price}}",
"current": "Текущее",
"premium": "премиум",
"standard": "стандартное",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Участник",
"event_date_variable": "Дата события",
"event_time_variable": "Время события",
"timezone_variable": "Часовой пояс",
"location_variable": "Местоположение",
"additional_notes_variable": "Дополнительная информация",
"organizer_name_variable": "Имя организатора",
"app_upgrade_description": "Чтобы использовать эту функцию, необходимо перейти на аккаунт Pro.",
"invalid_number": "Неверный номер телефона",
@@ -1309,7 +1302,6 @@
"profile_picture": "Фото профиля",
"upload": "Загрузить",
"add_profile_photo": "Добавить фото профиля",
"web3": "Web3",
"token_address": "Адрес токена",
"blockchain": "Блокчейн",
"old_password": "Старый пароль",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Подключить приложения для автоматизации",
"connect_analytics_apps": "Подключить приложения аналитики",
"connect_other_apps": "Подключить другие приложения",
"connect_web3_apps": "Подключить приложения web3",
"connect_messaging_apps": "Подключить мессенджеры",
"connect_crm_apps": "Подключить CRM-приложения",
"current_step_of_total": "Шаг {{currentStep}} из {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Вы",
"resend_email": "Отправить письмо еще раз",
"member_already_invited": "Участник уже приглашен",
"already_in_use_error": "Имя пользователя уже используется",
"enter_email_or_username": "Введите адрес электронной почты или имя пользователя",
"team_name_taken": "Это название уже занято",
"must_enter_team_name": "Необходимо ввести название команды",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "В связи с новой политикой Google в отношении спама существует вероятность, что вы не сможете увидеть уведомления об этом забронированном событии по электронной почте или в календаре.",
"resolve": "Как решить эту проблему",
"no_organization_slug": "При создании команд для этой организации произошла ошибка. Отсутствует слаг URL-адреса.",
"org_name": "Название организации",
"org_url": "URL-адрес организации",
"copy_link_org": "Копировать ссылку на организацию",
"404_the_org": "Организация",
"404_the_team": "Команда",
"404_claim_entity_org": "Получите поддомен для вашей организации",
"404_claim_entity_team": "Присоединитесь к команде и начните составлять расписания вместе",
"insights_all_org_filter": "Все приложения",
"insights_team_filter": "Команда: {{teamName}}",
"insights_user_filter": "Пользователь: {{userName}}",
"insights_subtitle": "Insights: просматривайте информацию о бронировании по всем вашим событиям",
@@ -2005,7 +1994,6 @@
"select_date": "Выбрать дату",
"see_all_available_times": "Посмотреть все доступные интервалы времени",
"org_team_names_example": "например, команда по маркетингу",
"org_team_names_example_1": "например, команда по маркетингу",
"org_team_names_example_2": "например, Отдел продаж",
"org_team_names_example_3": "например, отдел дизайна",
"org_team_names_example_4": "например, технический отдел",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Включить событие календаря",
"oAuth": "OAuth",
"recently_added": "Недавно добавленные",
"connect_all_calendars": "Подключите все свои календари",
"workflow_automation": "Автоматизация при помощи рабочих процессов",
"scheduling_for_your_team": "Автоматизация при помощи рабочих процессов",
"no_members_found": "Участники не найдены",
"event_setup_length_error": "Настройка события: продолжительность должна быть не менее 1 минуты.",
"availability_schedules": "График с информацией о доступности",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Редактировать данные о доступности пользователя: {{username}}",
"resend_invitation": "Повторно отправить приглашение",
"invitation_resent": "Приглашение отправлено еще раз.",
"saml_sso": "SAML",
"add_client": "Добавить клиента",
"copy_client_secret_info": "После копирования секретного ключа вы больше не сможете его просматривать",
"add_new_client": "Добавить нового клиента",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Заблокируйте часовой пояс на странице бронирования",
"description_lock_timezone_toggle_on_booking_page": "Блокировка часового пояса на странице бронирования подходит для личных событий.",
"extensive_whitelabeling": "Индивидуальная техподдержка и помощь при онбординге",
"need_help": "Нужна помощь?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Добавьте строки выше ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "„username“ će biti ispunjeno korisničkim imenom dodeljenog člana",
"assign_to": "Dodelite",
"add_members": "Dodajte članove...",
"count_members_one": "{{count}} član",
"count_members_other": "{{count}} članova",
"no_assigned_members": "Nema dodeljenih članova",
"assigned_to": "Dodeljeno",
"start_assigning_members_above": "Počnite da dodeljujete članove iznad",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} će obezbediti URL Tandem sastanka.",
"cal_provide_video_meeting_url": "{{appName}} će obezbediti Daily video meeting URL.",
"cal_provide_jitsi_meeting_url": "Mi ćemo generisati Jitsi Meet URL za vas.",
"cal_provide_huddle01_meeting_url": "{{appName}} će obezbediti Huddle01 web3 video meeting URL.",
"cal_provide_huddle01_meeting_url": "{{appName}} će obezbediti Huddle01 video meeting URL.",
"cal_provide_teams_meeting_url": "{{appName}} će dostaviti URL adresu MS Teams sastanka. NAPOMENA: NEOPHODAN JE POSLOVNI ILI ŠKOLSKI NALOG",
"require_payment": "Obavezno plaćanje",
"you_need_to_add_a_name": "Potrebno je da dodate ime",
@@ -849,7 +847,6 @@
"next_step": "Preskoči korak",
"prev_step": "Prethodni korak",
"install": "Instaliraj",
"install_paid_app": "Prijavite se",
"installed": "Instalirano",
"active_install_one": "{{count}} aktivna instalacija",
"active_install_other": "{{count}} aktivnih instalacija",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Dodajte analitičku aplikaciju za vaše stranice za rezervacije",
"no_category_apps_description_automation": "Dodajte aplikaciju za automatizaciju za korišćenje",
"no_category_apps_description_other": "Dodajte bilo koju vrstu aplikacije da biste uradili svakakve stvari",
"no_category_apps_description_web3": "Dodajte web3 aplikaciju na stranice za rezervacije",
"no_category_apps_description_messaging": "Dodajte aplikaciju za poruke da biste podesili uobičajena obaveštenja i podsetnike",
"no_category_apps_description_crm": "Dodajte aplikaciju za CRM da biste pratili sa kim ste se sreli",
"installed_app_calendar_description": "Podesite kalendar(e) da biste proverili da li postoje konflikti i time sprečili dvostruka zakazivanja.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Sve vaše instalirane aplikacije iz drugih kategorija.",
"installed_app_conferencing_description": "Konfigurišite koje aplikacije ćete koristiti za konferencije",
"installed_app_automation_description": "Konfigurišite koje ćete aplikacije za automatizaciju da koristite",
"installed_app_web3_description": "Konfigurišite koje web3 aplikacije će se koristiti za stranice za rezervacije",
"installed_app_messaging_description": "Konfigurišite koje aplikacije za poruke ćete koristiti da biste podesili uobičajena obaveštenja i podsetnike",
"installed_app_crm_description": "Konfigurišite koje CRM aplikacije ćete koristiti za praćenje sa kim ste se sreli",
"analytics": "Analitika",
@@ -1153,6 +1148,7 @@
"active_on": "Aktivan uključen",
"workflow_updated_successfully": "Radni tok {{workflowName}} je uspešno ažuriran",
"premium_to_standard_username_description": "Ovo je standardno korisničko ime i ažuriranje će vas poslati na naplatu da biste prešli na nižu verziju.",
"premium_username": "Ovo je premium korisničko ime, nabavite svoje za {{price}}",
"current": "Trenutno",
"premium": "premijum",
"standard": "standardno",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Učesnik",
"event_date_variable": "Datum događaja",
"event_time_variable": "Vreme događaja",
"timezone_variable": "Vremenska zona",
"location_variable": "Lokacija",
"additional_notes_variable": "Dodatne beleške",
"organizer_name_variable": "Ime organizatora",
"app_upgrade_description": "Da biste koristili ovu funkciju, morate izvršiti nadogradnju na Pro nalog.",
"invalid_number": "Neispravan broj telefona",
@@ -1309,7 +1302,6 @@
"profile_picture": "Profilna slika",
"upload": "Otpremi",
"add_profile_photo": "Dodajte profilnu fotografiju",
"web3": "Web3",
"token_address": "Adresa tokena",
"blockchain": "Lanac blokova",
"old_password": "Stara lozinka",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Povežite aplikacije za automatizaciju",
"connect_analytics_apps": "Povežite analitičke aplikacije",
"connect_other_apps": "Povežite druge aplikacije",
"connect_web3_apps": "Povežite web3 aplikacije",
"connect_messaging_apps": "Povežite aplikacije za razmenu poruka",
"connect_crm_apps": "Povežite CRM aplikacije",
"current_step_of_total": "Korak {{currentStep}} od {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Vi",
"resend_email": "Ponovo pošalji imejl",
"member_already_invited": "Član je već pozvan",
"already_in_use_error": "Korisničko ime je već u upotrebi",
"enter_email_or_username": "Unesite imejl ili korisničko ime",
"team_name_taken": "Ime je zauzeto",
"must_enter_team_name": "Morate uneti naziv tima",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Google-ova nova politika neželjene pošte može da spreči da primate bilo koje imejlove ili obaveštenja kalendara u vezi sa ovom rezervacijom.",
"resolve": "Kako rešiti taj problem",
"no_organization_slug": "Desila se greška kod kreiranja timova za ovu organizaciju. Nedostaje URL slug.",
"org_name": "Naziv organizacije",
"org_url": "URL organizacije",
"copy_link_org": "Kopiraj link do organizacije",
"404_the_org": "Organizacija",
"404_the_team": "Tim",
"404_claim_entity_org": "Zatražite poddomen za svoju organizaciju",
"404_claim_entity_team": "Postanite deo ovog tima i počnite da uređujete kolektivni raspored",
"insights_all_org_filter": "Sve aplikacije",
"insights_team_filter": "Tim: {{teamName}}",
"insights_user_filter": "Korisnik: {{userName}}",
"insights_subtitle": "Pogledajte insights rezervacije za vaše događaje",
@@ -2005,7 +1994,6 @@
"select_date": "Izaberite datum",
"see_all_available_times": "Pogledajte sva dostupna vremena",
"org_team_names_example": "npr. Marketing tim",
"org_team_names_example_1": "npr. Marketing tim",
"org_team_names_example_2": "npr. Prodajni tim",
"org_team_names_example_3": "npr. Dizajnerski tim",
"org_team_names_example_4": "npr. Inženjerski tim",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Uključite događaj u kalendaru",
"oAuth": "OAuth",
"recently_added": "Nedavno dodato",
"connect_all_calendars": "Povežite sve svoje kalendare",
"workflow_automation": "Automatizacija radnog toka",
"scheduling_for_your_team": "Automatizacija radnog toka",
"no_members_found": "Članovi nisu pronađeni",
"event_setup_length_error": "Podešavanje događaja: Trajanje mora da bude barem 1 minut.",
"availability_schedules": "Raspored dostupnosti",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Uredi dostupnost korisnika: {{username}}",
"resend_invitation": "Ponovo pošaljite pozivnicu",
"invitation_resent": "Pozivnica je ponovo poslata.",
"saml_sso": "SAML",
"add_client": "Dodajte klijenta",
"copy_client_secret_info": "Nakon kopiranja tajne, nećete moći više da je vidite",
"add_new_client": "Dodajte novog klijenta",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Zaključajte vremensku zonu na stranici za zakazivanja",
"description_lock_timezone_toggle_on_booking_page": "Za zaključavanje vremenske zone na stranici za zakazivanja, korisno za događaje licem u lice.",
"extensive_whitelabeling": "Posvećena podrška za uvodnu obuku i inženjering",
"need_help": "Potrebna vam je pomoć?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Dodajte svoje nove stringove iznad ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"användarnamn\" fylls i med användarnamnet för de medlemmar som tilldelats",
"assign_to": "Tilldela till",
"add_members": "Lägg till medlemmar ...",
"count_members_one": "{{count}} medlem",
"count_members_other": "{{count}} medlemmar",
"no_assigned_members": "Inga tilldelade medlemmar",
"assigned_to": "Tilldelad till",
"start_assigning_members_above": "Börja tilldela medlemmar ovan",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} kommer att generera en URL för Tandem-möte.",
"cal_provide_video_meeting_url": "{{appName}} kommer att generera en URL för Daily video-möte.",
"cal_provide_jitsi_meeting_url": "Vi kommer att generera en Jitsi Meet URL för dig.",
"cal_provide_huddle01_meeting_url": "{{appName}} kommer att generera en URL för Huddle01 web3-möte.",
"cal_provide_huddle01_meeting_url": "{{appName}} kommer att generera en URL för Huddle01-möte.",
"cal_provide_teams_meeting_url": "{{appName}} kommer att tillhandahålla en mötes-URL för MS Teams. OBS! MÅSTE HA ETT ARBETS- ELLER SKOLKONTO",
"require_payment": "Begär betalning",
"you_need_to_add_a_name": "Du måste lägga till ett namn",
@@ -849,7 +847,6 @@
"next_step": "Hoppa över steg",
"prev_step": "Föregående steg",
"install": "Installera",
"install_paid_app": "Prenumerera",
"installed": "Installerad",
"active_install_one": "{{count}} aktiv installation",
"active_install_other": "{{count}} aktiva installationer",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Lägg till en analysapp för dina bokningssidor",
"no_category_apps_description_automation": "Lägg till en automationsapp att använda",
"no_category_apps_description_other": "Lägg till en annan typ av app för att göra alla möjliga saker",
"no_category_apps_description_web3": "Lägg till en web3-app för dina bokningssidor",
"no_category_apps_description_messaging": "Lägg till en meddelandeapp för att skapa anpassade aviseringar och påminnelser",
"no_category_apps_description_crm": "Lägg till en CRM-app för att hålla reda på vilka du har träffat",
"installed_app_calendar_description": "Ställ in kalendrar att söka efter konflikter för att undvika dubbelbokningar.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Alla dina installerade appar från andra kategorier.",
"installed_app_conferencing_description": "Konfigurera vilka konferensappar som ska användas",
"installed_app_automation_description": "Konfigurera vilka automationsappar som ska användas",
"installed_app_web3_description": "Konfigurera vilka web3-appar som ska användas för dina bokningssidor",
"installed_app_messaging_description": "Konfigurera vilka meddelandeappar som ska användas för att skapa anpassade aviseringar och påminnelser",
"installed_app_crm_description": "Konfigurera vilka CRM-appar som ska användas för att hålla reda på vilka du har träffat",
"analytics": "Analys",
@@ -1153,6 +1148,7 @@
"active_on": "Aktiv på",
"workflow_updated_successfully": "{{workflowName}} uppdaterades framgångsrikt",
"premium_to_standard_username_description": "Det här är ett förinställt användarnamn och uppdateringen tar dig till fakturering för att nedgradera.",
"premium_username": "Detta är ett premiumanvändarnamn, få ditt för {{price}}",
"current": "Nuvarande",
"premium": "premium",
"standard": "standard",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Deltagare",
"event_date_variable": "Händelsedatum",
"event_time_variable": "Händelsetid",
"timezone_variable": "Tidszon",
"location_variable": "Plats",
"additional_notes_variable": "Ytterligare inmatning",
"organizer_name_variable": "Arrangörens namn",
"app_upgrade_description": "För att kunna använda den här funktionen måste du uppgradera till ett Pro-konto.",
"invalid_number": "Ogiltigt telefonnummer",
@@ -1309,7 +1302,6 @@
"profile_picture": "Profilbild",
"upload": "Ladda upp",
"add_profile_photo": "Lägg till profilfoto",
"web3": "Web3",
"token_address": "Tokenadress",
"blockchain": "Blockkedja",
"old_password": "Gammalt lösenord",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Anslut automationsappar",
"connect_analytics_apps": "Anslut analysappar",
"connect_other_apps": "Anslut andra appar",
"connect_web3_apps": "Anslut web3-appar",
"connect_messaging_apps": "Anslut meddelandeappar",
"connect_crm_apps": "Anslut CRM-appar",
"current_step_of_total": "Steg {{currentStep}} av {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Du",
"resend_email": "Skicka e-post igen",
"member_already_invited": "Medlemmen har redan bjudits in",
"already_in_use_error": "Användarnamnet används redan",
"enter_email_or_username": "Ange en e-postadress eller ett användarnamn",
"team_name_taken": "Det här namnet används redan",
"must_enter_team_name": "Teamnamn måste anges",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Googles nya skräppostpolicy kan hindra dig från att få e-post och kalenderaviseringen om denna bokning.",
"resolve": "Lös",
"no_organization_slug": "Ett fel uppstod när team skapades för den här organisationen. URL-sluggen saknas.",
"org_name": "Organisationens namn",
"org_url": "Organisationens URL",
"copy_link_org": "Kopiera länk till organisation",
"404_the_org": "Organisationen",
"404_the_team": "Teamet",
"404_claim_entity_org": "Registrera underdomänen för din organisation",
"404_claim_entity_team": "Registrera det här teamet och börja hantera scheman tillsammans",
"insights_all_org_filter": "Alla",
"insights_team_filter": "Team: {{teamName}}",
"insights_user_filter": "Användare: {{userName}}",
"insights_subtitle": "Visa Insights-bokningar för dina händelser",
@@ -2005,7 +1994,6 @@
"select_date": "Välj datum",
"see_all_available_times": "Se alla tillgängliga tider",
"org_team_names_example": "t.ex. marknadsföringsteam",
"org_team_names_example_1": "t.ex. marknadsföringsteam",
"org_team_names_example_2": "t.ex. säljteam",
"org_team_names_example_3": "t.ex. designteam",
"org_team_names_example_4": "t.ex. ingenjörsteam",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Inkludera kalenderhändelse",
"oAuth": "OAuth",
"recently_added": "Nyligen tillagda",
"connect_all_calendars": "Anslut alla dina kalendrar",
"workflow_automation": "Automatisering av arbetsflöde",
"scheduling_for_your_team": "Automatisering av arbetsflöde",
"no_members_found": "Inga medlemmar hittades",
"event_setup_length_error": "Konfigurering av evenemang: Längden måste vara minst 1 minut.",
"availability_schedules": "Scheman för tillgänglighet",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Redigera användarens tillgänglighet: {{username}}",
"resend_invitation": "Skicka inbjudan igen",
"invitation_resent": "Inbjudan skickades på nytt.",
"saml_sso": "SAML",
"add_client": "Lägg till kund",
"copy_client_secret_info": "När du har kopierat hemligheten kommer du inte att kunna se den längre",
"add_new_client": "Lägg till ny kund",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Lås tidszon på bokningssidan",
"description_lock_timezone_toggle_on_booking_page": "För att låsa tidszonen på bokningssidan, användbart för personliga händelser.",
"extensive_whitelabeling": "Dedikerad registrering och teknisk support",
"need_help": "Behöver du hjälp?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -75,7 +75,9 @@
"rescheduled_event_type_subject": "மறுஅட்டவணைக்கான கோரிக்கை அனுப்பப்பட்டது: {{eventType}} உடன் {{name}} உடன் {{date}}",
"requested_to_reschedule_subject_attendee": "மறுஅட்டவணை செயல் தேவை: {{eventType}} க்கு {{name}} உடன் புதிய நேரத்தை முன்பதிவு செய்யவும்",
"hi_user_name": "வணக்கம் {{name}}",
"premium_username": "இது ஒரு பிரீமியம் பயனர்பெயர், {{price}} க்கு உங்களுடையதைப் பெறுங்கள்",
"turn_on": "இயக்கவும்",
"already_in_use_error": "பயனர் பெயர் முன்னரே உபயோகத்தில் உள்ளது",
"email_no_user_invite_subheading": "{{invitedBy}} உங்களை {{appName}} குழுவில் சேர அழைத்துள்ளார். {{appName}} என்பது ஈமெயில் இல்லாமலேயே உங்களையும் உங்கள் குழுவையும் கூட்டங்களைத் திட்டமிடுவதற்கு உதவும் திட்டமிடல் பயன்பாடு.",
"email_no_user_step_one": "உங்கள் பயனர்பெயரை தேர்வு செய்யவும்",
"email_no_user_step_two": "உங்கள் காலெண்டர் கணக்கை இணைக்கவும்",
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"username\" alanı, atanan üyelerin kullanıcı adı ile doldurulur",
"assign_to": "Şuraya ata:",
"add_members": "Üye ekle...",
"count_members_one": "{{count}} üye",
"count_members_other": "{{count}} üye",
"no_assigned_members": "Atanan üye yok",
"assigned_to": "Şuraya atandı:",
"start_assigning_members_above": "Üyeleri en üstten itibaren atamaya başlayın",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}}, bir Tandem toplantı URL'si sağlayacaktır.",
"cal_provide_video_meeting_url": "{{appName}}, bir görüntülü toplantı URL'si sağlayacaktır.",
"cal_provide_jitsi_meeting_url": "Sizin için bir Jitsi Meet URL'si oluşturacağız.",
"cal_provide_huddle01_meeting_url": "{{appName}}, bir Huddle01 web3 görüntülü toplantı URL'si sağlayacaktır.",
"cal_provide_huddle01_meeting_url": "{{appName}}, bir Huddle01 görüntülü toplantı URL'si sağlayacaktır.",
"cal_provide_teams_meeting_url": "{{appName}}, bir MS Teams toplantı URL'si sağlayacaktır. NOT: İŞ VEYA OKUL HESABINIZIN OLMASI GEREKİR",
"require_payment": "Ödemeyi Gerekli Kılın",
"you_need_to_add_a_name": "Ad eklemeniz gerekiyor",
@@ -849,7 +847,6 @@
"next_step": "Adımı geç",
"prev_step": "Önceki adım",
"install": "Yükle",
"install_paid_app": "Abone Ol",
"installed": "Yüklendi",
"active_install_one": "{{count}} aktif yükleme",
"active_install_other": "{{count}} aktif yükleme",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Rezervasyon sayfalarınız için bir analiz uygulaması ekleyin",
"no_category_apps_description_automation": "Kullanılacak otomasyon uygulamasını ekleyin",
"no_category_apps_description_other": "Her türlü işlemi yapmak için farklı bir uygulama ekleyin",
"no_category_apps_description_web3": "Rezervasyon sayfalarınız için bir web3 uygulaması ekleyin",
"no_category_apps_description_messaging": "Özel bildirimler ve hatırlatıcılar ayarlamak için bir mesajlaşma uygulaması ekleyin",
"no_category_apps_description_crm": "Tanıştığınız kullanıcıları takip etmek için bir CRM uygulaması ekleyin",
"installed_app_calendar_description": "Çifte rezervasyonları önlemek amacıyla çakışmaları kontrol etmek için takvimleri ayarlayın.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Diğer kategorilerdeki tüm yüklü uygulamalarınız.",
"installed_app_conferencing_description": "Hangi konferans uygulamalarının kullanılacağını belirleyin",
"installed_app_automation_description": "Kullanılacak otomasyon uygulamalarını yapılandırın",
"installed_app_web3_description": "Rezervasyon sayfalarınız için hangi web3 uygulamalarının kullanılacağını yapılandırın",
"installed_app_messaging_description": "Özel bildirimleri ve hatırlatıcıları ayarlamak için hangi mesajlaşma uygulamalarının kullanılacağını belirleyin",
"installed_app_crm_description": "Tanıştığınız kullanıcıları takip etmek için hangi CRM uygulamalarının kullanılacağını belirleyin",
"analytics": "Analizler",
@@ -1153,6 +1148,7 @@
"active_on": "Aktif",
"workflow_updated_successfully": "{{workflowName}} iş akışı başarıyla güncellendi",
"premium_to_standard_username_description": "Bu, standart bir kullanıcı adıdır ve güncelleme işlemi eski sürüme geçmeniz için sizi faturalandırma sayfasına yönlendirir.",
"premium_username": "Bu premium bir kullanıcı adıdır, {{price}} karşılığında kendi adınızı alın",
"current": "Mevcut",
"premium": "premium",
"standard": "standart",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Katılımcı",
"event_date_variable": "Etkinlik tarihi",
"event_time_variable": "Etkinlik saati",
"timezone_variable": "Saat dilimi",
"location_variable": "Konum",
"additional_notes_variable": "Ek notlar",
"organizer_name_variable": "Düzenleyenin adı",
"app_upgrade_description": "Bu özelliği kullanmak için Pro hesabına geçmeniz gerekiyor.",
"invalid_number": "Geçersiz telefon numarası",
@@ -1309,7 +1302,6 @@
"profile_picture": "Profil resmi",
"upload": "Yükle",
"add_profile_photo": "Profil fotoğrafı ekleyin",
"web3": "Web3",
"token_address": "Token Adresi",
"blockchain": "Blok zinciri",
"old_password": "Eski şifre",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Otomasyon uygulamaları bağlayın",
"connect_analytics_apps": "Analiz uygulamaları bağlayın",
"connect_other_apps": "Diğer uygulamaları bağlayın",
"connect_web3_apps": "Web3 uygulamalarını bağlayın",
"connect_messaging_apps": "Mesajlaşma uygulamalarını bağlayın",
"connect_crm_apps": "CRM uygulamalarını bağlayın",
"current_step_of_total": "{{currentStep}} / {{maxSteps}}. Adım",
@@ -1536,6 +1527,7 @@
"you": "Siz",
"resend_email": "E-postayı tekrar gönder",
"member_already_invited": "Üye zaten davet edildi",
"already_in_use_error": "Kullanıcı adı zaten kullanımda",
"enter_email_or_username": "Bir e-posta veya kullanıcı adı girin",
"team_name_taken": "Bu ad zaten kullanılıyor",
"must_enter_team_name": "Ekip adı girilmelidir",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Google'ın yeni spam politikası nedeniyle bu rezervasyon için e-posta veya takvim bildirimleri alamayabilirsiniz.",
"resolve": "Çöz",
"no_organization_slug": "Bu kuruluş için ekipler oluşturulurken bir hata oluştu. URL bilgisi eksik.",
"org_name": "Kuruluş adı",
"org_url": "Kuruluş URL'si",
"copy_link_org": "Bağlantıyı kuruluşa kopyala",
"404_the_org": "Kuruluş",
"404_the_team": "Ekip",
"404_claim_entity_org": "Kuruluşunuz için alt alan adınızı talep edin",
"404_claim_entity_team": "Bu ekibi sahiplenin ve planlamaları toplu şekilde yönetmeye başlayın",
"insights_all_org_filter": "Tümü",
"insights_team_filter": "Ekip: {{teamName}}",
"insights_user_filter": "Kullanıcı: {{userName}}",
"insights_subtitle": "Etkinliklerinizdeki rezervasyon insights'ı görüntüleyin",
@@ -2005,7 +1994,6 @@
"select_date": "Tarihi Seçin",
"see_all_available_times": "Tüm müsait saatleri görün",
"org_team_names_example": "Örneğin. Pazarlama ekibi",
"org_team_names_example_1": "Örneğin. Pazarlama ekibi",
"org_team_names_example_2": "Örn. Satış Ekibi",
"org_team_names_example_3": "Örn. Tasarım Ekibi",
"org_team_names_example_4": "Örn. Mühendislik Ekibi",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Takvim etkinliğini dahil et",
"oAuth": "OAuth",
"recently_added": "Son eklenen",
"connect_all_calendars": "Tüm takvimlerinizi bağlayın",
"workflow_automation": "İş akışı otomasyonu",
"scheduling_for_your_team": "İş akışı otomasyonu",
"no_members_found": "Üye bulunamadı",
"event_setup_length_error": "Etkinlik Kurulumu: Süre en az 1 dakika olmalıdır.",
"availability_schedules": "Müsaitlik Planları",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Kullanıcının müsaitlik durumunu düzenleyin: {{username}}",
"resend_invitation": "Davetiyeyi yeniden gönder",
"invitation_resent": "Davetiye tekrar gönderildi.",
"saml_sso": "SAML",
"add_client": "Müşteri ekle",
"copy_client_secret_info": "Gizli bilgiyi kopyaladıktan sonra artık görüntüleyemeyeceksiniz",
"add_new_client": "Yeni Müşteri Ekle",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Rezervasyon sayfasında saat dilimini kilitle",
"description_lock_timezone_toggle_on_booking_page": "Rezervasyon sayfasında saat dilimini kilitlemek kişisel etkinlikler için kullanışlıdır.",
"extensive_whitelabeling": "Özel işe alıştırma ve mühendislik desteği",
"need_help": "Yardıma mı ihtiyacınız var?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Yeni dizelerinizi yukarıya ekleyin ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "Поле «username» буде заповнено іменами користувачів призначених учасників",
"assign_to": "Кому призначити:",
"add_members": "Додати учасників…",
"count_members_one": "{{count}} учасник",
"count_members_other": "Учасників: {{count}}",
"no_assigned_members": "Немає призначених учасників",
"assigned_to": "Кому призначено:",
"start_assigning_members_above": "Почніть призначати учасників вище",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} надасть URL-адресу наради Tandem.",
"cal_provide_video_meeting_url": "{{appName}} надасть URL-адресу наради Daily.",
"cal_provide_jitsi_meeting_url": "Ми згенеруємо URL-адресу Jitsi Meet.",
"cal_provide_huddle01_meeting_url": "{{appName}} надасть URL-адресу відеонаради Huddle01 web3.",
"cal_provide_huddle01_meeting_url": "{{appName}} надасть URL-адресу відеонаради Huddle01.",
"cal_provide_teams_meeting_url": "{{appName}} надасть URL-адресу наради в MS Teams. ПРИМІТКА: ПОТРІБЕН РОБОЧИЙ АБО НАВЧАЛЬНИЙ ОБЛІКОВИЙ ЗАПИС",
"require_payment": "Вимагати оплату",
"you_need_to_add_a_name": "Потрібно додати ім’я",
@@ -849,7 +847,6 @@
"next_step": "Пропустити крок",
"prev_step": "Попередній крок",
"install": "Установити",
"install_paid_app": "Підписатися",
"installed": "Установлено",
"active_install_one": "{{count}} активна інсталяція",
"active_install_other": "Активних інсталяцій: {{count}}",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Вибрати аналітичний додаток для своїх сторінок бронювання",
"no_category_apps_description_automation": "Вибрати додаток для автоматизації",
"no_category_apps_description_other": "Додавайте інші додатки з різноманітними функціями",
"no_category_apps_description_web3": "Вибрати додаток web3 для своїх сторінок бронювання",
"no_category_apps_description_messaging": "Додайте застосунок для обміну повідомленнями, щоб налаштувати сповіщення й нагадування",
"no_category_apps_description_crm": "Додайте застосунок для CRM, щоб вести облік користувачів, з якими ви зустрічалися",
"installed_app_calendar_description": "Налаштуйте календарі, щоб перевіряти, чи немає конфліктів у розкладі, і уникати подвійних бронювань.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Усі ваші встановлені додатки з інших категорій.",
"installed_app_conferencing_description": "Укажіть, які застосунки для конференцій використовувати",
"installed_app_automation_description": "Указати додатки для автоматизації",
"installed_app_web3_description": "Виберіть додатки web3 для своїх сторінок бронювання",
"installed_app_messaging_description": "Укажіть, які застосунки для обміну повідомленнями використовувати для налаштування сповіщень і нагадувань",
"installed_app_crm_description": "Укажіть, які застосунки для CRM використовувати, щоб вести облік користувачів, з якими ви зустрічались",
"analytics": "Аналітика",
@@ -1153,6 +1148,7 @@
"active_on": "Активується",
"workflow_updated_successfully": "Робочий процес «{{workflowName}}» оновлено",
"premium_to_standard_username_description": "Це стандартне ім’я користувача. Якщо оновити його, ви перейдете до оформлення підписки з вужчим функціоналом.",
"premium_username": "Це ім’я користувача класу преміум, отримай своє за {{price}}",
"current": "Поточна",
"premium": "преміум",
"standard": "стандарт",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Учасник",
"event_date_variable": "Дата заходу",
"event_time_variable": "Час заходу",
"timezone_variable": "Часовий пояс",
"location_variable": "Розташування",
"additional_notes_variable": "Додаткові примітки",
"organizer_name_variable": "Ім’я організатора",
"app_upgrade_description": "Щоб користуватися цією функцією, потрібен обліковий запис Pro.",
"invalid_number": "Недійсний номер телефону",
@@ -1309,7 +1302,6 @@
"profile_picture": "Зображення профілю",
"upload": "Передати",
"add_profile_photo": "Додати фото профілю",
"web3": "Web3",
"token_address": "Адреса токена",
"blockchain": "Блокчейн",
"old_password": "Старий пароль",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Підключити додатки для автоматизації",
"connect_analytics_apps": "Підключити аналітичні додатки",
"connect_other_apps": "Підключити інші додатки",
"connect_web3_apps": "Підключити додатки web3",
"connect_messaging_apps": "Підключити застосунки для повідомлень",
"connect_crm_apps": "Підключити застосунки CRM",
"current_step_of_total": "Крок {{currentStep}} із {{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Ви",
"resend_email": "Повторно надіслати лист",
"member_already_invited": "Учасника вже запрошено",
"already_in_use_error": "Це ім’я користувача вже використовується",
"enter_email_or_username": "Введіть електронну адресу або ім’я користувача",
"team_name_taken": "Це ім’я вже зайнято",
"must_enter_team_name": "Потрібно ввести назву команди",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Через нову політику Google щодо спаму ви можете не отримати електронний лист або сповіщення календаря щодо цього бронювання.",
"resolve": "Вирішити",
"no_organization_slug": "Сталася помилка під час створення команд для цієї організації. Відсутня URL-адреса.",
"org_name": "Назва організації",
"org_url": "URL-адреса організації",
"copy_link_org": "Копіювати посилання на організацію",
"404_the_org": "Організація",
"404_the_team": "Команда",
"404_claim_entity_org": "Отримайте піддомен для своєї організації",
"404_claim_entity_team": "Приєднайтеся до цієї команди й почніть спільно керувати розкладами",
"insights_all_org_filter": "Усі",
"insights_team_filter": "Команда: {{teamName}}",
"insights_user_filter": "Користувач: {{userName}}",
"insights_subtitle": "Переглядайте дані Insights про бронювання для своїх заходів",
@@ -2005,7 +1994,6 @@
"select_date": "Виберіть дату",
"see_all_available_times": "Переглянути доступні часові проміжки",
"org_team_names_example": "напр. команда маркетингу",
"org_team_names_example_1": "напр. маркетинговий відділ",
"org_team_names_example_2": "напр. відділ продажів",
"org_team_names_example_3": "напр. дизайнерський відділ",
"org_team_names_example_4": "e.g. інженерний відділ",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Включити календарний захід",
"oAuth": "OAuth",
"recently_added": "Нещодавно додані",
"connect_all_calendars": "Можливість підключення всіх календарів",
"workflow_automation": "Автоматизація робочого процесу",
"scheduling_for_your_team": "Автоматизація робочого процесу",
"no_members_found": "Учасників не знайдено",
"event_setup_length_error": "Налаштування заходу: мінімальна тривалість — 1 хвилина.",
"availability_schedules": "Розклад доступності",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Редагувати доступність користувача: {{username}}",
"resend_invitation": "Повторно надіслати запрошення",
"invitation_resent": "Запрошення надіслано повторно.",
"saml_sso": "SAML",
"add_client": "Додати клієнта",
"copy_client_secret_info": "Скопіювавши секрет, ви більше не зможете його переглянути",
"add_new_client": "Додати нового клієнта",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Заблокуйте часовий пояс на сторінці бронювання",
"description_lock_timezone_toggle_on_booking_page": "Для блокування часового поясу на сторінці бронювання (корисне для окремих заходів)",
"extensive_whitelabeling": "Технічна підтримка й підтримка під час ознайомлення",
"need_help": "Потрібна допомога?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
+8 -15
View File
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "\"username\" sẽ được điền vào theo tên người dùng của những thành viên được phân công",
"assign_to": "Phân công cho",
"add_members": "Thêm thành viên...",
"count_members_one": "{{count}} thành viên",
"count_members_other": "{{count}} thành viên",
"no_assigned_members": "Không thành viên nào được phân công",
"assigned_to": "Được phân công cho",
"start_assigning_members_above": "Bắt đầu phân công những thành viên bên trên",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} sẽ cung cấp URL cuộc họp Tandem.",
"cal_provide_video_meeting_url": "{{appName}} sẽ cung cấp URL cuộc họp video.",
"cal_provide_jitsi_meeting_url": "Chúng tôi sẽ tạo một URL Jitsi Meet cho bạn.",
"cal_provide_huddle01_meeting_url": "{{appName}} sẽ cung cấp URL cuộc họp video Huddle01 web3.",
"cal_provide_huddle01_meeting_url": "{{appName}} sẽ cung cấp URL cuộc họp video Huddle01.",
"cal_provide_teams_meeting_url": "{{appName}} sẽ cung cấp URL cuộc họp MS Teams. LƯU Ý: PHẢI CÓ TÀI KHOẢN CÔNG VIỆC HOẶC TRƯỜNG HỌC",
"require_payment": "Yêu cầu thanh toán",
"you_need_to_add_a_name": "Bạn cần thêm vào một tên",
@@ -849,7 +847,6 @@
"next_step": "Bỏ qua bước",
"prev_step": "Bước trước",
"install": "Cài đặt",
"install_paid_app": "Đăng ký",
"installed": "Đã cài đặt",
"active_install_one": "{{count}} cài đặt hoạt động",
"active_install_other": "{{count}} cài đặt hoạt động",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "Thêm một ứng dụng phân tích cho những trang lịch hẹn của bạn",
"no_category_apps_description_automation": "Thêm một ứng dụng tự động hoá để sử dụng",
"no_category_apps_description_other": "Thêm loại ứng dụng khác để làm mọi loại công việc",
"no_category_apps_description_web3": "Thêm một ứng dụng web3 cho những trang lịch hẹn của bạn",
"no_category_apps_description_messaging": "Thêm một ứng dụng nhắn tin để thiết lập thông báo & lời nhắc tuỳ chỉnh",
"no_category_apps_description_crm": "Thêm một ứng dụng CRM để theo dõi người mà bạn đã họp cùng",
"installed_app_calendar_description": "Đặt (các) lịch làm nhiệm vụ kiểm tra xung đột nhằm ngăn tình trạng đặt lịch trùng.",
@@ -916,7 +912,6 @@
"installed_app_other_description": "Tất cả những ứng dụng bạn đã cài từ những danh mục khác.",
"installed_app_conferencing_description": "Cấu hình ứng dụng hội nghị nào nên dùng",
"installed_app_automation_description": "Cấu hình ứng dụng tự động hoá nên dùng",
"installed_app_web3_description": "Cấu hình ứng dụng web3 nào cần để sử dụng cho những trang lịch hẹn của bạn",
"installed_app_messaging_description": "Cấu hình ứng dụng nhắn tin nào cần dùng để thiết lập thông báo & lời nhắc tuỳ chỉnh",
"installed_app_crm_description": "Cấu hình ứng dụng CRM nào cần dùng để theo dõi những người mà bạn đã họp cùng",
"analytics": "Phân tích",
@@ -1153,6 +1148,7 @@
"active_on": "Hoạt động vào",
"workflow_updated_successfully": "Đã cập nhật thành công tiến độ công việc {{workflowName}}",
"premium_to_standard_username_description": "Đây là tên người dùng tiêu chuẩn và việc cập nhật sẽ đưa bạn đến khâu thanh toán để hạ cấp độ.",
"premium_username": "Đây là tên người dùng cao cấp, hãy nhận lấy một cái cho bạn với mức {{price}}",
"current": "Hiện tại",
"premium": "cao cấp",
"standard": "tiêu chuẩn",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "Tên người tham dự",
"event_date_variable": "Ngày sự kiện",
"event_time_variable": "Thời gian sự kiện",
"timezone_variable": "Múi giờ",
"location_variable": "Vị trí",
"additional_notes_variable": "Ghi chú bổ sung",
"organizer_name_variable": "Tên nhà tổ chức",
"app_upgrade_description": "Để sử dụng tính năng này, bạn cần nâng cấp lên tài khoản Pro.",
"invalid_number": "Số điện thoại không hợp lệ",
@@ -1309,7 +1302,6 @@
"profile_picture": "Ảnh hồ sơ",
"upload": "Tải lên",
"add_profile_photo": "Thêm ảnh hồ sơ",
"web3": "Web3",
"token_address": "Địa chỉ token",
"blockchain": "Chuỗi khối",
"old_password": "Mật khẩu cũ",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "Kết nối các ứng dụng tự động hoá",
"connect_analytics_apps": "Kết nối các ứng dụng phân tích",
"connect_other_apps": "Kết nối các ứng dụng khác",
"connect_web3_apps": "Kết nối các ứng dụng web3",
"connect_messaging_apps": "Kết nối các ứng dụng nhắn tin",
"connect_crm_apps": "Kết nối các ứng dụng CRM",
"current_step_of_total": "Bước {{currentStep}}/{{maxSteps}}",
@@ -1536,6 +1527,7 @@
"you": "Bạn",
"resend_email": "Gửi lại email",
"member_already_invited": "Thành viên đã được mời rồi",
"already_in_use_error": "Tên người dùng đã được dùng",
"enter_email_or_username": "Nhập một email hoặc tên người dùng",
"team_name_taken": "Tên này đã có người lấy rồi",
"must_enter_team_name": "Cần phải nhập một tên nhóm",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "Chính sách thư rác mới của Google có thể ngăn bạn nhận bất kì thông báo nào về email và lịch liên quan đến lịch hẹn này.",
"resolve": "Giải quyết",
"no_organization_slug": "Có lỗi khi tạo nhóm cho tổ chức này. Slug URL bị thiếu.",
"org_name": "Tên tổ chức",
"org_url": "URL của tổ chức",
"copy_link_org": "Sao chép liên kết đến tổ chức",
"404_the_org": "Tổ chức",
"404_the_team": "Nhóm",
"404_claim_entity_org": "Lấy tên miền phụ cho tổ chức của bạn",
"404_claim_entity_team": "Lấy nhóm này và bắt đầu quản lý lịch hẹn theo tập thể",
"insights_all_org_filter": "Tất cả",
"insights_team_filter": "Nhóm: {{teamName}}",
"insights_user_filter": "Người dùng: {{userName}}",
"insights_subtitle": "Xem insights của đặt lịch ở mọi sự kiện của bạn",
@@ -2005,7 +1994,6 @@
"select_date": "Chọn ngày",
"see_all_available_times": "Xem tất cả những thời gian trống",
"org_team_names_example": "ví dụ Nhóm Tiếp thị",
"org_team_names_example_1": "ví dụ Nhóm Tiếp thị",
"org_team_names_example_2": "ví dụ Nhóm Kinh doanh",
"org_team_names_example_3": "ví dụ Nhóm Thiết kế",
"org_team_names_example_4": "ví dụ Nhóm Kỹ thuật",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "Bao gồm sự kiện lịch",
"oAuth": "OAuth",
"recently_added": "Đã thêm vào gần đây",
"connect_all_calendars": "Kết nối tất cả lịch của bạn",
"workflow_automation": "Tự động hoá dòng công việc",
"scheduling_for_your_team": "Tự động hoá dòng công việc",
"no_members_found": "Không tìm thấy thành viên nào",
"event_setup_length_error": "Thiết lập sự kiện: Thời lượng phải ít nhất 1 phút.",
"availability_schedules": "Lịch trống",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "Sửa tình trạng trống lịch của người dùng: {{username}}",
"resend_invitation": "Gửi lại lời mời",
"invitation_resent": "Lời mời đã được gửi lại.",
"saml_sso": "SAML",
"add_client": "Thêm khách hàng",
"copy_client_secret_info": "Sau khi sao chép bí mật, bạn sẽ không thể xem nó được nữa",
"add_new_client": "Thêm khách hàng mới",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "Khoá múi giờ trên trang lịch hẹn",
"description_lock_timezone_toggle_on_booking_page": "Để khoá múi giờ trên trang lịch hẹn, hữu dụng cho những sự kiện đích thân tham dự.",
"extensive_whitelabeling": "Hướng dẫn bắt đầu và hỗ trợ kỹ thuật chuyên biệt",
"need_help": "Cần hỗ trợ?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ Add your new strings above here ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "“用户名”将填写所分配成员的用户名",
"assign_to": "分配给",
"add_members": "添加成员...",
"count_members_one": "{{count}} 个成员",
"count_members_other": "{{count}} 个成员",
"no_assigned_members": "没有已分配的成员",
"assigned_to": "已分配给",
"start_assigning_members_above": "开始分配以上成员",
@@ -803,7 +801,7 @@
"cal_provide_tandem_meeting_url": "{{appName}} 将提供 Tandem 会议链接",
"cal_provide_video_meeting_url": "{{appName}}将提供 Daily 视频会议链接",
"cal_provide_jitsi_meeting_url": "{{appName}} 将提供 Jitsi Meet 视频会议链接",
"cal_provide_huddle01_meeting_url": "{{appName}} 将提供 Huddle01 web3 视频会议链接",
"cal_provide_huddle01_meeting_url": "{{appName}} 将提供 Huddle01 视频会议链接",
"cal_provide_teams_meeting_url": "{{appName}} 将提供 MS Teams 会议 URL。注意: 须拥有工作或学校帐户",
"require_payment": "需要付款",
"you_need_to_add_a_name": "您需要添加名称",
@@ -849,7 +847,6 @@
"next_step": "跳过步骤",
"prev_step": "上一步",
"install": "安装",
"install_paid_app": "订阅",
"start_paid_trial": "开始免费试用",
"installed": "已安装",
"active_install_one": "{{count}} 个活动安装",
@@ -908,7 +905,6 @@
"no_category_apps_description_analytics": "为您的预约页面添加分析应用",
"no_category_apps_description_automation": "添加要使用的自动化应用",
"no_category_apps_description_other": "添加任何其他类型的应用以执行各种操作",
"no_category_apps_description_web3": "为您的预约页面添加 web3 应用",
"no_category_apps_description_messaging": "添加一个消息应用,以设置自定义通知和提醒",
"no_category_apps_description_crm": "添加一个 CRM 应用,以记录您见过的人",
"installed_app_calendar_description": "设置日历以检查冲突,防止重复预约。",
@@ -917,7 +913,6 @@
"installed_app_other_description": "所有属于其他类别的已安装应用。",
"installed_app_conferencing_description": "配置要使用的会议应用",
"installed_app_automation_description": "配置要使用的自动化应用",
"installed_app_web3_description": "配置将用于您的预约页面的 web3 应用",
"installed_app_messaging_description": "配置用于设置自定义通知和提醒的消息应用",
"installed_app_crm_description": "配置用于记录您见过的人的 CRM 应用",
"analytics": "分析",
@@ -1154,6 +1149,7 @@
"active_on": "活跃于",
"workflow_updated_successfully": "{{workflowName}} 工作流程已成功更新",
"premium_to_standard_username_description": "这是一个标准用户名,更新流程会将您引导至账单页面进行降级。",
"premium_username": "这是高级用户名,支付 {{price}} 即可获得您的高级用户名",
"current": "当前",
"premium": "高级",
"standard": "标准",
@@ -1219,9 +1215,6 @@
"attendee_name_variable": "参与者",
"event_date_variable": "活动日期",
"event_time_variable": "活动时间",
"timezone_variable": "时区",
"location_variable": "位置",
"additional_notes_variable": "附加备注",
"organizer_name_variable": "组织者姓名",
"app_upgrade_description": "要使用此功能,您需要升级到专业版帐户。",
"invalid_number": "电话号码无效",
@@ -1310,7 +1303,6 @@
"profile_picture": "个人资料图片",
"upload": "上传",
"add_profile_photo": "添加个人资料照片",
"web3": "Web3",
"token_address": "代币地址",
"blockchain": "区块链",
"old_password": "旧密码",
@@ -1352,7 +1344,6 @@
"connect_automation_apps": "连接自动化应用",
"connect_analytics_apps": "连接分析应用",
"connect_other_apps": "连接其他应用",
"connect_web3_apps": "连接 web3 应用",
"connect_messaging_apps": "连接消息应用",
"connect_crm_apps": "连接 CRM 应用",
"current_step_of_total": "第 {{currentStep}} 步,共 {{maxSteps}} 步",
@@ -1537,6 +1528,7 @@
"you": "您",
"resend_email": "重新发送电子邮件",
"member_already_invited": "成员已被邀请",
"already_in_use_error": "用户名已在使用中",
"enter_email_or_username": "输入电子邮件或用户名",
"team_name_taken": "此名称已被使用",
"must_enter_team_name": "必须输入团队名称",
@@ -1987,14 +1979,11 @@
"google_new_spam_policy": "Google 的新垃圾邮件政策可能会阻止您收到任何有关此预约的电子邮件和日历通知。",
"resolve": "解决",
"no_organization_slug": "为该组织创建团队时出错。缺少链接 slug。",
"org_name": "组织名称",
"org_url": "组织链接",
"copy_link_org": "复制组织链接",
"404_the_org": "组织",
"404_the_team": "团队",
"404_claim_entity_org": "为您的组织声明子域",
"404_claim_entity_team": "声明成为此团队的一员并开始集体管理时间表",
"insights_all_org_filter": "所有应用",
"insights_team_filter": "团队:{{teamName}}",
"insights_user_filter": "用户:{{userName}}",
"insights_subtitle": "查看您活动的预约 insights",
@@ -2006,7 +1995,6 @@
"select_date": "选择日期",
"see_all_available_times": "查看所有可预约时间",
"org_team_names_example": "例如,营销团队",
"org_team_names_example_1": "例如,营销团队",
"org_team_names_example_2": "例如,销售团队",
"org_team_names_example_3": "例如,设计团队",
"org_team_names_example_4": "例如,工程团队",
@@ -2068,6 +2056,9 @@
"include_calendar_event": "包括日历活动",
"oAuth": "OAuth",
"recently_added": "最近添加",
"connect_all_calendars": "连接您的所有日历",
"workflow_automation": "工作流程自动化",
"scheduling_for_your_team": "工作流程自动化",
"no_members_found": "未找到成员",
"event_setup_length_error": "获得设置:持续时间必须至少为 1 分钟。",
"availability_schedules": "可预约时间表",
@@ -2091,6 +2082,7 @@
"edit_users_availability": "编辑用户的可预约时间:{{username}}",
"resend_invitation": "重新发送邀请",
"invitation_resent": "邀请已重新发送。",
"saml_sso": "SAML",
"add_client": "添加客户",
"copy_client_secret_info": "复制密码后,您将无法再查看",
"add_new_client": "添加新客户",
@@ -2102,5 +2094,6 @@
"lock_timezone_toggle_on_booking_page": "在预约页面上锁定时区",
"description_lock_timezone_toggle_on_booking_page": "在预约页面上锁定时区,这对面对面活动非常有用。",
"extensive_whitelabeling": "专门的入门和工程支持",
"need_help": "需要帮助?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ 在此上方添加您的新字符串 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -666,8 +666,6 @@
"managed_event_url_clarification": "「使用者名稱」會填入所指派成員的使用者名稱",
"assign_to": "指派給",
"add_members": "新增成員…",
"count_members_one": "{{count}} 名成員",
"count_members_other": "{{count}} 名成員",
"no_assigned_members": "無已指派成員",
"assigned_to": "已指派給",
"start_assigning_members_above": "開始指派以上成員",
@@ -849,7 +847,6 @@
"next_step": "跳過步驟",
"prev_step": "上個步驟",
"install": "安裝",
"install_paid_app": "訂閱",
"installed": "已安裝",
"active_install_one": "{{count}} 個有效安裝",
"active_install_other": "{{count}} 個有效安裝",
@@ -907,7 +904,6 @@
"no_category_apps_description_analytics": "為您的預約頁面加入分析應用程式",
"no_category_apps_description_automation": "加入自動化應用程式來使用",
"no_category_apps_description_other": "新增任何其他類型的應用程式以完成各種操作",
"no_category_apps_description_web3": "為您的預約頁面加入 web3 應用程式",
"no_category_apps_description_messaging": "新增傳訊應用程式,以設定自訂通知和提醒",
"no_category_apps_description_crm": "新增 CRM 應用程式,追蹤您已見過面的人",
"installed_app_calendar_description": "設定行事曆來檢查衝突,以避免重複預約。",
@@ -916,7 +912,6 @@
"installed_app_other_description": "其他類別的所有已安裝應用程式。",
"installed_app_conferencing_description": "設定要使用哪些會議應用程式",
"installed_app_automation_description": "設定要使用哪些自動化應用程式",
"installed_app_web3_description": "設定要為您的預約頁面使用哪些 web3 應用程式",
"installed_app_messaging_description": "設定要使用哪些傳訊應用程式來設定自訂通知與提醒",
"installed_app_crm_description": "設定要使用哪些 CRM 應用程式來追蹤您已見過面的人",
"analytics": "分析",
@@ -1153,6 +1148,7 @@
"active_on": "啟用類型:",
"workflow_updated_successfully": "已成功更新 {{workflowName}}",
"premium_to_standard_username_description": "此為標準使用者名稱,更新系統會帶您前往付費頁面進行降級。",
"premium_username": "此為高級使用者名稱,支付 {{price}} 即可擁有",
"current": "目前",
"premium": "高級",
"standard": "標準",
@@ -1218,9 +1214,6 @@
"attendee_name_variable": "與會者姓名",
"event_date_variable": "活動日期",
"event_time_variable": "活動時間",
"timezone_variable": "時區",
"location_variable": "地點",
"additional_notes_variable": "備註",
"organizer_name_variable": "主辦者姓名",
"app_upgrade_description": "您必須升級至專業版帳號才能使用此功能。",
"invalid_number": "電話號碼無效",
@@ -1309,7 +1302,6 @@
"profile_picture": "個人資料圖片",
"upload": "上傳",
"add_profile_photo": "新增個人資料相片",
"web3": "Web3",
"token_address": "代幣地址",
"blockchain": "區塊鏈",
"old_password": "舊密碼",
@@ -1351,7 +1343,6 @@
"connect_automation_apps": "連至自動化應用程式",
"connect_analytics_apps": "連至分析應用程式",
"connect_other_apps": "連接其他應用程式",
"connect_web3_apps": "連接 web3 應用程式",
"connect_messaging_apps": "連接傳訊應用程式",
"connect_crm_apps": "連接 CRM 應用程式",
"current_step_of_total": "第 {{currentStep}} 步,共 {{maxSteps}} 步",
@@ -1536,6 +1527,7 @@
"you": "您",
"resend_email": "重新傳送電子郵件",
"member_already_invited": "已邀請成員",
"already_in_use_error": "此使用者名稱已在使用中",
"enter_email_or_username": "輸入電子郵件或使用者名稱",
"team_name_taken": "已有人使用此名稱",
"must_enter_team_name": "團隊名稱為必填",
@@ -1986,14 +1978,11 @@
"google_new_spam_policy": "新的 Google 垃圾郵件政策可能會使您收不到任何關於此預約的電子郵件和行事曆通知。",
"resolve": "解決",
"no_organization_slug": "為此組織建立團隊時發生錯誤。缺少網址 slug。",
"org_name": "組織名稱",
"org_url": "組織網址",
"copy_link_org": "複製組織連結",
"404_the_org": "組織",
"404_the_team": "團隊",
"404_claim_entity_org": "為您的組織預留子網域",
"404_claim_entity_team": "加入此團隊並開始共同管理行程表",
"insights_all_org_filter": "所有應用程式",
"insights_team_filter": "團隊:{{teamName}}",
"insights_user_filter": "使用者:{{userName}}",
"insights_subtitle": "查看所有活動的預約 Insight",
@@ -2005,7 +1994,6 @@
"select_date": "選取日期",
"see_all_available_times": "查看所有可預約時段",
"org_team_names_example": "例如行銷團隊",
"org_team_names_example_1": "例如行銷團隊",
"org_team_names_example_2": "例如銷售團隊",
"org_team_names_example_3": "例如設計團隊",
"org_team_names_example_4": "例如工程團隊",
@@ -2067,6 +2055,9 @@
"include_calendar_event": "包含行事曆活動",
"oAuth": "OAuth",
"recently_added": "最近新增",
"connect_all_calendars": "連接您的所有行事曆",
"workflow_automation": "工作流程自動化",
"scheduling_for_your_team": "工作流程自動化",
"no_members_found": "找不到成員",
"event_setup_length_error": "活動設定:持續時間至少必須為 1 分鐘。",
"availability_schedules": "可預約時間行程表",
@@ -2090,6 +2081,7 @@
"edit_users_availability": "編輯使用者的可預約時間:{{username}}",
"resend_invitation": "重新傳送邀請",
"invitation_resent": "邀請已重新傳送。",
"saml_sso": "SAML",
"add_client": "新增用戶端",
"copy_client_secret_info": "密碼複製後即無法再查看",
"add_new_client": "新增客戶",
@@ -2101,5 +2093,6 @@
"lock_timezone_toggle_on_booking_page": "鎖定預約頁面的時區",
"description_lock_timezone_toggle_on_booking_page": "鎖定預約頁面的時區,安排實體活動時非常實用。",
"extensive_whitelabeling": "專屬的入門和工程支援",
"need_help": "需要協助?",
"ADD_NEW_STRINGS_ABOVE_THIS_LINE_TO_PREVENT_MERGE_CONFLICTS": "↑↑↑↑↑↑↑↑↑↑↑↑↑ 請在此處新增您的字串 ↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑"
}
@@ -547,6 +547,45 @@ export function expectCalendarEventCreationFailureEmails({
);
}
export function expectSuccessfulRoudRobinReschedulingEmails({
emails,
newOrganizer,
prevOrganizer,
}: {
emails: Fixtures["emails"];
newOrganizer: { email: string; name: string };
prevOrganizer: { email: string; name: string };
}) {
if (newOrganizer !== prevOrganizer) {
// new organizer should recieve scheduling emails
expect(emails).toHaveEmail(
{
heading: "new_event_scheduled",
to: `${newOrganizer.email}`,
},
`${newOrganizer.email}`
);
// old organizer should recieve cancelled emails
expect(emails).toHaveEmail(
{
heading: "event_request_cancelled",
to: `${prevOrganizer.email}`,
},
`${prevOrganizer.email}`
);
} else {
// organizer should recieve rescheduled emails
expect(emails).toHaveEmail(
{
heading: "event_has_been_rescheduled",
to: `${newOrganizer.email}`,
},
`${newOrganizer.email}`
);
}
}
export function expectSuccessfulBookingRescheduledEmails({
emails,
organizer,
+3 -4
View File
@@ -104,10 +104,9 @@ function generateFiles() {
* If a file has index.ts or index.tsx, it can be imported after removing the index.ts* part
*/
function getModulePath(path: string, moduleName: string) {
return (
`./${path.replace(/\\/g, "/")}/` +
moduleName.replace(/\/index\.ts|\/index\.tsx/, "").replace(/\.tsx$|\.ts$/, "")
);
return `./${path.replace(/\\/g, "/")}/${moduleName
.replace(/\/index\.ts|\/index\.tsx/, "")
.replace(/\.tsx$|\.ts$/, "")}`;
}
type ImportConfig =
@@ -5,6 +5,7 @@ import type z from "zod";
import { ErrorCode } from "@calcom/lib/errorCodes";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
@@ -87,7 +88,7 @@ export class PaymentService implements IAbstractPaymentService {
}
return paymentData;
} catch (error) {
log.error("Alby: Payment could not be created", bookingId, JSON.stringify(error));
log.error("Alby: Payment could not be created", bookingId, safeStringify(error));
throw new Error(ErrorCode.PaymentCreationFailure);
}
}
-2
View File
@@ -7,8 +7,6 @@ items:
- 5.jpg
---
**FREE TRIAL until December 1st, 2023**
{DESCRIPTION}
## Example questions:
-43
View File
@@ -1,57 +1,14 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { defaultResponder } from "@calcom/lib/server";
import { createContext } from "@calcom/trpc/server/createContext";
import { apiKeysRouter } from "@calcom/trpc/server/routers/viewer/apiKeys/_router";
import checkSession from "../../_utils/auth";
import getInstalledAppPath from "../../_utils/getInstalledAppPath";
import { checkInstalled, createDefaultInstallation } from "../../_utils/installation";
import { withPaidAppRedirect } from "../../_utils/paid-apps";
import appConfig from "../config.json";
const trialEndDate = new Date(Date.UTC(2023, 11, 1));
export async function getHandler(req: NextApiRequest, res: NextApiResponse) {
const session = checkSession(req);
// if date is in the future, we install normally.
if (new Date() < trialEndDate) {
const ctx = await createContext({ req, res });
const caller = apiKeysRouter.createCaller(ctx);
const apiKey = await caller.create({
note: "Cal.ai",
expiresAt: null,
appId: "cal-ai",
});
await checkInstalled(appConfig.slug, session.user.id);
await createDefaultInstallation({
appType: appConfig.type,
userId: session.user.id,
slug: appConfig.slug,
key: {
apiKey,
},
});
await fetch(
`${process.env.NODE_ENV === "development" ? "http://localhost:3005" : "https://cal.ai"}/api/onboard`,
{
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({
userId: session.user.id,
}),
}
);
return { url: getInstalledAppPath({ variant: appConfig.variant, slug: "cal-ai" }) };
}
const redirectUrl = await withPaidAppRedirect({
appPaidMode: appConfig.paid.mode,
appSlug: appConfig.slug,
@@ -9,6 +9,7 @@ import { getFeatureFlagMap } from "@calcom/features/flags/server/utils";
import { getLocation, getRichDescription } from "@calcom/lib/CalEventParser";
import type CalendarService from "@calcom/lib/CalendarService";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";
import type {
Calendar,
@@ -106,6 +107,7 @@ export default class GoogleCalendarService implements Calendar {
});
myGoogleAuth.setCredentials(googleCredentials);
} catch (err) {
this.log.error("Error Refreshing Google Token", safeStringify(err));
let message;
if (err instanceof Error) message = err.message;
else message = String(err);
@@ -251,7 +253,10 @@ export default class GoogleCalendarService implements Calendar {
iCalUID: event.data.iCalUID,
};
} catch (error) {
console.error("There was an error contacting google calendar service: ", error);
this.log.error(
"There was an error creating event in google calendar: ",
safeStringify({ error, selectedCalendar, credentialId })
);
throw error;
}
}
@@ -332,7 +337,10 @@ export default class GoogleCalendarService implements Calendar {
}
return evt?.data;
} catch (error) {
console.error("There was an error contacting google calendar service: ", error);
this.log.error(
"There was an error updating event in google calendar: ",
safeStringify({ error, event, uid })
);
throw error;
}
}
@@ -354,6 +362,10 @@ export default class GoogleCalendarService implements Calendar {
});
return event?.data;
} catch (error) {
this.log.error(
"There was an error deleting event from google calendar: ",
safeStringify({ error, event, externalCalendarId })
);
const err = error as GoogleCalError;
/**
* 410 is when an event is already deleted on the Google cal before on cal.com
@@ -502,7 +514,10 @@ export default class GoogleCalendarService implements Calendar {
return busyData;
}
} catch (error) {
this.log.error("There was an error contacting google calendar service: ", error);
this.log.error(
"There was an error getting availability from google calendar: ",
safeStringify({ error, selectedCalendars })
);
throw error;
}
}
@@ -524,7 +539,7 @@ export default class GoogleCalendarService implements Calendar {
} satisfies IntegrationCalendar)
);
} catch (error) {
this.log.error("There was an error contacting google calendar service: ", error);
this.log.error("There was an error getting calendars: ", safeStringify(error));
throw error;
}
}
@@ -6,6 +6,7 @@ import Paypal from "@calcom/app-store/paypal/lib/Paypal";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { ErrorCode } from "@calcom/lib/errorCodes";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
@@ -91,7 +92,7 @@ export class PaymentService implements IAbstractPaymentService {
}
return paymentData;
} catch (error) {
log.error("Paypal: Payment could not be created for bookingId", bookingId);
log.error("Paypal: Payment could not be created for bookingId", bookingId, safeStringify(error));
throw new Error(ErrorCode.PaymentCreationFailure);
}
}
@@ -170,7 +171,11 @@ export class PaymentService implements IAbstractPaymentService {
}
return paymentData;
} catch (error) {
log.error("Paypal: Payment method could not be collected for bookingId", bookingId);
log.error(
"Paypal: Payment method could not be collected for bookingId",
bookingId,
safeStringify(error)
);
throw new Error("Paypal: Payment method could not be collected");
}
}
@@ -166,6 +166,7 @@ const MultiSelectWidget = ({
return (
<Select
aria-label="multi-select-dropdown"
className="mb-2"
onChange={(items) => {
setValue(items?.map((item) => item.value));
@@ -193,6 +194,7 @@ function SelectWidget({ listValues, setValue, value, ...remainingProps }: Select
return (
<Select
aria-label="select-dropdown"
className="data-testid-select mb-2"
onChange={(item) => {
if (!item) {
@@ -7,6 +7,7 @@ import { sendAwaitingPaymentEmail } from "@calcom/emails";
import { ErrorCode } from "@calcom/lib/errorCodes";
import { getErrorFromUnknown } from "@calcom/lib/errors";
import logger from "@calcom/lib/logger";
import { safeStringify } from "@calcom/lib/safeStringify";
import prisma from "@calcom/prisma";
import type { CalendarEvent } from "@calcom/types/Calendar";
import type { IAbstractPaymentService } from "@calcom/types/PaymentService";
@@ -132,8 +133,7 @@ export class PaymentService implements IAbstractPaymentService {
}
return paymentData;
} catch (error) {
console.error(`Payment could not be created for bookingId ${bookingId}`, error);
log.error("Stripe: Payment could not be created", bookingId, JSON.stringify(error));
log.error("Stripe: Payment could not be created", bookingId, safeStringify(error));
throw new Error("payment_not_created_error");
}
}
@@ -207,7 +207,7 @@ export class PaymentService implements IAbstractPaymentService {
log.error(
"Stripe: Payment method could not be collected for bookingId",
bookingId,
JSON.stringify(error)
safeStringify(error)
);
throw new Error("Stripe: Payment method could not be collected");
}
@@ -286,7 +286,7 @@ export class PaymentService implements IAbstractPaymentService {
return paymentData;
} catch (error) {
log.error("Stripe: Could not charge card for payment", _bookingId, JSON.stringify(error));
log.error("Stripe: Could not charge card for payment", _bookingId, safeStringify(error));
throw new Error(ErrorCode.ChargeCardFailure);
}
}
@@ -378,7 +378,7 @@ export class PaymentService implements IAbstractPaymentService {
await this.stripe.paymentIntents.cancel(payment.externalId, { stripeAccount });
return true;
} catch (e) {
log.error("Stripe: Unable to delete Payment in stripe of paymentId", paymentId, JSON.stringify(e));
log.error("Stripe: Unable to delete Payment in stripe of paymentId", paymentId, safeStringify(e));
return false;
}
}
+37 -23
View File
@@ -323,7 +323,9 @@ export default class EventManager {
public async reschedule(
event: CalendarEvent,
rescheduleUid: string,
newBookingId?: number
newBookingId?: number,
changedOrganizer?: boolean,
newDestinationCalendar?: DestinationCalendar[] | null
): Promise<CreateUpdateResult> {
const originalEvt = processLocation(event);
const evt = cloneDeep(originalEvt);
@@ -370,38 +372,50 @@ export default class EventManager {
}
const results: Array<EventResult<Event>> = [];
const bookingReferenceChangedOrganizer: Array<PartialReference> = [];
if (evt.requiresConfirmation) {
log.debug("RescheduleRequiresConfirmation: Deleting Event and Meeting for previous booking");
// As the reschedule requires confirmation, we can't update the events and meetings to new time yet. So, just delete them and let it be handled when organizer confirms the booking.
await this.deleteEventsAndMeetings({ booking, event });
} else {
// If the reschedule doesn't require confirmation, we can "update" the events and meetings to new time.
const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
// If and only if event type is a dedicated meeting, update the dedicated video meeting.
if (isDedicated) {
const result = await this.updateVideoEvent(evt, booking);
const [updatedEvent] = Array.isArray(result.updatedEvent)
? result.updatedEvent
: [result.updatedEvent];
if (changedOrganizer) {
log.debug("RescheduleOrganizerChanged: Deleting Event and Meeting for previous booking");
await this.deleteEventsAndMeetings({ booking, event });
if (updatedEvent) {
evt.videoCallData = updatedEvent;
evt.location = updatedEvent.url;
log.debug("RescheduleOrganizerChanged: Creating Event and Meeting for for new booking");
const newEvent = { ...evt, destinationCalendar: newDestinationCalendar };
const createdEvent = await this.create(newEvent);
results.push(...createdEvent.results);
bookingReferenceChangedOrganizer.push(...createdEvent.referencesToCreate);
} else {
// If the reschedule doesn't require confirmation, we can "update" the events and meetings to new time.
const isDedicated = evt.location ? isDedicatedIntegration(evt.location) : null;
// If and only if event type is a dedicated meeting, update the dedicated video meeting.
if (isDedicated) {
const result = await this.updateVideoEvent(evt, booking);
const [updatedEvent] = Array.isArray(result.updatedEvent)
? result.updatedEvent
: [result.updatedEvent];
if (updatedEvent) {
evt.videoCallData = updatedEvent;
evt.location = updatedEvent.url;
}
results.push(result);
}
results.push(result);
}
const bookingCalendarReference = booking.references.find((reference) =>
reference.type.includes("_calendar")
);
// There was a case that booking didn't had any reference and we don't want to throw error on function
if (bookingCalendarReference) {
// Update all calendar events.
results.push(...(await this.updateAllCalendarEvents(evt, booking, newBookingId)));
const bookingCalendarReference = booking.references.find((reference) =>
reference.type.includes("_calendar")
);
// There was a case that booking didn't had any reference and we don't want to throw error on function
if (bookingCalendarReference) {
// Update all calendar events.
results.push(...(await this.updateAllCalendarEvents(evt, booking, newBookingId)));
}
}
}
const bookingPayment = booking?.payment;
// Updating all payment to new
@@ -421,7 +435,7 @@ export default class EventManager {
return {
results,
referencesToCreate: [...booking.references],
referencesToCreate: changedOrganizer ? bookingReferenceChangedOrganizer : [...booking.references],
};
}
+1 -1
View File
@@ -144,7 +144,7 @@ const updateMeeting = async (
if (!updatedMeeting) {
log.error(
"updateMeeting failed",
JSON.stringify({ bookingRef, canCallUpdateMeeting, calEvent, credential })
safeStringify({ bookingRef, canCallUpdateMeeting, calEvent, credential })
);
return {
appName: credential.appId || "",
+31
View File
@@ -102,6 +102,37 @@ export const sendScheduledEmails = async (
await Promise.all(emailsToSend);
};
// for rescheduled round robin booking that assigned new members
export const sendRoundRobinScheduledEmails = async (calEvent: CalendarEvent, members: Person[]) => {
const emailsToSend: Promise<unknown>[] = [];
for (const teamMember of members) {
emailsToSend.push(sendEmail(() => new OrganizerScheduledEmail({ calEvent, teamMember })));
}
await Promise.all(emailsToSend);
};
export const sendRoundRobinRescheduledEmails = async (calEvent: CalendarEvent, members: Person[]) => {
const emailsToSend: Promise<unknown>[] = [];
for (const teamMember of members) {
emailsToSend.push(sendEmail(() => new OrganizerRescheduledEmail({ calEvent, teamMember })));
}
await Promise.all(emailsToSend);
};
export const sendRoundRobinCancelledEmails = async (calEvent: CalendarEvent, members: Person[]) => {
const emailsToSend: Promise<unknown>[] = [];
for (const teamMember of members) {
emailsToSend.push(sendEmail(() => new OrganizerCancelledEmail({ calEvent, teamMember })));
}
await Promise.all(emailsToSend);
};
export const sendRescheduledEmails = async (calEvent: CalendarEvent) => {
const emailsToSend: Promise<unknown>[] = [];
@@ -78,13 +78,13 @@ export const TeamInviteEmail = (
marginTop: "48px",
lineHeightStep: "24px",
}}>
<>
{/* <>
{props.language("email_no_user_invite_steps_intro", {
entity: props.language(props.isOrg ? "organization" : "team").toLowerCase(),
})}
</>
</> */}
</p>
{/*
{!props.isCalcomMember && (
<div style={{ display: "flex", flexDirection: "column", gap: "32px" }}>
<EmailStep
@@ -120,7 +120,7 @@ export const TeamInviteEmail = (
}
/>
</div>
)}
)} */}
<div className="">
<p
@@ -37,6 +37,7 @@ export default function DisconnectIntegration({
},
async onSettled() {
await utils.viewer.connectedCalendars.invalidate();
await utils.viewer.integrations.invalidate();
},
});
@@ -0,0 +1,207 @@
import type { NextApiResponse } from "next";
import stripe from "@calcom/app-store/stripepayment/lib/server";
import { getPremiumMonthlyPlanPriceId } from "@calcom/app-store/stripepayment/lib/utils";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { WEBAPP_URL } from "@calcom/lib/constants";
import { getLocaleFromRequest } from "@calcom/lib/getLocaleFromRequest";
import { HttpError } from "@calcom/lib/http-error";
import { usernameHandler, type RequestWithUsernameStatus } from "@calcom/lib/server/username";
import { createWebUser as syncServicesCreateWebUser } from "@calcom/lib/sync/SyncServiceManager";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateUsername } from "@calcom/lib/validateUsername";
import { prisma } from "@calcom/prisma";
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
import { signupSchema, teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { joinAnyChildTeamOnOrgInvite } from "../utils/organization";
import { findTokenByToken, throwIfTokenExpired, validateUsernameForTeam } from "../utils/token";
async function handler(req: RequestWithUsernameStatus, res: NextApiResponse) {
const {
email: _email,
password,
token,
} = signupSchema
.pick({
email: true,
password: true,
token: true,
})
.parse(req.body);
let username: string | null = req.usernameStatus.requestedUserName;
let checkoutSessionId: string | null = null;
// Check for premium username
if (req.usernameStatus.statusCode === 418) {
return res.status(req.usernameStatus.statusCode).json(req.usernameStatus.json);
}
// Validate the user
if (!username) {
throw new HttpError({
statusCode: 422,
message: "Invalid username",
});
}
const email = _email.toLowerCase();
let foundToken: { id: number; teamId: number | null; expires: Date } | null = null;
if (token) {
foundToken = await findTokenByToken({ token });
throwIfTokenExpired(foundToken?.expires);
await validateUsernameForTeam({ username, email, teamId: foundToken?.teamId ?? null });
} else {
const usernameAndEmailValidation = await validateUsername(username, email);
if (!usernameAndEmailValidation.isValid) {
throw new HttpError({
statusCode: 409,
message: "Username or email is already taken",
});
}
}
// Create the customer in Stripe
const customer = await stripe.customers.create({
email,
metadata: {
email /* Stripe customer email can be changed, so we add this to keep track of which email was used to signup */,
username,
},
});
const returnUrl = `${WEBAPP_URL}/api/integrations/stripepayment/paymentCallback?checkoutSessionId={CHECKOUT_SESSION_ID}&callbackUrl=/auth/verify?sessionId={CHECKOUT_SESSION_ID}`;
// Pro username, must be purchased
if (req.usernameStatus.statusCode === 402) {
const checkoutSession = await stripe.checkout.sessions.create({
mode: "subscription",
payment_method_types: ["card"],
customer: customer.id,
line_items: [
{
price: getPremiumMonthlyPlanPriceId(),
quantity: 1,
},
],
success_url: returnUrl,
cancel_url: returnUrl,
allow_promotion_codes: true,
});
/** We create a username-less user until he pays */
checkoutSessionId = checkoutSession.id;
username = null;
}
// Hash the password
const hashedPassword = await hashPassword(password);
if (foundToken && foundToken?.teamId) {
const team = await prisma.team.findUnique({
where: {
id: foundToken.teamId,
},
});
if (team) {
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
const user = await prisma.user.upsert({
where: { email },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
},
create: {
username,
email,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
},
});
// Wrapping in a transaction as if one fails we want to rollback the whole thing to preventa any data inconsistencies
const membership = await prisma.$transaction(async (tx) => {
if (teamMetadata?.isOrganization) {
await tx.user.update({
where: {
id: user.id,
},
data: {
organizationId: team.id,
},
});
}
const membership = await tx.membership.upsert({
where: {
userId_teamId: { userId: user.id, teamId: team.id },
},
update: {
accepted: true,
},
create: {
userId: user.id,
teamId: team.id,
role: MembershipRole.MEMBER,
accepted: true,
},
});
return membership;
});
closeComUpsertTeamUser(team, user, membership.role);
// Accept any child team invites for orgs.
if (team.parentId) {
await joinAnyChildTeamOnOrgInvite({
userId: user.id,
orgId: team.parentId,
});
}
}
// Cleanup token after use
await prisma.verificationToken.delete({
where: {
id: foundToken.id,
},
});
} else {
// Create the user
const user = await prisma.user.create({
data: {
username,
email,
password: hashedPassword,
metadata: {
stripeCustomerId: customer.id,
checkoutSessionId,
},
},
});
sendEmailVerification({
email,
language: await getLocaleFromRequest(req),
username: username || "",
});
// Sync Services
await syncServicesCreateWebUser(user);
}
if (checkoutSessionId) {
console.log("Created user but missing payment", checkoutSessionId);
return res.status(402).json({
message: "Created user but missing payment",
checkoutSessionId,
});
}
return res.status(201).json({ message: "Created user", stripeCustomerId: customer.id });
}
export default usernameHandler(handler);
@@ -0,0 +1,147 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { checkPremiumUsername } from "@calcom/ee/common/lib/checkPremiumUsername";
import { hashPassword } from "@calcom/features/auth/lib/hashPassword";
import { sendEmailVerification } from "@calcom/features/auth/lib/verifyEmail";
import { IS_PREMIUM_USERNAME_ENABLED } from "@calcom/lib/constants";
import slugify from "@calcom/lib/slugify";
import { closeComUpsertTeamUser } from "@calcom/lib/sync/SyncServiceManager";
import { validateUsername } from "@calcom/lib/validateUsername";
import prisma from "@calcom/prisma";
import { IdentityProvider, MembershipRole } from "@calcom/prisma/enums";
import { signupSchema } from "@calcom/prisma/zod-utils";
import { teamMetadataSchema } from "@calcom/prisma/zod-utils";
import { joinAnyChildTeamOnOrgInvite } from "../utils/organization";
import { findTokenByToken, throwIfTokenExpired, validateUsernameForTeam } from "../utils/token";
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const data = req.body;
const { email, password, language, token } = signupSchema.parse(data);
const username = slugify(data.username);
const userEmail = email.toLowerCase();
if (!username) {
res.status(422).json({ message: "Invalid username" });
return;
}
let foundToken: { id: number; teamId: number | null; expires: Date } | null = null;
if (token) {
foundToken = await findTokenByToken({ token });
throwIfTokenExpired(foundToken?.expires);
await validateUsernameForTeam({ username, email: userEmail, teamId: foundToken?.teamId });
} else {
const userValidation = await validateUsername(username, userEmail);
if (!userValidation.isValid) {
return res.status(409).json({ message: "Username or email is already taken" });
}
}
const hashedPassword = await hashPassword(password);
if (foundToken && foundToken?.teamId) {
const team = await prisma.team.findUnique({
where: {
id: foundToken.teamId,
},
});
if (team) {
const teamMetadata = teamMetadataSchema.parse(team?.metadata);
const user = await prisma.user.upsert({
where: { email: userEmail },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
},
create: {
username,
email: userEmail,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
},
});
const membership = await prisma.$transaction(async (tx) => {
if (teamMetadata?.isOrganization) {
await tx.user.update({
where: {
id: user.id,
},
data: {
organizationId: team.id,
},
});
}
const membership = await tx.membership.upsert({
where: {
userId_teamId: { userId: user.id, teamId: team.id },
},
update: {
accepted: true,
},
create: {
userId: user.id,
teamId: team.id,
role: MembershipRole.MEMBER,
accepted: true,
},
});
return membership;
});
closeComUpsertTeamUser(team, user, membership.role);
// Accept any child team invites for orgs.
if (team.parentId) {
await joinAnyChildTeamOnOrgInvite({
userId: user.id,
orgId: team.parentId,
});
}
}
// Cleanup token after use
await prisma.verificationToken.delete({
where: {
id: foundToken.id,
},
});
} else {
if (IS_PREMIUM_USERNAME_ENABLED) {
const checkUsername = await checkPremiumUsername(username);
if (checkUsername.premium) {
res.status(422).json({
message: "Sign up from https://cal.com/signup to claim your premium username",
});
return;
}
}
await prisma.user.upsert({
where: { email: userEmail },
update: {
username,
password: hashedPassword,
emailVerified: new Date(Date.now()),
identityProvider: IdentityProvider.CAL,
},
create: {
username,
email: userEmail,
password: hashedPassword,
identityProvider: IdentityProvider.CAL,
},
});
await sendEmailVerification({
email: userEmail,
username,
language,
});
}
res.status(201).json({ message: "Created user" });
}
+124
View File
@@ -0,0 +1,124 @@
import type { NextApiRequest, NextApiResponse } from "next";
import { z } from "zod";
import notEmpty from "@calcom/lib/notEmpty";
import { isPremiumUserName, generateUsernameSuggestion } from "@calcom/lib/server/username";
import slugify from "@calcom/lib/slugify";
import prisma from "@calcom/prisma";
export type RequestWithUsernameStatus = NextApiRequest & {
usernameStatus: {
/**
* ```text
* 200: Username is available
* 402: Pro username, must be purchased
* 418: A user exists with that username
* ```
*/
statusCode: 200 | 402 | 418;
requestedUserName: string;
json: {
available: boolean;
premium: boolean;
message?: string;
suggestion?: string;
};
};
};
export const usernameStatusSchema = z.object({
statusCode: z.union([z.literal(200), z.literal(402), z.literal(418)]),
requestedUserName: z.string(),
json: z.object({
available: z.boolean(),
premium: z.boolean(),
message: z.string().optional(),
suggestion: z.string().optional(),
}),
});
type CustomNextApiHandler<T = unknown> = (
req: RequestWithUsernameStatus,
res: NextApiResponse<T>
) => void | Promise<void>;
const usernameHandler =
(handler: CustomNextApiHandler) =>
async (req: RequestWithUsernameStatus, res: NextApiResponse): Promise<void> => {
const username = slugify(req.body.username);
const check = await usernameCheck(username);
req.usernameStatus = {
statusCode: 200,
requestedUserName: username,
json: {
available: true,
premium: false,
message: "Username is available",
},
};
if (check.premium) {
req.usernameStatus.statusCode = 402;
req.usernameStatus.json.premium = true;
req.usernameStatus.json.message = "This is a premium username.";
}
if (!check.available) {
req.usernameStatus.statusCode = 418;
req.usernameStatus.json.available = false;
req.usernameStatus.json.message = "A user exists with that username";
}
req.usernameStatus.json.suggestion = check.suggestedUsername;
return handler(req, res);
};
const usernameCheck = async (usernameRaw: string) => {
const response = {
available: true,
premium: false,
suggestedUsername: "",
};
const username = slugify(usernameRaw);
const user = await prisma.user.findFirst({
where: { username, organizationId: null },
select: {
username: true,
},
});
if (user) {
response.available = false;
}
if (await isPremiumUserName(username)) {
response.premium = true;
}
// get list of similar usernames in the db
const users = await prisma.user.findMany({
where: {
username: {
contains: username,
},
},
select: {
username: true,
},
});
// We only need suggestedUsername if the username is not available
if (!response.available) {
response.suggestedUsername = await generateUsernameSuggestion(
users.map((user) => user.username).filter(notEmpty),
username
);
}
return response;
};
export { usernameHandler, usernameCheck };

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