diff --git a/apps/console/src/components/customDomains/CreateCustomDomainDialog.tsx b/apps/console/src/components/customDomains/CreateCustomDomainDialog.tsx new file mode 100644 index 000000000..600b3e1b1 --- /dev/null +++ b/apps/console/src/components/customDomains/CreateCustomDomainDialog.tsx @@ -0,0 +1,161 @@ +import { useTranslate } from "@probo/i18n"; +import { graphql } from "react-relay"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + Field, + useDialogRef, + Breadcrumb, +} from "@probo/ui"; +import { z } from "zod"; +import { useFormWithSchema } from "/hooks/useFormWithSchema"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import type { ReactNode } from "react"; +import type { CreateCustomDomainDialogMutation } from "./__generated__/CreateCustomDomainDialogMutation.graphql"; + +const createCustomDomainMutation = graphql` + mutation CreateCustomDomainDialogMutation($input: CreateCustomDomainInput!) { + createCustomDomain(input: $input) { + customDomain { + id + domain + sslStatus + dnsRecords { + type + name + value + ttl + purpose + } + createdAt + updatedAt + verifiedAt + sslExpiresAt + } + } + } +`; + +const schema = z.object({ + domain: z + .string() + .min(1, "Domain is required") + .regex( + /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i, + "Please enter a valid domain (e.g., compliance.example.com)" + ), +}); + +type FormData = z.infer; + +interface CreateCustomDomainDialogProps { + children: ReactNode; + organizationId: string; +} + +export function CreateCustomDomainDialog({ + children, + organizationId, +}: CreateCustomDomainDialogProps) { + const { __ } = useTranslate(); + const dialogRef = useDialogRef(); + + const { register, handleSubmit, formState, reset } = useFormWithSchema( + schema, + { + defaultValues: { + domain: "", + }, + } + ); + + const [createCustomDomain, isCreating] = + useMutationWithToasts( + createCustomDomainMutation, + { + successMessage: __( + "Domain added successfully. Configure the DNS records to verify and activate your domain." + ), + errorMessage: __("Failed to add domain"), + } + ); + + const onSubmit = handleSubmit(async (data: FormData) => { + const normalizedDomain = data.domain + .trim() + .toLowerCase() + .replace(/^https?:\/\//, "") + .replace(/\/$/, ""); + + await createCustomDomain({ + variables: { + input: { + organizationId, + domain: normalizedDomain, + }, + }, + updater: (store, data) => { + // Update the cache by setting the new customDomain on the organization + const organizationRecord = store.get(organizationId); + if (organizationRecord && data?.createCustomDomain?.customDomain) { + const customDomainRecord = store.get( + data.createCustomDomain.customDomain.id + ); + if (customDomainRecord) { + organizationRecord.setLinkedRecord( + customDomainRecord, + "customDomain" + ); + } + } + }, + onSuccess: () => { + reset(); + dialogRef.current?.close(); + }, + }); + }); + + return ( + } + > +
+ +
+

+ {__( + "Enter your domain and we'll generate the DNS records you need to add" + )} +

+
+ + + +
+

+ {__("Examples:")} compliance.example.com, + trust.example.com +

+
+
+ + + +
+
+ ); +} diff --git a/apps/console/src/components/customDomains/CustomDomainManager.tsx b/apps/console/src/components/customDomains/CustomDomainManager.tsx index 396c971a3..179df8e6e 100644 --- a/apps/console/src/components/customDomains/CustomDomainManager.tsx +++ b/apps/console/src/components/customDomains/CustomDomainManager.tsx @@ -1,78 +1,11 @@ -import { useState } from "react"; import { useTranslate } from "@probo/i18n"; -import { graphql, useLazyLoadQuery, useMutation } from "react-relay"; -import { useOrganizationId } from "/hooks/useOrganizationId"; -import { - Button, - Card, - Badge, - Field, - Dialog, - DialogContent, - DialogFooter, - useDialogRef, - useToast, -} from "@probo/ui"; -import type { CustomDomainManagerQuery } from "./__generated__/CustomDomainManagerQuery.graphql"; -import type { CustomDomainManagerCreateMutation } from "./__generated__/CustomDomainManagerCreateMutation.graphql"; +import { Button, Card, Badge, IconPlusLarge } from "@probo/ui"; +import { useMutationWithToasts } from "/hooks/useMutationWithToasts"; +import { graphql } from "relay-runtime"; import type { CustomDomainManagerDeleteMutation } from "./__generated__/CustomDomainManagerDeleteMutation.graphql"; - -const customDomainsQuery = graphql` - query CustomDomainManagerQuery($organizationId: ID!) { - organization: node(id: $organizationId) { - ... on Organization { - id - customDomains(first: 100) { - edges { - node { - id - domain - sslStatus - isActive - dnsRecords { - type - name - value - ttl - purpose - } - createdAt - updatedAt - verifiedAt - sslExpiresAt - } - } - } - } - } - } -`; - -const createCustomDomainMutation = graphql` - mutation CustomDomainManagerCreateMutation($input: CreateCustomDomainInput!) { - createCustomDomain(input: $input) { - customDomainEdge { - node { - id - domain - sslStatus - isActive - dnsRecords { - type - name - value - ttl - purpose - } - createdAt - updatedAt - verifiedAt - sslExpiresAt - } - } - } - } -`; +import { CreateCustomDomainDialog } from "./CreateCustomDomainDialog"; +import { DeleteCustomDomainDialog } from "./DeleteCustomDomainDialog"; +import { DomainDetailsDialog } from "./DomainDetailsDialog"; const deleteCustomDomainMutation = graphql` mutation CustomDomainManagerDeleteMutation($input: DeleteCustomDomainInput!) { @@ -82,280 +15,56 @@ const deleteCustomDomainMutation = graphql` } `; -function DomainDetailsDialog({ - domain, - children, - onDelete, - isDeletingDomain, -}: { - domain: any; - children: React.ReactNode; - onDelete: (domainId: string, domainName: string) => void; - isDeletingDomain: boolean; -}) { - const { __ } = useTranslate(); - const { toast } = useToast(); - const dialogRef = useDialogRef(); - - const getStatusBadge = (domain: any) => { - if (domain.sslStatus === "ACTIVE") { - return {__("Active")}; - } - if (domain.sslStatus === "PROVISIONING" || domain.sslStatus === "RENEWING") { - return {__("Provisioning")}; - } - if (domain.sslStatus === "PENDING") { - return {__("Pending")}; - } - if (domain.sslStatus === "FAILED") { - return {__("Failed")}; - } - if (domain.sslStatus === "EXPIRED") { - return {__("Expired")}; - } - return {__("Unknown")}; - }; - - const copyToClipboard = (text: string) => { - navigator.clipboard.writeText(text); - toast({ - title: __("Copied"), - description: __("Value copied to clipboard"), - variant: "success", - }); - }; - - return ( - - {domain.domain} - {getStatusBadge(domain)} - +interface CustomDomainManagerProps { + organizationId: string; + customDomain: + | { + readonly id: string; + readonly domain: string; + readonly sslStatus: string; + readonly dnsRecords?: + | readonly { + readonly type: string; + readonly name: string; + readonly value: string; + readonly ttl?: number; + readonly purpose: string; + }[] + | null; + readonly createdAt?: string | null; + readonly updatedAt?: string | null; + readonly verifiedAt?: string | null; + readonly sslExpiresAt?: string | null; } - > - - {domain.verifiedAt && ( -

- {__("Verified")} {new Date(domain.verifiedAt).toLocaleDateString()} -

- )} - - {domain.sslStatus === "ACTIVE" ? ( -
-
- - - -
-

{__("Domain is active")}

-

- {__("Your custom domain is verified and SSL certificate is active")} -

- {domain.sslExpiresAt && ( -

- {__("SSL expires")} {new Date(domain.sslExpiresAt).toLocaleDateString()} -

- )} -
-
-
- ) : ( -
-

{__("DNS Configuration")}

-

- {__("Add these DNS records to your domain to complete verification")} -

- -
- {domain.dnsRecords?.map((record: any, index: number) => ( -
-
- {record.type} - {record.purpose} -
-
-
- -
- - {record.name} - - -
-
-
- -
- - {record.value} - - -
-
- {record.ttl && ( -
- TTL: {record.ttl} -
- )} -
-
- ))} -
- - {domain.sslStatus === "PENDING" && ( -
-

- {__("After adding the DNS records, verification will happen automatically. This may take a few minutes to propagate.")} -

-
- )} -
- )} - -
- -
-
-
- ); + | null + | undefined; } -export function CustomDomainManager() { +export function CustomDomainManager({ + organizationId, + customDomain, +}: CustomDomainManagerProps) { const { __ } = useTranslate(); - const { toast } = useToast(); - const organizationId = useOrganizationId(); - const dialogRef = useDialogRef(); - const data = useLazyLoadQuery( - customDomainsQuery, - { organizationId }, - { fetchPolicy: "network-only" } - ); + const [deleteCustomDomain] = + useMutationWithToasts( + deleteCustomDomainMutation, + { + successMessage: __("Domain deleted successfully"), + errorMessage: __("Failed to delete domain"), + } + ); - const [createCustomDomain, isCreatingDomain] = - useMutation(createCustomDomainMutation); - const [deleteCustomDomain, isDeletingDomain] = - useMutation(deleteCustomDomainMutation); - - const [newDomain, setNewDomain] = useState(""); - - const domains = - data.organization?.customDomains?.edges?.map((edge) => edge.node) || []; - - const validateDomain = (domainInput: string): boolean => { - const domainRegex = - /^(?:[a-z0-9](?:[a-z0-9-]{0,61}[a-z0-9])?\.)+[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/i; - return domainRegex.test(domainInput); - }; - - const handleAddDomain = () => { - const trimmedDomain = newDomain - .trim() - .toLowerCase() - .replace(/^https?:\/\//, "") - .replace(/\/$/, ""); - - if (!validateDomain(trimmedDomain)) { - toast({ - title: __("Invalid Domain"), - description: __( - "Please enter a valid domain (e.g., compliance.example.com)" - ), - variant: "error", - }); - return; - } - - createCustomDomain({ - variables: { - input: { - organizationId, - domain: trimmedDomain, - }, - }, - onCompleted: () => { - setNewDomain(""); - dialogRef.current?.close(); - - toast({ - title: __("Domain Added Successfully"), - description: __( - "Configure the DNS records below to verify and activate your domain" - ), - variant: "success", - }); - }, - onError: (error) => { - toast({ - title: __("Failed to Add Domain"), - description: error.message, - variant: "error", - }); - }, - }); - }; - - const handleDeleteDomain = (domainId: string, domainName: string) => { - if (!confirm(`${__("Are you sure you want to delete")} ${domainName}?`)) { - return; - } - - deleteCustomDomain({ - variables: { - input: { domainId }, - }, - onCompleted: () => { - toast({ - title: __("Domain Deleted"), - description: __("The domain has been removed"), - variant: "success", - }); - }, - onError: (error) => { - toast({ - title: __("Failed to Delete Domain"), - description: error.message, - variant: "error", - }); - }, - }); - }; + const domain = customDomain; const getStatusBadge = (domain: any) => { if (domain.sslStatus === "ACTIVE") { return {__("Active")}; } - if (domain.sslStatus === "PROVISIONING" || domain.sslStatus === "RENEWING") { + if ( + domain.sslStatus === "PROVISIONING" || + domain.sslStatus === "RENEWING" + ) { return {__("Provisioning")}; } if (domain.sslStatus === "PENDING") { @@ -370,132 +79,73 @@ export function CustomDomainManager() { return {__("Unknown")}; }; - return ( -
-
-
-

{__("Custom Domains")}

-

- {__("Use your own domain for your trust center")} + const handleDeleteDomain = async () => { + return deleteCustomDomain({ + variables: { + input: { organizationId }, + }, + updater: (store) => { + // Update the cache by setting customDomain to null + const organizationRecord = store.get(organizationId); + if (organizationRecord) { + organizationRecord.setValue(null, "customDomain"); + } + }, + }); + }; + + if (!domain) { + return ( + +

+

+ {__("No custom domain configured")} +

+

+ {__( + "Add your own domain to make your trust center more professional" + )}

+
+ + + +
- -
+ + ); + } - {domains.length === 0 ? ( - -
-
- - - -
-

- {__("No custom domains yet")} -

-

- {__( - "Add your own domain to make your trust center more professional" - )} -

- -
-
- ) : ( - -
- {domains.map((domain: any) => ( - -
-
-
-
{domain.domain}
- {getStatusBadge(domain)} -
- - - -
-
-
- ))} -
-
- )} - - { - setNewDomain(""); - }} - > - -
+ return ( + +
+
+
-

- {__("Add Custom Domain")} -

-

- {__( - "Enter your domain and we'll generate the DNS records you need to add" - )} -

-
- - ) => - setNewDomain(e.target.value) - } - placeholder="compliance.example.com" - help={__("Enter without http:// or https://")} - autoFocus - /> - -
-

- {__("Examples:")} compliance.example.com, - trust.example.com -

+
{domain.domain}
+
+ {domain.verifiedAt + ? `${__("Verified")} ${new Date(domain.verifiedAt).toLocaleDateString()}` + : __("Pending verification")} +
+ {getStatusBadge(domain)}
- - - - -
-
+ +
+ + + + + + + +
+
+ + ); } diff --git a/apps/console/src/components/customDomains/DeleteCustomDomainDialog.tsx b/apps/console/src/components/customDomains/DeleteCustomDomainDialog.tsx new file mode 100644 index 000000000..240528e41 --- /dev/null +++ b/apps/console/src/components/customDomains/DeleteCustomDomainDialog.tsx @@ -0,0 +1,97 @@ +import { useTranslate } from "@probo/i18n"; +import { + Button, + Dialog, + DialogContent, + DialogFooter, + Field, + useDialogRef, + IconTrashCan, +} from "@probo/ui"; +import { useState, useEffect, type ReactNode } from "react"; +import { sprintf } from "@probo/helpers"; + +interface DeleteCustomDomainDialogProps { + children: ReactNode; + domainName: string; + onConfirm: () => Promise; +} + +export function DeleteCustomDomainDialog({ + children, + domainName, + onConfirm, +}: DeleteCustomDomainDialogProps) { + const { __ } = useTranslate(); + const [inputValue, setInputValue] = useState(""); + const [isDeleting, setIsDeleting] = useState(false); + const dialogRef = useDialogRef(); + + const isConfirmDisabled = inputValue !== domainName || isDeleting; + + const handleConfirm = async () => { + if (inputValue === domainName && !isDeleting) { + setIsDeleting(true); + try { + await onConfirm(); + dialogRef.current?.close(); + } finally { + setIsDeleting(false); + } + } + }; + + useEffect(() => { + if (!isDeleting) { + setInputValue(""); + } + }, [isDeleting]); + + return ( + + +

+ {sprintf( + __( + "This will permanently delete the custom domain %s and all its configuration." + ), + domainName + )} +

+ +

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

+ + setInputValue(e.target.value)} + placeholder={domainName} + disabled={isDeleting} + autoComplete="off" + autoFocus + /> +
+ + + +
+ ); +} diff --git a/apps/console/src/components/customDomains/DomainDetailsDialog.tsx b/apps/console/src/components/customDomains/DomainDetailsDialog.tsx new file mode 100644 index 000000000..79ea3c68b --- /dev/null +++ b/apps/console/src/components/customDomains/DomainDetailsDialog.tsx @@ -0,0 +1,192 @@ +import { useTranslate } from "@probo/i18n"; +import { + Button, + Dialog, + DialogContent, + Badge, + useDialogRef, + useToast, +} from "@probo/ui"; +import type { ReactNode } from "react"; + +interface DomainDetailsDialogProps { + children: ReactNode; + domain: { + readonly id: string; + readonly domain: string; + readonly sslStatus: string; + readonly dnsRecords?: + | readonly { + readonly type: string; + readonly name: string; + readonly value: string; + readonly ttl?: number; + readonly purpose: string; + }[] + | null; + readonly verifiedAt?: string | null; + readonly sslExpiresAt?: string | null; + }; +} + +export function DomainDetailsDialog({ + children, + domain, +}: DomainDetailsDialogProps) { + const { __ } = useTranslate(); + const { toast } = useToast(); + const dialogRef = useDialogRef(); + + const getStatusBadge = (domain: DomainDetailsDialogProps["domain"]) => { + if (domain.sslStatus === "ACTIVE") { + return {__("Active")}; + } + if ( + domain.sslStatus === "PROVISIONING" || + domain.sslStatus === "RENEWING" + ) { + return {__("Provisioning")}; + } + if (domain.sslStatus === "PENDING") { + return {__("Pending")}; + } + if (domain.sslStatus === "FAILED") { + return {__("Failed")}; + } + if (domain.sslStatus === "EXPIRED") { + return {__("Expired")}; + } + return {__("Unknown")}; + }; + + const copyToClipboard = (text: string) => { + navigator.clipboard.writeText(text); + toast({ + title: __("Copied"), + description: __("Value copied to clipboard"), + variant: "success", + }); + }; + + return ( + + {domain.domain} + {getStatusBadge(domain)} + + } + > + + {domain.verifiedAt && ( +

+ {__("Verified")} {new Date(domain.verifiedAt).toLocaleDateString()} +

+ )} + + {domain.sslStatus === "ACTIVE" ? ( +
+
+ + + +
+

{__("Domain is active")}

+

+ {__( + "Your custom domain is verified and SSL certificate is active" + )} +

+ {domain.sslExpiresAt && ( +

+ {__("SSL expires")}{" "} + {new Date(domain.sslExpiresAt).toLocaleDateString()} +

+ )} +
+
+
+ ) : ( +
+

{__("DNS Configuration")}

+

+ {__( + "Add these DNS records to your domain to complete verification" + )} +

+ +
+ {domain.dnsRecords?.map((record, index) => ( +
+
+ {record.type} + {record.purpose} +
+
+
+ +
+ + {record.name} + + +
+
+
+ +
+ + {record.value} + + +
+
+ {record.ttl && ( +
+ TTL: {record.ttl} +
+ )} +
+
+ ))} +
+ + {domain.sslStatus === "PENDING" && ( +
+

+ {__( + "After adding the DNS records, verification will happen automatically. This may take a few minutes to propagate." + )} +

+
+ )} +
+ )} +
+
+ ); +} diff --git a/apps/console/src/components/customDomains/__generated__/CreateCustomDomainDialogMutation.graphql.ts b/apps/console/src/components/customDomains/__generated__/CreateCustomDomainDialogMutation.graphql.ts new file mode 100644 index 000000000..7c178b089 --- /dev/null +++ b/apps/console/src/components/customDomains/__generated__/CreateCustomDomainDialogMutation.graphql.ts @@ -0,0 +1,208 @@ +/** + * @generated SignedSource<> + * @lightSyntaxTransform + * @nogrep + */ + +/* tslint:disable */ +/* eslint-disable */ +// @ts-nocheck + +import { ConcreteRequest } from 'relay-runtime'; +export type SSLStatus = "ACTIVE" | "EXPIRED" | "FAILED" | "PENDING" | "PROVISIONING" | "RENEWING"; +export type CreateCustomDomainInput = { + domain: string; + organizationId: string; +}; +export type CreateCustomDomainDialogMutation$variables = { + input: CreateCustomDomainInput; +}; +export type CreateCustomDomainDialogMutation$data = { + readonly createCustomDomain: { + readonly customDomain: { + readonly createdAt: any; + readonly dnsRecords: ReadonlyArray<{ + readonly name: string; + readonly purpose: string; + readonly ttl: number; + readonly type: string; + readonly value: string; + }>; + readonly domain: string; + readonly id: string; + readonly sslExpiresAt: any | null | undefined; + readonly sslStatus: SSLStatus; + readonly updatedAt: any; + readonly verifiedAt: any | null | undefined; + }; + }; +}; +export type CreateCustomDomainDialogMutation = { + response: CreateCustomDomainDialogMutation$data; + variables: CreateCustomDomainDialogMutation$variables; +}; + +const node: ConcreteRequest = (function(){ +var v0 = [ + { + "defaultValue": null, + "kind": "LocalArgument", + "name": "input" + } +], +v1 = [ + { + "alias": null, + "args": [ + { + "kind": "Variable", + "name": "input", + "variableName": "input" + } + ], + "concreteType": "CreateCustomDomainPayload", + "kind": "LinkedField", + "name": "createCustomDomain", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "concreteType": "CustomDomain", + "kind": "LinkedField", + "name": "customDomain", + "plural": false, + "selections": [ + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "id", + "storageKey": null + }, + { + "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 + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "name", + "storageKey": null + }, + { + "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": "verifiedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "sslExpiresAt", + "storageKey": null + } + ], + "storageKey": null + } + ], + "storageKey": null + } +]; +return { + "fragment": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Fragment", + "metadata": null, + "name": "CreateCustomDomainDialogMutation", + "selections": (v1/*: any*/), + "type": "Mutation", + "abstractKey": null + }, + "kind": "Request", + "operation": { + "argumentDefinitions": (v0/*: any*/), + "kind": "Operation", + "name": "CreateCustomDomainDialogMutation", + "selections": (v1/*: any*/) + }, + "params": { + "cacheID": "9a6fad6d53811f74048b3ff0b64e0afe", + "id": null, + "metadata": {}, + "name": "CreateCustomDomainDialogMutation", + "operationKind": "mutation", + "text": "mutation CreateCustomDomainDialogMutation(\n $input: CreateCustomDomainInput!\n) {\n createCustomDomain(input: $input) {\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 verifiedAt\n sslExpiresAt\n }\n }\n}\n" + } +}; +})(); + +(node as any).hash = "312e66e894a78da19c28c7f03d03fe96"; + +export default node; diff --git a/apps/console/src/components/customDomains/__generated__/CustomDomainManagerCreateMutation.graphql.ts b/apps/console/src/components/customDomains/__generated__/CustomDomainManagerCreateMutation.graphql.ts deleted file mode 100644 index d17d69a5d..000000000 --- a/apps/console/src/components/customDomains/__generated__/CustomDomainManagerCreateMutation.graphql.ts +++ /dev/null @@ -1,229 +0,0 @@ -/** - * @generated SignedSource<> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -export type SSLStatus = "ACTIVE" | "EXPIRED" | "EXPIRED" | "FAILED" | "PENDING" | "PROVISIONING" | "RENEWING"; -export type CreateCustomDomainInput = { - domain: string; - organizationId: string; -}; -export type CustomDomainManagerCreateMutation$variables = { - input: CreateCustomDomainInput; -}; -export type CustomDomainManagerCreateMutation$data = { - readonly createCustomDomain: { - readonly customDomainEdge: { - readonly node: { - readonly createdAt: any; - readonly dnsRecords: ReadonlyArray<{ - readonly name: string; - readonly purpose: string; - readonly ttl: number; - readonly type: string; - readonly value: string; - }>; - readonly domain: string; - readonly id: string; - readonly isActive: boolean; - readonly sslExpiresAt: any | null | undefined; - readonly sslStatus: SSLStatus; - readonly updatedAt: any; - readonly verifiedAt: any | null | undefined; - }; - }; - }; -}; -export type CustomDomainManagerCreateMutation = { - response: CustomDomainManagerCreateMutation$data; - variables: CustomDomainManagerCreateMutation$variables; -}; - -const node: ConcreteRequest = (function(){ -var v0 = [ - { - "defaultValue": null, - "kind": "LocalArgument", - "name": "input" - } -], -v1 = [ - { - "alias": null, - "args": [ - { - "kind": "Variable", - "name": "input", - "variableName": "input" - } - ], - "concreteType": "CreateCustomDomainPayload", - "kind": "LinkedField", - "name": "createCustomDomain", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "CustomDomainEdge", - "kind": "LinkedField", - "name": "customDomainEdge", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "CustomDomain", - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "id", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "domain", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sslStatus", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "isActive", - "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 - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "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": "verifiedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sslExpiresAt", - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null - } -]; -return { - "fragment": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Fragment", - "metadata": null, - "name": "CustomDomainManagerCreateMutation", - "selections": (v1/*: any*/), - "type": "Mutation", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Operation", - "name": "CustomDomainManagerCreateMutation", - "selections": (v1/*: any*/) - }, - "params": { - "cacheID": "f10f04ab871fe2b42cc29c5789dc103a", - "id": null, - "metadata": {}, - "name": "CustomDomainManagerCreateMutation", - "operationKind": "mutation", - "text": "mutation CustomDomainManagerCreateMutation(\n $input: CreateCustomDomainInput!\n) {\n createCustomDomain(input: $input) {\n customDomainEdge {\n node {\n id\n domain\n sslStatus\n isActive\n dnsRecords {\n type\n name\n value\n ttl\n purpose\n }\n createdAt\n updatedAt\n verifiedAt\n sslExpiresAt\n }\n }\n }\n}\n" - } -}; -})(); - -(node as any).hash = "f2c706c80df1d0a9fa2e7ea4faa75fb3"; - -export default node; diff --git a/apps/console/src/components/customDomains/__generated__/CustomDomainManagerDeleteMutation.graphql.ts b/apps/console/src/components/customDomains/__generated__/CustomDomainManagerDeleteMutation.graphql.ts index 44ff64ccf..6a79b083a 100644 --- a/apps/console/src/components/customDomains/__generated__/CustomDomainManagerDeleteMutation.graphql.ts +++ b/apps/console/src/components/customDomains/__generated__/CustomDomainManagerDeleteMutation.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<869106a8eb759c253f8578a596b748a0>> + * @generated SignedSource<> * @lightSyntaxTransform * @nogrep */ @@ -10,7 +10,7 @@ import { ConcreteRequest } from 'relay-runtime'; export type DeleteCustomDomainInput = { - domainId: string; + organizationId: string; }; export type CustomDomainManagerDeleteMutation$variables = { input: DeleteCustomDomainInput; diff --git a/apps/console/src/components/customDomains/__generated__/CustomDomainManagerQuery.graphql.ts b/apps/console/src/components/customDomains/__generated__/CustomDomainManagerQuery.graphql.ts deleted file mode 100644 index a161057fb..000000000 --- a/apps/console/src/components/customDomains/__generated__/CustomDomainManagerQuery.graphql.ts +++ /dev/null @@ -1,283 +0,0 @@ -/** - * @generated SignedSource<<04bd8d8635434cd05ea80edbb804b56f>> - * @lightSyntaxTransform - * @nogrep - */ - -/* tslint:disable */ -/* eslint-disable */ -// @ts-nocheck - -import { ConcreteRequest } from 'relay-runtime'; -export type SSLStatus = "ACTIVE" | "EXPIRED" | "EXPIRED" | "FAILED" | "PENDING" | "PROVISIONING" | "RENEWING"; -export type CustomDomainManagerQuery$variables = { - organizationId: string; -}; -export type CustomDomainManagerQuery$data = { - readonly organization: { - readonly customDomains?: { - readonly edges: ReadonlyArray<{ - readonly node: { - readonly createdAt: any; - readonly dnsRecords: ReadonlyArray<{ - readonly name: string; - readonly purpose: string; - readonly ttl: number; - readonly type: string; - readonly value: string; - }>; - readonly domain: string; - readonly id: string; - readonly isActive: boolean; - readonly sslExpiresAt: any | null | undefined; - readonly sslStatus: SSLStatus; - readonly updatedAt: any; - readonly verifiedAt: any | null | undefined; - }; - }>; - }; - readonly id?: string; - }; -}; -export type CustomDomainManagerQuery = { - response: CustomDomainManagerQuery$data; - variables: CustomDomainManagerQuery$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": [ - { - "kind": "Literal", - "name": "first", - "value": 100 - } - ], - "concreteType": "CustomDomainConnection", - "kind": "LinkedField", - "name": "customDomains", - "plural": false, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "CustomDomainEdge", - "kind": "LinkedField", - "name": "edges", - "plural": true, - "selections": [ - { - "alias": null, - "args": null, - "concreteType": "CustomDomain", - "kind": "LinkedField", - "name": "node", - "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, - "kind": "ScalarField", - "name": "isActive", - "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 - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "name", - "storageKey": null - }, - { - "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": "verifiedAt", - "storageKey": null - }, - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "sslExpiresAt", - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": null - } - ], - "storageKey": "customDomains(first:100)" -}; -return { - "fragment": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Fragment", - "metadata": null, - "name": "CustomDomainManagerQuery", - "selections": [ - { - "alias": "organization", - "args": (v1/*: any*/), - "concreteType": null, - "kind": "LinkedField", - "name": "node", - "plural": false, - "selections": [ - { - "kind": "InlineFragment", - "selections": [ - (v2/*: any*/), - (v3/*: any*/) - ], - "type": "Organization", - "abstractKey": null - } - ], - "storageKey": null - } - ], - "type": "Query", - "abstractKey": null - }, - "kind": "Request", - "operation": { - "argumentDefinitions": (v0/*: any*/), - "kind": "Operation", - "name": "CustomDomainManagerQuery", - "selections": [ - { - "alias": "organization", - "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*/) - ], - "type": "Organization", - "abstractKey": null - } - ], - "storageKey": null - } - ] - }, - "params": { - "cacheID": "c561676e49338e212721ed12e1069dd0", - "id": null, - "metadata": {}, - "name": "CustomDomainManagerQuery", - "operationKind": "query", - "text": "query CustomDomainManagerQuery(\n $organizationId: ID!\n) {\n organization: node(id: $organizationId) {\n __typename\n ... on Organization {\n id\n customDomains(first: 100) {\n edges {\n node {\n id\n domain\n sslStatus\n isActive\n dnsRecords {\n type\n name\n value\n ttl\n purpose\n }\n createdAt\n updatedAt\n verifiedAt\n sslExpiresAt\n }\n }\n }\n }\n id\n }\n}\n" - } -}; -})(); - -(node as any).hash = "0aea6f600572fac571dbc0e1e6a3117c"; - -export default node; diff --git a/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts b/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts index 0c6696efe..ff33fcefb 100644 --- a/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts +++ b/apps/console/src/hooks/graph/__generated__/OrganizationGraph_ViewQuery.graphql.ts @@ -1,5 +1,5 @@ /** - * @generated SignedSource<<148c6dc2b419fa0611529004e0a3535a>> + * @generated SignedSource<<7493102ba1f19bb7721e444d0e372629>> * @lightSyntaxTransform * @nogrep */ @@ -61,20 +61,27 @@ v4 = { "name": "email", "storageKey": null }, -v5 = [ - { - "kind": "Literal", - "name": "first", - "value": 100 - } -], +v5 = { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "type", + "storageKey": null +}, v6 = { "alias": null, "args": null, "kind": "ScalarField", "name": "createdAt", "storageKey": null -}; +}, +v7 = [ + { + "kind": "Literal", + "name": "first", + "value": 100 + } +]; return { "fragment": { "argumentDefinitions": (v0/*: any*/), @@ -168,7 +175,89 @@ return { }, { "alias": null, - "args": (v5/*: any*/), + "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": [ + (v5/*: any*/), + (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 + }, + (v6/*: any*/), + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "updatedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "verifiedAt", + "storageKey": null + }, + { + "alias": null, + "args": null, + "kind": "ScalarField", + "name": "sslExpiresAt", + "storageKey": null + } + ], + "storageKey": null + }, + { + "alias": null, + "args": (v7/*: any*/), "concreteType": "UserConnection", "kind": "LinkedField", "name": "users", @@ -211,7 +300,7 @@ return { }, { "alias": null, - "args": (v5/*: any*/), + "args": (v7/*: any*/), "concreteType": "ConnectorConnection", "kind": "LinkedField", "name": "connectors", @@ -235,13 +324,7 @@ return { "selections": [ (v2/*: any*/), (v3/*: any*/), - { - "alias": null, - "args": null, - "kind": "ScalarField", - "name": "type", - "storageKey": null - }, + (v5/*: any*/), (v6/*: any*/) ], "storageKey": null @@ -262,12 +345,12 @@ return { ] }, "params": { - "cacheID": "c19271fc733cfb1de051ccdd98ae9bef", + "cacheID": "793d8f1eb4118deebe64f3025d3e8c31", "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 SettingsPageFragment on Organization {\n id\n name\n logoUrl\n description\n websiteUrl\n email\n headquarterAddress\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n connectors(first: 100) {\n edges {\n node {\n id\n name\n type\n createdAt\n }\n }\n }\n}\n" + "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 SettingsPageFragment on Organization {\n id\n name\n logoUrl\n description\n websiteUrl\n email\n headquarterAddress\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 verifiedAt\n sslExpiresAt\n }\n users(first: 100) {\n edges {\n node {\n id\n fullName\n email\n createdAt\n }\n }\n }\n connectors(first: 100) {\n edges {\n node {\n id\n name\n type\n createdAt\n }\n }\n }\n}\n" } }; })(); diff --git a/apps/console/src/pages/organizations/SettingsPage.tsx b/apps/console/src/pages/organizations/SettingsPage.tsx index 1aa2c7a4a..19e1c9470 100644 --- a/apps/console/src/pages/organizations/SettingsPage.tsx +++ b/apps/console/src/pages/organizations/SettingsPage.tsx @@ -62,6 +62,22 @@ const organizationFragment = graphql` websiteUrl email headquarterAddress + customDomain { + id + domain + sslStatus + dnsRecords { + type + name + value + ttl + purpose + } + createdAt + updatedAt + verifiedAt + sslExpiresAt + } users(first: 100) { edges { node { @@ -117,8 +133,9 @@ export default function SettingsPage({ queryRef }: Props) { const [deleteOrganization, isDeleting] = useDeleteOrganizationMutation(); const users = organization.users.edges.map((edge) => edge.node); - const { formState, handleSubmit, register, reset } = - useFormWithSchema(organizationSchema, { + const { formState, handleSubmit, register, reset } = useFormWithSchema( + organizationSchema, + { defaultValues: { name: organization.name || "", description: organization.description || "", @@ -126,7 +143,8 @@ export default function SettingsPage({ queryRef }: Props) { email: organization.email || "", headquarterAddress: organization.headquarterAddress || "", }, - }); + } + ); useEffect(() => { reset({ @@ -160,7 +178,9 @@ export default function SettingsPage({ queryRef }: Props) { onCompleted() { toast({ title: __("Organization updated"), - description: __("Your organization details have been updated successfully."), + description: __( + "Your organization details have been updated successfully." + ), variant: "success", }); }, @@ -220,80 +240,81 @@ export default function SettingsPage({ queryRef }: Props) { {formState.isSubmitting && } -
- -
- + +
+ + + {__("Change logo")} + +
+
+ +
+ +