+
{errors.latitude?.message ? (
- {String(errors.latitude.message)}
+ {t(String(errors.latitude.message))}
) : null}
@@ -174,7 +228,76 @@ export function LocationStep() {
/>
{errors.longitude?.message ? (
- {String(errors.longitude.message)}
+ {t(String(errors.longitude.message))}
+
+ ) : null}
+
+
+
+
+
+
+ {t('height_info_text')}
+
+
+ {elevation.status === 'loading' ? (
+
+
+
+
+
+ {t('fetching_elevation')}
+
+
+ ) : elevation.status === 'error' ? (
+
+
+ {elevation.error === 'unavailable'
+ ? t('elevation_unavailable')
+ : t('elevation_error')}
+
+
+
+ ) : elevation.result ? (
+
+
+ {t('terrain_elevation')}:{' '}
+ {Math.round(elevation.result.elevation)} m
+
+ {finalHeight !== null ? (
+
+ {t('final_height')}: {Math.round(finalHeight)} m
+
+ ) : null}
+
+ {t('elevation_source')}:{' '}
+ {elevation.result.attribution ?? elevation.result.dataset}
+ {elevation.result.datum ? ` (${elevation.result.datum})` : ''}
+
+
+ ) : null}
+
+ {errors.heightAboveGround?.message ? (
+
+ {t(String(errors.heightAboveGround.message))}
) : null}
diff --git a/app/components/device/new/new-device-stepper.tsx b/app/components/device/new/new-device-stepper.tsx
index 57b386c2..cdd8b623 100644
--- a/app/components/device/new/new-device-stepper.tsx
+++ b/app/components/device/new/new-device-stepper.tsx
@@ -4,17 +4,19 @@ import { Info, Slash } from 'lucide-react'
import { type MouseEvent, useEffect, useState } from 'react'
import { type FieldErrors, FormProvider, useForm } from 'react-hook-form'
import { useTranslation } from 'react-i18next'
-import { Form, useLoaderData, useSubmit, useNavigation } from 'react-router'
+import {
+ Form,
+ useActionData,
+ useLoaderData,
+ useNavigation,
+ useSubmit,
+} from 'react-router'
import { z } from 'zod'
import { AdvancedStep } from './advanced-info'
import { DeviceSelectionStep } from './device-info'
import { GeneralInfoStep } from './general-info'
import { LocationStep } from './location-info'
-import {
- customDeviceSchemaUploadSchema,
- sensorSchema,
- SensorSelectionStep,
-} from './sensors-info'
+import { SensorSelectionStep } from './sensors-info'
import { SummaryInfo } from './summary-info'
import {
Breadcrumb,
@@ -31,34 +33,23 @@ import {
TooltipTrigger,
} from '~/components/ui/tooltip'
import { useToast } from '~/components/ui/use-toast'
-import { DeviceModelEnum } from '~/db/schema/enum'
-import { type loader } from '~/routes/device.new'
-import { locationSchema, type LocationData } from '~/lib/location'
+import { type action, type loader } from '~/routes/device.new'
+import {
+ advancedSchema,
+ deviceSelectionSchema,
+ sensorSelectionSchema,
+} from '~/lib/new-device-form'
+import {
+ deviceLocationInputSchema,
+ type DeviceLocationInput,
+} from '~/lib/location'
import { generalInfoSchema, type GeneralInfoData } from '~/lib/device-general'
-const deviceSchema = z.object({
- model: z.enum(DeviceModelEnum.enumValues, {
- error: () => 'Please select a device.',
- }),
-})
-
-// selectedSensors can be an array of sensors
-const sensorsSchema = z.object({
- selectedSensors: z
- .array(sensorSchema)
- .min(1, 'Please select at least one sensor'),
- deviceSchema: customDeviceSchemaUploadSchema,
- deviceSchemaVersionId: z.string().optional(),
- deviceSchemaRegistrySelection: z.any().optional(),
-})
-
-const advancedSchema = z.record(z.string(), z.any())
-
const formSchema = z.union([
generalInfoSchema,
- locationSchema,
- deviceSchema,
- sensorsSchema,
+ deviceLocationInputSchema,
+ deviceSelectionSchema,
+ sensorSelectionSchema,
advancedSchema,
])
@@ -74,21 +65,21 @@ export const Stepper = defineStepper([
id: 'location',
label: 'location',
infoKey: 'location_info_text',
- schema: locationSchema,
+ schema: deviceLocationInputSchema,
index: 1,
},
{
id: 'device-selection',
label: 'device_selection',
infoKey: 'device_selection_info_text',
- schema: deviceSchema,
+ schema: deviceSelectionSchema,
index: 2,
},
{
id: 'sensor-selection',
label: 'sensor_selection',
infoKey: 'sensor_selection_info_text',
- schema: sensorsSchema,
+ schema: sensorSelectionSchema,
index: 3,
},
{
@@ -107,13 +98,13 @@ export const Stepper = defineStepper([
},
])
-type DeviceData = z.infer
-type SensorData = z.infer
+type DeviceData = z.infer
+type SensorData = z.infer
type AdvancedData = z.infer
type FormData =
| GeneralInfoData
- | LocationData
+ | DeviceLocationInput
| DeviceData
| SensorData
| AdvancedData
@@ -135,12 +126,23 @@ export default function NewDeviceStepper() {
const { t } = useTranslation('newdevice')
const [isFirst, setIsFirst] = useState(false)
const navigation = useNavigation()
+ const actionData = useActionData()
const isSubmitting = navigation.state !== 'idle'
useEffect(() => {
setIsFirst(stepper.isFirst)
}, [stepper.isFirst])
+ useEffect(() => {
+ if (!actionData || actionData.ok) return
+
+ toast({
+ title: t('device_creation_error'),
+ description: t(actionData.error),
+ variant: 'destructive',
+ })
+ }, [actionData, t, toast])
+
const onSubmit = (data: FormData) => {
const updatedData = {
...formData,
@@ -173,13 +175,26 @@ export default function NewDeviceStepper() {
if (message) {
toast({
title: 'Form Error',
- description: message,
+ description: t(message),
variant: 'destructive',
duration: 2000,
})
}
}
+ const onBack = () => {
+ const parsed = stepper.current.schema.safeParse(form.getValues())
+
+ if (parsed.success) {
+ setFormData((current) => ({
+ ...current,
+ [stepper.current.id]: parsed.data,
+ }))
+ }
+
+ stepper.prev()
+ }
+
return (
@@ -196,7 +211,17 @@ export default function NewDeviceStepper() {
stepper.goTo(step.id)}
+ onClick={() => {
+ if (stepper.current.id === step.id) return
+
+ void form.handleSubmit((data) => {
+ setFormData((current) => ({
+ ...current,
+ [stepper.current.id]: data,
+ }))
+ stepper.goTo(step.id)
+ }, onError)()
+ }}
className={` ${
stepper.index === step.index
? 'text-foreground font-bold'
@@ -262,7 +287,7 @@ export default function NewDeviceStepper() {