1- import { labelExpiryDate , labelIsExpired , membership_t , UploadedLabel } from "frontend_common" ;
1+ import {
2+ labelExpiryDate ,
3+ labelIsExpired ,
4+ membership_t ,
5+ UploadedLabel ,
6+ } from "frontend_common" ;
27import { get , post } from "gateway" ;
38import { useEffect , useRef , useState } from "react" ;
49import { ActiveLogo } from "./ActiveLogo" ;
@@ -12,53 +17,94 @@ export interface LabelActionResponse {
1217
1318const labelUrlRegex = / \/ L \/ ( [ 0 - 9 _ - ] + ) $ / ;
1419
15- const ScanResultPopover = ( { labelAction, membership, state } : { labelAction : LabelActionResponse , membership : membership_t , state : "active" | "fading-out" } ) => {
16-
20+ const ScanResultPopover = ( {
21+ labelAction,
22+ membership,
23+ state,
24+ } : {
25+ labelAction : LabelActionResponse ;
26+ membership : membership_t ;
27+ state : "active" | "fading-out" ;
28+ } ) => {
1729 const label = labelAction . label . label ;
1830 const now = new Date ( ) ;
1931 const expiresAt = labelExpiryDate ( label , membership ) ;
2032 const isExpired = labelIsExpired ( now , label , membership ) ;
21-
22- return < div className = { `box-terminator-popover` + ( state === "fading-out" ? " fading-out" : "" ) + ( isExpired ? " expired" : " active" ) } >
23- < div >
33+
34+ return (
35+ < div
36+ className = {
37+ `box-terminator-popover` +
38+ ( state === "fading-out" ? " fading-out" : "" ) +
39+ ( isExpired ? " expired" : " active" )
40+ }
41+ >
2442 < div >
25- { isExpired ? < ExpiredLogo /> : < ActiveLogo /> }
43+ < div > { isExpired ? < ExpiredLogo /> : < ActiveLogo /> } </ div >
44+ { expiresAt && (
45+ < div >
46+ Expires
47+ { ( ( ) => {
48+ const expiresDate = new Date ( expiresAt ) ;
49+ const now = new Date ( ) ;
50+ const diffMs =
51+ expiresDate . getTime ( ) - now . getTime ( ) ;
52+ if ( diffMs < 0 ) return "in the past" ;
53+ const diffDays = Math . floor (
54+ diffMs / ( 1000 * 60 * 60 * 24 ) ,
55+ ) ;
56+ if ( diffDays > 0 )
57+ return `in ${ diffDays } day${
58+ diffDays > 1 ? "s" : ""
59+ } `;
60+ const diffHours = Math . floor (
61+ diffMs / ( 1000 * 60 * 60 ) ,
62+ ) ;
63+ if ( diffHours > 0 )
64+ return `in ${ diffHours } hour${
65+ diffHours > 1 ? "s" : ""
66+ } `;
67+ const diffMinutes = Math . floor (
68+ diffMs / ( 1000 * 60 ) ,
69+ ) ;
70+ if ( diffMinutes > 0 )
71+ return `in ${ diffMinutes } minute${
72+ diffMinutes > 1 ? "s" : ""
73+ } `;
74+ return "soon" ;
75+ } ) ( ) }
76+ </ div >
77+ ) }
78+ < a
79+ href = { labelAction . label . public_url }
80+ className = "uk-button uk-button-default"
81+ >
82+ View
83+ </ a >
2684 </ div >
27- { expiresAt && (
28- < div >
29- Expires
30- { ( ( ) => {
31- const expiresDate = new Date ( expiresAt ) ;
32- const now = new Date ( ) ;
33- const diffMs = expiresDate . getTime ( ) - now . getTime ( ) ;
34- if ( diffMs < 0 ) return "in the past" ;
35- const diffDays = Math . floor ( diffMs / ( 1000 * 60 * 60 * 24 ) ) ;
36- if ( diffDays > 0 ) return `in ${ diffDays } day${ diffDays > 1 ? "s" : "" } ` ;
37- const diffHours = Math . floor ( diffMs / ( 1000 * 60 * 60 ) ) ;
38- if ( diffHours > 0 ) return `in ${ diffHours } hour${ diffHours > 1 ? "s" : "" } ` ;
39- const diffMinutes = Math . floor ( diffMs / ( 1000 * 60 ) ) ;
40- if ( diffMinutes > 0 ) return `in ${ diffMinutes } minute${ diffMinutes > 1 ? "s" : "" } ` ;
41- return "soon" ;
42- } ) ( ) }
43- </ div >
44- ) }
45- < a
46- href = { labelAction . label . public_url }
47- className = "uk-button uk-button-default"
48- >
49- View
50- </ a >
5185 </ div >
52- </ div >
86+ ) ;
5387} ;
5488
5589const BoxTerminator = ( ) => {
5690 // Ensure only a single request is in flight at any one time
5791 const isScanning = useRef ( false ) ;
5892 const [ pendingScan , setPendingScan ] = useState < string | null > ( null ) ;
59- const [ lastScanResult , setLastScanResult ] = useState < { label : LabelActionResponse , membership : membership_t , state : "active" | "fading-out" , timer : NodeJS . Timeout } [ ] > ( [ ] ) ;
93+ const [ lastScanResult , setLastScanResult ] = useState <
94+ {
95+ label : LabelActionResponse ;
96+ membership : membership_t ;
97+ state : "active" | "fading-out" ;
98+ timer : NodeJS . Timeout ;
99+ } [ ]
100+ > ( [ ] ) ;
60101 const nullTimerRef = useRef < ReturnType < typeof setTimeout > | null > ( null ) ;
61- const scanCache = useRef ( new Map < string , { label : LabelActionResponse , membership : membership_t } | null > ( ) ) ;
102+ const scanCache = useRef (
103+ new Map <
104+ string ,
105+ { label : LabelActionResponse ; membership : membership_t } | null
106+ > ( ) ,
107+ ) ;
62108
63109 const processScan = async ( scannedString : string ) => {
64110 // Check cache first
@@ -79,24 +125,30 @@ const BoxTerminator = () => {
79125 }
80126 console . log ( matchedId ) ;
81127 let label : LabelActionResponse | null = null ;
82- let membership : membership_t | null = null ;
128+ let membership : membership_t | null = null ;
83129 try {
84130 if ( matchedId !== null ) {
85131 const [ observeRes , membershipRes ] = await Promise . all ( [
86132 post ( {
87133 url : `/multiaccess/memberbooth/label/${ matchedId } /observe` ,
88134 allowedErrorCodes : [ 404 ] ,
89- } ) , await get ( {
135+ } ) ,
136+ await get ( {
90137 url : `/multiaccess/memberbooth/label/${ matchedId } /membership` ,
91138 allowedErrorCodes : [ 404 ] ,
92- } )
139+ } ) ,
93140 ] ) ;
94141 label = observeRes . data as LabelActionResponse | null ;
95142 membership = membershipRes . data as membership_t ;
96143 } else {
97144 // Try old format
98145 const data = JSON . parse ( scannedString ) ;
99- if ( ( data [ "v" ] >= 1 && data [ "v" ] <= 2 ) && data . hasOwnProperty ( "member_number" ) && data . hasOwnProperty ( "type" ) ) {
146+ if (
147+ data [ "v" ] >= 1 &&
148+ data [ "v" ] <= 2 &&
149+ data . hasOwnProperty ( "member_number" ) &&
150+ data . hasOwnProperty ( "type" )
151+ ) {
100152 // Seems legit. Try to observe the label by id, which is just the timestamp for v1 and v2 labels
101153 const observeRes = await post ( {
102154 url : `/multiaccess/memberbooth/label/${ data . unix_timestamp } /observe` ,
@@ -106,7 +158,7 @@ const BoxTerminator = () => {
106158 if ( observeRes . data == null ) {
107159 // Submit unknown label and migrate to new format in the process
108160 const createRes = await post ( {
109- url : ' /multiaccess/memberbooth/label' ,
161+ url : " /multiaccess/memberbooth/label" ,
110162 data : data ,
111163 } ) ;
112164 const createdLabel = createRes . data as UploadedLabel ;
@@ -117,9 +169,10 @@ const BoxTerminator = () => {
117169 post ( {
118170 url : `/multiaccess/memberbooth/label/${ createdLabel . label . id } /observe` ,
119171 allowedErrorCodes : [ 404 ] ,
120- } ) , await get ( {
172+ } ) ,
173+ await get ( {
121174 url : `/multiaccess/memberbooth/label/${ createdLabel . label . id } /membership` ,
122- } )
175+ } ) ,
123176 ] ) ;
124177 label = observeRes2 . data as LabelActionResponse | null ;
125178 membership = membershipRes . data as membership_t ;
@@ -153,18 +206,23 @@ const BoxTerminator = () => {
153206 return cache_item ;
154207 } ;
155208
156- const fadeoutLabelItem = ( item : { label : LabelActionResponse , membership : membership_t , state : "active" | "fading-out" , timer : NodeJS . Timeout } ) => {
209+ const fadeoutLabelItem = ( item : {
210+ label : LabelActionResponse ;
211+ membership : membership_t ;
212+ state : "active" | "fading-out" ;
213+ timer : NodeJS . Timeout ;
214+ } ) => {
157215 if ( item . state === "fading-out" ) return ;
158216 item . state = "fading-out" ;
159217 window . clearTimeout ( item . timer ) ;
160218 item . timer = setTimeout ( ( ) => {
161- setLastScanResult ( current => current . filter ( i => i !== item ) ) ;
219+ setLastScanResult ( ( current ) => current . filter ( ( i ) => i !== item ) ) ;
162220 } , 1000 ) ;
163221 } ;
164222
165223 const fadeoutLabel = ( id : number ) => {
166- setLastScanResult ( prev => {
167- const item = prev . find ( v => v . label . id === id ) ;
224+ setLastScanResult ( ( prev ) => {
225+ const item = prev . find ( ( v ) => v . label . id === id ) ;
168226 if ( item ) {
169227 fadeoutLabelItem ( item ) ;
170228 }
@@ -180,29 +238,40 @@ const BoxTerminator = () => {
180238 }
181239 isScanning . current = true ;
182240 processScan ( scannedString )
183- . then ( ( label ) => {
184- // Timer logic for null label
185- if ( ! label ) return ;
186-
187- console . log ( "Finished scan" , label ) ;
188- setLastScanResult ( prev => {
189- // Remove any existing entry for this label
190- for ( const v of prev ) {
191- if ( v . label . id === label . label . id ) {
192- window . clearTimeout ( v . timer ) ;
241+ . then ( ( label ) => {
242+ // Timer logic for null label
243+ if ( ! label ) return ;
244+
245+ console . log ( "Finished scan" , label ) ;
246+ setLastScanResult ( ( prev ) => {
247+ // Remove any existing entry for this label
248+ for ( const v of prev ) {
249+ if ( v . label . id === label . label . id ) {
250+ window . clearTimeout ( v . timer ) ;
251+ }
193252 }
194- }
195- prev = prev . filter ( v => v . label . id !== label . label . id ) ;
253+ prev = prev . filter ( ( v ) => v . label . id !== label . label . id ) ;
196254
197- // Mark any existing entries for removal
198- for ( const v of prev ) {
199- fadeoutLabelItem ( v ) ;
200- }
201- return [ ...prev , { ...label , state : "active" , timer : setTimeout ( ( ) => fadeoutLabel ( label . label . id ) , 3000 ) } ] ;
255+ // Mark any existing entries for removal
256+ for ( const v of prev ) {
257+ fadeoutLabelItem ( v ) ;
258+ }
259+ return [
260+ ...prev ,
261+ {
262+ ...label ,
263+ state : "active" ,
264+ timer : setTimeout (
265+ ( ) => fadeoutLabel ( label . label . id ) ,
266+ 3000 ,
267+ ) ,
268+ } ,
269+ ] ;
270+ } ) ;
271+ } )
272+ . finally ( ( ) => {
273+ isScanning . current = false ;
202274 } ) ;
203- } ) . finally ( ( ) => {
204- isScanning . current = false ;
205- } ) ;
206275 } ;
207276
208277 // Effect to process pending scans
@@ -214,17 +283,18 @@ const BoxTerminator = () => {
214283 } , [ isScanning , pendingScan ] ) ;
215284
216285 return (
217- < div
218- className = "box-terminator"
219- >
220- < QrCodeScanner
221- onSuccess = { scanCallback }
222- />
223- { lastScanResult . map ( item => {
224- return < ScanResultPopover key = { item . label . id } labelAction = { item . label } membership = { item . membership } state = { item . state } />
225- } ) }
226-
227-
286+ < div className = "box-terminator" >
287+ < QrCodeScanner onSuccess = { scanCallback } />
288+ { lastScanResult . map ( ( item ) => {
289+ return (
290+ < ScanResultPopover
291+ key = { item . label . id }
292+ labelAction = { item . label }
293+ membership = { item . membership }
294+ state = { item . state }
295+ />
296+ ) ;
297+ } ) }
228298 </ div >
229299 ) ;
230300} ;
0 commit comments