mirror of
https://github.com/hanzoai/calendar.git
synced 2026-10-30 14:46:43 +00:00
* fix * move event types components to web * update import paths * mv apps components * migrate form builder * fix * mv sso * fix * mv * update import paths * update import paths * mv * mv * mv * fix * update Booker * fix * fix * fix * fix * mv video * mv embed components to web * update import paths * mv calendar weekly view components * update import paths * fix * fixp * fix * fix * fix * fix: update FormBuilder imports to use @calcom/features/form-builder Co-Authored-By: [email protected] <[email protected]> * fix: update broken import paths after file migrations Co-Authored-By: [email protected] <[email protected]> * fix: correct import paths for platform atoms and moved components Co-Authored-By: [email protected] <[email protected]> * fix: apply CSS type fixes and add missing atoms exports Co-Authored-By: [email protected] <[email protected]> * fix: resolve type errors in test files after component migrations Co-Authored-By: [email protected] <[email protected]> * fix: resolve remaining type errors in test files Co-Authored-By: [email protected] <[email protected]> * fix * migrate * fix: resolve type errors in test and mock files - Add missing bookingForm, bookerFormErrorRef, instantConnectCooldownMs to Booker.test.tsx bookings prop - Add all required BookerEvent properties to event.mock.ts - Add vi import from vitest to all mock files - Fix date parameter types in packages/dayjs/__mocks__/index.ts - Add verificationCode and setVerificationCode to test-utils.tsx mock store - Remove children.type access in Section.tsx mock to fix type error - Fix lint issues: remove unused React imports, use import type where needed, add return types - Add biome-ignore comments for pre-existing lint warnings in test files Co-Authored-By: [email protected] <[email protected]> * migrate * migrate * migrate * update import paths * update import paths * update import paths * fix * migrate data table * migrate data table * fix * fix * fix * migrate insights components * migrate insights components * fix * mv * update import paths * fix * fix * fix * fix * fix * fix: resolve type errors in test mocks - Booker.test.tsx: Add all required UseFormReturn methods to bookingForm mock - event.mock.ts: Fix entity, subsetOfHosts, instantMeetingParameters, fieldTranslations, image types - dayjs/__mocks__/index.ts: Use Object.assign for proper typing of mock properties - Section.tsx: Change 'class' to 'className' in JSX with biome-ignore comment Co-Authored-By: [email protected] <[email protected]> * fix: add missing hasDataErrors and dataErrors to bookings.errors mock Co-Authored-By: [email protected] <[email protected]> * fix: add missing loadingStates properties to bookings mock Co-Authored-By: [email protected] <[email protected]> * fix: add missing slots properties (setTentativeSelectedTimeslots, tentativeSelectedTimeslots, slotReservationId) Co-Authored-By: [email protected] <[email protected]> * fix: update quickAvailabilityChecks to include utcEndIso and use valid status type Co-Authored-By: [email protected] <[email protected]> * fix: add missing bookerForm properties (formName, beforeVerifyEmail, formErrors) Co-Authored-By: [email protected] <[email protected]> * fix: add missing UseFormReturn properties to bookerForm.bookingForm mock Co-Authored-By: [email protected] <[email protected]> * fix: add hasFormErrors and formErrors to bookerForm.formErrors mock Co-Authored-By: [email protected] <[email protected]> * fix: add hasFormErrors and formErrors to bookerForm.errors mock Co-Authored-By: [email protected] <[email protected]> * fix: add missing isError property to mockEvent Co-Authored-By: [email protected] <[email protected]> * fix: use complete BookerEvent mock in Booker.test.tsx Co-Authored-By: [email protected] <[email protected]> * fix: use branded bookingFields type in event mock Co-Authored-By: [email protected] <[email protected]> * fix: add missing schedule mock properties (isError, isSuccess, isLoading, dataUpdatedAt) Co-Authored-By: [email protected] <[email protected]> * revert * fix * fix * fix * fix build * fix * fix * fix * fix: correct AddMembersWithSwitch test wrapper to use initial assignAllTeamMembers value - Fixed test wrapper to initialize useState with componentProps.assignAllTeamMembers instead of hardcoded false, allowing tests to properly test different states - Updated test expectations for ALL_TEAM_MEMBERS_ENABLED_AND_SEGMENT_NOT_APPLICABLE state to match actual component behavior (toggle should be present and checked) - Fixed 'should show Segment when toggled on' test to start with assignAllTeamMembers: false to properly test the flow of enabling it - Added explicit types to satisfy biome lint requirements Co-Authored-By: [email protected] <[email protected]> * fix: use JSX.Element instead of React.JSX.Element for type compatibility Co-Authored-By: [email protected] <[email protected]> --------- Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
262 lines
8.2 KiB
TypeScript
262 lines
8.2 KiB
TypeScript
"use client";
|
|
|
|
// import { debounce } from "lodash";
|
|
import { keepPreviousData } from "@tanstack/react-query";
|
|
import { useSession } from "next-auth/react";
|
|
import { useRouter } from "next/navigation";
|
|
import { useState, useEffect } from "react";
|
|
|
|
import { checkAdminOrOwner } from "@calcom/features/auth/lib/checkAdminOrOwner";
|
|
import MemberListItem from "~/ee/organizations/components/MemberListItem";
|
|
import { useLocale } from "@calcom/lib/hooks/useLocale";
|
|
import { useParamsWithFallback } from "@calcom/lib/hooks/useParamsWithFallback";
|
|
import { CreationSource } from "@calcom/prisma/enums";
|
|
import { trpc } from "@calcom/trpc/react";
|
|
import type { RouterOutputs } from "@calcom/trpc/react";
|
|
import { Button } from "@calcom/ui/components/button";
|
|
import { showToast } from "@calcom/ui/components/toast";
|
|
import { revalidateTeamsList } from "@calcom/web/app/(use-page-wrapper)/(main-nav)/teams/actions";
|
|
|
|
import MakeTeamPrivateSwitch from "~/ee/teams/components/MakeTeamPrivateSwitch";
|
|
import MemberInvitationModal from "~/ee/teams/components/MemberInvitationModal";
|
|
|
|
type Members = RouterOutputs["viewer"]["organizations"]["listOtherTeamMembers"]["rows"];
|
|
type Team = RouterOutputs["viewer"]["organizations"]["getOtherTeam"];
|
|
|
|
interface MembersListProps {
|
|
members: Members | undefined;
|
|
team: Team | undefined;
|
|
fetchNextPage: () => void;
|
|
hasNextPage: boolean | undefined;
|
|
isFetchingNextPage: boolean | undefined;
|
|
}
|
|
|
|
function MembersList(props: MembersListProps) {
|
|
const { t } = useLocale();
|
|
const { hasNextPage, members = [], team, fetchNextPage, isFetchingNextPage } = props;
|
|
|
|
return (
|
|
<div className="flex flex-col gap-y-3">
|
|
{members?.length && team ? (
|
|
<ul className="divide-subtle border-subtle divide-y rounded-md border">
|
|
{members.map((member) => {
|
|
return <MemberListItem key={member.id} member={member} />;
|
|
})}
|
|
</ul>
|
|
) : null}
|
|
{members?.length === 0 && (
|
|
<div className="flex flex-col items-center justify-center">
|
|
<p className="text-default text-sm font-bold">{t("no_members_found")}</p>
|
|
</div>
|
|
)}
|
|
<div className="text-default p-4 text-center">
|
|
<Button
|
|
color="minimal"
|
|
loading={isFetchingNextPage}
|
|
disabled={!hasNextPage}
|
|
onClick={() => fetchNextPage()}>
|
|
{hasNextPage ? t("load_more_results") : t("no_more_results")}
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
export const memberInvitationModalRef = {
|
|
current: null as null | ((show: boolean) => void),
|
|
};
|
|
|
|
export const TeamMembersCTA = () => {
|
|
const { t } = useLocale();
|
|
const session = useSession();
|
|
const { data: currentOrg } = trpc.viewer.organizations.listCurrent.useQuery(undefined, {
|
|
enabled: !!session.data?.user?.org,
|
|
});
|
|
|
|
const isOrgAdminOrOwner = currentOrg && checkAdminOrOwner(currentOrg.user.role);
|
|
|
|
if (!isOrgAdminOrOwner) return null;
|
|
|
|
return (
|
|
<Button
|
|
type="button"
|
|
color="primary"
|
|
StartIcon="plus"
|
|
className="ml-auto"
|
|
onClick={() => memberInvitationModalRef.current?.(true)}
|
|
data-testid="new-member-button">
|
|
{t("add")}
|
|
</Button>
|
|
);
|
|
};
|
|
|
|
const MembersView = () => {
|
|
const { t, i18n } = useLocale();
|
|
const router = useRouter();
|
|
const params = useParamsWithFallback();
|
|
const teamId = Number(params.id);
|
|
const utils = trpc.useUtils();
|
|
// const [query, setQuery] = useState<string | undefined>("");
|
|
// const [queryToFetch, setQueryToFetch] = useState<string | undefined>("");
|
|
const limit = 20;
|
|
const [showMemberInvitationModal, setShowMemberInvitationModal] = useState<boolean>(false);
|
|
|
|
const {
|
|
data: team,
|
|
isPending: isTeamLoading,
|
|
error: otherTeamError,
|
|
} = trpc.viewer.organizations.getOtherTeam.useQuery(
|
|
{ teamId },
|
|
{
|
|
enabled: !Number.isNaN(teamId),
|
|
}
|
|
);
|
|
const { data: orgMembersNotInThisTeam, isPending: isOrgListLoading } =
|
|
trpc.viewer.organizations.getMembers.useQuery(
|
|
{
|
|
teamIdToExclude: teamId,
|
|
distinctUser: true,
|
|
},
|
|
{
|
|
enabled: !Number.isNaN(teamId),
|
|
}
|
|
);
|
|
|
|
const {
|
|
fetchNextPage,
|
|
isFetchingNextPage,
|
|
hasNextPage,
|
|
error: otherMembersError,
|
|
data,
|
|
} = trpc.viewer.organizations.listOtherTeamMembers.useInfiniteQuery(
|
|
{ teamId, limit },
|
|
{
|
|
enabled: !Number.isNaN(teamId),
|
|
getNextPageParam: (lastPage) => lastPage.nextCursor,
|
|
placeholderData: keepPreviousData,
|
|
}
|
|
);
|
|
|
|
useEffect(
|
|
function refactorMeWithoutEffect() {
|
|
if (otherMembersError || otherTeamError) {
|
|
router.replace("/enterprise");
|
|
}
|
|
},
|
|
[router, otherMembersError, otherTeamError]
|
|
);
|
|
|
|
useEffect(() => {
|
|
memberInvitationModalRef.current = setShowMemberInvitationModal;
|
|
return () => {
|
|
memberInvitationModalRef.current = null;
|
|
};
|
|
}, []);
|
|
|
|
const isPending = isTeamLoading || isOrgListLoading;
|
|
const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation({
|
|
onSuccess: () => {
|
|
utils.viewer.organizations.getMembers.invalidate();
|
|
utils.viewer.organizations.listOtherTeams.invalidate();
|
|
utils.viewer.teams.list.invalidate();
|
|
revalidateTeamsList();
|
|
utils.viewer.organizations.listOtherTeamMembers.invalidate();
|
|
},
|
|
});
|
|
|
|
return (
|
|
<>
|
|
{!isPending && (
|
|
<>
|
|
<div>
|
|
<>
|
|
{/* Currently failing due to re render and loose focus */}
|
|
{/* <TextField
|
|
type="search"
|
|
autoComplete="false"
|
|
onChange={(e) => {
|
|
setQuery(e.target.value);
|
|
debouncedFunction(e.target.value);
|
|
}}
|
|
value={query}
|
|
placeholder={`${t("search")}...`}
|
|
/> */}
|
|
<MembersList
|
|
members={data?.pages?.flatMap((page) => page.rows) ?? []}
|
|
team={team}
|
|
fetchNextPage={fetchNextPage}
|
|
hasNextPage={hasNextPage}
|
|
isFetchingNextPage={isFetchingNextPage}
|
|
/>
|
|
</>
|
|
|
|
{team && (
|
|
<>
|
|
<hr className="border-subtle my-8 mt-6" />
|
|
<MakeTeamPrivateSwitch
|
|
teamId={team.id}
|
|
isPrivate={team.isPrivate}
|
|
disabled={false}
|
|
isOrg={false}
|
|
/>
|
|
</>
|
|
)}
|
|
</div>
|
|
{showMemberInvitationModal && team && (
|
|
<MemberInvitationModal
|
|
isPending={inviteMemberMutation.isPending}
|
|
isOpen={showMemberInvitationModal}
|
|
orgMembers={orgMembersNotInThisTeam}
|
|
teamId={team.id}
|
|
disableCopyLink={true}
|
|
onExit={() => setShowMemberInvitationModal(false)}
|
|
onSubmit={(values, resetFields) => {
|
|
inviteMemberMutation.mutate(
|
|
{
|
|
teamId,
|
|
language: i18n.language,
|
|
role: values.role,
|
|
usernameOrEmail: values.emailOrUsername,
|
|
creationSource: CreationSource.WEBAPP,
|
|
},
|
|
{
|
|
onSuccess: async (data) => {
|
|
await utils.viewer.teams.get.invalidate();
|
|
setShowMemberInvitationModal(false);
|
|
|
|
if (Array.isArray(data.usernameOrEmail)) {
|
|
showToast(
|
|
t("email_invite_team_bulk", {
|
|
userCount: data.numUsersInvited,
|
|
}),
|
|
"success"
|
|
);
|
|
resetFields();
|
|
} else {
|
|
showToast(
|
|
t("email_invite_team", {
|
|
email: data.usernameOrEmail,
|
|
}),
|
|
"success"
|
|
);
|
|
}
|
|
},
|
|
onError: (error) => {
|
|
showToast(error.message, "error");
|
|
},
|
|
}
|
|
);
|
|
}}
|
|
onSettingsOpen={() => {
|
|
setShowMemberInvitationModal(false);
|
|
}}
|
|
/>
|
|
)}
|
|
</>
|
|
)}
|
|
</>
|
|
);
|
|
};
|
|
|
|
export default MembersView;
|