@@ -18,6 +18,11 @@ import {
1818
1919const delay = ( ms : number ) => new Promise ( ( res ) => setTimeout ( res , ms ) ) ;
2020
21+ const normalizePhone = ( value : string ) => {
22+ const digits = value . replace ( / \D / g, "" ) ;
23+ return digits . length === 10 ? digits : "" ;
24+ } ;
25+
2126/* -------------------------------------------------------------------------- */
2227/* Default avatars */
2328/* -------------------------------------------------------------------------- */
@@ -155,6 +160,35 @@ let USERS_DB: Record<string, UserProfile> = {
155160 } ,
156161} ;
157162
163+ type UserLookupIndex = {
164+ byEmail : Map < string , UserProfile > ;
165+ byPhone : Map < string , UserProfile > ;
166+ byUsername : Map < string , UserProfile > ;
167+ } ;
168+
169+ const buildUserLookupIndex = ( ) : UserLookupIndex => {
170+ const byEmail = new Map < string , UserProfile > ( ) ;
171+ const byPhone = new Map < string , UserProfile > ( ) ;
172+ const byUsername = new Map < string , UserProfile > ( ) ;
173+
174+ for ( const user of Object . values ( USERS_DB ) ) {
175+ if ( user . email ) byEmail . set ( user . email , user ) ;
176+
177+ const normalizedPhone = normalizePhone ( user . phone || "" ) ;
178+ if ( normalizedPhone ) byPhone . set ( normalizedPhone , user ) ;
179+
180+ if ( user . username ) byUsername . set ( user . username , user ) ;
181+ }
182+
183+ return { byEmail, byPhone, byUsername } ;
184+ } ;
185+
186+ let USER_LOOKUP_INDEX = buildUserLookupIndex ( ) ;
187+
188+ const refreshUserLookupIndex = ( ) => {
189+ USER_LOOKUP_INDEX = buildUserLookupIndex ( ) ;
190+ } ;
191+
158192// Initialize spot for 26/07/2025
159193const SPOT_DATE = "2025-07-26T10:00:00" ;
160194const SPOT_LOCATION = "Attibele Toll Plaza" ;
@@ -242,6 +276,8 @@ let INVITATIONS: Invitation[] = [
242276 } ,
243277] ;
244278
279+ let INVITATION_BY_ID = new Map ( INVITATIONS . map ( ( invitation ) => [ invitation . id , invitation ] ) ) ;
280+
245281// Initialize payments with real payment statuses
246282let PAYMENTS : Payment [ ] = [
247283 {
@@ -302,6 +338,8 @@ let PAYMENTS: Payment[] = [
302338 } ,
303339] ;
304340
341+ let PAYMENT_BY_ID = new Map ( PAYMENTS . map ( ( payment ) => [ payment . id , payment ] ) ) ;
342+
305343let DRINKS : Drink [ ] = [ ] ;
306344let MESSAGES : ChatMessage [ ] = [ ] ;
307345let MOMENTS : Moment [ ] = [ ] ;
@@ -320,19 +358,19 @@ export const mockApi = {
320358 ) : Promise < { user : User ; profile : UserProfile } > {
321359 await delay ( 300 ) ;
322360
323- // Strip formatting from phone number for comparison
324- const cleanIdentifier = identifier . replace ( / \D / g , '' ) . length === 10 ? identifier . replace ( / \D / g , '' ) : identifier ;
325-
326- const profile = Object . values ( USERS_DB ) . find (
327- ( u ) =>
328- ( u . email === identifier ||
329- u . phone === cleanIdentifier ||
330- u . username === identifier ) &&
331- u . password === password &&
332- ( ! orgCode ?. trim ( ) || u . org_code = == orgCode . trim ( ) )
333- ) ;
334-
335- if ( ! profile ) throw new Error ( "Invalid credentials" ) ;
361+ const cleanIdentifier = normalizePhone ( identifier ) ;
362+ const profile =
363+ USER_LOOKUP_INDEX . byEmail . get ( identifier ) ??
364+ ( cleanIdentifier ? USER_LOOKUP_INDEX . byPhone . get ( cleanIdentifier ) : undefined ) ??
365+ USER_LOOKUP_INDEX . byUsername . get ( identifier ) ;
366+
367+ if (
368+ ! profile ||
369+ profile . password !== password ||
370+ ( orgCode ?. trim ( ) && profile . org_code ! == orgCode . trim ( ) )
371+ ) {
372+ throw new Error ( "Invalid credentials" ) ;
373+ }
336374
337375 const user : User = {
338376 id : profile . id ,
@@ -357,6 +395,7 @@ export const mockApi = {
357395 ) : Promise < UserProfile > {
358396 await delay ( 200 ) ;
359397 USERS_DB [ userId ] = { ...USERS_DB [ userId ] , ...updates } ;
398+ refreshUserLookupIndex ( ) ;
360399 return USERS_DB [ userId ] ;
361400 } ,
362401
@@ -370,7 +409,8 @@ export const mockApi = {
370409 } ) : Promise < UserProfile > {
371410 await delay ( 300 ) ;
372411
373- if ( Object . values ( USERS_DB ) . some ( ( u ) => u . phone === data . phone ) ) {
412+ const normalizedPhone = normalizePhone ( data . phone ) ;
413+ if ( normalizedPhone && USER_LOOKUP_INDEX . byPhone . has ( normalizedPhone ) ) {
374414 throw new Error ( "User already exists" ) ;
375415 }
376416
@@ -390,6 +430,7 @@ export const mockApi = {
390430 } ;
391431
392432 USERS_DB [ id ] = newUser ;
433+ refreshUserLookupIndex ( ) ;
393434 return newUser ;
394435 } ,
395436
@@ -425,35 +466,45 @@ export const mockApi = {
425466 if ( creator && newSpot . members ) {
426467 newSpot . members . push ( creator ) ;
427468
428- INVITATIONS . push ( {
469+ const creatorInvitation : Invitation = {
429470 id : `inv-${ Date . now ( ) } ` ,
430471 spot_id : newSpot . id ,
431472 user_id : creator . id ,
432473 profiles : creator ,
433474 status : InvitationStatus . CONFIRMED ,
434- } ) ;
475+ } ;
476+ INVITATIONS . push ( creatorInvitation ) ;
477+ INVITATION_BY_ID . set ( creatorInvitation . id , creatorInvitation ) ;
435478
436- PAYMENTS . push ( {
479+ const creatorPayment : Payment = {
437480 id : `pay-${ Date . now ( ) } ` ,
438481 spot_id : newSpot . id ,
439482 user_id : creator . id ,
440483 profiles : creator ,
441484 status : PaymentStatus . NOT_PAID ,
442- } ) ;
485+ } ;
486+ PAYMENTS . push ( creatorPayment ) ;
487+ PAYMENT_BY_ID . set ( creatorPayment . id , creatorPayment ) ;
443488 }
444489
445490 return newSpot ;
446491 } ,
447492
448493 async getUpcomingSpot ( ) : Promise < Spot | null > {
449494 await delay ( 200 ) ;
450- return (
451- SPOTS . filter ( ( s ) => new Date ( s . date ) >= new Date ( ) )
452- . sort (
453- ( a , b ) =>
454- new Date ( a . date ) . getTime ( ) - new Date ( b . date ) . getTime ( )
455- ) [ 0 ] ?? null
456- ) ;
495+ const now = Date . now ( ) ;
496+ let nextSpot : Spot | null = null ;
497+ let nextSpotTime = Number . POSITIVE_INFINITY ;
498+
499+ for ( const spot of SPOTS ) {
500+ const spotTime = new Date ( spot . date ) . getTime ( ) ;
501+ if ( spotTime >= now && spotTime < nextSpotTime ) {
502+ nextSpot = spot ;
503+ nextSpotTime = spotTime ;
504+ }
505+ }
506+
507+ return nextSpot ;
457508 } ,
458509
459510 async getPastSpots ( ) : Promise < Spot [ ] > {
@@ -473,7 +524,7 @@ export const mockApi = {
473524 status : InvitationStatus
474525 ) : Promise < void > {
475526 await delay ( 200 ) ;
476- const inv = INVITATIONS . find ( ( i ) => i . id === invitationId ) ;
527+ const inv = INVITATION_BY_ID . get ( invitationId ) ;
477528 if ( ! inv ) throw new Error ( "Invitation not found" ) ;
478529 inv . status = status ;
479530 } ,
@@ -490,7 +541,7 @@ export const mockApi = {
490541 status : PaymentStatus
491542 ) : Promise < void > {
492543 await delay ( 200 ) ;
493- const payment = PAYMENTS . find ( ( p ) => p . id === paymentId ) ;
544+ const payment = PAYMENT_BY_ID . get ( paymentId ) ;
494545 if ( ! payment ) throw new Error ( "Payment not found" ) ;
495546 payment . status = status ;
496547 } ,
@@ -566,15 +617,15 @@ export const mockApi = {
566617
567618 async sendOtp ( email : string ) : Promise < void > {
568619 await delay ( 300 ) ;
569- const user = Object . values ( USERS_DB ) . find ( ( u ) => u . email === email ) ;
620+ const user = USER_LOOKUP_INDEX . byEmail . get ( email ) ;
570621 if ( ! user ) throw new Error ( "User not found" ) ;
571622 // In a real implementation, this would send an OTP
572623 // For now, we'll just simulate success
573624 } ,
574625
575626 async resetPassword ( email : string , newPassword : string ) : Promise < void > {
576627 await delay ( 300 ) ;
577- const user = Object . values ( USERS_DB ) . find ( ( u ) => u . email === email ) ;
628+ const user = USER_LOOKUP_INDEX . byEmail . get ( email ) ;
578629 if ( ! user ) throw new Error ( "User not found" ) ;
579630 user . password = newPassword ;
580631 } ,
0 commit comments