Make console buildable and relay-compilable

Signed-off-by: Émile Ré <nemile.re@gmail.com>
This commit is contained in:
Émile Ré
2025-12-17 19:23:40 +01:00
committed by Bryan Frimin
parent e61d72f15d
commit 87a05f0333
45 changed files with 233 additions and 7629 deletions

View File

@@ -1,154 +0,0 @@
import { useState, useEffect } from "react";
import { useLocation, useNavigate, Link } from "react-router";
import { Button, Field, useToast } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useMutation } from "react-relay";
import { graphql } from "relay-runtime";
import type { PayloadError } from "relay-runtime";
import { z } from "zod";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { usePageTitle } from "@probo/hooks";
import type { ConfirmEmailPageMutation } from "./__generated__/ConfirmEmailPageMutation.graphql";
const ConfirmEmailMutation = graphql`
mutation ConfirmEmailPageMutation($input: ConfirmEmailInput!) {
confirmEmail(input: $input) {
success
}
}
`;
const confirmEmailSchema = z.object({
token: z.string().min(1, "Please enter a confirmation token"),
});
export default function ConfirmEmailPage() {
const [isLoading, setIsLoading] = useState(false);
const [isConfirmed, setIsConfirmed] = useState(false);
const location = useLocation();
const navigate = useNavigate();
const { __ } = useTranslate();
const { toast } = useToast();
const [commitMutation] =
useMutation<ConfirmEmailPageMutation>(ConfirmEmailMutation);
const form = useFormWithSchema(confirmEmailSchema, {
defaultValues: {
token: "",
},
});
usePageTitle(__("Confirm Email"));
useEffect(() => {
const searchParams = new URLSearchParams(location.search);
const urlToken = searchParams.get("token");
if (urlToken) {
form.setValue("token", urlToken);
}
}, [location.search, form]);
const handleSubmit = form.handleSubmit(async (data) => {
setIsLoading(true);
try {
commitMutation({
variables: {
input: {
token: data.token.trim(),
},
},
onCompleted: (_response, errors: PayloadError[] | null) => {
if (errors) {
toast({
title: __("Error"),
description: errors[0]?.message || __("Failed to confirm email"),
variant: "error",
});
setIsLoading(false);
return;
}
setIsConfirmed(true);
toast({
title: __("Success"),
description: __("Your email has been confirmed successfully"),
variant: "success",
});
setIsLoading(false);
},
onError: (err) => {
toast({
title: __("Error"),
description: err.message || __("Failed to confirm email"),
variant: "error",
});
setIsLoading(false);
},
});
} catch (error) {
toast({
title: __("Error"),
description: error instanceof Error ? error.message : __("Failed to confirm email"),
variant: "error",
});
setIsLoading(false);
}
});
return (
<div className="space-y-6 w-full max-w-md mx-auto">
<div className="space-y-2 text-center">
<h1 className="text-3xl font-bold">{__("Email Confirmation")}</h1>
<p className="text-txt-tertiary">
{__("Confirm your email address to complete registration")}
</p>
</div>
{isConfirmed ? (
<div className="space-y-4 text-center">
<p className="text-green-600 dark:text-green-400">
{__("Your email has been confirmed successfully!")}
</p>
<Button onClick={() => navigate("/auth/login")} className="w-full">
{__("Proceed to Login")}
</Button>
</div>
) : (
<form onSubmit={handleSubmit} className="space-y-4">
<Field
label={__("Confirmation Token")}
type="text"
placeholder={__("Enter your confirmation token")}
{...form.register("token")}
error={form.formState.errors.token?.message}
disabled={isLoading}
help={__("The token has been automatically filled from the URL if available")}
/>
<Button
type="submit"
className="w-full"
disabled={isLoading}
>
{isLoading ? __("Confirming...") : __("Confirm Email")}
</Button>
</form>
)}
<div className="text-center">
{!isConfirmed && (
<p className="text-sm text-txt-tertiary">
<Link
to="/auth/login"
className="underline text-txt-primary hover:text-txt-secondary"
>
{__("Back to Login")}
</Link>
</p>
)}
</div>
</div>
);
}

View File

@@ -1,92 +0,0 @@
/**
* @generated SignedSource<<3de8e69abff0cf5f1000d93dde7a3031>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type ConfirmEmailInput = {
token: string;
};
export type ConfirmEmailPageMutation$variables = {
input: ConfirmEmailInput;
};
export type ConfirmEmailPageMutation$data = {
readonly confirmEmail: {
readonly success: boolean;
};
};
export type ConfirmEmailPageMutation = {
response: ConfirmEmailPageMutation$data;
variables: ConfirmEmailPageMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "ConfirmEmailPayload",
"kind": "LinkedField",
"name": "confirmEmail",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "ConfirmEmailPageMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "ConfirmEmailPageMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "b6bde3a559a4ecb70a519ffb7fa83330",
"id": null,
"metadata": {},
"name": "ConfirmEmailPageMutation",
"operationKind": "mutation",
"text": "mutation ConfirmEmailPageMutation(\n $input: ConfirmEmailInput!\n) {\n confirmEmail(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "e7f442b5acc6d912bf272ca2f5dc1ef1";
export default node;

View File

@@ -1,133 +0,0 @@
import { Button, Card, Field, PageHeader, useToast } from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { ConnectionHandler, graphql } from "relay-runtime";
import { useLazyLoadQuery, useMutation } from "react-relay";
import type { NewOrganizationPageQuery as NewOrganizationPageQueryType } from "./__generated__/NewOrganizationPageQuery.graphql";
import type { NewOrganizationPageMutation as NewOrganizationPageMutationType } from "./__generated__/NewOrganizationPageMutation.graphql";
import { useState, type FormEventHandler } from "react";
import { useNavigate } from "react-router";
import { formatError, type GraphQLError } from "@probo/helpers";
const createOrganizationMutation = graphql`
mutation NewOrganizationPageMutation(
$input: CreateOrganizationInput!
$connections: [ID!]!
) {
createOrganization(input: $input) {
organizationEdge @appendEdge(connections: $connections) {
node {
id
name
logoUrl
}
}
}
}
`;
const viewerQuery = graphql`
query NewOrganizationPageQuery {
viewer {
id
}
}
`;
export default function NewOrganizationPage() {
const { __ } = useTranslate();
const navigate = useNavigate();
const { toast } = useToast();
const [isFetching, setIsFetching] = useState(false);
const data = useLazyLoadQuery<NewOrganizationPageQueryType>(viewerQuery, {});
const [createOrganization] = useMutation<NewOrganizationPageMutationType>(
createOrganizationMutation
);
const handleSubmit: FormEventHandler<HTMLFormElement> = async (e) => {
e.preventDefault();
const formData = new FormData(e.currentTarget);
const name = formData.get("name")?.toString();
if (!name) {
toast({
title: __("Error"),
description: __("Name is required"),
variant: "error",
});
return;
}
setIsFetching(true);
createOrganization({
variables: {
input: {
name,
},
connections: [
ConnectionHandler.getConnectionID(
data.viewer.id,
"OrganizationsPage_organizations"
),
ConnectionHandler.getConnectionID(
data.viewer.id,
"MainLayout_OrganizationSelector_organizations"
),
],
},
onCompleted: (r) => {
setIsFetching(false);
const org = r.createOrganization.organizationEdge.node;
navigate(`/organizations/${org.id}`);
toast({
title: __("Success"),
description: __("Organization has been created successfully"),
variant: "success",
});
},
onError: (e: GraphQLError) => {
setIsFetching(false);
toast({
title: __("Error"),
description: formatError(__("Failed to create organization"), e),
variant: "error",
});
},
});
};
return (
<div className="space-y-6">
<PageHeader
title={__("Create Organization")}
description={__(
"Create a new organization to manage your compliance and security needs."
)}
/>
<Card padded asChild>
<form onSubmit={handleSubmit} className="space-y-4">
<h2 className="text-xl font-semibold mb-1">
{__("Organization Details")}
</h2>
<p className="text-txt-tertiary text-sm mb-4">
{__("Enter the basic information about your organization.")}
</p>
<Field
required
name="name"
type="text"
placeholder={__("Organization name")}
label={__("Organization name")}
help={__(
"The name of your organization as it will appear throughout the platform."
)}
/>
<Button disabled={isFetching} type="submit" className="w-full">
{__("Create Organization")}
</Button>
</form>
</Card>
</div>
);
}

View File

@@ -22,11 +22,11 @@ const organizationFragment = graphql`
fragment SettingsPageFragment on Organization {
id
name
...GeneralSettingsTabFragment
...MembersSettingsTabMembershipsFragment
...MembersSettingsTabInvitationsFragment
# ...GeneralSettingsTabFragment
# ...MembersSettingsTabMembershipsFragment
# ...MembersSettingsTabInvitationsFragment
...DomainSettingsTabFragment
...SAMLSettingsTabFragment
# ...SAMLSettingsTabFragment
}
`;

View File

@@ -1,174 +0,0 @@
/**
* @generated SignedSource<<b15b08fb2d1a0e4e289a9c3c5b9ec7ef>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type CreateOrganizationInput = {
name: string;
};
export type NewOrganizationPageMutation$variables = {
connections: ReadonlyArray<string>;
input: CreateOrganizationInput;
};
export type NewOrganizationPageMutation$data = {
readonly createOrganization: {
readonly organizationEdge: {
readonly node: {
readonly id: string;
readonly logoUrl: string | null | undefined;
readonly name: string;
};
};
};
};
export type NewOrganizationPageMutation = {
response: NewOrganizationPageMutation$data;
variables: NewOrganizationPageMutation$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": "OrganizationEdge",
"kind": "LinkedField",
"name": "organizationEdge",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "NewOrganizationPageMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateOrganizationPayload",
"kind": "LinkedField",
"name": "createOrganization",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "NewOrganizationPageMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "CreateOrganizationPayload",
"kind": "LinkedField",
"name": "createOrganization",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "appendEdge",
"key": "",
"kind": "LinkedHandle",
"name": "organizationEdge",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "917f8c27b3faacda07aa6d62e6123677",
"id": null,
"metadata": {},
"name": "NewOrganizationPageMutation",
"operationKind": "mutation",
"text": "mutation NewOrganizationPageMutation(\n $input: CreateOrganizationInput!\n) {\n createOrganization(input: $input) {\n organizationEdge {\n node {\n id\n name\n logoUrl\n }\n }\n }\n}\n"
}
};
})();
(node as any).hash = "b88831f98e1414c16a18381ab4aeaa38";
export default node;

View File

@@ -1,74 +0,0 @@
/**
* @generated SignedSource<<83489e54447230c6d654a6baa9fb3e9d>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type NewOrganizationPageQuery$variables = Record<PropertyKey, never>;
export type NewOrganizationPageQuery$data = {
readonly viewer: {
readonly id: string;
};
};
export type NewOrganizationPageQuery = {
response: NewOrganizationPageQuery$data;
variables: NewOrganizationPageQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "NewOrganizationPageQuery",
"selections": (v0/*: any*/),
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "NewOrganizationPageQuery",
"selections": (v0/*: any*/)
},
"params": {
"cacheID": "3a624ea532d0ad916c5e4bc883867f5e",
"id": null,
"metadata": {},
"name": "NewOrganizationPageQuery",
"operationKind": "query",
"text": "query NewOrganizationPageQuery {\n viewer {\n id\n }\n}\n"
}
};
})();
(node as any).hash = "c4d28895f85836c0698352ac737720ad";
export default node;

View File

@@ -1,5 +1,5 @@
/**
* @generated SignedSource<<552529ec3c732ce161f2dcb4a16179b9>>
* @generated SignedSource<<1b79ba90084eae4139b4fd4a31e5061b>>
* @lightSyntaxTransform
* @nogrep
*/
@@ -13,7 +13,7 @@ import { FragmentRefs } from "relay-runtime";
export type SettingsPageFragment$data = {
readonly id: string;
readonly name: string;
readonly " $fragmentSpreads": FragmentRefs<"DomainSettingsTabFragment" | "GeneralSettingsTabFragment" | "MembersSettingsTabInvitationsFragment" | "MembersSettingsTabMembershipsFragment" | "SAMLSettingsTabFragment">;
readonly " $fragmentSpreads": FragmentRefs<"DomainSettingsTabFragment">;
readonly " $fragmentType": "SettingsPageFragment";
};
export type SettingsPageFragment$key = {
@@ -41,36 +41,16 @@ const node: ReaderFragment = {
"name": "name",
"storageKey": null
},
{
"args": null,
"kind": "FragmentSpread",
"name": "GeneralSettingsTabFragment"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "MembersSettingsTabMembershipsFragment"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "MembersSettingsTabInvitationsFragment"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "DomainSettingsTabFragment"
},
{
"args": null,
"kind": "FragmentSpread",
"name": "SAMLSettingsTabFragment"
}
],
"type": "Organization",
"abstractKey": null
};
(node as any).hash = "4f0ec089ac8ee79935eb56c22de31eca";
(node as any).hash = "c00c2edf8bd9f8255c6bd6943bd4d445";
export default node;

