From 610c86837a5592e69576cfc2525dac70f105912e Mon Sep 17 00:00:00 2001 From: sean-brydon <55134778+sean-brydon@users.noreply.github.com> Date: Wed, 19 Jul 2023 20:45:13 +0100 Subject: [PATCH] feat: orgs - user management v2 figma: https://www.figma.com/file/ZJNAFHCAq6rnuGRgUFxjzQ/Organizations----New-features?type=design&node-id=4304-330348&mode=design&t=nKonHZpucwxnqwQI-4 (Not all features are in this PR) loom: https://www.loom.com/share/30e8ef20a5b34a708dca160d8c04ae6f shoutout @shadcn for the base of this table <3 Member View: ![CleanShot 2023-07-14 at 14 09 02](https://github.com/calcom/cal.com/assets/55134778/5db6eedb-bece-4955-91f0-9f2b3b042300) Admin view: ![CleanShot 2023-07-14 at 14 09 07](https://github.com/calcom/cal.com/assets/55134778/708c624d-1990-4075-8b32-d4587d09deff) How to test - Create ORG - add members - view settings -> orgs -> members --- .../components/eventtype/EventAdvancedTab.tsx | 17 +- .../bookings/components/EventTypeFilter.tsx | 4 +- .../organizations/pages/settings/members.tsx | 155 +--- .../ee/payments/components/Payment.tsx | 4 +- .../components/MemberChangeRoleModal.tsx | 2 +- .../components/MemberInvitationModal.tsx | 2 +- .../workflows/components/AddActionDialog.tsx | 4 +- .../components/WorkflowStepContainer.tsx | 4 +- .../BulkEditDefaultConferencingModal.tsx | 6 +- packages/features/form-builder/Components.tsx | 4 +- .../insights/filters/EventTypeList.tsx | 4 +- packages/features/package.json | 2 + .../schedules/components/Schedule.tsx | 6 +- .../settings/BookerLayoutSelector.tsx | 4 +- .../settings/layouts/SettingsLayout.tsx | 2 +- .../UserTable/ChangeUserRoleModal.tsx | 27 + .../UserTable/DeleteMemberModal.tsx | 55 ++ .../UserTable/ImpersonationMemberModal.tsx | 47 + .../UserTable/InviteMemberModal.tsx | 76 ++ .../components/UserTable/UserListTable.tsx | 331 +++++++ .../components/UserTable/UserTableActions.tsx | 182 ++++ packages/lib/hooks/usePagination.ts | 28 + .../routers/viewer/organizations/_router.tsx | 4 +- .../organizations/adminVerify.handler.ts | 9 - .../organizations/listMembers.handler.ts | 94 +- .../organizations/listMembers.schema.ts | 8 + packages/types/tanstack-table.d.ts | 7 + packages/ui/components/command/index.tsx | 136 +++ .../components/data-table/DataTableFilter.tsx | 116 +++ .../data-table/DataTablePagination.tsx | 60 ++ .../data-table/DataTableSelectionBar.tsx | 31 + .../data-table/DataTableToolbar.tsx | 76 ++ .../data-table/__storybook__/columns.tsx | 78 ++ .../data-table/__storybook__/data.ts | 100 ++ .../__storybook__/datatable.stories.mdx | 27 + packages/ui/components/data-table/index.tsx | 173 ++++ packages/ui/components/dialog/Dialog.tsx | 1 + .../ui/components/form/checkbox/Checkbox.tsx | 22 +- .../form/checkbox/checkbox.stories.mdx | 2 +- packages/ui/components/form/checkbox/index.ts | 2 +- .../ui/components/form/dropdown/Dropdown.tsx | 5 +- packages/ui/components/form/index.ts | 2 +- packages/ui/components/popover/index.ts | 2 - packages/ui/components/popover/index.tsx | 32 + packages/ui/components/table/TableNew.tsx | 78 ++ packages/ui/index.tsx | 2 + packages/ui/package.json | 7 +- yarn.lock | 855 ++++++++++++++++-- 48 files changed, 2635 insertions(+), 260 deletions(-) create mode 100644 packages/features/users/components/UserTable/ChangeUserRoleModal.tsx create mode 100644 packages/features/users/components/UserTable/DeleteMemberModal.tsx create mode 100644 packages/features/users/components/UserTable/ImpersonationMemberModal.tsx create mode 100644 packages/features/users/components/UserTable/InviteMemberModal.tsx create mode 100644 packages/features/users/components/UserTable/UserListTable.tsx create mode 100644 packages/features/users/components/UserTable/UserTableActions.tsx create mode 100644 packages/lib/hooks/usePagination.ts create mode 100644 packages/trpc/server/routers/viewer/organizations/listMembers.schema.ts create mode 100644 packages/types/tanstack-table.d.ts create mode 100644 packages/ui/components/command/index.tsx create mode 100644 packages/ui/components/data-table/DataTableFilter.tsx create mode 100644 packages/ui/components/data-table/DataTablePagination.tsx create mode 100644 packages/ui/components/data-table/DataTableSelectionBar.tsx create mode 100644 packages/ui/components/data-table/DataTableToolbar.tsx create mode 100644 packages/ui/components/data-table/__storybook__/columns.tsx create mode 100644 packages/ui/components/data-table/__storybook__/data.ts create mode 100644 packages/ui/components/data-table/__storybook__/datatable.stories.mdx create mode 100644 packages/ui/components/data-table/index.tsx delete mode 100644 packages/ui/components/popover/index.ts create mode 100644 packages/ui/components/popover/index.tsx create mode 100644 packages/ui/components/table/TableNew.tsx diff --git a/apps/web/components/eventtype/EventAdvancedTab.tsx b/apps/web/components/eventtype/EventAdvancedTab.tsx index 8e416b3884..b2ed894e9d 100644 --- a/apps/web/components/eventtype/EventAdvancedTab.tsx +++ b/apps/web/components/eventtype/EventAdvancedTab.tsx @@ -5,7 +5,7 @@ import { useEffect, useState } from "react"; import { Controller, useFormContext } from "react-hook-form"; import short from "short-uuid"; import { v5 as uuidv5 } from "uuid"; -import { z } from "zod"; +import type { z } from "zod"; import type { EventNameObjectType } from "@calcom/core/event"; import { getEventName } from "@calcom/core/event"; @@ -17,14 +17,23 @@ import { allowDisablingHostConfirmationEmails, } from "@calcom/features/ee/workflows/lib/allowDisablingStandardEmails"; import { FormBuilder } from "@calcom/features/form-builder/FormBuilder"; -import { EditableSchema } from "@calcom/features/form-builder/FormBuilderFieldsSchema"; +import type { EditableSchema } from "@calcom/features/form-builder/FormBuilderFieldsSchema"; import { BookerLayoutSelector } from "@calcom/features/settings/BookerLayoutSelector"; import { classNames } from "@calcom/lib"; import { APP_NAME, CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { Prisma } from "@calcom/prisma/client"; import { trpc } from "@calcom/trpc/react"; -import { Button, Checkbox, Label, SettingsToggle, showToast, TextField, Tooltip, Alert } from "@calcom/ui"; +import { + Button, + CheckboxField, + Label, + SettingsToggle, + showToast, + TextField, + Tooltip, + Alert, +} from "@calcom/ui"; import { Edit, Copy } from "@calcom/ui/components/icon"; import RequiresConfirmationController from "./RequiresConfirmationController"; @@ -342,7 +351,7 @@ export const EventAdvancedTab = ({ eventType, team }: Pick
- formMethods.setValue("seatsShowAttendees", e.target.checked)} diff --git a/packages/features/bookings/components/EventTypeFilter.tsx b/packages/features/bookings/components/EventTypeFilter.tsx index 4449fc5a51..9b1781573c 100644 --- a/packages/features/bookings/components/EventTypeFilter.tsx +++ b/packages/features/bookings/components/EventTypeFilter.tsx @@ -5,7 +5,7 @@ import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc/react"; import { trpc } from "@calcom/trpc/react"; import { AnimatedPopover } from "@calcom/ui"; -import { Checkbox } from "@calcom/ui"; +import { CheckboxField } from "@calcom/ui"; import { groupBy } from "../groupBy"; import { useFilterQuery } from "../lib/useFilterQuery"; @@ -73,7 +73,7 @@ export const EventTypeFilter = () => {
{groupedEventTypes[teamName].map((eventType) => (
- { if (e.target.checked) { diff --git a/packages/features/ee/organizations/pages/settings/members.tsx b/packages/features/ee/organizations/pages/settings/members.tsx index 43e38ee1ac..36ba6b64c5 100644 --- a/packages/features/ee/organizations/pages/settings/members.tsx +++ b/packages/features/ee/organizations/pages/settings/members.tsx @@ -1,133 +1,17 @@ -import { useRouter } from "next/router"; -import { useState } from "react"; - import LicenseRequired from "@calcom/features/ee/common/components/LicenseRequired"; -import MemberInvitationModal from "@calcom/features/ee/teams/components/MemberInvitationModal"; -import MemberListItem from "@calcom/features/ee/teams/components/MemberListItem"; -import TeamInviteList from "@calcom/features/ee/teams/components/TeamInviteList"; import { getLayout } from "@calcom/features/settings/layouts/SettingsLayout"; +import { UserListTable } from "@calcom/features/users/components/UserTable/UserListTable"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { MembershipRole } from "@calcom/prisma/enums"; -import { trpc } from "@calcom/trpc/react"; -import type { RouterOutputs } from "@calcom/trpc/react"; -import { Button, Meta, TextField, showToast } from "@calcom/ui"; -import { Plus } from "@calcom/ui/components/icon"; - -type Team = RouterOutputs["viewer"]["teams"]["get"]; - -interface MembersListProps { - team: Team | 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 { t } = useLocale(); - const [query, setQuery] = useState(""); - - const members = team?.members; - const membersList = members - ? members && query === "" - ? members - : members.filter((member) => { - const email = member.email ? checkIfExist(member.email, query) : false; - const username = member.username ? checkIfExist(member.username, query) : false; - const name = member.name ? checkIfExist(member.name, query) : false; - - return email || username || name; - }) - : undefined; - return ( -
- setQuery(e.target.value)} - value={query} - placeholder={`${t("search")}...`} - /> - {membersList?.length && team ? ( -
    - {membersList.map((member) => { - return ; - })} -
- ) : null} -
- ); -} +import { Meta } from "@calcom/ui"; const MembersView = () => { - const { t, i18n } = useLocale(); - const router = useRouter(); - const utils = trpc.useContext(); - const showDialog = router.query.inviteModal === "true"; - const [showMemberInvitationModal, setShowMemberInvitationModal] = useState(showDialog); - const { data: team, isLoading } = trpc.viewer.organizations.listMembers.useQuery(undefined, { - onError: () => { - router.push("/settings"); - }, - }); - - const inviteMemberMutation = trpc.viewer.teams.inviteMember.useMutation({ - async onSuccess(data) { - await utils.viewer.organizations.listMembers.invalidate(); - setShowMemberInvitationModal(false); - if (data.sendEmailInvitation) { - if (Array.isArray(data.usernameOrEmail)) { - showToast( - t("email_invite_team_bulk", { - userCount: data.usernameOrEmail.length, - }), - "success" - ); - } else { - showToast( - t("email_invite_team", { - email: data.usernameOrEmail, - }), - "success" - ); - } - } - }, - onError: (error) => { - showToast(error.message, "error"); - }, - }); - - const isInviteOpen = !team?.membership.accepted; - - const isAdminOrOwner = - team && (team.membership.role === MembershipRole.OWNER || team.membership.role === MembershipRole.ADMIN); + const { t } = useLocale(); return ( - setShowMemberInvitationModal(true)} - data-testid="new-organization-member-button"> - {t("add")} - - ) : ( - <> - ) - } - /> - {!isLoading && ( - <> -
- {team && ( + +
+ {/* {team && ( <> {isInviteOpen && ( { /> )} - )} - -
- {showMemberInvitationModal && team && ( - setShowMemberInvitationModal(false)} - isLoading={inviteMemberMutation.isLoading} - onSubmit={(values) => { - inviteMemberMutation.mutate({ - teamId: team.id, - language: i18n.language, - role: values.role, - usernameOrEmail: values.emailOrUsername, - sendEmailInvitation: values.sendInviteEmail, - isOrg: true, - }); - }} - /> - )} - - )} + )} */} + +
); }; diff --git a/packages/features/ee/payments/components/Payment.tsx b/packages/features/ee/payments/components/Payment.tsx index 510c601bd4..09b66a5504 100644 --- a/packages/features/ee/payments/components/Payment.tsx +++ b/packages/features/ee/payments/components/Payment.tsx @@ -11,7 +11,7 @@ import type { StripePaymentData, StripeSetupIntentData } from "@calcom/app-store import { bookingSuccessRedirect } from "@calcom/lib/bookingSuccessRedirect"; import { CAL_URL } from "@calcom/lib/constants"; import { useLocale } from "@calcom/lib/hooks/useLocale"; -import { Button, Checkbox } from "@calcom/ui"; +import { Button, CheckboxField } from "@calcom/ui"; import type { EventType } from ".prisma/client"; @@ -101,7 +101,7 @@ const PaymentForm = (props: Props) => {
{paymentOption === "HOLD" && (
- )} - + diff --git a/packages/features/ee/teams/components/MemberInvitationModal.tsx b/packages/features/ee/teams/components/MemberInvitationModal.tsx index dd388009e2..811c030902 100644 --- a/packages/features/ee/teams/components/MemberInvitationModal.tsx +++ b/packages/features/ee/teams/components/MemberInvitationModal.tsx @@ -13,7 +13,7 @@ import type { RouterOutputs } from "@calcom/trpc"; import { trpc } from "@calcom/trpc"; import { Button, - Checkbox as CheckboxField, + CheckboxField, Dialog, DialogContent, DialogFooter, diff --git a/packages/features/ee/workflows/components/AddActionDialog.tsx b/packages/features/ee/workflows/components/AddActionDialog.tsx index 45a517549e..4562b6fc29 100644 --- a/packages/features/ee/workflows/components/AddActionDialog.tsx +++ b/packages/features/ee/workflows/components/AddActionDialog.tsx @@ -11,7 +11,7 @@ import { WorkflowActions } from "@calcom/prisma/enums"; import { trpc } from "@calcom/trpc/react"; import { Button, - Checkbox, + CheckboxField, Dialog, DialogClose, DialogContent, @@ -239,7 +239,7 @@ export const AddActionDialog = (props: IAddActionDialog) => { name="numberRequired" control={form.control} render={() => ( - form.setValue("numberRequired", e.target.checked)} diff --git a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx index f51ccd6246..dd9516b00f 100644 --- a/packages/features/ee/workflows/components/WorkflowStepContainer.tsx +++ b/packages/features/ee/workflows/components/WorkflowStepContainer.tsx @@ -17,7 +17,7 @@ import type { RouterOutputs } from "@calcom/trpc/react"; import { Badge, Button, - Checkbox, + CheckboxField, Dialog, DialogClose, DialogContent, @@ -657,7 +657,7 @@ export default function WorkflowStepContainer(props: WorkflowStepProps) { name={`steps.${step.stepNumber - 1}.numberRequired`} control={form.control} render={() => ( - {data.eventTypes.length > 0 && (
- { @@ -59,7 +59,7 @@ export function BulkEditDefaultConferencingModal(props: { open: boolean; setOpen )} {data.eventTypes.map((eventType) => (
- = { factory: ({ readOnly, label, value, setValue }) => { return (
- { if (e.target.checked) { setValue(true); diff --git a/packages/features/insights/filters/EventTypeList.tsx b/packages/features/insights/filters/EventTypeList.tsx index 221a2762b4..e532d71180 100644 --- a/packages/features/insights/filters/EventTypeList.tsx +++ b/packages/features/insights/filters/EventTypeList.tsx @@ -4,7 +4,7 @@ import { FilterCheckboxFieldsContainer } from "@calcom/features/filters/componen import { useLocale } from "@calcom/lib/hooks/useLocale"; import type { RouterOutputs } from "@calcom/trpc"; import { trpc } from "@calcom/trpc"; -import { AnimatedPopover, Checkbox } from "@calcom/ui"; +import { AnimatedPopover, CheckboxField } from "@calcom/ui"; import { useFilterContext } from "../context/provider"; @@ -49,7 +49,7 @@ export const EventTypeList = memo(() => { {filterOptions?.map((eventType) => (
- { if (e.target.checked) { diff --git a/packages/features/package.json b/packages/features/package.json index 3d83161fc9..5763dbdffb 100644 --- a/packages/features/package.json +++ b/packages/features/package.json @@ -12,11 +12,13 @@ "@calcom/trpc": "*", "@calcom/ui": "*", "@lexical/react": "^0.9.0", + "@tanstack/react-table": "^8.9.3", "dompurify": "^2.4.1", "framer-motion": "^10.12.8", "lexical": "^0.9.0", "react-select": "^5.7.0", "react-sticky-box": "^2.0.4", + "react-virtual": "^2.10.4", "zustand": "^4.3.2" }, "devDependencies": { diff --git a/packages/features/schedules/components/Schedule.tsx b/packages/features/schedules/components/Schedule.tsx index ac705a51cf..463735f083 100644 --- a/packages/features/schedules/components/Schedule.tsx +++ b/packages/features/schedules/components/Schedule.tsx @@ -28,7 +28,7 @@ import { Select, SkeletonText, Switch, - Checkbox, + CheckboxField, } from "@calcom/ui"; import { Copy, Plus, Trash } from "@calcom/ui/components/icon"; @@ -447,7 +447,7 @@ const CopyTimes = ({