Change compliance update modals
- Merge new and edit modals design - Create a send modal Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<458f30b721ecd9df210621a8853008c3>>
|
* @generated SignedSource<<2cec7e8ca8209de4cb2d007ad8bfe9fa>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -15,11 +15,11 @@ export type CreateMailingListUpdateInput = {
|
|||||||
mailingListId: string;
|
mailingListId: string;
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
export type NewComplianceUpdateDialogMutation$variables = {
|
export type ComplianceUpdateFormDialogCreateMutation$variables = {
|
||||||
connections: ReadonlyArray<string>;
|
connections: ReadonlyArray<string>;
|
||||||
input: CreateMailingListUpdateInput;
|
input: CreateMailingListUpdateInput;
|
||||||
};
|
};
|
||||||
export type NewComplianceUpdateDialogMutation$data = {
|
export type ComplianceUpdateFormDialogCreateMutation$data = {
|
||||||
readonly createMailingListUpdate: {
|
readonly createMailingListUpdate: {
|
||||||
readonly mailingListUpdate: {
|
readonly mailingListUpdate: {
|
||||||
readonly body: string;
|
readonly body: string;
|
||||||
@@ -31,9 +31,9 @@ export type NewComplianceUpdateDialogMutation$data = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type NewComplianceUpdateDialogMutation = {
|
export type ComplianceUpdateFormDialogCreateMutation = {
|
||||||
response: NewComplianceUpdateDialogMutation$data;
|
response: ComplianceUpdateFormDialogCreateMutation$data;
|
||||||
variables: NewComplianceUpdateDialogMutation$variables;
|
variables: ComplianceUpdateFormDialogCreateMutation$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -115,7 +115,7 @@ return {
|
|||||||
],
|
],
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "NewComplianceUpdateDialogMutation",
|
"name": "ComplianceUpdateFormDialogCreateMutation",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -140,7 +140,7 @@ return {
|
|||||||
(v0/*: any*/)
|
(v0/*: any*/)
|
||||||
],
|
],
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "NewComplianceUpdateDialogMutation",
|
"name": "ComplianceUpdateFormDialogCreateMutation",
|
||||||
"selections": [
|
"selections": [
|
||||||
{
|
{
|
||||||
"alias": null,
|
"alias": null,
|
||||||
@@ -178,16 +178,16 @@ return {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "33fc392100d93803de3a189e75961a75",
|
"cacheID": "52d07d51ab21154cf26f8a932aba5e77",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "NewComplianceUpdateDialogMutation",
|
"name": "ComplianceUpdateFormDialogCreateMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation NewComplianceUpdateDialogMutation(\n $input: CreateMailingListUpdateInput!\n) {\n createMailingListUpdate(input: $input) {\n mailingListUpdate {\n id\n title\n body\n status\n createdAt\n updatedAt\n }\n }\n}\n"
|
"text": "mutation ComplianceUpdateFormDialogCreateMutation(\n $input: CreateMailingListUpdateInput!\n) {\n createMailingListUpdate(input: $input) {\n mailingListUpdate {\n id\n title\n body\n status\n createdAt\n updatedAt\n }\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "bf02b571efaf38d4f4a8fde50b311832";
|
(node as any).hash = "6e8c7b014c850c90bcfb876d6e504f4d";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<622eccc63036573a8e38ffc8dd36e7d7>>
|
* @generated SignedSource<<160a29eaaba4b3db2c1b7aca34164dd7>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -15,10 +15,10 @@ export type UpdateMailingListUpdateInput = {
|
|||||||
id: string;
|
id: string;
|
||||||
title: string;
|
title: string;
|
||||||
};
|
};
|
||||||
export type EditComplianceUpdateDialogMutation$variables = {
|
export type ComplianceUpdateFormDialogUpdateMutation$variables = {
|
||||||
input: UpdateMailingListUpdateInput;
|
input: UpdateMailingListUpdateInput;
|
||||||
};
|
};
|
||||||
export type EditComplianceUpdateDialogMutation$data = {
|
export type ComplianceUpdateFormDialogUpdateMutation$data = {
|
||||||
readonly updateMailingListUpdate: {
|
readonly updateMailingListUpdate: {
|
||||||
readonly mailingListUpdate: {
|
readonly mailingListUpdate: {
|
||||||
readonly body: string;
|
readonly body: string;
|
||||||
@@ -29,9 +29,9 @@ export type EditComplianceUpdateDialogMutation$data = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type EditComplianceUpdateDialogMutation = {
|
export type ComplianceUpdateFormDialogUpdateMutation = {
|
||||||
response: EditComplianceUpdateDialogMutation$data;
|
response: ComplianceUpdateFormDialogUpdateMutation$data;
|
||||||
variables: EditComplianceUpdateDialogMutation$variables;
|
variables: ComplianceUpdateFormDialogUpdateMutation$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -112,7 +112,7 @@ return {
|
|||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "EditComplianceUpdateDialogMutation",
|
"name": "ComplianceUpdateFormDialogUpdateMutation",
|
||||||
"selections": (v1/*: any*/),
|
"selections": (v1/*: any*/),
|
||||||
"type": "Mutation",
|
"type": "Mutation",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
@@ -121,20 +121,20 @@ return {
|
|||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "EditComplianceUpdateDialogMutation",
|
"name": "ComplianceUpdateFormDialogUpdateMutation",
|
||||||
"selections": (v1/*: any*/)
|
"selections": (v1/*: any*/)
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "49b4c9f194a0bf6ebe01408ae6387f2e",
|
"cacheID": "49b5feb2a9a2e157568ad2431e032425",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "EditComplianceUpdateDialogMutation",
|
"name": "ComplianceUpdateFormDialogUpdateMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation EditComplianceUpdateDialogMutation(\n $input: UpdateMailingListUpdateInput!\n) {\n updateMailingListUpdate(input: $input) {\n mailingListUpdate {\n id\n title\n body\n status\n updatedAt\n }\n }\n}\n"
|
"text": "mutation ComplianceUpdateFormDialogUpdateMutation(\n $input: UpdateMailingListUpdateInput!\n) {\n updateMailingListUpdate(input: $input) {\n mailingListUpdate {\n id\n title\n body\n status\n updatedAt\n }\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "8b2246ab3bddba658cc488610afcd80f";
|
(node as any).hash = "3737c127b41a55df71e09d64aaa3da9e";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* @generated SignedSource<<f5323e8e7bd73882c9169eb01318a21c>>
|
* @generated SignedSource<<7eced414e0638e806b52e86afcb6cca4>>
|
||||||
* @lightSyntaxTransform
|
* @lightSyntaxTransform
|
||||||
* @nogrep
|
* @nogrep
|
||||||
*/
|
*/
|
||||||
@@ -13,10 +13,10 @@ export type MailingListUpdateStatus = "DRAFT" | "ENQUEUED" | "PROCESSING" | "SEN
|
|||||||
export type SendMailingListUpdateInput = {
|
export type SendMailingListUpdateInput = {
|
||||||
id: string;
|
id: string;
|
||||||
};
|
};
|
||||||
export type CompliancePageUpdatesListSendMutation$variables = {
|
export type SendUpdateDialogMutation$variables = {
|
||||||
input: SendMailingListUpdateInput;
|
input: SendMailingListUpdateInput;
|
||||||
};
|
};
|
||||||
export type CompliancePageUpdatesListSendMutation$data = {
|
export type SendUpdateDialogMutation$data = {
|
||||||
readonly sendMailingListUpdate: {
|
readonly sendMailingListUpdate: {
|
||||||
readonly mailingListUpdate: {
|
readonly mailingListUpdate: {
|
||||||
readonly body: string;
|
readonly body: string;
|
||||||
@@ -27,9 +27,9 @@ export type CompliancePageUpdatesListSendMutation$data = {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
export type CompliancePageUpdatesListSendMutation = {
|
export type SendUpdateDialogMutation = {
|
||||||
response: CompliancePageUpdatesListSendMutation$data;
|
response: SendUpdateDialogMutation$data;
|
||||||
variables: CompliancePageUpdatesListSendMutation$variables;
|
variables: SendUpdateDialogMutation$variables;
|
||||||
};
|
};
|
||||||
|
|
||||||
const node: ConcreteRequest = (function(){
|
const node: ConcreteRequest = (function(){
|
||||||
@@ -110,7 +110,7 @@ return {
|
|||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Fragment",
|
"kind": "Fragment",
|
||||||
"metadata": null,
|
"metadata": null,
|
||||||
"name": "CompliancePageUpdatesListSendMutation",
|
"name": "SendUpdateDialogMutation",
|
||||||
"selections": (v1/*: any*/),
|
"selections": (v1/*: any*/),
|
||||||
"type": "Mutation",
|
"type": "Mutation",
|
||||||
"abstractKey": null
|
"abstractKey": null
|
||||||
@@ -119,20 +119,20 @@ return {
|
|||||||
"operation": {
|
"operation": {
|
||||||
"argumentDefinitions": (v0/*: any*/),
|
"argumentDefinitions": (v0/*: any*/),
|
||||||
"kind": "Operation",
|
"kind": "Operation",
|
||||||
"name": "CompliancePageUpdatesListSendMutation",
|
"name": "SendUpdateDialogMutation",
|
||||||
"selections": (v1/*: any*/)
|
"selections": (v1/*: any*/)
|
||||||
},
|
},
|
||||||
"params": {
|
"params": {
|
||||||
"cacheID": "23fee99a4999d2c9cbeecd6fa9863b67",
|
"cacheID": "714c7cc9cdb06cb384690e17cdaa9958",
|
||||||
"id": null,
|
"id": null,
|
||||||
"metadata": {},
|
"metadata": {},
|
||||||
"name": "CompliancePageUpdatesListSendMutation",
|
"name": "SendUpdateDialogMutation",
|
||||||
"operationKind": "mutation",
|
"operationKind": "mutation",
|
||||||
"text": "mutation CompliancePageUpdatesListSendMutation(\n $input: SendMailingListUpdateInput!\n) {\n sendMailingListUpdate(input: $input) {\n mailingListUpdate {\n id\n title\n body\n status\n updatedAt\n }\n }\n}\n"
|
"text": "mutation SendUpdateDialogMutation(\n $input: SendMailingListUpdateInput!\n) {\n sendMailingListUpdate(input: $input) {\n mailingListUpdate {\n id\n title\n body\n status\n updatedAt\n }\n }\n}\n"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
(node as any).hash = "37c68b46d761280e2ff16f5bff08b249";
|
(node as any).hash = "9ec6f363395b934616b97fe50b83d328";
|
||||||
|
|
||||||
export default node;
|
export default node;
|
||||||
@@ -10,9 +10,8 @@ import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
|||||||
|
|
||||||
import { CompliancePageMailingList } from "./_components/CompliancePageMailingList";
|
import { CompliancePageMailingList } from "./_components/CompliancePageMailingList";
|
||||||
import { CompliancePageUpdatesList, type UpdateNode } from "./_components/CompliancePageUpdatesList";
|
import { CompliancePageUpdatesList, type UpdateNode } from "./_components/CompliancePageUpdatesList";
|
||||||
import { EditComplianceUpdateDialog } from "./_components/EditComplianceUpdateDialog";
|
import { ComplianceUpdateFormDialog } from "./_components/ComplianceUpdateFormDialog";
|
||||||
import { NewCompliancePageSubscriberDialog } from "./_components/NewCompliancePageSubscriberDialog";
|
import { NewCompliancePageSubscriberDialog } from "./_components/NewCompliancePageSubscriberDialog";
|
||||||
import { NewComplianceUpdateDialog } from "./_components/NewComplianceUpdateDialog";
|
|
||||||
|
|
||||||
export const compliancePageMailingListPageQuery = graphql`
|
export const compliancePageMailingListPageQuery = graphql`
|
||||||
query CompliancePageMailingListPageQuery($organizationId: ID!) {
|
query CompliancePageMailingListPageQuery($organizationId: ID!) {
|
||||||
@@ -174,14 +173,14 @@ export function CompliancePageMailingListPage(props: {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{mailingListId && updatesConnectionId && (
|
{mailingListId && updatesConnectionId && (
|
||||||
<NewComplianceUpdateDialog
|
<ComplianceUpdateFormDialog
|
||||||
ref={newUpdateDialogRef}
|
ref={newUpdateDialogRef}
|
||||||
mailingListId={mailingListId}
|
mailingListId={mailingListId}
|
||||||
connectionId={updatesConnectionId}
|
connectionId={updatesConnectionId}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<EditComplianceUpdateDialog
|
<ComplianceUpdateFormDialog
|
||||||
ref={editUpdateDialogRef}
|
ref={editUpdateDialogRef}
|
||||||
update={selectedUpdate}
|
update={selectedUpdate}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,14 +1,16 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
import { useTranslate } from "@probo/i18n";
|
||||||
import { Badge, Button, IconChevronDown, IconPencil, IconSend, IconTrashCan, Spinner, Table, Tbody, Td, Th, Thead, Tr, useConfirm } from "@probo/ui";
|
import { Badge, Button, IconChevronDown, IconPageTextLine, IconPencil, IconSend, IconTrashCan, Spinner, Table, Tbody, Td, Th, Thead, Tr, useDialogRef } from "@probo/ui";
|
||||||
|
import { useState } from "react";
|
||||||
import { usePaginationFragment } from "react-relay";
|
import { usePaginationFragment } from "react-relay";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
import type { CompliancePageUpdatesListDeleteMutation } from "#/__generated__/core/CompliancePageUpdatesListDeleteMutation.graphql";
|
import type { CompliancePageUpdatesListDeleteMutation } from "#/__generated__/core/CompliancePageUpdatesListDeleteMutation.graphql";
|
||||||
import type { CompliancePageUpdatesListFragment$data, CompliancePageUpdatesListFragment$key } from "#/__generated__/core/CompliancePageUpdatesListFragment.graphql";
|
import type { CompliancePageUpdatesListFragment$data, CompliancePageUpdatesListFragment$key } from "#/__generated__/core/CompliancePageUpdatesListFragment.graphql";
|
||||||
import type { CompliancePageUpdatesListQuery } from "#/__generated__/core/CompliancePageUpdatesListQuery.graphql";
|
import type { CompliancePageUpdatesListQuery } from "#/__generated__/core/CompliancePageUpdatesListQuery.graphql";
|
||||||
import type { CompliancePageUpdatesListSendMutation } from "#/__generated__/core/CompliancePageUpdatesListSendMutation.graphql";
|
|
||||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
import { SendUpdateDialog } from "./SendUpdateDialog";
|
||||||
|
|
||||||
const deleteMutation = graphql`
|
const deleteMutation = graphql`
|
||||||
mutation CompliancePageUpdatesListDeleteMutation(
|
mutation CompliancePageUpdatesListDeleteMutation(
|
||||||
$input: DeleteMailingListUpdateInput!
|
$input: DeleteMailingListUpdateInput!
|
||||||
@@ -20,20 +22,6 @@ const deleteMutation = graphql`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const sendMutation = graphql`
|
|
||||||
mutation CompliancePageUpdatesListSendMutation($input: SendMailingListUpdateInput!) {
|
|
||||||
sendMailingListUpdate(input: $input) {
|
|
||||||
mailingListUpdate {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
status
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
const fragment = graphql`
|
const fragment = graphql`
|
||||||
fragment CompliancePageUpdatesListFragment on MailingList
|
fragment CompliancePageUpdatesListFragment on MailingList
|
||||||
@argumentDefinitions(
|
@argumentDefinitions(
|
||||||
@@ -72,7 +60,9 @@ export function CompliancePageUpdatesList(props: {
|
|||||||
}) {
|
}) {
|
||||||
const { fragmentRef, onEdit } = props;
|
const { fragmentRef, onEdit } = props;
|
||||||
const { __ } = useTranslate();
|
const { __ } = useTranslate();
|
||||||
const confirm = useConfirm();
|
|
||||||
|
const sendDialogRef = useDialogRef();
|
||||||
|
const [updateToSend, setUpdateToSend] = useState<UpdateNode | null>(null);
|
||||||
|
|
||||||
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment<
|
const { data, hasNext, loadNext, isLoadingNext } = usePaginationFragment<
|
||||||
CompliancePageUpdatesListQuery,
|
CompliancePageUpdatesListQuery,
|
||||||
@@ -87,12 +77,6 @@ export function CompliancePageUpdatesList(props: {
|
|||||||
errorMessage: __("Failed to delete update"),
|
errorMessage: __("Failed to delete update"),
|
||||||
});
|
});
|
||||||
|
|
||||||
const [sendUpdate, isSending]
|
|
||||||
= useMutationWithToasts<CompliancePageUpdatesListSendMutation>(sendMutation, {
|
|
||||||
successMessage: __("Update enqueued for delivery"),
|
|
||||||
errorMessage: __("Failed to enqueue update for delivery"),
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleDelete = (id: string) => {
|
const handleDelete = (id: string) => {
|
||||||
void deleteUpdate({
|
void deleteUpdate({
|
||||||
variables: {
|
variables: {
|
||||||
@@ -103,26 +87,13 @@ export function CompliancePageUpdatesList(props: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleSend = (node: UpdateNode) => {
|
const handleSend = (node: UpdateNode) => {
|
||||||
confirm(
|
setUpdateToSend(node);
|
||||||
() =>
|
sendDialogRef.current?.open();
|
||||||
sendUpdate({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
id: node.id,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}),
|
|
||||||
{
|
|
||||||
title: __("Send this update to all subscribers?"),
|
|
||||||
message: `"${node.title}" — ${node.body.length > 120 ? node.body.slice(0, 120) + "…" : node.body}`,
|
|
||||||
label: __("Send"),
|
|
||||||
variant: "primary",
|
|
||||||
},
|
|
||||||
);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
<SendUpdateDialog ref={sendDialogRef} update={updateToSend} />
|
||||||
{connection.edges.length === 0
|
{connection.edges.length === 0
|
||||||
? (
|
? (
|
||||||
<Table>
|
<Table>
|
||||||
@@ -159,26 +130,25 @@ export function CompliancePageUpdatesList(props: {
|
|||||||
{new Date(node.createdAt).toLocaleDateString()}
|
{new Date(node.createdAt).toLocaleDateString()}
|
||||||
</Td>
|
</Td>
|
||||||
<Td className="w-auto">
|
<Td className="w-auto">
|
||||||
<div className="flex gap-1 justify-end">
|
<div className="flex gap-2 justify-end">
|
||||||
{node.status === "DRAFT" && (
|
{node.status === "DRAFT" && (
|
||||||
<Button
|
<Button
|
||||||
|
variant="secondary"
|
||||||
icon={IconSend}
|
icon={IconSend}
|
||||||
disabled={isSending}
|
|
||||||
onClick={() => handleSend(node)}
|
onClick={() => handleSend(node)}
|
||||||
className="bg-green-600 text-white hover:bg-green-700 active:bg-green-800 shadow-none"
|
|
||||||
aria-label={__("Send")}
|
aria-label={__("Send")}
|
||||||
>
|
>
|
||||||
{__("Send")}
|
{__("Send")}
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="secondary"
|
||||||
icon={IconPencil}
|
icon={node.status === "DRAFT" ? IconPencil : IconPageTextLine}
|
||||||
onClick={() => onEdit(node)}
|
onClick={() => onEdit(node)}
|
||||||
aria-label={__("Edit update")}
|
aria-label={node.status === "DRAFT" ? __("Edit update") : __("View update")}
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
variant="tertiary"
|
variant="danger"
|
||||||
icon={IconTrashCan}
|
icon={IconTrashCan}
|
||||||
disabled={isDeleting}
|
disabled={isDeleting}
|
||||||
onClick={() => handleDelete(node.id)}
|
onClick={() => handleDelete(node.id)}
|
||||||
|
|||||||
@@ -0,0 +1,180 @@
|
|||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { Button, Dialog, DialogContent, DialogFooter, type DialogRef, Field, IconCircleInfo, Spinner, Textarea } from "@probo/ui";
|
||||||
|
import { useEffect } from "react";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
import type { ComplianceUpdateFormDialogCreateMutation } from "#/__generated__/core/ComplianceUpdateFormDialogCreateMutation.graphql";
|
||||||
|
import type { ComplianceUpdateFormDialogUpdateMutation } from "#/__generated__/core/ComplianceUpdateFormDialogUpdateMutation.graphql";
|
||||||
|
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
||||||
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
import type { UpdateNode } from "./CompliancePageUpdatesList";
|
||||||
|
|
||||||
|
const createMutation = graphql`
|
||||||
|
mutation ComplianceUpdateFormDialogCreateMutation(
|
||||||
|
$input: CreateMailingListUpdateInput!
|
||||||
|
$connections: [ID!]!
|
||||||
|
) {
|
||||||
|
createMailingListUpdate(input: $input) {
|
||||||
|
mailingListUpdate
|
||||||
|
@prependNode(connections: $connections, edgeTypeName: "MailingListUpdateEdge") {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
status
|
||||||
|
createdAt
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
const updateMutation = graphql`
|
||||||
|
mutation ComplianceUpdateFormDialogUpdateMutation($input: UpdateMailingListUpdateInput!) {
|
||||||
|
updateMailingListUpdate(input: $input) {
|
||||||
|
mailingListUpdate {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
status
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type CreateProps = {
|
||||||
|
ref: DialogRef;
|
||||||
|
mailingListId: string;
|
||||||
|
connectionId: string;
|
||||||
|
onCreated?: () => void;
|
||||||
|
update?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type EditProps = {
|
||||||
|
ref: DialogRef;
|
||||||
|
update: UpdateNode | null;
|
||||||
|
mailingListId?: never;
|
||||||
|
connectionId?: never;
|
||||||
|
onCreated?: never;
|
||||||
|
};
|
||||||
|
|
||||||
|
type Props = CreateProps | EditProps;
|
||||||
|
|
||||||
|
export function ComplianceUpdateFormDialog(props: Props) {
|
||||||
|
const { ref, update, mailingListId, connectionId, onCreated } = props;
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const isEditMode = update !== undefined;
|
||||||
|
const isSent = isEditMode && update?.status !== "DRAFT";
|
||||||
|
|
||||||
|
const schemaWithMessages = z.object({
|
||||||
|
title: z.string().trim().min(1, __("Title is required")),
|
||||||
|
body: z.string().trim().min(1, __("Body is required")),
|
||||||
|
});
|
||||||
|
|
||||||
|
const form = useFormWithSchema(schemaWithMessages, {
|
||||||
|
defaultValues: { title: "", body: "" },
|
||||||
|
});
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (update) {
|
||||||
|
form.reset({ title: update.title, body: update.body });
|
||||||
|
}
|
||||||
|
}, [update, form]);
|
||||||
|
|
||||||
|
const [createUpdate, isCreating] = useMutationWithToasts<ComplianceUpdateFormDialogCreateMutation>(
|
||||||
|
createMutation,
|
||||||
|
{
|
||||||
|
successMessage: __("Update created successfully"),
|
||||||
|
errorMessage: __("Failed to create update"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const [saveUpdate, isSaving] = useMutationWithToasts<ComplianceUpdateFormDialogUpdateMutation>(
|
||||||
|
updateMutation,
|
||||||
|
{
|
||||||
|
successMessage: __("Update saved successfully"),
|
||||||
|
errorMessage: __("Failed to save update"),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleSubmit = async (data: z.infer<typeof schemaWithMessages>) => {
|
||||||
|
if (isEditMode) {
|
||||||
|
if (!update) return;
|
||||||
|
await saveUpdate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
id: update.id,
|
||||||
|
title: data.title,
|
||||||
|
body: data.body,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
onCompleted: (_, errors) => {
|
||||||
|
if (!errors?.length) {
|
||||||
|
ref.current?.close();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
await createUpdate({
|
||||||
|
variables: {
|
||||||
|
input: {
|
||||||
|
mailingListId: mailingListId,
|
||||||
|
title: data.title,
|
||||||
|
body: data.body,
|
||||||
|
},
|
||||||
|
connections: [connectionId],
|
||||||
|
},
|
||||||
|
onCompleted: (_, errors) => {
|
||||||
|
if (!errors?.length) {
|
||||||
|
form.reset();
|
||||||
|
ref.current?.close();
|
||||||
|
onCreated?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog ref={ref} title={isSent ? __("View Update") : isEditMode ? __("Edit Update") : __("Add Update")}>
|
||||||
|
<form onSubmit={e => void form.handleSubmit(handleSubmit)(e)}>
|
||||||
|
<DialogContent className="px-6 pt-4 pb-2 space-y-4">
|
||||||
|
<div className="flex gap-2.5 rounded-lg bg-surface-secondary p-3 text-sm text-txt-secondary">
|
||||||
|
<IconCircleInfo size={16} className="mt-0.5 shrink-0 text-txt-tertiary" />
|
||||||
|
{__("Do not include confidential or sensitive information in this update. Any content that requires protection should be placed behind your NDA-gated documents instead.")}
|
||||||
|
</div>
|
||||||
|
<Field
|
||||||
|
label={__("Title")}
|
||||||
|
required
|
||||||
|
disabled={isSent}
|
||||||
|
error={form.formState.errors.title?.message}
|
||||||
|
{...form.register("title")}
|
||||||
|
/>
|
||||||
|
<Field
|
||||||
|
label={__("Body")}
|
||||||
|
required
|
||||||
|
error={form.formState.errors.body?.message}
|
||||||
|
>
|
||||||
|
<Textarea rows={12} disabled={isSent} {...form.register("body")} />
|
||||||
|
</Field>
|
||||||
|
{isSent && (
|
||||||
|
<p className="text-sm text-txt-tertiary">
|
||||||
|
{__("This update has been sent and can no longer be edited.")}
|
||||||
|
</p>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
{!isSent && (
|
||||||
|
<Button type="submit" disabled={isCreating || isSaving}>
|
||||||
|
{(isCreating || isSaving) && <Spinner />}
|
||||||
|
{isEditMode ? __("Save") : __("Create")}
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</DialogFooter>
|
||||||
|
</form>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,118 +0,0 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
|
||||||
import { Button, Dialog, DialogContent, DialogFooter, type DialogRef, Field, IconCircleInfo, Spinner, Textarea } from "@probo/ui";
|
|
||||||
import { useEffect } from "react";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import type { EditComplianceUpdateDialogMutation } from "#/__generated__/core/EditComplianceUpdateDialogMutation.graphql";
|
|
||||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
|
||||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
|
||||||
|
|
||||||
import type { UpdateNode } from "./CompliancePageUpdatesList";
|
|
||||||
|
|
||||||
const updateMutation = graphql`
|
|
||||||
mutation EditComplianceUpdateDialogMutation($input: UpdateMailingListUpdateInput!) {
|
|
||||||
updateMailingListUpdate(input: $input) {
|
|
||||||
mailingListUpdate {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
status
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function EditComplianceUpdateDialog(props: {
|
|
||||||
update: UpdateNode | null;
|
|
||||||
ref: DialogRef;
|
|
||||||
}) {
|
|
||||||
const { update, ref } = props;
|
|
||||||
const { __ } = useTranslate();
|
|
||||||
|
|
||||||
const isSent = update?.status !== "DRAFT";
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
title: z.string().trim().min(1, __("Title is required")),
|
|
||||||
body: z.string().trim().min(1, __("Body is required")),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useFormWithSchema(schema, {
|
|
||||||
defaultValues: { title: "", body: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
if (update) {
|
|
||||||
form.reset({ title: update.title, body: update.body });
|
|
||||||
}
|
|
||||||
}, [update, form]);
|
|
||||||
|
|
||||||
const [saveUpdate, isSaving] = useMutationWithToasts<EditComplianceUpdateDialogMutation>(
|
|
||||||
updateMutation,
|
|
||||||
{
|
|
||||||
successMessage: __("Update saved successfully"),
|
|
||||||
errorMessage: __("Failed to save update"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = async (data: { title: string; body: string }) => {
|
|
||||||
if (!update) return;
|
|
||||||
await saveUpdate({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
id: update.id,
|
|
||||||
title: data.title.trim(),
|
|
||||||
body: data.body.trim(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
onCompleted: (_, errors) => {
|
|
||||||
if (!errors?.length) {
|
|
||||||
ref.current?.close();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog ref={ref} title={__("Edit Update")}>
|
|
||||||
<form onSubmit={e => void form.handleSubmit(handleSubmit)(e)}>
|
|
||||||
<DialogContent padded className="space-y-6">
|
|
||||||
<div className="flex gap-2.5 rounded-lg bg-surface-secondary p-3 text-sm text-txt-secondary">
|
|
||||||
<IconCircleInfo size={16} className="mt-0.5 shrink-0 text-txt-tertiary" />
|
|
||||||
<p>
|
|
||||||
{__("Do not include confidential or sensitive information in this update. Any content that requires protection should be placed behind your NDA-gated documents instead.")}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<Field
|
|
||||||
label={__("Title")}
|
|
||||||
required
|
|
||||||
disabled={isSent}
|
|
||||||
error={form.formState.errors.title?.message}
|
|
||||||
{...form.register("title")}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label={__("Body")}
|
|
||||||
required
|
|
||||||
error={form.formState.errors.body?.message}
|
|
||||||
>
|
|
||||||
<Textarea rows={6} disabled={isSent} {...form.register("body")} />
|
|
||||||
</Field>
|
|
||||||
{isSent && (
|
|
||||||
<p className="text-sm text-txt-tertiary">
|
|
||||||
{__("This update has been sent and can no longer be edited.")}
|
|
||||||
</p>
|
|
||||||
)}
|
|
||||||
</DialogContent>
|
|
||||||
<DialogFooter>
|
|
||||||
{!isSent && (
|
|
||||||
<Button type="submit" disabled={isSaving}>
|
|
||||||
{isSaving && <Spinner />}
|
|
||||||
{__("Save")}
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,102 +0,0 @@
|
|||||||
import { useTranslate } from "@probo/i18n";
|
|
||||||
import { Button, Dialog, DialogContent, DialogFooter, type DialogRef, Field, Spinner, Textarea } from "@probo/ui";
|
|
||||||
import { graphql } from "relay-runtime";
|
|
||||||
import { z } from "zod";
|
|
||||||
|
|
||||||
import type { NewComplianceUpdateDialogMutation } from "#/__generated__/core/NewComplianceUpdateDialogMutation.graphql";
|
|
||||||
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
|
|
||||||
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
|
||||||
|
|
||||||
const createMutation = graphql`
|
|
||||||
mutation NewComplianceUpdateDialogMutation(
|
|
||||||
$input: CreateMailingListUpdateInput!
|
|
||||||
$connections: [ID!]!
|
|
||||||
) {
|
|
||||||
createMailingListUpdate(input: $input) {
|
|
||||||
mailingListUpdate
|
|
||||||
@prependNode(connections: $connections, edgeTypeName: "MailingListUpdateEdge") {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
body
|
|
||||||
status
|
|
||||||
createdAt
|
|
||||||
updatedAt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
export function NewComplianceUpdateDialog(props: {
|
|
||||||
mailingListId: string;
|
|
||||||
connectionId: string;
|
|
||||||
ref: DialogRef;
|
|
||||||
onCreated?: () => void;
|
|
||||||
}) {
|
|
||||||
const { mailingListId, connectionId, ref, onCreated } = props;
|
|
||||||
const { __ } = useTranslate();
|
|
||||||
|
|
||||||
const schema = z.object({
|
|
||||||
title: z.string().trim().min(1, __("Title is required")),
|
|
||||||
body: z.string().trim().min(1, __("Body is required")),
|
|
||||||
});
|
|
||||||
|
|
||||||
const form = useFormWithSchema(schema, {
|
|
||||||
defaultValues: { title: "", body: "" },
|
|
||||||
});
|
|
||||||
|
|
||||||
const [createUpdate, isCreating] = useMutationWithToasts<NewComplianceUpdateDialogMutation>(
|
|
||||||
createMutation,
|
|
||||||
{
|
|
||||||
successMessage: __("Update created successfully"),
|
|
||||||
errorMessage: __("Failed to create update"),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const handleSubmit = async (data: { title: string; body: string }) => {
|
|
||||||
await createUpdate({
|
|
||||||
variables: {
|
|
||||||
input: {
|
|
||||||
mailingListId,
|
|
||||||
title: data.title.trim(),
|
|
||||||
body: data.body.trim(),
|
|
||||||
},
|
|
||||||
connections: [connectionId],
|
|
||||||
},
|
|
||||||
onCompleted: (_, errors) => {
|
|
||||||
if (!errors?.length) {
|
|
||||||
form.reset();
|
|
||||||
ref.current?.close();
|
|
||||||
onCreated?.();
|
|
||||||
}
|
|
||||||
},
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Dialog ref={ref} title={__("Add Update")}>
|
|
||||||
<form onSubmit={e => void form.handleSubmit(handleSubmit)(e)}>
|
|
||||||
<DialogContent padded className="space-y-6">
|
|
||||||
<Field
|
|
||||||
label={__("Title")}
|
|
||||||
required
|
|
||||||
error={form.formState.errors.title?.message}
|
|
||||||
{...form.register("title")}
|
|
||||||
/>
|
|
||||||
<Field
|
|
||||||
label={__("Body")}
|
|
||||||
required
|
|
||||||
error={form.formState.errors.body?.message}
|
|
||||||
>
|
|
||||||
<Textarea rows={6} {...form.register("body")} />
|
|
||||||
</Field>
|
|
||||||
</DialogContent>
|
|
||||||
<DialogFooter>
|
|
||||||
<Button type="submit" disabled={isCreating}>
|
|
||||||
{isCreating && <Spinner />}
|
|
||||||
{__("Create")}
|
|
||||||
</Button>
|
|
||||||
</DialogFooter>
|
|
||||||
</form>
|
|
||||||
</Dialog>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,80 @@
|
|||||||
|
import { useTranslate } from "@probo/i18n";
|
||||||
|
import { Button, Dialog, DialogContent, DialogFooter, type DialogRef, IconSend, Spinner } from "@probo/ui";
|
||||||
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
|
import type { SendUpdateDialogMutation } from "#/__generated__/core/SendUpdateDialogMutation.graphql";
|
||||||
|
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
|
||||||
|
|
||||||
|
import type { UpdateNode } from "./CompliancePageUpdatesList";
|
||||||
|
|
||||||
|
const sendMutation = graphql`
|
||||||
|
mutation SendUpdateDialogMutation($input: SendMailingListUpdateInput!) {
|
||||||
|
sendMailingListUpdate(input: $input) {
|
||||||
|
mailingListUpdate {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
body
|
||||||
|
status
|
||||||
|
updatedAt
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
ref: DialogRef;
|
||||||
|
update: UpdateNode | null;
|
||||||
|
onSent?: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function SendUpdateDialog({ ref, update, onSent }: Props) {
|
||||||
|
const { __ } = useTranslate();
|
||||||
|
|
||||||
|
const [sendUpdate, isSending] = useMutationWithToasts<SendUpdateDialogMutation>(sendMutation, {
|
||||||
|
successMessage: __("Update enqueued for delivery"),
|
||||||
|
errorMessage: __("Failed to enqueue update for delivery"),
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSend = async () => {
|
||||||
|
if (!update) return;
|
||||||
|
await sendUpdate({
|
||||||
|
variables: { input: { id: update.id } },
|
||||||
|
onCompleted: (_, errors) => {
|
||||||
|
if (!errors?.length) {
|
||||||
|
ref.current?.close();
|
||||||
|
onSent?.();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dialog ref={ref} title={__("Send Update to Subscribers")}>
|
||||||
|
<DialogContent className="px-6 pt-5 pb-2 space-y-4">
|
||||||
|
<p className="text-sm text-txt-secondary">
|
||||||
|
{__("This update will be sent to all current subscribers of this mailing list. This action cannot be undone.")}
|
||||||
|
</p>
|
||||||
|
{update && (
|
||||||
|
<div className="rounded-lg border border-border-low bg-surface-secondary overflow-hidden">
|
||||||
|
<div className="px-4 py-3 border-b border-border-low">
|
||||||
|
<p className="text-sm font-medium text-txt-primary">{update.title}</p>
|
||||||
|
</div>
|
||||||
|
<div className="px-4 py-3">
|
||||||
|
<p className="text-sm text-txt-secondary whitespace-pre-wrap">{update.body}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</DialogContent>
|
||||||
|
<DialogFooter>
|
||||||
|
<Button
|
||||||
|
icon={IconSend}
|
||||||
|
disabled={isSending}
|
||||||
|
onClick={() => void handleSend()}
|
||||||
|
>
|
||||||
|
{isSending && <Spinner />}
|
||||||
|
{__("Send")}
|
||||||
|
</Button>
|
||||||
|
</DialogFooter>
|
||||||
|
</Dialog>
|
||||||
|
);
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user