diff --git a/packages/x-components/src/index.ts b/packages/x-components/src/index.ts index c32bee25e..f215f3c1b 100644 --- a/packages/x-components/src/index.ts +++ b/packages/x-components/src/index.ts @@ -26,6 +26,7 @@ export * from './x-modules/popular-searches'; export * from './x-modules/queries-preview'; export * from './x-modules/query-suggestions'; export * from './x-modules/recommendations'; +export * from './x-modules/related-prompts'; export * from './x-modules/related-tags'; export * from './x-modules/scroll'; export * from './x-modules/search'; diff --git a/packages/x-components/src/wiring/events.types.ts b/packages/x-components/src/wiring/events.types.ts index fbca9c3ef..835e7f679 100644 --- a/packages/x-components/src/wiring/events.types.ts +++ b/packages/x-components/src/wiring/events.types.ts @@ -22,6 +22,7 @@ import { UrlXEvents } from '../x-modules/url/events.types'; import { XModuleName } from '../x-modules/x-modules.types'; import { SemanticQueriesXEvents } from '../x-modules/semantic-queries/events.types'; import { ExperienceControlsXEvents } from '../x-modules/experience-controls/events.types'; +import { RelatedPromptsXEvents } from '../x-modules/related-prompts/events.types'; import { WireMetadata } from './wiring.types'; /* eslint-disable max-len */ /**. @@ -51,6 +52,7 @@ import { WireMetadata } from './wiring.types'; * {@link https://github.com/empathyco/x/blob/main/packages/x-components/src/x-modules/search/events.types.ts | SearchXEvents} * {@link https://github.com/empathyco/x/blob/main/packages/x-components/src/x-modules/tagging/events.types.ts | TaggingXEvents} * {@link https://github.com/empathyco/x/blob/main/packages/x-components/src/x-modules/url/events.types.ts | UrlXEvents} + * {@link https://github.com/empathyco/x/blob/main/packages/x-components/src/x-modules/related-prompts/events.types.ts | UrlXEvents} * * @public */ @@ -73,7 +75,8 @@ export interface XEventsTypes SemanticQueriesXEvents, TaggingXEvents, ExperienceControlsXEvents, - UrlXEvents { + UrlXEvents, + RelatedPromptsXEvents { /** * The provided number of columns of a grid has changed. * Payload: the columns number. diff --git a/packages/x-components/src/x-modules/related-prompts/components/index.ts b/packages/x-components/src/x-modules/related-prompts/components/index.ts new file mode 100644 index 000000000..557325c88 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/components/index.ts @@ -0,0 +1,2 @@ +export { default as RelatedPrompt } from './related-prompt.vue'; +export { default as RelatedPromptsList } from './related-prompts-list.vue'; diff --git a/packages/x-components/src/x-modules/related-prompts/components/related-prompt.vue b/packages/x-components/src/x-modules/related-prompts/components/related-prompt.vue new file mode 100644 index 000000000..9b141cf05 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/components/related-prompt.vue @@ -0,0 +1,106 @@ + + diff --git a/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue b/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue new file mode 100644 index 000000000..e8eb1fbf0 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/components/related-prompts-list.vue @@ -0,0 +1,185 @@ + diff --git a/packages/x-components/src/x-modules/related-prompts/events.types.ts b/packages/x-components/src/x-modules/related-prompts/events.types.ts new file mode 100644 index 000000000..589bf822d --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/events.types.ts @@ -0,0 +1,14 @@ +import { RelatedPromptsRequest } from '@empathyco/x-types'; + +/** + * Dictionary of the events of RelatedPrompts XModule, where each key is the event name, + * and the value is the event payload type or `void` if it has no payload. + */ +export interface RelatedPromptsXEvents { + /** + * Any property of the related-prompts request has changed + * Payload: The new related-prompts request or `null` if there is not enough data in the state + * to conform a valid request. + */ + RelatedPromptsRequestUpdated: RelatedPromptsRequest | null; +} diff --git a/packages/x-components/src/x-modules/related-prompts/index.ts b/packages/x-components/src/x-modules/related-prompts/index.ts new file mode 100644 index 000000000..0baeb1b99 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/index.ts @@ -0,0 +1,5 @@ +export * from './components'; +export * from './events.types'; +export * from './store'; +export * from './wiring'; +export * from './x-module'; diff --git a/packages/x-components/src/x-modules/related-prompts/store/actions/fetch-and-save-related-prompts.action.ts b/packages/x-components/src/x-modules/related-prompts/store/actions/fetch-and-save-related-prompts.action.ts new file mode 100644 index 000000000..63f83c7d0 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/actions/fetch-and-save-related-prompts.action.ts @@ -0,0 +1,30 @@ +import { RelatedPrompt, RelatedPromptsRequest } from '@empathyco/x-types'; +import { createFetchAndSaveActions } from '../../../../store/utils/fetch-and-save-action.utils'; +import { RelatedPromptsActionContext } from '../types'; + +const { fetchAndSave, cancelPrevious } = createFetchAndSaveActions< + RelatedPromptsActionContext, + RelatedPromptsRequest | null, + RelatedPrompt[] | null +>({ + fetch({ dispatch }, request) { + return dispatch('fetchRelatedPrompts', request); + }, + onSuccess({ commit }, relatedPrompts) { + if (relatedPrompts) { + commit('setRelatedPromptsProducts', relatedPrompts); + } + } +}); + +/** + * Default implementation for + * {@link RelatedPromptsActions.fetchAndSaveRelatedPrompts} action. + */ +export const fetchAndSaveRelatedPrompts = fetchAndSave; + +/** + * Default implementation for + * {@link RelatedPromptsActions.cancelFetchAndSaveRelatedPrompts} action. + */ +export const cancelFetchAndSaveRelatedPrompts = cancelPrevious; diff --git a/packages/x-components/src/x-modules/related-prompts/store/actions/fetch-related-prompts.action.ts b/packages/x-components/src/x-modules/related-prompts/store/actions/fetch-related-prompts.action.ts new file mode 100644 index 000000000..5acbfaedf --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/actions/fetch-related-prompts.action.ts @@ -0,0 +1,19 @@ +import { XPlugin } from '../../../../plugins/x-plugin'; +import { RelatedPromptsXStoreModule } from '../types'; + +/** + * Default implementation for the {@link RelatedPromptsActions.fetchRelatedPrompts}. + * + * @param _ - The {@link https://vuex.vuejs.org/guide/actions.html | context} of the actions, + * provided by Vuex. + * @param request - The related prompts request to make. + * @returns The related prompts response. + */ +export const fetchRelatedPrompts: RelatedPromptsXStoreModule['actions']['fetchRelatedPrompts'] = ( + _, + request +) => { + return request + ? XPlugin.adapter.relatedPrompts(request).then(({ relatedPrompts }) => relatedPrompts) + : null; +}; diff --git a/packages/x-components/src/x-modules/related-prompts/store/emitters.ts b/packages/x-components/src/x-modules/related-prompts/store/emitters.ts new file mode 100644 index 000000000..c91871e06 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/emitters.ts @@ -0,0 +1,9 @@ +import { createStoreEmitters } from '../../../store'; +import { relatedPromptsXStoreModule } from './module'; + +/** + * {@link StoreEmitters} For the related-prompts module. + */ +export const relatedPromptsStoreEmitters = createStoreEmitters(relatedPromptsXStoreModule, { + RelatedPromptsRequestUpdated: (_, getters) => getters.request +}); diff --git a/packages/x-components/src/x-modules/related-prompts/store/getters/request.getter.ts b/packages/x-components/src/x-modules/related-prompts/store/getters/request.getter.ts new file mode 100644 index 000000000..89cabb8d1 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/getters/request.getter.ts @@ -0,0 +1,13 @@ +import { RelatedPromptsXStoreModule } from '../types'; + +/** + * Default implementation for the {@link RelatedPromptsGetters.request} getter. + * + * @param state - Current {@link https://vuex.vuejs.org/guide/state.html | state} of the related + * prompts module. + * + * @returns The related prompts request to fetch data from the API. + */ +export const request: RelatedPromptsXStoreModule['getters']['request'] = ({ params, query }) => { + return query ? { query, extraParams: params } : null; +}; diff --git a/packages/x-components/src/x-modules/related-prompts/store/index.ts b/packages/x-components/src/x-modules/related-prompts/store/index.ts new file mode 100644 index 000000000..5b601e646 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/index.ts @@ -0,0 +1,6 @@ +export * from './actions/fetch-and-save-related-prompts.action'; +export * from './actions/fetch-related-prompts.action'; +export { request as relatedPromptRequest } from './getters/request.getter'; +export * from './emitters'; +export * from './module'; +export * from './types'; diff --git a/packages/x-components/src/x-modules/related-prompts/store/module.ts b/packages/x-components/src/x-modules/related-prompts/store/module.ts new file mode 100644 index 000000000..cac37857a --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/module.ts @@ -0,0 +1,39 @@ +import { setStatus } from '../../../store/utils/status-store.utils'; +import { setQuery } from '../../../store/utils/query.utils'; +import { RelatedPromptsXStoreModule } from './types'; +import { + cancelFetchAndSaveRelatedPrompts, + fetchAndSaveRelatedPrompts +} from './actions/fetch-and-save-related-prompts.action'; +import { fetchRelatedPrompts } from './actions/fetch-related-prompts.action'; +import { request } from './getters/request.getter'; + +export const relatedPromptsXStoreModule: RelatedPromptsXStoreModule = { + state: () => ({ + query: '', + relatedPrompts: [], + status: 'initial', + params: {} + }), + getters: { + request + }, + mutations: { + setStatus, + setQuery, + setParams(state, params) { + state.params = params; + }, + setRelatedPromptsProducts(state, products) { + state.relatedPrompts = products; + }, + resetRelatedPromptsState(state) { + state.relatedPrompts = []; + } + }, + actions: { + fetchRelatedPrompts, + fetchAndSaveRelatedPrompts, + cancelFetchAndSaveRelatedPrompts + } +}; diff --git a/packages/x-components/src/x-modules/related-prompts/store/types.ts b/packages/x-components/src/x-modules/related-prompts/store/types.ts new file mode 100644 index 000000000..0df7c349f --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/store/types.ts @@ -0,0 +1,91 @@ +import { RelatedPrompt, RelatedPromptsRequest } from '@empathyco/x-types'; +import { Dictionary } from '@empathyco/x-utils'; +import { QueryMutations, QueryState } from '../../../store/utils/query.utils'; +import { StatusMutations, StatusState } from '../../../store/utils/status-store.utils'; +import { XActionContext, XStoreModule } from '../../../store'; + +/** + * Related prompts module state. + */ +export interface RelatedPromptsState extends StatusState, QueryState { + /** The list of the related-prompts, related to the `query` property of the state. */ + relatedPrompts: RelatedPrompt[]; + /** The extra params property of the state. */ + params: Dictionary; +} + +/** + * Related prompts module getters. + */ +export interface RelatedPromptsGetters { + /** + * Request object to retrieve the related prompts using the search adapter, or null if there is + * no valid data to conform a valid request. + */ + request: RelatedPromptsRequest | null; +} + +/** + * Related prompts module mutations. + */ +export interface RelatedPromptsMutations extends StatusMutations, QueryMutations { + /** + * Sets the extra params of the module. + * + * @param params - The new extra params. + */ + setParams(params: Dictionary): void; + /** + * Sets the related prompts of the module. + * + * @param relatedPrompts - The new related prompts to save to the state. + */ + setRelatedPromptsProducts(products: RelatedPrompt[]): void; + /** + * Resets the related prompts state. + */ + resetRelatedPromptsState(): void; +} + +/** + * Related prompts module actions. + */ +export interface RelatedPromptsActions { + /** + * Requests a new set of related prompts for the module query, and returns them. + * + * @param request - The related prompts request. + */ + fetchRelatedPrompts(request: RelatedPromptsRequest | null): RelatedPrompt[] | null; + /** + * Requests a new set of related prompts and stores them in the module. + * + * @param request - The related prompts request. + */ + fetchAndSaveRelatedPrompts(request: RelatedPromptsRequest | null): void; + /** + * Cancels / interrupt {@link RelatedPromptsActions.fetchAndSaveRelatedPrompts} + * synchronous promise. + */ + cancelFetchAndSaveRelatedPrompts(): void; +} + +/** + * Related prompts store module. + */ +export type RelatedPromptsXStoreModule = XStoreModule< + RelatedPromptsState, + RelatedPromptsGetters, + RelatedPromptsMutations, + RelatedPromptsActions +>; + +/** + * Alias type for actions context of the {@link RelatedPromptsXStoreModule}. + */ +export type RelatedPromptsActionContext = XActionContext< + RelatedPromptsState, + RelatedPromptsGetters, + RelatedPromptsMutations, + RelatedPromptsActions +>; diff --git a/packages/x-components/src/x-modules/related-prompts/types.ts b/packages/x-components/src/x-modules/related-prompts/types.ts new file mode 100644 index 000000000..3b26690fb --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/types.ts @@ -0,0 +1,10 @@ +import { RelatedPrompt } from '@empathyco/x-types'; +import { ListItem } from '../../utils'; + +/** + * Related promts group interface for the RelatedPrompts. + */ +export interface RelatedPromptsGroup extends ListItem { + modelName: 'RelatedPromptsGroup'; + relatedPrompts: RelatedPrompt[]; +} diff --git a/packages/x-components/src/x-modules/related-prompts/wiring.ts b/packages/x-components/src/x-modules/related-prompts/wiring.ts new file mode 100644 index 000000000..bf229a306 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/wiring.ts @@ -0,0 +1,95 @@ +import { + namespacedWireCommit, + namespacedWireCommitWithoutPayload, + namespacedWireDispatch, + namespacedWireDispatchWithoutPayload +} from '../../wiring/namespaced-wires.factory'; +import { + NamespacedWireCommit, + NamespacedWireCommitWithoutPayload +} from '../../wiring/namespaced-wiring.types'; +import { createWiring } from '../../wiring/wiring.utils'; + +/** + * `relatedPrompts` {@link XModuleName | XModule name}. + */ +const moduleName = 'relatedPrompts'; + +/** + * WireCommit for {@link RelatedPromptsXModule}. + */ +const wireCommit: NamespacedWireCommit = namespacedWireCommit(moduleName); + +/** + * WireCommitWithoutPayload for {@link RelatedPromptsXModule}. + */ +const wireCommitWithoutPayload: NamespacedWireCommitWithoutPayload = + namespacedWireCommitWithoutPayload(moduleName); + +/** + * WireDispatch for {@link RelatedPromptsXModule}. + */ +const wireDispatch = namespacedWireDispatch(moduleName); + +/** + * WireDispatchWithoutPayload for {@link RelatedPromptsXModule}. + */ +const wireDispatchWithoutPayload = namespacedWireDispatchWithoutPayload(moduleName); + +/** + * Sets the related prompts state `query`. + */ +const setRelatedPromptsQuery = wireCommit('setQuery'); + +/** + * Sets the related prompts state `query` from url params. + */ +const setRelatedPromptsQueryFromUrl = wireCommit( + 'setQuery', + ({ eventPayload: { query } }) => query +); + +/** + * Sets the related prompts state `params`. + */ +export const setRelatedPromptsExtraParams = wireCommit('setParams'); + +/** + * Resets the related prompts state. + */ +const resetRelatedPromptsStateWire = wireCommitWithoutPayload('resetRelatedPromptsState'); + +/** + * Fetches and saves the related prompts response. + */ +const fetchAndSaveRelatedPromptsResponseWire = wireDispatch('fetchAndSaveRelatedPrompts'); + +/** + * Cancels the fetch and save related prompts response. + */ +const cancelFetchAndSaveSearchResponseWire = wireDispatchWithoutPayload( + 'cancelFetchAndSaveRelatedPrompts' +); + +/** + * Wiring configuration for the {@link RelatedPromptsXModule | related prompts module}. + */ +export const relatedPromptsWiring = createWiring({ + ParamsLoadedFromUrl: { + setRelatedPromptsQueryFromUrl + }, + UserAcceptedAQuery: { + setRelatedPromptsQuery + }, + UserClearedQuery: { + cancelFetchAndSaveSearchResponseWire, + resetRelatedPromptsStateWire, + setRelatedPromptsQuery + }, + RelatedPromptsRequestUpdated: { + fetchAndSaveRelatedPromptsResponseWire + }, + ExtraParamsChanged: { + setRelatedPromptsExtraParams + } +}); diff --git a/packages/x-components/src/x-modules/related-prompts/x-module.ts b/packages/x-components/src/x-modules/related-prompts/x-module.ts new file mode 100644 index 000000000..fb5abd538 --- /dev/null +++ b/packages/x-components/src/x-modules/related-prompts/x-module.ts @@ -0,0 +1,24 @@ +import { XPlugin } from '../../plugins/x-plugin'; +import { XModule } from '../x-modules.types'; +import { RelatedPromptsXStoreModule } from './store/types'; +import { relatedPromptsXStoreModule } from './store/module'; +import { relatedPromptsStoreEmitters } from './store/emitters'; +import { relatedPromptsWiring } from './wiring'; + +/** + * RelatedPrompts {@link XModule} alias. + */ +export type RelatedPromptsXModule = XModule; + +/** + * Related Prompts {@link XModule} implementation. This module is auto-registered as soon as you + * import any component from the `related-prompts` entry point. + */ +export const relatedPromptsXModule: RelatedPromptsXModule = { + name: 'relatedPrompts', + storeModule: relatedPromptsXStoreModule, + storeEmitters: relatedPromptsStoreEmitters, + wiring: relatedPromptsWiring +}; + +XPlugin.registerXModule(relatedPromptsXModule); diff --git a/packages/x-components/src/x-modules/x-modules.types.ts b/packages/x-components/src/x-modules/x-modules.types.ts index 858ec432e..c9f97a7a1 100644 --- a/packages/x-components/src/x-modules/x-modules.types.ts +++ b/packages/x-components/src/x-modules/x-modules.types.ts @@ -20,6 +20,7 @@ import { SemanticQueriesXModule } from './semantic-queries/x-module'; import { TaggingXModule } from './tagging'; import { UrlXModule } from './url'; import { ExperienceControlsXModule } from './experience-controls/x-module'; +import { RelatedPromptsXModule } from './related-prompts/x-module'; /** * Gives each {@link XModule} a name, that can be used to retrieve then its value. @@ -38,6 +39,7 @@ export interface XModulesTree { queriesPreview: QueriesPreviewXModule; querySuggestions: QuerySuggestionsXModule; recommendations: RecommendationsXModule; + relatedPrompts: RelatedPromptsXModule; relatedTags: RelatedTagsXModule; scroll: ScrollXModule; search: SearchXModule;