Add compliance page mailing list base

Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
Sacha Al Himdani
2026-02-25 14:59:00 +01:00
parent c0a69100e8
commit 4d2cb793b6
50 changed files with 6365 additions and 110 deletions

View File

@@ -0,0 +1,132 @@
/**
* @generated SignedSource<<72b5471e12ed01f059b4192ee5827a1a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteMailingListSubscriberInput = {
id: string;
};
export type CompliancePageMailingListDeleteMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteMailingListSubscriberInput;
};
export type CompliancePageMailingListDeleteMutation$data = {
readonly deleteMailingListSubscriber: {
readonly deletedMailingListSubscriberId: string;
};
};
export type CompliancePageMailingListDeleteMutation = {
response: CompliancePageMailingListDeleteMutation$data;
variables: CompliancePageMailingListDeleteMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedMailingListSubscriberId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageMailingListDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteMailingListSubscriberPayload",
"kind": "LinkedField",
"name": "deleteMailingListSubscriber",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "CompliancePageMailingListDeleteMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteMailingListSubscriberPayload",
"kind": "LinkedField",
"name": "deleteMailingListSubscriber",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedMailingListSubscriberId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "65de43d2f00cd2c553aa72568f81eac2",
"id": null,
"metadata": {},
"name": "CompliancePageMailingListDeleteMutation",
"operationKind": "mutation",
"text": "mutation CompliancePageMailingListDeleteMutation(\n $input: DeleteMailingListSubscriberInput!\n) {\n deleteMailingListSubscriber(input: $input) {\n deletedMailingListSubscriberId\n }\n}\n"
}
};
})();
(node as any).hash = "bb627bfeb68e7b72c337c40133ab63a8";
export default node;

View File

@@ -0,0 +1,234 @@
/**
* @generated SignedSource<<e9dd50980011bde2eb30f8c1f7fd5344>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type MailingListSubscriberStatus = "CONFIRMED" | "PENDING";
import { FragmentRefs } from "relay-runtime";
export type CompliancePageMailingListFragment$data = {
readonly id: string;
readonly mailingList: {
readonly id: string;
readonly subscribers: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly createdAt: string;
readonly email: string;
readonly fullName: string;
readonly id: string;
readonly status: MailingListSubscriberStatus;
};
}>;
readonly pageInfo: {
readonly endCursor: string | null | undefined;
readonly hasNextPage: boolean;
};
};
} | null | undefined;
readonly " $fragmentType": "CompliancePageMailingListFragment";
};
export type CompliancePageMailingListFragment$key = {
readonly " $data"?: CompliancePageMailingListFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageMailingListFragment">;
};
import CompliancePageMailingListQuery_graphql from './CompliancePageMailingListQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"mailingList",
"subscribers"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 20,
"kind": "LocalArgument",
"name": "first"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": "first",
"cursor": "after",
"direction": "forward",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "first",
"cursor": "after"
},
"backward": null,
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": CompliancePageMailingListQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "CompliancePageMailingListFragment",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingList",
"kind": "LinkedField",
"name": "mailingList",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": "subscribers",
"args": null,
"concreteType": "MailingListSubscriberConnection",
"kind": "LinkedField",
"name": "__CompliancePageMailingList_subscribers_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriberEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriber",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v1/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
}
],
"storageKey": null
},
(v1/*: any*/)
],
"type": "TrustCenter",
"abstractKey": null
};
})();
(node as any).hash = "b1828ed7ded38bb3e3e150b0875ea0a6";
export default node;

View File

