1- import { useFormContext } from 'react-hook-form'
2- import {
3- Card ,
4- CardContent ,
5- CardDescription ,
6- CardHeader ,
7- CardTitle ,
8- } from '~/components/ui/card'
9- import { Input } from '~/components/ui/input'
10- import { Label } from '~/components/ui/label'
11- import {
12- Select ,
13- SelectContent ,
14- SelectItem ,
15- SelectTrigger ,
16- SelectValue ,
17- } from '~/components/ui/select'
18- import { Switch } from '~/components/ui/switch'
19- import { Textarea } from '~/components/ui/textarea'
20-
21- export function AdvancedStep ( ) {
22- const { register, setValue, watch, resetField } = useFormContext ( )
23-
24- // Watch field states
25- const isMqttEnabled = watch ( 'mqttEnabled' ) || false
26- const isTtnEnabled = watch ( 'ttnEnabled' ) || false
27-
28- // Clear corresponding fields when disabling
29- const handleMqttToggle = ( checked : boolean ) => {
30- setValue ( 'mqttEnabled' , checked )
31- if ( ! checked ) {
32- resetField ( 'url' )
33- resetField ( 'topic' )
34- resetField ( 'messageFormat' )
35- resetField ( 'decodeOptions' )
36- resetField ( 'connectionOptions' )
37- }
38- }
39-
40- const handleTtnToggle = ( checked : boolean ) => {
41- setValue ( 'ttnEnabled' , checked )
42- if ( ! checked ) {
43- resetField ( 'dev_id' )
44- resetField ( 'app_id' )
45- resetField ( 'profile' )
46- resetField ( 'decodeOptions' )
47- resetField ( 'port' )
48- }
49- }
50-
51- const handleInputChange = (
52- event : React . ChangeEvent < HTMLInputElement | HTMLTextAreaElement > ,
53- ) => {
54- const { name, value } = event . target
55- setValue ( name , value )
56- }
57-
58- const handleSelectChange = ( field : string , value : string ) => {
59- setValue ( field , value )
60- }
61-
62- return (
63- < >
64- { /* MQTT Configuration */ }
65- < Card className = "w-full" >
66- < CardHeader >
67- < CardTitle > MQTT Configuration</ CardTitle >
68- < CardDescription >
69- Configure your MQTT settings for data streaming
70- </ CardDescription >
71- </ CardHeader >
72- < CardContent >
73- < div className = "flex items-center justify-between space-x-2" >
74- < Label htmlFor = "mqttEnabled" className = "text-base font-semibold" >
75- Enable MQTT
76- </ Label >
77- < Switch
78- disabled
79- id = "mqttEnabled"
80- checked = { isMqttEnabled }
81- onCheckedChange = { handleMqttToggle }
82- />
83- </ div >
84-
85- { isMqttEnabled && (
86- < div className = "space-y-4" >
87- < div className = "space-y-2" >
88- < Label htmlFor = "mqtt-url" > MQTT URL</ Label >
89- < Input
90- id = "mqtt-url"
91- placeholder = "mqtt://example.com:1883"
92- { ...register ( 'url' ) }
93- onChange = { handleInputChange }
94- />
95- </ div >
96-
97- < div className = "space-y-2" >
98- < Label htmlFor = "mqtt-topic" > MQTT Topic</ Label >
99- < Input
100- id = "mqtt-topic"
101- placeholder = "my/mqtt/topic"
102- { ...register ( 'topic' ) }
103- onChange = { handleInputChange }
104- />
105- </ div >
106-
107- < div className = "space-y-2" >
108- < Label htmlFor = "mqtt-message-format" > Message Format</ Label >
109- < Select
110- onValueChange = { ( value ) =>
111- handleSelectChange ( 'messageFormat' , value )
112- }
113- defaultValue = { watch ( 'messageFormat' ) }
114- >
115- < SelectTrigger id = "mqtt-message-format" >
116- < SelectValue placeholder = "Select a message format" />
117- </ SelectTrigger >
118- < SelectContent >
119- < SelectItem value = "json" > JSON</ SelectItem >
120- < SelectItem value = "csv" > CSV</ SelectItem >
121- </ SelectContent >
122- </ Select >
123- </ div >
124-
125- < div className = "space-y-2" >
126- < Label htmlFor = "mqtt-decode-options" > Decode Options</ Label >
127- < Textarea
128- id = "mqtt-decode-options"
129- placeholder = "Enter decode options as JSON"
130- className = "resize-none"
131- { ...register ( 'decodeOptions' ) }
132- onChange = { handleInputChange }
133- />
134- </ div >
135-
136- < div className = "space-y-2" >
137- < Label htmlFor = "mqtt-connection-options" >
138- Connection Options
139- </ Label >
140- < Textarea
141- id = "mqtt-connection-options"
142- placeholder = "Enter connection options as JSON"
143- className = "resize-none"
144- { ...register ( 'connectionOptions' ) }
145- onChange = { handleInputChange }
146- />
147- </ div >
148- </ div >
149- ) }
150- </ CardContent >
151- </ Card >
152-
153- { /* TTN Configuration */ }
154- < Card className = "mt-6 w-full" >
155- < CardHeader >
156- < CardTitle > TTN Configuration</ CardTitle >
157- < CardDescription >
158- Configure your TTN (The Things Network) settings
159- </ CardDescription >
160- </ CardHeader >
161- < CardContent >
162- < div className = "flex items-center justify-between space-x-2" >
163- < Label htmlFor = "ttnEnabled" className = "text-base font-semibold" >
164- Enable TTN
165- </ Label >
166- < Switch
167- disabled
168- id = "ttnEnabled"
169- checked = { isTtnEnabled }
170- onCheckedChange = { handleTtnToggle }
171- />
172- </ div >
173-
174- { isTtnEnabled && (
175- < div className = "space-y-4" >
176- < div className = "space-y-2" >
177- < Label htmlFor = "ttn-dev-id" > Device ID</ Label >
178- < Input
179- id = "ttn-dev-id"
180- placeholder = "Enter TTN Device ID"
181- { ...register ( 'dev_id' ) }
182- onChange = { handleInputChange }
183- />
184- </ div >
185-
186- < div className = "space-y-2" >
187- < Label htmlFor = "ttn-app-id" > Application ID</ Label >
188- < Input
189- id = "ttn-app-id"
190- placeholder = "Enter TTN Application ID"
191- { ...register ( 'app_id' ) }
192- onChange = { handleInputChange }
193- />
194- </ div >
195-
196- < div className = "space-y-2" >
197- < Label htmlFor = "ttn-profile" > Profile</ Label >
198- < Select
199- onValueChange = { ( value ) =>
200- handleSelectChange ( 'profile' , value )
201- }
202- defaultValue = { watch ( 'profile' ) }
203- >
204- < SelectTrigger id = "ttn-profile" >
205- < SelectValue placeholder = "Select a profile" />
206- </ SelectTrigger >
207- < SelectContent >
208- < SelectItem value = "lora-serialization" >
209- Lora Serialization
210- </ SelectItem >
211- < SelectItem value = "sensebox/home" > Sensebox/Home</ SelectItem >
212- < SelectItem value = "json" > JSON</ SelectItem >
213- < SelectItem value = "debug" > Debug</ SelectItem >
214- < SelectItem value = "cayenne-lpp" > Cayenne LPP</ SelectItem >
215- </ SelectContent >
216- </ Select >
217- </ div >
218-
219- < div className = "space-y-2" >
220- < Label htmlFor = "ttn-decode-options" > Decode Options</ Label >
221- < Textarea
222- id = "ttn-decode-options"
223- placeholder = "Enter decode options as JSON"
224- className = "resize-none"
225- { ...register ( 'decodeOptions' ) }
226- onChange = { handleInputChange }
227- />
228- </ div >
1+ import Form from "@rjsf/core" ;
2+ import validator from "@rjsf/validator-ajv8" ;
3+ import { useEffect , useState } from "react" ;
4+ import { useFormContext } from "react-hook-form" ;
5+ import { CheckboxWidget } from "~/components/rjsf/checkboxWidget" ;
6+ import { FieldTemplate } from "~/components/rjsf/fieldTemplate" ;
7+ import { BaseInputTemplate } from "~/components/rjsf/inputTemplate" ;
8+ import { Card , CardContent , CardDescription , CardHeader , CardTitle } from "~/components/ui/card" ;
9+ import { Label } from "~/components/ui/label" ;
10+ import { Switch } from "~/components/ui/switch" ;
11+
12+ interface Integration {
13+ id : string ;
14+ name : string ;
15+ slug : string ;
16+ icon ?: string | null ;
17+ description ?: string | null ;
18+ order : number ;
19+ }
22920
230- < div className = "space-y-2" >
231- < Label htmlFor = "ttn-port" > Port</ Label >
232- < Input
233- id = "ttn-port"
234- placeholder = "Enter TTN Port"
235- type = "number"
236- { ...register ( 'port' , { valueAsNumber : true } ) }
237- onChange = { handleInputChange }
238- />
239- </ div >
240- </ div >
241- ) }
242- </ CardContent >
243- </ Card >
244- </ >
245- )
21+ interface AdvancedStepProps {
22+ integrations : Integration [ ] ;
24623}
24+
25+ export function AdvancedStep ( { integrations } : AdvancedStepProps ) {
26+ const { watch, setValue, resetField } = useFormContext ( ) ;
27+ const [ schemas , setSchemas ] = useState < Record < string , { schema : any ; uiSchema : any } > > ( { } ) ;
28+ const [ loading , setLoading ] = useState < Record < string , boolean > > ( { } ) ;
29+
30+
31+ const loadSchema = async ( slug : string ) => {
32+ if ( schemas [ slug ] ) return ;
33+
34+ setLoading ( ( prev ) => ( { ...prev , [ slug ] : true } ) ) ;
35+
36+ try {
37+ const res = await fetch ( `/api/integrations/schema/${ slug } ` ) ;
38+ if ( ! res . ok ) throw new Error ( `Failed to fetch ${ slug } schema` ) ;
39+
40+ const data = await res . json ( ) ;
41+ setSchemas ( ( prev ) => ( { ...prev , [ slug ] : data } ) ) ;
42+ } catch ( err ) {
43+ console . error ( `Failed to load ${ slug } schema` , err ) ;
44+ } finally {
45+ setLoading ( ( prev ) => ( { ...prev , [ slug ] : false } ) ) ;
46+ }
47+ }
48+
49+ const handleToggle = ( slug : string , checked : boolean ) => {
50+ setValue ( `${ slug } Enabled` , checked ) ;
51+
52+ if ( checked ) {
53+ void loadSchema ( slug ) ;
54+ } else {
55+ resetField ( `${ slug } Config` ) ;
56+ }
57+ } ;
58+
59+ return (
60+ < >
61+ { integrations . map ( ( intg ) => {
62+ const enabled = watch ( `${ intg . slug } Enabled` ) ?? false ;
63+ const config = watch ( `${ intg . slug } Config` ) ?? { } ;
64+ const isLoading = loading [ intg . slug ] ?? false ;
65+ const schema = schemas [ intg . slug ] ;
66+
67+ return (
68+ < Card key = { intg . id } className = "w-full mb-6" >
69+ < CardHeader >
70+ < CardTitle > { intg . name } Configuration</ CardTitle >
71+ { intg . description && (
72+ < CardDescription > { intg . description } </ CardDescription >
73+ ) }
74+ </ CardHeader >
75+
76+ < CardContent className = "space-y-4" >
77+ < div className = "flex items-center justify-between" >
78+ < Label htmlFor = { `${ intg . slug } Enabled` } className = "text-base font-semibold" >
79+ Enable { intg . name }
80+ </ Label >
81+ < Switch
82+ id = { `${ intg . slug } Enabled` }
83+ checked = { enabled }
84+ onCheckedChange = { ( checked ) => handleToggle ( intg . slug , checked ) }
85+ />
86+ </ div >
87+
88+ { enabled && (
89+ < >
90+ { isLoading && (
91+ < p className = "text-sm text-muted-foreground" >
92+ Loading { intg . name } configuration…
93+ </ p >
94+ ) }
95+
96+ { schema && (
97+ < Form
98+ widgets = { { CheckboxWidget } }
99+ templates = { { FieldTemplate, BaseInputTemplate } }
100+ schema = { schema . schema }
101+ uiSchema = { schema . uiSchema }
102+ validator = { validator }
103+ formData = { config }
104+ onChange = { ( e ) => {
105+ setValue ( `${ intg . slug } Config` , e . formData , {
106+ shouldDirty : true ,
107+ shouldValidate : true ,
108+ } ) ;
109+ } }
110+ onSubmit = { ( ) => { } }
111+ >
112+ < > </ >
113+ </ Form >
114+ ) }
115+ </ >
116+ ) }
117+ </ CardContent >
118+ </ Card >
119+ ) ;
120+ } ) }
121+ </ >
122+ ) ;
123+ }
0 commit comments