View File

@@ -4,7 +4,6 @@ import {
loadQuery,
useFragment,
usePreloadedQuery,
useLazyLoadQuery,
} from "react-relay";
import type { DocumentGraphNodeQuery } from "/hooks/graph/__generated__/DocumentGraphNodeQuery.graphql";
import {
@@ -19,7 +18,6 @@ import type {
} from "./__generated__/DocumentDetailPageDocumentFragment.graphql";
import type { DocumentDetailPageExportPDFMutation } from "./__generated__/DocumentDetailPageExportPDFMutation.graphql";
import type { DocumentDetailPageUpdateMutation } from "./__generated__/DocumentDetailPageUpdateMutation.graphql";
import type { DocumentDetailPageUserEmailQuery } from "./__generated__/DocumentDetailPageUserEmailQuery.graphql";
import { useTranslate } from "@probo/i18n";
import {
ActionDropdown,
@@ -178,15 +176,13 @@ const documentUpdateSchema = z.object({
classification: z.enum(documentClassifications),
});
const UserEmailQuery = graphql`
query DocumentDetailPageUserEmailQuery {
viewer {
user {
email
}
}
}
`;
// const UserEmailQuery = graphql`
// query DocumentDetailPageUserEmailQuery {
// viewer {
// email
// }
// }
// `;
export default function DocumentDetailPage(props: Props) {
const { versionId } = useParams<{ versionId?: string }>();
@@ -230,15 +226,19 @@ export default function DocumentDetailPage(props: Props) {
}
);
const userEmailData = useLazyLoadQuery<DocumentDetailPageUserEmailQuery>(UserEmailQuery, {});
const defaultEmail = userEmailData.viewer.user.email;
const [updateDocument, isUpdatingDocument] = useMutationWithToasts<DocumentDetailPageUpdateMutation>(
updateDocumentMutation,
{
successMessage: __("Document updated successfully."),
errorMessage: __("Failed to update document"),
}
);
// const userEmailData = useLazyLoadQuery<DocumentDetailPageUserEmailQuery>(
// UserEmailQuery,
// {}
// );
// const defaultEmail = userEmailData.viewer.user.email;
const [updateDocument, isUpdatingDocument] =
useMutationWithToasts<DocumentDetailPageUpdateMutation>(
updateDocumentMutation,
{
successMessage: __("Document updated successfully."),
errorMessage: __("Failed to update document"),
}
);
const versionConnectionId = document.versions.__id;
const { register, control, handleSubmit, reset } = useFormWithSchema(
@@ -443,7 +443,7 @@ export default function DocumentDetailPage(props: Props) {
ref={pdfDownloadDialogRef}
onDownload={handleDownloadPdf}
isLoading={isExporting}
defaultEmail={defaultEmail}
// defaultEmail={defaultEmail}
>
{null}
</PdfDownloadDialog>
@@ -499,15 +499,17 @@ export default function DocumentDetailPage(props: Props) {
{isDraft ? __("Edit draft document") : __("Create new draft")}
</DropdownItem>
)}
{isDraft && versions.length > 1 && isAuthorized("Document", "deleteDraftDocumentVersion") && (
<DropdownItem
onClick={handleDeleteDraft}
icon={IconTrashCan}
disabled={isDeletingDraft}
>
{__("Delete draft document")}
</DropdownItem>
)}
{isDraft &&
versions.length > 1 &&
isAuthorized("Document", "deleteDraftDocumentVersion") && (
<DropdownItem
onClick={handleDeleteDraft}
icon={IconTrashCan}
disabled={isDeletingDraft}
>
{__("Delete draft document")}
</DropdownItem>
)}
<DropdownItem
onClick={() => pdfDownloadDialogRef.current?.open()}
icon={IconArrowDown}
@@ -616,7 +618,10 @@ export default function DocumentDetailPage(props: Props) {
/>
</EditablePropertyContent>
) : (
<ReadOnlyPropertyContent onEdit={() => setIsEditingOwner(true)} canEdit={isAuthorized("Document", "updateDocument")}>
<ReadOnlyPropertyContent
onEdit={() => setIsEditingOwner(true)}
canEdit={isAuthorized("Document", "updateDocument")}
>
<Badge variant="highlight" size="md" className="gap-2">
<Avatar name={currentVersion.owner?.fullName ?? ""} />
{currentVersion.owner?.fullName}
@@ -643,7 +648,10 @@ export default function DocumentDetailPage(props: Props) {
</ControlledField>
</EditablePropertyContent>
) : (
<ReadOnlyPropertyContent onEdit={() => setIsEditingType(true)} canEdit={isAuthorized("Document", "updateDocument")}>
<ReadOnlyPropertyContent
onEdit={() => setIsEditingType(true)}
canEdit={isAuthorized("Document", "updateDocument")}
>
<div className="text-sm text-txt-secondary">
{getDocumentTypeLabel(__, document.documentType)}
</div>
@@ -674,7 +682,10 @@ export default function DocumentDetailPage(props: Props) {
canEdit={isAuthorized("Document", "updateDocument")}
>
<div className="text-sm text-txt-secondary">
{getDocumentClassificationLabel(__, currentVersion.classification)}
{getDocumentClassificationLabel(
__,
currentVersion.classification
)}
</div>
</ReadOnlyPropertyContent>
)}
@@ -749,7 +760,9 @@ function ReadOnlyPropertyContent({
return (
<div className="flex items-center justify-between gap-3">
{children}
{canEdit && <Button variant="quaternary" icon={IconPencil} onClick={onEdit} />}
{canEdit && (
<Button variant="quaternary" icon={IconPencil} onClick={onEdit} />
)}
</div>
);
}

View File

@@ -26,7 +26,6 @@ import {
useFragment,
usePaginationFragment,
usePreloadedQuery,
useLazyLoadQuery,
type PreloadedQuery,
} from "react-relay";
import { use, useRef } from "react";
@@ -56,7 +55,6 @@ import {
BulkExportDialog,
type BulkExportDialogRef,
} from "/components/documents/BulkExportDialog";
import type { DocumentsPageUserEmailQuery } from "./__generated__/DocumentsPageUserEmailQuery.graphql";
import { PermissionsContext } from "/providers/PermissionsContext";
const documentsFragment = graphql`
@@ -94,15 +92,15 @@ type Props = {
queryRef: PreloadedQuery<DocumentGraphListQuery>;
};
const UserEmailQuery = graphql`
query DocumentsPageUserEmailQuery {
viewer {
user {
email
}
}
}
`;
// const UserEmailQuery = graphql`
// query DocumentsPageUserEmailQuery {
// viewer {
// user {
// email
// }
// }
// }
// `;
export default function DocumentsPage(props: Props) {
const { __ } = useTranslate();
@@ -113,11 +111,11 @@ export default function DocumentsPage(props: Props) {
props.queryRef
).organization;
const userEmailData = useLazyLoadQuery<DocumentsPageUserEmailQuery>(
UserEmailQuery,
{}
);
const defaultEmail = userEmailData.viewer.user.email;
// const userEmailData = useLazyLoadQuery<DocumentsPageUserEmailQuery>(
// UserEmailQuery,
// {}
// );
// const defaultEmail = userEmailData.viewer.user.email;
const pagination = usePaginationFragment(
documentsFragment,
organization as DocumentsPageListFragment$key
@@ -137,7 +135,8 @@ export default function DocumentsPage(props: Props) {
usePageTitle(__("Documents"));
const hasAnyAction = isAuthorized("Document", "updateDocument") ||
const hasAnyAction =
isAuthorized("Document", "updateDocument") ||
isAuthorized("Document", "deleteDocument");
const handleSendSigningNotifications = () => {
@@ -210,7 +209,9 @@ export default function DocumentsPage(props: Props) {
{isAuthorized("Organization", "createDocument") && (
<CreateDocumentDialog
connection={connectionId}
trigger={<Button icon={IconPlusLarge}>{__("New document")}</Button>}
trigger={
<Button icon={IconPlusLarge}>{__("New document")}</Button>
}
/>
)}
</div>
@@ -289,7 +290,7 @@ export default function DocumentsPage(props: Props) {
ref={bulkExportDialogRef}
onExport={handleBulkExport}
isLoading={isBulkExporting}
defaultEmail={defaultEmail}
// defaultEmail={defaultEmail}
selectedCount={selection.length}
>
<Button

View File

@@ -1,120 +0,0 @@
/**
* @generated SignedSource<<b5ef7f2a5ceb3688743c82362f625a55>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DocumentDetailPageUserEmailQuery$variables = Record<PropertyKey, never>;
export type DocumentDetailPageUserEmailQuery$data = {
readonly viewer: {
readonly user: {
readonly email: any;
};
};
};
export type DocumentDetailPageUserEmailQuery = {
response: DocumentDetailPageUserEmailQuery$data;
variables: DocumentDetailPageUserEmailQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "DocumentDetailPageUserEmailQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v0/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "DocumentDetailPageUserEmailQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/)
],
"storageKey": null
},
(v1/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "9321ba3f2fb159e06a07bfa9b2d91922",
"id": null,
"metadata": {},
"name": "DocumentDetailPageUserEmailQuery",
"operationKind": "query",
"text": "query DocumentDetailPageUserEmailQuery {\n viewer {\n user {\n email\n id\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "b4ffc243e94463feab8fd6555a674c58";
export default node;

View File

@@ -1,120 +0,0 @@
/**
* @generated SignedSource<<959a59c16a4d27856820501f80379429>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DocumentsPageUserEmailQuery$variables = Record<PropertyKey, never>;
export type DocumentsPageUserEmailQuery$data = {
readonly viewer: {
readonly user: {
readonly email: any;
};
};
};
export type DocumentsPageUserEmailQuery = {
response: DocumentsPageUserEmailQuery$data;
variables: DocumentsPageUserEmailQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "DocumentsPageUserEmailQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v0/*: any*/)
],
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [],
"kind": "Operation",
"name": "DocumentsPageUserEmailQuery",
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Viewer",
"kind": "LinkedField",
"name": "viewer",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "User",
"kind": "LinkedField",
"name": "user",
"plural": false,
"selections": [
(v0/*: any*/),
(v1/*: any*/)
],
"storageKey": null
},
(v1/*: any*/)
],
"storageKey": null
}
]
},
"params": {
"cacheID": "d2d6cd91f55d59dc35d1c919f7760bac",
"id": null,
"metadata": {},
"name": "DocumentsPageUserEmailQuery",
"operationKind": "query",
"text": "query DocumentsPageUserEmailQuery {\n viewer {\n user {\n email\n id\n }\n id\n }\n}\n"
}
};
})();
(node as any).hash = "e8af56c98fa637c3e7487374fb2eaa0e";
export default node;

View File

@@ -1,463 +0,0 @@
import { useState, useRef, useEffect, type ChangeEventHandler, use } from "react";
import { useOutletContext, useNavigate } from "react-router";
import { useFragment, graphql } from "react-relay";
import {
Avatar,
Button,
Card,
Dialog,
DialogContent,
DialogFooter,
Field,
FileButton,
IconTrashCan,
Label,
Spinner,
Textarea,
useDialogRef,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { z } from "zod";
import type { GeneralSettingsTabFragment$key } from "./__generated__/GeneralSettingsTabFragment.graphql";
import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog";
import { useDeleteOrganizationMutation } from "/hooks/graph/OrganizationGraph";
import { PermissionsContext } from "/providers/PermissionsContext";
const generalSettingsTabFragment = graphql`
fragment GeneralSettingsTabFragment on Organization {
id
name
logoUrl
horizontalLogoUrl
description
websiteUrl
email
headquarterAddress
createdAt
updatedAt
}
`;
const updateOrganizationMutation = graphql`
mutation GeneralSettingsTab_UpdateMutation($input: UpdateOrganizationInput!) {
updateOrganization(input: $input) {
organization {
id
name
logoUrl
horizontalLogoUrl
description
websiteUrl
email
headquarterAddress
}
}
}
`;
const deleteHorizontalLogoMutation = graphql`
mutation GeneralSettingsTab_DeleteHorizontalLogoMutation(
$input: DeleteOrganizationHorizontalLogoInput!
) {
deleteOrganizationHorizontalLogo(input: $input) {
organization {
id
horizontalLogoUrl
}
}
}
`;
const organizationSchema = z.object({
name: z.string().min(1, "Organization name is required"),
description: z.string().optional(),
websiteUrl: z.string().optional(),
email: z.string().optional(),
headquarterAddress: z.string().optional(),
});
type OrganizationFormData = z.infer<typeof organizationSchema>;
type OutletContext = {
organization: GeneralSettingsTabFragment$key;
};
export default function GeneralSettingsTab() {
const { __ } = useTranslate();
const navigate = useNavigate();
const { organization: organizationKey } = useOutletContext<OutletContext>();
const organization = useFragment(generalSettingsTabFragment, organizationKey);
const deleteDialogRef = useDialogRef();
const { isAuthorized } = use(PermissionsContext);
const canUpdate = isAuthorized("Organization", "updateOrganization");
const canDelete = isAuthorized("Organization", "deleteOrganization");
const [logoPreview, setLogoPreview] = useState<string | null>(null);
const [horizontalLogoPreview, setHorizontalLogoPreview] = useState<
string | null
>(null);
const [updateOrganization, isUpdatingOrganization] = useMutationWithToasts(
updateOrganizationMutation,
{
successMessage: __("Organization updated successfully"),
errorMessage: __("Failed to update organization"),
}
);
const [deleteHorizontalLogo, isDeletingHorizontalLogo] =
useMutationWithToasts(deleteHorizontalLogoMutation, {
successMessage: __("Horizontal logo deleted successfully"),
errorMessage: __("Failed to delete horizontal logo"),
});
const [deleteOrganization, isDeletingOrganization] =
useDeleteOrganizationMutation();
const { formState, handleSubmit, register, reset } = useFormWithSchema(
organizationSchema,
{
defaultValues: {
name: organization.name || "",
description: organization.description || "",
websiteUrl: organization.websiteUrl || "",
email: organization.email || "",
headquarterAddress: organization.headquarterAddress || "",
},
}
);
const prevOrgDataRef = useRef({
name: organization.name,
description: organization.description,
websiteUrl: organization.websiteUrl,
email: organization.email,
headquarterAddress: organization.headquarterAddress,
});
useEffect(() => {
const prevData = prevOrgDataRef.current;
const currentData = {
name: organization.name,
description: organization.description,
websiteUrl: organization.websiteUrl,
email: organization.email,
headquarterAddress: organization.headquarterAddress,
};
if (JSON.stringify(prevData) !== JSON.stringify(currentData)) {
reset({
name: organization.name || "",
description: organization.description || "",
websiteUrl: organization.websiteUrl || "",
email: organization.email || "",
headquarterAddress: organization.headquarterAddress || "",
});
prevOrgDataRef.current = currentData;
}
}, [organization, reset]);
const onSubmit = handleSubmit((data: OrganizationFormData) => {
updateOrganization({
variables: {
input: {
organizationId: organization.id,
name: data.name,
description: data.description || null,
websiteUrl: data.websiteUrl || null,
email: data.email || null,
headquarterAddress: data.headquarterAddress || null,
},
},
});
});
const handleLogoChange: ChangeEventHandler<HTMLInputElement> = (e) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
setLogoPreview(reader.result as string);
};
reader.readAsDataURL(file);
updateOrganization({
variables: {
input: {
organizationId: organization.id,
logoFile: null,
},
},
uploadables: {
"input.logoFile": file,
},
onCompleted: () => {
setLogoPreview(null);
},
});
};
const handleHorizontalLogoChange: ChangeEventHandler<HTMLInputElement> = (
e
) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = () => {
setHorizontalLogoPreview(reader.result as string);
};
reader.readAsDataURL(file);
updateOrganization({
variables: {
input: {
organizationId: organization.id,
horizontalLogoFile: null,
},
},
uploadables: {
"input.horizontalLogoFile": file,
},
onCompleted: () => {
setHorizontalLogoPreview(null);
},
});
};
const handleDeleteHorizontalLogo = () => {
deleteHorizontalLogo({
variables: {
input: {
organizationId: organization.id,
},
},
onCompleted: () => {
deleteDialogRef.current?.close();
},
});
};
const handleDeleteOrganization = () => {
return deleteOrganization({
variables: {
input: {
organizationId: organization.id,
},
connections: [],
},
onSuccess: () => {
navigate("/", { replace: true });
},
});
};
return (
<form onSubmit={onSubmit} className="space-y-6">
<div className="space-y-4">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">
{__("Organization details")}
</h2>
{formState.isSubmitting && <Spinner />}
</div>
<Card padded className="space-y-4">
<div>
<Label>{__("Organization logo")}</Label>
<div className="flex w-max items-center gap-4">
<Avatar
src={logoPreview || organization.logoUrl}
name={organization.name}
size="xl"
/>
{canUpdate && (
<FileButton
disabled={formState.isSubmitting || isUpdatingOrganization}
onChange={handleLogoChange}
variant="secondary"
className="ml-auto"
accept="image/png,image/jpeg,image/jpg,image/svg+xml"
>
{isUpdatingOrganization
? __("Uploading...")
: __("Change logo")}
</FileButton>
)}
</div>
</div>
<div>
<Label>{__("Horizontal logo")}</Label>
<p className="text-sm text-txt-tertiary mb-2">
{__(
"Upload a horizontal version of your logo for use in documents"
)}
</p>
<div className="flex items-center gap-4">
{(horizontalLogoPreview || organization.horizontalLogoUrl) && (
<div className="border border-border-solid rounded-md p-4 bg-surface-secondary">
<img
src={
horizontalLogoPreview ||
organization.horizontalLogoUrl ||
undefined
}
alt={__("Horizontal logo")}
className="h-12 max-w-xs object-contain"
/>
</div>
)}
{canUpdate && (
<FileButton
disabled={formState.isSubmitting || isUpdatingOrganization}
onChange={handleHorizontalLogoChange}
variant="secondary"
accept="image/png,image/jpeg,image/jpg,image/svg+xml"
>
{isUpdatingOrganization
? __("Uploading...")
: horizontalLogoPreview || organization.horizontalLogoUrl
? __("Change horizontal logo")
: __("Upload horizontal logo")}
</FileButton>
)}
{canUpdate && organization.horizontalLogoUrl && (
<Dialog
ref={deleteDialogRef}
trigger={
<Button
type="button"
variant="quaternary"
icon={IconTrashCan}
aria-label={__("Delete horizontal logo")}
className="text-red-600 hover:text-red-700"
/>
}
title={__("Delete Horizontal Logo")}
className="max-w-md"
>
<DialogContent padded>
<p className="text-txt-secondary">
{__(
"Are you sure you want to delete the horizontal logo?"
)}
</p>
<p className="text-txt-secondary mt-2">
{__("This action cannot be undone.")}
</p>
</DialogContent>
<DialogFooter>
<Button
variant="danger"
onClick={handleDeleteHorizontalLogo}
disabled={isDeletingHorizontalLogo}
icon={isDeletingHorizontalLogo ? Spinner : IconTrashCan}
>
{isDeletingHorizontalLogo
? __("Deleting...")
: __("Delete")}
</Button>
</DialogFooter>
</Dialog>
)}
</div>
</div>
<Field
{...register("name")}
readOnly={formState.isSubmitting || !canUpdate}
name="name"
type="text"
label={__("Organization name")}
placeholder={__("Organization name")}
/>
<div>
<Label>{__("Description")}</Label>
<Textarea
{...register("description")}
readOnly={formState.isSubmitting || !canUpdate}
name="description"
placeholder={__("Brief description of your organization")}
rows={3}
/>
</div>
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<Field
{...register("websiteUrl")}
readOnly={formState.isSubmitting || !canUpdate}
name="websiteUrl"
type="url"
label={__("Website URL")}
placeholder={__("https://example.com")}
/>
<Field
{...register("email")}
readOnly={formState.isSubmitting || !canUpdate}
name="email"
type="email"
label={__("Email")}
placeholder={__("contact@example.com")}
/>
</div>
<div>
<Label>{__("Headquarter Address")}</Label>
<Textarea
{...register("headquarterAddress")}
readOnly={formState.isSubmitting || !canUpdate}
name="headquarterAddress"
placeholder={__("123 Main St, City, Country")}
/>
</div>
{formState.isDirty && canUpdate && (
<div className="flex justify-end pt-6">
<Button
type="submit"
disabled={formState.isSubmitting || isUpdatingOrganization}
>
{formState.isSubmitting || isUpdatingOrganization
? __("Updating...")
: __("Update Organization")}
</Button>
</div>
)}
</Card>
</div>
{canDelete && (
<div className="space-y-4 mt-12">
<h2 className="text-base font-medium text-red-600">
{__("Danger Zone")}
</h2>
<Card padded className="border-red-200 flex items-center gap-3">
<div className="mr-auto">
<h3 className="text-base font-semibold text-red-700">
{__("Delete Organization")}
</h3>
<p className="text-sm text-txt-tertiary">
{__("Permanently delete this organization and all its data.")}{" "}
<span className="text-red-600 font-medium">
{__("This action cannot be undone.")}
</span>
</p>
</div>
<DeleteOrganizationDialog
organizationName={organization.name}
onConfirm={handleDeleteOrganization}
isDeleting={isDeletingOrganization}
>
<Button
variant="danger"
icon={IconTrashCan}
disabled={isDeletingOrganization}
>
{__("Delete Organization")}
</Button>
</DeleteOrganizationDialog>
</Card>
</div>
)}
</form>
);
}

