UNPKG

@livelike/react-native

Version:

LiveLike React Native package

467 lines (438 loc) 12.9 kB
import { CHOICE_WIDGET_KIND, IRewardTransaction, IWidgetChoiceItem, IWidgetEarnableReward, IWidgetInteraction, IWidgetOptionItem, IWidgetPayload, MULTI_INTERACTION_WIDGET_KINDS, OPTION_WIDGET_KIND, SINGLE_INTERACTION_WIDGET_KINDS, WidgetKind, } from '@livelike/javascript'; import { WidgetResultState, WidgetUIPhase } from '../types'; import { createStore } from './store'; export type WidgetState = { widgetPayload: IWidgetPayload; widgetUIPhase?: WidgetUIPhase; widgetInteractions?: IWidgetInteraction[]; selectedOptionIndex?: number; widgetResultState?: WidgetResultState; widgetRewards?: IWidgetEarnableReward[]; isTimelineWidget?: boolean; }; export type WidgetStoreValue = Record<string, WidgetState>; const initialWidgetStoreValue: WidgetStoreValue = {}; export const widgetStore = createStore(initialWidgetStoreValue); export type BaseWidgetActionArgs = { widgetId: string; }; export type UpdateWidgetPhaseActionArgs = BaseWidgetActionArgs & { widgetUIPhase: WidgetUIPhase; }; export type UpdateWidgetChoicesActionArgs = BaseWidgetActionArgs & { widgetChoices: Pick<IWidgetChoiceItem, 'id' | 'answer_count'>[]; }; export type UpdateWidgetOptionsActionArgs = BaseWidgetActionArgs & { widgetOptions: Pick<IWidgetOptionItem, 'id' | 'vote_count'>[]; }; export type UpdateWidgetInteractionActionArgs = BaseWidgetActionArgs & { widgetInteractions: IWidgetInteraction[]; }; export type UpdateSelectedOptionIndexActionArgs = BaseWidgetActionArgs & { selectedOptionIndex: number; }; export type UpdateWidgetResultStateActionArgs = BaseWidgetActionArgs & { widgetResultState: WidgetResultState; }; export type UpdateWidgetRewardsActionArgs = BaseWidgetActionArgs & { widgetRewards: IWidgetEarnableReward[]; }; export type UpdateWidgetStateActionArgs = BaseWidgetActionArgs & { widgetState: Partial<WidgetState>; }; export type UpdateWidgetAverageMagnitudeArgs = BaseWidgetActionArgs & { averageMagnitude: string; }; export const widgetStoreActions = { updateWidgetStateAction({ widgetId, widgetState, }: UpdateWidgetStateActionArgs) { const prevWidgetState = widgetStore.get()[widgetId]; widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...prevWidgetState, ...widgetState, }, }); }, updateWidgetChoicesAction({ widgetId, widgetChoices, }: UpdateWidgetChoicesActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget choices, widget not found for widgetId=${widgetId}` ); } // check if there's any choice change const changedChoices = widgetState.widgetPayload.choices.filter( (choice, index) => { return Object.entries(widgetChoices[index]).some( ([key, value]) => choice[key] !== widgetChoices[key] ); } ); if (!changedChoices.length) { return; } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetPayload: { ...widgetState.widgetPayload, choices: widgetChoices.map((choice, index) => ({ ...widgetState.widgetPayload?.choices?.[index], ...choice, })), }, }, }); }, updateWidgetOptionsAction({ widgetId, widgetOptions, }: UpdateWidgetOptionsActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget options, widget not found for widgetId=${widgetId}` ); } // check if there's any option vote change const changedOptions = widgetState.widgetPayload.options.filter( (option, index) => { return Object.entries(widgetOptions[index]).some( ([key, value]) => option[key] !== widgetOptions[index][key] ); } ); if (!changedOptions.length) { return; } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetPayload: { ...widgetState.widgetPayload, options: widgetOptions.map((option, index) => ({ ...widgetState.widgetPayload?.options?.[index], ...option, })), }, }, }); }, updateWidgetUIPhaseAction({ widgetId, widgetUIPhase, }: UpdateWidgetPhaseActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error('Error while updating widget phase, Widget Id not found'); } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetUIPhase, }, }); }, updateWidgetInteractionAction({ widgetId, widgetInteractions, }: UpdateWidgetInteractionActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget interaction, widget state not found for widgetId=${widgetId}` ); } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetInteractions, widgetResultState: getWidgetResultState({ ...widgetState, widgetInteractions, }), widgetUIPhase: getWidgetUIPhaseOnInteractionUpdate(widgetState), }, }); }, updateSelectedOptionIndexAction({ widgetId, selectedOptionIndex, }: UpdateSelectedOptionIndexActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget selected option, widget state not found for widgetId=${widgetId}` ); } const { widgetPayload, selectedOptionIndex: prevSelectedOptionIndex } = widgetState; const { options, choices, kind } = widgetPayload; const isChoiceWidgetKind = CHOICE_WIDGET_KIND.includes(kind); const updatedChoicesOrOptions = isChoiceWidgetKind ? getUpdatedChoices({ choices, selectedOptionIndex, prevSelectedOptionIndex, }) : getUpdatedOptions({ options, selectedOptionIndex, prevSelectedOptionIndex, }); widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetPayload: { ...widgetState.widgetPayload, [isChoiceWidgetKind ? 'choices' : 'options']: updatedChoicesOrOptions, }, selectedOptionIndex, }, }); }, updateWidgetResultStateAction({ widgetId, widgetResultState, }: UpdateWidgetResultStateActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget selected option, widget state not found for widgetId=${widgetId}` ); } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetResultState, }, }); }, updateWidgetRewardsAction({ widgetId, widgetRewards, }: UpdateWidgetRewardsActionArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget selected option, widget state not found for widgetId=${widgetId}` ); } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetRewards, }, }); }, updateWidgetAverageMagnitude({ widgetId, averageMagnitude, }: UpdateWidgetAverageMagnitudeArgs) { const widgetState = widgetStore.get()[widgetId]; if (!widgetState) { throw new Error( `Error while updating widget selected option, widget state not found for widgetId=${widgetId}` ); } widgetStore.set({ ...widgetStore.get(), [widgetId]: { ...widgetState, widgetPayload: { ...widgetState.widgetPayload, average_magnitude: averageMagnitude, }, }, }); }, }; function getUpdatedOptions({ options, prevSelectedOptionIndex, selectedOptionIndex, }: { options: IWidgetOptionItem[]; prevSelectedOptionIndex: number; selectedOptionIndex: number; }) { let validOptionUpdate = true; const updatedOptions = options.map((option, index) => { let { vote_count } = option; if (index === selectedOptionIndex) { vote_count += 1; } else if (index === prevSelectedOptionIndex) { vote_count -= 1; } if (vote_count < 0) { validOptionUpdate = false; } return { ...option, vote_count, }; }); return validOptionUpdate ? updatedOptions : options; } function getUpdatedChoices({ choices, prevSelectedOptionIndex, selectedOptionIndex, }: { choices: IWidgetChoiceItem[]; prevSelectedOptionIndex: number; selectedOptionIndex: number; }) { return choices.map((choice, index) => { let { answer_count } = choice; if (index === selectedOptionIndex) { answer_count += 1; } else if (index === prevSelectedOptionIndex) { answer_count -= 1; } return { ...choice, answer_count, }; }); } export function getWidgetResultState(widgetState: WidgetState) { const { widgetPayload, widgetInteractions } = widgetState; switch (widgetPayload.kind) { case WidgetKind.TEXT_PREDICTION: case WidgetKind.IMAGE_PREDICTION: case WidgetKind.TEXT_NUMBER_PREDICTION: case WidgetKind.IMAGE_NUMBER_PREDICTION: { const followUp = widgetPayload?.follow_ups?.[widgetPayload?.follow_ups?.length - 1]; return widgetInteractions?.length && followUp && followUp.status === 'published' ? WidgetResultState.SHOWN : WidgetResultState.HIDDEN; } case WidgetKind.TEXT_PREDICTION_FOLLOW_UP: case WidgetKind.IMAGE_PREDICTION_FOLLOW_UP: case WidgetKind.IMAGE_NUMBER_PREDICTION_FOLLOW_UP: case WidgetKind.TEXT_NUMBER_PREDICTION_FOLLOW_UP: { return WidgetResultState.SHOWN; } default: { return widgetInteractions?.length ? WidgetResultState.SHOWN : WidgetResultState.HIDDEN; } } } export function getWidgetUIPhaseOnInteractionUpdate(widgetState: WidgetState) { const { widgetPayload } = widgetState; if (SINGLE_INTERACTION_WIDGET_KINDS.includes(widgetPayload.kind)) { return WidgetUIPhase.SUBMITTED; } else if (MULTI_INTERACTION_WIDGET_KINDS.includes(widgetPayload.kind)) { return WidgetUIPhase.INTERACTIVE; } return undefined; } export function getWidgeUIPhase(widgetState: WidgetState) { const { widgetPayload, widgetInteractions } = widgetState; if ( widgetPayload?.interactive_until && isTimeExpired(widgetPayload?.interactive_until) ) { return WidgetUIPhase.EXPIRED; } const followUp = widgetPayload?.follow_ups?.[widgetPayload?.follow_ups?.length - 1]; switch (widgetPayload.kind) { case WidgetKind.TEXT_PREDICTION: case WidgetKind.IMAGE_PREDICTION: { return followUp && followUp.status === 'published' ? WidgetUIPhase.FOLLOW_UP_PUBLISHED : WidgetUIPhase.INTERACTIVE; } case WidgetKind.TEXT_NUMBER_PREDICTION: case WidgetKind.IMAGE_NUMBER_PREDICTION: { if (followUp && followUp.status === 'published') { return WidgetUIPhase.FOLLOW_UP_PUBLISHED; } return widgetInteractions?.length ? WidgetUIPhase.SUBMITTED : WidgetUIPhase.INTERACTIVE; } case WidgetKind.TEXT_PREDICTION_FOLLOW_UP: case WidgetKind.IMAGE_PREDICTION_FOLLOW_UP: case WidgetKind.IMAGE_NUMBER_PREDICTION_FOLLOW_UP: case WidgetKind.TEXT_NUMBER_PREDICTION_FOLLOW_UP: { return WidgetUIPhase.FOLLOW_UP_PUBLISHED; } default: { return widgetInteractions?.length ? WidgetUIPhase.SUBMITTED : WidgetUIPhase.INTERACTIVE; } } } export function getSelectedOptionIndex({ widgetPayload, widgetInteractions, }: WidgetState) { const { kind, choices, options } = widgetPayload; if (CHOICE_WIDGET_KIND.includes(kind)) { return choices.findIndex(({ id }) => widgetInteractions?.find(({ choice_id }) => choice_id === id) ); } else if (OPTION_WIDGET_KIND.includes(kind)) { return options.findIndex(({ id }) => widgetInteractions?.find(({ option_id }) => option_id === id) ); } return -1; } export function getWidgetRewardsFromRewardTransactions( rewardTransactions: IRewardTransaction[] ): IWidgetEarnableReward[] { return rewardTransactions.map( ({ reward_item_amount, reward_item_name, reward_item_id, reward_action_key, }) => ({ reward_item_amount, reward_item_name, reward_item_id, reward_action_key, }) ); } export function isTimeExpired(expiryTimestamp: string) { const expiredTime = new Date(expiryTimestamp); const timeout = expiredTime.getTime() - Date.now(); return timeout <= 0; }