diff --git a/components/Decision/Modals/DecisionHistoryModal.tsx b/components/Decision/Modals/DecisionHistoryModal.tsx new file mode 100644 index 0000000..2bbbfde --- /dev/null +++ b/components/Decision/Modals/DecisionHistoryModal.tsx @@ -0,0 +1,260 @@ +import { UilHistory } from '@iconscout/react-unicons' +import React, { Fragment, useRef, useState } from 'react' +import { useFormContext } from 'react-hook-form' + +import { + setClickedConnect, + setCurrentTab, + setDecisionActivityId, + setDecisionEngineOptionTab, + setDecisionHistoryModal, + setDecisionQuestion, + setDecisionRatingUpdate, + setIsDecisionRehydrated, + setIsQuestionSafeForAI, + setPreviousIndex, + setSideCardStep, + setUserIgnoredUnsafeWarning, + updateDecisionFormState, + updateFormCopy, +} from '../../../features/decision/decisionSlice' +import useIntersectionObserver from '../../../hooks/useIntersectionObserver' +import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' +import { useInfiniteDecisionsQuery } from '../../../queries/decisionActivity' +import { deepCopy } from '../../../utils/helpers/common' +import { FirebaseDecisionActivity } from '../../../utils/types/firebase' +import { + GenerateNotificationLoaders, + NotificationLoader, +} from '../../Header/Notifications/NotificationLoaders' +import Modal from '../../Utils/Modal' +import { BaseCard } from '../common/BaseCard' + +export const DecisionHistoryModal = () => { + const { decisionHistoryModal, currentTab } = useAppSelector( + state => state.decisionSlice + ) + const { + user: { uid }, + } = useAppSelector(state => state.userSlice) + const { setValue, getValues } = useFormContext() + const [showComplete, setShowComplete] = useState(false) + + const { status, data, isFetchingNextPage, fetchNextPage, hasNextPage } = + useInfiniteDecisionsQuery(uid, showComplete, decisionHistoryModal) + + // Instantiate intersection observer + const loadMoreRef = useRef(null) + useIntersectionObserver({ + threshold: 0.3, + target: loadMoreRef, + onIntersect: fetchNextPage, + enabled: !!hasNextPage, + }) + + // Handler functions + const showCompleteDecisions = () => { + setShowComplete(true) + } + const showIncompleteDecisions = () => { + setShowComplete(false) + } + + const onHandleSelect = (decision: FirebaseDecisionActivity) => { + // Create copies + const decisionCopy = deepCopy(decision) + + // Set rehydration flags + useAppDispatch(setIsDecisionRehydrated(true)) + // remove extra fields + const extraFields = [ + 'timestamp', + 'currentTab', + 'id', + 'userId', + 'isComplete', + 'ipAddress', + ] + for (const field of extraFields) { + delete decisionCopy[field] + } + const formState: FirebaseDecisionActivity = { + id: decision.id, + } + for (const [key, value] of Object.entries( + decisionCopy as FirebaseDecisionActivity + )) { + if (key !== 'clickedConnect') { + setValue(key, deepCopy(value), { + shouldValidate: true, + shouldDirty: true, + }) + } + formState[key] = deepCopy(value) + } + const { + question, + clickedConnect, + userIgnoredUnsafeWarning, + isQuestionSafeForAI, + } = decisionCopy + + // Set form state in redux + useAppDispatch(setDecisionActivityId(decision.id)) + useAppDispatch(setDecisionQuestion(question)) + useAppDispatch(setClickedConnect(clickedConnect)) + useAppDispatch(setUserIgnoredUnsafeWarning(userIgnoredUnsafeWarning)) + useAppDispatch(setIsQuestionSafeForAI(isQuestionSafeForAI)) + useAppDispatch(updateDecisionFormState(formState)) + + if (clickedConnect) { + useAppDispatch(setSideCardStep(2)) + } + // update current tab + if (decision.currentTab && !decision.isComplete) { + // Needed for forward compatibility of decisions made prior to us + // switching the order of entering options and criteria. + if (!('criteria' in decision)) { + useAppDispatch(setCurrentTab(2)) + } else { + useAppDispatch(setCurrentTab(decision.currentTab)) + } + if (currentTab === 4) useAppDispatch(setDecisionRatingUpdate(true)) + } + + if (decision.isComplete) { + useAppDispatch(setPreviousIndex(4)) + useAppDispatch(setCurrentTab(5)) + useAppDispatch(setDecisionEngineOptionTab(0)) + } + + useAppDispatch( + updateFormCopy( + deepCopy({ + question: getValues('question'), + context: getValues('context'), + options: getValues('options'), + criteria: getValues('criteria'), + }) + ) + ) + useAppDispatch(setDecisionHistoryModal(false)) + } + + return ( + + useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) + } + className="!p-3 md:w-[60%] md:!p-5" + > +
+ + + Decision History + + + You can view the results of a past decision or return to an + incomplete decision. + +
+ +
+ + +
+
+ {status === 'loading' ? ( + + ) : status === 'error' ? ( +
Error loading decisions
+ ) : ( + + {/* Infinite scroller / lazy loader */} + {data?.pages.map(page => ( + + {/* If notifications exist */} + {page.decisions?.length > 0 && + page.decisions.map( + ( + decision: FirebaseDecisionActivity + ) => ( + + onHandleSelect( + decision + ) + } + className="cursor-pointer !rounded-lg p-2 md:p-4" + key={decision.id} + > + + { + decision.question + } + + + ) + )} + + ))} + + {/* Lazy loaader sentinel and end of decisions */} + {isFetchingNextPage || hasNextPage ? ( + + ) : data?.pages[0].decisions && + data?.pages[0].decisions.length === 0 ? ( + + {`No ${ + showComplete + ? 'Complete' + : 'Incomplete' + } decisions`} + + ) : ( + <> + )} + + )} +
+
+
+
+
+ ) +} diff --git a/components/Decision/Modals/TabInfoModal.tsx b/components/Decision/Modals/TabInfoModal.tsx new file mode 100644 index 0000000..6d1219b --- /dev/null +++ b/components/Decision/Modals/TabInfoModal.tsx @@ -0,0 +1,36 @@ +import { UilQuestionCircle } from '@iconscout/react-unicons' +import React from 'react' + +import { setInfoModal } from '../../../features/decision/decisionSlice' +import { useAppDispatch, useAppSelector } from '../../../hooks/useRedux' +import Modal from '../../Utils/Modal' + +export const TabInfoModal = () => { + const { isInfoModal, infoModalDetails, currentTab, matrixStep } = + useAppSelector(state => state.decisionSlice) + return ( + useAppDispatch(setInfoModal(!isInfoModal))} + className="md:w-[40%]" + > +
+ + + + {currentTab === 0 + ? infoModalDetails.title.split('/')[matrixStep] + : infoModalDetails.title} + + + + {infoModalDetails.context} + +
+
+ ) +} diff --git a/components/Decision/SideCards/DecisionHelperCard.tsx b/components/Decision/SideCards/DecisionHelperCard.tsx index b29879f..95767e7 100644 --- a/components/Decision/SideCards/DecisionHelperCard.tsx +++ b/components/Decision/SideCards/DecisionHelperCard.tsx @@ -34,7 +34,7 @@ export const DecisionHelperCard: FC = ({ return (
diff --git a/components/Decision/SideCards/QuestionHelperCard.tsx b/components/Decision/SideCards/QuestionHelperCard.tsx index b344e22..dfa082d 100644 --- a/components/Decision/SideCards/QuestionHelperCard.tsx +++ b/components/Decision/SideCards/QuestionHelperCard.tsx @@ -1,9 +1,15 @@ -import { UilPlusCircle, UilQuestionCircle } from '@iconscout/react-unicons' +import { useUser } from '@auth0/nextjs-auth0' +import { + UilHistory, + UilPlusCircle, + UilQuestionCircle, +} from '@iconscout/react-unicons' import React, { FC } from 'react' import { useFormContext } from 'react-hook-form' import { handleResetState, + setDecisionHistoryModal, setInfoModal, setInfoModalDetails, } from '../../../features/decision/decisionSlice' @@ -22,7 +28,10 @@ export const QuestionHelperCard: FC = ({ }: QuestionHelperCardProps) => { const { reset } = useFormContext() const isMobile = useMediaQuery('(max-width: 965px)') - const { currentTab } = useAppSelector(state => state.decisionSlice) + const { currentTab, decisionHistoryModal, matrixStep } = useAppSelector( + state => state.decisionSlice + ) + const { user } = useUser() const handleInfoClick = () => { useAppDispatch( @@ -38,15 +47,35 @@ export const QuestionHelperCard: FC = ({ reset() useAppDispatch(handleResetState()) } + + const handleHistory = () => { + useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) + } + return ( +
+ {user && matrixStep < 2 && currentTab < 1 && ( +
+ + + Decision History + +
+ )}
@@ -56,19 +85,21 @@ export const QuestionHelperCard: FC = ({ New Decision
-
- - - Explain - -
+ + + Explain + +
+ )}
) } diff --git a/components/Decision/SideCards/ScoreCard.tsx b/components/Decision/SideCards/ScoreCard.tsx index 8a152bf..db2dd38 100644 --- a/components/Decision/SideCards/ScoreCard.tsx +++ b/components/Decision/SideCards/ScoreCard.tsx @@ -18,7 +18,7 @@ export const ScoreCard = () => { const isMobile = useMediaQuery('(max-width: 965px)') return ( - +
setOpen(!isOpen)} diff --git a/components/Decision/Tabs/ResultTab.tsx b/components/Decision/Tabs/ResultTab.tsx index dbfbc4f..2bad798 100644 --- a/components/Decision/Tabs/ResultTab.tsx +++ b/components/Decision/Tabs/ResultTab.tsx @@ -211,7 +211,7 @@ export const ResultTab: FC = ({ deviceIp }: ResultTabProps) => {
{isMobile ? : ''} - + diff --git a/components/Decision/common/BaseCard.tsx b/components/Decision/common/BaseCard.tsx index 26c731c..e5238c5 100644 --- a/components/Decision/common/BaseCard.tsx +++ b/components/Decision/common/BaseCard.tsx @@ -3,10 +3,12 @@ import React from 'react' interface BaseCardProps { children: JSX.Element | JSX.Element[] | any className?: string + onClick?: () => void } -export const BaseCard = ({ children, className }: BaseCardProps) => { +export const BaseCard = ({ children, className, onClick }: BaseCardProps) => { return (
= ({ className={`${bodySmall} mr-4 flex w-1/3 flex-col items-start rounded-lg bg-primary/20 py-1.5 px-2 text-primary dark:text-primaryDark`} > - {item.name.split('').length > 14 ? ( + {item.name.split('').length > 12 ? ( = ({ }} > - + = ({ {rating.map((item: Ratings, index: number) => ( - - {item.option} - + <> + + {item.option.split('').length > 14 && ( + + {item.option} + + )} + {item.option} + + ))} @@ -187,14 +194,14 @@ export const ResultTable: FC = ({ - {item.name.split('').length > 16 ? ( + {item.name.split('').length > 14 ? ( {item.name} @@ -205,7 +212,6 @@ export const ResultTable: FC = ({ {item.name} )} - {weightToString(item.weight)} diff --git a/components/Decision/common/WrapperTabMenu.tsx b/components/Decision/common/WrapperTabMenu.tsx index a5ffebc..c6ea551 100644 --- a/components/Decision/common/WrapperTabMenu.tsx +++ b/components/Decision/common/WrapperTabMenu.tsx @@ -1,6 +1,8 @@ +import { useUser } from '@auth0/nextjs-auth0' import { Menu, Transition } from '@headlessui/react' import { UilEllipsisH, + UilHistory, UilInfoCircle, UilPlusCircle, } from '@iconscout/react-unicons' @@ -9,6 +11,7 @@ import { useFormContext } from 'react-hook-form' import { handleResetState, + setDecisionHistoryModal, setInfoModal, setInfoModalDetails, } from '../../../features/decision/decisionSlice' @@ -24,7 +27,10 @@ export const WrapperTabMenu: FC = ({ title, }: WrapperTabMenuProps) => { const { reset } = useFormContext() - const { currentTab } = useAppSelector(state => state.decisionSlice) + const { user } = useUser() + const { currentTab, decisionHistoryModal, matrixStep } = useAppSelector( + state => state.decisionSlice + ) const handleReset = () => { reset() @@ -40,6 +46,10 @@ export const WrapperTabMenu: FC = ({ ) useAppDispatch(setInfoModal(true)) } + const handleHistory = () => { + useAppDispatch(setDecisionHistoryModal(!decisionHistoryModal)) + } + return ( = ({ > + {user && matrixStep < 2 && currentTab < 1 && ( + + + + Decision History + + + )} handleReset()} as="section" diff --git a/components/Decision/layout/DecisionTabWrapper.tsx b/components/Decision/layout/DecisionTabWrapper.tsx index 72139a1..76236b7 100644 --- a/components/Decision/layout/DecisionTabWrapper.tsx +++ b/components/Decision/layout/DecisionTabWrapper.tsx @@ -161,36 +161,36 @@ export const DecisionTabWrapper: FC = ({ {!isMobile && currentTab !== 4 && ( )} - {currentTab !== 5 ? ( -

- {currentTab === 0 ? title.split('/')[matrixStep] : title} - {isMobile && } - {!isMobile && - decisionMatrixHasResults && - currentTab === 0 && - matrixStep === 1 && ( - - )} -

- ) : null} + {/* {currentTab !== 5 ? ( */} +

+ {currentTab === 0 ? title.split('/')[matrixStep] : title} + {isMobile && } + {!isMobile && + decisionMatrixHasResults && + currentTab === 0 && + matrixStep === 1 && ( + + )} +

+ {/* ) : null} */} {children}
) diff --git a/components/Utils/Tooltip.tsx b/components/Utils/Tooltip.tsx index fd08abd..b31666b 100644 --- a/components/Utils/Tooltip.tsx +++ b/components/Utils/Tooltip.tsx @@ -7,6 +7,7 @@ interface TooltipProps { classForParent?: string classForToolTipBox?: string classForBottomArrow?: string + removeFlexFromParent?: boolean } export const Tooltip: FC< @@ -18,10 +19,13 @@ export const Tooltip: FC< classForParent = '', classForToolTipBox = '', classForBottomArrow = '', + removeFlexFromParent = false, }: TooltipProps) => { return (
{children}
+ ) => { + state.decisionHistoryModal = payload + }, handleResetState: state => { state.currentTab = 0 state.matrixStep = 0 - state.decisionQuestion = undefined + state.decisionEngineOptionTab = 0 + state.decisionEngineBestOption = undefined + state.decisionRatingUpdate = true + state.loadingAiSuggestions = false + state.isSuggestionsEmpty = false + state.previousIndex = 1 + state.ratingTabChecker = [] + state.suggestions = { + optionsList: [], + criteriaList: [], + copyOptionsList: [], + copyCriteriaList: [], + } + state.formCopy = { + question: '', + context: '', + options: [], + criteria: [], + } + state.decisionCriteriaQueryKey = undefined state.decisionActivityId = undefined + state.decisionQuestion = undefined + state.criteriaMobileIndex = 0 state.sideCardStep = 1 - state.clickedConnect = false state.decisionFormState = {} state.isDecisionRehydrated = false + state.isThereATie = false state.isQuestionSafeForAI = true state.userIgnoredUnsafeWarning = false + state.decisionMatrixHasResults = true }, setInfoModalDetails: ( state, @@ -323,6 +352,7 @@ export const { setIsQuestionSafeForAI, setUserIgnoredUnsafeWarning, setDecisionMatrixHasResults, + setDecisionHistoryModal, } = decisionSlice.actions export default decisionSlice.reducer diff --git a/features/interfaces.ts b/features/interfaces.ts index bd8b5f6..308d71e 100644 --- a/features/interfaces.ts +++ b/features/interfaces.ts @@ -67,4 +67,5 @@ export interface DecisionSliceStates { isQuestionSafeForAI: boolean userIgnoredUnsafeWarning: boolean decisionMatrixHasResults: boolean + decisionHistoryModal: boolean } diff --git a/hooks/useInstantiateDecisionForm.tsx b/hooks/useInstantiateDecisionForm.tsx index 9568f7d..b9bcd5b 100644 --- a/hooks/useInstantiateDecisionForm.tsx +++ b/hooks/useInstantiateDecisionForm.tsx @@ -1,35 +1,11 @@ import { useEffect } from 'react' import { useForm } from 'react-hook-form' -import { - setClickedConnect, - setCurrentTab, - setDecisionActivityId, - setDecisionFormState, - setDecisionQuestion, - setDecisionRatingUpdate, - setIsDecisionRehydrated, - setIsQuestionSafeForAI, - setSideCardStep, - setUserIgnoredUnsafeWarning, - updateDecisionFormState, -} from '../features/decision/decisionSlice' -import { useInfiniteDecisionsQuery } from '../queries/decisionActivity' -import { deepCopy } from '../utils/helpers/common' -import { FirebaseDecisionActivity } from '../utils/types/firebase' +import { handleResetState } from '../features/decision/decisionSlice' import { DecisionForm } from '../utils/types/global' -import { useAppDispatch, useAppSelector } from './useRedux' - -interface UseInstantiateDecisionFormProps { - rehydrate: boolean -} - -const useInstantiateDecisionForm = ({ - rehydrate, -}: UseInstantiateDecisionFormProps) => { - const currentTab = useAppSelector(state => state.decisionSlice.currentTab) - const userProfile = useAppSelector(state => state.userSlice.user) +import { useAppDispatch } from './useRedux' +const useInstantiateDecisionForm = () => { // Rehydrate form state from stored values const methods = useForm({ defaultValues: { @@ -47,109 +23,11 @@ const useInstantiateDecisionForm = ({ }, }) - if (rehydrate) { - const { setValue } = methods - const { isFetched, isSuccess } = useInfiniteDecisionsQuery( - userProfile.uid, - undefined, - userProfile.uid !== '', // only enable the call if the userProfile.uid is defined - retrievedData => { - if (!retrievedData.pages[0].decisions[0].isComplete) { - // Set rehydration flags - useAppDispatch(setIsDecisionRehydrated(true)) - - // Create copies - const incompleteDecision = deepCopy( - retrievedData.pages[0].decisions[0] - ) - // remove extra fields - const extraFields = [ - 'timestamp', - 'currentTab', - 'id', - 'userId', - 'isComplete', - 'ipAddress', - ] - for (const field of extraFields) { - delete incompleteDecision[field] - } - - // set values - const formState: FirebaseDecisionActivity = { - id: retrievedData.pages[0].decisions[0].id, - } - for (const [key, value] of Object.entries( - incompleteDecision as FirebaseDecisionActivity - )) { - if (key !== 'clickedConnect') { - setValue(key, deepCopy(value), { - shouldValidate: true, - shouldDirty: true, - }) - } - formState[key] = deepCopy(value) - } - // Set form state in redux - useAppDispatch( - setDecisionActivityId( - retrievedData.pages[0].decisions[0].id - ) - ) - useAppDispatch( - setDecisionQuestion(incompleteDecision.question) - ) - useAppDispatch( - setClickedConnect(incompleteDecision.clickedConnect) - ) - useAppDispatch( - setUserIgnoredUnsafeWarning( - incompleteDecision.userIgnoredUnsafeWarning - ) - ) - useAppDispatch( - setIsQuestionSafeForAI( - incompleteDecision.isQuestionSafeForAI - ) - ) - if (incompleteDecision.clickedConnect) - useAppDispatch(setSideCardStep(2)) - useAppDispatch(updateDecisionFormState(formState)) - - // update current tab - if (retrievedData.pages[0].decisions[0].currentTab) { - useAppDispatch( - setCurrentTab( - retrievedData.pages[0].decisions[0].currentTab - ) - ) - if (currentTab === 4) - useAppDispatch(setDecisionRatingUpdate(true)) - } - } - } - ) - - // Update ratings when user navigates back to decision engine tab - // and form is rehydrated - useEffect(() => { - if (isFetched && isSuccess) - useAppDispatch(setDecisionRatingUpdate(true)) - }, []) - } - // Clear redux state on unmount useEffect(() => { return () => { // Wipe previous decision question and id - useAppDispatch(setDecisionQuestion(undefined)) - useAppDispatch(setDecisionActivityId(undefined)) - useAppDispatch(setSideCardStep(1)) - useAppDispatch(setClickedConnect(false)) - useAppDispatch(setDecisionFormState({})) - useAppDispatch(setIsDecisionRehydrated(false)) - useAppDispatch(setIsQuestionSafeForAI(true)) - useAppDispatch(setUserIgnoredUnsafeWarning(false)) + useAppDispatch(handleResetState()) } }, []) diff --git a/pages/index.tsx b/pages/index.tsx index 835c3db..a17fe9f 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -1,5 +1,4 @@ import { useUser } from '@auth0/nextjs-auth0' -import { UilQuestionCircle } from '@iconscout/react-unicons' import Cookies from 'js-cookie' import Head from 'next/head' import { useRouter } from 'next/router' @@ -12,6 +11,8 @@ import { DecisionBarHandler } from '../components/Decision/layout/DecisionBarHan import { DecisionSideBar } from '../components/Decision/layout/DecisionSideBar' import { DecisionTabWrapper } from '../components/Decision/layout/DecisionTabWrapper' import OptionRatingTabWrapper from '../components/Decision/layout/OptionRatingTabWrapper' +import { DecisionHistoryModal } from '../components/Decision/Modals/DecisionHistoryModal' +import { TabInfoModal } from '../components/Decision/Modals/TabInfoModal' import { CriteriaInfo } from '../components/Decision/SideCards/CriteriaInfo' import { CriteriaSuggestions } from '../components/Decision/SideCards/CriteriaSuggestions' import { DecisionHelperCard } from '../components/Decision/SideCards/DecisionHelperCard' @@ -27,17 +28,14 @@ import { OptionTab } from '../components/Decision/Tabs/OptionTab' import { RatingTab } from '../components/Decision/Tabs/RatingTab' import { ResultTab } from '../components/Decision/Tabs/ResultTab' import FeedDisclaimer from '../components/Feed/Sidebar/FeedDisclaimer' -import Modal from '../components/Utils/Modal' -import { setInfoModal } from '../features/decision/decisionSlice' import useInstantiateDecisionForm from '../hooks/useInstantiateDecisionForm' import useMediaQuery from '../hooks/useMediaQuery' -import { useAppDispatch, useAppSelector } from '../hooks/useRedux' +import { useAppSelector } from '../hooks/useRedux' import useSaveDecisionFormState from '../hooks/useSaveDecisionFormState' import { bigContainer, decisionContainer } from '../styles/decision' import { decisionSideBarOptions, decisionTitle, - rehydrateDecisionForm, } from '../utils/constants/global' import { insertAtArray } from '../utils/helpers/common' @@ -47,8 +45,6 @@ const DecisionEngine: FC = () => { userExceedsMaxDecisions, decisionEngineOptionTab, userIgnoredUnsafeWarning, - isInfoModal, - infoModalDetails, currentTab, matrixStep, } = useAppSelector(state => state.decisionSlice) @@ -59,9 +55,7 @@ const DecisionEngine: FC = () => { const router = useRouter() // Instantiate form - const methods = useInstantiateDecisionForm({ - rehydrate: rehydrateDecisionForm, - }) + const methods = useInstantiateDecisionForm() const { control, getValues, setValue } = methods const watchQuestion = useWatch({ name: 'question', control }) const optionList = getValues('options') @@ -287,30 +281,9 @@ const DecisionEngine: FC = () => { {isMobile && currentTab === 0 && ( )} + {user && } - useAppDispatch(setInfoModal(!isInfoModal))} - className="md:w-[40%]" - > -
- - - - {currentTab === 0 - ? infoModalDetails.title.split('/')[matrixStep] - : infoModalDetails.title} - - - - {infoModalDetails.context} - -
-
+
) } diff --git a/utils/constants/global.ts b/utils/constants/global.ts index 51a8928..cdf4a80 100644 --- a/utils/constants/global.ts +++ b/utils/constants/global.ts @@ -35,7 +35,7 @@ export const decisionTitle = [ 'Your criteria', 'Your options', 'Your ratings', - '', + 'Your result', ] export const decisionInfo = [ @@ -58,7 +58,6 @@ export const decisionInfoParagraph = [ export const criteriaInfoCacheDays = 21 export const maxAllowedUnauthenticatedDecisions = 2 -export const rehydrateDecisionForm = false /** * Decision Page diff --git a/utils/types/firebase.ts b/utils/types/firebase.ts index 4f2feb0..82d7a65 100644 --- a/utils/types/firebase.ts +++ b/utils/types/firebase.ts @@ -205,6 +205,27 @@ export interface FirebaseUnauthenticatedDecision { createdAt?: FieldValue updatedAt?: FieldValue } +export interface Timestamp { + seconds: number + nanoseconds: number +} + +export interface DecisionFirebase { + isComplete: boolean + id: string + question: string + criteria: Criteria[] + context: string + clickedConnect: boolean + options: Options[] + suggestedCriteria: Criteria[] + suggestedOptions: Options[] + ipAddress: string + ratings: Rating[] + timestamp: Timestamp + userId: string + currentTab: number +} /** * Type guards