Skip to content

Commit cfcd69f

Browse files
authored
Merge pull request #682 from seamapi/kadir/cx-258-add-error-handling-to-climate-presets
fix: Add error messages to Thermostat Climate Preset Management
2 parents 2df135f + cfb8dd4 commit cfcd69f

File tree

3 files changed

+130
-73
lines changed

3 files changed

+130
-73
lines changed

src/lib/errors.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import { isSeamHttpApiError } from '@seamapi/http/connect'
2+
3+
export function getErrorMessage(error?: Error | null | undefined): string {
4+
if (isSeamHttpApiError(error)) {
5+
return error.message
6+
}
7+
8+
return t.anUnknownErrorOccurred
9+
}
10+
11+
const t = {
12+
anUnknownErrorOccurred: 'An unknown error occurred',
13+
}

src/lib/ui/thermostat/ClimatePreset.tsx

Lines changed: 56 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
} from 'react'
99
import { Controller, useForm, type UseFormReturn } from 'react-hook-form'
1010

11+
import { getErrorMessage } from 'lib/errors.js'
1112
import type {
1213
FanModeSetting,
1314
HvacModeSetting,
@@ -21,6 +22,7 @@ import { Button } from 'lib/ui/Button.js'
2122
import { FormField } from 'lib/ui/FormField.js'
2223
import { InputLabel } from 'lib/ui/InputLabel.js'
2324
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
25+
import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
2426
import { TextField } from 'lib/ui/TextField/TextField.js'
2527
import { ClimateModeMenu } from 'lib/ui/thermostat/ClimateModeMenu.js'
2628
import { FanModeMenu } from 'lib/ui/thermostat/FanModeMenu.js'
@@ -266,11 +268,14 @@ interface CreateFormProps {
266268
}
267269

268270
function CreateForm({ device, onComplete }: CreateFormProps): JSX.Element {
269-
const mutation = useCreateThermostatClimatePreset()
271+
const { mutate, isError, error, isPending } =
272+
useCreateThermostatClimatePreset()
273+
274+
const errorMessage = getErrorMessage(error)
270275

271276
const onSubmit = useCallback(
272277
(values: PresetFormProps['defaultValues']) => {
273-
mutation.mutate(
278+
mutate(
274279
{
275280
climate_preset_key: values.key,
276281
device_id: device.device_id,
@@ -285,24 +290,33 @@ function CreateForm({ device, onComplete }: CreateFormProps): JSX.Element {
285290
{ onSuccess: onComplete }
286291
)
287292
},
288-
[device, mutation, onComplete]
293+
[device, mutate, onComplete]
289294
)
290295

291296
return (
292-
<PresetForm
293-
defaultValues={{
294-
key: '',
295-
coolPoint: 60,
296-
heatPoint: 80,
297-
name: '',
298-
hvacMode: 'off',
299-
fanMode: 'auto',
300-
}}
301-
device={device}
302-
loading={mutation.isPending}
303-
onSubmit={onSubmit}
304-
withKeyField
305-
/>
297+
<>
298+
<Snackbar
299+
message={errorMessage}
300+
variant='error'
301+
visible={isError}
302+
automaticVisibility
303+
/>
304+
305+
<PresetForm
306+
defaultValues={{
307+
key: '',
308+
coolPoint: 60,
309+
heatPoint: 80,
310+
name: '',
311+
hvacMode: 'off',
312+
fanMode: 'auto',
313+
}}
314+
device={device}
315+
loading={isPending}
316+
onSubmit={onSubmit}
317+
withKeyField
318+
/>
319+
</>
306320
)
307321
}
308322

@@ -317,11 +331,13 @@ function UpdateForm({
317331
onComplete,
318332
preset,
319333
}: UpdateFormProps): JSX.Element {
320-
const mutation = useUpdateThermostatClimatePreset()
334+
const { mutate, isError, error, isPending } =
335+
useUpdateThermostatClimatePreset()
336+
321337
const defaultValues = useMemo<PresetFormProps['defaultValues']>(
322338
() => ({
323-
coolPoint: preset.cooling_set_point_fahrenheit ?? 60,
324-
heatPoint: preset.heating_set_point_fahrenheit ?? 80,
339+
coolPoint: preset.cooling_set_point_fahrenheit,
340+
heatPoint: preset.heating_set_point_fahrenheit,
325341
name: preset.display_name,
326342
hvacMode: preset.hvac_mode_setting,
327343
fanMode: preset.fan_mode_setting,
@@ -332,7 +348,7 @@ function UpdateForm({
332348

333349
const onSubmit = useCallback(
334350
(values: PresetFormProps['defaultValues']) => {
335-
mutation.mutate(
351+
mutate(
336352
{
337353
climate_preset_key: values.key,
338354
device_id: device.device_id,
@@ -347,16 +363,27 @@ function UpdateForm({
347363
{ onSuccess: onComplete }
348364
)
349365
},
350-
[device, mutation, onComplete]
366+
[device, mutate, onComplete]
351367
)
352368

369+
const errorMessage = getErrorMessage(error)
370+
353371
return (
354-
<PresetForm
355-
defaultValues={defaultValues}
356-
device={device}
357-
loading={mutation.isPending}
358-
onSubmit={onSubmit}
359-
/>
372+
<>
373+
<Snackbar
374+
message={errorMessage}
375+
variant='error'
376+
visible={isError}
377+
automaticVisibility
378+
/>
379+
380+
<PresetForm
381+
defaultValues={defaultValues}
382+
device={device}
383+
loading={isPending}
384+
onSubmit={onSubmit}
385+
/>
386+
</>
360387
)
361388
}
362389

@@ -370,4 +397,5 @@ const t = {
370397
delete: 'Delete',
371398
save: 'Save',
372399
crateNewPreset: 'Create New Climate Preset',
400+
unknownErrorOccured: 'An unknown error occurred.',
373401
}

src/lib/ui/thermostat/ClimatePresets.tsx

Lines changed: 61 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import classNames from 'classnames'
22
import { type HTMLAttributes, type ReactNode, useState } from 'react'
33

4+
import { getErrorMessage } from 'lib/errors.js'
45
import { AddIcon } from 'lib/icons/Add.js'
56
import { EditIcon } from 'lib/icons/Edit.js'
67
import { FanIcon } from 'lib/icons/Fan.js'
@@ -18,6 +19,7 @@ import { IconButton } from 'lib/ui/IconButton.js'
1819
import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
1920
import { Popover } from 'lib/ui/Popover/Popover.js'
2021
import { PopoverContentPrompt } from 'lib/ui/Popover/PopoverContentPrompt.js'
22+
import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
2123
import { Spinner } from 'lib/ui/Spinner/Spinner.js'
2224
import { ClimatePreset } from 'lib/ui/thermostat/ClimatePreset.js'
2325

@@ -40,7 +42,11 @@ export function ClimatePresets(props: ClimatePresetsManagement): JSX.Element {
4042
climatePresetKeySelectedForDeletion,
4143
setClimatePresetKeySelectedForDeletion,
4244
] = useState<ThermostatClimatePreset['climate_preset_key'] | null>(null)
43-
const deleteMutation = useDeleteThermostatClimatePreset()
45+
46+
const { mutate, isError, error, isPending } =
47+
useDeleteThermostatClimatePreset()
48+
49+
const errorMessage = getErrorMessage(error)
4450

4551
if (
4652
selectedClimatePreset != null ||
@@ -62,52 +68,61 @@ export function ClimatePresets(props: ClimatePresetsManagement): JSX.Element {
6268
}
6369

6470
return (
65-
<div className='seam-thermostat-climate-presets'>
66-
<ContentHeader title={t.title} onBack={onBack} />
67-
<div className='seam-thermostat-climate-presets-body'>
68-
<Button
69-
onClick={() => {
70-
setSelectedClimatePreset(CreateNewPresetSymbol)
71-
}}
72-
className='seam-climate-presets-add-button'
73-
>
74-
<AddIcon />
75-
{t.createNew}
76-
</Button>
77-
78-
<div className='seam-thermostat-climate-presets-cards'>
79-
{device.properties.available_climate_presets.map((preset) => (
80-
<PresetCard
81-
onClickEdit={() => {
82-
setSelectedClimatePreset(preset)
83-
}}
84-
onClickDelete={() => {
85-
setClimatePresetKeySelectedForDeletion(
86-
preset.climate_preset_key
87-
)
88-
deleteMutation.mutate({
89-
climate_preset_key: preset.climate_preset_key,
90-
device_id: device.device_id,
91-
})
92-
}}
93-
temperatureUnit={props.temperatureUnit}
94-
preset={preset}
95-
key={preset.climate_preset_key}
96-
deletionLoading={
97-
deleteMutation.isPending &&
98-
climatePresetKeySelectedForDeletion ===
99-
preset.climate_preset_key
100-
}
101-
disabled={
102-
deleteMutation.isPending &&
103-
climatePresetKeySelectedForDeletion !==
104-
preset.climate_preset_key
105-
}
106-
/>
107-
))}
71+
<>
72+
<Snackbar
73+
message={errorMessage}
74+
variant='error'
75+
visible={isError}
76+
automaticVisibility
77+
/>
78+
79+
<div className='seam-thermostat-climate-presets'>
80+
<ContentHeader title={t.title} onBack={onBack} />
81+
<div className='seam-thermostat-climate-presets-body'>
82+
<Button
83+
onClick={() => {
84+
setSelectedClimatePreset(CreateNewPresetSymbol)
85+
}}
86+
className='seam-climate-presets-add-button'
87+
>
88+
<AddIcon />
89+
{t.createNew}
90+
</Button>
91+
92+
<div className='seam-thermostat-climate-presets-cards'>
93+
{device.properties.available_climate_presets.map((preset) => (
94+
<PresetCard
95+
onClickEdit={() => {
96+
setSelectedClimatePreset(preset)
97+
}}
98+
onClickDelete={() => {
99+
setClimatePresetKeySelectedForDeletion(
100+
preset.climate_preset_key
101+
)
102+
mutate({
103+
climate_preset_key: preset.climate_preset_key,
104+
device_id: device.device_id,
105+
})
106+
}}
107+
temperatureUnit={props.temperatureUnit}
108+
preset={preset}
109+
key={preset.climate_preset_key}
110+
deletionLoading={
111+
isPending &&
112+
climatePresetKeySelectedForDeletion ===
113+
preset.climate_preset_key
114+
}
115+
disabled={
116+
isPending &&
117+
climatePresetKeySelectedForDeletion !==
118+
preset.climate_preset_key
119+
}
120+
/>
121+
))}
122+
</div>
108123
</div>
109124
</div>
110-
</div>
125+
</>
111126
)
112127
}
113128

@@ -232,4 +247,5 @@ const t = {
232247
createNew: 'Create New',
233248
delete: 'Delete',
234249
edit: 'Edit',
250+
climatePresetNotFound: 'Climate Preset not found.',
235251
}

0 commit comments

Comments
 (0)