Refactor notifications to redux #6
							
								
								
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.vscode/settings.json
									
									
									
									
										vendored
									
									
								
							@ -8,6 +8,7 @@
 | 
			
		||||
    "heroicons",
 | 
			
		||||
    "konva",
 | 
			
		||||
    "libretranslate",
 | 
			
		||||
    "reduxjs",
 | 
			
		||||
    "tailwindcss",
 | 
			
		||||
    "Tesseract",
 | 
			
		||||
    "Textualize",
 | 
			
		||||
 | 
			
		||||
@ -1,6 +1,6 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useRef, useState } from 'react'
 | 
			
		||||
import React, { useState } from 'react'
 | 
			
		||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import { Html } from 'react-konva-utils'
 | 
			
		||||
import { ClipboardIcon, ArrowPathIcon, TrashIcon, LanguageIcon } from '@heroicons/react/24/outline'
 | 
			
		||||
@ -9,9 +9,10 @@ import { useProject } from '../../../context/Project/provider'
 | 
			
		||||
import asyncClick from '../../../utils/asyncClick'
 | 
			
		||||
import processImageArea from '../../../useCases/processImageArea'
 | 
			
		||||
import classNames from '../../../utils/classNames'
 | 
			
		||||
import { useNotification } from '../../../context/Notification/provider'
 | 
			
		||||
import LanguageSelect from '../../utils/LanguageSelect'
 | 
			
		||||
import { RequestTranslateArea } from '../../../wailsjs/wailsjs/go/ipc/Channel'
 | 
			
		||||
import { useDispatch } from 'react-redux'
 | 
			
		||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
 | 
			
		||||
 | 
			
		||||
