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

@@ -5,6 +5,7 @@ import { UnAuthenticatedError } from "@probo/relay";
import {
Button,
Card,
IconBell2,
IconBlock,
IconLock,
IconMedal,
@@ -20,6 +21,8 @@ import type { TrustGraphCurrentQuery$data } from "#/queries/__generated__/TrustG
import { getPathPrefix } from "#/utils/pathPrefix";
import type { OrganizationSidebar_requestAllAccessesMutation } from "./__generated__/OrganizationSidebar_requestAllAccessesMutation.graphql";
import type { OrganizationSidebar_subscribeToMailingListMutation } from "./__generated__/OrganizationSidebar_subscribeToMailingListMutation.graphql";
import type { OrganizationSidebar_unsubscribeFromMailingListMutation } from "./__generated__/OrganizationSidebar_unsubscribeFromMailingListMutation.graphql";
import { FrameworkBadge } from "./FrameworkBadge";
const requestAllAccessesMutation = graphql`
@@ -32,11 +35,35 @@ const requestAllAccessesMutation = graphql`
}
`;
const subscribeToMailingListMutation = graphql`
mutation OrganizationSidebar_subscribeToMailingListMutation {
subscribeToMailingList {
subscription {
id
email
createdAt
updatedAt
}
}
}
`;
const unsubscribeFromMailingListMutation = graphql`
mutation OrganizationSidebar_unsubscribeFromMailingListMutation {
unsubscribeFromMailingList {
deletedMailingListSubscriberId @deleteRecord
}
}
`;
export function OrganizationSidebar({
trustCenter,
isAuthenticated,
}: {
trustCenter: TrustGraphCurrentQuery$data["currentTrustCenter"];
isAuthenticated: boolean;
}) {
const trustCenterId = trustCenter?.id;
const { __ } = useTranslate();
const { toast } = useToast();
const theme = useSystemTheme();
@@ -51,6 +78,16 @@ export function OrganizationSidebar({
requestAllAccessesMutation,
);
const [subscribeToMailingList, isSubscribing]
= useMutation<OrganizationSidebar_subscribeToMailingListMutation>(
subscribeToMailingListMutation,
);
const [unsubscribeFromMailingList, isUnsubscribing]
= useMutation<OrganizationSidebar_unsubscribeFromMailingListMutation>(
unsubscribeFromMailingListMutation,
);
const handleRequestAllAccesses = () => {
requestAllAccesses({
variables: {},
@@ -93,6 +130,71 @@ export function OrganizationSidebar({
});
};
const handleSubscribe = () => {
subscribeToMailingList({
variables: {},
updater: (store, data) => {
const subscription = data?.subscribeToMailingList?.subscription;
if (!subscription?.id || !trustCenterId) return;
const trustCenterRecord = store.get(trustCenterId);
if (!trustCenterRecord) return;
const subscriptionRecord = store.get(subscription.id);
if (!subscriptionRecord) return;
trustCenterRecord.setLinkedRecord(subscriptionRecord, "viewerSubscription");
},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(__("Cannot subscribe"), errors),
variant: "error",
});
return;
}
toast({
title: __("Subscribed"),
description: __("You will be notified of security updates."),
variant: "success",
});
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message ?? __("Cannot subscribe"),
variant: "error",
});
},
});
};
const handleUnsubscribe = () => {
unsubscribeFromMailingList({
variables: {},
onCompleted: (_, errors) => {
if (errors?.length) {
toast({
title: __("Error"),
description: formatError(__("Cannot unsubscribe"), errors),
variant: "error",
});
return;
}
toast({
title: __("Unsubscribed"),
description: __("You will no longer receive security updates."),
variant: "success",
});
},
onError: (error) => {
toast({
title: __("Error"),
description: error.message ?? __("Cannot unsubscribe"),
variant: "error",
});
},
});
};
if (!trustCenter) {
return null;
}
@@ -188,15 +290,43 @@ export function OrganizationSidebar({
</>
)}
<Button
disabled={isRequestingAccess}
variant="primary"
icon={IconLock}
className="w-full h-10"
onClick={handleRequestAllAccesses}
>
{__("Request access")}
</Button>
{/* Actions */}
<div className="space-y-2">
<Button
disabled={isRequestingAccess}
variant="primary"
icon={IconLock}
className="w-full h-10"
onClick={handleRequestAllAccesses}
>
{__("Request access")}
</Button>
{isAuthenticated && (
trustCenter.viewerSubscription
? (
<Button
disabled={isUnsubscribing}
variant="secondary"
icon={IconBell2}
className="w-full h-10"
onClick={handleUnsubscribe}
>
{__("Unsubscribe from updates")}
</Button>
)
: (
<Button
disabled={isSubscribing}
variant="secondary"
icon={IconBell2}
className="w-full h-10"
onClick={handleSubscribe}
>
{__("Subscribe to updates")}
</Button>
)
)}
</div>
</div>
</Card>
);

View File

@@ -0,0 +1,111 @@
/**
* @generated SignedSource<<80e6e1e3558d126472e7affb5b7b6cbd>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type OrganizationSidebar_subscribeToMailingListMutation$variables = Record<PropertyKey, never>;
export type OrganizationSidebar_subscribeToMailingListMutation$data = {
readonly subscribeToMailingList: {
readonly subscription: {
readonly createdAt: any;
readonly email: any;
readonly id: string;
readonly updatedAt: any;
};
};
};
export type OrganizationSidebar_subscribeToMailingListMutation = {
response: OrganizationSidebar_subscribeToMailingListMutation$data;
variables: OrganizationSidebar_subscribeToMailingListMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "SubscribeToMailingListPayload",
"kind": "LinkedField",
"name": "subscribeToMailingList",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "MailingListSubscriber",
"kind": "LinkedField",
"name": "subscription",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationSidebar_subscribeToMailingListMutation",
"selections": (v0/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "OrganizationSidebar_subscribeToMailingListMutation",
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "07d99ad1645ea1e8752678fd9b5e6748",
"id": null,
"metadata": {},
"name": "OrganizationSidebar_subscribeToMailingListMutation",
"operationKind": "mutation",
"text": "mutation OrganizationSidebar_subscribeToMailingListMutation {\n subscribeToMailingList {\n subscription {\n id\n email\n createdAt\n updatedAt\n }\n }\n}\n"
}
};
})();
(node as any).hash = "80d3e85f4a55a7de7b066959f4a457c8";
export default node;

View File

@@ -0,0 +1,96 @@
/**
* @generated SignedSource<<8c1211258e4480dcaa6fdf14364cb49b>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type OrganizationSidebar_unsubscribeFromMailingListMutation$variables = Record<PropertyKey, never>;
export type OrganizationSidebar_unsubscribeFromMailingListMutation$data = {
readonly unsubscribeFromMailingList: {
readonly deletedMailingListSubscriberId: string | null | undefined;
};
};
export type OrganizationSidebar_unsubscribeFromMailingListMutation = {
response: OrganizationSidebar_unsubscribeFromMailingListMutation$data;
variables: OrganizationSidebar_unsubscribeFromMailingListMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "deletedMailingListSubscriberId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "OrganizationSidebar_unsubscribeFromMailingListMutation",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "UnsubscribeFromMailingListPayload",
"kind": "LinkedField",
"name": "unsubscribeFromMailingList",
"plural": false,
"selections": [
(v0/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "OrganizationSidebar_unsubscribeFromMailingListMutation",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "UnsubscribeFromMailingListPayload",
"kind": "LinkedField",
"name": "unsubscribeFromMailingList",
"plural": false,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteRecord",
"key": "",
"kind": "ScalarHandle",
"name": "deletedMailingListSubscriberId"
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "1c6706f94348a5426c1e57d5fe99ab82",
"id": null,
"metadata": {},
"name": "OrganizationSidebar_unsubscribeFromMailingListMutation",
"operationKind": "mutation",
"text": "mutation OrganizationSidebar_unsubscribeFromMailingListMutation {\n unsubscribeFromMailingList {\n deletedMailingListSubscriberId\n }\n}\n"
}
};
})();
(node as any).hash = "7f3949f63166fc3bd23838a44fb94993";
export default node;