11import type { AccessCode } from '@seamapi/types/connect'
22import classNames from 'classnames'
33import { DateTime } from 'luxon'
4- import { useState } from 'react'
4+ import { useCallback , useEffect , useState } from 'react'
55
66import { CopyIcon } from 'lib/icons/Copy.js'
77import { useAccessCode } from 'lib/seam/access-codes/use-access-code.js'
@@ -12,6 +12,7 @@ import {
1212 withRequiredCommonProps ,
1313} from 'lib/seam/components/common-props.js'
1414import { NestedDeviceDetails } from 'lib/seam/components/DeviceDetails/DeviceDetails.js'
15+ import { NestedEditAccessCodeForm } from 'lib/seam/components/EditAccessCodeForm/EditAccessCodeForm.js'
1516import {
1617 accessCodeErrorFilter ,
1718 accessCodeWarningFilter ,
@@ -22,11 +23,15 @@ import { Button } from 'lib/ui/Button.js'
2223import { copyToClipboard } from 'lib/ui/clipboard.js'
2324import { IconButton } from 'lib/ui/IconButton.js'
2425import { ContentHeader } from 'lib/ui/layout/ContentHeader.js'
26+ import { Snackbar } from 'lib/ui/Snackbar/Snackbar.js'
2527import { useIsDateInPast } from 'lib/ui/use-is-date-in-past.js'
2628
2729export interface AccessCodeDetailsProps extends CommonProps {
2830 accessCodeId : string
29- onEdit : ( ) => void
31+ onEdit ?: ( ) => void
32+ preventDefaultOnEdit ?: boolean
33+ onDelete ?: ( ) => void
34+ preventDefaultOnDelete ?: boolean
3035}
3136
3237export const NestedAccessCodeDetails =
@@ -35,6 +40,9 @@ export const NestedAccessCodeDetails =
3540export function AccessCodeDetails ( {
3641 accessCodeId,
3742 onEdit,
43+ preventDefaultOnEdit = false ,
44+ onDelete,
45+ preventDefaultOnDelete = false ,
3846 errorFilter = ( ) => true ,
3947 warningFilter = ( ) => true ,
4048 disableCreateAccessCode = false ,
@@ -52,12 +60,72 @@ export function AccessCodeDetails({
5260 const { accessCode } = useAccessCode ( { access_code_id : accessCodeId } )
5361 const [ selectedDeviceId , selectDevice ] = useState < string | null > ( null )
5462 const { mutate : deleteCode , isPending : isDeleting } = useDeleteAccessCode ( )
63+ const [ editFormOpen , setEditFormOpen ] = useState < boolean > ( false )
64+
65+ const [ accessCodeResult , setAccessCodeResult ] = useState <
66+ 'updated' | 'deleted' | null
67+ > ( null )
68+ const [ snackbarMessage , setSnackbarMessage ] = useState < string > ( '' )
69+
70+ // Circumvent Snackbar bug that causes it to switch to default message
71+ // while the dismiss animation is playing
72+ useEffect ( ( ) => {
73+ if ( accessCodeResult !== null ) {
74+ setSnackbarMessage ( accessCodeResultToMessage ( accessCodeResult ) )
75+ }
76+ } , [ accessCodeResult ] )
77+
78+ const handleEdit = useCallback ( ( ) : void => {
79+ onEdit ?.( )
80+ if ( preventDefaultOnEdit ) return
81+ setEditFormOpen ( true )
82+ } , [ onEdit , preventDefaultOnEdit , setEditFormOpen ] )
83+
84+ const handleDelete = useCallback ( ( ) : void => {
85+ onDelete ?.( )
86+ if ( preventDefaultOnDelete ) return
87+ if ( accessCode == null ) return
88+ deleteCode (
89+ { access_code_id : accessCode . access_code_id } ,
90+ {
91+ onSuccess : ( ) => {
92+ setAccessCodeResult ( 'deleted' )
93+ } ,
94+ }
95+ )
96+ } , [ accessCode , deleteCode , onDelete , preventDefaultOnDelete ] )
5597
5698 if ( accessCode == null ) {
5799 return null
58100 }
59101
60102 const name = accessCode . name ?? t . fallbackName
103+ const isAccessCodeBeingRemoved = accessCode . status === 'removing'
104+
105+ if ( editFormOpen ) {
106+ return (
107+ < NestedEditAccessCodeForm
108+ accessCodeId = { accessCode . access_code_id }
109+ errorFilter = { errorFilter }
110+ warningFilter = { warningFilter }
111+ disableLockUnlock = { disableLockUnlock }
112+ disableCreateAccessCode = { disableCreateAccessCode }
113+ disableEditAccessCode = { disableEditAccessCode }
114+ disableDeleteAccessCode = { disableDeleteAccessCode }
115+ disableResourceIds = { disableResourceIds }
116+ disableConnectedAccountInformation = { disableConnectedAccountInformation }
117+ disableClimateSettingSchedules = { disableClimateSettingSchedules }
118+ onBack = { ( ) => {
119+ setEditFormOpen ( false )
120+ } }
121+ onSuccess = { ( ) => {
122+ setAccessCodeResult ( 'updated' )
123+ setEditFormOpen ( false )
124+ } }
125+ className = { className }
126+ />
127+ )
128+ }
61129
62130 if ( selectedDeviceId != null ) {
63131 return (
@@ -96,92 +164,114 @@ export function AccessCodeDetails({
96164 variant : 'warning' as const ,
97165 message : warning . message ,
98166 } ) ) ,
167+
168+ ...( isAccessCodeBeingRemoved
169+ ? [
170+ {
171+ variant : 'warning' as const ,
172+ message : t . warningRemoving ,
173+ } ,
174+ ]
175+ : [ ] ) ,
99176 ]
100177
101178 return (
102- < div className = { classNames ( 'seam-access-code-details' , className ) } >
103- < ContentHeader title = 'Access code' onBack = { onBack } />
104- < div className = 'seam-summary' >
105- < div
106- className = { classNames (
107- 'seam-top' ,
108- alerts . length > 0 && 'seam-top-has-alerts'
109- ) }
110- >
111- < span className = 'seam-label' > { t . accessCode } </ span >
112- < h5 className = 'seam-access-code-name' > { name } </ h5 >
113- < div className = 'seam-code' >
114- < span > { accessCode . code } </ span >
115- < IconButton
116- onClick = { ( ) => {
117- void copyToClipboard ( accessCode . code ?? '' )
118- } }
119- >
120- < CopyIcon />
121- </ IconButton >
122- </ div >
123- < div className = 'seam-duration' >
124- < Duration accessCode = { accessCode } />
125- </ div >
126- </ div >
127- < Alerts alerts = { alerts } className = 'seam-alerts-padded' />
128- < AccessCodeDevice
129- deviceId = { accessCode . device_id }
130- disableLockUnlock = { disableLockUnlock }
131- onSelectDevice = { selectDevice }
132- />
133- </ div >
134- { ( ! disableEditAccessCode || ! disableDeleteAccessCode ) && (
135- < div className = 'seam-actions' >
136- { ! disableEditAccessCode && (
137- < Button size = 'small' onClick = { onEdit } disabled = { isDeleting } >
138- { t . editCode }
139- </ Button >
140- ) }
141- { ! disableDeleteAccessCode && (
142- < Button
143- size = 'small'
144- onClick = { ( ) => {
145- deleteCode ( { access_code_id : accessCode . access_code_id } )
146- } }
147- disabled = { isDeleting }
148- >
149- { t . deleteCode }
150- </ Button >
151- ) }
152- </ div >
153- ) }
154- < div className = 'seam-details' >
155- { ! disableResourceIds && (
156- < div className = 'seam-row' >
157- < div className = 'seam-heading' > { t . id } :</ div >
158- < div className = 'seam-content seam-code-id' >
159- < span > { accessCode . access_code_id } </ span >
179+ < >
180+ < Snackbar
181+ variant = 'success'
182+ message = { snackbarMessage }
183+ visible = { accessCodeResult != null }
184+ autoDismiss
185+ onClose = { ( ) => {
186+ setAccessCodeResult ( null )
187+ } }
188+ />
189+ < div className = { classNames ( 'seam-access-code-details' , className ) } >
190+ < ContentHeader title = 'Access code' onBack = { onBack } />
191+ < div className = 'seam-summary' >
192+ < div
193+ className = { classNames (
194+ 'seam-top' ,
195+ alerts . length > 0 && 'seam-top-has-alerts'
196+ ) }
197+ >
198+ < span className = 'seam-label' > { t . accessCode } </ span >
199+ < h5 className = 'seam-access-code-name' > { name } </ h5 >
200+ < div className = 'seam-code' >
201+ < span > { accessCode . code } </ span >
160202 < IconButton
161203 onClick = { ( ) => {
162- void copyToClipboard ( accessCode . access_code_id )
204+ void copyToClipboard ( accessCode . code ?? '' )
163205 } }
164206 >
165207 < CopyIcon />
166208 </ IconButton >
167209 </ div >
210+ < div className = 'seam-duration' >
211+ < Duration accessCode = { accessCode } />
212+ </ div >
213+ </ div >
214+ < Alerts alerts = { alerts } className = 'seam-alerts-padded' />
215+ < AccessCodeDevice
216+ deviceId = { accessCode . device_id }
217+ disableLockUnlock = { disableLockUnlock }
218+ onSelectDevice = { selectDevice }
219+ />
220+ </ div >
221+ { ( ! disableEditAccessCode || ! disableDeleteAccessCode ) && (
222+ < div className = 'seam-actions' >
223+ { ! disableEditAccessCode && (
224+ < Button
225+ size = 'small'
226+ onClick = { handleEdit }
227+ disabled = { isAccessCodeBeingRemoved || isDeleting }
228+ >
229+ { t . editCode }
230+ </ Button >
231+ ) }
232+ { ! disableDeleteAccessCode && (
233+ < Button
234+ size = 'small'
235+ onClick = { handleDelete }
236+ disabled = { isAccessCodeBeingRemoved || isDeleting }
237+ >
238+ { t . deleteCode }
239+ </ Button >
240+ ) }
168241 </ div >
169242 ) }
170- < div className = 'seam-row' >
171- < div className = 'seam-heading' > { t . created } :</ div >
172- < div className = 'seam-content' >
173- { formatDate ( accessCode . created_at ) }
243+ < div className = 'seam-details' >
244+ { ! disableResourceIds && (
245+ < div className = 'seam-row' >
246+ < div className = 'seam-heading' > { t . id } :</ div >
247+ < div className = 'seam-content seam-code-id' >
248+ < span > { accessCode . access_code_id } </ span >
249+ < IconButton
250+ onClick = { ( ) => {
251+ void copyToClipboard ( accessCode . access_code_id )
252+ } }
253+ >
254+ < CopyIcon />
255+ </ IconButton >
256+ </ div >
257+ </ div >
258+ ) }
259+ < div className = 'seam-row' >
260+ < div className = 'seam-heading' > { t . created } :</ div >
261+ < div className = 'seam-content' >
262+ { formatDate ( accessCode . created_at ) }
263+ </ div >
174264 </ div >
175- </ div >
176265
177- < div className = 'seam-row seam-schedule' >
178- < div className = 'seam-heading' > { t . timing } :</ div >
179- < div className = 'seam-content' >
180- < ScheduleInfo accessCode = { accessCode } />
266+ < div className = 'seam-row seam-schedule' >
267+ < div className = 'seam-heading' > { t . timing } :</ div >
268+ < div className = 'seam-content' >
269+ < ScheduleInfo accessCode = { accessCode } />
270+ </ div >
181271 </ div >
182272 </ div >
183273 </ div >
184- </ div >
274+ </ >
185275 )
186276}
187277
@@ -266,6 +356,11 @@ const formatDate = (date: string): string =>
266356 year : 'numeric' ,
267357 } )
268358
359+ const accessCodeResultToMessage = ( result : 'updated' | 'deleted' ) : string => {
360+ if ( result === 'deleted' ) return t . accessCodeDeleted
361+ return t . accessCodeUpdated
362+ }
363+
269364const t = {
270365 accessCode : 'Access code' ,
271366 fallbackName : 'Code' ,
@@ -282,4 +377,7 @@ const t = {
282377 at : 'at' ,
283378 editCode : 'Edit code' ,
284379 deleteCode : 'Delete code' ,
380+ warningRemoving : 'This access code is currently being removed.' ,
381+ accessCodeUpdated : 'Access code updated' ,
382+ accessCodeDeleted : 'Access code is being removed' ,
285383}
0 commit comments