View File

@@ -1,592 +0,0 @@
import { useState, Suspense, use } from "react";
import { useOutletContext } from "react-router";
import { usePaginationFragment, graphql } from "react-relay";
import {
Badge,
Button,
Card,
Dialog,
DialogContent,
DialogFooter,
Field,
IconPencil,
IconTrashCan,
Option,
Select,
Spinner,
TabBadge,
TabItem,
Tabs,
Tbody,
Td,
Th,
Thead,
Tr,
useConfirm,
useDialogRef,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { SortableTable, SortableTh } from "/components/SortableTable";
import { InviteUserDialog } from "/components/organizations/InviteUserDialog";
import { useMutationWithToasts } from "/hooks/useMutationWithToasts";
import { getAssignableRoles, sprintf } from "@probo/helpers";
import clsx from "clsx";
import type { NodeOf } from "/types";
import type {
MembersSettingsTabMembershipsFragment$data,
MembersSettingsTabMembershipsFragment$key
} from "./__generated__/MembersSettingsTabMembershipsFragment.graphql";
import type {
MembersSettingsTabInvitationsFragment$data,
MembersSettingsTabInvitationsFragment$key
} from "./__generated__/MembersSettingsTabInvitationsFragment.graphql";
import { PermissionsContext } from "/providers/PermissionsContext";
const paginatedMembershipsFragment = graphql`
fragment MembersSettingsTabMembershipsFragment on Organization
@refetchable(queryName: "MembersSettingsTabMembershipsRefetchQuery")
@argumentDefinitions(
first: { type: "Int", defaultValue: 20 }
order: { type: "MembershipOrder", defaultValue: { direction: ASC, field: CREATED_AT } }
after: { type: "CursorKey", defaultValue: null }
before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null }
) {
memberships(
first: $first
after: $after
last: $last
before: $before
orderBy: $order
) @connection(key: "MembersSettingsTabMemberships_memberships") {
__id
totalCount
edges {
node {
id
fullName
emailAddress
role
authMethod
createdAt
}
}
}
}
`;
const paginatedInvitationsFragment = graphql`
fragment MembersSettingsTabInvitationsFragment on Organization
@refetchable(queryName: "MembersSettingsTabInvitationsRefetchQuery")
@argumentDefinitions(
first: { type: "Int", defaultValue: 20 }
order: { type: "InvitationOrder", defaultValue: { direction: ASC, field: CREATED_AT } }
after: { type: "CursorKey", defaultValue: null }
before: { type: "CursorKey", defaultValue: null }
last: { type: "Int", defaultValue: null }
) {
invitations(
first: $first
after: $after
last: $last
before: $before
orderBy: $order
) @connection(key: "MembersSettingsTabInvitations_invitations") {
__id
totalCount
edges {
node {
id
fullName
email
role
status
createdAt
expiresAt
acceptedAt
}
}
}
}
`;
const removeMemberMutation = graphql`
mutation MembersSettingsTab_RemoveMemberMutation(
$input: RemoveMemberInput!
$connections: [ID!]!
) {
removeMember(input: $input) {
deletedMemberId @deleteEdge(connections: $connections)
}
}
`;
const updateMembershipMutation = graphql`
mutation MembersSettingsTab_UpdateMembershipMutation(
$input: UpdateMembershipInput!
) {
updateMembership(input: $input) {
membership {
id
role
}
}
}
`;
const deleteInvitationMutation = graphql`
mutation MembersSettingsTab_DeleteInvitationMutation(
$input: DeleteInvitationInput!
$connections: [ID!]!
) {
deleteInvitation(input: $input) {
deletedInvitationId @deleteEdge(connections: $connections)
}
}
`;
type OutletContext = {
organization: MembersSettingsTabMembershipsFragment$key & MembersSettingsTabInvitationsFragment$key & { id: string };
};
export default function MembersSettingsTab() {
const { __ } = useTranslate();
const { organization: organizationKey } = useOutletContext<OutletContext>();
const { isAuthorized } = use(PermissionsContext);
const membershipsPagination = usePaginationFragment(
paginatedMembershipsFragment,
organizationKey as MembersSettingsTabMembershipsFragment$key
);
const invitationsPagination = usePaginationFragment(
paginatedInvitationsFragment,
organizationKey as MembersSettingsTabInvitationsFragment$key
);
const refetchMemberships = () => {
membershipsPagination.refetch({}, { fetchPolicy: 'network-only' });
};
const refetchInvitations = () => {
invitationsPagination.refetch({}, { fetchPolicy: 'network-only' });
};
const memberships = membershipsPagination.data.memberships?.edges.map((edge) => edge.node) || [];
const invitations = invitationsPagination.data.invitations?.edges.map((edge) => edge.node) || [];
const [activeTab, setActiveTab] = useState<"memberships" | "invitations">("memberships");
return (
<div className="space-y-2">
<div className="flex items-center justify-between">
<h2 className="text-base font-medium">{__("Workspace members")}</h2>
{isAuthorized("Organization", "inviteUser") && (
<InviteUserDialog
connectionId={invitationsPagination.data.invitations?.__id}
onRefetch={refetchInvitations}
>
<Button variant="secondary">{__("Invite member")}</Button>
</InviteUserDialog>
)}
</div>
<Tabs>
<TabItem
active={activeTab === "memberships"}
onClick={() => setActiveTab("memberships")}
>
{__("Members")}
{(membershipsPagination.data.memberships?.totalCount || 0) > 0 && (
<TabBadge>{membershipsPagination.data.memberships?.totalCount}</TabBadge>
)}
</TabItem>
<TabItem
active={activeTab === "invitations"}
onClick={() => setActiveTab("invitations")}
>
{__("Invitations")}
{(invitationsPagination.data.invitations?.totalCount || 0) > 0 && (
<TabBadge>{invitationsPagination.data.invitations?.totalCount}</TabBadge>
)}
</TabItem>
</Tabs>
<Card>
<div className="px-6 pb-6 pt-6">
{activeTab === "memberships" && (
<SortableTable
{...membershipsPagination}
refetch={({ order }: { order: { direction: string; field: string } }) => {
membershipsPagination.refetch({
order: {
direction: order.direction as "ASC" | "DESC",
field: order.field as "CREATED_AT" | "FULL_NAME" | "EMAIL_ADDRESS" | "ROLE"
}
});
}}
pageSize={20}
>
<Thead>
<Tr>
<SortableTh field="FULL_NAME">{__("Name")}</SortableTh>
<SortableTh field="EMAIL_ADDRESS">{__("Email")}</SortableTh>
<SortableTh field="ROLE">{__("Role")}</SortableTh>
<SortableTh field="CREATED_AT">{__("Joined")}</SortableTh>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{memberships.length === 0 ? (
<Tr>
<Td colSpan={5} className="text-center text-txt-secondary">
{__("No members")}
</Td>
</Tr>
) : (
memberships.map((membership) => (
<MembershipRow
key={membership.id}
membership={membership}
connectionId={membershipsPagination.data.memberships?.__id}
organizationId={(organizationKey as { id: string }).id}
onRefetch={refetchMemberships}
/>
))
)}
</Tbody>
</SortableTable>
)}
{activeTab === "invitations" && (
<SortableTable
{...invitationsPagination}
refetch={({ order }: { order: { direction: string; field: string } }) => {
invitationsPagination.refetch({
order: {
direction: order.direction as "ASC" | "DESC",
field: order.field as "CREATED_AT" | "EXPIRES_AT" | "FULL_NAME" | "EMAIL" | "ROLE" | "STATUS" | "ACCEPTED_AT"
}
});
}}
pageSize={20}
>
<Thead>
<Tr>
<SortableTh field="FULL_NAME">{__("Name")}</SortableTh>
<SortableTh field="EMAIL">{__("Email")}</SortableTh>
<SortableTh field="ROLE">{__("Role")}</SortableTh>
<SortableTh field="CREATED_AT">{__("Invited")}</SortableTh>
<Th>{__("Status")}</Th>
<SortableTh field="ACCEPTED_AT">{__("Accepted at")}</SortableTh>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{invitations.length === 0 ? (
<Tr>
<Td colSpan={7} className="text-center text-txt-secondary">
{__("No invitations")}
</Td>
</Tr>
) : (
invitations.map((invitation) => (
<InvitationRow
key={invitation.id}
invitation={invitation}
connectionId={invitationsPagination.data.invitations?.__id}
organizationId={(organizationKey as { id: string }).id}
onRefetch={refetchInvitations}
/>
))
)}
</Tbody>
</SortableTable>
)}
</div>
</Card>
</div>
);
}
function InvitationRow(props: {
invitation: NodeOf<MembersSettingsTabInvitationsFragment$data["invitations"]>;
connectionId?: string;
organizationId: string;
onRefetch: () => void;
}) {
const { __ } = useTranslate();
const confirm = useConfirm();
const { isAuthorized } = use(PermissionsContext);
const [deleteInvitation, isDeleting] = useMutationWithToasts(
deleteInvitationMutation,
{
successMessage: __("Invitation deleted successfully"),
errorMessage: __("Failed to delete invitation"),
}
);
const onDelete = () => {
confirm(
() => {
return deleteInvitation({
variables: {
input: {
invitationId: props.invitation.id,
},
connections: props.connectionId ? [props.connectionId] : [],
},
onCompleted: () => {
props.onRefetch();
},
});
},
{
message: sprintf(
__("Are you sure you want to delete the invitation for %s?"),
props.invitation.fullName
),
}
);
};
return (
<Tr className={clsx(isDeleting && "opacity-60 pointer-events-none")}>
<Td>
<div className="font-semibold">{props.invitation.fullName}</div>
</Td>
<Td>{props.invitation.email}</Td>
<Td>
<Badge>{props.invitation.role}</Badge>
</Td>
<Td>{new Date(props.invitation.createdAt).toLocaleDateString()}</Td>
<Td>
{props.invitation.status === "ACCEPTED" ? (
<Badge variant="success">{__("Accepted")}</Badge>
) : props.invitation.status === "EXPIRED" ? (
<Badge variant="danger">{__("Expired")}</Badge>
) : (
<Badge variant="warning">{__("Pending")}</Badge>
)}
</Td>
<Td>
{props.invitation.acceptedAt ? new Date(props.invitation.acceptedAt).toLocaleDateString() : "-"}
</Td>
<Td noLink width={80} className="text-end">
<div
className="flex gap-2 justify-end"
onClick={(e) => e.stopPropagation()}
>
{isDeleting ? (
<Spinner size={16} />
) : (
isAuthorized("Invitation", "deleteInvitation") && (
<Button
variant="danger"
onClick={onDelete}
disabled={isDeleting}
icon={IconTrashCan}
aria-label={__("Delete invitation")}
/>
)
)}
</div>
</Td>
</Tr>
);
}
function MembershipRowContent(props: {
membership: NodeOf<MembersSettingsTabMembershipsFragment$data["memberships"]>;
connectionId?: string;
organizationId: string;
onRefetch: () => void;
}) {
const { __ } = useTranslate();
const { role: currentUserRole } = use(PermissionsContext);
const availableRoles = getAssignableRoles(currentUserRole);
const { isAuthorized } = use(PermissionsContext);
const [removeMember, isRemoving] = useMutationWithToasts(removeMemberMutation, {
successMessage: __("Member removed successfully"),
errorMessage: __("Failed to remove member"),
});
const [updateMembership, isUpdating] = useMutationWithToasts(updateMembershipMutation, {
successMessage: __("Role updated successfully"),
errorMessage: __("Failed to update role"),
});
const confirm = useConfirm();
const editDialogRef = useDialogRef();
const [isRemoved, setIsRemoved] = useState(false);
const [selectedRole, setSelectedRole] = useState<string>(props.membership.role);
// Only OWNER can edit OWNER members
const canEditThisRole = props.membership.role === "OWNER"
? currentUserRole === "OWNER"
: true;
if (isRemoved) {
return null;
}
const onRemove = async () => {
confirm(
() => {
return removeMember({
variables: {
input: {
memberId: props.membership.id,
organizationId: props.organizationId,
},
connections: props.connectionId ? [props.connectionId] : [],
},
onCompleted: () => {
setIsRemoved(true);
props.onRefetch();
},
});
},
{
message: sprintf(
__("Are you sure you want to remove %s?"),
props.membership.fullName
),
}
);
};
const handleEditClick = () => {
setSelectedRole(props.membership.role);
editDialogRef.current?.open();
};
const handleUpdateRole = (e: React.FormEvent) => {
e.preventDefault();
updateMembership({
variables: {
input: {
memberId: props.membership.id,
organizationId: props.organizationId,
role: selectedRole,
},
},
onCompleted: () => {
editDialogRef.current?.close();
},
});
};
return (
<>
<Tr className={clsx(isRemoving && "opacity-60 pointer-events-none")}>
<Td>
<div className="font-semibold">{props.membership.fullName}</div>
</Td>
<Td>
<div className="flex items-center gap-2">
{props.membership.emailAddress}
{props.membership.authMethod === "SAML" && (
<Badge variant="info">SAML</Badge>
)}
</div>
</Td>
<Td>
<Badge>{props.membership.role}</Badge>
</Td>
<Td>{new Date(props.membership.createdAt).toLocaleDateString()}</Td>
<Td noLink width={160} className="text-end">
<div
className="flex gap-2 justify-end"
onClick={(e) => e.stopPropagation()}
>
{isAuthorized("Organization", "updateMembership") && canEditThisRole && (
<Button
variant="secondary"
onClick={handleEditClick}
disabled={isUpdating}
icon={IconPencil}
aria-label={__("Edit role")}
/>
)}
{isRemoving ? (
<Spinner size={16} />
) : (
isAuthorized("Organization", "removeMember") && canEditThisRole && (
<Button
variant="danger"
onClick={onRemove}
disabled={isRemoving}
icon={IconTrashCan}
aria-label={__("Remove member")}
/>
)
)}
</div>
</Td>
</Tr>
<Dialog ref={editDialogRef} title={__("Edit Member Role")}>
<form onSubmit={handleUpdateRole}>
<DialogContent padded className="space-y-6">
<div>
<p className="text-txt-secondary text-sm mb-4">
{sprintf(__("Update the role for %s"), props.membership.fullName)}
</p>
<Field label={__("Role")} required>
<Select value={selectedRole} onValueChange={setSelectedRole}>
{availableRoles.includes("OWNER") && <Option value="OWNER">{__("Owner")}</Option>}
{availableRoles.includes("ADMIN") && <Option value="ADMIN">{__("Admin")}</Option>}
{availableRoles.includes("VIEWER") && <Option value="VIEWER">{__("Viewer")}</Option>}
{availableRoles.includes("AUDITOR") && <Option value="AUDITOR">{__("Auditor")}</Option>}
{availableRoles.includes("EMPLOYEE") && <Option value="EMPLOYEE">{__("Employee")}</Option>}
</Select>
</Field>
<div className="mt-4 space-y-2 text-sm text-txt-tertiary">
{selectedRole === "OWNER" && (
<p>{__("Full access to everything")}</p>
)}
{selectedRole === "ADMIN" && (
<p>{__("Full access except organization setup and API keys")}</p>
)}
{selectedRole === "VIEWER" && (
<p>{__("Read-only access")}</p>
)}
{selectedRole === "AUDITOR" && (
<p>{__("Read-only access without settings, tasks and meetings")}</p>
)}
{selectedRole === "EMPLOYEE" && (
<p>{__("Access to employee page")}</p>
)}
</div>
</div>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isUpdating || selectedRole === props.membership.role}>
{isUpdating && <Spinner />}
{__("Update Role")}
</Button>
</DialogFooter>
</form>
</Dialog>
</>
);
}
function MembershipRow(props: {
membership: NodeOf<MembersSettingsTabMembershipsFragment$data["memberships"]>;
connectionId?: string;
organizationId: string;
onRefetch: () => void;
}) {
return (
<Suspense fallback={
<Tr>
<Td><Spinner size={16} /></Td>
<Td></Td>
<Td></Td>
<Td></Td>
</Tr>
}>
<MembershipRowContent {...props} />
</Suspense>
);
}

