Merge branch 'main' into testE2E-timezone

This commit is contained in:
GitStart-Cal.com 2023-11-03 05:51:00 +05:45 committed by GitHub
commit 4c9461937b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
15 changed files with 85 additions and 32 deletions

View File

@ -47,3 +47,9 @@ assignees: ""
-->
(Share it here.)
---
##### House rules
- If this issue has a `🚨 needs approval` label, don't start coding yet. Wait until a core member approves feature request by removing this label, then you can start coding.
- For clarity: Non-core member issues automatically get the `🚨 needs approval` label.
- Your feature ideas are invaluable to us! However, they undergo review to ensure alignment with the product's direction.

View File

@ -40,7 +40,7 @@ Fixes # (issue)
## Checklist
<!-- Please remove all the irrelevant bullets to your PR -->
<!-- Remove bullet points below that don't apply to you -->
- 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

View File

@ -2,7 +2,18 @@
Contributions are what makes the open source community such an amazing place to learn, inspire, and create. Any contributions you make are **greatly appreciated**.
- Before jumping into a PR be sure to search [existing PRs](https://github.com/calcom/cal.com/pulls) or [issues](https://github.com/calcom/cal.com/issues) for an open or closed item that relates to your submission.
## House rules
- Before submitting a new issue or PR, check if it already exists in [issues](https://github.com/calcom/cal.com/issues) or [PRs](https://github.com/calcom/cal.com/pulls).
- GitHub issues: take note of the `🚨 needs approval` label.
- **For Contributors**:
- Feature Requests: Wait for a core member to approve and remove the `🚨 needs approval` label before you start coding or submit a PR.
- Bugs, Security, Performance, Documentation, etc.: You can start coding immediately, even if the `🚨 needs approval` label is present. This label mainly concerns feature requests.
- **Our Process**:
- Issues from non-core members automatically receive the `🚨 needs approval` label.
- We greatly value new feature ideas. To ensure consistency in the product's direction, they undergo review and approval.
## Priorities

View File

@ -122,7 +122,7 @@ Here is what you need to be able to run Cal.com.
### Setup
1. Clone the repo into a public GitHub repository (or fork https://github.com/calcom/cal.com/fork). If you plan to distribute the code, keep the source code public to comply with [AGPLv3](https://github.com/calcom/cal.com/blob/main/LICENSE). To clone in a private repository, [acquire a commercial license](https://cal.com/sales))
1. Clone the repo into a public GitHub repository (or fork https://github.com/calcom/cal.com/fork). If you plan to distribute the code, keep the source code public to comply with [AGPLv3](https://github.com/calcom/cal.com/blob/main/LICENSE). To clone in a private repository, [acquire a commercial license](https://cal.com/sales)
```sh
git clone https://github.com/calcom/cal.com.git
@ -221,7 +221,7 @@ echo 'NEXT_PUBLIC_DEBUG=1' >> .env
1. Copy and paste your `DATABASE_URL` from `.env` to `.env.appStore`.
1. Set a 32 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set a 24 character random string in your `.env` file for the `CALENDSO_ENCRYPTION_KEY` (You can use a command like `openssl rand -base64 24` to generate one).
1. Set up the database using the Prisma schema (found in `packages/prisma/schema.prisma`)
In a development environment, run:
@ -597,8 +597,6 @@ Distributed under the [AGPLv3 License](https://github.com/calcom/cal.com/blob/ma
Special thanks to these amazing projects which help power Cal.com:
[<img src="https://cal.com/powered-by-vercel.svg">](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
- [Vercel](https://vercel.com/?utm_source=calend-so&utm_campaign=oss)
- [Next.js](https://nextjs.org/)
- [Day.js](https://day.js.org/)

View File

@ -316,8 +316,9 @@ async function checkPermissions(req: NextApiRequest) {
statusCode: 401,
message: "ADMIN required for `userId`",
});
/* Admin users are required to pass in a userId */
if (isAdmin && !body.userId) throw new HttpError({ statusCode: 400, message: "`userId` required" });
/* Admin users are required to pass in a userId or teamId */
if (isAdmin && (!body.userId || !body.teamId))
throw new HttpError({ statusCode: 400, message: "`userId` or `teamId` required" });
}
export default defaultResponder(postHandler);

View File

@ -261,13 +261,11 @@ function BookingListItem(booking: BookingItemProps) {
const title = booking.title;
const showRecordingsButtons = !!(booking.isRecorded && isPast && isConfirmed);
const checkForRecordingsButton =
!showRecordingsButtons && (booking.location === "integrations:daily" || booking?.location?.trim() === "");
const showRecordingActions: ActionType[] = [
{
id: checkForRecordingsButton ? "check_for_recordings" : "view_recordings",
label: checkForRecordingsButton ? t("check_for_recordings") : t("view_recordings"),
id: "view_recordings",
label: t("view_recordings"),
onClick: () => {
setViewRecordingsDialogIsOpen(true);
},
@ -298,7 +296,7 @@ function BookingListItem(booking: BookingItemProps) {
paymentCurrency={booking.payment[0].currency}
/>
)}
{(showRecordingsButtons || checkForRecordingsButton) && (
{showRecordingsButtons && (
<ViewRecordingsDialog
booking={booking}
isOpenDialog={viewRecordingsDialogIsOpen}
@ -468,9 +466,7 @@ function BookingListItem(booking: BookingItemProps) {
</>
) : null}
{isPast && isPending && !isConfirmed ? <TableActions actions={bookedActions} /> : null}
{(showRecordingsButtons || checkForRecordingsButton) && (
<TableActions actions={showRecordingActions} />
)}
{showRecordingsButtons && <TableActions actions={showRecordingActions} />}
{isCancelled && booking.rescheduled && (
<div className="hidden h-full items-center md:flex">
<RequestSentMessage />

View File

@ -356,9 +356,9 @@ export const EditLocationDialog = (props: ISetLocationDialog) => {
onChange={(val) => {
if (val) {
locationFormMethods.setValue("locationType", val.value);
if (val.credential) {
locationFormMethods.setValue("credentialId", val.credential.id);
locationFormMethods.setValue("teamName", val.credential.team?.name);
if (!!val.credentialId) {
locationFormMethods.setValue("credentialId", val.credentialId);
locationFormMethods.setValue("teamName", val.teamName);
}
locationFormMethods.unregister([

View File

@ -298,9 +298,21 @@ export const EventSetupTab = (
!validLocations.find((location) => location.type === newLocationType);
if (canAddLocation) {
updateLocationField(index, { type: newLocationType });
updateLocationField(index, {
type: newLocationType,
...(e.credentialId && {
credentialId: e.credentialId,
teamName: e.teamName,
}),
});
} else {
updateLocationField(index, { type: field.type });
updateLocationField(index, {
type: field.type,
...(field.credentialId && {
credentialId: field.credentialId,
teamName: field.teamName,
}),
});
showToast(t("location_already_exists"), "warning");
}
}
@ -382,7 +394,13 @@ export const EventSetupTab = (
!validLocations.find((location) => location.type === newLocationType);
if (canAppendLocation) {
append({ type: newLocationType });
append({
type: newLocationType,
...(e.credentialId && {
credentialId: e.credentialId,
teamName: e.teamName,
}),
});
setSelectedNewOption(e);
} else {
showToast(t("location_already_exists"), "warning");

View File

@ -2,7 +2,6 @@ import type { GroupBase, Props, SingleValue } from "react-select";
import { components } from "react-select";
import type { EventLocationType } from "@calcom/app-store/locations";
import type { CredentialDataWithTeamName } from "@calcom/app-store/utils";
import { classNames } from "@calcom/lib";
import invertLogoOnDark from "@calcom/lib/invertLogoOnDark";
import { Select } from "@calcom/ui";
@ -13,7 +12,8 @@ export type LocationOption = {
icon?: string;
disabled?: boolean;
address?: string;
credential?: CredentialDataWithTeamName;
credentialId?: number;
teamName?: string;
};
export type SingleValueLocationOption = SingleValue<LocationOption>;

View File

@ -88,7 +88,13 @@ export async function getLocationGroupedOptions(
teamName: credential.team?.name,
}))) {
const label = `${app.locationOption.label} ${teamName ? `(${teamName})` : ""}`;
const option = { ...app.locationOption, label, icon: app.logo, slug: app.slug };
const option = {
...app.locationOption,
label,
icon: app.logo,
slug: app.slug,
...(app.credential ? { credentialId: app.credential.id, teamName: app.credential.team?.name } : {}),
};
if (apps[groupByCategory]) {
apps[groupByCategory] = [...apps[groupByCategory], option];
} else {

View File

@ -236,7 +236,7 @@ export class Cal {
}: {
calLink: string;
queryObject?: PrefillAndIframeAttrsConfig & { guest?: string | string[] };
calOrigin?: string;
calOrigin: string | null;
}) {
const iframe = (this.iframe = document.createElement("iframe"));
iframe.className = "cal-embed";
@ -473,10 +473,12 @@ class CalApi {
}
config.embedType = "inline";
const calConfig = this.cal.getConfig();
const iframe = this.cal.createIframe({
calLink,
queryObject: withColorScheme(Cal.getQueryObject(config), containerEl),
calOrigin: calConfig.calOrigin,
});
iframe.style.height = "100%";
@ -555,6 +557,7 @@ class CalApi {
modal({
calLink,
config = {},
calOrigin,
__prerender = false,
}: {
calLink: string;
@ -607,6 +610,7 @@ class CalApi {
iframe = this.cal.createIframe({
calLink,
queryObject,
calOrigin: calOrigin || null,
});
}

View File

@ -37,6 +37,7 @@ import TeamPill, { TeamRole } from "./TeamPill";
interface Props {
team: RouterOutputs["viewer"]["teams"]["get"];
member: RouterOutputs["viewer"]["teams"]["get"]["members"][number];
isOrgAdminOrOwner: boolean | undefined;
}
/** TODO: Migrate the one in apps/web to tRPC package */
@ -109,7 +110,8 @@ export default function MemberListItem(props: Props) {
(props.member.role !== MembershipRole.OWNER ||
ownersInTeam() > 1 ||
props.member.id !== currentUserId)) ||
(props.team.membership?.role === MembershipRole.ADMIN && props.member.role !== MembershipRole.OWNER);
(props.team.membership?.role === MembershipRole.ADMIN && props.member.role !== MembershipRole.OWNER) ||
props.isOrgAdminOrOwner;
const impersonationMode =
editMode &&
!props.member.disableImpersonation &&

View File

@ -22,13 +22,14 @@ type Team = RouterOutputs["viewer"]["teams"]["get"];
interface MembersListProps {
team: Team | undefined;
isOrgAdminOrOwner: boolean | undefined;
}
const checkIfExist = (comp: string, query: string) =>
comp.toLowerCase().replace(/\s+/g, "").includes(query.toLowerCase().replace(/\s+/g, ""));
function MembersList(props: MembersListProps) {
const { team } = props;
const { team, isOrgAdminOrOwner } = props;
const { t } = useLocale();
const [query, setQuery] = useState<string>("");
@ -56,7 +57,14 @@ function MembersList(props: MembersListProps) {
{membersList?.length && team ? (
<ul className="divide-subtle border-subtle divide-y rounded-md border ">
{membersList.map((member) => {
return <MemberListItem key={member.id} team={team} member={member} />;
return (
<MemberListItem
key={member.id}
team={team}
member={member}
isOrgAdminOrOwner={isOrgAdminOrOwner}
/>
);
})}
</ul>
) : null}
@ -161,7 +169,7 @@ const MembersView = () => {
{((team?.isPrivate && isAdmin) || !team?.isPrivate || isOrgAdminOrOwner) && (
<>
<MembersList team={team} />
<MembersList team={team} isOrgAdminOrOwner={isOrgAdminOrOwner} />
<hr className="border-subtle my-8" />
</>
)}

View File

@ -13,7 +13,9 @@ export function defaultResponder<T>(f: Handle<T>) {
performance.mark("Start");
const result = await f(req, res);
ok = true;
if (result) res.json(result);
if (result && !res.writableEnded) {
res.json(result);
}
} catch (err) {
console.error(err);
const error = getServerErrorFromUnknown(err);

View File

@ -23,7 +23,8 @@ export const removeMemberHandler = async ({ ctx, input }: RemoveMemberOptions) =
const isOrgAdmin = ctx.user.organizationId
? await isTeamAdmin(ctx.user.id, ctx.user.organizationId)
: false;
if (!isAdmin && ctx.user.id !== input.memberId) throw new TRPCError({ code: "UNAUTHORIZED" });
if (!(isAdmin || isOrgAdmin) && ctx.user.id !== input.memberId)
throw new TRPCError({ code: "UNAUTHORIZED" });
// Only a team owner can remove another team owner.
if ((await isTeamOwner(input.memberId, input.teamId)) && !(await isTeamOwner(ctx.user.id, input.teamId)))
throw new TRPCError({ code: "UNAUTHORIZED" });