@@ -0,0 +1,327 @@
/**
* @generated SignedSource<<7e30e59b53799ccae49199d7b46f0320>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompliancePageMailingListPageQuery$variables = {
organizationId: string;
};
export type CompliancePageMailingListPageQuery$data = {
readonly organization: {
readonly __typename: "Organization";
readonly compliancePage: {
readonly id: string;
readonly mailingList: {
readonly id: string;
readonly replyTo: string | null | undefined;
} | null | undefined;
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageMailingListFragment">;
};
} | {
// This will never be '%other', but we need some
// value in case none of the concrete values match.
readonly __typename: "%other";
};
};
export type CompliancePageMailingListPageQuery = {
response: CompliancePageMailingListPageQuery$data;
variables: CompliancePageMailingListPageQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "organizationId"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "organizationId"
}
],
v2 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "replyTo",
"storageKey": null
},
v5 = [
{
"kind": "Literal",
"name": "first",
"value": 20
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageMailingListPageQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"kind": "RequiredField",
"field": {
"alias": "compliancePage",
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "MailingList",
"kind": "LinkedField",
"name": "mailingList",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/)
],
"storageKey": null
},
{
"args": null,
"kind": "FragmentSpread",
"name": "CompliancePageMailingListFragment"
}
],
"storageKey": null
},
"action": "THROW"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageMailingListPageQuery",
"selections": [
{
"alias": "organization",
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v2/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": "compliancePage",
"args": null,
"concreteType": "TrustCenter",
"kind": "LinkedField",
"name": "trustCenter",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"concreteType": "MailingList",
"kind": "LinkedField",
"name": "mailingList",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
{
"alias": null,
"args": (v5/*: any*/),
"concreteType": "MailingListSubscriberConnection",
"kind": "LinkedField",
"name": "subscribers",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriberEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriber",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
(v2/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": "subscribers(first:20)"
},
{
"alias": null,
"args": (v5/*: any*/),
"filters": null,
"handle": "connection",
"key": "CompliancePageMailingList_subscribers",
"kind": "LinkedHandle",
"name": "subscribers"
}
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
},
(v3/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "8eb57a5cf8f8b1294da6aee3228176f3",
"id": null,
"metadata": {},
"name": "CompliancePageMailingListPageQuery",
"operationKind": "query",
"text": "query CompliancePageMailingListPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n compliancePage: trustCenter {\n id\n mailingList {\n id\n replyTo\n }\n ...CompliancePageMailingListFragment\n }\n }\n id\n }\n}\n\nfragment CompliancePageMailingListFragment on TrustCenter {\n mailingList {\n id\n subscribers(first: 20) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n fullName\n email\n status\n createdAt\n __typename\n }\n cursor\n }\n }\n }\n id\n}\n"
}
};
})();
(node as any).hash = "0df7b2a8b9f0d2f421f354bd56773e43";
export default node;

View File

@@ -0,0 +1,114 @@
/**
* @generated SignedSource<<c5e3b54834d5bcf46746de91d32e964a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UpdateMailingListInput = {
id: string;
replyTo?: string | null | undefined;
};
export type CompliancePageMailingListPage_updateMailingListMutation$variables = {
input: UpdateMailingListInput;
};
export type CompliancePageMailingListPage_updateMailingListMutation$data = {
readonly updateMailingList: {
readonly mailingList: {
readonly id: string;
readonly replyTo: string | null | undefined;
};
};
};
export type CompliancePageMailingListPage_updateMailingListMutation = {
response: CompliancePageMailingListPage_updateMailingListMutation$data;
variables: CompliancePageMailingListPage_updateMailingListMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateMailingListPayload",
"kind": "LinkedField",
"name": "updateMailingList",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingList",
"kind": "LinkedField",
"name": "mailingList",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "replyTo",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageMailingListPage_updateMailingListMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageMailingListPage_updateMailingListMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "30b67540b107297075df95ee044714ea",
"id": null,
"metadata": {},
"name": "CompliancePageMailingListPage_updateMailingListMutation",
"operationKind": "mutation",
"text": "mutation CompliancePageMailingListPage_updateMailingListMutation(\n $input: UpdateMailingListInput!\n) {\n updateMailingList(input: $input) {\n mailingList {\n id\n replyTo\n }\n }\n}\n"
}
};
})();
(node as any).hash = "0a1e6524623210147ba5db2affec456c";
export default node;

View File

@@ -0,0 +1,275 @@
/**
* @generated SignedSource<<88648e469580db301e2d4105dae6a072>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type CompliancePageMailingListQuery$variables = {
after?: string | null | undefined;
first?: number | null | undefined;
id: string;
};
export type CompliancePageMailingListQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"CompliancePageMailingListFragment">;
};
};
export type CompliancePageMailingListQuery = {
response: CompliancePageMailingListQuery$data;
variables: CompliancePageMailingListQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": 20,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
}
],
v1 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v2 = [
{
"kind": "Variable",
"name": "after",
"variableName": "after"
},
{
"kind": "Variable",
"name": "first",
"variableName": "first"
}
],
v3 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v4 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "CompliancePageMailingListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": (v2/*: any*/),
"kind": "FragmentSpread",
"name": "CompliancePageMailingListFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "CompliancePageMailingListQuery",
"selections": [
{
"alias": null,
"args": (v1/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v3/*: any*/),
(v4/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingList",
"kind": "LinkedField",
"name": "mailingList",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "MailingListSubscriberConnection",
"kind": "LinkedField",
"name": "subscribers",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriberEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriber",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v4/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
(v3/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
{
"alias": null,
"args": (v2/*: any*/),
"filters": null,
"handle": "connection",
"key": "CompliancePageMailingList_subscribers",
"kind": "LinkedHandle",
"name": "subscribers"
}
],
"storageKey": null
}
],
"type": "TrustCenter",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "409500a631f678d3b173a2feae512f71",
"id": null,
"metadata": {},
"name": "CompliancePageMailingListQuery",
"operationKind": "query",
"text": "query CompliancePageMailingListQuery(\n $after: CursorKey = null\n $first: Int = 20\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...CompliancePageMailingListFragment_2HEEH6\n id\n }\n}\n\nfragment CompliancePageMailingListFragment_2HEEH6 on TrustCenter {\n mailingList {\n id\n subscribers(first: $first, after: $after) {\n pageInfo {\n hasNextPage\n endCursor\n }\n edges {\n node {\n id\n fullName\n email\n status\n createdAt\n __typename\n }\n cursor\n }\n }\n }\n id\n}\n"
}
};
})();
(node as any).hash = "b1828ed7ded38bb3e3e150b0875ea0a6";
export default node;

