diff --git a/client/src/Root.tsx b/client/src/Root.tsx
index 02f20d6..2756746 100644
--- a/client/src/Root.tsx
+++ b/client/src/Root.tsx
@@ -1,5 +1,6 @@
import { QueryClient, QueryClientProvider } from 'react-query'
import { AppContextProvider } from './contexts/AppContext'
+import { ConfirmModalsContextProvider } from './contexts/ConfirmModalsContext'
import { Router } from './pages/Router'
const queryClient = new QueryClient({
@@ -14,7 +15,9 @@ export const Root = () => {
return (
-
+
+
+
)
diff --git a/client/src/assets/components/_modal.scss b/client/src/assets/components/_modal.scss
new file mode 100644
index 0000000..2824126
--- /dev/null
+++ b/client/src/assets/components/_modal.scss
@@ -0,0 +1,49 @@
+.modal {
+ position: fixed;
+ top: 0;
+ left: 0;
+ bottom: 0;
+ right: 0;
+ overflow: auto;
+ display: none;
+ justify-content: center;
+ align-items: flex-start;
+ background-color: var(--modal-overlay-bg-color);
+ z-index: 5;
+
+ &.show {
+ display: flex;
+ }
+
+ &.confirm {
+ background-color: var(--confirm-overlay-bg-color);
+ }
+
+ .inner {
+ background-color: var(--box-bg-color);
+ color: var(--box-fg-color);
+ margin-top: 5vh;
+ box-shadow: var(--box-shadow);
+ border-radius: 0.5rem;
+ width: 20rem;
+ max-height: 90%;
+ overflow: auto;
+ max-width: 100%;
+
+ .body {
+ padding: 0.75rem 1rem;
+ }
+ }
+
+ .actions {
+ display: flex;
+ align-items: center;
+ justify-content: flex-end;
+
+ .remove {
+ margin-right: auto;
+ background-color: var(--button-remove-bg-color);
+ color: var(--button-remove-fg-color);
+ }
+ }
+}
diff --git a/client/src/assets/style.scss b/client/src/assets/style.scss
index 2e33aa6..e9955d3 100644
--- a/client/src/assets/style.scss
+++ b/client/src/assets/style.scss
@@ -20,7 +20,8 @@
--box-action-fg-color: #666;
--box-preview-bg-color: #3988ff;
--box-shadow: 0px 10px 15px -3px rgba(0, 0, 0, 0.1);
- --modal-overlay-bg-color: rgba(0, 0, 0, 0.2);
+ --modal-overlay-bg-color: rgba(0, 0, 0, 0.5);
+ --confirm-overlay-bg-color: rgba(0, 0, 0, 0.8);
--graph-axis-fg-color: #777;
--graph-grid-color: rgb(238, 238, 238);
--link-fg-color: #3988ff;
@@ -76,7 +77,7 @@ select {
font-family: var(--main-font);
}
-@import 'components/button';
+@import "components/button";
input,
select {
@@ -216,51 +217,7 @@ section.content {
border-radius: 0.5rem;
}
-.settings-modal {
- position: fixed;
- top: 0;
- left: 0;
- bottom: 0;
- right: 0;
- overflow: auto;
- display: none;
- justify-content: center;
- align-items: flex-start;
- background-color: var(--modal-overlay-bg-color);
- z-index: 5;
-}
-
-.settings-modal.show {
- display: flex;
-}
-
-.settings-modal .inner {
- background-color: var(--box-bg-color);
- color: var(--box-fg-color);
- margin-top: 5vh;
- box-shadow: var(--box-shadow);
- border-radius: 0.5rem;
- width: 20rem;
- max-height: 90%;
- overflow: auto;
- max-width: 100%;
-}
-
-.settings-modal .inner .body {
- padding: 0.75rem 1rem;
-}
-
-.settings-modal .actions {
- display: flex;
- align-items: center;
- justify-content: flex-end;
-}
-
-.settings-modal .actions .remove {
- margin-right: auto;
- background-color: var(--button-remove-bg-color);
- color: var(--button-remove-fg-color);
-}
+@import "components/modal";
form {
display: flex;
diff --git a/client/src/components/ConfirmModal.tsx b/client/src/components/ConfirmModal.tsx
new file mode 100644
index 0000000..eee24f9
--- /dev/null
+++ b/client/src/components/ConfirmModal.tsx
@@ -0,0 +1,35 @@
+import { ComponentChildren } from 'preact'
+import { Modal } from './Modal'
+
+type Props = {
+ open: boolean
+ onConfirm: () => void
+ onCancel: () => void
+ confirmText?: string
+ cancelText?: string
+ children: ComponentChildren
+}
+
+export const ConfirmModal = ({
+ children,
+ confirmText = 'Yes',
+ cancelText = 'No',
+ onCancel,
+ onConfirm,
+ open,
+}: Props) => {
+ return (
+
+ {children}
+
+
+
+
+
+
+ )
+}
diff --git a/client/src/components/Modal.tsx b/client/src/components/Modal.tsx
index b093420..7de9805 100644
--- a/client/src/components/Modal.tsx
+++ b/client/src/components/Modal.tsx
@@ -5,20 +5,26 @@ type Props = {
open: boolean
onClose: () => void
children: ComponentChild
+ width?: string
+ className?: string
}
-export const Modal = ({ open, onClose, children }: Props) => {
+export const Modal = ({ open, onClose, children, width, className }: Props) => {
const preventPropagation = (e: Event) => {
e.stopPropagation()
}
return (
-
+
diff --git a/client/src/contexts/ConfirmModalContext.tsx b/client/src/contexts/ConfirmModalsContext.tsx
similarity index 51%
rename from client/src/contexts/ConfirmModalContext.tsx
rename to client/src/contexts/ConfirmModalsContext.tsx
index b18dc1c..c3dbc70 100644
--- a/client/src/contexts/ConfirmModalContext.tsx
+++ b/client/src/contexts/ConfirmModalsContext.tsx
@@ -1,4 +1,4 @@
-import { Modal } from '@/components/Modal'
+import { ConfirmModal } from '@/components/ConfirmModal'
import { ComponentChild, createContext } from 'preact'
import { useCallback, useContext, useMemo, useState } from 'preact/hooks'
@@ -6,31 +6,33 @@ type Props = {
children: ComponentChild
}
-type ShowModalProps = {
+type CreateModalProps = {
content: ComponentChild
onConfirm: () => void
- onCancel: () => void
+ onCancel?: () => void
}
-type ShowModelResult = {
+type CreateModalResult = {
show: () => void
}
-type ConfirmModalContext = {
- createModal: (props: ShowModalProps) => ShowModelResult
+type ConfirmModalsContextType = {
+ createModal: (props: CreateModalProps) => CreateModalResult
}
type ModalState = {
id: string
- props: ShowModalProps
+ props: CreateModalProps
}
-const ConfirmModalContext = createContext
(null)
+const ConfirmModalsContext = createContext(
+ null
+)
-export const ConfirmModalContextProvider = ({ children }: Props) => {
+export const ConfirmModalsContextProvider = ({ children }: Props) => {
const [modals, setModals] = useState([] as ModalState[])
- const createModal = useCallback((props: ShowModalProps) => {
+ const createModal = useCallback((props: CreateModalProps) => {
return {
show: () =>
setModals((p) => [
@@ -43,7 +45,7 @@ export const ConfirmModalContextProvider = ({ children }: Props) => {
const handleClose = (modal: ModalState) => {
setModals((p) => p.filter((m) => m.id !== modal.id))
- modal.props.onCancel()
+ modal.props.onCancel?.()
}
const handleConfirm = (modal: ModalState) => {
@@ -55,21 +57,24 @@ export const ConfirmModalContextProvider = ({ children }: Props) => {
const value = useMemo(() => ({ createModal }), [createModal])
return (
-
+
{children}
{modals.map((m) => (
- handleClose(m)}>
+ handleClose(m)}
+ onConfirm={() => handleConfirm(m)}
+ >
{m.props.content}
-
-
-
+
))}
-
+
)
}
-export const useConfirmModalContext = () => {
- const ctx = useContext(ConfirmModalContext)
+export const useConfirmModalsContext = () => {
+ const ctx = useContext(ConfirmModalsContext)
if (!ctx) {
throw new Error('useConfirmModalContext used outside context')
@@ -78,8 +83,8 @@ export const useConfirmModalContext = () => {
return ctx
}
-export const useConfirmModal = (modal: ShowModalProps) => {
- const ctx = useConfirmModalContext()
+export const useConfirmModal = (modal: CreateModalProps) => {
+ const ctx = useConfirmModalsContext()
return ctx.createModal(modal)
}
diff --git a/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx b/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx
index b572014..e1ff990 100644
--- a/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx
+++ b/client/src/pages/dashboard/components/BoxSettings/BoxSettings.tsx
@@ -1,4 +1,6 @@
import { getSensors } from '@/api/sensors'
+import { Modal } from '@/components/Modal'
+import { useConfirmModal } from '@/contexts/ConfirmModalsContext'
import { DashboardDialData, DashboardGraphData } from '@/utils/parseDashboard'
import { useState } from 'preact/hooks'
import { useQuery } from 'react-query'
@@ -24,6 +26,13 @@ export const BoxSettings = ({ value, onSave, onClose, onRemove }: Props) => {
const [data, setData] = useState(() => value.data)
+ const deleteConfirm = useConfirmModal({
+ content: 'Are you sure you want to delete the box?',
+ onConfirm: () => {
+ onRemove()
+ },
+ })
+
const handleSave = async (e: Event) => {
e.preventDefault()
e.stopPropagation()
@@ -47,86 +56,73 @@ export const BoxSettings = ({ value, onSave, onClose, onRemove }: Props) => {
})
}
- const preventPropagation = (e: Event) => {
- e.stopPropagation()
- }
-
return (
-
-
+
+
)
}
diff --git a/client/src/pages/dashboard/components/SensorSettings.tsx b/client/src/pages/dashboard/components/SensorSettings.tsx
deleted file mode 100644
index ffcddb9..0000000
--- a/client/src/pages/dashboard/components/SensorSettings.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import { setSensorConfig } from '@/api/sensor'
-import { SensorInfo } from '@/api/sensors'
-import { useState } from 'preact/hooks'
-
-type Props = {
- sensor: SensorInfo
- onClose: () => void
- onUpdate: () => void
-}
-
-export const SensorSettings = ({ sensor, onClose, onUpdate }: Props) => {
- const [value, setValue] = useState(() => ({ ...sensor.config }))
- const [saving, setSaving] = useState(false)
-
- const handleSave = async (e: Event) => {
- e.preventDefault()
- e.stopPropagation()
-
- if (saving) {
- return
- }
-
- setSaving(true)
-
- try {
- await Promise.all(
- Object.entries(value).map(([name, value]) =>
- setSensorConfig({ sensor: sensor.id, name, value })
- )
- )
- } catch (err) {
- // TODO: Better error handling
- alert(err)
- }
-
- setSaving(false)
-
- onClose()
- onUpdate()
- }
-
- const handleChange = (e: Event) => {
- const target = e.target as HTMLSelectElement | HTMLInputElement
-
- setValue({
- ...value,
- [target.name]: target.value,
- })
- }
-
- const preventPropagation = (e: Event) => {
- e.stopPropagation()
- }
-
- return (
-
- )
-}
diff --git a/client/src/pages/sensors/components/SensorFormModal.tsx b/client/src/pages/sensors/components/SensorFormModal.tsx
index 0974d02..5bbe34c 100644
--- a/client/src/pages/sensors/components/SensorFormModal.tsx
+++ b/client/src/pages/sensors/components/SensorFormModal.tsx
@@ -1,6 +1,6 @@
import { createSensor, SensorInfo, updateSensor } from '@/api/sensors'
import { Modal } from '@/components/Modal'
-import { useState } from 'preact/hooks'
+import { useForm } from '@/utils/hooks/useForm'
import { useMutation, useQueryClient } from 'react-query'
type Props = {
@@ -14,43 +14,35 @@ export const SensorFormModal = ({ open, onClose, sensor }: Props) => {
const createMutation = useMutation(createSensor)
const updateMutation = useMutation(updateSensor)
- const [formState, setFormState] = useState(() => ({
- name: sensor?.name ?? '',
- }))
+ const { value, handleSubmit, handleChange } = useForm({
+ defaultValue: () => ({
+ name: sensor?.name ?? '',
+ }),
+ onSubmit: async (v) => {
+ if (isLoading) {
+ return
+ }
- const handleSave = async (e: Event) => {
- e.preventDefault()
- e.stopPropagation()
+ if (sensor) {
+ await updateMutation.mutateAsync({
+ id: sensor.id,
+ name: v.name,
+ })
+ } else {
+ await createMutation.mutateAsync(v.name)
+ }
- if (isLoading) {
- return
- }
+ queryClient.invalidateQueries(['/sensors'])
- if (sensor) {
- await updateMutation.mutateAsync({ id: sensor.id, name: formState.name })
- } else {
- await createMutation.mutateAsync(formState.name)
- }
-
- queryClient.invalidateQueries(['/sensors'])
-
- onClose()
- }
-
- const handleChange = (e: Event) => {
- const target = e.target as HTMLSelectElement | HTMLInputElement
-
- setFormState({
- ...formState,
- [target.name]: target.value,
- })
- }
+ onClose()
+ },
+ })
const isLoading = createMutation.isLoading || updateMutation.isLoading
return (
-