Skip to content

Commit

Permalink
Domains: Add Glue Records management UI (#84261)
Browse files Browse the repository at this point in the history
* Add initial structure

* Fix styles

* Improve styles

* Add feature flags

* Replace `Remove` link with button

* Improve loading state

* Improve placeholder labels

* Update field labels

* Add limit of glue records

* Improve react-query cached data

* Include placeholder style

* Force lowercase nameservers

* Render `Add Glue Records` action only when necessary

* Fix cancel button behavior

* Lazy query

* Improve data caching

* Only show glue records card for domains registered with us

* Fix comments

* Validate record and IP address fields before saving glue record

* Rename method `handleAddGlueRecord` to `showGlueRecordForm`

* Fix comment

* Add missing dependency for `useEffect`

* Simplify condition

* Only show glue records card for domains registered through Key-Systems

* Add error validation message to glue record fields

* Remove references to domain forwarding and simplify CSS

* Fetch data when expanding card to prevent stale data from being shown

---------

Co-authored-by: Leonardo Sameshima Taba <leonardo.taba@automattic.com>
  • Loading branch information
rafaelgallani and leonardost committed Dec 7, 2023
1 parent abe8454 commit 7ddfb9d
Show file tree
Hide file tree
Showing 16 changed files with 579 additions and 1 deletion.
2 changes: 2 additions & 0 deletions client/components/domains/accordion/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ const Accordion = ( {
isDisabled,
expanded = false,
onClose,
onOpen,
className,
}: AccordionProps ) => {
const classes = classNames( {
Expand Down Expand Up @@ -49,6 +50,7 @@ const Accordion = ( {
</button>
}
onClose={ onClose }
onOpen={ onOpen }
>
{ children }
</FoldableCard>
Expand Down
1 change: 1 addition & 0 deletions client/components/domains/accordion/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ export type AccordionProps = {
subtitle?: string | React.ReactNode;
expanded?: boolean;
onClose?: () => void;
onOpen?: () => void;

isPlaceholder?: boolean;
isDisabled?: boolean;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { QueryKey } from '@tanstack/react-query';

export function domainGlueRecordQueryKey( domainName: string ): QueryKey {
return [ 'glue-record', domainName ];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { domainGlueRecordQueryKey } from 'calypso/data/domains/glue-records/domain-glue-record-query-key';
import {
GlueRecordObject,
GlueRecordQueryData,
} from 'calypso/data/domains/glue-records/use-domain-glue-records-query';
import { DomainsApiError } from 'calypso/lib/domains/types';
import wp from 'calypso/lib/wp';

export default function useDeleteGlueRecordMutation(
domainName: string,
queryOptions: {
onSuccess?: () => void;
onError?: ( error: DomainsApiError ) => void;
}
) {
const queryClient = useQueryClient();
const mutation = useMutation( {
mutationFn: ( glueRecord: GlueRecordObject ) =>
wp.req
.post(
{
path: `/domains/glue-records/${ domainName }`,
apiNamespace: 'wpcom/v2',
method: 'DELETE',
},
{
name_server: glueRecord.record,
}
)
.then( () => glueRecord ),
...queryOptions,
onSuccess( glueRecord: GlueRecordObject ) {
const key = domainGlueRecordQueryKey( domainName );
queryClient.setQueryData( key, ( old: GlueRecordQueryData ) => {
if ( ! old ) {
return [];
}
return old.filter( ( item ) => item.nameserver !== glueRecord.record );
} );
queryOptions.onSuccess?.();
},
} );

const { mutate } = mutation;

const deleteGlueRecord = useCallback(
( glueRecord: GlueRecordObject ) => mutate( glueRecord ),
[ mutate ]
);

return { deleteGlueRecord, ...mutation };
}
56 changes: 56 additions & 0 deletions client/data/domains/glue-records/use-domain-glue-records-query.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { UseQueryResult, useQuery } from '@tanstack/react-query';
import wp from 'calypso/lib/wp';
import { domainGlueRecordQueryKey } from './domain-glue-record-query-key';

export type Maybe< T > = T | null | undefined;
export type GlueRecordResponse = GlueRecordObject[] | null | undefined;

export type GlueRecordObject = {
record: string;
address: string;
};

export type GlueRecordQueryData = Maybe< GlueRecordApiObject[] >;

export type GlueRecordApiObject = {
nameserver: string;
ip_addresses: string[];
};

export const mapGlueRecordObjectToApiObject = ( record: GlueRecordObject ): GlueRecordApiObject => {
return {
nameserver: record.record.toLowerCase(),
ip_addresses: [ record.address ],
};
};

const selectGlueRecords = ( response: GlueRecordApiObject[] | null ): GlueRecordResponse => {
if ( ! response ) {
return null;
}

return response?.map( ( record: GlueRecordApiObject ) => {
return {
record: record.nameserver.toLowerCase(),
address: record.ip_addresses[ 0 ],
};
} );
};

export default function useDomainGlueRecordsQuery(
domainName: string
): UseQueryResult< GlueRecordResponse > {
return useQuery( {
queryKey: domainGlueRecordQueryKey( domainName ),
queryFn: () =>
wp.req.get( {
path: `/domains/glue-records/${ domainName }`,
apiNamespace: 'wpcom/v2',
} ),
refetchOnWindowFocus: false,
select: selectGlueRecords,
enabled: false,
staleTime: 5 * 60 * 1000,
cacheTime: 5 * 60 * 1000,
} );
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
import { useMutation, useQueryClient } from '@tanstack/react-query';
import { useCallback } from 'react';
import { domainGlueRecordQueryKey } from 'calypso/data/domains/glue-records/domain-glue-record-query-key';
import { DomainsApiError } from 'calypso/lib/domains/types';
import wp from 'calypso/lib/wp';
import {
GlueRecordObject,
GlueRecordQueryData,
mapGlueRecordObjectToApiObject,
} from './use-domain-glue-records-query';

export default function useUpdateGlueRecordMutation(
domainName: string,
queryOptions: {
onSuccess?: () => void;
onError?: ( error: DomainsApiError ) => void;
}
) {
const queryClient = useQueryClient();
const mutation = useMutation( {
mutationFn: ( glueRecord: GlueRecordObject ) =>
wp.req
.post(
{
path: `/domains/glue-records`,
apiNamespace: 'wpcom/v2',
},
{
name_server: glueRecord.record.toLowerCase(),
ip_addresses: [ glueRecord.address ],
}
)
.then( () => glueRecord ),
...queryOptions,
onSuccess( glueRecord: GlueRecordObject ) {
const key = domainGlueRecordQueryKey( domainName );
queryClient.setQueryData( key, ( old: GlueRecordQueryData ) => {
if ( ! old ) {
return [ mapGlueRecordObjectToApiObject( glueRecord ) ];
}
return [ ...old, mapGlueRecordObjectToApiObject( glueRecord ) ];
} );
queryOptions.onSuccess?.();
},
onError( error: DomainsApiError ) {
queryOptions.onError?.( error );
},
} );

const { mutate } = mutation;

const updateGlueRecord = useCallback(
( glueRecord: GlueRecordObject ) => mutate( glueRecord ),
[ mutate ]
);

return { updateGlueRecord, ...mutation };
}
Loading

0 comments on commit 7ddfb9d

Please sign in to comment.