diff --git a/.github/ISSUE_TEMPLATE/feature_request.md b/.github/ISSUE_TEMPLATE/feature_request.md
index 0040d6d364..7c266e0957 100644
--- a/.github/ISSUE_TEMPLATE/feature_request.md
+++ b/.github/ISSUE_TEMPLATE/feature_request.md
@@ -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.
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index 2ef5c789ea..0b19591ddd 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -40,7 +40,7 @@ Fixes # (issue)
## Checklist
-
+
- 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
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 520107e0dc..eb52da8d6f 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -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
diff --git a/README.md b/README.md
index 2b19c6a3fe..a617a65b1f 100644
--- a/README.md
+++ b/README.md
@@ -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:
-[
](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/)
diff --git a/apps/api/pages/api/event-types/_post.ts b/apps/api/pages/api/event-types/_post.ts
index 79a537c420..1531485e7b 100644
--- a/apps/api/pages/api/event-types/_post.ts
+++ b/apps/api/pages/api/event-types/_post.ts
@@ -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);
diff --git a/apps/web/components/booking/BookingListItem.tsx b/apps/web/components/booking/BookingListItem.tsx
index 32bb2ca515..fe33b6e9df 100644
--- a/apps/web/components/booking/BookingListItem.tsx
+++ b/apps/web/components/booking/BookingListItem.tsx
@@ -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 && (
) : null}
{isPast && isPending && !isConfirmed ? : null}
- {(showRecordingsButtons || checkForRecordingsButton) && (
-
- )}
+ {showRecordingsButtons && }
{isCancelled && booking.rescheduled && (
diff --git a/apps/web/components/dialog/EditLocationDialog.tsx b/apps/web/components/dialog/EditLocationDialog.tsx
index b891235c65..252d5fcf92 100644
--- a/apps/web/components/dialog/EditLocationDialog.tsx
+++ b/apps/web/components/dialog/EditLocationDialog.tsx
@@ -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([
diff --git a/apps/web/components/eventtype/EventSetupTab.tsx b/apps/web/components/eventtype/EventSetupTab.tsx
index 754f060868..559762f9cf 100644
--- a/apps/web/components/eventtype/EventSetupTab.tsx
+++ b/apps/web/components/eventtype/EventSetupTab.tsx
@@ -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");
diff --git a/apps/web/components/ui/form/LocationSelect.tsx b/apps/web/components/ui/form/LocationSelect.tsx
index 5eed7c3b8e..1e3ede2341 100644
--- a/apps/web/components/ui/form/LocationSelect.tsx
+++ b/apps/web/components/ui/form/LocationSelect.tsx
@@ -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
;
diff --git a/packages/app-store/server.ts b/packages/app-store/server.ts
index f88ec7597f..9bea796991 100644
--- a/packages/app-store/server.ts
+++ b/packages/app-store/server.ts
@@ -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 {
diff --git a/packages/embeds/embed-core/src/embed.ts b/packages/embeds/embed-core/src/embed.ts
index df9f27e2e5..0cb67b3a05 100644
--- a/packages/embeds/embed-core/src/embed.ts
+++ b/packages/embeds/embed-core/src/embed.ts
@@ -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,
});
}
diff --git a/packages/features/ee/teams/components/MemberListItem.tsx b/packages/features/ee/teams/components/MemberListItem.tsx
index 936732b7dc..2b356747ca 100644
--- a/packages/features/ee/teams/components/MemberListItem.tsx
+++ b/packages/features/ee/teams/components/MemberListItem.tsx
@@ -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 &&
diff --git a/packages/features/ee/teams/pages/team-members-view.tsx b/packages/features/ee/teams/pages/team-members-view.tsx
index ca73614763..227c213f08 100644
--- a/packages/features/ee/teams/pages/team-members-view.tsx
+++ b/packages/features/ee/teams/pages/team-members-view.tsx
@@ -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("");
@@ -56,7 +57,14 @@ function MembersList(props: MembersListProps) {
{membersList?.length && team ? (
{membersList.map((member) => {
- return ;
+ return (
+
+ );
})}
) : null}
@@ -161,7 +169,7 @@ const MembersView = () => {
{((team?.isPrivate && isAdmin) || !team?.isPrivate || isOrgAdminOrOwner) && (
<>
-
+
>
)}
diff --git a/packages/lib/server/defaultResponder.ts b/packages/lib/server/defaultResponder.ts
index 7caf1e1b51..b70f99cd04 100644
--- a/packages/lib/server/defaultResponder.ts
+++ b/packages/lib/server/defaultResponder.ts
@@ -13,7 +13,9 @@ export function defaultResponder(f: Handle) {
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);
diff --git a/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts b/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts
index 9f9ed4bbb6..96b7b3cc9d 100644
--- a/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts
+++ b/packages/trpc/server/routers/viewer/teams/removeMember.handler.ts
@@ -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" });