View File

@@ -1,759 +0,0 @@
import { useState, useEffect, use } from "react";
import { useOutletContext } from "react-router";
import { useFragment, graphql } from "react-relay";
import { Controller } from "react-hook-form";
import { z } from "zod";
import {
Breadcrumb,
Button,
Card,
Checkbox,
Dialog,
DialogContent,
DialogFooter,
Field,
Label,
Option,
Select,
Table,
Tbody,
Td,
Textarea,
Th,
Thead,
Tr,
useConfirm,
useDialogRef,
} from "@probo/ui";
import { useTranslate } from "@probo/i18n";
import { useFormWithSchema } from "/hooks/useFormWithSchema";
import {
useCreateSAMLConfigurationMutation,
useUpdateSAMLConfigurationMutation,
useDeleteSAMLConfigurationMutation,
useEnableSAMLMutation,
useDisableSAMLMutation,
useInitiateDomainVerificationMutation,
useVerifyDomainMutation,
} from "/hooks/graph/SAMLConfigurationGraph";
import type { SAMLSettingsTabFragment$key } from "./__generated__/SAMLSettingsTabFragment.graphql";
import { PermissionsContext } from "/providers/PermissionsContext";
const samlSettingsTabFragment = graphql`
fragment SAMLSettingsTabFragment on Organization {
id
name
samlConfigurations {
id
enabled
emailDomain
enforcementPolicy
domainVerified
domainVerificationToken
domainVerifiedAt
spEntityId
spAcsUrl
spMetadataUrl
testLoginUrl
idpEntityId
idpSsoUrl
idpCertificate
idpMetadataUrl
attributeEmail
attributeFirstname
attributeLastname
attributeRole
autoSignupEnabled
}
}
`;
const initiateSchema = z.object({
emailDomain: z.string().min(1, "Email domain is required").regex(/^[a-z0-9.-]+\.[a-z]{2,}$/i, "Must be a valid domain (e.g., example.com)"),
});
const samlConfigSchema = z.object({
emailDomain: z.string().min(1, "Email domain is required").regex(/^[a-z0-9.-]+\.[a-z]{2,}$/i, "Must be a valid domain (e.g., example.com)"),
enforcementPolicy: z.enum(["OFF", "OPTIONAL", "REQUIRED"]),
spCertificate: z.string().optional(),
spPrivateKey: z.string().optional(),
idpEntityId: z.string().min(1, "IdP Entity ID is required"),
idpSsoUrl: z.string().url("IdP SSO URL must be a valid URL"),
idpCertificate: z.string().min(1, "IdP Certificate is required"),
idpMetadataUrl: z.string().url("IdP Metadata URL must be a valid URL").optional().or(z.literal("")),
attributeEmail: z.string().optional(),
attributeFirstname: z.string().optional(),
attributeLastname: z.string().optional(),
attributeRole: z.string().optional(),
autoSignupEnabled: z.boolean().default(false),
});
type OutletContext = {
organization: SAMLSettingsTabFragment$key;
};
type SetupStep = "initiate" | "verify" | "configure";
export default function SAMLSettingsTab() {
const { __ } = useTranslate();
const { organization: organizationKey } = useOutletContext<OutletContext>();
const { isAuthorized } = use(PermissionsContext);
const organization = useFragment(samlSettingsTabFragment, organizationKey);
const configs = organization.samlConfigurations;
const dialogRef = useDialogRef();
const [editingConfig, setEditingConfig] = useState<Partial<typeof configs[0]> & {id: string} | null>(null);
const [currentStep, setCurrentStep] = useState<SetupStep>("initiate");
const [dnsRecord, setDnsRecord] = useState<string>("");
const [createMutation, isCreating] = useCreateSAMLConfigurationMutation();
const [updateMutation, isUpdating] = useUpdateSAMLConfigurationMutation();
const [deleteMutation] = useDeleteSAMLConfigurationMutation();
const [enableMutation, isEnabling] = useEnableSAMLMutation();
const [disableMutation, isDisabling] = useDisableSAMLMutation();
const [initiateDomainMutation, isInitiating] = useInitiateDomainVerificationMutation();
const [verifyDomainMutation, isVerifying] = useVerifyDomainMutation();
const confirm = useConfirm();
const handleOpenModal = (config?: typeof configs[0]) => {
setEditingConfig(config || null);
if (config) {
if (!config.domainVerified) {
setCurrentStep("verify");
setDnsRecord(`probo-verification=${config.domainVerificationToken}`);
} else {
setCurrentStep("configure");
}
} else {
setCurrentStep("initiate");
}
dialogRef.current?.open();
};
const handleCloseModal = () => {
setEditingConfig(null);
setCurrentStep("initiate");
setDnsRecord("");
dialogRef.current?.close();
};
const initiateForm = useFormWithSchema(initiateSchema, {
defaultValues: {
emailDomain: editingConfig?.emailDomain || "",
},
});
const form = useFormWithSchema(samlConfigSchema, {
defaultValues: editingConfig
? {
emailDomain: editingConfig.emailDomain || "",
enforcementPolicy: editingConfig.enforcementPolicy || "OPTIONAL",
idpEntityId: editingConfig.idpEntityId || "",
idpSsoUrl: editingConfig.idpSsoUrl || "",
idpCertificate: editingConfig.idpCertificate || "",
idpMetadataUrl: editingConfig.idpMetadataUrl || "",
attributeEmail: editingConfig.attributeEmail || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
attributeFirstname: editingConfig.attributeFirstname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
attributeLastname: editingConfig.attributeLastname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
attributeRole: editingConfig.attributeRole || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role",
autoSignupEnabled: editingConfig.autoSignupEnabled || false,
}
: {
emailDomain: "",
enforcementPolicy: "OPTIONAL",
idpEntityId: "",
idpSsoUrl: "",
idpCertificate: "",
idpMetadataUrl: "",
attributeEmail: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
attributeFirstname: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
attributeLastname: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
attributeRole: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role",
autoSignupEnabled: false,
},
});
useEffect(() => {
if (editingConfig) {
form.reset({
emailDomain: editingConfig.emailDomain || "",
enforcementPolicy: editingConfig.enforcementPolicy || "OPTIONAL",
idpEntityId: editingConfig.idpEntityId || "",
idpSsoUrl: editingConfig.idpSsoUrl || "",
idpCertificate: editingConfig.idpCertificate || "",
idpMetadataUrl: editingConfig.idpMetadataUrl || "",
attributeEmail: editingConfig.attributeEmail || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
attributeFirstname: editingConfig.attributeFirstname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
attributeLastname: editingConfig.attributeLastname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
attributeRole: editingConfig.attributeRole || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role",
autoSignupEnabled: editingConfig.autoSignupEnabled || false,
});
initiateForm.reset({
emailDomain: editingConfig.emailDomain || "",
});
} else {
form.reset({
emailDomain: "",
enforcementPolicy: "OPTIONAL",
idpEntityId: "",
idpSsoUrl: "",
idpCertificate: "",
idpMetadataUrl: "",
attributeEmail: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
attributeFirstname: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
attributeLastname: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
attributeRole: "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role",
autoSignupEnabled: false,
});
initiateForm.reset({
emailDomain: "",
});
}
}, [editingConfig, form, initiateForm]);
const handleInitiateDomain = initiateForm.handleSubmit((data) => {
initiateDomainMutation({
variables: {
input: {
organizationId: organization.id,
emailDomain: data.emailDomain,
},
},
onCompleted: (response) => {
setDnsRecord(response.initiateDomainVerification.dnsRecord);
setEditingConfig(response.initiateDomainVerification.samlConfiguration);
setCurrentStep("verify");
},
});
});
const handleVerifyDomain = () => {
if (!editingConfig) return;
verifyDomainMutation({
variables: {
input: {
id: editingConfig.id,
},
},
onCompleted: (response) => {
if (response.verifyDomain.verified) {
setCurrentStep("configure");
}
},
});
};
const onSubmit = form.handleSubmit((data) => {
if (editingConfig) {
updateMutation({
variables: {
input: {
id: editingConfig.id,
enforcementPolicy: data.enforcementPolicy,
idpEntityId: data.idpEntityId,
idpSsoUrl: data.idpSsoUrl,
idpCertificate: data.idpCertificate,
idpMetadataUrl: data.idpMetadataUrl || null,
attributeEmail: data.attributeEmail || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
attributeFirstname: data.attributeFirstname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
attributeLastname: data.attributeLastname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
attributeRole: data.attributeRole || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role",
autoSignupEnabled: data.autoSignupEnabled || false,
},
},
onCompleted: () => {
handleCloseModal();
},
});
} else {
createMutation({
variables: {
input: {
organizationId: organization.id,
emailDomain: data.emailDomain,
enforcementPolicy: data.enforcementPolicy,
idpEntityId: data.idpEntityId,
idpSsoUrl: data.idpSsoUrl,
idpCertificate: data.idpCertificate,
idpMetadataUrl: data.idpMetadataUrl || null,
attributeEmail: data.attributeEmail || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress",
attributeFirstname: data.attributeFirstname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname",
attributeLastname: data.attributeLastname || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname",
attributeRole: data.attributeRole || "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role",
autoSignupEnabled: data.autoSignupEnabled || false,
},
},
onCompleted: () => {
handleCloseModal();
},
});
}
});
const handleToggleEnabled = (config: typeof configs[0]) => {
if (config.enabled) {
confirm(
async () => {
disableMutation({
variables: {
input: {
id: config.id,
},
},
});
},
{
title: __("Disable SAML"),
message: __(
"Are you sure you want to disable SAML authentication for " + config.emailDomain + "?"
),
label: __("Disable"),
variant: "danger",
}
);
} else {
enableMutation({
variables: {
input: {
id: config.id,
},
},
});
}
};
const handleDelete = (config: typeof configs[0]) => {
confirm(
async () => {
deleteMutation({
variables: {
input: {
id: config.id,
},
},
onCompleted: () => {
handleCloseModal();
},
});
},
{
title: __("Delete SAML Configuration"),
message: __(
"Are you sure you want to delete the SAML configuration for " + config.emailDomain + "? This action cannot be undone."
),
label: __("Delete"),
variant: "danger",
}
);
};
const [copiedUrl, setCopiedUrl] = useState<string | null>(null);
const handleCopy = (url: string) => {
navigator.clipboard.writeText(url);
setCopiedUrl(url);
setTimeout(() => setCopiedUrl(null), 2000);
};
const getEnforcementPolicyLabel = (policy: string) => {
switch (policy) {
case "OFF":
return __("Your team members can't use single sign-on and must use their password");
case "REQUIRED":
return __("Your team members must use single sign-on to log in");
case "OPTIONAL":
default:
return __("Your team members may use either single sign-on or their password to log in");
}
};
return (
<>
<div className="space-y-4">
<div className="flex justify-between items-center">
<h2 className="text-base font-medium">{__("SAML Single Sign-On")}</h2>
{isAuthorized("Organization", "createSAMLConfiguration") && (
<Button onClick={() => handleOpenModal()}>
{__("Add Configuration")}
</Button>
)}
</div>
{configs.length === 0 ? (
<Card padded>
<div className="text-center py-12">
<h3 className="text-lg font-medium text-gray-900 mb-2">
{__("No SAML Configurations")}
</h3>
<p className="text-gray-600 mb-6">
{__("Set up SAML 2.0 single sign-on for your organization by adding a configuration for each email domain.")}
</p>
{isAuthorized("Organization", "createSAMLConfiguration") && (
<Button onClick={() => handleOpenModal()}>
{__("Add Your First Configuration")}
</Button>
)}
</div>
</Card>
) : (
<Table>
<Thead>
<Tr>
<Th>{__("Email Domain")}</Th>
<Th>{__("Domain Status")}</Th>
<Th>{__("SAML Status")}</Th>
<Th>{__("Enforcement")}</Th>
<Th>{__("SSO URL")}</Th>
<Th></Th>
</Tr>
</Thead>
<Tbody>
{configs.map((config) => (
<Tr key={config.id}>
<Td>
<button
onClick={() => handleOpenModal(config)}
className="font-semibold text-blue-600 hover:text-blue-800"
>
{config.emailDomain}
</button>
</Td>
<Td>
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
config.domainVerified
? "bg-green-100 text-green-800"
: "bg-yellow-100 text-yellow-800"
}`}
>
{config.domainVerified ? __("Verified") : __("Pending Verification")}
</span>
</Td>
<Td>
<span
className={`inline-flex items-center px-2.5 py-0.5 rounded-full text-xs font-medium ${
config.enabled
? "bg-green-100 text-green-800"
: "bg-gray-100 text-gray-800"
}`}
>
{config.enabled ? __("Enabled") : __("Disabled")}
</span>
</Td>
<Td>{config.enforcementPolicy}</Td>
<Td>
{config.domainVerified && config.enabled ? (
<button
onClick={() => handleCopy(config.testLoginUrl)}
className="text-blue-600 hover:text-blue-800"
>
{copiedUrl === config.testLoginUrl ? __("Copied!") : __("Copy URL")}
</button>
) : (
<span className="text-gray-400">—</span>
)}
</Td>
<Td width={180} className="text-end">
<div className="flex gap-2 justify-end">
{config.domainVerified ? (
<>
{isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && (
<Button
variant={config.enabled ? "danger" : "primary"}
onClick={() => handleToggleEnabled(config)}
disabled={isEnabling || isDisabling}
>
{config.enabled ? __("Disable") : __("Enable")}
</Button>
)}
{isAuthorized("SAMLConfiguration", "updateSAMLConfiguration") && (
<Button
variant="secondary"
onClick={() => handleOpenModal(config)}
>
{__("Edit")}
</Button>
)}
</>
) : (
<>
{isAuthorized("Organization", "verifyDomain") && (
<Button
variant="primary"
onClick={() => handleOpenModal(config)}
>
{__("Verify Domain")}
</Button>
)}
{isAuthorized("Organization", "deleteOrganization") && (
<Button
variant="danger"
onClick={() => handleDelete(config)}
>
{__("Delete")}
</Button>
)}
</>
)}
</div>
</Td>
</Tr>
))}
</Tbody>
</Table>
)}
</div>
<Dialog
ref={dialogRef}
onClose={handleCloseModal}
title={
<Breadcrumb
items={[
__("SAML Settings"),
currentStep === "initiate" && __("Register Domain"),
currentStep === "verify" && __("Verify Domain"),
currentStep === "configure" && (editingConfig?.domainVerified ? __("Configure SAML") : __("Configure SAML")),
].filter(Boolean) as string[]}
/>
}
>
{currentStep === "initiate" && (
<form onSubmit={handleInitiateDomain}>
<DialogContent padded className="space-y-6">
<div>
<h3 className="text-base font-medium mb-4">
{__("Register Your Domain")}
</h3>
<p className="text-sm text-gray-600 mb-4">
{__("To set up SAML SSO, you must first register and verify ownership of your email domain.")}
</p>
<div className="space-y-4">
<Field
{...initiateForm.register("emailDomain")}
label={__("Email Domain") + " *"}
placeholder="example.com"
error={initiateForm.formState.errors.emailDomain?.message}
/>
<p className="text-xs text-gray-600">
{__("The email domain this SAML configuration will apply to (e.g., example.com)")}
</p>
</div>
</div>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isInitiating}>
{__("Next: Verify Domain")}
</Button>
</DialogFooter>
</form>
)}
{currentStep === "verify" && (
<>
<DialogContent padded className="space-y-6">
<div>
<h3 className="text-base font-medium mb-4">
{__("Verify Domain Ownership")}
</h3>
<p className="text-sm text-gray-600 mb-4">
{__("Add the following TXT record to your domain's DNS configuration to verify ownership:")}
</p>
<div className="bg-gray-50 rounded-lg p-4 mb-4">
<div className="space-y-2">
<div>
<span className="font-semibold text-sm">{__("Host/Name:")}</span>
<code className="ml-2 bg-white px-2 py-1 rounded text-sm">@</code>
<span className="ml-2 text-xs text-gray-600">{__("or use your domain name")}</span>
</div>
<div>
<span className="font-semibold text-sm">{__("Type:")}</span>
<code className="ml-2 bg-white px-2 py-1 rounded text-sm">TXT</code>
</div>
<div>
<span className="font-semibold text-sm">{__("Value:")}</span>
<div className="mt-1 flex items-center gap-2">
<code className="flex-1 bg-white px-2 py-1 rounded text-sm break-all font-mono">
{dnsRecord}
</code>
<Button
type="button"
variant="secondary"
onClick={() => handleCopy(dnsRecord)}
>
{copiedUrl === dnsRecord ? __("Copied!") : __("Copy")}
</Button>
</div>
</div>
</div>
</div>
<div className="bg-blue-50 border border-blue-200 rounded-lg p-4">
<p className="text-sm text-blue-800">
<strong>{__("Note:")}</strong> {__("DNS changes may take up to 48 hours to propagate, but typically complete within a few minutes.")}
</p>
</div>
</div>
</DialogContent>
<DialogFooter>
<Button onClick={handleVerifyDomain} disabled={isVerifying}>
{__("Verify and Continue")}
</Button>
</DialogFooter>
</>
)}
{currentStep === "configure" && (
<form onSubmit={onSubmit}>
<DialogContent padded className="space-y-6">
<div>
<h3 className="text-base font-medium mb-4">
{__("Basic Configuration")}
</h3>
<div className="space-y-4">
<div>
<Field
{...form.register("emailDomain")}
label={__("Email Domain") + " *"}
placeholder="example.com"
disabled={!!editingConfig}
error={form.formState.errors.emailDomain?.message}
/>
<p className="text-xs text-gray-600 mt-1">
{editingConfig
? __("Email domain cannot be changed after creation")
: __("The email domain this SAML configuration applies to (e.g., example.com)")}
</p>
</div>
<div>
<Label htmlFor="enforcementPolicy">{__("Enforcement Policy") + " *"}</Label>
<Controller
control={form.control}
name="enforcementPolicy"
render={({ field }) => (
<div className="mt-2">
<Select
value={field.value}
onValueChange={field.onChange}
>
<Option value="OPTIONAL">{__("Optional")}</Option>
<Option value="REQUIRED">{__("Required")}</Option>
<Option value="OFF">{__("Off")}</Option>
</Select>
</div>
)}
/>
{form.watch("enforcementPolicy") && (
<p className="text-xs text-gray-600 mt-2">
{getEnforcementPolicyLabel(form.watch("enforcementPolicy"))}
</p>
)}
</div>
</div>
</div>
<div>
<h3 className="text-base font-medium mb-4">
{__("Identity Provider Configuration")}
</h3>
<div className="space-y-4">
<Field
{...form.register("idpEntityId")}
label={__("IdP Entity ID") + " *"}
placeholder="https://idp.example.com/metadata"
error={form.formState.errors.idpEntityId?.message}
/>
<Field
{...form.register("idpSsoUrl")}
label={__("IdP SSO URL") + " *"}
placeholder="https://idp.example.com/sso"
error={form.formState.errors.idpSsoUrl?.message}
/>
<div>
<Label htmlFor="idpCertificate">
{__("IdP X.509 Certificate") + " *"}
</Label>
<Textarea
{...form.register("idpCertificate")}
id="idpCertificate"
rows={6}
placeholder="-----BEGIN CERTIFICATE-----&#10;...&#10;-----END CERTIFICATE-----"
className="font-mono text-sm"
/>
{form.formState.errors.idpCertificate && (
<p className="text-sm text-red-600 mt-1">
{form.formState.errors.idpCertificate.message}
</p>
)}
</div>
<Field
{...form.register("idpMetadataUrl")}
label={__("IdP Metadata URL (Optional)")}
placeholder="https://idp.example.com/metadata.xml"
error={form.formState.errors.idpMetadataUrl?.message}
/>
</div>
</div>
<div>
<h3 className="text-base font-medium mb-4">
{__("Attribute Mapping")}
</h3>
<div className="space-y-4">
<Field
{...form.register("attributeEmail")}
label={__("Email Attribute")}
placeholder="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress"
error={form.formState.errors.attributeEmail?.message}
/>
<Field
{...form.register("attributeFirstname")}
label={__("First Name Attribute")}
placeholder="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/givenname"
error={form.formState.errors.attributeFirstname?.message}
/>
<Field
{...form.register("attributeLastname")}
label={__("Last Name Attribute")}
placeholder="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/surname"
error={form.formState.errors.attributeLastname?.message}
/>
<Field
{...form.register("attributeRole")}
label={__("Role Attribute")}
placeholder="http://schemas.xmlsoap.org/ws/2005/05/identity/claims/role"
error={form.formState.errors.attributeRole?.message}
/>
</div>
</div>
<div>
<Controller
control={form.control}
name="autoSignupEnabled"
render={({ field }) => (
<div className="flex items-center gap-2">
<Checkbox
checked={field.value ?? false}
onChange={field.onChange}
/>
<Label htmlFor="autoSignupEnabled" className="cursor-pointer">
{__("Enable automatic user signup via SAML")}
</Label>
</div>
)}
/>
</div>
</DialogContent>
<DialogFooter>
<Button type="submit" disabled={isCreating || isUpdating}>
{editingConfig?.domainVerified ? __("Update Configuration") : __("Create Configuration")}
</Button>
</DialogFooter>
</form>
)}
</Dialog>
</>
);
}

View File

@@ -1,114 +0,0 @@
/**
* @generated SignedSource<<02beba57812b8dd7c5c61fb10291de5a>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type GeneralSettingsTabFragment$data = {
readonly createdAt: any;
readonly description: string | null | undefined;
readonly email: string | null | undefined;
readonly headquarterAddress: string | null | undefined;
readonly horizontalLogoUrl: string | null | undefined;
readonly id: string;
readonly logoUrl: string | null | undefined;
readonly name: string;
readonly updatedAt: any;
readonly websiteUrl: string | null | undefined;
readonly " $fragmentType": "GeneralSettingsTabFragment";
};
export type GeneralSettingsTabFragment$key = {
readonly " $data"?: GeneralSettingsTabFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"GeneralSettingsTabFragment">;
};
const node: ReaderFragment = {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "GeneralSettingsTabFragment",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "websiteUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "headquarterAddress",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "updatedAt",
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
};
(node as any).hash = "f6552148ce1c0061c4f5cbef78d46c0e";
export default node;

View File

@@ -1,113 +0,0 @@
/**
* @generated SignedSource<<8e0818a9214ba3613f9d356e61a75e08>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteOrganizationHorizontalLogoInput = {
organizationId: string;
};
export type GeneralSettingsTab_DeleteHorizontalLogoMutation$variables = {
input: DeleteOrganizationHorizontalLogoInput;
};
export type GeneralSettingsTab_DeleteHorizontalLogoMutation$data = {
readonly deleteOrganizationHorizontalLogo: {
readonly organization: {
readonly horizontalLogoUrl: string | null | undefined;
readonly id: string;
};
};
};
export type GeneralSettingsTab_DeleteHorizontalLogoMutation = {
response: GeneralSettingsTab_DeleteHorizontalLogoMutation$data;
variables: GeneralSettingsTab_DeleteHorizontalLogoMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "DeleteOrganizationHorizontalLogoPayload",
"kind": "LinkedField",
"name": "deleteOrganizationHorizontalLogo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "GeneralSettingsTab_DeleteHorizontalLogoMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "GeneralSettingsTab_DeleteHorizontalLogoMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "fbfaf507b48e2ef274e44372a70b3d88",
"id": null,
"metadata": {},
"name": "GeneralSettingsTab_DeleteHorizontalLogoMutation",
"operationKind": "mutation",
"text": "mutation GeneralSettingsTab_DeleteHorizontalLogoMutation(\n $input: DeleteOrganizationHorizontalLogoInput!\n) {\n deleteOrganizationHorizontalLogo(input: $input) {\n organization {\n id\n horizontalLogoUrl\n }\n }\n}\n"
}
};
})();
(node as any).hash = "7910936d423f99e36ee0a082f5c9336c";
export default node;

View File

@@ -1,168 +0,0 @@
/**
* @generated SignedSource<<270832e99647c6636914a9644a65358c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type UpdateOrganizationInput = {
description?: string | null | undefined;
email?: string | null | undefined;
headquarterAddress?: string | null | undefined;
horizontalLogoFile?: any | null | undefined;
logoFile?: any | null | undefined;
name?: string | null | undefined;
organizationId: string;
websiteUrl?: string | null | undefined;
};
export type GeneralSettingsTab_UpdateMutation$variables = {
input: UpdateOrganizationInput;
};
export type GeneralSettingsTab_UpdateMutation$data = {
readonly updateOrganization: {
readonly organization: {
readonly description: string | null | undefined;
readonly email: string | null | undefined;
readonly headquarterAddress: string | null | undefined;
readonly horizontalLogoUrl: string | null | undefined;
readonly id: string;
readonly logoUrl: string | null | undefined;
readonly name: string;
readonly websiteUrl: string | null | undefined;
};
};
};
export type GeneralSettingsTab_UpdateMutation = {
response: GeneralSettingsTab_UpdateMutation$data;
variables: GeneralSettingsTab_UpdateMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateOrganizationPayload",
"kind": "LinkedField",
"name": "updateOrganization",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Organization",
"kind": "LinkedField",
"name": "organization",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "logoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "horizontalLogoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "description",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "websiteUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "email",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "headquarterAddress",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "GeneralSettingsTab_UpdateMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "GeneralSettingsTab_UpdateMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "55c07b334317a5ca30023ef5354a6c45",
"id": null,
"metadata": {},
"name": "GeneralSettingsTab_UpdateMutation",
"operationKind": "mutation",
"text": "mutation GeneralSettingsTab_UpdateMutation(\n $input: UpdateOrganizationInput!\n) {\n updateOrganization(input: $input) {\n organization {\n id\n name\n logoUrl\n horizontalLogoUrl\n description\n websiteUrl\n email\n headquarterAddress\n }\n }\n}\n"
}
};
})();
(node as any).hash = "f1731adcb4bf7b7214301a612f48567e";
export default node;

View File

@@ -1,288 +0,0 @@
/**
* @generated SignedSource<<e3763975ef1132c3e2f909019b6cd98c>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type InvitationStatus = "ACCEPTED" | "EXPIRED" | "PENDING";
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
import { FragmentRefs } from "relay-runtime";
export type MembersSettingsTabInvitationsFragment$data = {
readonly id: string;
readonly invitations: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly acceptedAt: any | null | undefined;
readonly createdAt: any;
readonly email: any;
readonly expiresAt: any;
readonly fullName: string;
readonly id: string;
readonly role: MembershipRole;
readonly status: InvitationStatus;
};
}>;
readonly totalCount: number;
};
readonly " $fragmentType": "MembersSettingsTabInvitationsFragment";
};
export type MembersSettingsTabInvitationsFragment$key = {
readonly " $data"?: MembersSettingsTabInvitationsFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"MembersSettingsTabInvitationsFragment">;
};
import MembersSettingsTabInvitationsRefetchQuery_graphql from './MembersSettingsTabInvitationsRefetchQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"invitations"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
{
"defaultValue": 20,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
},
{
"defaultValue": {
"direction": "ASC",
"field": "CREATED_AT"
},
"kind": "LocalArgument",
"name": "order"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "bidirectional",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "first",
"cursor": "after"
},
"backward": {
"count": "last",
"cursor": "before"
},
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": MembersSettingsTabInvitationsRefetchQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "MembersSettingsTabInvitationsFragment",
"selections": [
{
"alias": "invitations",
"args": [
{
"kind": "Variable",
"name": "orderBy",
"variableName": "order"
}
],
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "__MembersSettingsTabInvitations_invitations_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "InvitationEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Invitation",
"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": "role",
"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": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "acceptedAt",
"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
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
(v1/*: any*/)
],
"type": "Organization",
"abstractKey": null
};
})();
(node as any).hash = "632fb80f7f536c576adaef2ec4007588";
export default node;

View File

@@ -1,375 +0,0 @@
/**
* @generated SignedSource<<24e16bb5ea83a195376f3356801ecdd6>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type InvitationOrderField = "ACCEPTED_AT" | "CREATED_AT" | "EMAIL" | "EXPIRES_AT" | "FULL_NAME" | "ROLE";
export type OrderDirection = "ASC" | "DESC";
export type InvitationOrder = {
direction: OrderDirection;
field: InvitationOrderField;
};
export type MembersSettingsTabInvitationsRefetchQuery$variables = {
after?: any | null | undefined;
before?: any | null | undefined;
first?: number | null | undefined;
id: string;
last?: number | null | undefined;
order?: InvitationOrder | null | undefined;
};
export type MembersSettingsTabInvitationsRefetchQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"MembersSettingsTabInvitationsFragment">;
};
};
export type MembersSettingsTabInvitationsRefetchQuery = {
response: MembersSettingsTabInvitationsRefetchQuery$data;
variables: MembersSettingsTabInvitationsRefetchQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
v2 = {
"defaultValue": 20,
"kind": "LocalArgument",
"name": "first"
},
v3 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
},
v4 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
},
v5 = {
"defaultValue": {
"direction": "ASC",
"field": "CREATED_AT"
},
"kind": "LocalArgument",
"name": "order"
},
v6 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v7 = {
"kind": "Variable",
"name": "after",
"variableName": "after"
},
v8 = {
"kind": "Variable",
"name": "before",
"variableName": "before"
},
v9 = {
"kind": "Variable",
"name": "first",
"variableName": "first"
},
v10 = {
"kind": "Variable",
"name": "last",
"variableName": "last"
},
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v12 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v13 = [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"kind": "Variable",
"name": "orderBy",
"variableName": "order"
}
];
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "MembersSettingsTabInvitationsRefetchQuery",
"selections": [
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"kind": "Variable",
"name": "order",
"variableName": "order"
}
],
"kind": "FragmentSpread",
"name": "MembersSettingsTabInvitationsFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v3/*: any*/)
],
"kind": "Operation",
"name": "MembersSettingsTabInvitationsRefetchQuery",
"selections": [
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v11/*: any*/),
(v12/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v13/*: any*/),
"concreteType": "InvitationConnection",
"kind": "LinkedField",
"name": "invitations",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "InvitationEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Invitation",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v12/*: 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": "role",
"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": "expiresAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "acceptedAt",
"storageKey": null
},
(v11/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
{
"alias": null,
"args": (v13/*: any*/),
"filters": [
"orderBy"
],
"handle": "connection",
"key": "MembersSettingsTabInvitations_invitations",
"kind": "LinkedHandle",
"name": "invitations"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "e6105642c2d4bc7c1023e4456cebff8e",
"id": null,
"metadata": {},
"name": "MembersSettingsTabInvitationsRefetchQuery",
"operationKind": "query",
"text": "query MembersSettingsTabInvitationsRefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: InvitationOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MembersSettingsTabInvitationsFragment_16fISc\n id\n }\n}\n\nfragment MembersSettingsTabInvitationsFragment_16fISc on Organization {\n invitations(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n fullName\n email\n role\n status\n createdAt\n expiresAt\n acceptedAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
}
};
})();
(node as any).hash = "632fb80f7f536c576adaef2ec4007588";
export default node;

View File

@@ -1,272 +0,0 @@
/**
* @generated SignedSource<<21ae595420b0098920e8d7f28834c7af>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
export type UserAuthMethod = "PASSWORD" | "SAML";
import { FragmentRefs } from "relay-runtime";
export type MembersSettingsTabMembershipsFragment$data = {
readonly id: string;
readonly memberships: {
readonly __id: string;
readonly edges: ReadonlyArray<{
readonly node: {
readonly authMethod: UserAuthMethod;
readonly createdAt: any;
readonly emailAddress: any;
readonly fullName: string;
readonly id: string;
readonly role: MembershipRole;
};
}>;
readonly totalCount: number;
};
readonly " $fragmentType": "MembersSettingsTabMembershipsFragment";
};
export type MembersSettingsTabMembershipsFragment$key = {
readonly " $data"?: MembersSettingsTabMembershipsFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"MembersSettingsTabMembershipsFragment">;
};
import MembersSettingsTabMembershipsRefetchQuery_graphql from './MembersSettingsTabMembershipsRefetchQuery.graphql';
const node: ReaderFragment = (function(){
var v0 = [
"memberships"
],
v1 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
{
"defaultValue": 20,
"kind": "LocalArgument",
"name": "first"
},
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
},
{
"defaultValue": {
"direction": "ASC",
"field": "CREATED_AT"
},
"kind": "LocalArgument",
"name": "order"
}
],
"kind": "Fragment",
"metadata": {
"connection": [
{
"count": null,
"cursor": null,
"direction": "bidirectional",
"path": (v0/*: any*/)
}
],
"refetch": {
"connection": {
"forward": {
"count": "first",
"cursor": "after"
},
"backward": {
"count": "last",
"cursor": "before"
},
"path": (v0/*: any*/)
},
"fragmentPathInResult": [
"node"
],
"operation": MembersSettingsTabMembershipsRefetchQuery_graphql,
"identifierInfo": {
"identifierField": "id",
"identifierQueryVariableName": "id"
}
}
},
"name": "MembersSettingsTabMembershipsFragment",
"selections": [
{
"alias": "memberships",
"args": [
{
"kind": "Variable",
"name": "orderBy",
"variableName": "order"
}
],
"concreteType": "MembershipConnection",
"kind": "LinkedField",
"name": "__MembersSettingsTabMemberships_memberships_connection",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "MembershipEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Membership",
"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": "emailAddress",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "authMethod",
"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
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
(v1/*: any*/)
],
"type": "Organization",
"abstractKey": null
};
})();
(node as any).hash = "c9e341e99052ba74299c5ddd0433d7c0";
export default node;

View File

@@ -1,361 +0,0 @@
/**
* @generated SignedSource<<b99cb7c099e133b550b2467abbe270e8>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
import { FragmentRefs } from "relay-runtime";
export type MembershipOrderField = "CREATED_AT" | "EMAIL_ADDRESS" | "FULL_NAME" | "ROLE";
export type OrderDirection = "ASC" | "DESC";
export type MembershipOrder = {
direction: OrderDirection;
field: MembershipOrderField;
};
export type MembersSettingsTabMembershipsRefetchQuery$variables = {
after?: any | null | undefined;
before?: any | null | undefined;
first?: number | null | undefined;
id: string;
last?: number | null | undefined;
order?: MembershipOrder | null | undefined;
};
export type MembersSettingsTabMembershipsRefetchQuery$data = {
readonly node: {
readonly " $fragmentSpreads": FragmentRefs<"MembersSettingsTabMembershipsFragment">;
};
};
export type MembersSettingsTabMembershipsRefetchQuery = {
response: MembersSettingsTabMembershipsRefetchQuery$data;
variables: MembersSettingsTabMembershipsRefetchQuery$variables;
};
const node: ConcreteRequest = (function(){
var v0 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "after"
},
v1 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "before"
},
v2 = {
"defaultValue": 20,
"kind": "LocalArgument",
"name": "first"
},
v3 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "id"
},
v4 = {
"defaultValue": null,
"kind": "LocalArgument",
"name": "last"
},
v5 = {
"defaultValue": {
"direction": "ASC",
"field": "CREATED_AT"
},
"kind": "LocalArgument",
"name": "order"
},
v6 = [
{
"kind": "Variable",
"name": "id",
"variableName": "id"
}
],
v7 = {
"kind": "Variable",
"name": "after",
"variableName": "after"
},
v8 = {
"kind": "Variable",
"name": "before",
"variableName": "before"
},
v9 = {
"kind": "Variable",
"name": "first",
"variableName": "first"
},
v10 = {
"kind": "Variable",
"name": "last",
"variableName": "last"
},
v11 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__typename",
"storageKey": null
},
v12 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
v13 = [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"kind": "Variable",
"name": "orderBy",
"variableName": "order"
}
];
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v3/*: any*/),
(v4/*: any*/),
(v5/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "MembersSettingsTabMembershipsRefetchQuery",
"selections": [
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
{
"args": [
(v7/*: any*/),
(v8/*: any*/),
(v9/*: any*/),
(v10/*: any*/),
{
"kind": "Variable",
"name": "order",
"variableName": "order"
}
],
"kind": "FragmentSpread",
"name": "MembersSettingsTabMembershipsFragment"
}
],
"storageKey": null
}
],
"type": "Query",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/),
(v2/*: any*/),
(v4/*: any*/),
(v5/*: any*/),
(v3/*: any*/)
],
"kind": "Operation",
"name": "MembersSettingsTabMembershipsRefetchQuery",
"selections": [
{
"alias": null,
"args": (v6/*: any*/),
"concreteType": null,
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v11/*: any*/),
(v12/*: any*/),
{
"kind": "InlineFragment",
"selections": [
{
"alias": null,
"args": (v13/*: any*/),
"concreteType": "MembershipConnection",
"kind": "LinkedField",
"name": "memberships",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "totalCount",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "MembershipEdge",
"kind": "LinkedField",
"name": "edges",
"plural": true,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Membership",
"kind": "LinkedField",
"name": "node",
"plural": false,
"selections": [
(v12/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "fullName",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailAddress",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "authMethod",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "createdAt",
"storageKey": null
},
(v11/*: any*/)
],
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "cursor",
"storageKey": null
}
],
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "PageInfo",
"kind": "LinkedField",
"name": "pageInfo",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "endCursor",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasNextPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "hasPreviousPage",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "startCursor",
"storageKey": null
}
],
"storageKey": null
},
{
"kind": "ClientExtension",
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "__id",
"storageKey": null
}
]
}
],
"storageKey": null
},
{
"alias": null,
"args": (v13/*: any*/),
"filters": [
"orderBy"
],
"handle": "connection",
"key": "MembersSettingsTabMemberships_memberships",
"kind": "LinkedHandle",
"name": "memberships"
}
],
"type": "Organization",
"abstractKey": null
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "27627ac3e1ea017ee7783fcfd502c0f8",
"id": null,
"metadata": {},
"name": "MembersSettingsTabMembershipsRefetchQuery",
"operationKind": "query",
"text": "query MembersSettingsTabMembershipsRefetchQuery(\n $after: CursorKey = null\n $before: CursorKey = null\n $first: Int = 20\n $last: Int = null\n $order: MembershipOrder = {direction: ASC, field: CREATED_AT}\n $id: ID!\n) {\n node(id: $id) {\n __typename\n ...MembersSettingsTabMembershipsFragment_16fISc\n id\n }\n}\n\nfragment MembersSettingsTabMembershipsFragment_16fISc on Organization {\n memberships(first: $first, after: $after, last: $last, before: $before, orderBy: $order) {\n totalCount\n edges {\n node {\n id\n fullName\n emailAddress\n role\n authMethod\n createdAt\n __typename\n }\n cursor\n }\n pageInfo {\n endCursor\n hasNextPage\n hasPreviousPage\n startCursor\n }\n }\n id\n}\n"
}
};
})();
(node as any).hash = "c9e341e99052ba74299c5ddd0433d7c0";
export default node;

View File

@@ -1,132 +0,0 @@
/**
* @generated SignedSource<<057a86325a80ac7377b18a50896f01e1>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type DeleteInvitationInput = {
invitationId: string;
};
export type MembersSettingsTab_DeleteInvitationMutation$variables = {
connections: ReadonlyArray<string>;
input: DeleteInvitationInput;
};
export type MembersSettingsTab_DeleteInvitationMutation$data = {
readonly deleteInvitation: {
readonly deletedInvitationId: string;
};
};
export type MembersSettingsTab_DeleteInvitationMutation = {
response: MembersSettingsTab_DeleteInvitationMutation$data;
variables: MembersSettingsTab_DeleteInvitationMutation$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": "deletedInvitationId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "MembersSettingsTab_DeleteInvitationMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteInvitationPayload",
"kind": "LinkedField",
"name": "deleteInvitation",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "MembersSettingsTab_DeleteInvitationMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "DeleteInvitationPayload",
"kind": "LinkedField",
"name": "deleteInvitation",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedInvitationId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "c995f13c967dab141f64f9ad6314f3a2",
"id": null,
"metadata": {},
"name": "MembersSettingsTab_DeleteInvitationMutation",
"operationKind": "mutation",
"text": "mutation MembersSettingsTab_DeleteInvitationMutation(\n $input: DeleteInvitationInput!\n) {\n deleteInvitation(input: $input) {\n deletedInvitationId\n }\n}\n"
}
};
})();
(node as any).hash = "ad47509295919c7f0e6ff7895777231f";
export default node;

View File

@@ -1,133 +0,0 @@
/**
* @generated SignedSource<<b5471d97fe00df7ef4c16669ebc89916>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RemoveMemberInput = {
memberId: string;
organizationId: string;
};
export type MembersSettingsTab_RemoveMemberMutation$variables = {
connections: ReadonlyArray<string>;
input: RemoveMemberInput;
};
export type MembersSettingsTab_RemoveMemberMutation$data = {
readonly removeMember: {
readonly deletedMemberId: string;
};
};
export type MembersSettingsTab_RemoveMemberMutation = {
response: MembersSettingsTab_RemoveMemberMutation$data;
variables: MembersSettingsTab_RemoveMemberMutation$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": "deletedMemberId",
"storageKey": null
};
return {
"fragment": {
"argumentDefinitions": [
(v0/*: any*/),
(v1/*: any*/)
],
"kind": "Fragment",
"metadata": null,
"name": "MembersSettingsTab_RemoveMemberMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "RemoveMemberPayload",
"kind": "LinkedField",
"name": "removeMember",
"plural": false,
"selections": [
(v3/*: any*/)
],
"storageKey": null
}
],
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": [
(v1/*: any*/),
(v0/*: any*/)
],
"kind": "Operation",
"name": "MembersSettingsTab_RemoveMemberMutation",
"selections": [
{
"alias": null,
"args": (v2/*: any*/),
"concreteType": "RemoveMemberPayload",
"kind": "LinkedField",
"name": "removeMember",
"plural": false,
"selections": [
(v3/*: any*/),
{
"alias": null,
"args": null,
"filters": null,
"handle": "deleteEdge",
"key": "",
"kind": "ScalarHandle",
"name": "deletedMemberId",
"handleArgs": [
{
"kind": "Variable",
"name": "connections",
"variableName": "connections"
}
]
}
],
"storageKey": null
}
]
},
"params": {
"cacheID": "6ccac45c6bedfbfe98b6c6344ea5df28",
"id": null,
"metadata": {},
"name": "MembersSettingsTab_RemoveMemberMutation",
"operationKind": "mutation",
"text": "mutation MembersSettingsTab_RemoveMemberMutation(\n $input: RemoveMemberInput!\n) {\n removeMember(input: $input) {\n deletedMemberId\n }\n}\n"
}
};
})();
(node as any).hash = "97f72349476066a0de4e580d2e4e1b0e";
export default node;

View File

@@ -1,116 +0,0 @@
/**
* @generated SignedSource<<e40f8c235af2bfed3a33bdf0e5a3c24b>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type MembershipRole = "ADMIN" | "AUDITOR" | "EMPLOYEE" | "OWNER" | "VIEWER";
export type UpdateMembershipInput = {
memberId: string;
organizationId: string;
role: MembershipRole;
};
export type MembersSettingsTab_UpdateMembershipMutation$variables = {
input: UpdateMembershipInput;
};
export type MembersSettingsTab_UpdateMembershipMutation$data = {
readonly updateMembership: {
readonly membership: {
readonly id: string;
readonly role: MembershipRole;
};
};
};
export type MembersSettingsTab_UpdateMembershipMutation = {
response: MembersSettingsTab_UpdateMembershipMutation$data;
variables: MembersSettingsTab_UpdateMembershipMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "UpdateMembershipPayload",
"kind": "LinkedField",
"name": "updateMembership",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"concreteType": "Membership",
"kind": "LinkedField",
"name": "membership",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "role",
"storageKey": null
}
],
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "MembersSettingsTab_UpdateMembershipMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "MembersSettingsTab_UpdateMembershipMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "675437becfdd9cc5ea20d2b40b9ace37",
"id": null,
"metadata": {},
"name": "MembersSettingsTab_UpdateMembershipMutation",
"operationKind": "mutation",
"text": "mutation MembersSettingsTab_UpdateMembershipMutation(\n $input: UpdateMembershipInput!\n) {\n updateMembership(input: $input) {\n membership {\n id\n role\n }\n }\n}\n"
}
};
})();
(node as any).hash = "29ead2b06842cc8ed98bbea1cf6c1bed";
export default node;

View File

@@ -1,221 +0,0 @@
/**
* @generated SignedSource<<ad8f1e5fd866cefc124b7b7c78bce367>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ReaderFragment } from 'relay-runtime';
export type SAMLEnforcementPolicy = "OFF" | "OPTIONAL" | "REQUIRED";
import { FragmentRefs } from "relay-runtime";
export type SAMLSettingsTabFragment$data = {
readonly id: string;
readonly name: string;
readonly samlConfigurations: ReadonlyArray<{
readonly attributeEmail: string;
readonly attributeFirstname: string;
readonly attributeLastname: string;
readonly attributeRole: string;
readonly autoSignupEnabled: boolean;
readonly domainVerificationToken: string | null | undefined;
readonly domainVerified: boolean;
readonly domainVerifiedAt: any | null | undefined;
readonly emailDomain: string;
readonly enabled: boolean;
readonly enforcementPolicy: SAMLEnforcementPolicy;
readonly id: string;
readonly idpCertificate: string;
readonly idpEntityId: string;
readonly idpMetadataUrl: string | null | undefined;
readonly idpSsoUrl: string;
readonly spAcsUrl: string;
readonly spEntityId: string;
readonly spMetadataUrl: string;
readonly testLoginUrl: string;
}>;
readonly " $fragmentType": "SAMLSettingsTabFragment";
};
export type SAMLSettingsTabFragment$key = {
readonly " $data"?: SAMLSettingsTabFragment$data;
readonly " $fragmentSpreads": FragmentRefs<"SAMLSettingsTabFragment">;
};
const node: ReaderFragment = (function(){
var v0 = {
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "id",
"storageKey": null
};
return {
"argumentDefinitions": [],
"kind": "Fragment",
"metadata": null,
"name": "SAMLSettingsTabFragment",
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "name",
"storageKey": null
},
{
"alias": null,
"args": null,
"concreteType": "SAMLConfiguration",
"kind": "LinkedField",
"name": "samlConfigurations",
"plural": true,
"selections": [
(v0/*: any*/),
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "enabled",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "emailDomain",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "enforcementPolicy",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "domainVerified",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "domainVerificationToken",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "domainVerifiedAt",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "spEntityId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "spAcsUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "spMetadataUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "testLoginUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "idpEntityId",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "idpSsoUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "idpCertificate",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "idpMetadataUrl",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "attributeEmail",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "attributeFirstname",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "attributeLastname",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "attributeRole",
"storageKey": null
},
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "autoSignupEnabled",
"storageKey": null
}
],
"storageKey": null
}
],
"type": "Organization",
"abstractKey": null
};
})();
(node as any).hash = "eec380838cfd22d70a504028bb5046b4";
export default node;