| 
									
										
										
										
											2020-10-30 15:34:04 -05:00
										 |  |  | // Copyright 2020 Signal Messenger, LLC
 | 
					
						
							|  |  |  | // SPDX-License-Identifier: AGPL-3.0-only
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-19 19:08:36 +02:00
										 |  |  | import React, { useState, useCallback } from 'react'; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  | import classNames from 'classnames'; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-06 16:27:14 -04:00
										 |  |  | import { Button, ButtonVariant } from './Button'; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  | import { QrCode } from './QrCode'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { ConversationType } from '../state/ducks/conversations'; | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  | import { Intl } from './Intl'; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  | import { Emojify } from './conversation/Emojify'; | 
					
						
							| 
									
										
										
										
											2021-10-26 14:15:33 -05:00
										 |  |  | import type { LocalizerType } from '../types/Util'; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  | import type { SafetyNumberType } from '../types/safetyNumber'; | 
					
						
							|  |  |  | import { SAFETY_NUMBER_MIGRATION_URL } from '../types/support'; | 
					
						
							|  |  |  | import { | 
					
						
							|  |  |  |   SafetyNumberIdentifierType, | 
					
						
							|  |  |  |   SafetyNumberMode, | 
					
						
							|  |  |  | } from '../types/safetyNumber'; | 
					
						
							| 
									
										
										
										
											2023-09-19 19:08:36 +02:00
										 |  |  | import { arrow } from '../util/keyboard'; | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-10-06 17:49:22 -07:00
										 |  |  | export type PropsType = { | 
					
						
							| 
									
										
										
										
											2022-03-02 13:24:28 -05:00
										 |  |  |   contact: ConversationType; | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   generateSafetyNumber: (contact: ConversationType) => void; | 
					
						
							|  |  |  |   i18n: LocalizerType; | 
					
						
							| 
									
										
										
										
											2022-03-02 13:24:28 -05:00
										 |  |  |   onClose: () => void; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   safetyNumberMode: SafetyNumberMode; | 
					
						
							|  |  |  |   safetyNumbers?: ReadonlyArray<SafetyNumberType>; | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   toggleVerified: (contact: ConversationType) => void; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   showOnboarding?: () => void; | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   verificationDisabled: boolean; | 
					
						
							|  |  |  | }; | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-17 16:45:19 -08:00
										 |  |  | export function SafetyNumberViewer({ | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   contact, | 
					
						
							|  |  |  |   generateSafetyNumber, | 
					
						
							|  |  |  |   i18n, | 
					
						
							|  |  |  |   onClose, | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   safetyNumberMode, | 
					
						
							|  |  |  |   safetyNumbers, | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   toggleVerified, | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   showOnboarding, | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   verificationDisabled, | 
					
						
							| 
									
										
										
										
											2022-11-17 16:45:19 -08:00
										 |  |  | }: PropsType): JSX.Element | null { | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   const hasSafetyNumbers = safetyNumbers != null; | 
					
						
							| 
									
										
										
										
											2020-09-11 17:46:52 -07:00
										 |  |  |   React.useEffect(() => { | 
					
						
							|  |  |  |     if (!contact) { | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     generateSafetyNumber(contact); | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   }, [contact, generateSafetyNumber]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-19 19:08:36 +02:00
										 |  |  |   // Keyboard navigation
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   const [selectedIndex, setSelectedIndex] = useState(0); | 
					
						
							| 
									
										
										
										
											2020-09-11 17:46:52 -07:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-19 19:08:36 +02:00
										 |  |  |   const selectPrevNumber = useCallback(() => { | 
					
						
							|  |  |  |     setSelectedIndex(x => Math.max(x - 1, 0)); | 
					
						
							|  |  |  |   }, []); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const selectNextNumber = useCallback(() => { | 
					
						
							|  |  |  |     if (!safetyNumbers || safetyNumbers.length === 0) { | 
					
						
							|  |  |  |       setSelectedIndex(0); | 
					
						
							|  |  |  |       return; | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     setSelectedIndex(x => Math.min(x + 1, safetyNumbers.length - 1)); | 
					
						
							|  |  |  |   }, [safetyNumbers]); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   React.useEffect(() => { | 
					
						
							|  |  |  |     const handleKeyDown = (ev: KeyboardEvent) => { | 
					
						
							|  |  |  |       if (ev.key === arrow('start')) { | 
					
						
							|  |  |  |         selectPrevNumber(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       if (ev.key === arrow('end')) { | 
					
						
							|  |  |  |         selectNextNumber(); | 
					
						
							|  |  |  |       } | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |     document.addEventListener('keydown', handleKeyDown); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     return () => { | 
					
						
							|  |  |  |       document.removeEventListener('keydown', handleKeyDown); | 
					
						
							|  |  |  |     }; | 
					
						
							|  |  |  |   }, [selectPrevNumber, selectNextNumber]); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   if (!contact || !hasSafetyNumbers) { | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |     return null; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |   if (!safetyNumbers.length) { | 
					
						
							| 
									
										
										
										
											2020-10-06 17:49:22 -07:00
										 |  |  |     return ( | 
					
						
							| 
									
										
										
										
											2021-04-27 12:29:59 -07:00
										 |  |  |       <div className="module-SafetyNumberViewer"> | 
					
						
							| 
									
										
										
										
											2023-03-29 17:03:25 -07:00
										 |  |  |         <div>{i18n('icu:cannotGenerateSafetyNumber')}</div> | 
					
						
							| 
									
										
										
										
											2022-03-02 13:24:28 -05:00
										 |  |  |         <div className="module-SafetyNumberViewer__buttons"> | 
					
						
							|  |  |  |           <Button | 
					
						
							|  |  |  |             className="module-SafetyNumberViewer__button" | 
					
						
							|  |  |  |             onClick={() => onClose?.()} | 
					
						
							|  |  |  |             variant={ButtonVariant.Primary} | 
					
						
							|  |  |  |           > | 
					
						
							| 
									
										
										
										
											2023-03-29 17:03:25 -07:00
										 |  |  |             {i18n('icu:ok')} | 
					
						
							| 
									
										
										
										
											2022-03-02 13:24:28 -05:00
										 |  |  |           </Button> | 
					
						
							|  |  |  |         </div> | 
					
						
							| 
									
										
										
										
											2020-10-06 17:49:22 -07:00
										 |  |  |       </div> | 
					
						
							|  |  |  |     ); | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-07-29 16:20:05 -07:00
										 |  |  |   const boldName = ( | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |     <span className="module-SafetyNumberViewer__bold-name"> | 
					
						
							|  |  |  |       <Emojify text={contact.title} /> | 
					
						
							|  |  |  |     </span> | 
					
						
							| 
									
										
										
										
											2020-07-23 18:35:32 -07:00
										 |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-09-11 17:46:52 -07:00
										 |  |  |   const { isVerified } = contact; | 
					
						
							| 
									
										
										
										
											2023-02-03 18:32:17 -08:00
										 |  |  |   const verifyButtonText = isVerified | 
					
						
							|  |  |  |     ? i18n('icu:SafetyNumberViewer__clearVerification') | 
					
						
							|  |  |  |     : i18n('icu:SafetyNumberViewer__markAsVerified'); | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-07-19 01:40:14 +02:00
										 |  |  |   const isMigrationVisible = safetyNumberMode !== SafetyNumberMode.JustE164; | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  |   const visibleSafetyNumber = safetyNumbers.at(selectedIndex); | 
					
						
							|  |  |  |   if (!visibleSafetyNumber) { | 
					
						
							|  |  |  |     return null; | 
					
						
							|  |  |  |   } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const cardClassName = classNames('module-SafetyNumberViewer__card', { | 
					
						
							|  |  |  |     'module-SafetyNumberViewer__card--aci': | 
					
						
							|  |  |  |       visibleSafetyNumber.identifierType === | 
					
						
							|  |  |  |       SafetyNumberIdentifierType.ACIIdentifier, | 
					
						
							|  |  |  |     'module-SafetyNumberViewer__card--e164': | 
					
						
							|  |  |  |       visibleSafetyNumber.identifierType === | 
					
						
							|  |  |  |       SafetyNumberIdentifierType.E164Identifier, | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const numberBlocks = visibleSafetyNumber.numberBlocks.join(' '); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const safetyNumberCard = ( | 
					
						
							|  |  |  |     <div className="module-SafetyNumberViewer__card-container"> | 
					
						
							|  |  |  |       <div className={cardClassName}> | 
					
						
							|  |  |  |         <QrCode | 
					
						
							|  |  |  |           className="module-SafetyNumberViewer__card__qr" | 
					
						
							|  |  |  |           data={visibleSafetyNumber.qrData} | 
					
						
							|  |  |  |           alt={i18n('icu:Install__scan-this-code')} | 
					
						
							|  |  |  |         /> | 
					
						
							|  |  |  |         <div className="module-SafetyNumberViewer__card__number"> | 
					
						
							|  |  |  |           {numberBlocks} | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         {selectedIndex > 0 && ( | 
					
						
							|  |  |  |           <button | 
					
						
							|  |  |  |             type="button" | 
					
						
							|  |  |  |             aria-label={i18n('icu:SafetyNumberViewer__card__prev')} | 
					
						
							|  |  |  |             className="module-SafetyNumberViewer__card__prev" | 
					
						
							| 
									
										
										
										
											2023-09-19 19:08:36 +02:00
										 |  |  |             onClick={selectPrevNumber} | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |           /> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         {selectedIndex < safetyNumbers.length - 1 && ( | 
					
						
							|  |  |  |           <button | 
					
						
							|  |  |  |             type="button" | 
					
						
							|  |  |  |             aria-label={i18n('icu:SafetyNumberViewer__card__next')} | 
					
						
							|  |  |  |             className="module-SafetyNumberViewer__card__next" | 
					
						
							| 
									
										
										
										
											2023-09-19 19:08:36 +02:00
										 |  |  |             onClick={selectNextNumber} | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |           /> | 
					
						
							|  |  |  |         )} | 
					
						
							|  |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   const carousel = ( | 
					
						
							|  |  |  |     <div className="module-SafetyNumberViewer__carousel"> | 
					
						
							|  |  |  |       {safetyNumbers.map(({ identifierType }, index) => { | 
					
						
							|  |  |  |         return ( | 
					
						
							|  |  |  |           <button | 
					
						
							|  |  |  |             type="button" | 
					
						
							|  |  |  |             aria-label={i18n('icu:SafetyNumberViewer__carousel__dot', { | 
					
						
							|  |  |  |               index: index + 1, | 
					
						
							|  |  |  |               total: safetyNumbers.length, | 
					
						
							|  |  |  |             })} | 
					
						
							|  |  |  |             aria-pressed={index === selectedIndex} | 
					
						
							|  |  |  |             key={identifierType} | 
					
						
							|  |  |  |             className="module-SafetyNumberViewer__carousel__dot" | 
					
						
							|  |  |  |             onClick={() => setSelectedIndex(index)} | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         ); | 
					
						
							|  |  |  |       })} | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |   return ( | 
					
						
							| 
									
										
										
										
											2021-04-27 12:29:59 -07:00
										 |  |  |     <div className="module-SafetyNumberViewer"> | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |       {isMigrationVisible && ( | 
					
						
							|  |  |  |         <div className="module-SafetyNumberViewer__migration"> | 
					
						
							|  |  |  |           <div className="module-SafetyNumberViewer__migration__icon" /> | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |           <div className="module-SafetyNumberViewer__migration__text"> | 
					
						
							|  |  |  |             <p> | 
					
						
							|  |  |  |               <Intl i18n={i18n} id="icu:SafetyNumberViewer__migration__text" /> | 
					
						
							|  |  |  |             </p> | 
					
						
							|  |  |  |             <p> | 
					
						
							|  |  |  |               <a | 
					
						
							|  |  |  |                 href={SAFETY_NUMBER_MIGRATION_URL} | 
					
						
							|  |  |  |                 rel="noreferrer" | 
					
						
							|  |  |  |                 target="_blank" | 
					
						
							|  |  |  |                 onClick={e => { | 
					
						
							|  |  |  |                   if (showOnboarding) { | 
					
						
							|  |  |  |                     e.preventDefault(); | 
					
						
							|  |  |  |                     showOnboarding(); | 
					
						
							|  |  |  |                   } | 
					
						
							|  |  |  |                 }} | 
					
						
							|  |  |  |               > | 
					
						
							|  |  |  |                 <Intl | 
					
						
							|  |  |  |                   i18n={i18n} | 
					
						
							|  |  |  |                   id="icu:SafetyNumberViewer__migration__learn_more" | 
					
						
							|  |  |  |                 /> | 
					
						
							|  |  |  |               </a> | 
					
						
							|  |  |  |             </p> | 
					
						
							|  |  |  |           </div> | 
					
						
							|  |  |  |         </div> | 
					
						
							|  |  |  |       )} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       {safetyNumberCard} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       {safetyNumbers.length > 1 && carousel} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       <div className="module-SafetyNumberViewer__help"> | 
					
						
							|  |  |  |         {isMigrationVisible ? ( | 
					
						
							| 
									
										
										
										
											2023-03-29 17:03:25 -07:00
										 |  |  |           <Intl | 
					
						
							|  |  |  |             i18n={i18n} | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |             id="icu:SafetyNumberViewer__hint--migration" | 
					
						
							| 
									
										
										
										
											2023-03-29 17:03:25 -07:00
										 |  |  |             components={{ name: boldName }} | 
					
						
							|  |  |  |           /> | 
					
						
							| 
									
										
										
										
											2023-01-05 14:43:33 -08:00
										 |  |  |         ) : ( | 
					
						
							| 
									
										
										
										
											2023-03-27 16:37:39 -07:00
										 |  |  |           <Intl | 
					
						
							|  |  |  |             i18n={i18n} | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |             id="icu:SafetyNumberViewer__hint--normal" | 
					
						
							| 
									
										
										
										
											2023-03-27 16:37:39 -07:00
										 |  |  |             components={{ name: boldName }} | 
					
						
							|  |  |  |           /> | 
					
						
							| 
									
										
										
										
											2023-01-05 14:43:33 -08:00
										 |  |  |         )} | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  |         <br /> | 
					
						
							|  |  |  |         <a href={SAFETY_NUMBER_MIGRATION_URL} rel="noreferrer" target="_blank"> | 
					
						
							|  |  |  |           <Intl | 
					
						
							|  |  |  |             i18n={i18n} | 
					
						
							|  |  |  |             id="icu:SafetyNumberViewer__migration__learn_more" | 
					
						
							|  |  |  |           /> | 
					
						
							|  |  |  |         </a> | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |       </div> | 
					
						
							| 
									
										
										
										
											2023-07-13 21:06:42 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-06 16:27:14 -04:00
										 |  |  |       <div className="module-SafetyNumberViewer__button"> | 
					
						
							|  |  |  |         <Button | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |           disabled={verificationDisabled} | 
					
						
							|  |  |  |           onClick={() => { | 
					
						
							|  |  |  |             toggleVerified(contact); | 
					
						
							|  |  |  |           }} | 
					
						
							| 
									
										
										
										
											2021-10-06 16:27:14 -04:00
										 |  |  |           variant={ButtonVariant.Secondary} | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |         > | 
					
						
							|  |  |  |           {verifyButtonText} | 
					
						
							| 
									
										
										
										
											2021-10-06 16:27:14 -04:00
										 |  |  |         </Button> | 
					
						
							| 
									
										
										
										
											2020-06-25 20:08:58 -04:00
										 |  |  |       </div> | 
					
						
							|  |  |  |     </div> | 
					
						
							|  |  |  |   ); | 
					
						
							| 
									
										
										
										
											2022-11-17 16:45:19 -08:00
										 |  |  | } |