diff --git a/apps/console/src/hooks/graph/OrganizationGraph.ts b/apps/console/src/hooks/graph/OrganizationGraph.ts deleted file mode 100644 index 785510e87..000000000 --- a/apps/console/src/hooks/graph/OrganizationGraph.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { graphql } from "relay-runtime"; -// import { useTranslate } from "@probo/i18n"; -// import { useMutationWithToasts } from "../useMutationWithToasts"; -// import type { OrganizationGraphDeleteMutation } from "./__generated__/OrganizationGraphDeleteMutation.graphql"; - -export const organizationViewQuery = graphql` - query OrganizationGraph_ViewQuery($organizationId: ID!) { - node(id: $organizationId) { - ... on Organization { - id - name - ...SettingsPageFragment - } - } - } -`; - -// const deleteOrganizationMutation = graphql` -// mutation OrganizationGraphDeleteMutation( -// $input: DeleteOrganizationInput! -// $connections: [ID!]! -// ) { -// deleteOrganization(input: $input) { -// deletedOrganizationId @deleteEdge(connections: $connections) -// } -// } -// `; - -// export function useDeleteOrganizationMutation() { -// const { __ } = useTranslate(); - -// return useMutationWithToasts( -// deleteOrganizationMutation, -// { -// successMessage: __("Organization deleted successfully."), -// errorMessage: __("Failed to delete organization"), -// } -// ); -// } diff --git a/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts deleted file mode 100644 index a36fc2c83..000000000 --- a/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts +++ /dev/null @@ -1,228 +0,0 @@ -/** - * @generated SignedSource<<871b1bf04087566d269a7d5b92353966>> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -import { FragmentRefs } from "relay-runtime"; -export type OrganizationGraph_ViewQuery$variables = { - organizationId: string; -}; -export type OrganizationGraph_ViewQuery$data = { - readonly node: { - readonly id?: string; - readonly name?: string; - readonly " $fragmentSpreads": FragmentRefs<"SettingsPageFragment">; - }; -}; -export type OrganizationGraph_ViewQuery = { - response: OrganizationGraph_ViewQuery$data; - variables: OrganizationGraph_ViewQuery$variables; -}; - -const node: ConcreteRequest = (function(){ -var v0 = [ - { - "defaultValue": null, - "kind": "LocalArgument", - "name": "organizationId" - } -], -v1 = [ - { - "kind": "Variable", - "name": "id", - "variableName": "organizationId" - } -], -v2 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null -}, -v3 = { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null -}; -return { - "fragment": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Fragment", - "metadata": null, - "name": "OrganizationGraph_ViewQuery", - "selections": [ - { - "alias": null, - "args": (v1/*: any*/), - "concreteType": null, - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - { - "kind": "InlineFragment", - "selections": [ - (v2/*: any*/), - (v3/*: any*/), - { - "args": null, - "kind": "FragmentSpread", - "name": "SettingsPageFragment" - } - ], - "type": "Organization", - "abstractKey": null - } - ], - "storageKey": null - } - ], - "type": "Query", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Operation", - "name": "OrganizationGraph_ViewQuery", - "selections": [ - { - "alias": null, - "args": (v1/*: any*/), - "concreteType": null, - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "__typename", - "storageKey": null - }, - (v2/*: any*/), - { - "kind": "InlineFragment", - "selections": [ - (v3/*: any*/), - { - "alias": null, - "args": null, - "concreteType": "CustomDomain", - "kind": "LinkedField", - "name": "customDomain", - "plural": false, - "selections": [ - (v2/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "domain", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sslStatus", - "storageKey": null - }, - { - "alias": null, - "args": null, - "concreteType": "DNSRecordInstruction", - "kind": "LinkedField", - "name": "dnsRecords", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "type", - "storageKey": null - }, - (v3/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "value", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "ttl", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "purpose", - "storageKey": null - } - ], - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "createdAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "updatedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sslExpiresAt", - "storageKey": null - } - ], - "storageKey": null - } - ], - "type": "Organization", - "abstractKey": null - } - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "a3048b63d657ae43793f9cfca1d0f53d", - "id": null, - "metadata": {}, - "name": "OrganizationGraph_ViewQuery", - "operationKind": "query", - "text": "query OrganizationGraph_ViewQuery(\n $organizationId: ID!\n) {\n node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n ...SettingsPageFragment\n }\n id\n }\n}\n\nfragment DomainSettingsTabFragment on Organization {\n id\n customDomain {\n id\n domain\n sslStatus\n dnsRecords {\n type\n name\n value\n ttl\n purpose\n }\n createdAt\n updatedAt\n sslExpiresAt\n }\n}\n\nfragment SettingsPageFragment on Organization {\n id\n name\n ...DomainSettingsTabFragment\n}\n" - } -}; -})(); - -(node as any).hash = "196e8c1fc9c2e0b3c76c8b338ed5c7f7"; - -export default node; diff --git a/apps/console/src/pages/iam/organizations/settings/GeneralSettingsPage.tsx b/apps/console/src/pages/iam/organizations/settings/GeneralSettingsPage.tsx new file mode 100644 index 000000000..9426a1dc2 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/settings/GeneralSettingsPage.tsx @@ -0,0 +1,106 @@ +import { useTranslate } from "@probo/i18n"; +import { Button, Card, IconTrashCan } from "@probo/ui"; +import { use } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { useNavigate } from "react-router"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import { graphql } from "relay-runtime"; +import { usePreloadedQuery, type PreloadedQuery } from "react-relay"; +import type { GeneralSettingsPageQuery } from "./__generated__/GeneralSettingsPageQuery.graphql"; +import { generalSettingsPageQuery } from "./GeneralSettingsPageQuery"; +import type { GeneralSettingsPage_deleteMutation } from "./__generated__/GeneralSettingsPage_deleteMutation.graphql"; +import { DeleteOrganizationDialog } from "/components/organizations/DeleteOrganizationDialog"; +import { OrganizationForm } from "./_components/OrganizationForm"; + +const deleteOrganizationMutation = graphql` + mutation GeneralSettingsPage_deleteMutation( + $input: DeleteOrganizationInput! + $connections: [ID!]! + ) { + deleteOrganization(input: $input) { + deletedOrganizationId @deleteEdge(connections: $connections) + } + } +`; + +export function GeneralSettingsPage(props: { + queryRef: PreloadedQuery; +}) { + const { queryRef } = props; + const { __ } = useTranslate(); + const navigate = useNavigate(); + + const { isAuthorized } = use(PermissionsContext); + const canDelete = isAuthorized("Organization", "deleteOrganization"); + + const { organization } = usePreloadedQuery( + generalSettingsPageQuery, + queryRef, + ); + if (organization.__typename === "%other") { + throw new Error("Relay node is not an organization"); + } + + const [deleteOrganization, isDeletingOrganization] = + useMutationWithToasts( + deleteOrganizationMutation, + { + successMessage: __("Organization deleted successfully."), + errorMessage: __("Failed to delete organization"), + }, + ); + + const handleDeleteOrganization = () => { + return deleteOrganization({ + variables: { + input: { + organizationId: organization.id, + }, + connections: [], + }, + onSuccess: () => { + navigate("/", { replace: true }); + }, + }); + }; + + return ( +
+ + + {canDelete && ( +
+

+ {__("Danger Zone")} +

+ +
+

+ {__("Delete Organization")} +

+

+ {__("Permanently delete this organization and all its data.")}{" "} + + {__("This action cannot be undone.")} + +

+
+ + + +
+
+ )} +
+ ); +} diff --git a/apps/console/src/pages/iam/organizations/settings/GeneralSettingsPageQuery.tsx b/apps/console/src/pages/iam/organizations/settings/GeneralSettingsPageQuery.tsx new file mode 100644 index 000000000..0a71d55e7 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/settings/GeneralSettingsPageQuery.tsx @@ -0,0 +1,47 @@ +import { graphql } from "relay-runtime"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { useQueryLoader } from "react-relay"; +import { useEffect } from "react"; +import { GeneralSettingsPage } from "./GeneralSettingsPage"; +import type { GeneralSettingsPageQuery } from "./__generated__/GeneralSettingsPageQuery.graphql"; +import { IAMRelayProvider } from "/providers/IAMRelayProvider"; + +export const generalSettingsPageQuery = graphql` + query GeneralSettingsPageQuery($organizationId: ID!) { + organization: node(id: $organizationId) @required(action: THROW) { + __typename + ... on Organization { + id + name @required(action: THROW) + ...OrganizationFormFragment + } + } + } +`; + +function GeneralSettingsPageQuery() { + const organizationId = useOrganizationId(); + const [queryRef, loadQuery] = useQueryLoader( + generalSettingsPageQuery, + ); + + useEffect(() => { + loadQuery({ + organizationId, + }); + }, [loadQuery, organizationId]); + + if (!queryRef) { + return null; + } + + return ; +} + +export default function () { + return ( + + + + ); +} diff --git a/apps/console/src/pages/iam/organizations/settings/SettingsLayout.tsx b/apps/console/src/pages/iam/organizations/settings/SettingsLayout.tsx new file mode 100644 index 000000000..d5981ebf5 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/settings/SettingsLayout.tsx @@ -0,0 +1,44 @@ +import { + IconLock, + IconPeopleAdd, + IconSettingsGear2, + IconStore, + PageHeader, + TabLink, + Tabs, +} from "@probo/ui"; +import { Outlet } from "react-router"; +import { useOrganizationId } from "/hooks/useOrganizationId"; +import { useTranslate } from "@probo/i18n"; + +export default function SettingsLayout() { + const organizationId = useOrganizationId(); + const { __ } = useTranslate(); + + return ( +
+ + + + + + {__("General")} + + + + {__("Members")} + + + + {__("Domain")} + + + + {__("SAML SSO")} + + + + +
+ ); +} diff --git a/apps/console/src/pages/iam/organizations/settings/__generated__/GeneralSettingsPageQuery.graphql.ts b/apps/console/src/pages/iam/organizations/settings/__generated__/GeneralSettingsPageQuery.graphql.ts new file mode 100644 index 000000000..d5383976a --- /dev/null +++ b/apps/console/src/pages/iam/organizations/settings/__generated__/GeneralSettingsPageQuery.graphql.ts @@ -0,0 +1,198 @@ +/** + * @generated SignedSource<<7b9dd98afd5bdc994e7d2e422dec2cf2>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +import { FragmentRefs } from "relay-runtime"; +export type GeneralSettingsPageQuery$variables = { + organizationId: string; +}; +export type GeneralSettingsPageQuery$data = { + readonly organization: { + readonly __typename: "Organization"; + readonly id: string; + readonly name: string; + readonly " $fragmentSpreads": FragmentRefs<"OrganizationFormFragment">; + } | { + // This will never be '%other', but we need some + // value in case none of the concrete values match. + readonly __typename: "%other"; + }; +}; +export type GeneralSettingsPageQuery = { + response: GeneralSettingsPageQuery$data; + variables: GeneralSettingsPageQuery$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "organizationId" + } +], +v1 = [ + { + "kind": "Variable", + "name": "id", + "variableName": "organizationId" + } +], +v2 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "__typename", + "storageKey": null +}, +v3 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null +}, +v4 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "GeneralSettingsPageQuery", + "selections": [ + { + "kind": "RequiredField", + "field": { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v3/*: any*/), + { + "kind": "RequiredField", + "field": (v4/*: any*/), + "action": "THROW" + }, + { + "args": null, + "kind": "FragmentSpread", + "name": "OrganizationFormFragment" + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + }, + "action": "THROW" + } + ], + "type": "Query", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "GeneralSettingsPageQuery", + "selections": [ + { + "alias": "organization", + "args": (v1/*: any*/), + "concreteType": null, + "kind": "LinkedField", + "name": "node", + "plural": false, + "selections": [ + (v2/*: any*/), + (v3/*: any*/), + { + "kind": "InlineFragment", + "selections": [ + (v4/*: any*/), + { + "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 + } + ], + "type": "Organization", + "abstractKey": null + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "11f654f40301ba539b6d78ead3029308", + "id": null, + "metadata": {}, + "name": "GeneralSettingsPageQuery", + "operationKind": "query", + "text": "query GeneralSettingsPageQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n name\n ...OrganizationFormFragment\n }\n id\n }\n}\n\nfragment OrganizationFormFragment on Organization {\n id\n name\n logoUrl\n horizontalLogoUrl\n description\n websiteUrl\n email\n headquarterAddress\n}\n" + } +}; +})(); + +(node as any).hash = "a83e24379e0772bbaf19bad92d60c6d4"; + +export default node; diff --git a/apps/console/src/pages/iam/organizations/settings/__generated__/GeneralSettingsPage_deleteMutation.graphql.ts b/apps/console/src/pages/iam/organizations/settings/__generated__/GeneralSettingsPage_deleteMutation.graphql.ts new file mode 100644 index 000000000..2630e9695 --- /dev/null +++ b/apps/console/src/pages/iam/organizations/settings/__generated__/GeneralSettingsPage_deleteMutation.graphql.ts @@ -0,0 +1,132 @@ +/** + * @generated SignedSource<<71619b1be320df410e3ebe2157b11ed4>> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type DeleteOrganizationInput = { + organizationId: string; +}; +export type GeneralSettingsPage_deleteMutation$variables = { + connections: ReadonlyArray; + input: DeleteOrganizationInput; +}; +export type GeneralSettingsPage_deleteMutation$data = { + readonly deleteOrganization: { + readonly deletedOrganizationId: string; + } | null | undefined; +}; +export type GeneralSettingsPage_deleteMutation = { + response: GeneralSettingsPage_deleteMutation$data; + variables: GeneralSettingsPage_deleteMutation$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": "deletedOrganizationId", + "storageKey": null +}; +return { + "fragment": { + "argumentDefinitions": [ + (v0/*: any*/), + (v1/*: any*/) + ], + "kind": "Fragment", + "metadata": null, + "name": "GeneralSettingsPage_deleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteOrganizationPayload", + "kind": "LinkedField", + "name": "deleteOrganization", + "plural": false, + "selections": [ + (v3/*: any*/) + ], + "storageKey": null + } + ], + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": [ + (v1/*: any*/), + (v0/*: any*/) + ], + "kind": "Operation", + "name": "GeneralSettingsPage_deleteMutation", + "selections": [ + { + "alias": null, + "args": (v2/*: any*/), + "concreteType": "DeleteOrganizationPayload", + "kind": "LinkedField", + "name": "deleteOrganization", + "plural": false, + "selections": [ + (v3/*: any*/), + { + "alias": null, + "args": null, + "filters": null, + "handle": "deleteEdge", + "key": "", + "kind": "ScalarHandle", + "name": "deletedOrganizationId", + "handleArgs": [ + { + "kind": "Variable", + "name": "connections", + "variableName": "connections" + } + ] + } + ], + "storageKey": null + } + ] + }, + "params": { + "cacheID": "53d34f4a9fe61e454f0ca80e8f34d88a", + "id": null, + "metadata": {}, + "name": "GeneralSettingsPage_deleteMutation", + "operationKind": "mutation", + "text": "mutation GeneralSettingsPage_deleteMutation(\n $input: DeleteOrganizationInput!\n) {\n deleteOrganization(input: $input) {\n deletedOrganizationId\n }\n}\n" + } +}; +})(); + +(node as any).hash = "c473475817881aaa4adee8f183faf580"; + +export default node; diff --git a/apps/console/src/pages/iam/organizations/settings/_components/OrganizationForm.tsx b/apps/console/src/pages/iam/organizations/settings/_components/OrganizationForm.tsx new file mode 100644 index 000000000..f4a53150a --- /dev/null +++ b/apps/console/src/pages/iam/organizations/settings/_components/OrganizationForm.tsx @@ -0,0 +1,373 @@ +import { + Avatar, + Button, + Card, + Dialog, + DialogContent, + DialogFooter, + Field, + FileButton, + IconTrashCan, + Label, + Spinner, + Textarea, + useDialogRef, +} from "@probo/ui"; +import { use, useState, type ChangeEventHandler } from "react"; +import { PermissionsContext } from "/providers/PermissionsContext"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import { graphql } from "relay-runtime"; +import z from "zod"; +import { useTranslate } from "@probo/i18n"; +import { useFragment } from "react-relay"; +import type { OrganizationFormFragment$key } from "./__generated__/OrganizationFormFragment.graphql"; + +const fragment = graphql` + fragment OrganizationFormFragment on Organization { + id + name @required(action: THROW) + logoUrl + horizontalLogoUrl + description + websiteUrl + email + headquarterAddress + } +`; + +const updateOrganizationMutation = graphql` + mutation OrganizationForm_updateMutation($input: UpdateOrganizationInput!) { + updateOrganization(input: $input) { + organization { + id + name + logoUrl + horizontalLogoUrl + description + websiteUrl + email + headquarterAddress + } + } + } +`; + +const deleteHorizontalLogoMutation = graphql` + mutation OrganizationForm_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; + +export function OrganizationForm(props: { + fKey: OrganizationFormFragment$key; +}) { + const { fKey } = props; + const { __ } = useTranslate(); + const deleteDialogRef = useDialogRef(); + + const { isAuthorized } = use(PermissionsContext); + const canUpdate = isAuthorized("Organization", "updateOrganization"); + + const [logoPreview, setLogoPreview] = useState(null); + const [horizontalLogoPreview, setHorizontalLogoPreview] = useState< + string | null + >(null); + + const organization = useFragment( + fragment, + fKey, + ); + + 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 { formState, handleSubmit, register } = useFormWithSchema( + organizationSchema, + { + defaultValues: { + name: organization.name, + description: organization.description || "", + websiteUrl: organization.websiteUrl || "", + email: organization.email || "", + headquarterAddress: organization.headquarterAddress || "", + }, + }, + ); + + const handleLogoChange: ChangeEventHandler = (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 = ( + 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 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, + }, + }, + }); + }); + + return ( +
+
+

{__("Organization details")}

+ {formState.isSubmitting && } +
+ +
+ +
+ + {canUpdate && ( + + {isUpdatingOrganization + ? __("Uploading...") + : __("Change logo")} + + )} +
+
+
+ +

+ {__( + "Upload a horizontal version of your logo for use in documents", + )} +

+
+ {(horizontalLogoPreview || organization.horizontalLogoUrl) && ( +
+ {__("Horizontal +
+ )} + {canUpdate && ( + + {isUpdatingOrganization + ? __("Uploading...") + : horizontalLogoPreview || organization.horizontalLogoUrl + ? __("Change horizontal logo") + : __("Upload horizontal logo")} + + )} + {canUpdate && organization.horizontalLogoUrl && ( + + } + title={__("Delete Horizontal Logo")} + className="max-w-md" + > + +

+ {__("Are you sure you want to delete the horizontal logo?")} +

+

+ {__("This action cannot be undone.")} +

+
+ + + + +
+ )} +
+
+ +
+ +