type Props = {
 | 
			
		||||
  x: number,
 | 
			
		||||
@ -23,9 +24,8 @@ type Props = {
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const AreaContextMenu = (props: Props) => {
 | 
			
		||||
  const dispatch = useDispatch()
 | 
			
		||||
  const { getProcessedAreaById, requestDeleteAreaById, getSelectedDocument, requestUpdateArea } = useProject()
 | 
			
		||||
  const { addNotificationToQueue } = useNotification()
 | 
			
		||||
  const formRef = useRef<HTMLFormElement>(null)
 | 
			
		||||
  const [shouldShowProcessLanguageSelect, setShouldShowProcessLanguageSelect] = useState(false)
 | 
			
		||||
  const { area, setIsAreaContextMenuOpen, scale, x, y } = props
 | 
			
		||||
 | 
			
		||||
@ -36,15 +36,15 @@ const AreaContextMenu = (props: Props) => {
 | 
			
		||||
    const wordsOfProcessedArea = processedArea?.lines.flatMap(l => l.words.map(w => w.fullText))
 | 
			
		||||
    const fullText = wordsOfProcessedArea?.join(' ')
 | 
			
		||||
    if (!fullText) {
 | 
			
		||||
      addNotificationToQueue({ message: 'No text found to copy.', level: 'warning' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'No text found to copy.', level: 'warning' }))
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await navigator.clipboard.writeText(fullText)
 | 
			
		||||
      addNotificationToQueue({ message: 'Copied area to clipboard' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Copied area to clipboard' }))
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error copying area', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error copying area', level: 'error' }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -53,9 +53,9 @@ const AreaContextMenu = (props: Props) => {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const response = await requestDeleteAreaById(area.id)
 | 
			
		||||
      if (!response) addNotificationToQueue({ message: 'Could not delete area', level: 'warning' })
 | 
			
		||||
      if (!response) dispatch(pushNotification({ message: 'Could not delete area', level: 'warning' }))
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error deleting area', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error deleting area', level: 'error' }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -64,17 +64,17 @@ const AreaContextMenu = (props: Props) => {
 | 
			
		||||
 | 
			
		||||
    const documentId = getSelectedDocument()?.id
 | 
			
		||||
    if (!documentId) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Issue finding selected document', level: 'warning' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Issue finding selected document', level: 'warning' }))
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      addNotificationToQueue({ message: 'Processing test of area' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Processing test of area' }))
 | 
			
		||||
      const response = await processImageArea(documentId, area.id)
 | 
			
		||||
      if (response) addNotificationToQueue({ message: 'Area successfully processed' })
 | 
			
		||||
      else addNotificationToQueue({ message: 'No text result from processing area', level: 'warning' })
 | 
			
		||||
      if (response) dispatch(pushNotification({ message: 'Area successfully processed' }))
 | 
			
		||||
      else dispatch(pushNotification({ message: 'No text result from processing area', level: 'warning' }))
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error processing area', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -84,10 +84,10 @@ const AreaContextMenu = (props: Props) => {
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      const wasSuccessful = await RequestTranslateArea(area.id)
 | 
			
		||||
      if (wasSuccessful) addNotificationToQueue({ message: 'Successfully translated area' })
 | 
			
		||||
      else addNotificationToQueue({ message: 'Issue translating area', level: 'warning' })
 | 
			
		||||
      if (wasSuccessful) dispatch(pushNotification({ message: 'Successfully translated area' }))
 | 
			
		||||
      else dispatch(pushNotification({ message: 'Issue translating area', level: 'warning' }))
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error translating area', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error translating area', level: 'error' }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -98,26 +98,25 @@ const AreaContextMenu = (props: Props) => {
 | 
			
		||||
    try {
 | 
			
		||||
      successfullyUpdatedLanguageOnArea = await requestUpdateArea({...area, ...{language: selectedLanguage}})
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const selectedDocumentId = getSelectedDocument()?.id
 | 
			
		||||
    if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await processImageArea(selectedDocumentId, area.id)
 | 
			
		||||
      addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error processing area', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const handleOnBlur = (e: React.FocusEvent) => {
 | 
			
		||||
    console.log(e.relatedTarget)
 | 
			
		||||
    e.preventDefault()
 | 
			
		||||
    if (e.relatedTarget === null) setIsAreaContextMenuOpen(false)
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@ -1,19 +1,20 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import React, { useEffect, useState } from 'react'
 | 
			
		||||
import { useDispatch } from 'react-redux'
 | 
			
		||||
import { DocumentTextIcon, LanguageIcon, LinkIcon, MagnifyingGlassMinusIcon, MagnifyingGlassPlusIcon, SquaresPlusIcon } from '@heroicons/react/24/outline'
 | 
			
		||||
import { useProject } from '../../../context/Project/provider'
 | 
			
		||||
import { entities } from '../../../wailsjs/wailsjs/go/models'
 | 
			
		||||
import LanguageSelect from '../../utils/LanguageSelect'
 | 
			
		||||
import { useStage } from '../context/provider'
 | 
			
		||||
import ToolToggleButton from './ToolToggleButton'
 | 
			
		||||
import { useNotification } from '../../../context/Notification/provider'
 | 
			
		||||
import processImageArea from '../../../useCases/processImageArea'
 | 
			
		||||
import { pushNotification } from '../../../redux/features/notifications/notificationQueueSlice'
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
const ToolingOverlay = () => {
 | 
			
		||||
  const dispatch = useDispatch()
 | 
			
		||||
  const { getSelectedDocument, selectedAreaId, requestUpdateArea, requestUpdateDocument, updateDocuments } = useProject()
 | 
			
		||||
  const { addNotificationToQueue } = useNotification()
 | 
			
		||||
  const {
 | 
			
		||||
    scale, scaleStep, maxScale, setScale,
 | 
			
		||||
    isLinkAreaContextsVisible, setIsLinkAreaContextsVisible,
 | 
			
		||||
@ -36,22 +37,22 @@ const ToolingOverlay = () => {
 | 
			
		||||
    try {
 | 
			
		||||
      successfullyUpdatedLanguageOnArea = await requestUpdateArea({ ...selectedArea, ...{ language: selectedLanguage } })
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error updating area language', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error updating area language', level: 'error' }))
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    const selectedDocumentId = getSelectedDocument()?.id
 | 
			
		||||
    if (!successfullyUpdatedLanguageOnArea || !selectedDocumentId) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Did not successfully update area language', level: 'warning' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Did not successfully update area language', level: 'warning' }))
 | 
			
		||||
      return
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    try {
 | 
			
		||||
      await processImageArea(selectedDocumentId, selectedArea.id)
 | 
			
		||||
      await updateDocuments()
 | 
			
		||||
      addNotificationToQueue({ message: 'Finished processing area', level: 'info' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Finished processing area', level: 'info' }))
 | 
			
		||||
    } catch (err) {
 | 
			
		||||
      addNotificationToQueue({ message: 'Error processing area', level: 'error' })
 | 
			
		||||
      dispatch(pushNotification({ message: 'Error processing area', level: 'error' }))
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										86
									
								
								frontend/components/Notifications/index.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										86
									
								
								frontend/components/Notifications/index.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,86 @@
 | 
			
		||||
import { Fragment, useEffect } from 'react'
 | 
			
		||||
import { Transition } from '@headlessui/react'
 | 
			
		||||
import { useDispatch, useSelector } from 'react-redux'
 | 
			
		||||
import { XMarkIcon, InformationCircleIcon, ExclamationTriangleIcon, ExclamationCircleIcon } from '@heroicons/react/24/outline'
 | 
			
		||||
import { RootState } from '../../redux/store'
 | 
			
		||||
import { NotificationProps } from '../../redux/features/notifications/types'
 | 
			
		||||
import { dismissCurrentNotification } from '../../redux/features/notifications/notificationQueueSlice'
 | 
			
		||||
 | 
			
		||||
const renderIcon = (level: NotificationProps['level'] = 'info') => {
 | 
			
		||||
  switch (level) {
 | 
			
		||||
    default: return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
 | 
			
		||||
    case 'info': return <InformationCircleIcon className='w-6 h-6 text-blue-400' />
 | 
			
		||||
    case 'warning': return <ExclamationTriangleIcon className='w-6 h-6 text-orange-400' />
 | 
			
		||||
    case 'error': return <ExclamationCircleIcon className='w-6 h-6 text-red-600' />
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const notificationTime = 3000
 | 
			
		||||
 | 
			
		||||
const Notification = () => {
 | 
			
		||||
  const { currentNotification, queue } = useSelector((state: RootState) => state.notificationQueue)
 | 
			
		||||
  const dispatch = useDispatch()
 | 
			
		||||
 | 
			
		||||
  const handleOnClick = () => {
 | 
			
		||||
    if (currentNotification?.onActionClickCallback) currentNotification?.onActionClickCallback()
 | 
			
		||||
    if (currentNotification?.closeOnAction) dispatch(dismissCurrentNotification())
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  useEffect(() => {
 | 
			
		||||
    if (queue.length) setTimeout(() => dispatch(dismissCurrentNotification()), notificationTime)
 | 
			
		||||
  }, [currentNotification])
 | 
			
		||||
 | 
			
		||||
  return <>
 | 
			
		||||
    <div
 | 
			
		||||
      aria-live="assertive"
 | 
			
		||||
      className="pointer-events-none absolute block top-0 left-0 w-full h-full"
 | 
			
		||||
    >
 | 
			
		||||
      <div className="absolute items-center" style={{ bottom: '12px', right: '16px' }}>
 | 
			
		||||
        <Transition
 | 
			
		||||
          show={!!currentNotification}
 | 
			
		||||
          as={Fragment}
 | 
			
		||||
          enter="transform ease-out duration-1300 transition"
 | 
			
		||||
          enterFrom="translate-y-2 opacity-0 sm:translate-y-0 sm:translate-x-2"
 | 
			
		||||
          enterTo="translate-y-0 opacity-100 sm:translate-x-0"
 | 
			
		||||
          leave="transition ease-in duration-100"
 | 
			
		||||
          leaveFrom="opacity-100"
 | 
			
		||||
          leaveTo="opacity-0"
 | 
			
		||||
        >
 | 
			
		||||
          <div className="pointer-events-auto w-full max-w-sm overflow-hidden rounded-lg bg-white shadow-lg ring-1 ring-black ring-opacity-5">
 | 
			
		||||
            <div className="p-4">
 | 
			
		||||
              <div className="flex items-center">
 | 
			
		||||
                {renderIcon(currentNotification?.level)}
 | 
			
		||||
                <div className="flex content-center flex-1 justify-between">
 | 
			
		||||
                  <p className="flex-1 text-sm font-medium text-gray-900 ml-2">{currentNotification?.message}</p>
 | 
			
		||||
                  {currentNotification?.actionButtonText ? <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="ml-3 flex-shrink-0 rounded-md bg-white text-sm font-medium text-indigo-600 hover:text-indigo-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
 | 
			
		||||
                    onClick={() => handleOnClick()}
 | 
			
		||||
                  >
 | 
			
		||||
                    {currentNotification?.actionButtonText}
 | 
			
		||||
                  </button>
 | 
			
		||||
                    : <></>
 | 
			
		||||
                  }
 | 
			
		||||
                </div>
 | 
			
		||||
                <div className="ml-4 flex flex-shrink-0">
 | 
			
		||||
                  <button
 | 
			
		||||
                    type="button"
 | 
			
		||||
                    className="inline-flex rounded-md bg-white text-gray-400 hover:text-gray-500 focus:outline-none focus:ring-2 focus:ring-indigo-500 focus:ring-offset-2"
 | 
			
		||||
                    onClick={() => {
 | 
			
		||||
                      dispatch(dismissCurrentNotification())
 | 
			
		||||
                    }}
 | 
			
		||||
                  >
 | 
			
		||||
                    <span className="sr-only">Close</span>
 | 
			
		||||
                    <XMarkIcon className="h-5 w-5" aria-hidden="true" />
 | 
			
		||||
                  </button>
 | 
			
		||||
                </div>
 | 
			
		||||
              </div>
 | 
			
		||||
            </div>
 | 
			
		||||
          </div>
 | 
			
		||||
        </Transition>
 | 
			
		||||
      </div>
 | 
			
		||||
    </div>
 | 
			
		||||
  </>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export default Notification
 | 
			
		||||
							
								
								
									
										217
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										217
									
								
								frontend/package-lock.json
									
									
									
										generated
									
									
									
								
							@ -11,6 +11,7 @@
 | 
			
		||||
        "@headlessui/react": "^1.7.4",
 | 
			
		||||
        "@heroicons/react": "^2.0.13",
 | 
			
		||||
        "@monaco-editor/react": "^4.4.6",
 | 
			
		||||
        "@reduxjs/toolkit": "^1.9.5",
 | 
			
		||||
        "@tailwindcss/forms": "^0.5.3",
 | 
			
		||||
        "konva": "^9.2.0",
 | 
			
		||||
        "next": "^13.4.4",
 | 
			
		||||
@ -19,6 +20,7 @@
 | 
			
		||||
        "react-konva": "^18.2.9",
 | 
			
		||||
        "react-konva-utils": "^1.0.4",
 | 
			
		||||
        "react-markdown": "^8.0.5",
 | 
			
		||||
        "react-redux": "^8.1.2",
 | 
			
		||||
        "rehype-raw": "^6.1.1",
 | 
			
		||||
        "tesseract.js": "^4.0.2",
 | 
			
		||||
        "use-image": "^1.1.0",
 | 
			
		||||
@ -214,7 +216,6 @@
 | 
			
		||||
      "version": "7.20.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
 | 
			
		||||
      "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "regenerator-runtime": "^0.13.11"
 | 
			
		||||
      },
 | 
			
		||||
@ -633,6 +634,29 @@
 | 
			
		||||
        "url": "https://opencollective.com/unts"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@reduxjs/toolkit": {
 | 
			
		||||
      "version": "1.9.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
 | 
			
		||||
      "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "immer": "^9.0.21",
 | 
			
		||||
        "redux": "^4.2.1",
 | 
			
		||||
        "redux-thunk": "^2.4.2",
 | 
			
		||||
        "reselect": "^4.1.8"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.9.0 || ^17.0.0 || ^18",
 | 
			
		||||
        "react-redux": "^7.2.1 || ^8.0.2"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "react-redux": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@rushstack/eslint-patch": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
 | 
			
		||||
@ -674,6 +698,15 @@
 | 
			
		||||
        "@types/unist": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/hoist-non-react-statics": {
 | 
			
		||||
      "version": "3.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "hoist-non-react-statics": "^3.3.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/json5": {
 | 
			
		||||
      "version": "0.0.29",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
 | 
			
		||||
@ -723,7 +756,7 @@
 | 
			
		||||
      "version": "18.0.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
 | 
			
		||||
      "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@types/react": "*"
 | 
			
		||||
      }
 | 
			
		||||
@ -746,6 +779,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/use-sync-external-store": {
 | 
			
		||||
      "version": "0.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/@types/uuid": {
 | 
			
		||||
      "version": "8.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
 | 
			
		||||
@ -2588,6 +2626,14 @@
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/hoist-non-react-statics": {
 | 
			
		||||
      "version": "3.3.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
 | 
			
		||||
      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "react-is": "^16.7.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/html-void-elements": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
 | 
			
		||||
@ -2622,6 +2668,15 @@
 | 
			
		||||
        "node": ">= 4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/immer": {
 | 
			
		||||
      "version": "9.0.21",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
			
		||||
      "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA==",
 | 
			
		||||
      "funding": {
 | 
			
		||||
        "type": "opencollective",
 | 
			
		||||
        "url": "https://opencollective.com/immer"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/import-fresh": {
 | 
			
		||||
      "version": "3.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 | 
			
		||||
@ -4433,6 +4488,49 @@
 | 
			
		||||
        "react": "^18.2.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-redux": {
 | 
			
		||||
      "version": "8.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.12.1",
 | 
			
		||||
        "@types/hoist-non-react-statics": "^3.3.1",
 | 
			
		||||
        "@types/use-sync-external-store": "^0.0.3",
 | 
			
		||||
        "hoist-non-react-statics": "^3.3.2",
 | 
			
		||||
        "react-is": "^18.0.0",
 | 
			
		||||
        "use-sync-external-store": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "@types/react": "^16.8 || ^17.0 || ^18.0",
 | 
			
		||||
        "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
 | 
			
		||||
        "react": "^16.8 || ^17.0 || ^18.0",
 | 
			
		||||
        "react-dom": "^16.8 || ^17.0 || ^18.0",
 | 
			
		||||
        "react-native": ">=0.59",
 | 
			
		||||
        "redux": "^4 || ^5.0.0-beta.0"
 | 
			
		||||
      },
 | 
			
		||||
      "peerDependenciesMeta": {
 | 
			
		||||
        "@types/react": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "@types/react-dom": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "react-dom": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "react-native": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        },
 | 
			
		||||
        "redux": {
 | 
			
		||||
          "optional": true
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/react-redux/node_modules/react-is": {
 | 
			
		||||
      "version": "18.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/read-cache": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
			
		||||
@ -4452,6 +4550,22 @@
 | 
			
		||||
        "node": ">=8.10.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/redux": {
 | 
			
		||||
      "version": "4.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "@babel/runtime": "^7.9.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/redux-thunk": {
 | 
			
		||||
      "version": "2.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "redux": "^4"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/regenerator-runtime": {
 | 
			
		||||
      "version": "0.13.11",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
 | 
			
		||||
@ -4528,6 +4642,11 @@
 | 
			
		||||
        "url": "https://opencollective.com/unified"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/reselect": {
 | 
			
		||||
      "version": "4.1.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
 | 
			
		||||
      "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/resolve": {
 | 
			
		||||
      "version": "1.22.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
 | 
			
		||||
@ -5288,6 +5407,14 @@
 | 
			
		||||
        "react-dom": ">=16.8.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/use-sync-external-store": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
 | 
			
		||||
      "peerDependencies": {
 | 
			
		||||
        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "node_modules/util-deprecate": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
			
		||||
@ -5630,7 +5757,6 @@
 | 
			
		||||
      "version": "7.20.7",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.7.tgz",
 | 
			
		||||
      "integrity": "sha512-UF0tvkUtxwAgZ5W/KrkHf0Rn0fdnLDU9ScxBrEVNUprE/MzirjK4MJUX1/BVDv00Sv8cljtukVK1aky++X1SjQ==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "regenerator-runtime": "^0.13.11"
 | 
			
		||||
      }
 | 
			
		||||
@ -5895,6 +6021,17 @@
 | 
			
		||||
        "tslib": "^2.4.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@reduxjs/toolkit": {
 | 
			
		||||
      "version": "1.9.5",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.9.5.tgz",
 | 
			
		||||
      "integrity": "sha512-Rt97jHmfTeaxL4swLRNPD/zV4OxTes4la07Xc4hetpUW/vc75t5m1ANyxG6ymnEQ2FsLQsoMlYB2vV1sO3m8tQ==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "immer": "^9.0.21",
 | 
			
		||||
        "redux": "^4.2.1",
 | 
			
		||||
        "redux-thunk": "^2.4.2",
 | 
			
		||||
        "reselect": "^4.1.8"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@rushstack/eslint-patch": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.2.0.tgz",
 | 
			
		||||
@ -5933,6 +6070,15 @@
 | 
			
		||||
        "@types/unist": "*"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/hoist-non-react-statics": {
 | 
			
		||||
      "version": "3.3.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
 | 
			
		||||
      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/react": "*",
 | 
			
		||||
        "hoist-non-react-statics": "^3.3.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "@types/json5": {
 | 
			
		||||
      "version": "0.0.29",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz",
 | 
			
		||||
@ -5982,7 +6128,7 @@
 | 
			
		||||
      "version": "18.0.10",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-18.0.10.tgz",
 | 
			
		||||
      "integrity": "sha512-E42GW/JA4Qv15wQdqJq8DL4JhNpB3prJgjgapN3qJT9K2zO5IIAQh4VXvCEDupoqAwnz0cY4RlXeC/ajX5SFHg==",
 | 
			
		||||
      "dev": true,
 | 
			
		||||
      "devOptional": true,
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@types/react": "*"
 | 
			
		||||
      }
 | 
			
		||||
@ -6005,6 +6151,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/unist/-/unist-2.0.6.tgz",
 | 
			
		||||
      "integrity": "sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "@types/use-sync-external-store": {
 | 
			
		||||
      "version": "0.0.3",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
 | 
			
		||||
      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
 | 
			
		||||
    },
 | 
			
		||||
    "@types/uuid": {
 | 
			
		||||
      "version": "8.3.4",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
 | 
			
		||||
@ -7317,6 +7468,14 @@
 | 
			
		||||
        "space-separated-tokens": "^2.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "hoist-non-react-statics": {
 | 
			
		||||
      "version": "3.3.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
 | 
			
		||||
      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "react-is": "^16.7.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "html-void-elements": {
 | 
			
		||||
      "version": "2.0.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/html-void-elements/-/html-void-elements-2.0.1.tgz",
 | 
			
		||||
@ -7341,6 +7500,11 @@
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
 | 
			
		||||
      "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "immer": {
 | 
			
		||||
      "version": "9.0.21",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/immer/-/immer-9.0.21.tgz",
 | 
			
		||||
      "integrity": "sha512-bc4NBHqOqSfRW7POMkHd51LvClaeMXpm8dx0e8oE2GORbq5aRK7Bxl4FyzVLdGtLmvLKL7BTDBG5ACQm4HWjTA=="
 | 
			
		||||
    },
 | 
			
		||||
    "import-fresh": {
 | 
			
		||||
      "version": "3.3.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
 | 
			
		||||
@ -8446,6 +8610,26 @@
 | 
			
		||||
        "scheduler": "^0.23.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "react-redux": {
 | 
			
		||||
      "version": "8.1.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.2.tgz",
 | 
			
		||||
      "integrity": "sha512-xJKYI189VwfsFc4CJvHqHlDrzyFTY/3vZACbE+rr/zQ34Xx1wQfB4OTOSeOSNrF6BDVe8OOdxIrAnMGXA3ggfw==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@babel/runtime": "^7.12.1",
 | 
			
		||||
        "@types/hoist-non-react-statics": "^3.3.1",
 | 
			
		||||
        "@types/use-sync-external-store": "^0.0.3",
 | 
			
		||||
        "hoist-non-react-statics": "^3.3.2",
 | 
			
		||||
        "react-is": "^18.0.0",
 | 
			
		||||
        "use-sync-external-store": "^1.0.0"
 | 
			
		||||
      },
 | 
			
		||||
      "dependencies": {
 | 
			
		||||
        "react-is": {
 | 
			
		||||
          "version": "18.2.0",
 | 
			
		||||
          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
 | 
			
		||||
          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
 | 
			
		||||
        }
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "read-cache": {
 | 
			
		||||
      "version": "1.0.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz",
 | 
			
		||||
@ -8462,6 +8646,20 @@
 | 
			
		||||
        "picomatch": "^2.2.1"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "redux": {
 | 
			
		||||
      "version": "4.2.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
 | 
			
		||||
      "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
 | 
			
		||||
      "requires": {
 | 
			
		||||
        "@babel/runtime": "^7.9.2"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "redux-thunk": {
 | 
			
		||||
      "version": "2.4.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.2.tgz",
 | 
			
		||||
      "integrity": "sha512-+P3TjtnP0k/FEjcBL5FZpoovtvrTNT/UXd4/sluaSyrURlSlhLSzEdfsTBW7WsKB6yPvgd7q/iZPICFjW4o57Q==",
 | 
			
		||||
      "requires": {}
 | 
			
		||||
    },
 | 
			
		||||
    "regenerator-runtime": {
 | 
			
		||||
      "version": "0.13.11",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
 | 
			
		||||
@ -8514,6 +8712,11 @@
 | 
			
		||||
        "unified": "^10.0.0"
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    "reselect": {
 | 
			
		||||
      "version": "4.1.8",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.8.tgz",
 | 
			
		||||
      "integrity": "sha512-ab9EmR80F/zQTMNeneUr4cv+jSwPJgIlvEmVwLerwrWVbpLlBuls9XHzIeTFy4cegU2NHBp3va0LKOzU5qFEYQ=="
 | 
			
		||||
    },
 | 
			
		||||
    "resolve": {
 | 
			
		||||
      "version": "1.22.1",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
 | 
			
		||||
@ -9045,6 +9248,12 @@
 | 
			
		||||
      "integrity": "sha512-+cBHRR/44ZyMUS873O0vbVylgMM0AbdTunEplAWXvIQ2p69h2sIo2Qq74zeUsq6AMo+27e5lERQvXzd1crGiMg==",
 | 
			
		||||
      "requires": {}
 | 
			
		||||
    },
 | 
			
		||||
    "use-sync-external-store": {
 | 
			
		||||
      "version": "1.2.0",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
 | 
			
		||||
      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
 | 
			
		||||
      "requires": {}
 | 
			
		||||
    },
 | 
			
		||||
    "util-deprecate": {
 | 
			
		||||
      "version": "1.0.2",
 | 
			
		||||
      "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
 | 
			
		||||
 | 
			
		||||
@ -16,6 +16,7 @@
 | 
			
		||||
    "@headlessui/react": "^1.7.4",
 | 
			
		||||
    "@heroicons/react": "^2.0.13",
 | 
			
		||||
    "@monaco-editor/react": "^4.4.6",
 | 
			
		||||
    "@reduxjs/toolkit": "^1.9.5",
 | 
			
		||||
    "@tailwindcss/forms": "^0.5.3",
 | 
			
		||||
    "konva": "^9.2.0",
 | 
			
		||||
    "next": "^13.4.4",
 | 
			
		||||
@ -24,6 +25,7 @@
 | 
			
		||||
    "react-konva": "^18.2.9",
 | 
			
		||||
    "react-konva-utils": "^1.0.4",
 | 
			
		||||
    "react-markdown": "^8.0.5",
 | 
			
		||||
    "react-redux": "^8.1.2",
 | 
			
		||||
    "rehype-raw": "^6.1.1",
 | 
			
		||||
    "tesseract.js": "^4.0.2",
 | 
			
		||||
    "use-image": "^1.1.0",
 | 
			
		||||
 | 
			
		||||
@ -1 +1 @@
 | 
			
		||||
e331f957a49840160190db6ea894d0b5
 | 
			
		||||
bf8d6eeb2add78baa4092415a836f1ad
 | 
			
		||||
@ -8,6 +8,7 @@ import '../styles/globals.css'
 | 
			
		||||
import { NavigationProvider } from '../context/Navigation/provider'
 | 
			
		||||
import { mainPages, workspaces } from '../context/Navigation/types'
 | 
			
		||||
import { NotificationProvider } from '../context/Notification/provider'
 | 
			
		||||
import { Providers } from '../redux/provider'
 | 
			
		||||
 | 
			
		||||
const initialProjectProps = {
 | 
			
		||||
  id: '',
 | 
			
		||||
@ -25,7 +26,9 @@ export default function MainAppLayout({ Component, pageProps }: AppProps) {
 | 
			
		||||
    <NavigationProvider navigationProps={initialNavigationProps}>
 | 
			
		||||
      <ProjectProvider projectProps={initialProjectProps}>
 | 
			
		||||
        <NotificationProvider>
 | 
			
		||||
          <Component {...pageProps} />
 | 
			
		||||
          <Providers>
 | 
			
		||||
            <Component {...pageProps} />
 | 
			
		||||
          </Providers>
 | 
			
		||||
        </NotificationProvider>
 | 
			
		||||
      </ProjectProvider>
 | 
			
		||||
    </NavigationProvider>
 | 
			
		||||
 | 
			
		||||
@ -7,6 +7,7 @@ import Navigation from '../components/workspace/Navigation'
 | 
			
		||||
import { useNavigation } from '../context/Navigation/provider'
 | 
			
		||||
import { mainPages } from '../context/Navigation/types'
 | 
			
		||||
import { useProject } from '../context/Project/provider'
 | 
			
		||||
import Notification from '../components/Notifications'
 | 
			
		||||
 | 
			
		||||
const Home: NextPage = () => {
 | 
			
		||||
  const { currentSession } = useProject()
 | 
			
		||||
@ -27,6 +28,7 @@ const Home: NextPage = () => {
 | 
			
		||||
  return <>
 | 
			
		||||
    <MainHead />
 | 
			
		||||
    {renderSelectedMainPage()}
 | 
			
		||||
    <Notification />
 | 
			
		||||
  </>
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -0,0 +1,44 @@
 | 
			
		||||
import { createSlice } from '@reduxjs/toolkit'
 | 
			
		||||
import type { PayloadAction } from '@reduxjs/toolkit'
 | 
			
		||||
import { NotificationProps, NotificationQueueState } from './types'
 | 
			
		||||
 | 
			
		||||
const initialState: NotificationQueueState = {
 | 
			
		||||
  currentNotification: undefined,
 | 
			
		||||
  queue: []
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export const notificationQueueSlice = createSlice({
 | 
			
		||||
  name: 'propertyList',
 | 
			
		||||
  initialState,
 | 
			
		||||
  reducers: {
 | 
			
		||||
    setNotifications: (state, action: PayloadAction<NotificationProps[]>) => {
 | 
			
		||||
      state.queue = action.payload
 | 
			
		||||
    },
 | 
			
		||||
    setCurrentNotification: (state, action: PayloadAction<NotificationProps | undefined>) => {
 | 
			
		||||
      state.currentNotification = action.payload
 | 
			
		||||
    },
 | 
			
		||||
    pushNotification: (state, action: PayloadAction<NotificationProps>) => {
 | 
			
		||||
      let { queue } = state
 | 
			
		||||
      const { payload: newNotification } = action
 | 
			
		||||
 | 
			
		||||
      if (queue.length) queue.push(newNotification)
 | 
			
		||||
      else {
 | 
			
		||||
        queue.push(newNotification)
 | 
			
		||||
        state.currentNotification = newNotification
 | 
			
		||||
      }
 | 
			
		||||
    },
 | 
			
		||||
    dismissCurrentNotification: (state) => {
 | 
			
		||||
      state.queue.shift()
 | 
			
		||||
      state.currentNotification = state.queue[0] || undefined
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export const {
 | 
			
		||||
  setNotifications,
 | 
			
		||||
  setCurrentNotification,
 | 
			
		||||
  pushNotification,
 | 
			
		||||
  dismissCurrentNotification
 | 
			
		||||
} = notificationQueueSlice.actions
 | 
			
		||||
 | 
			
		||||
export default notificationQueueSlice.reducer
 | 
			
		||||
							
								
								
									
										15
									
								
								frontend/redux/features/notifications/types.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								frontend/redux/features/notifications/types.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,15 @@
 | 
			
		||||
export type NotificationLevel = 'info' | 'warning' | 'error'
 | 
			
		||||
 | 
			
		||||
export type NotificationProps = {
 | 
			
		||||
  shouldShow?: boolean,
 | 
			
		||||
  message: string,
 | 
			
		||||
  actionButtonText?: string,
 | 
			
		||||
  onActionClickCallback?: Function,
 | 
			
		||||
  closeOnAction?: boolean,
 | 
			
		||||
  level?: NotificationLevel,
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type NotificationQueueState = {
 | 
			
		||||
  queue: NotificationProps[],
 | 
			
		||||
  currentNotification?: NotificationProps
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										5
									
								
								frontend/redux/hooks.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								frontend/redux/hooks.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,5 @@
 | 
			
		||||
import { TypedUseSelectorHook, useDispatch, useSelector } from 'react-redux'
 | 
			
		||||
import type { RootState, AppDispatch } from './store'
 | 
			
		||||
 | 
			
		||||
export const useAppDispatch = () => useDispatch<AppDispatch>()
 | 
			
		||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
 | 
			
		||||
							
								
								
									
										8
									
								
								frontend/redux/provider.tsx
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										8
									
								
								frontend/redux/provider.tsx
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,8 @@
 | 
			
		||||
'use client'
 | 
			
		||||
 | 
			
		||||
import { store } from './store'
 | 
			
		||||
import { Provider } from 'react-redux'
 | 
			
		||||
 | 
			
		||||
export function Providers({ children }: { children: React.ReactNode }) {
 | 
			
		||||
  return <Provider store={store}>{children}</Provider>
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										16
									
								
								frontend/redux/store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								frontend/redux/store.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,16 @@
 | 
			
		||||
import { configureStore } from '@reduxjs/toolkit'
 | 
			
		||||
import notificationQueueSlice from './features/notifications/notificationQueueSlice'
 | 
			
		||||
import { setupListeners } from '@reduxjs/toolkit/dist/query'
 | 
			
		||||
 | 
			
		||||
export const store = configureStore({
 | 
			
		||||
  reducer: {
 | 
			
		||||
    notificationQueue: notificationQueueSlice,
 | 
			
		||||
  },
 | 
			
		||||
  middleware: (getDefaultMiddleware) => getDefaultMiddleware(),
 | 
			
		||||
})
 | 
			
		||||
 | 
			
		||||
export type RootState = ReturnType<typeof store.getState>
 | 
			
		||||
 | 
			
		||||
export type AppDispatch = typeof store.dispatch
 | 
			
		||||
 | 
			
		||||
setupListeners(store.dispatch)
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user