diff --git a/apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx b/apps/console/src/components/compliancePage/CompliancePageReferenceDialog.tsx similarity index 89% rename from apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx rename to apps/console/src/components/compliancePage/CompliancePageReferenceDialog.tsx index b297d9506..d2f106821 100644 --- a/apps/console/src/components/trustCenter/TrustCenterReferenceDialog.tsx +++ b/apps/console/src/components/compliancePage/CompliancePageReferenceDialog.tsx @@ -36,9 +36,9 @@ import { z } from "zod"; import type { CompliancePageReferenceListItemFragment$data } from "#/__generated__/core/CompliancePageReferenceListItemFragment.graphql"; import { - useCreateTrustCenterReferenceMutation, - useUpdateTrustCenterReferenceMutation, -} from "#/hooks/graph/TrustCenterReferenceGraph"; + useCreateCompliancePageReferenceMutation, + useUpdateCompliancePageReferenceMutation, +} from "#/hooks/graph/CompliancePageReferenceGraph"; import { useFormWithSchema } from "#/hooks/useFormWithSchema"; const referenceSchema = z.object({ @@ -50,23 +50,23 @@ const referenceSchema = z.object({ type ReferenceFormData = z.infer; -export type TrustCenterReferenceDialogRef = { - openCreate: (trustCenterId: string, connectionId: string) => void; +export type CompliancePageReferenceDialogRef = { + openCreate: (compliancePageId: string, connectionId: string) => void; openEdit: (reference: CompliancePageReferenceListItemFragment$data, rank: number) => void; }; -export const TrustCenterReferenceDialog = forwardRef( - function TrustCenterReferenceDialog({ children }, ref) { +export const CompliancePageReferenceDialog = forwardRef( + function CompliancePageReferenceDialog({ children }, ref) { const { __ } = useTranslate(); const dialogRef = useDialogRef(); const [mode, setMode] = useState<"create" | "edit">("create"); - const [trustCenterId, setTrustCenterId] = useState(""); + const [compliancePageId, setCompliancePageId] = useState(""); const [connectionId, setConnectionId] = useState(""); const [editReference, setEditReference] = useState(null); const [uploadedFile, setUploadedFile] = useState(null); - const [createReference, isCreating] = useCreateTrustCenterReferenceMutation(); - const [updateReference, isUpdating] = useUpdateTrustCenterReferenceMutation(); + const [createReference, isCreating] = useCreateCompliancePageReferenceMutation(); + const [updateReference, isUpdating] = useUpdateCompliancePageReferenceMutation(); const { register, handleSubmit, formState: { errors }, reset } = useFormWithSchema( referenceSchema, @@ -80,9 +80,9 @@ export const TrustCenterReferenceDialog = forwardRef ({ - openCreate: (tId: string, cId: string) => { + openCreate: (pageId: string, cId: string) => { setMode("create"); - setTrustCenterId(tId); + setCompliancePageId(pageId); setConnectionId(cId); setEditReference(null); setUploadedFile(null); @@ -123,7 +123,7 @@ export const TrustCenterReferenceDialog = forwardRef { - reset(); - setUploadedFile(null); - dialogRef.current?.close(); - }, }); + + reset(); + setUploadedFile(null); + dialogRef.current?.close(); } else if (editReference) { const input: { id: string; @@ -169,12 +168,11 @@ export const TrustCenterReferenceDialog = forwardRef 0 ? uploadables : undefined, - onSuccess: () => { - reset(); - setUploadedFile(null); - dialogRef.current?.close(); - }, }); + + reset(); + setUploadedFile(null); + dialogRef.current?.close(); } }; diff --git a/apps/console/src/components/trustCenter/DeleteTrustCenterReferenceDialog.tsx b/apps/console/src/components/compliancePage/DeleteCompliancePageReferenceDialog.tsx similarity index 81% rename from apps/console/src/components/trustCenter/DeleteTrustCenterReferenceDialog.tsx rename to apps/console/src/components/compliancePage/DeleteCompliancePageReferenceDialog.tsx index 78a04cea1..5941a70da 100644 --- a/apps/console/src/components/trustCenter/DeleteTrustCenterReferenceDialog.tsx +++ b/apps/console/src/components/compliancePage/DeleteCompliancePageReferenceDialog.tsx @@ -30,8 +30,9 @@ import { useDialogRef, } from "@probo/ui"; -import { deleteTrustCenterReferenceMutation } from "#/hooks/graph/TrustCenterReferenceGraph"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +import type { CompliancePageReferenceGraphDeleteMutation } from "#/__generated__/core/CompliancePageReferenceGraphDeleteMutation.graphql"; +import { deleteCompliancePageReferenceMutation } from "#/hooks/graph/CompliancePageReferenceGraph"; +import { useMutation } from "#/lib/relay/useMutation"; type Props = { children: React.ReactNode; @@ -41,7 +42,7 @@ type Props = { onSuccess?: () => void; }; -export function DeleteTrustCenterReferenceDialog({ +export function DeleteCompliancePageReferenceDialog({ children, referenceId, referenceName, @@ -51,10 +52,13 @@ export function DeleteTrustCenterReferenceDialog({ const { __ } = useTranslate(); const ref = useDialogRef(); - const [mutate, isDeleting] = useMutationWithToasts(deleteTrustCenterReferenceMutation, { - successMessage: __("Reference deleted successfully"), - errorMessage: __("Failed to delete reference"), - }); + const [mutate, isDeleting] = useMutation( + deleteCompliancePageReferenceMutation, + { + successMessage: __("Reference deleted successfully"), + errorToast: __("Failed to delete reference"), + }, + ); const handleDelete = async () => { await mutate({ diff --git a/apps/console/src/hooks/graph/TrustCenterGraph.ts b/apps/console/src/hooks/graph/CompliancePageGraph.ts similarity index 58% rename from apps/console/src/hooks/graph/TrustCenterGraph.ts rename to apps/console/src/hooks/graph/CompliancePageGraph.ts index 242b6270b..71afe1bd1 100644 --- a/apps/console/src/hooks/graph/TrustCenterGraph.ts +++ b/apps/console/src/hooks/graph/CompliancePageGraph.ts @@ -20,34 +20,40 @@ import { graphql } from "relay-runtime"; -import type { TrustCenterGraphUpdateMutation } from "#/__generated__/core/TrustCenterGraphUpdateMutation.graphql"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; +import type { CompliancePageGraphDeleteNDAMutation } from "#/__generated__/core/CompliancePageGraphDeleteNDAMutation.graphql"; +import type { CompliancePageGraphUpdateMutation } from "#/__generated__/core/CompliancePageGraphUpdateMutation.graphql"; +import type { CompliancePageGraphUploadNDAMutation } from "#/__generated__/core/CompliancePageGraphUploadNDAMutation.graphql"; +import { useMutation } from "#/lib/relay/useMutation"; -const updateTrustCenterMutation = graphql` - mutation TrustCenterGraphUpdateMutation($input: UpdateTrustCenterInput!) { +const updateCompliancePageMutation = graphql` + mutation CompliancePageGraphUpdateMutation($input: UpdateTrustCenterInput!) { updateTrustCenter(input: $input) { trustCenter { id active searchEngineIndexing + description + websiteUrl + email + headquarterAddress updatedAt } } } `; -export function useUpdateTrustCenterMutation() { - return useMutationWithToasts( - updateTrustCenterMutation, +export function useUpdateCompliancePageMutation() { + return useMutation( + updateCompliancePageMutation, { successMessage: "Compliance Page updated successfully", - errorMessage: "Failed to update compliance page", + errorToast: "Failed to update compliance page", }, ); } -const uploadTrustCenterNDAMutation = graphql` - mutation TrustCenterGraphUploadNDAMutation( +const uploadCompliancePageNDAMutation = graphql` + mutation CompliancePageGraphUploadNDAMutation( $input: UploadTrustCenterNDAInput! ) { uploadTrustCenterNDA(input: $input) { @@ -63,15 +69,15 @@ const uploadTrustCenterNDAMutation = graphql` } `; -export function useUploadTrustCenterNDAMutation() { - return useMutationWithToasts(uploadTrustCenterNDAMutation, { +export function useUploadCompliancePageNDAMutation() { + return useMutation(uploadCompliancePageNDAMutation, { successMessage: "NDA uploaded successfully", - errorMessage: "Failed to upload NDA", + errorToast: "Failed to upload NDA", }); } -const deleteTrustCenterNDAMutation = graphql` - mutation TrustCenterGraphDeleteNDAMutation( +const deleteCompliancePageNDAMutation = graphql` + mutation CompliancePageGraphDeleteNDAMutation( $input: DeleteTrustCenterNDAInput! ) { deleteTrustCenterNDA(input: $input) { @@ -87,9 +93,9 @@ const deleteTrustCenterNDAMutation = graphql` } `; -export function useDeleteTrustCenterNDAMutation() { - return useMutationWithToasts(deleteTrustCenterNDAMutation, { +export function useDeleteCompliancePageNDAMutation() { + return useMutation(deleteCompliancePageNDAMutation, { successMessage: "NDA deleted successfully", - errorMessage: "Failed to delete NDA", + errorToast: "Failed to delete NDA", }); } diff --git a/apps/console/src/hooks/graph/CompliancePageReferenceGraph.ts b/apps/console/src/hooks/graph/CompliancePageReferenceGraph.ts new file mode 100644 index 000000000..2d5716588 --- /dev/null +++ b/apps/console/src/hooks/graph/CompliancePageReferenceGraph.ts @@ -0,0 +1,135 @@ +// 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 { graphql } from "react-relay"; + +import type { CompliancePageReferenceGraphCreateMutation } from "#/__generated__/core/CompliancePageReferenceGraphCreateMutation.graphql"; +import type { CompliancePageReferenceGraphDeleteMutation } from "#/__generated__/core/CompliancePageReferenceGraphDeleteMutation.graphql"; +import type { CompliancePageReferenceGraphUpdateMutation } from "#/__generated__/core/CompliancePageReferenceGraphUpdateMutation.graphql"; +import type { CompliancePageReferenceGraphUpdateRankMutation } from "#/__generated__/core/CompliancePageReferenceGraphUpdateRankMutation.graphql"; +import { useMutation } from "#/lib/relay/useMutation"; + +export const createCompliancePageReferenceMutation = graphql` + mutation CompliancePageReferenceGraphCreateMutation( + $input: CreateTrustCenterReferenceInput! + $connections: [ID!]! + ) { + createTrustCenterReference(input: $input) { + trustCenterReferenceEdge @appendEdge(connections: $connections) { + cursor + node { + id + name + description + websiteUrl + logo { + downloadUrl + } + rank + createdAt + updatedAt + canUpdate: permission(action: "compliance-portal:portal-reference:update") + canDelete: permission(action: "compliance-portal:portal-reference:delete") + } + } + } + } +`; + +export const updateCompliancePageReferenceMutation = graphql` + mutation CompliancePageReferenceGraphUpdateMutation( + $input: UpdateTrustCenterReferenceInput! + ) { + updateTrustCenterReference(input: $input) { + trustCenterReference { + id + name + description + websiteUrl + logo { + downloadUrl + } + rank + createdAt + updatedAt + canUpdate: permission(action: "compliance-portal:portal-reference:update") + canDelete: permission(action: "compliance-portal:portal-reference:delete") + } + } + } +`; + +export const deleteCompliancePageReferenceMutation = graphql` + mutation CompliancePageReferenceGraphDeleteMutation( + $input: DeleteTrustCenterReferenceInput! + $connections: [ID!]! + ) { + deleteTrustCenterReference(input: $input) { + deletedTrustCenterReferenceId @deleteEdge(connections: $connections) + } + } +`; + +export function useCreateCompliancePageReferenceMutation() { + return useMutation( + createCompliancePageReferenceMutation, + { + successMessage: "Reference created successfully", + errorToast: "Failed to create reference", + }, + ); +} + +export function useUpdateCompliancePageReferenceMutation() { + return useMutation( + updateCompliancePageReferenceMutation, + { + successMessage: "Reference updated successfully", + errorToast: "Failed to update reference", + }, + ); +} + +export const updateCompliancePageReferenceRankMutation = graphql` + mutation CompliancePageReferenceGraphUpdateRankMutation( + $input: UpdateTrustCenterReferenceInput! + ) { + updateTrustCenterReference(input: $input) { + trustCenterReference { + id + rank + } + } + } +`; + +export function useUpdateCompliancePageReferenceRankMutation() { + return useMutation( + updateCompliancePageReferenceRankMutation, + { + successMessage: "Order updated successfully", + errorToast: "Failed to update order", + }, + ); +} + +export function useDeleteCompliancePageReferenceMutation() { + return useMutation( + deleteCompliancePageReferenceMutation, + { + successMessage: "Reference deleted successfully", + errorToast: "Failed to delete reference", + }, + ); +} diff --git a/apps/console/src/hooks/graph/TrustCenterReferenceGraph.ts b/apps/console/src/hooks/graph/TrustCenterReferenceGraph.ts deleted file mode 100644 index 79ad9d07c..000000000 --- a/apps/console/src/hooks/graph/TrustCenterReferenceGraph.ts +++ /dev/null @@ -1,141 +0,0 @@ -// Copyright (c) 2025-2026 Probo Inc . -// -// Permission is hereby granted, free of charge, to any person obtaining a copy -// of this software and associated documentation files (the "Software"), to deal -// in the Software without restriction, including without limitation the rights -// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -// copies of the Software, and to permit persons to whom the Software is -// furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included in -// all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -// SOFTWARE. - -import { graphql } from "react-relay"; - -import type { TrustCenterReferenceGraphCreateMutation } from "#/__generated__/core/TrustCenterReferenceGraphCreateMutation.graphql"; -import type { TrustCenterReferenceGraphDeleteMutation } from "#/__generated__/core/TrustCenterReferenceGraphDeleteMutation.graphql"; -import type { TrustCenterReferenceGraphUpdateMutation } from "#/__generated__/core/TrustCenterReferenceGraphUpdateMutation.graphql"; -import type { TrustCenterReferenceGraphUpdateRankMutation } from "#/__generated__/core/TrustCenterReferenceGraphUpdateRankMutation.graphql"; -import { useMutationWithToasts } from "#/hooks/useMutationWithToasts"; - -export const createTrustCenterReferenceMutation = graphql` - mutation TrustCenterReferenceGraphCreateMutation( - $input: CreateTrustCenterReferenceInput! - $connections: [ID!]! - ) { - createTrustCenterReference(input: $input) { - trustCenterReferenceEdge @appendEdge(connections: $connections) { - cursor - node { - id - name - description - websiteUrl - logo { - downloadUrl - } - rank - createdAt - updatedAt - canUpdate: permission(action: "core:trust-center-reference:update") - canDelete: permission(action: "core:trust-center-reference:delete") - } - } - } - } -`; - -export const updateTrustCenterReferenceMutation = graphql` - mutation TrustCenterReferenceGraphUpdateMutation( - $input: UpdateTrustCenterReferenceInput! - ) { - updateTrustCenterReference(input: $input) { - trustCenterReference { - id - name - description - websiteUrl - logo { - downloadUrl - } - rank - createdAt - updatedAt - canUpdate: permission(action: "core:trust-center-reference:update") - canDelete: permission(action: "core:trust-center-reference:delete") - } - } - } -`; - -export const deleteTrustCenterReferenceMutation = graphql` - mutation TrustCenterReferenceGraphDeleteMutation( - $input: DeleteTrustCenterReferenceInput! - $connections: [ID!]! - ) { - deleteTrustCenterReference(input: $input) { - deletedTrustCenterReferenceId @deleteEdge(connections: $connections) - } - } -`; - -export function useCreateTrustCenterReferenceMutation() { - return useMutationWithToasts( - createTrustCenterReferenceMutation, - { - successMessage: "Reference created successfully", - errorMessage: "Failed to create reference", - }, - ); -} - -export function useUpdateTrustCenterReferenceMutation() { - return useMutationWithToasts( - updateTrustCenterReferenceMutation, - { - successMessage: "Reference updated successfully", - errorMessage: "Failed to update reference", - }, - ); -} - -export const updateTrustCenterReferenceRankMutation = graphql` - mutation TrustCenterReferenceGraphUpdateRankMutation( - $input: UpdateTrustCenterReferenceInput! - ) { - updateTrustCenterReference(input: $input) { - trustCenterReference { - id - rank - } - } - } -`; - -export function useUpdateTrustCenterReferenceRankMutation() { - return useMutationWithToasts( - updateTrustCenterReferenceRankMutation, - { - successMessage: "Order updated successfully", - errorMessage: "Failed to update order", - }, - ); -} - -export function useDeleteTrustCenterReferenceMutation() { - return useMutationWithToasts( - deleteTrustCenterReferenceMutation, - { - successMessage: "Reference deleted successfully", - errorMessage: "Failed to delete reference", - }, - ); -} diff --git a/apps/console/src/lib/relay/useMutation.ts b/apps/console/src/lib/relay/useMutation.ts new file mode 100644 index 000000000..bc7fac046 --- /dev/null +++ b/apps/console/src/lib/relay/useMutation.ts @@ -0,0 +1,53 @@ +// 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 { formatError } from "@probo/helpers"; +import { useTranslate } from "@probo/i18n"; +import { createUseMutation, type MutationNotifier } from "@probo/relay"; +import { useToast } from "@probo/ui"; +import { useMemo } from "react"; + +/** + * Binds the shared awaitable useMutation (`@probo/relay`) to this app's + * feedback stack: `@probo/ui` toasts, i18n titles, and `formatError` + * descriptions. This is the only place those opinions are wired. + * + * Always import useMutation from `#/lib/relay/useMutation` — never useMutation + * from react-relay. + */ +function useMutationNotifier(): MutationNotifier { + const { toast } = useToast(); + const { __ } = useTranslate(); + + return useMemo( + () => ({ + notifySuccess: (title) => { + toast({ title, description: "", variant: "success" }); + }, + notifyError: (error, title) => { + const finalTitle = title ?? __("Error"); + toast({ + title: finalTitle, + description: formatError(finalTitle, error), + variant: "error", + }); + }, + }), + [toast, __], + ); +} + +export type { MutationFeedback } from "@probo/relay"; + +export const useMutation = createUseMutation(useMutationNotifier); diff --git a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx index 745fd9466..a56732206 100644 --- a/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx +++ b/apps/console/src/pages/iam/organizations/_components/Sidebar.tsx @@ -68,7 +68,7 @@ const fragment = graphql` action: "core:processing-activity:list" ) canListRightsRequests: permission(action: "core:rights-request:list") - canGetTrustCenter: permission(action: "core:trust-center:get") + canGetCompliancePage: permission(action: "compliance-portal:portal:get") canListCookieBanners: permission(action: "core:cookie-banner:list") canUpdateOrganization: permission(action: "iam:organization:update") canListStatementsOfApplicability: permission( @@ -212,7 +212,7 @@ export function Sidebar(props: { fKey: SidebarFragment$key }) { to={`${prefix}/access-reviews`} /> )} - {organization.canGetTrustCenter && ( + {organization.canGetCompliancePage && ( ; @@ -140,10 +127,6 @@ export function OrganizationForm(props: { { defaultValues: { name: organization.name, - description: organization.description || "", - websiteUrl: organization.websiteUrl || "", - email: organization.email || "", - headquarterAddress: organization.headquarterAddress || "", }, }, ); @@ -221,10 +204,6 @@ export function OrganizationForm(props: { input: { organizationId: organization.id, name: data.name, - description: data.description || null, - websiteUrl: data.websiteUrl || null, - email: data.email || null, - headquarterAddress: data.headquarterAddress || null, }, }, }); @@ -344,43 +323,6 @@ export function OrganizationForm(props: { label={__("Organization name")} placeholder={__("Organization name")} /> -
- -