/ components / permissions / AskUserQuestionPermissionRequest / use-multiple-choice-state.ts
use-multiple-choice-state.ts
  1  import { useCallback, useReducer } from 'react'
  2  
  3  export type AnswerValue = string
  4  
  5  export type QuestionState = {
  6    selectedValue?: string | string[]
  7    textInputValue: string
  8  }
  9  
 10  type State = {
 11    currentQuestionIndex: number
 12    answers: Record<string, AnswerValue>
 13    questionStates: Record<string, QuestionState>
 14    isInTextInput: boolean
 15  }
 16  
 17  type Action =
 18    | { type: 'next-question' }
 19    | { type: 'prev-question' }
 20    | {
 21        type: 'update-question-state'
 22        questionText: string
 23        updates: Partial<QuestionState>
 24        isMultiSelect: boolean
 25      }
 26    | {
 27        type: 'set-answer'
 28        questionText: string
 29        answer: string
 30        shouldAdvance: boolean
 31      }
 32    | { type: 'set-text-input-mode'; isInInput: boolean }
 33  
 34  function reducer(state: State, action: Action): State {
 35    switch (action.type) {
 36      case 'next-question':
 37        return {
 38          ...state,
 39          currentQuestionIndex: state.currentQuestionIndex + 1,
 40          isInTextInput: false,
 41        }
 42  
 43      case 'prev-question':
 44        return {
 45          ...state,
 46          currentQuestionIndex: Math.max(0, state.currentQuestionIndex - 1),
 47          isInTextInput: false,
 48        }
 49  
 50      case 'update-question-state': {
 51        const existing = state.questionStates[action.questionText]
 52        const newState: QuestionState = {
 53          selectedValue:
 54            action.updates.selectedValue ??
 55            existing?.selectedValue ??
 56            (action.isMultiSelect ? [] : undefined),
 57          textInputValue:
 58            action.updates.textInputValue ?? existing?.textInputValue ?? '',
 59        }
 60  
 61        return {
 62          ...state,
 63          questionStates: {
 64            ...state.questionStates,
 65            [action.questionText]: newState,
 66          },
 67        }
 68      }
 69  
 70      case 'set-answer': {
 71        const newState = {
 72          ...state,
 73          answers: {
 74            ...state.answers,
 75            [action.questionText]: action.answer,
 76          },
 77        }
 78  
 79        if (action.shouldAdvance) {
 80          return {
 81            ...newState,
 82            currentQuestionIndex: newState.currentQuestionIndex + 1,
 83            isInTextInput: false,
 84          }
 85        }
 86  
 87        return newState
 88      }
 89  
 90      case 'set-text-input-mode':
 91        return {
 92          ...state,
 93          isInTextInput: action.isInInput,
 94        }
 95    }
 96  }
 97  
 98  const INITIAL_STATE: State = {
 99    currentQuestionIndex: 0,
100    answers: {},
101    questionStates: {},
102    isInTextInput: false,
103  }
104  
105  export type MultipleChoiceState = {
106    currentQuestionIndex: number
107    answers: Record<string, AnswerValue>
108    questionStates: Record<string, QuestionState>
109    isInTextInput: boolean
110    nextQuestion: () => void
111    prevQuestion: () => void
112    updateQuestionState: (
113      questionText: string,
114      updates: Partial<QuestionState>,
115      isMultiSelect: boolean,
116    ) => void
117    setAnswer: (
118      questionText: string,
119      answer: string,
120      shouldAdvance?: boolean,
121    ) => void
122    setTextInputMode: (isInInput: boolean) => void
123  }
124  
125  export function useMultipleChoiceState(): MultipleChoiceState {
126    const [state, dispatch] = useReducer(reducer, INITIAL_STATE)
127  
128    const nextQuestion = useCallback(() => {
129      dispatch({ type: 'next-question' })
130    }, [])
131  
132    const prevQuestion = useCallback(() => {
133      dispatch({ type: 'prev-question' })
134    }, [])
135  
136    const updateQuestionState = useCallback(
137      (
138        questionText: string,
139        updates: Partial<QuestionState>,
140        isMultiSelect: boolean,
141      ) => {
142        dispatch({
143          type: 'update-question-state',
144          questionText,
145          updates,
146          isMultiSelect,
147        })
148      },
149      [],
150    )
151  
152    const setAnswer = useCallback(
153      (questionText: string, answer: string, shouldAdvance: boolean = true) => {
154        dispatch({
155          type: 'set-answer',
156          questionText,
157          answer,
158          shouldAdvance,
159        })
160      },
161      [],
162    )
163  
164    const setTextInputMode = useCallback((isInInput: boolean) => {
165      dispatch({ type: 'set-text-input-mode', isInInput })
166    }, [])
167  
168    return {
169      currentQuestionIndex: state.currentQuestionIndex,
170      answers: state.answers,
171      questionStates: state.questionStates,
172      isInTextInput: state.isInTextInput,
173      nextQuestion,
174      prevQuestion,
175      updateQuestionState,
176      setAnswer,
177      setTextInputMode,
178    }
179  }