View File

@@ -0,0 +1,201 @@
/**
* @generated SignedSource<<61ab320d87e6fb43d245dc070871a160>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type MailingListSubscriberStatus = "CONFIRMED" | "PENDING";
export type CreateMailingListSubscriberInput = {
email: string;
fullName: string;
mailingListId: string;
};
export type NewCompliancePageSubscriberDialogMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateMailingListSubscriberInput;
};
export type NewCompliancePageSubscriberDialogMutation$data = {
readonly createMailingListSubscriber: {
readonly mailingListSubscriberEdge: {
readonly cursor: string;
readonly node: {
readonly createdAt: string;
readonly email: string;
readonly fullName: string;
readonly id: string;
readonly status: MailingListSubscriberStatus;
};
};
};
};
export type NewCompliancePageSubscriberDialogMutation = {
response: NewCompliancePageSubscriberDialogMutation$data;
variables: NewCompliancePageSubscriberDialogMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "connections"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
},
v2 = [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
v3 = {
"alias": null,
"args": null,
"concreteType": "MailingListSubscriberEdge",
"kind": "LinkedField",
"name": "mailingListSubscriberEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriber",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "status",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "NewCompliancePageSubscriberDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateMailingListSubscriberPayload",
"kind": "LinkedField",
"name": "createMailingListSubscriber",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "NewCompliancePageSubscriberDialogMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateMailingListSubscriberPayload",
"kind": "LinkedField",
"name": "createMailingListSubscriber",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "prependEdge",
"key": "",
"kind": "LinkedHandle",
"name": "mailingListSubscriberEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "6029b34056e1e0477db8bec0080b4ad1",
"id": null,
"metadata": {},
"name": "NewCompliancePageSubscriberDialogMutation",
"operationKind": "mutation",
"text": "mutation NewCompliancePageSubscriberDialogMutation(\n $input: CreateMailingListSubscriberInput!\n) {\n createMailingListSubscriber(input: $input) {\n mailingListSubscriberEdge {\n cursor\n node {\n id\n fullName\n email\n status\n createdAt\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "fae262a9df20589ff2923f631692a6ce";
export default node;

View File

@@ -1,6 +1,6 @@
import { usePageTitle } from "@probo/hooks";
import { useTranslate } from "@probo/i18n";
import { Badge, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPencil, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui";
import { Badge, IconBell2, IconCheckmark1, IconFolder2, IconMedal, IconPageTextLine, IconPencil, IconPeopleAdd, IconSettingsGear2, IconStore, PageHeader, TabLink, Tabs } from "@probo/ui";
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { Outlet } from "react-router";
import { graphql } from "relay-runtime";
@@ -84,6 +84,10 @@ export function CompliancePageLayout(props: { queryRef: PreloadedQuery<Complianc
<IconPeopleAdd className="size-4" />
{__("Access")}
</TabLink>
<TabLink to={`/organizations/${organizationId}/compliance-page/mailing-list`}>
<IconBell2 className="size-4" />
{__("Mailing List")}
</TabLink>
</Tabs>
<Outlet />

View File

@@ -0,0 +1,147 @@
import { useTranslate } from "@probo/i18n";
import { Button, Card, Field, IconPlusLarge, Spinner, useDialogRef } from "@probo/ui";
import { useState } from "react";
import { type PreloadedQuery, usePreloadedQuery } from "react-relay";
import { ConnectionHandler, graphql } from "relay-runtime";
import type { CompliancePageMailingListPage_updateMailingListMutation } from "#/__generated__/core/CompliancePageMailingListPage_updateMailingListMutation.graphql";
import type { CompliancePageMailingListPageQuery } from "#/__generated__/core/CompliancePageMailingListPageQuery.graphql";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
import { CompliancePageMailingList } from "./_components/CompliancePageMailingList";
import { NewCompliancePageSubscriberDialog } from "./_components/NewCompliancePageSubscriberDialog";
export const compliancePageMailingListPageQuery = graphql`
query CompliancePageMailingListPageQuery($organizationId: ID!) {
organization: node(id: $organizationId) {
__typename
... on Organization {
compliancePage: trustCenter @required(action: THROW) {
id
mailingList {
id
replyTo
}
...CompliancePageMailingListFragment
}
}
}
}
`;
const updateMailingListMutation = graphql`
mutation CompliancePageMailingListPage_updateMailingListMutation($input: UpdateMailingListInput!) {
updateMailingList(input: $input) {
mailingList {
id
replyTo
}
}
}
`;
export function CompliancePageMailingListPage(props: {
queryRef: PreloadedQuery<CompliancePageMailingListPageQuery>;
}) {
const { queryRef } = props;
const { __ } = useTranslate();
const dialogRef = useDialogRef();
const { organization } = usePreloadedQuery<CompliancePageMailingListPageQuery>(
compliancePageMailingListPageQuery,
queryRef,
);
if (organization.__typename !== "Organization") {
throw new Error("invalid type for node");
}
const mailingList = organization.compliancePage.mailingList;
const mailingListId = mailingList?.id;
const connectionId = mailingListId
? ConnectionHandler.getConnectionID(mailingListId, "CompliancePageMailingList_subscribers")
: null;
const [replyTo, setReplyTo] = useState(mailingList?.replyTo ?? "");
const [updateMailingList, isUpdating] = useMutationWithToasts<CompliancePageMailingListPage_updateMailingListMutation>(
updateMailingListMutation,
{
successMessage: __("Mailing list updated successfully"),
errorMessage: __("Failed to update mailing list"),
},
);
const handleSaveReplyTo = () => {
if (!mailingListId) return;
void updateMailingList({
variables: {
input: {
id: mailingListId,
replyTo: replyTo.trim() || null,
},
},
});
};
return (
<div className="space-y-6">
{mailingListId && (
<Card className="p-6 space-y-4">
<div>
<h3 className="text-base font-medium">{__("Settings")}</h3>
<p className="text-sm text-txt-tertiary">
{__("Configure how your mailing list behaves")}
</p>
</div>
<div className="flex items-end gap-3">
<div className="flex-1">
<Field
label={__("Reply-to email")}
type="email"
placeholder={__("security@example.com")}
value={replyTo}
onChange={e => setReplyTo(e.target.value)}
/>
</div>
<Button
onClick={handleSaveReplyTo}
disabled={isUpdating}
className="shrink-0"
>
{isUpdating && <Spinner />}
{__("Save")}
</Button>
</div>
</Card>
)}
<div className="space-y-4">
<div className="flex items-center justify-between">
<div>
<h3 className="text-base font-medium">{__("Subscribers")}</h3>
<p className="text-sm text-txt-tertiary">
{__("People subscribed to receive security and compliance updates")}
</p>
</div>
{mailingListId && (
<Button icon={IconPlusLarge} onClick={() => dialogRef.current?.open()}>
{__("Add Subscriber")}
</Button>
)}
</div>
<CompliancePageMailingList fragmentRef={organization.compliancePage} />
</div>
{mailingListId && connectionId && (
<NewCompliancePageSubscriberDialog
ref={dialogRef}
mailingListId={mailingListId}
connectionId={connectionId}
/>
)}
</div>
);
}

View File

@@ -0,0 +1,37 @@
import { useEffect } from "react";
import { useQueryLoader } from "react-relay";
import type { CompliancePageMailingListPageQuery } from "#/__generated__/core/CompliancePageMailingListPageQuery.graphql";
import { LinkCardSkeleton } from "#/components/skeletons/LinkCardSkeleton";
import { useOrganizationId } from "#/hooks/useOrganizationId";
import { CoreRelayProvider } from "#/providers/CoreRelayProvider";
import {
CompliancePageMailingListPage,
compliancePageMailingListPageQuery,
} from "./CompliancePageMailingListPage";
function CompliancePageMailingListPageQueryLoader() {
const organizationId = useOrganizationId();
const [queryRef, loadQuery] = useQueryLoader<CompliancePageMailingListPageQuery>(
compliancePageMailingListPageQuery,
);
useEffect(() => {
if (!queryRef) {
loadQuery({ organizationId });
}
});
if (!queryRef) return <LinkCardSkeleton />;
return <CompliancePageMailingListPage queryRef={queryRef} />;
}
export default function CompliancePageMailingListPageLoader() {
return (
<CoreRelayProvider>
<CompliancePageMailingListPageQueryLoader />
</CoreRelayProvider>
);
}

View File

@@ -0,0 +1,160 @@
import { useTranslate } from "@probo/i18n";
import { Badge, Button, IconChevronDown, IconTrashCan, Spinner, Table, Tbody, Td, Th, Thead, Tr } from "@probo/ui";
import { usePaginationFragment } from "react-relay";
import { graphql } from "relay-runtime";
import type { CompliancePageMailingListDeleteMutation } from "#/__generated__/core/CompliancePageMailingListDeleteMutation.graphql";
import type { CompliancePageMailingListFragment$key } from "#/__generated__/core/CompliancePageMailingListFragment.graphql";
import type { CompliancePageMailingListQuery } from "#/__generated__/core/CompliancePageMailingListQuery.graphql";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
const deleteMutation = graphql`
mutation CompliancePageMailingListDeleteMutation(
$input: DeleteMailingListSubscriberInput!
$connections: [ID!]!
) {
deleteMailingListSubscriber(input: $input) {
deletedMailingListSubscriberId @deleteEdge(connections: $connections)
}
}
`;
const fragment = graphql`
fragment CompliancePageMailingListFragment on TrustCenter
@argumentDefinitions(
first: { type: Int, defaultValue: 20 }
after: { type: CursorKey, defaultValue: null }
)
@refetchable(queryName: "CompliancePageMailingListQuery") {
mailingList {
id
subscribers(
first: $first
after: $after
) @connection(key: "CompliancePageMailingList_subscribers") {
__id
pageInfo {
hasNextPage
endCursor
}
edges {
node {
id
fullName
email
status
createdAt
}
}
}
}
}
`;
export function CompliancePageMailingList(props: {
fragmentRef: CompliancePageMailingListFragment$key;
}) {
const { fragmentRef } = props;
const { __ } = useTranslate();
const {
data,
hasNext,
loadNext,
isLoadingNext,
} = usePaginationFragment<CompliancePageMailingListQuery, CompliancePageMailingListFragment$key>(
fragment,
fragmentRef,
);
const subscribers = data.mailingList?.subscribers;
const [deleteSubscriber, isDeleting] = useMutationWithToasts<CompliancePageMailingListDeleteMutation>(
deleteMutation,
{
successMessage: __("Subscriber removed successfully"),
errorMessage: __("Failed to delete subscriber"),
},
);
const handleDelete = (id: string) => {
if (!subscribers) return;
void deleteSubscriber({
variables: {
input: { id },
connections: [subscribers.__id],
},
});
};
return (
<>
{!subscribers || subscribers.edges.length === 0
? (
<Table>
<Tbody>
<Tr>
<Td className="text-center text-txt-tertiary py-8">
{__("No mailing list subscribers yet")}
</Td>
</Tr>
</Tbody>
</Table>
)
: (
<>
<Table>
<Thead>
<Tr>
<Th>{__("Name")}</Th>
<Th>{__("Email")}</Th>
<Th>{__("Status")}</Th>
<Th>{__("Subscribed on")}</Th>
<Th />
</Tr>
</Thead>
<Tbody>
{subscribers.edges.map(({ node: subscriber }) => (
<Tr key={subscriber.id}>
<Td>{subscriber.fullName}</Td>
<Td>{subscriber.email}</Td>
<Td>
<Badge
variant={subscriber.status === "CONFIRMED" ? "success" : "warning"}
>
{subscriber.status === "CONFIRMED" ? __("Confirmed") : __("Pending")}
</Badge>
</Td>
<Td className="text-txt-tertiary text-sm">
{new Date(subscriber.createdAt).toLocaleDateString()}
</Td>
<Td className="w-10">
<Button
variant="tertiary"
icon={IconTrashCan}
disabled={isDeleting}
onClick={() => handleDelete(subscriber.id)}
aria-label={__("Delete subscriber")}
/>
</Td>
</Tr>
))}
</Tbody>
</Table>
{hasNext && (
<Button
variant="tertiary"
onClick={() => loadNext(10)}
disabled={isLoadingNext}
className="mt-3 mx-auto"
icon={IconChevronDown}
>
{isLoadingNext && <Spinner />}
{__("Show More")}
</Button>
)}
</>
)}
</>
);
}

View File

@@ -0,0 +1,114 @@
import { useTranslate } from "@probo/i18n";
import { Button, Dialog, DialogContent, DialogFooter, type DialogRef, Field, Spinner } from "@probo/ui";
import { type DataID, graphql } from "relay-runtime";
import { z } from "zod";
import type { NewCompliancePageSubscriberDialogMutation } from "#/__generated__/core/NewCompliancePageSubscriberDialogMutation.graphql";
import { useFormWithSchema } from "#/hooks/useFormWithSchema";
import { useMutationWithToasts } from "#/hooks/useMutationWithToasts";
const createSubscriberMutation = graphql`
mutation NewCompliancePageSubscriberDialogMutation(
$input: CreateMailingListSubscriberInput!
$connections: [ID!]!
) {
createMailingListSubscriber(input: $input) {
mailingListSubscriberEdge @prependEdge(connections: $connections) {
cursor
node {
id
fullName
email
status
createdAt
}
}
}
}
`;
export function NewCompliancePageSubscriberDialog(props: {
mailingListId: string;
connectionId: DataID;
ref: DialogRef;
}) {
const { mailingListId, connectionId, ref } = props;
const { __ } = useTranslate();
const schema = z.object({
fullName: z.string().min(1, __("Full name is required")).trim(),
email: z
.string()
.min(1, __("Email is required"))
.trim()
.email(__("Please enter a valid email address")),
});
const form = useFormWithSchema(schema, {
defaultValues: { fullName: "", email: "" },
});
const [createSubscriber, isCreating] = useMutationWithToasts<NewCompliancePageSubscriberDialogMutation>(
createSubscriberMutation,
{
successMessage: __("Subscriber added successfully"),
errorMessage: __("Failed to add subscriber"),
},
);
const handleSubmit = async (data: z.infer<typeof schema>) => {
await createSubscriber({
variables: {
input: {
mailingListId,
fullName: data.fullName.trim(),
email: data.email.trim(),
},
connections: connectionId ? [connectionId] : [],
},
onCompleted: (_, errors) => {
if (errors?.length) return;
setTimeout(() => {
form.reset();
ref.current?.close();
}, 50);
setTimeout(() => {
form.reset();
}, 300);
},
});
};
return (
<Dialog ref={ref} title={__("Add Subscriber")}>
<form onSubmit={e => void form.handleSubmit(handleSubmit)(e)}>
<DialogContent padded className="space-y-6">
<p className="text-txt-secondary text-sm">
{__("Add a person to receive security and compliance updates")}
</p>
<Field
label={__("Full Name")}
required
error={form.formState.errors.fullName?.message}
{...form.register("fullName")}
placeholder={__("John Doe")}
/>
<Field
label={__("Email Address")}
required
error={form.formState.errors.email?.message}
type="email"
{...form.register("email")}
placeholder={__("john@example.com")}
/>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isCreating}>
{isCreating && <Spinner />}
{__("Add Subscriber")}
</Button>
</DialogFooter>
</form>
</Dialog>
);
}

View File

@@ -57,6 +57,11 @@ export const compliancePageRoutes = [
Fallback: LinkCardSkeleton,
Component: lazy(() => import("#/pages/organizations/compliance-page/access/CompliancePageAccessPageLoader")),
},
{
path: "mailing-list",
Fallback: LinkCardSkeleton,
Component: lazy(() => import("#/pages/organizations/compliance-page/mailing-list/CompliancePageMailingListPageLoader")),
},
],
},
];