diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/ErrorPopup/ErrorPopup.tsx b/components/brave_rewards/resources/page/ping_doc_signer/components/ErrorPopup/ErrorPopup.tsx new file mode 100644 index 000000000000..d5b2cebd505c --- /dev/null +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/ErrorPopup/ErrorPopup.tsx @@ -0,0 +1,24 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ +import * as React from 'react'; +import { + StyledErrorPopup, + StyledErrorTitle, + StyledErrorMessage, + StyledErrorButtons, + StyledConfirmButton +} from './styles'; +import { ErrorPopupProps } from '../../utils/types'; + +export const ErrorPopup: React.FC = ({ message, onContinue }) => ( + + + Action Incomplete! + + {message} + + Continue + + +); diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/ErrorPopup/styles.ts b/components/brave_rewards/resources/page/ping_doc_signer/components/ErrorPopup/styles.ts new file mode 100644 index 000000000000..e18d837c4632 --- /dev/null +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/ErrorPopup/styles.ts @@ -0,0 +1,79 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + import styled from "styled-components"; + + export const StyledErrorPopup = styled('div')` + position: fixed; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + background-color: #323639; + border-radius: 24px; + padding: 20px 45px; + width: 595px; + display: flex; + flex-direction: column; + gap: 10px; + height: fit-content; + border: 1px solid #CACACA; + transition: height 0.3s ease; + overflow: hidden; + z-index: 1000; + ` + + export const StyledErrorTitle = styled('h2')<{ verification?: boolean }>` + color: Red; + font-family: Poppins; + font-size: ${p => p.verification ? '40px' : '45px'}; + margin: 12px 0 0 0; + padding: 0; + font-style: normal; + font-weight: 400; + line-height: normal; + ` + + export const StyledErrorMessage = styled('p')` + color: #FFF; + font-family: Poppins; + font-size: 20px; + font-style: normal; + font-weight: 400; + line-height: normal; + margin-top: -5px; + ` + + export const StyledErrorName = styled('p')` + color: #FFF; + font-family: Poppins; + font-size: 25px; + font-style: normal; + margin: -10px 0 10px 0; + font-weight: 700; + line-height: normal; + ` + + export const StyledErrorButtons = styled('div')` + display: flex; + margin-top: 45px; + justify-content: center; + gap: 28px; + margin-left: 350px; + ` + + export const StyledConfirmButton = styled('button')<{ $continue?: boolean }>` + display: flex; + padding: 15px 35px; + align-items: baseline; + gap: 10px; + background-color: ${p => p.$continue ? '#2BB563' : 'white'}; + color: ${p => p.$continue ? 'white' : 'black'}; + border: none; + border-radius: 40px; + font-size: 16px; + cursor: pointer; + &:hover { + background-color: ${p => p.$continue ? '#2BB563' : '#2BB563'}; + color: white; + } + ` \ No newline at end of file diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/Header/Header.tsx b/components/brave_rewards/resources/page/ping_doc_signer/components/Header/Header.tsx index 9cb551f31469..a77fccab63f2 100644 --- a/components/brave_rewards/resources/page/ping_doc_signer/components/Header/Header.tsx +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/Header/Header.tsx @@ -33,6 +33,7 @@ import uploadLogo from '../../../assets/upload.svg'; import uploadHoverLogo from '../../../assets/uploadHover.svg'; import { AnimatedStatus } from '../AnimatedStatus/AnimatedStatus'; import { HeaderProps } from '../../utils/types'; +import { Tooltip } from '../ToolTip/ToolTip'; export const Header: React.FC = ({ pdfFileName, @@ -57,8 +58,13 @@ export const Header: React.FC = ({ statusType, handleLogoClick, fileInputRef, + isSigned, }) => { const [logoSrc, setLogoSrc] = useState(uploadLogo); + const [showPDFNameTooltip, setShowPDFNameTooltip] = useState(false); + const [showVerificationTooltip, setShowVerificationTooltip] = useState(false); + const [showHelpTooltip, setShowHelpTooltip] = useState(false); + const renderPageNumber = () => ( isEditingPageNumber ? (
@@ -92,13 +98,21 @@ export const Header: React.FC = ({ Add signature - - Verify document - + setShowVerificationTooltip(true)} + onMouseLeave={() => setShowVerificationTooltip(false)} + > + Verify document + + @@ -111,39 +125,60 @@ export const Header: React.FC = ({ - setLogoSrc(uploadHoverLogo)} - onMouseLeave={() => setLogoSrc(uploadLogo)} - /> - - {pdfFileName} + setLogoSrc(uploadHoverLogo)} + onMouseLeave={() => setLogoSrc(uploadLogo)} + /> + + + setShowPDFNameTooltip(true)} + onMouseLeave={() => setShowPDFNameTooltip(false)} + > + {pdfFileName} + + - - {renderHeaderControls()} - - < - - {renderPageNumber()} - - > - - - Save + + {renderHeaderControls()} + + < + + {renderPageNumber()} + + > + + + Save - - ? - + + setShowHelpTooltip(true)} + onMouseLeave={() => setShowHelpTooltip(false)} + > + ? + + - + ); }; \ No newline at end of file diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/Header/styles.ts b/components/brave_rewards/resources/page/ping_doc_signer/components/Header/styles.ts index 2321ceea94df..a5b84ff8b4aa 100644 --- a/components/brave_rewards/resources/page/ping_doc_signer/components/Header/styles.ts +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/Header/styles.ts @@ -5,6 +5,7 @@ interface StyledDisabledProps { pdfFile?: Blob | null; + isSigned?: boolean; } const breakpoints = { @@ -205,6 +206,7 @@ export const StyledNavBar = styled('nav')` margin-right: 12px; cursor: pointer; margin-left: 20px; + visibility: ${({ isSigned }) => (isSigned ? 'visible' : 'hidden')}; `; export const StyledHelpButton = styled('button')` diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/InputPopup/InputPopup.tsx b/components/brave_rewards/resources/page/ping_doc_signer/components/InputPopup/InputPopup.tsx new file mode 100644 index 000000000000..6ca6981ec05a --- /dev/null +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/InputPopup/InputPopup.tsx @@ -0,0 +1,54 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ +import { useState } from 'react'; +import * as React from 'react'; +import { PopupContainer, PopupContent, Title, InputField, ButtonContainer, BackButton, CompleteButton, InputWrapper, EyeButton } from './styles'; + +interface InputPopupProps { + userName: string; + onBack: () => void; + onComplete: (pin: string) => void; +} + +const InputPopup: React.FC = ({ userName, onBack, onComplete }) => { + const [pin, setPin] = useState(''); + const [showPassword, setShowPassword] = useState(false); + + const handlePinChange = (event: React.ChangeEvent) => { + setPin(event.target.value); + }; + + const handleComplete = () => { + onComplete(pin); + }; + + const togglePasswordVisibility = () => { + setShowPassword(!showPassword); + }; + + return ( + + + {userName} + + + + {showPassword ? "👁️" : "👁️‍🗨️"} + + + + Back + Complete signature + + + + ); +}; + +export default InputPopup; \ No newline at end of file diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/InputPopup/styles.ts b/components/brave_rewards/resources/page/ping_doc_signer/components/InputPopup/styles.ts new file mode 100644 index 000000000000..e72a2a143fd5 --- /dev/null +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/InputPopup/styles.ts @@ -0,0 +1,100 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this file, + * You can obtain one at https://mozilla.org/MPL/2.0/. */ + import styled from 'styled-components'; + + export const InputWrapper = styled.div` + position: relative; + width: 100%; +`; + +export const EyeButton = styled.button` + position: absolute; + right: 10px; + top: 50%; + transform: translateY(-82%); + background: none; + border: none; + cursor: pointer; + font-size: 20px; +`; + +export const InputField = styled.input` + width: 100%; + padding: 10px; + padding-right: 40px; // Make room for the eye button + margin-bottom: 15px; + background-color: #3a3a3a; + border: none; + border-radius: 4px; + color: white; +`; + + +export const PopupContainer = styled.div` + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); + display: flex; + justify-content: center; + align-items: center; +`; + +export const PopupContent = styled.div` + background-color: #2c2c2c; + padding: 40px; + border-radius: 24px; + width: 550px; + display: flex; + flex-direction: column; + gap: 15px; + height: fit-content; +`; + +export const Title = styled.h2` + color: white; + font-size: 25px; + margin-bottom: 12px; + margin-top: -2px; +`; + +export const ButtonContainer = styled.div` + display: flex; + justify-content: space-between; +`; + +export const Button = styled.button` + padding: 10px 15px; + border: none; + border-radius: 4px; + cursor: pointer; +`; + +export const BackButton = styled(Button)` + background-color: transparent; + color: white; + display: flex; + padding: 15px 30px; + justify-content: center; + align-items: baseline; + gap: 10px; + border-radius: 58px; + border: 1px solid #FFF; +`; + +export const CompleteButton = styled(Button)` + background-color: white; + color: black; + width: 208px; + padding: 15px 30px; + display: flex; + justify-content: center; + border-radius: 58px; + align-items: baseline; + font-size: 15px; + gap: 10px; +} +`; \ No newline at end of file diff --git a/components/brave_rewards/resources/page/ping_doc_signer/components/ToolTip/styles.ts b/components/brave_rewards/resources/page/ping_doc_signer/components/ToolTip/styles.ts index bbb09b1a4202..1c390cbcc45f 100644 --- a/components/brave_rewards/resources/page/ping_doc_signer/components/ToolTip/styles.ts +++ b/components/brave_rewards/resources/page/ping_doc_signer/components/ToolTip/styles.ts @@ -10,7 +10,7 @@ export const TooltipContainer = styled.div` export const StyledTooltip = styled.div<{ isError?: boolean }>` position: absolute; - top: 100%; + top: 110%; left: 50%; transform: translateX(-50%); background-color: ${({ isError }) => (isError ? '#e74c3c' : '#333')}; diff --git a/components/brave_rewards/resources/page/ping_doc_signer/pdf_renderer.tsx b/components/brave_rewards/resources/page/ping_doc_signer/pdf_renderer.tsx index 66c59b8cf4fd..57bb53c53c34 100644 --- a/components/brave_rewards/resources/page/ping_doc_signer/pdf_renderer.tsx +++ b/components/brave_rewards/resources/page/ping_doc_signer/pdf_renderer.tsx @@ -8,8 +8,7 @@ import 'react-pdf/dist/esm/Page/AnnotationLayer.css'; import { verifyPdf } from './utils/pdf_verify'; import { signPdf } from './utils/pdf_signer'; import { Signature } from './utils/types'; - -// Import existing components +import { ErrorPopup } from './components/ErrorPopup/ErrorPopup'; import { Header } from './components/Header/Header'; import { DropZone } from './components/DropZone/DropZone'; import PdfPage from './components/PdfPage/PdfPage'; @@ -17,6 +16,7 @@ import { SignatureMethodPopup } from './components/SignatureMethodPopup/Signatur import { SignaturePopup } from './components/SignaturePopup/SignaturePopup'; import { SignatureTypePopup } from './components/SignatureTypePopup/SignatureTypePopup'; import { SuccessPopup } from './components/SuccessPopup/SuccessPopup'; +import InputPopup from './components/InputPopup/InputPopup'; import * as S from './styles'; @@ -58,12 +58,15 @@ export const PdfRenderer: React.FC = () => { const [isStatusVisible, setIsStatusVisible] = useState(false); const [showTypeSignaturePopup, setShowTypeSignaturePopup] = useState(false); const [statusType, setStatusType] = useState<'checking' | 'success' | 'error'>('checking'); - + const [showError, setShowError] = useState(false); + const [errorMessage, setErrorMessage] = useState(''); + const [showPinPopup, setShowPinPopup] = useState(false); const overlayCanvasRefs = useRef>([]); const pdfCanvasRefs = useRef>([]); const pdfContainerRef = useRef(null); const pageRefs = useRef>([]); const fileInputRef = useRef(null); + const [isSigned, setIsSigned] = useState(false); const resetSignatureState = useCallback(() => { setIsSelectionEnabled(false); @@ -160,6 +163,16 @@ export const PdfRenderer: React.FC = () => { } }; + const handleErrorContinue = () => { + setShowError(false); + setIsSelectionEnabled(false); + setStatusMessage('Error Siging PDF'); + setTimeout(() => { + setIsStatusVisible(false); + setStatusMessage(''); + }, 3000); + } + const drawSelection = (endX: number, endY: number, pageIndex: number) => { const { startX, startY } = selectionCoords; const canvas = overlayCanvasRefs.current[pageIndex]; @@ -209,27 +222,23 @@ export const PdfRenderer: React.FC = () => { const sendSignRequest = async () => { if (!pdfBuff || currentPageIndex === null || selectedSignature === null) return; - - setIsLoading(true); setStatusMessage('Signing ...'); setStatusType('checking'); setIsStatusVisible(true); + setShowPinPopup(true); + }; + const handlePinSubmit = async (pin: string) => { + setShowPinPopup(false); + setIsLoading(true); try { - const pin = prompt('Please enter your PIN:'); - if (!pin) { - alert('Please enter your PIN!'); - return; - } - const signedPdfBuffer = await signPdf( - pdfBuff, - currentPageIndex, + pdfBuff!, + currentPageIndex!, selectionCoords, - selectedSignature, + selectedSignature!, pin ); - setPdfFile(new Blob([signedPdfBuffer], { type: 'application/pdf' })); setPdfBuff(Buffer.from(signedPdfBuffer)); setStatusMessage('Signature Complete'); @@ -237,25 +246,26 @@ export const PdfRenderer: React.FC = () => { setIsStatusVisible(true); setTimeout(() => { setIsStatusVisible(false); + setStatusMessage(''); }, 3000); setIsSelectionEnabled(false); setShowSuccessPopup(true); + setIsSigned(true); setSuccessMessage(`Your document has been signed`); } catch (error) { console.error('Signing error:', error); setStatusMessage('Error'); setStatusType('error'); setIsStatusVisible(true); - setTimeout(() => { - setIsStatusVisible(false); - alert(`Error: ${error}`); - }, 3000); + setErrorMessage(`${error}`); + setIsSelectionEnabled(false); + clearAllSelections(); + setShowError(true); } finally { setIsLoading(false); clearAllSelections(); } }; - const handleSignButtonClick = useCallback(() => { if (!pdfBuff) { alert('Please upload a PDF first'); @@ -428,6 +438,7 @@ export const PdfRenderer: React.FC = () => { handleDownloadButtonClick={handleDownloadButtonClick} handleLogoClick={handleLogoClick} fileInputRef={fileInputRef} + isSigned={isSigned} /> {!pdfFile ? ( @@ -522,6 +533,24 @@ export const PdfRenderer: React.FC = () => { )} + {showError && ( + + )} + {showPinPopup && ( + { + setShowPinPopup(false); + setIsStatusVisible(false); + setIsSelectionEnabled(false); + setStatusMessage(''); + }} + onComplete={handlePinSubmit} + /> + )} void; fileInputRef: React.RefObject; + isSigned: boolean; } export type StoredSignature = { @@ -77,6 +78,11 @@ export type SuccessPopupProps = { isVerification: boolean; } +export type ErrorPopupProps = { + message: string; + onContinue: () => void; +} + export type AnimatedStatusProps = { message: string; type: 'checking' | 'success' | 'error';