1- import { faClone } from '@fortawesome/free-regular-svg-icons' ;
2- import { faCheck , faFileDownload as downloadIcon } from '@fortawesome/free-solid-svg-icons' ;
1+ import { faClone , faImage } from '@fortawesome/free-regular-svg-icons' ;
2+ import { faCheck , faFileDownload as downloadIcon , faXmark } from '@fortawesome/free-solid-svg-icons' ;
33import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' ;
44import { useTimeoutToggle } from '@shlinkio/shlink-frontend-kit' ;
5- import type { FC } from 'react' ;
5+ import type { ChangeEvent , FC } from 'react' ;
66import { useCallback , useRef , useState } from 'react' ;
77import { ExternalLink } from 'react-external-link' ;
88import { Button , Modal , ModalBody , ModalHeader } from 'reactstrap' ;
99import { ColorInput } from '../../utils/components/ColorInput' ;
1010import type { QrRef } from '../../utils/components/QrCode' ;
1111import { QrCode } from '../../utils/components/QrCode' ;
12- import { useFeature } from '../../utils/features' ;
1312import { copyToClipboard } from '../../utils/helpers/clipboard' ;
1413import type { QrCodeFormat , QrDrawType , QrErrorCorrection } from '../../utils/helpers/qrCodes' ;
1514import type { ShortUrlModalProps } from '../data' ;
@@ -32,8 +31,18 @@ export const QrCodeModal: FC<QrCodeModalProps> = (
3231 const [ color , setColor ] = useState ( '#000000' ) ;
3332 const [ bgColor , setBgColor ] = useState ( '#ffffff' ) ;
3433 const [ format , setFormat ] = useState < QrCodeFormat > ( 'png' ) ;
34+ const [ logo , setLogo ] = useState < { url : string ; name : string } > ( ) ;
3535
36- const qrCodeColorsSupported = useFeature ( 'qrCodeColors' ) ;
36+ const logoInputRef = useRef < HTMLInputElement > ( null ) ;
37+ const onSelectLogo = useCallback ( ( e : ChangeEvent < HTMLInputElement > ) => {
38+ const file = e . target . files ?. [ 0 ] ;
39+ if ( file ) {
40+ setLogo ( {
41+ url : URL . createObjectURL ( new Blob ( [ file ] , { type : file . type } ) ) ,
42+ name : file . name ,
43+ } ) ;
44+ }
45+ } , [ ] ) ;
3746
3847 const qrCodeRef = useRef < QrRef > ( null ) ;
3948 const downloadQrCode = useCallback (
@@ -46,14 +55,24 @@ export const QrCodeModal: FC<QrCodeModalProps> = (
4655 return copyToClipboard ( { text : uri , onCopy : toggleCopied } ) ;
4756 } , [ format , toggleCopied ] ) ;
4857
58+ const resetOptions = useCallback ( ( ) => {
59+ setSize ( 300 ) ;
60+ setMargin ( 0 ) ;
61+ setErrorCorrection ( 'L' ) ;
62+ setColor ( '#000000' ) ;
63+ setBgColor ( '#ffffff' ) ;
64+ setFormat ( 'png' ) ;
65+ setLogo ( undefined ) ;
66+ } , [ ] ) ;
67+
4968 return (
50- < Modal isOpen = { isOpen } toggle = { toggle } centered size = "lg" >
69+ < Modal isOpen = { isOpen } toggle = { toggle } centered size = "lg" onClosed = { resetOptions } >
5170 < ModalHeader toggle = { toggle } >
5271 QR code for < ExternalLink href = { shortUrl } > { shortUrl } </ ExternalLink >
5372 </ ModalHeader >
5473 < ModalBody className = "d-flex flex-column-reverse flex-lg-row gap-3" >
5574 < div className = "flex-grow-1 d-flex align-items-center justify-content-around qr-code-modal__qr-code" >
56- < div className = "d-flex flex-column gap-1" data-testid = "qr-code-container" >
75+ < div className = "d-flex flex-column gap-1 align-items-center " data-testid = "qr-code-container" >
5776 < QrCode
5877 ref = { qrCodeRef }
5978 data = { shortUrl }
@@ -62,6 +81,7 @@ export const QrCodeModal: FC<QrCodeModalProps> = (
6281 errorCorrection = { errorCorrection }
6382 color = { color }
6483 bgColor = { bgColor }
84+ logo = { logo ?. url }
6585 drawType = { qrDrawType }
6686 />
6787 < div className = "text-center fst-italic" > Preview ({ size + margin } x{ size + margin } )</ div >
@@ -85,13 +105,41 @@ export const QrCodeModal: FC<QrCodeModalProps> = (
85105 max = { 100 }
86106 />
87107 < QrErrorCorrectionDropdown errorCorrection = { errorCorrection } onChange = { setErrorCorrection } />
108+ < ColorInput name = "color" color = { color } onChange = { setColor } />
109+ < ColorInput name = "background" color = { bgColor } onChange = { setBgColor } />
88110
89- { qrCodeColorsSupported && (
111+ { ! logo && (
90112 < >
91- < ColorInput name = "color" color = { color } onChange = { setColor } />
92- < ColorInput name = "background" color = { bgColor } onChange = { setBgColor } />
113+ < Button
114+ outline
115+ className = "d-flex align-items-center gap-1"
116+ onClick = { ( ) => logoInputRef . current ?. click ( ) }
117+ >
118+ < FontAwesomeIcon icon = { faImage } />
119+ Select logo
120+ </ Button >
121+ < input
122+ ref = { logoInputRef }
123+ type = "file"
124+ accept = "image/*"
125+ aria-hidden
126+ tabIndex = { - 1 }
127+ className = "d-none"
128+ onChange = { onSelectLogo }
129+ data-testid = "logo-input"
130+ />
93131 </ >
94132 ) }
133+ { logo && (
134+ < Button
135+ outline
136+ className = "d-flex align-items-center gap-1"
137+ onClick = { ( ) => setLogo ( undefined ) }
138+ >
139+ < FontAwesomeIcon icon = { faXmark } />
140+ < div className = "text-truncate" > Clear logo ({ logo . name } )</ div >
141+ </ Button >
142+ ) }
95143
96144 < div className = "my-auto" >
97145 < hr className = "my-2" />
0 commit comments