diff --git a/app/soapbox/entity-store/hooks/index.ts b/app/soapbox/entity-store/hooks/index.ts index 07d597912..af27c8f3e 100644 --- a/app/soapbox/entity-store/hooks/index.ts +++ b/app/soapbox/entity-store/hooks/index.ts @@ -1,2 +1,3 @@ export { useEntities } from './useEntities'; -export { useEntity } from './useEntity'; \ No newline at end of file +export { useEntity } from './useEntity'; +export { useEntityActions } from './useEntityActions'; \ No newline at end of file diff --git a/app/soapbox/entity-store/hooks/types.ts b/app/soapbox/entity-store/hooks/types.ts new file mode 100644 index 000000000..89992c12d --- /dev/null +++ b/app/soapbox/entity-store/hooks/types.ts @@ -0,0 +1,6 @@ +import type { Entity } from '../types'; +import type z from 'zod'; + +type EntitySchema = z.ZodType; + +export type { EntitySchema }; \ No newline at end of file diff --git a/app/soapbox/entity-store/hooks/useEntities.ts b/app/soapbox/entity-store/hooks/useEntities.ts index 1d1cd5eed..f1c817bd4 100644 --- a/app/soapbox/entity-store/hooks/useEntities.ts +++ b/app/soapbox/entity-store/hooks/useEntities.ts @@ -8,6 +8,7 @@ import { filteredArray } from 'soapbox/schemas/utils'; import { entitiesFetchFail, entitiesFetchRequest, entitiesFetchSuccess } from '../actions'; import type { Entity, EntityListState } from '../types'; +import type { EntitySchema } from './types'; import type { RootState } from 'soapbox/store'; /** Tells us where to find/store the entity in the cache. */ @@ -21,7 +22,7 @@ type EntityPath = [ /** Additional options for the hook. */ interface UseEntitiesOpts { /** A zod schema to parse the API entities. */ - schema?: z.ZodType + schema?: EntitySchema /** * Time (milliseconds) until this query becomes stale and should be refetched. * It is 1 minute by default, and can be set to `Infinity` to opt-out of automatic fetching. diff --git a/app/soapbox/entity-store/hooks/useEntity.ts b/app/soapbox/entity-store/hooks/useEntity.ts index 92f20560e..1dad1ff1e 100644 --- a/app/soapbox/entity-store/hooks/useEntity.ts +++ b/app/soapbox/entity-store/hooks/useEntity.ts @@ -6,13 +6,14 @@ import { useApi, useAppDispatch, useAppSelector } from 'soapbox/hooks'; import { importEntities } from '../actions'; import type { Entity } from '../types'; +import type { EntitySchema } from './types'; type EntityPath = [entityType: string, entityId: string] /** Additional options for the hook. */ -interface UseEntityOpts { +interface UseEntityOpts { /** A zod schema to parse the API entity. */ - schema?: z.ZodType + schema?: EntitySchema /** Whether to refetch this entity every time the hook mounts, even if it's already in the store. */ refetch?: boolean } diff --git a/app/soapbox/entity-store/hooks/useEntityActions.ts b/app/soapbox/entity-store/hooks/useEntityActions.ts new file mode 100644 index 000000000..c1e40f37e --- /dev/null +++ b/app/soapbox/entity-store/hooks/useEntityActions.ts @@ -0,0 +1,73 @@ +import { z } from 'zod'; + +import { useApi, useAppDispatch } from 'soapbox/hooks'; + +import { importEntities } from '../actions'; + +import type { Entity } from '../types'; +import type { EntitySchema } from './types'; +import type { AxiosResponse } from 'axios'; + +type EntityPath = [entityType: string, listKey?: string] + +interface UseEntityActionsOpts { + schema?: EntitySchema +} + +interface CreateEntityResult { + response: AxiosResponse + entity: TEntity +} + +interface DeleteEntityResult { + response: AxiosResponse +} + +interface EntityActionEndpoints { + post?: string + delete?: string +} + +function useEntityActions( + path: EntityPath, + endpoints: EntityActionEndpoints, + opts: UseEntityActionsOpts = {}, +) { + const api = useApi(); + const dispatch = useAppDispatch(); + const [entityType, listKey] = path; + + function createEntity(params: P): Promise> { + if (!endpoints.post) return Promise.reject(endpoints); + + return api.post(endpoints.post, params).then((response) => { + const schema = opts.schema || z.custom(); + const entity = schema.parse(response.data); + + // TODO: optimistic updating + dispatch(importEntities([entity], entityType, listKey)); + + return { + response, + entity, + }; + }); + } + + function deleteEntity(entityId: string): Promise { + if (!endpoints.delete) return Promise.reject(endpoints); + return api.delete(endpoints.delete.replaceAll(':id', entityId)).then((response) => { + + return { + response, + }; + }); + } + + return { + createEntity: endpoints.post ? createEntity : undefined, + deleteEntity: endpoints.delete ? deleteEntity : undefined, + }; +} + +export { useEntityActions }; \ No newline at end of file