From 6c5c1fa81805504d443338412f2a432033666a59 Mon Sep 17 00:00:00 2001 From: Sacha Al Himdani Date: Thu, 16 Apr 2026 17:57:34 +0200 Subject: [PATCH] Remove meeting feature Drop meetings and meeting_attendees tables, remove all meeting-related code across GraphQL, MCP, CLI, N8N, webhooks, frontend, and e2e tests. Signed-off-by: Sacha Al Himdani --- .../people/_components/PersonForm.tsx | 2 +- .../context/ContextLayoutLoader.tsx | 12 +- .../meetings/MeetingDetailPage.tsx | 197 --- .../meetings/MeetingDetailPageLoader.tsx | 46 - .../organizations/meetings/MeetingsPage.tsx | 281 ---- .../meetings/MeetingsPageLoader.tsx | 44 - .../meetings/dialogs/CreateMeetingDialog.tsx | 133 -- .../dialogs/UpdateMeetingMinutesDialog.tsx | 133 -- .../meetings/tabs/MeetingMinutesTab.tsx | 38 - .../settings/WebhooksSettingsPage.tsx | 3 - apps/console/src/routes/contextRoutes.ts | 23 - e2e/console/meeting_test.go | 1327 ----------------- e2e/internal/factory/factory.go | 71 - packages/n8n-node/nodes/Probo/Probo.node.ts | 5 - .../n8n-node/nodes/Probo/actions/index.ts | 2 - .../Probo/actions/meeting/create.operation.ts | 188 --- .../Probo/actions/meeting/delete.operation.ts | 55 - .../Probo/actions/meeting/get.operation.ts | 115 -- .../Probo/actions/meeting/getAll.operation.ts | 164 -- .../nodes/Probo/actions/meeting/index.ts | 74 - .../Probo/actions/meeting/update.operation.ts | 182 --- pkg/cmd/webhook/create/create.go | 6 +- pkg/coredata/audit_log_resource_type.go | 2 - pkg/coredata/entity_type_reg.go | 4 +- pkg/coredata/meeting.go | 306 ---- pkg/coredata/meeting_attendee.go | 82 - pkg/coredata/meeting_order_field.go | 57 - pkg/coredata/membership_profile.go | 116 -- pkg/coredata/migrations/20260420T130000Z.sql | 16 + pkg/coredata/webhook_event_type.go | 6 +- pkg/probo/actions.go | 7 - pkg/probo/meeting_service.go | 338 ----- pkg/probo/policies.go | 1 - pkg/probo/service.go | 2 - pkg/server/api/console/v1/base_resolvers.go | 9 - .../api/console/v1/graphql/meeting.graphql | 82 - .../console/v1/graphql/organization.graphql | 8 - .../api/console/v1/graphql/webhook.graphql | 6 - .../api/console/v1/meeting_resolvers.go | 196 --- .../api/console/v1/organization_resolvers.go | 30 - pkg/server/api/console/v1/types/meeting.go | 75 - pkg/server/api/mcp/v1/schema.resolvers.go | 124 -- pkg/server/api/mcp/v1/specification.yaml | 271 ---- pkg/server/api/mcp/v1/types/meeting.go | 49 - pkg/webhook/types/meeting.go | 42 - 45 files changed, 24 insertions(+), 4906 deletions(-) delete mode 100644 apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx delete mode 100644 apps/console/src/pages/organizations/meetings/MeetingDetailPageLoader.tsx delete mode 100644 apps/console/src/pages/organizations/meetings/MeetingsPage.tsx delete mode 100644 apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx delete mode 100644 apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx delete mode 100644 apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx delete mode 100644 apps/console/src/pages/organizations/meetings/tabs/MeetingMinutesTab.tsx delete mode 100644 e2e/console/meeting_test.go delete mode 100644 packages/n8n-node/nodes/Probo/actions/meeting/create.operation.ts delete mode 100644 packages/n8n-node/nodes/Probo/actions/meeting/delete.operation.ts delete mode 100644 packages/n8n-node/nodes/Probo/actions/meeting/get.operation.ts delete mode 100644 packages/n8n-node/nodes/Probo/actions/meeting/getAll.operation.ts delete mode 100644 packages/n8n-node/nodes/Probo/actions/meeting/index.ts delete mode 100644 packages/n8n-node/nodes/Probo/actions/meeting/update.operation.ts delete mode 100644 pkg/coredata/meeting.go delete mode 100644 pkg/coredata/meeting_attendee.go delete mode 100644 pkg/coredata/meeting_order_field.go create mode 100644 pkg/coredata/migrations/20260420T130000Z.sql delete mode 100644 pkg/probo/meeting_service.go delete mode 100644 pkg/server/api/console/v1/graphql/meeting.graphql delete mode 100644 pkg/server/api/console/v1/meeting_resolvers.go delete mode 100644 pkg/server/api/console/v1/types/meeting.go delete mode 100644 pkg/server/api/mcp/v1/types/meeting.go delete mode 100644 pkg/webhook/types/meeting.go diff --git a/apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx b/apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx index 1f6adc5a5..184db1b3a 100644 --- a/apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx +++ b/apps/console/src/pages/iam/organizations/people/_components/PersonForm.tsx @@ -225,7 +225,7 @@ export function PersonForm(props: { {watchedRole === "VIEWER" &&

{__("Read-only access")}

} {watchedRole === "AUDITOR" && (

- {__("Read-only access without settings, tasks and meetings")} + {__("Read-only access without settings and tasks")}

)} {watchedRole === "EMPLOYEE" && ( diff --git a/apps/console/src/pages/organizations/context/ContextLayoutLoader.tsx b/apps/console/src/pages/organizations/context/ContextLayoutLoader.tsx index e1c71cb5f..5928e72ad 100644 --- a/apps/console/src/pages/organizations/context/ContextLayoutLoader.tsx +++ b/apps/console/src/pages/organizations/context/ContextLayoutLoader.tsx @@ -14,15 +14,11 @@ import { usePageTitle } from "@probo/hooks"; import { useTranslate } from "@probo/i18n"; -import { PageHeader, TabLink, Tabs } from "@probo/ui"; +import { PageHeader } from "@probo/ui"; import { Outlet } from "react-router"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; - export default function ContextLayoutLoader() { const { __ } = useTranslate(); - const organizationId = useOrganizationId(); - const prefix = `/organizations/${organizationId}/context`; usePageTitle(__("Context")); @@ -31,13 +27,9 @@ export default function ContextLayoutLoader() { - - {__("Context")} - {__("Meetings")} - ); diff --git a/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx b/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx deleted file mode 100644 index 9106e4b7e..000000000 --- a/apps/console/src/pages/organizations/meetings/MeetingDetailPage.tsx +++ /dev/null @@ -1,197 +0,0 @@ -// Copyright (c) 2025-2026 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -import { formatDate, sprintf } from "@probo/helpers"; -import { usePageTitle } from "@probo/hooks"; -import { useTranslate } from "@probo/i18n"; -import { - ActionDropdown, - Avatar, - Breadcrumb, - DropdownItem, - IconPencil, - IconTrashCan, - PageHeader, - useConfirm, -} from "@probo/ui"; -import { useRef } from "react"; -import type { PreloadedQuery } from "react-relay"; -import { graphql, useFragment, usePreloadedQuery } from "react-relay"; -import { Link, Outlet, useNavigate } from "react-router"; - -import type { MeetingDetailPageDeleteMutation } from "#/__generated__/core/MeetingDetailPageDeleteMutation.graphql"; -import type { MeetingDetailPageMeetingFragment$key } from "#/__generated__/core/MeetingDetailPageMeetingFragment.graphql"; -import type { MeetingDetailPageQuery } from "#/__generated__/core/MeetingDetailPageQuery.graphql"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; - -import { - UpdateMeetingMinutesDialog, - type UpdateMeetingMinutesDialogRef, -} from "./dialogs/UpdateMeetingMinutesDialog"; - -export const meetingDetailPageQuery = graphql` - query MeetingDetailPageQuery($meetingId: ID!) { - node(id: $meetingId) { - ...MeetingDetailPageMeetingFragment - } - } -`; - -const meetingFragment = graphql` - fragment MeetingDetailPageMeetingFragment on Meeting { - id - name - date - # eslint-disable-next-line relay/unused-fields - minutes - canUpdate: permission(action: "core:meeting:update") - canDelete: permission(action: "core:meeting:delete") - attendees { - id - fullName - } - } -`; - -const deleteMeetingMutation = graphql` - mutation MeetingDetailPageDeleteMutation($input: DeleteMeetingInput!) { - deleteMeeting(input: $input) { - deletedMeetingId @deleteRecord - } - } -`; - -function useDeleteMeetingMutation() { - const { __ } = useTranslate(); - - return useMutationWithToasts( - deleteMeetingMutation, - { - successMessage: __("Meeting deleted successfully."), - errorMessage: __("Failed to delete meeting"), - }, - ); -} - -type Props = { - queryRef: PreloadedQuery; -}; - -export default function MeetingDetailPage(props: Props) { - const node = usePreloadedQuery(meetingDetailPageQuery, props.queryRef).node; - const meeting = useFragment( - meetingFragment, - node, - ); - const { __ } = useTranslate(); - const organizationId = useOrganizationId(); - const navigate = useNavigate(); - - const [deleteMeeting, isDeleting] = useDeleteMeetingMutation(); - const confirm = useConfirm(); - const updateMinutesDialogRef = useRef(null); - - usePageTitle(meeting.name); - - const hasAnyAction = meeting.canUpdate || meeting.canDelete; - - const handleDelete = () => { - confirm( - () => - deleteMeeting({ - variables: { - input: { meetingId: meeting.id }, - }, - onSuccess: () => { - void navigate(`/organizations/${organizationId}/context/meetings`); - }, - }), - { - message: sprintf( - __( - "This will permanently delete the meeting \"%s\". This action cannot be undone.", - ), - meeting.name, - ), - }, - ); - }; - - return ( - <> - -
-
- - {hasAnyAction && ( - - {meeting.canUpdate && ( - updateMinutesDialogRef.current?.open()} - icon={IconPencil} - > - {__("Edit minutes")} - - )} - {meeting.canDelete && ( - - {__("Delete meeting")} - - )} - - )} -
- - {meeting.attendees && meeting.attendees.length > 0 && ( -
- {meeting.attendees.map(attendee => ( -
- - - {attendee.fullName} - -
- ))} -
- )} - -
- - ); -} diff --git a/apps/console/src/pages/organizations/meetings/MeetingDetailPageLoader.tsx b/apps/console/src/pages/organizations/meetings/MeetingDetailPageLoader.tsx deleted file mode 100644 index 62c3ed8b2..000000000 --- a/apps/console/src/pages/organizations/meetings/MeetingDetailPageLoader.tsx +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -import { useEffect } from "react"; -import { useQueryLoader } from "react-relay"; -import { useParams } from "react-router"; - -import type { MeetingDetailPageQuery } from "#/__generated__/core/MeetingDetailPageQuery.graphql"; -import { PageSkeleton } from "#/components/skeletons/PageSkeleton"; -import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; - -import MeetingDetailPage, { meetingDetailPageQuery } from "./MeetingDetailPage"; - -function MeetingDetailPageQueryLoader() { - const { meetingId } = useParams<{ meetingId: string }>(); - const [queryRef, loadQuery] = useQueryLoader(meetingDetailPageQuery); - - useEffect(() => { - if (meetingId) { - loadQuery({ meetingId }); - } - }, [meetingId, loadQuery]); - - if (!queryRef) return ; - - return ; -} - -export default function MeetingDetailPageLoader() { - return ( - - - - ); -} diff --git a/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx b/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx deleted file mode 100644 index a9a191c83..000000000 --- a/apps/console/src/pages/organizations/meetings/MeetingsPage.tsx +++ /dev/null @@ -1,281 +0,0 @@ -// Copyright (c) 2025-2026 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -import { formatDate, sprintf } from "@probo/helpers"; -import { useTranslate } from "@probo/i18n"; -import { - ActionDropdown, - Avatar, - Button, - Card, - DropdownItem, - IconPlusLarge, - IconTrashCan, - Tbody, - Td, - Th, - Thead, - Tr, - useConfirm, -} from "@probo/ui"; -import { - type PreloadedQuery, - useFragment, - usePaginationFragment, - usePreloadedQuery, -} from "react-relay"; -import { Link } from "react-router"; -import { graphql } from "relay-runtime"; - -import type { MeetingsPageDeleteMutation } from "#/__generated__/core/MeetingsPageDeleteMutation.graphql"; -import type { MeetingsPageListFragment$key } from "#/__generated__/core/MeetingsPageListFragment.graphql"; -import type { MeetingsPageQuery } from "#/__generated__/core/MeetingsPageQuery.graphql"; -import type { MeetingsPageRowFragment$key } from "#/__generated__/core/MeetingsPageRowFragment.graphql"; -import { SortableTable, SortableTh } from "#/components/SortableTable"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; - -import { CreateMeetingDialog } from "./dialogs/CreateMeetingDialog"; - -export const meetingsPageQuery = graphql` - query MeetingsPageQuery($organizationId: ID!) { - organization: node(id: $organizationId) { - id - ... on Organization { - canCreateMeeting: permission(action: "core:meeting:create") - } - ...MeetingsPageListFragment - } - } -`; - -const meetingsFragment = graphql` - fragment MeetingsPageListFragment on Organization - @refetchable(queryName: "MeetingsListQuery") - @argumentDefinitions( - first: { type: "Int", defaultValue: 50 } - order: { - type: "MeetingOrder" - defaultValue: { field: DATE, direction: DESC } - } - after: { type: "CursorKey", defaultValue: null } - before: { type: "CursorKey", defaultValue: null } - last: { type: "Int", defaultValue: null } - ) { - id - canCreateMeeting: permission(action: "core:meeting:create") - meetings( - first: $first - after: $after - last: $last - before: $before - orderBy: $order - ) @connection(key: "MeetingsListQuery_meetings") { - __id - edges { - node { - id - ...MeetingsPageRowFragment - } - } - } - } -`; - -const deleteMeetingMutation = graphql` - mutation MeetingsPageDeleteMutation($input: DeleteMeetingInput!) { - deleteMeeting(input: $input) { - deletedMeetingId @deleteRecord - } - } -`; - -function useDeleteMeetingMutation() { - const { __ } = useTranslate(); - - return useMutationWithToasts( - deleteMeetingMutation, - { - successMessage: __("Meeting deleted successfully."), - errorMessage: __("Failed to delete meeting"), - }, - ); -} - -type Props = { - queryRef: PreloadedQuery; -}; - -export default function MeetingsPage(props: Props) { - const { __ } = useTranslate(); - const organization = usePreloadedQuery( - meetingsPageQuery, - props.queryRef, - ).organization; - - // eslint-disable-next-line relay/generated-typescript-types - const pagination = usePaginationFragment( - meetingsFragment, - organization as MeetingsPageListFragment$key, - ); - - const meetingNodes = pagination.data.meetings.edges - .map(edge => edge.node) - .filter(Boolean); - const connectionId = pagination.data.meetings.__id; - - return ( -
- {pagination.data.canCreateMeeting && ( -
- - - -
- )} - {meetingNodes.length > 0 - ? ( - - - - - {__("Date")} - - - {__("Meeting name")} - - {__("Attendees")} - - - - - {meetingNodes.map(meeting => ( - - ))} - - - ) - : ( - -
-

- {__("No meetings yet")} -

-

- {__("Create your first meeting to get started.")} -

-
-
- )} -
- ); -} - -const rowFragment = graphql` - fragment MeetingsPageRowFragment on Meeting { - id - name - date - attendees { - id - fullName - } - canDelete: permission(action: "core:meeting:delete") - } -`; - -function MeetingRow({ - meeting: meetingKey, - organizationId, -}: { - meeting: MeetingsPageRowFragment$key; - organizationId: string; -}) { - const meeting = useFragment( - rowFragment, - meetingKey, - ); - const { __ } = useTranslate(); - const [deleteMeeting] = useDeleteMeetingMutation(); - const confirm = useConfirm(); - const handleDelete = () => { - confirm( - () => - deleteMeeting({ - variables: { - input: { meetingId: meeting.id }, - }, - }), - { - message: sprintf( - __( - "This will permanently delete the meeting \"%s\". This action cannot be undone.", - ), - meeting.name, - ), - }, - ); - }; - - return ( - - {formatDate(meeting.date)} - -
{meeting.name}
- - - {meeting.attendees && meeting.attendees.length > 0 - ? ( -
- {meeting.attendees.map(attendee => ( -
- - { - e.stopPropagation(); - }} - className="text-sm hover:underline" - > - {attendee.fullName} - -
- ))} -
- ) - : ( - - {__("No attendees")} - - )} - - {meeting.canDelete && ( - - - - {__("Delete")} - - - - )} - - ); -} diff --git a/apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx b/apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx deleted file mode 100644 index 26775daf3..000000000 --- a/apps/console/src/pages/organizations/meetings/MeetingsPageLoader.tsx +++ /dev/null @@ -1,44 +0,0 @@ -// Copyright (c) 2026 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -import { useEffect } from "react"; -import { useQueryLoader } from "react-relay"; - -import type { MeetingsPageQuery } from "#/__generated__/core/MeetingsPageQuery.graphql"; -import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; -import { CoreRelayProvider } from "#/providers/CoreRelayProvider"; - -import MeetingsPage, { meetingsPageQuery } from "./MeetingsPage"; - -function MeetingsPageQueryLoader() { - const organizationId = useOrganizationId(); - const [queryRef, loadQuery] = useQueryLoader(meetingsPageQuery); - - useEffect(() => { - loadQuery({ organizationId }); - }, [organizationId, loadQuery]); - - if (!queryRef) return ; - - return ; -} - -export default function MeetingsPageLoader() { - return ( - - - - ); -} diff --git a/apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx b/apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx deleted file mode 100644 index 30e8b5dd1..000000000 --- a/apps/console/src/pages/organizations/meetings/dialogs/CreateMeetingDialog.tsx +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2025-2026 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -import { formatDatetime } from "@probo/helpers"; -import { useTranslate } from "@probo/i18n"; -import { - Button, - Dialog, - DialogContent, - DialogFooter, - Field, - Input, - Spinner, - useDialogRef, -} from "@probo/ui"; -import { useMutation } from "react-relay"; -import { graphql } from "relay-runtime"; -import { z } from "zod"; - -import type { CreateMeetingDialogCreateMutation } from "#/__generated__/core/CreateMeetingDialogCreateMutation.graphql"; -import { PeopleMultiSelectField } from "#/components/form/PeopleMultiSelectField"; -import { useFormWithSchema } from "#/hooks/useFormWithSchema"; -import { useOrganizationId } from "#/hooks/useOrganizationId"; - -const createMeetingMutation = graphql` - mutation CreateMeetingDialogCreateMutation( - $input: CreateMeetingInput! - $connections: [ID!]! - ) { - createMeeting(input: $input) { - meetingEdge @prependEdge(connections: $connections) { - node { - id - name - date - minutes - attendees { - id - fullName - } - canDelete: permission(action: "core:meeting:delete") - } - } - } - } -`; - -type Props = { - children: React.ReactElement; - connectionId: string; -}; - -const meetingSchema = z.object({ - name: z.string().min(1, "Meeting name is required"), - date: z.string().min(1, "Date is required"), - attendeeIds: z.array(z.string()).optional(), -}); - -export function CreateMeetingDialog({ children, connectionId }: Props) { - const { __ } = useTranslate(); - const dialogRef = useDialogRef(); - const organizationId = useOrganizationId(); - const [createMeeting, isCreating] - = useMutation(createMeetingMutation); - const { handleSubmit, register, control } = useFormWithSchema( - meetingSchema, - {}, - ); - - const onSubmit = (data: z.infer) => { - createMeeting({ - variables: { - input: { - organizationId, - name: data.name, - date: formatDatetime(data.date)!, - attendeeIds: data.attendeeIds || null, - }, - connections: [connectionId], - }, - onCompleted: () => { - dialogRef.current?.close(); - }, - }); - }; - - return ( - -
void handleSubmit(onSubmit)(e)}> - - - - - - - - - - - - -
-
- ); -} diff --git a/apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx b/apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx deleted file mode 100644 index d47ca4ab6..000000000 --- a/apps/console/src/pages/organizations/meetings/dialogs/UpdateMeetingMinutesDialog.tsx +++ /dev/null @@ -1,133 +0,0 @@ -// Copyright (c) 2025-2026 Probo Inc . -// -// Permission to use, copy, modify, and/or distribute this software for any -// purpose with or without fee is hereby granted, provided that the above -// copyright notice and this permission notice appear in all copies. -// -// THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH -// REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY -// AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, -// INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM -// LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR -// OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR -// PERFORMANCE OF THIS SOFTWARE. - -import { useTranslate } from "@probo/i18n"; -import { - Breadcrumb, - Button, - Dialog, - DialogContent, - DialogFooter, - Spinner, - Textarea, - useDialogRef, -} from "@probo/ui"; -import { forwardRef, useImperativeHandle } from "react"; -import { graphql } from "relay-runtime"; -import { z } from "zod"; - -import type { MeetingDetailPageMeetingFragment$data } from "#/__generated__/core/MeetingDetailPageMeetingFragment.graphql"; -import type { UpdateMeetingMinutesDialogMutation } from "#/__generated__/core/UpdateMeetingMinutesDialogMutation.graphql"; -import { useFormWithSchema } from "#/hooks/useFormWithSchema"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; - -const updateMeetingMutation = graphql` - mutation UpdateMeetingMinutesDialogMutation($input: UpdateMeetingInput!) { - updateMeeting(input: $input) { - meeting { - id - name - date - minutes - attendees { - id - fullName - } - } - } - } -`; - -type Props = { - meeting: MeetingDetailPageMeetingFragment$data; -}; - -export type UpdateMeetingMinutesDialogRef = { - open: () => void; -}; - -const minutesSchema = z.object({ - minutes: z.string(), -}); - -export const UpdateMeetingMinutesDialog = forwardRef< - UpdateMeetingMinutesDialogRef, - Props ->(function UpdateMeetingMinutesDialog({ meeting }, ref) { - const { __ } = useTranslate(); - const dialogRef = useDialogRef(); - const [updateMeeting, isUpdating] - = useMutationWithToasts( - updateMeetingMutation, - { - successMessage: __("Meeting updated successfully."), - errorMessage: __("Failed to update meeting"), - }, - ); - const { handleSubmit, register, reset } = useFormWithSchema(minutesSchema, { - defaultValues: { - minutes: meeting.minutes || "", - }, - }); - - useImperativeHandle(ref, () => ({ - open: () => { - reset({ - minutes: meeting.minutes || "", - }); - dialogRef.current?.open(); - }, - })); - - const onSubmit = async (data: z.infer) => { - await updateMeeting({ - variables: { - input: { - meetingId: meeting.id, - minutes: data.minutes, - }, - }, - onSuccess: () => { - dialogRef.current?.close(); - }, - }); - }; - - return ( - } - > -
void handleSubmit(onSubmit)(e)}> - -