diff --git a/src/App.scss b/src/App.scss index b24e16a..d3bff8a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -41,6 +41,7 @@ p { body { color: $text-color; + background: $body-background-color; font-family: $font-familiy; letter-spacing: $letter-spacing; font-size: $body-font-size; @@ -70,6 +71,18 @@ input { font-family: inherit; } +ul { + list-style-type: none; /* Removes bullet points */ + margin: 0; /* Removes default margin */ + padding: 0; /* Removes default padding */ +} + +li { + list-style-type: none; /* Removes the bullets */ + margin: 0; /* Removes any default margin */ + padding: 0; /* Removes any default padding */ +} + // Shared styles for center content (Create, Sign, Verify) .files-wrapper { display: flex; diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index 66f6952..7efae8b 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -1,7 +1,5 @@ import { Close } from '@mui/icons-material' import { - Box, - CircularProgress, FormControl, InputLabel, ListItemIcon, @@ -11,7 +9,6 @@ import { } from '@mui/material' import styles from './style.module.scss' import React, { useEffect, useState } from 'react' -import * as PDFJS from 'pdfjs-dist' import { ProfileMetadata, User, UserRole } from '../../types' import { MouseState, PdfPage, DrawnField, DrawTool } from '../../types/drawing' import { truncate } from 'lodash' @@ -22,11 +19,12 @@ import { ExtensionFileBox } from '../ExtensionFileBox' import { inPx } from '../../utils/pdf' import { useScale } from '../../hooks/useScale' import { AvatarIconButton } from '../UserAvatarIconButton' +import { LoadingSpinner } from '../LoadingSpinner' -PDFJS.GlobalWorkerOptions.workerSrc = new URL( - 'pdfjs-dist/build/pdf.worker.min.mjs', - import.meta.url -).toString() +const DEFAULT_START_SIZE = { + width: 140, + height: 40 +} as const interface Props { selectedFiles: File[] @@ -47,6 +45,8 @@ export const DrawPDFFields = (props: Props) => { clicked: false }) + const [activeDrawField, setActiveDrawField] = useState() + useEffect(() => { if (selectedFiles) { /** @@ -78,10 +78,12 @@ export const DrawPDFFields = (props: Props) => { * Drawing events */ useEffect(() => { - window.addEventListener('mouseup', onMouseUp) + window.addEventListener('pointerup', handlePointerUp) + window.addEventListener('pointercancel', handlePointerUp) return () => { - window.removeEventListener('mouseup', onMouseUp) + window.removeEventListener('pointerup', handlePointerUp) + window.removeEventListener('pointercancel', handlePointerUp) } }, []) @@ -90,17 +92,14 @@ export const DrawPDFFields = (props: Props) => { } /** - * Fired only when left click and mouse over pdf page + * Fired only on when left (primary pointer interaction) clicking page image * Creates new drawnElement and pushes in the array * It is re rendered and visible right away * - * @param event Mouse event + * @param event Pointer event * @param page PdfPage where press happened */ - const onMouseDown = ( - event: React.MouseEvent, - page: PdfPage - ) => { + const handlePointerDown = (event: React.PointerEvent, page: PdfPage) => { // Proceed only if left click if (event.button !== 0) return @@ -108,13 +107,13 @@ export const DrawPDFFields = (props: Props) => { return } - const { mouseX, mouseY } = getMouseCoordinates(event) + const { x, y } = getPointerCoordinates(event) const newField: DrawnField = { - left: to(page.width, mouseX), - top: to(page.width, mouseY), - width: 0, - height: 0, + left: to(page.width, x), + top: to(page.width, y), + width: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.width, + height: event.pointerType === 'mouse' ? 0 : DEFAULT_START_SIZE.height, counterpart: '', type: selectedTool.identifier } @@ -131,9 +130,9 @@ export const DrawPDFFields = (props: Props) => { /** * Drawing is finished, resets all the variables used to draw - * @param event Mouse event + * @param event Pointer event */ - const onMouseUp = () => { + const handlePointerUp = () => { setMouseState((prev) => { return { ...prev, @@ -145,16 +144,13 @@ export const DrawPDFFields = (props: Props) => { } /** - * After {@link onMouseDown} create an drawing element, this function gets called on every pixel moved - * which alters the newly created drawing element, resizing it while mouse move - * @param event Mouse event + * After {@link handlePointerDown} create an drawing element, this function gets called on every pixel moved + * which alters the newly created drawing element, resizing it while pointer moves + * @param event Pointer event * @param page PdfPage where moving is happening */ - const onMouseMove = ( - event: React.MouseEvent, - page: PdfPage - ) => { - if (mouseState.clicked && selectedTool) { + const handlePointerMove = (event: React.PointerEvent, page: PdfPage) => { + if (mouseState.clicked && selectedTool && event.pointerType === 'mouse') { const lastElementIndex = page.drawnFields.length - 1 const lastDrawnField = page.drawnFields[lastElementIndex] @@ -164,10 +160,10 @@ export const DrawPDFFields = (props: Props) => { // to the page below (without releaseing mouse click) if (!lastDrawnField) return - const { mouseX, mouseY } = getMouseCoordinates(event) + const { x, y } = getPointerCoordinates(event) - const width = to(page.width, mouseX) - lastDrawnField.left - const height = to(page.width, mouseY) - lastDrawnField.top + const width = to(page.width, x) - lastDrawnField.left + const height = to(page.width, y) - lastDrawnField.top lastDrawnField.width = width lastDrawnField.height = height @@ -182,55 +178,60 @@ export const DrawPDFFields = (props: Props) => { /** * Fired when event happens on the drawn element which will be moved - * mouse coordinates relative to drawn element will be stored + * pointer coordinates relative to drawn element will be stored * so when we start moving, offset can be calculated - * mouseX - offsetX - * mouseY - offsetY + * x - offsetX + * y - offsetY * - * @param event Mouse event - * @param drawnField Which we are moving + * @param event Pointer event + * @param drawnFieldIndex Which we are moving */ - const onDrawnFieldMouseDown = (event: React.MouseEvent) => { + const handleDrawnFieldPointerDown = ( + event: React.PointerEvent, + drawnFieldIndex: number + ) => { event.stopPropagation() // Proceed only if left click if (event.button !== 0) return - const drawingRectangleCoords = getMouseCoordinates(event) + const drawingRectangleCoords = getPointerCoordinates(event) + setActiveDrawField(drawnFieldIndex) setMouseState({ dragging: true, clicked: false, coordsInWrapper: { - mouseX: drawingRectangleCoords.mouseX, - mouseY: drawingRectangleCoords.mouseY + x: drawingRectangleCoords.x, + y: drawingRectangleCoords.y } }) } /** - * Moves the drawnElement by the mouse position (mouse can grab anywhere on the drawn element) - * @param event Mouse event + * Moves the drawnElement by the pointer position (pointer can grab anywhere on the drawn element) + * @param event Pointer event * @param drawnField which we are moving + * @param pageWidth pdf value which is used to calculate scaled offset */ - const onDrawnFieldMouseMove = ( - event: React.MouseEvent, + const handleDrawnFieldPointerMove = ( + event: React.PointerEvent, drawnField: DrawnField, pageWidth: number ) => { if (mouseState.dragging) { - const { mouseX, mouseY, rect } = getMouseCoordinates( + const { x, y, rect } = getPointerCoordinates( event, event.currentTarget.parentElement ) const coordsOffset = mouseState.coordsInWrapper if (coordsOffset) { - let left = to(pageWidth, mouseX - coordsOffset.mouseX) - let top = to(pageWidth, mouseY - coordsOffset.mouseY) + let left = to(pageWidth, x - coordsOffset.x) + let top = to(pageWidth, y - coordsOffset.y) - const rightLimit = to(pageWidth, rect.width) - drawnField.width - 3 - const bottomLimit = to(pageWidth, rect.height) - drawnField.height - 3 + const rightLimit = to(pageWidth, rect.width) - drawnField.width + const bottomLimit = to(pageWidth, rect.height) - drawnField.height if (left < 0) left = 0 if (top < 0) top = 0 @@ -247,17 +248,18 @@ export const DrawPDFFields = (props: Props) => { /** * Fired when clicked on the resize handle, sets the state for a resize action - * @param event Mouse event - * @param drawnField which we are resizing + * @param event Pointer event + * @param drawnFieldIndex which we are resizing */ - const onResizeHandleMouseDown = ( - event: React.MouseEvent + const handleResizePointerDown = ( + event: React.PointerEvent, + drawnFieldIndex: number ) => { // Proceed only if left click if (event.button !== 0) return - event.stopPropagation() + setActiveDrawField(drawnFieldIndex) setMouseState({ resizing: true }) @@ -265,16 +267,17 @@ export const DrawPDFFields = (props: Props) => { /** * Resizes the drawn element by the mouse position - * @param event Mouse event + * @param event Pointer event * @param drawnField which we are resizing + * @param pageWidth pdf value which is used to calculate scaled offset */ - const onResizeHandleMouseMove = ( - event: React.MouseEvent, + const handleResizePointerMove = ( + event: React.PointerEvent, drawnField: DrawnField, pageWidth: number ) => { if (mouseState.resizing) { - const { mouseX, mouseY } = getMouseCoordinates( + const { x, y } = getPointerCoordinates( event, // currentTarget = span handle // 1st parent = drawnField @@ -282,8 +285,8 @@ export const DrawPDFFields = (props: Props) => { event.currentTarget.parentElement?.parentElement ) - const width = to(pageWidth, mouseX) - drawnField.left - const height = to(pageWidth, mouseY) - drawnField.top + const width = to(pageWidth, x) - drawnField.left + const height = to(pageWidth, y) - drawnField.top drawnField.width = width drawnField.height = height @@ -294,13 +297,13 @@ export const DrawPDFFields = (props: Props) => { /** * Removes the drawn element using the indexes in the params - * @param event Mouse event + * @param event Pointer event * @param pdfFileIndex pdf file index * @param pdfPageIndex pdf page index * @param drawnFileIndex drawn file index */ - const onRemoveHandleMouseDown = ( - event: React.MouseEvent, + const handleRemovePointerDown = ( + event: React.PointerEvent, pdfFileIndex: number, pdfPageIndex: number, drawnFileIndex: number @@ -314,40 +317,37 @@ export const DrawPDFFields = (props: Props) => { } /** - * Used to stop mouse click propagating to the parent elements + * Used to stop pointer click propagating to the parent elements * so select can work properly - * @param event Mouse event + * @param event Pointer event */ - const onUserSelectHandleMouseDown = ( - event: React.MouseEvent - ) => { + const handleUserSelectPointerDown = (event: React.PointerEvent) => { event.stopPropagation() } /** - * Gets the mouse coordinates relative to a element in the `event` param - * @param event MouseEvent - * @param customTarget mouse coordinates relative to this element, if not provided + * Gets the pointer coordinates relative to a element in the `event` param + * @param event PointerEvent + * @param customTarget coordinates relative to this element, if not provided * event.target will be used */ - const getMouseCoordinates = ( - event: React.MouseEvent, + const getPointerCoordinates = ( + event: React.PointerEvent, customTarget?: HTMLElement | null ) => { const target = customTarget ? customTarget : event.currentTarget const rect = target.getBoundingClientRect() // Clamp X Y within the target - const mouseX = Math.min(event.clientX, rect.right) - rect.left //x position within the element. - const mouseY = Math.min(event.clientY, rect.bottom) - rect.top //y position within the element. + const x = Math.min(event.clientX, rect.right) - rect.left //x position within the element. + const y = Math.min(event.clientY, rect.bottom) - rect.top //y position within the element. return { - mouseX, - mouseY, + x, + y, rect } } - /** * Renders the pdf pages and drawing elements */ @@ -364,43 +364,67 @@ export const DrawPDFFields = (props: Props) => { className={`image-wrapper ${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`} > { - onMouseMove(event, page) + onPointerMove={(event) => { + handlePointerMove(event, page) }} - onMouseDown={(event) => { - onMouseDown(event, page) + onPointerDown={(event) => { + handlePointerDown(event, page) }} draggable="false" src={page.image} + alt={`page ${pageIndex + 1} of ${file.name}`} /> {page.drawnFields.map((drawnField, drawnFieldIndex: number) => { return (
{ - onDrawnFieldMouseMove(event, drawnField, page.width) + onPointerDown={(event) => + handleDrawnFieldPointerDown(event, drawnFieldIndex) + } + onPointerMove={(event) => { + handleDrawnFieldPointerMove(event, drawnField, page.width) }} className={styles.drawingRectangle} style={{ + backgroundColor: drawnField.counterpart + ? `#${npubToHex(drawnField.counterpart)?.substring(0, 6)}4b` + : undefined, + borderColor: drawnField.counterpart + ? `#${npubToHex(drawnField.counterpart)?.substring(0, 6)}` + : undefined, left: inPx(from(page.width, drawnField.left)), top: inPx(from(page.width, drawnField.top)), width: inPx(from(page.width, drawnField.width)), height: inPx(from(page.width, drawnField.height)), - pointerEvents: mouseState.clicked ? 'none' : 'all' + pointerEvents: mouseState.clicked ? 'none' : 'all', + touchAction: 'none', + opacity: + mouseState.dragging && + activeDrawField === drawnFieldIndex + ? 0.8 + : undefined }} > { - onResizeHandleMouseMove(event, drawnField, page.width) + onPointerDown={(event) => + handleResizePointerDown(event, drawnFieldIndex) + } + onPointerMove={(event) => { + handleResizePointerMove(event, drawnField, page.width) }} className={styles.resizeHandle} + style={{ + background: + mouseState.resizing && + activeDrawField === drawnFieldIndex + ? 'var(--primary-main)' + : undefined + }} > { - onRemoveHandleMouseDown( + onPointerDown={(event) => { + handleRemovePointerDown( event, fileIndex, pageIndex, @@ -412,7 +436,7 @@ export const DrawPDFFields = (props: Props) => {
@@ -529,11 +553,7 @@ export const DrawPDFFields = (props: Props) => { } if (parsingPdf) { - return ( - - - - ) + return } if (!sigitFiles.length) { diff --git a/src/components/FileList/index.tsx b/src/components/FileList/index.tsx index 38fab88..a47ace7 100644 --- a/src/components/FileList/index.tsx +++ b/src/components/FileList/index.tsx @@ -22,30 +22,26 @@ const FileList = ({ const isActive = (file: CurrentUserFile) => file.id === currentFile.id return (
-
-
    - {files.map((currentUserFile: CurrentUserFile) => ( -
  • setCurrentFile(currentUserFile)} - > -
    {currentUserFile.id}
    -
    -
    - {currentUserFile.file.name} -
    -
    +
      + {files.map((currentUserFile: CurrentUserFile) => ( +
    • setCurrentFile(currentUserFile)} + > +
      {currentUserFile.id}
      +
      +
      {currentUserFile.file.name}
      +
      -
      - {currentUserFile.isHashValid && ( - - )} -
      -
    • - ))} -
    -
+
+ {currentUserFile.isHashValid && ( + + )} +
+ + ))} + diff --git a/src/components/FileList/style.module.scss b/src/components/FileList/style.module.scss index 22d8515..a05fcbd 100644 --- a/src/components/FileList/style.module.scss +++ b/src/components/FileList/style.module.scss @@ -1,12 +1,3 @@ -.container { - border-radius: 4px; - background: white; - padding: 15px; - display: flex; - flex-direction: column; - grid-gap: 0px; -} - .filesPageContainer { width: 100%; display: grid; @@ -15,18 +6,6 @@ flex-grow: 1; } -ul { - list-style-type: none; /* Removes bullet points */ - margin: 0; /* Removes default margin */ - padding: 0; /* Removes default padding */ -} - -li { - list-style-type: none; /* Removes the bullets */ - margin: 0; /* Removes any default margin */ - padding: 0; /* Removes any default padding */ -} - .wrap { display: flex; flex-direction: column; @@ -34,14 +13,16 @@ li { } .files { + border-radius: 4px; + background: white; + padding: 15px; + display: flex; flex-direction: column; width: 100%; grid-gap: 15px; - max-height: 350px; - overflow: auto; - padding: 0 5px 0 0; - margin: 0 -5px 0 0; + overflow-y: auto; + overflow-x: none; } .files::-webkit-scrollbar { diff --git a/src/components/Footer/Footer.tsx b/src/components/Footer/Footer.tsx index eac4166..17140e4 100644 --- a/src/components/Footer/Footer.tsx +++ b/src/components/Footer/Footer.tsx @@ -4,125 +4,128 @@ import styles from './style.module.scss' import { Container } from '../Container' import nostrImage from '../../assets/images/nostr.gif' import { appPublicRoutes } from '../../routes' +import { createPortal } from 'react-dom' -export const Footer = () => ( -
- - + createPortal( +
+ - - Logo - - - - - + + + + - Source - + + - - - - - -
- Built by  - - Nostr Dev - {' '} - 2024. -
-
-) +
+
+ Built by  + + Nostr Dev + {' '} + 2024. +
+
, + document.getElementById('root')! + ) diff --git a/src/components/LoadingSpinner/index.tsx b/src/components/LoadingSpinner/index.tsx index 980a763..2c6f4e5 100644 --- a/src/components/LoadingSpinner/index.tsx +++ b/src/components/LoadingSpinner/index.tsx @@ -1,18 +1,35 @@ import styles from './style.module.scss' interface Props { - desc: string + desc?: string + variant?: 'small' | 'default' } export const LoadingSpinner = (props: Props) => { - const { desc } = props + const { desc, variant = 'default' } = props - return ( -
-
-
- {desc && {desc}} -
-
- ) + switch (variant) { + case 'small': + return ( +
+
+
+ ) + + default: + return ( +
+
+
+ {desc &&

{desc}

} +
+
+ ) + } } diff --git a/src/components/LoadingSpinner/style.module.scss b/src/components/LoadingSpinner/style.module.scss index 75b2609..e1a5978 100644 --- a/src/components/LoadingSpinner/style.module.scss +++ b/src/components/LoadingSpinner/style.module.scss @@ -2,34 +2,48 @@ .loadingSpinnerOverlay { position: fixed; - top: 0; - left: 0; - width: 100%; - height: 100%; + inset: 0; display: flex; align-items: center; justify-content: center; background-color: rgba(0, 0, 0, 0.5); z-index: 9999; + backdrop-filter: blur(10px); +} - .loadingSpinnerContainer { - display: flex; - flex-direction: column; - align-items: center; - justify-content: center; +.loadingSpinnerContainer { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + + &[data-variant='default'] { + width: 100%; + max-width: 500px; + margin: 25px 20px; + background: $overlay-background-color; + border-radius: 4px; + box-shadow: 0 0 4px 0 rgb(0, 0, 0, 0.1); } - - .loadingSpinner { - background: url('/favicon.png') no-repeat center / cover; - width: 40px; - height: 40px; - animation: spin 1s linear infinite; + &[data-variant='small'] { + min-height: 250px; } } +.loadingSpinner { + background: url('/favicon.png') no-repeat center / cover; + margin: 40px 25px; + width: 65px; + height: 65px; + animation: spin 1s linear infinite; +} + .loadingSpinnerDesc { - color: white; - margin-top: 13px; + width: 100%; + padding: 15px; + border-top: solid 1px rgba(0, 0, 0, 0.1); + text-align: center; + color: rgba(0, 0, 0, 0.5); font-size: 16px; font-weight: 400; diff --git a/src/components/MarkFormField/style.module.scss b/src/components/MarkFormField/style.module.scss index 1275038..9f4b092 100644 --- a/src/components/MarkFormField/style.module.scss +++ b/src/components/MarkFormField/style.module.scss @@ -1,11 +1,19 @@ +@import '../../styles/sizes.scss'; + .container { - width: 100%; display: flex; flex-direction: column; position: fixed; - bottom: 0; - right: 0; - left: 0; + + @media only screen and (min-width: 768px) { + bottom: 0; + right: 0; + left: 0; + } + bottom: $tabs-height + 5px; + right: 5px; + left: 5px; + align-items: center; z-index: 1000; @@ -107,7 +115,7 @@ .actions { background: white; width: 100%; - border-radius: 4px; + border-radius: 5px; padding: 10px 20px; display: none; flex-direction: column; diff --git a/src/components/PDFView/PdfItem.tsx b/src/components/PDFView/PdfItem.tsx index 1a6c6a6..f1dbe87 100644 --- a/src/components/PDFView/PdfItem.tsx +++ b/src/components/PDFView/PdfItem.tsx @@ -36,6 +36,8 @@ const PdfItem = ({ return file.pages?.map((page, i) => { return ( {
} right={meta !== null && } + leftIcon={faFileDownload} + centerIcon={faPen} + rightIcon={faCircleInfo} > {currentUserMarks?.length > 0 && ( void otherUserMarks: Mark[] @@ -19,6 +21,8 @@ interface PdfPageProps { * Responsible for rendering a single Pdf Page and its Marks */ const PdfPageItem = ({ + fileName, + pageIndex, page, currentUserMarks, handleMarkClick, @@ -38,7 +42,11 @@ const PdfPageItem = ({ return (
- + {`page {currentUserMarks.map((m, i) => (
(markRefs.current[m.id] = el)}> - {files.map((currentUserFile, index, arr) => { - const { hash, file, id } = currentUserFile + {files.length > 0 ? ( + files.map((currentUserFile, index, arr) => { + const { hash, file, id } = currentUserFile - if (!hash) return - return ( - -
(pdfRefs.current[id] = el)} - > - -
- {isNotLastPdfFile(index, arr) && } -
- ) - })} + if (!hash) return + return ( + +
(pdfRefs.current[id] = el)} + > + +
+ {isNotLastPdfFile(index, arr) && } +
+ ) + }) + ) : ( + + )}
) } diff --git a/src/components/UsersDetails.tsx/index.tsx b/src/components/UsersDetails.tsx/index.tsx index 16ff440..bddae82 100644 --- a/src/components/UsersDetails.tsx/index.tsx +++ b/src/components/UsersDetails.tsx/index.tsx @@ -118,32 +118,44 @@ export const UsersDetails = ({ meta }: UsersDetailsProps) => { ) })} - {viewers.map((signer) => { - const pubkey = npubToHex(signer)! - const profile = profiles[pubkey] - - return ( - - - - - - ) - })}
+ + {viewers.length > 0 && ( + <> +

Viewers

+
+ + {viewers.map((signer) => { + const pubkey = npubToHex(signer)! + const profile = profiles[pubkey] + + return ( + + + + + + ) + })} + +
+ + )}

Details

diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index ac233cc..f3962eb 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -26,7 +26,6 @@ import { } from '../utils' import { useAppSelector } from '../hooks' import styles from './style.module.scss' -import { Footer } from '../components/Footer/Footer' export const MainLayout = () => { const dispatch: Dispatch = useDispatch() @@ -160,7 +159,6 @@ export const MainLayout = () => { > -
) } diff --git a/src/layouts/StickySideColumns.module.scss b/src/layouts/StickySideColumns.module.scss index 77daa03..a116720 100644 --- a/src/layouts/StickySideColumns.module.scss +++ b/src/layouts/StickySideColumns.module.scss @@ -3,9 +3,33 @@ .container { display: grid; - grid-template-columns: 0.75fr 1.5fr 0.75fr; - grid-gap: 30px; - flex-grow: 1; + + @media only screen and (max-width: 767px) { + gap: 20px; + grid-auto-flow: column; + grid-auto-columns: 100%; + + // Hide Scrollbar and let's use tabs to navigate + -ms-overflow-style: none; /* Internet Explorer 10+ */ + scrollbar-width: none; /* Firefox */ + &::-webkit-scrollbar { + display: none; /* Safari and Chrome */ + } + overflow-x: auto; + overscroll-behavior-inline: contain; + scroll-snap-type: inline mandatory; + + > * { + scroll-margin-top: $header-height + $body-vertical-padding; + scroll-snap-align: start; + scroll-snap-stop: always; // Touch devices will always stop on each element + } + } + + @media only screen and (min-width: 768px) { + grid-template-columns: 0.75fr 1.5fr 0.75fr; + gap: 30px; + } } .sidesWrap { @@ -16,17 +40,58 @@ } .sides { - position: sticky; - top: $header-height + $body-vertical-padding; + @media only screen and (min-width: 768px) { + position: sticky; + top: $header-height + $body-vertical-padding; + } + > :first-child { + max-height: calc( + 100dvh - $header-height - $body-vertical-padding * 2 - $tabs-height + ); + } } -.files { - display: flex; - flex-direction: column; - grid-gap: 15px; +.scrollAdjust { + @media only screen and (max-width: 767px) { + max-height: calc( + 100svh - $header-height - $body-vertical-padding * 2 - $tabs-height + ); + overflow-y: auto; + } } + .content { - padding: 10px; - border: 10px solid $overlay-background-color; - border-radius: 4px; + @media only screen and (min-width: 768px) { + padding: 10px; + border: 10px solid $overlay-background-color; + border-radius: 4px; + } +} + +.navTabs { + display: none; + position: fixed; + left: 0; + bottom: 0; + right: 0; + height: $tabs-height; + z-index: 2; + background: $overlay-background-color; + box-shadow: 0 0 4px 0 rgb(0, 0, 0, 0.1); + + padding: 5px; + gap: 5px; + + @media only screen and (max-width: 767px) { + display: flex; + } + + > li { + flex-grow: 1; + } +} + +.active { + background-color: $primary-main !important; + color: white !important; } diff --git a/src/layouts/StickySideColumns.tsx b/src/layouts/StickySideColumns.tsx index d27ad4b..c460fbd 100644 --- a/src/layouts/StickySideColumns.tsx +++ b/src/layouts/StickySideColumns.tsx @@ -1,30 +1,147 @@ -import { PropsWithChildren, ReactNode } from 'react' +import { + PropsWithChildren, + ReactNode, + useEffect, + useRef, + useState +} from 'react' import styles from './StickySideColumns.module.scss' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { IconDefinition } from '@fortawesome/free-solid-svg-icons' +import { Button } from '@mui/material' interface StickySideColumnsProps { - left?: ReactNode - right?: ReactNode + left: ReactNode + right: ReactNode + leftIcon: IconDefinition + centerIcon: IconDefinition + rightIcon: IconDefinition } +const DEFAULT_TAB = 'nav-content' export const StickySideColumns = ({ left, right, + leftIcon, + centerIcon, + rightIcon, children }: PropsWithChildren) => { + const [tab, setTab] = useState(DEFAULT_TAB) + const ref = useRef(null) + const tabsRefs = useRef<{ [id: string]: HTMLDivElement | null }>({}) + const handleNavClick = (id: string) => { + if (ref.current && tabsRefs.current) { + const x = tabsRefs.current[id]?.offsetLeft + ref.current.scrollTo({ + left: x, + behavior: 'smooth' + }) + } + } + const isActive = (id: string) => id === tab + + useEffect(() => { + setTab(DEFAULT_TAB) + handleNavClick(DEFAULT_TAB) + }, []) + + useEffect(() => { + const tabs = tabsRefs.current + // Set up the observer + const observer = new IntersectionObserver( + (entries) => { + entries.forEach((entry) => { + if (entry.isIntersecting) { + setTab(entry.target.id) + } + }) + }, + { + root: ref.current, + threshold: 0.5, + rootMargin: '-20px' + } + ) + + if (tabs) { + Object.values(tabs).forEach((tab) => { + if (tab) observer.observe(tab) + }) + } + + return () => { + if (tabs) { + Object.values(tabs).forEach((tab) => { + if (tab) observer.unobserve(tab) + }) + } + } + }, []) + return ( -
-
-
{left}
-
-
-
- {children} + <> +
+ + +
-
-
{right}
-
-
+
    +
  • + +
  • +
  • + +
  • +
  • + +
  • +
+ ) } diff --git a/src/layouts/style.module.scss b/src/layouts/style.module.scss index c1aee30..6c8aa59 100644 --- a/src/layouts/style.module.scss +++ b/src/layouts/style.module.scss @@ -4,5 +4,4 @@ .main { flex-grow: 1; padding: $header-height + $body-vertical-padding 0 $body-vertical-padding 0; - background-color: $body-background-color; } diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 2c32f7d..f1cfbd0 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -66,6 +66,8 @@ import { faCreditCard, faEllipsis, faEye, + faFile, + faFileCirclePlus, faGripLines, faHeading, faIdCard, @@ -80,6 +82,7 @@ import { faStamp, faT, faTableCellsLarge, + faToolbox, faTrash, faUpload } from '@fortawesome/free-solid-svg-icons' @@ -132,109 +135,109 @@ export const CreatePage = () => { const [toolbox] = useState([ { identifier: MarkType.TEXT, - icon: , + icon: faT, label: 'Text', active: true }, { identifier: MarkType.SIGNATURE, - icon: , + icon: faSignature, label: 'Signature', active: false }, { identifier: MarkType.JOBTITLE, - icon: , + icon: faBriefcase, label: 'Job Title', active: false }, { identifier: MarkType.FULLNAME, - icon: , + icon: faIdCard, label: 'Full Name', active: false }, { identifier: MarkType.INITIALS, - icon: , + icon: faHeading, label: 'Initials', active: false }, { identifier: MarkType.DATETIME, - icon: , + icon: faClock, label: 'Date Time', active: false }, { identifier: MarkType.DATE, - icon: , + icon: faCalendarDays, label: 'Date', active: false }, { identifier: MarkType.NUMBER, - icon: , + icon: fa1, label: 'Number', active: false }, { identifier: MarkType.IMAGES, - icon: , + icon: faImage, label: 'Images', active: false }, { identifier: MarkType.CHECKBOX, - icon: , + icon: faSquareCheck, label: 'Checkbox', active: false }, { identifier: MarkType.MULTIPLE, - icon: , + icon: faCheckDouble, label: 'Multiple', active: false }, { identifier: MarkType.FILE, - icon: , + icon: faPaperclip, label: 'File', active: false }, { identifier: MarkType.RADIO, - icon: , + icon: faCircleDot, label: 'Radio', active: false }, { identifier: MarkType.SELECT, - icon: , + icon: faSquareCaretDown, label: 'Select', active: false }, { identifier: MarkType.CELLS, - icon: , + icon: faTableCellsLarge, label: 'Cells', active: false }, { identifier: MarkType.STAMP, - icon: , + icon: faStamp, label: 'Stamp', active: false }, { identifier: MarkType.PAYMENT, - icon: , + icon: faCreditCard, label: 'Payment', active: false }, { identifier: MarkType.PHONE, - icon: , + icon: faPhone, label: 'Phone', active: false } @@ -457,10 +460,8 @@ export const CreatePage = () => { return false } - if (users.length === 0) { - toast.error( - 'No signer/viewer is provided. At least add one signer or viewer.' - ) + if (!users.some((u) => u.role === UserRole.signer)) { + toast.error('No signer is provided. At least add one signer.') return false } @@ -1001,7 +1002,7 @@ export const CreatePage = () => {
-
+
{ return (
{ - handleToolSelect(drawTool) - } - : () => null - } + {...(drawTool.active && { + onClick: () => handleToolSelect(drawTool) + })} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''} ${!drawTool.active ? styles.comingSoon : ''} `} > - {drawTool.icon} + {drawTool.label} {drawTool.active ? ( - + ) : ( { )}
} + leftIcon={faFileCirclePlus} + centerIcon={faFile} + rightIcon={faToolbox} > { ))}
+
) } diff --git a/src/pages/landing/index.tsx b/src/pages/landing/index.tsx index 015d721..deae096 100644 --- a/src/pages/landing/index.tsx +++ b/src/pages/landing/index.tsx @@ -19,6 +19,7 @@ import { faWifi } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIconStack } from '../../components/FontAwesomeIconStack' +import { Footer } from '../../components/Footer/Footer' export const LandingPage = () => { const navigate = useNavigate() @@ -162,6 +163,7 @@ export const LandingPage = () => { +
) } diff --git a/src/pages/profile/index.tsx b/src/pages/profile/index.tsx index a7b205b..ca4bb87 100644 --- a/src/pages/profile/index.tsx +++ b/src/pages/profile/index.tsx @@ -20,6 +20,7 @@ import { } from '../../utils' import styles from './style.module.scss' import { Container } from '../../components/Container' +import { Footer } from '../../components/Footer/Footer' export const ProfilePage = () => { const navigate = useNavigate() @@ -41,6 +42,16 @@ export const ProfilePage = () => { const [isLoading, setIsLoading] = useState(true) const [loadingSpinnerDesc] = useState('Fetching metadata') + const profileName = + pubkey && + profileMetadata && + truncate( + profileMetadata.display_name || profileMetadata.name || hexToNpub(pubkey), + { + length: 16 + } + ) + useEffect(() => { if (npub) { try { @@ -165,7 +176,10 @@ export const ProfilePage = () => { className={`${styles.banner} ${!profileMetadata || !profileMetadata.banner ? styles.noImage : ''}`} > {profileMetadata && profileMetadata.banner ? ( - + {`banner ) : ( '' )} @@ -185,6 +199,7 @@ export const ProfilePage = () => { {profileName}
@@ -224,14 +239,7 @@ export const ProfilePage = () => { variant="h6" className={styles.bold} > - {truncate( - profileMetadata.display_name || - profileMetadata.name || - hexToNpub(pubkey), - { - length: 16 - } - )} + {profileName} )} @@ -285,6 +293,7 @@ export const ProfilePage = () => { )} +