From f21d158a8ec5fd204fd513f140cfa33b97cb5383 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 30 Jul 2024 18:04:40 +0200 Subject: [PATCH 01/49] fix: input font-family inherit --- src/App.scss | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App.scss b/src/App.scss index f5394f7..5461a4a 100644 --- a/src/App.scss +++ b/src/App.scss @@ -63,3 +63,7 @@ a { text-decoration-color: inherit; } } + +input { + font-family: inherit; +} From b959aa7dc254559841fe352b5777cc87fe9b7396 Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 5 Aug 2024 09:21:22 +0200 Subject: [PATCH 02/49] style: applied css prettier formatting --- src/index.css | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/index.css b/src/index.css index 6a734df..76373ff 100644 --- a/src/index.css +++ b/src/index.css @@ -111,7 +111,9 @@ button:disabled { /* Fonts */ @font-face { font-family: 'Roboto'; - src: local('Roboto Medium'), local('Roboto-Medium'), + src: + local('Roboto Medium'), + local('Roboto-Medium'), url('assets/fonts/roboto-medium.woff2') format('woff2'), url('assets/fonts/roboto-medium.woff') format('woff'); font-weight: 500; @@ -121,7 +123,9 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto Light'), local('Roboto-Light'), + src: + local('Roboto Light'), + local('Roboto-Light'), url('assets/fonts/roboto-light.woff2') format('woff2'), url('assets/fonts/roboto-light.woff') format('woff'); font-weight: 300; @@ -131,7 +135,9 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto Bold'), local('Roboto-Bold'), + src: + local('Roboto Bold'), + local('Roboto-Bold'), url('assets/fonts/roboto-bold.woff2') format('woff2'), url('assets/fonts/roboto-bold.woff') format('woff'); font-weight: bold; @@ -141,10 +147,12 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto'), local('Roboto-Regular'), + src: + local('Roboto'), + local('Roboto-Regular'), url('assets/fonts/roboto-regular.woff2') format('woff2'), url('assets/fonts/roboto-regular.woff') format('woff'); font-weight: normal; font-style: normal; font-display: swap; -} \ No newline at end of file +} From 64b6f8309f361a6db6c6e91005b50969cdbfa549 Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 5 Aug 2024 09:24:14 +0200 Subject: [PATCH 03/49] fix: modal override removed Mui modal overrides affected tooltip positioning --- src/layouts/modal/style.module.scss | 4 +++- src/theme/index.ts | 8 -------- 2 files changed, 3 insertions(+), 9 deletions(-) diff --git a/src/layouts/modal/style.module.scss b/src/layouts/modal/style.module.scss index f7dd2fc..501f171 100644 --- a/src/layouts/modal/style.module.scss +++ b/src/layouts/modal/style.module.scss @@ -5,7 +5,7 @@ $default-modal-padding: 15px 25px; .modal { position: absolute; top: 0; - left: 50%; + left: calc(50% - 10px); transform: translate(-50%, 0); background-color: $overlay-background-color; @@ -16,6 +16,8 @@ $default-modal-padding: 15px 25px; flex-direction: column; border-radius: 4px; + + margin: 25px 10px; } .header { diff --git a/src/theme/index.ts b/src/theme/index.ts index 4a1e0ab..a7a37d4 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -23,14 +23,6 @@ export const theme = extendTheme({ } }, components: { - MuiModal: { - styleOverrides: { - root: { - insetBlock: '25px', - insetInline: '10px' - } - } - }, MuiButton: { styleOverrides: { root: { From 8d168314de807bfb7b5d96ddc0cf82109afdf343 Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 5 Aug 2024 09:39:46 +0200 Subject: [PATCH 04/49] feat: custom select component --- src/components/Select/index.tsx | 105 ++++++++++++++++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 src/components/Select/index.tsx diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx new file mode 100644 index 0000000..4667def --- /dev/null +++ b/src/components/Select/index.tsx @@ -0,0 +1,105 @@ +import { + FormControl, + MenuItem, + Select as SelectMui, + SelectChangeEvent, + styled, + SelectProps as SelectMuiProps, + MenuItemProps +} from '@mui/material' + +const SelectCustomized = styled(SelectMui)(() => ({ + backgroundColor: 'var(--primary-main)', + fontSize: '14px', + fontWeight: '500', + color: 'white', + ':hover': { + backgroundColor: 'var(--primary-light)' + }, + '& .MuiSelect-select:focus': { + backgroundColor: 'var(--primary-light)' + }, + '& .MuiSvgIcon-root': { + color: 'white' + }, + '& .MuiOutlinedInput-notchedOutline': { + border: 'none' + } +})) + +const MenuItemCustomized = styled(MenuItem)(() => ({ + marginInline: '5px', + borderRadius: '4px', + '&:hover': { + background: 'var(--primary-light)', + color: 'white' + }, + '&.Mui-selected': { + background: 'var(--primary-dark)', + color: 'white' + }, + '&.Mui-selected:hover': { + background: 'var(--primary-light)' + }, + '&.Mui-selected.Mui-focusVisible': { + background: 'var(--primary-light)', + color: 'white' + }, + '&.Mui-focusVisible': { + background: 'var(--primary-light)', + color: 'white' + }, + '& + *': { + marginTop: '5px' + } +})) + +interface SelectItemProps { + value: T + label: string +} + +interface SelectProps { + setValue: React.Dispatch> + options: SelectItemProps[] +} + +export function Select({ + setValue, + options +}: SelectProps) { + const handleChange = (event: SelectChangeEvent) => { + setValue(event.target.value as T) + } + + return ( + + + {options.map((o) => { + return ( + + {o.label} + + ) + })} + + + ) +} From f51afe3b677d418cdf4c4d29132f63f9ff1bd56b Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 5 Aug 2024 09:49:56 +0200 Subject: [PATCH 05/49] fix: some linter warnings and an error --- src/layouts/Main.tsx | 2 +- src/pages/nostr/index.tsx | 2 +- src/pages/settings/profile/index.tsx | 6 +++--- src/utils/nostr.ts | 2 +- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index c8f9f27..ee82da3 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -145,7 +145,7 @@ export const MainLayout = () => { }) .finally(() => setIsLoading(false)) } - }, [authState]) + }, [authState, dispatch]) if (isLoading) return diff --git a/src/pages/nostr/index.tsx b/src/pages/nostr/index.tsx index 562f184..bd99485 100644 --- a/src/pages/nostr/index.tsx +++ b/src/pages/nostr/index.tsx @@ -51,7 +51,7 @@ export const Nostr = () => { /** * Call login function when enter is pressed */ - const handleInputKeyDown = (event: any) => { + const handleInputKeyDown = (event: React.KeyboardEvent) => { if (event.code === 'Enter' || event.code === 'NumpadEnter') { event.preventDefault() login() diff --git a/src/pages/settings/profile/index.tsx b/src/pages/settings/profile/index.tsx index 7dd2d0d..7d6c923 100644 --- a/src/pages/settings/profile/index.tsx +++ b/src/pages/settings/profile/index.tsx @@ -12,7 +12,7 @@ import { useTheme } from '@mui/material' import { UnsignedEvent, nip19, kinds, VerifiedEvent, Event } from 'nostr-tools' -import { useEffect, useMemo, useRef, useState } from 'react' +import React, { useEffect, useMemo, useRef, useState } from 'react' import { Link, useParams } from 'react-router-dom' import { toast } from 'react-toastify' import { MetadataController, NostrController } from '../../../controllers' @@ -321,8 +321,8 @@ export const ProfileSettingsPage = () => { }} > { - event.target.src = getRoboHashPicture(npub!) + onError={(event: React.SyntheticEvent) => { + event.currentTarget.src = getRoboHashPicture(npub!) }} className={styles.img} src={getProfileImage(profileMetadata)} diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index ff38faa..e27601b 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -120,7 +120,7 @@ export const queryNip05 = async ( if (!match) throw new Error('Invalid nip05') // Destructure the match result, assigning default value '_' to name if not provided - const [_, name = '_', domain] = match + const [, name = '_', domain] = match // Construct the URL to query the NIP-05 data const url = `https://${domain}/.well-known/nostr.json?name=${name}` From 64c48835a4ea35ec53ab61a55cd917b69558fc17 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 12:42:21 +0200 Subject: [PATCH 06/49] refactor: sigit cards design and split components --- src/components/Container/index.tsx | 12 + src/components/DisplaySigit/index.tsx | 203 ++++++++ src/components/DisplaySigit/style.module.scss | 129 ++++++ src/components/DisplaySigner/index.tsx | 76 +++ .../DisplaySigner/style.module.scss | 23 + src/components/TooltipChild.tsx | 11 + src/components/UserAvatar/index.tsx | 28 +- src/components/UserAvatar/styles.module.scss | 2 +- src/components/UserAvatarGroup/index.tsx | 38 ++ .../UserAvatarGroup/style.module.scss | 19 + .../UserAvatarIconButton/style.module.scss | 2 +- src/hooks/useSigitMeta.tsx | 86 ++++ src/index.css | 9 + src/pages/home/index.tsx | 435 ++++++------------ src/pages/home/style.module.scss | 136 +++--- 15 files changed, 812 insertions(+), 397 deletions(-) create mode 100644 src/components/DisplaySigit/index.tsx create mode 100644 src/components/DisplaySigit/style.module.scss create mode 100644 src/components/DisplaySigner/index.tsx create mode 100644 src/components/DisplaySigner/style.module.scss create mode 100644 src/components/TooltipChild.tsx create mode 100644 src/components/UserAvatarGroup/index.tsx create mode 100644 src/components/UserAvatarGroup/style.module.scss create mode 100644 src/hooks/useSigitMeta.tsx diff --git a/src/components/Container/index.tsx b/src/components/Container/index.tsx index 57857b9..5b955bb 100644 --- a/src/components/Container/index.tsx +++ b/src/components/Container/index.tsx @@ -6,6 +6,18 @@ interface ContainerProps { className?: string } +/** + * Container component with pre-defined width, padding and margins for top level layout. + * + * **Important:** To avoid conflicts with `defaultStyle` (changing the `width`, `max-width`, `padding-inline`, and/or `margin-inline`) make sure to either: + * - When using *className* override, that styles are imported after the actual `Container` component + * ``` + * import { Container } from './components/Container' + * import styles from './style.module.scss' + * ``` + * - or add *!important* to imported styles + * - or override styles with *CSSProperties* object + */ export const Container = ({ style = {}, className = '', diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx new file mode 100644 index 0000000..2b49f2f --- /dev/null +++ b/src/components/DisplaySigit/index.tsx @@ -0,0 +1,203 @@ +import { Dispatch, SetStateAction, useEffect } from 'react' +import { Meta, ProfileMetadata } from '../../types' +import { SignedStatus, useSigitMeta } from '../../hooks/useSigitMeta' +import { Event, kinds } from 'nostr-tools' +import { Link } from 'react-router-dom' +import { MetadataController } from '../../controllers' +import { hexToNpub, npubToHex, shorten } from '../../utils' +import { appPublicRoutes, appPrivateRoutes } from '../../routes' +import { Button, Divider, Tooltip } from '@mui/material' +import { DisplaySigner } from '../DisplaySigner' +import { + faArchive, + faCalendar, + faCopy, + faEye, + faFilePdf +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { UserAvatar } from '../UserAvatar' +import { UserAvatarGroup } from '../UserAvatarGroup' + +import styles from './style.module.scss' +import { TooltipChild } from '../TooltipChild' + +type SigitProps = { + meta: Meta + profiles: { [key: string]: ProfileMetadata } + setProfiles: Dispatch> +} + +// function +// const ExtensionIconMapper = new Map([ +// [ +// 'pdf', +// <> +// PDF +// +// ], +// [ +// 'csv', +// <> +// CSV +// +// ] +// ]) + +export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { + const { + title, + createdAt, + submittedBy, + signers, + signedStatus + // fileExtensions + } = useSigitMeta(meta) + + useEffect(() => { + const hexKeys: string[] = [] + + if (submittedBy) { + hexKeys.push(npubToHex(submittedBy)!) + } + hexKeys.push(...signers.map((signer) => npubToHex(signer)!)) + + const metadataController = new MetadataController() + hexKeys.forEach((key) => { + if (!(key in profiles)) { + const handleMetadataEvent = (event: Event) => { + const metadataContent = + metadataController.extractProfileMetadataContent(event) + + if (metadataContent) + setProfiles((prev) => ({ + ...prev, + [key]: metadataContent + })) + } + + metadataController.on(key, (kind: number, event: Event) => { + if (kind === kinds.Metadata) { + handleMetadataEvent(event) + } + }) + + metadataController + .findMetadata(key) + .then((metadataEvent) => { + if (metadataEvent) handleMetadataEvent(metadataEvent) + }) + .catch((err) => { + console.error(`error occurred in finding metadata for: ${key}`, err) + }) + } + }) + }, [submittedBy, signers, profiles, setProfiles]) + + return ( + +

{title}

+
+ {submittedBy && + (function () { + const profile = profiles[submittedBy] + return ( + + + + + + ) + })()} + {submittedBy && signers.length ? ( + + ) : null} + + {signers.map((signer) => { + const pubkey = npubToHex(signer)! + const profile = profiles[pubkey] + + return ( + + + + + + ) + })} + +
+
+ + {createdAt} +
+
+ + {signedStatus} + + + {/* {fileExtensions.map(ext => + + return + )} */} + {'PDF'} + +
+
+ + + + + + +
+ + ) +} diff --git a/src/components/DisplaySigit/style.module.scss b/src/components/DisplaySigit/style.module.scss new file mode 100644 index 0000000..a615713 --- /dev/null +++ b/src/components/DisplaySigit/style.module.scss @@ -0,0 +1,129 @@ +@import '../../styles/colors.scss'; + +.itemWrapper { + position: relative; + overflow: hidden; + background-color: $overlay-background-color; + border-radius: 4px; + + display: flex; + padding: 15px; + gap: 15px; + flex-direction: column; + + cursor: pointer; + + &:only-child { + max-width: 600px; + } + + &::before { + content: ''; + position: absolute; + top: 0; + left: 0; + bottom: 0; + transition: opacity ease 0.2s; + opacity: 0; + width: 4px; + background-color: $primary-main; + pointer-events: none; + } + + &:hover, + &:focus-within { + &::before { + opacity: 1; + } + + .itemActions { + transform: translateX(0); + } + } +} + +.itemActions { + display: flex; + gap: 10px; + padding: 10px; + + > * { + flex-grow: 1; + } + + @media (hover: hover) { + transition: ease 0.2s; + transform: translateX(100%); + position: absolute; + right: 0; + top: 0; + bottom: 0; + + flex-direction: column; + background: $overlay-background-color; + border-left: solid 1px rgba(0, 0, 0, 0.1); + + &:hover, + &:focus-within { + transform: translateX(0); + } + } + + @media (hover: none) { + border-top: solid 1px rgba(0, 0, 0, 0.1); + padding-top: 10px; + margin-inline: -15px; + margin-bottom: -15px; + } +} + +.title { + font-size: 20px; +} + +.users { + margin-top: auto; + + display: flex; + grid-gap: 10px; +} + +.signers { + padding: 0 0 0 10px; + + > * { + transition: margin ease 0.2s; + margin: 0 0 0 -10px; + position: relative; + z-index: 1; + &:first-child { + margin-left: -10px !important; + } + } + + > *:hover, + > *:focus-within { + margin: 0 15px 0 5px; + z-index: 2; + } +} + +.details { + color: rgba(0, 0, 0, 0.3); + font-size: 14px; +} + +.iconLabel { + display: flex; + grid-gap: 10px; + align-items: center; +} + +.status { + display: flex; + grid-gap: 25px; +} + +a.itemWrapper:hover { + text-decoration: none; +} diff --git a/src/components/DisplaySigner/index.tsx b/src/components/DisplaySigner/index.tsx new file mode 100644 index 0000000..a7d3f5b --- /dev/null +++ b/src/components/DisplaySigner/index.tsx @@ -0,0 +1,76 @@ +import { Badge } from '@mui/material' +import { Event, verifyEvent } from 'nostr-tools' +import { useState, useEffect } from 'react' +import { Meta, ProfileMetadata } from '../../types' +import { hexToNpub, parseJson } from '../../utils' +import styles from './style.module.scss' +import { UserAvatar } from '../UserAvatar' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faCheck, faExclamation } from '@fortawesome/free-solid-svg-icons' + +enum SignStatus { + Signed = 'Signed', + Pending = 'Pending', + Invalid = 'Invalid Sign' +} + +type DisplaySignerProps = { + meta: Meta + profile: ProfileMetadata + pubkey: string +} + +export const DisplaySigner = ({ + meta, + profile, + pubkey +}: DisplaySignerProps) => { + const [signStatus, setSignedStatus] = useState() + + useEffect(() => { + const updateSignStatus = async () => { + const npub = hexToNpub(pubkey) + if (npub in meta.docSignatures) { + parseJson(meta.docSignatures[npub]) + .then((event) => { + const isValidSignature = verifyEvent(event) + if (isValidSignature) { + setSignedStatus(SignStatus.Signed) + } else { + setSignedStatus(SignStatus.Invalid) + } + }) + .catch((err) => { + console.log(`err in parsing the docSignatures for ${npub}:>> `, err) + setSignedStatus(SignStatus.Invalid) + }) + } else { + setSignedStatus(SignStatus.Pending) + } + } + + updateSignStatus() + }, [meta, pubkey]) + + return ( + + {signStatus === SignStatus.Signed && ( + + )} + {signStatus === SignStatus.Invalid && ( + + )} + + ) + } + > + + + ) +} diff --git a/src/components/DisplaySigner/style.module.scss b/src/components/DisplaySigner/style.module.scss new file mode 100644 index 0000000..fa62cab --- /dev/null +++ b/src/components/DisplaySigner/style.module.scss @@ -0,0 +1,23 @@ +@import '../../styles/colors.scss'; + +.statusBadge { + width: 22px; + height: 22px; + border-radius: 50%; + + color: white; + + display: flex; + align-items: center; + justify-content: center; + + font-size: 10px; + + background-color: $primary-main; +} + +.signer { + background-color: white; + border-radius: 50%; + z-index: 1; +} diff --git a/src/components/TooltipChild.tsx b/src/components/TooltipChild.tsx new file mode 100644 index 0000000..d70f916 --- /dev/null +++ b/src/components/TooltipChild.tsx @@ -0,0 +1,11 @@ +import { forwardRef, PropsWithChildren } from 'react' + +export const TooltipChild = forwardRef( + ({ children, ...rest }, ref) => { + return ( + + {children} + + ) + } +) diff --git a/src/components/UserAvatar/index.tsx b/src/components/UserAvatar/index.tsx index 65e993d..5382475 100644 --- a/src/components/UserAvatar/index.tsx +++ b/src/components/UserAvatar/index.tsx @@ -1,12 +1,11 @@ -import { useNavigate } from 'react-router-dom' import { getProfileRoute } from '../../routes' import styles from './styles.module.scss' -import React from 'react' import { AvatarIconButton } from '../UserAvatarIconButton' +import { Link } from 'react-router-dom' interface UserAvatarProps { - name: string + name?: string pubkey: string image?: string } @@ -16,27 +15,18 @@ interface UserAvatarProps { * Clicking will navigate to the user's profile. */ export const UserAvatar = ({ pubkey, name, image }: UserAvatarProps) => { - const navigate = useNavigate() - - const handleClick = (e: React.MouseEvent) => { - e.stopPropagation() - navigate(getProfileRoute(pubkey)) - } - return ( -
+ - {name ? ( - - ) : null} -
+ {name ? : null} + ) } diff --git a/src/components/UserAvatar/styles.module.scss b/src/components/UserAvatar/styles.module.scss index 6147819..fbe8cf5 100644 --- a/src/components/UserAvatar/styles.module.scss +++ b/src/components/UserAvatar/styles.module.scss @@ -2,7 +2,7 @@ display: flex; align-items: center; gap: 10px; - flex-grow: 1; + // flex-grow: 1; } .username { diff --git a/src/components/UserAvatarGroup/index.tsx b/src/components/UserAvatarGroup/index.tsx new file mode 100644 index 0000000..13f8b25 --- /dev/null +++ b/src/components/UserAvatarGroup/index.tsx @@ -0,0 +1,38 @@ +import { Children, PropsWithChildren } from 'react' + +import styles from './style.module.scss' + +interface UserAvatarGroupProps extends React.HTMLAttributes { + max: number + renderSurplus?: ((surplus: number) => React.ReactNode) | undefined +} + +const defaultSurplus = (surplus: number) => { + return +{surplus} +} + +/** + * Renders children with the `max` limit (including surplus if available). + * The children are wrapped with a `div` (accepts standard `HTMLDivElement` attributes) + * @param max The maximum number of children rendered in a div. + * @param renderSurplus Custom render for surplus children (accepts surplus number). + */ +export const UserAvatarGroup = ({ + max, + renderSurplus = defaultSurplus, + children, + ...rest +}: PropsWithChildren) => { + const total = Children.count(children) + const surplus = total - max + 1 + + const childrenArray = Children.toArray(children) + return ( +
+ {surplus > 1 + ? childrenArray.slice(0, surplus * -1).map((c) => c) + : children} + {surplus > 1 && renderSurplus(surplus)} +
+ ) +} diff --git a/src/components/UserAvatarGroup/style.module.scss b/src/components/UserAvatarGroup/style.module.scss new file mode 100644 index 0000000..9604202 --- /dev/null +++ b/src/components/UserAvatarGroup/style.module.scss @@ -0,0 +1,19 @@ +@import '../../styles/colors.scss'; + +.icon { + width: 40px; + height: 40px; + border-radius: 50%; + border-width: 2px; + overflow: hidden; + + display: inline-flex; + align-items: center; + justify-content: center; + + background: white; + color: rgba(0, 0, 0, 0.5); + font-weight: bold; + font-size: 14px; + border: solid 2px $primary-main; +} diff --git a/src/components/UserAvatarIconButton/style.module.scss b/src/components/UserAvatarIconButton/style.module.scss index f8213f0..57f2688 100644 --- a/src/components/UserAvatarIconButton/style.module.scss +++ b/src/components/UserAvatarIconButton/style.module.scss @@ -2,6 +2,6 @@ width: 40px; height: 40px; border-radius: 50%; - border-width: 3px; + border-width: 2px; overflow: hidden; } diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx new file mode 100644 index 0000000..b87f3b3 --- /dev/null +++ b/src/hooks/useSigitMeta.tsx @@ -0,0 +1,86 @@ +import { useEffect, useState } from 'react' +import { toast } from 'react-toastify' +import { CreateSignatureEventContent, Meta } from '../types' +import { parseJson, formatTimestamp } from '../utils' +import { Event } from 'nostr-tools' + +export enum SignedStatus { + Partial = 'In-Progress', + Complete = 'Completed' +} + +export const useSigitMeta = (meta: Meta) => { + const [title, setTitle] = useState() + const [createdAt, setCreatedAt] = useState('') + const [submittedBy, setSubmittedBy] = useState() + const [signers, setSigners] = useState<`npub1${string}`[]>([]) + const [signedStatus, setSignedStatus] = useState( + SignedStatus.Partial + ) + const [fileExtensions, setFileExtensions] = useState([]) + + useEffect(() => { + const extractInfo = async () => { + const createSignatureEvent = await parseJson( + meta.createSignature + ).catch((err) => { + console.log('err in parsing the createSignature event:>> ', err) + toast.error( + err.message || 'error occurred in parsing the create signature event' + ) + return null + }) + + if (!createSignatureEvent) return + + // created_at in nostr events are stored in seconds + // convert it to ms before formatting + setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000)) + + const createSignatureContent = + await parseJson( + createSignatureEvent.content + ).catch((err) => { + console.log( + `err in parsing the createSignature event's content :>> `, + err + ) + return null + }) + + if (!createSignatureContent) return + + const files = Object.keys(createSignatureContent.fileHashes) + const extensions = files.reduce((result: string[], file: string) => { + const extension = file.split('.').pop() + if (extension) { + result.push(extension) + } + return result + }, []) + + setTitle(createSignatureContent.title) + setSubmittedBy(createSignatureEvent.pubkey) + setSigners(createSignatureContent.signers) + setFileExtensions(extensions) + + const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] + const isCompletelySigned = createSignatureContent.signers.every( + (signer) => signedBy.includes(signer) + ) + if (isCompletelySigned) { + setSignedStatus(SignedStatus.Complete) + } + } + extractInfo() + }, [meta]) + + return { + title, + createdAt, + submittedBy, + signers, + signedStatus, + fileExtensions + } +} diff --git a/src/index.css b/src/index.css index 76373ff..7ee0eea 100644 --- a/src/index.css +++ b/src/index.css @@ -101,6 +101,15 @@ button:disabled { color: inherit !important; } +.line-clamp-2 { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; + text-overflow: ellipsis; + -webkit-line-clamp: 2; + line-clamp: 2; +} + .profile-image { width: 40px; height: 40px; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 4ad3c3b..07304b6 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,24 +1,42 @@ -import { CalendarMonth, Description, Upload } from '@mui/icons-material' -import { Box, Button, Tooltip, Typography } from '@mui/material' +import { Button, Divider, TextField, Tooltip } from '@mui/material' import JSZip from 'jszip' -import { Event, kinds, verifyEvent } from 'nostr-tools' -import { Dispatch, SetStateAction, useEffect, useRef, useState } from 'react' +import { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' -import { UserAvatar } from '../../components/UserAvatar' -import { MetadataController } from '../../controllers' import { useAppSelector } from '../../hooks' import { appPrivateRoutes, appPublicRoutes } from '../../routes' -import { CreateSignatureEventContent, Meta, ProfileMetadata } from '../../types' +import { Meta, ProfileMetadata } from '../../types' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { - formatTimestamp, - hexToNpub, - npubToHex, - parseJson, - shorten -} from '../../utils' -import styles from './style.module.scss' + faAdd, + faFilter, + faFilterCircleXmark, + faSearch +} from '@fortawesome/free-solid-svg-icons' +import { Select } from '../../components/Select' +import { DisplaySigit } from '../../components/DisplaySigit' + import { Container } from '../../components/Container' +import styles from './style.module.scss' + +// Unsupported Filter options are commented +const FILTERS = [ + 'Show all', + // 'Drafts', + 'In-progress', + 'Completed' + // 'Archived' +] as const +type Filter = (typeof FILTERS)[number] + +const SORT_BY = [ + { + label: 'Newest', + value: 'desc' + }, + { label: 'Oldest', value: 'asc' } +] as const +type Sort = (typeof SORT_BY)[number]['value'] export const HomePage = () => { const navigate = useNavigate() @@ -81,54 +99,112 @@ export const HomePage = () => { } } + const [filter, setFilter] = useState('Show all') + const [isFilterVisible, setIsFilterVisible] = useState(true) + const [sort, setSort] = useState('asc') + + console.log(filter, sort) + return ( - - - Sigits - - {/* This is for desktop view */} - - - - - {/* This is for mobile view */} - - - + + + + - - - + + + + + + +
+
Click or drag files to upload!
+
+
{sigits.map((sigit, index) => ( { setProfiles={setProfiles} /> ))} - +
) } - -type SigitProps = { - meta: Meta - profiles: { [key: string]: ProfileMetadata } - setProfiles: Dispatch> -} - -enum SignedStatus { - Partial = 'Partially Signed', - Complete = 'Completely Signed' -} - -const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { - const navigate = useNavigate() - - const [title, setTitle] = useState() - const [createdAt, setCreatedAt] = useState('') - const [submittedBy, setSubmittedBy] = useState() - const [signers, setSigners] = useState<`npub1${string}`[]>([]) - const [signedStatus, setSignedStatus] = useState( - SignedStatus.Partial - ) - - useEffect(() => { - const extractInfo = async () => { - const createSignatureEvent = await parseJson( - meta.createSignature - ).catch((err) => { - console.log('err in parsing the createSignature event:>> ', err) - toast.error( - err.message || 'error occurred in parsing the create signature event' - ) - return null - }) - - if (!createSignatureEvent) return - - // created_at in nostr events are stored in seconds - // convert it to ms before formatting - setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000)) - - const createSignatureContent = - await parseJson( - createSignatureEvent.content - ).catch((err) => { - console.log( - `err in parsing the createSignature event's content :>> `, - err - ) - return null - }) - - if (!createSignatureContent) return - - setTitle(createSignatureContent.title) - setSubmittedBy(createSignatureEvent.pubkey) - setSigners(createSignatureContent.signers) - - const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] - const isCompletelySigned = createSignatureContent.signers.every( - (signer) => signedBy.includes(signer) - ) - if (isCompletelySigned) { - setSignedStatus(SignedStatus.Complete) - } - } - extractInfo() - }, [meta]) - - useEffect(() => { - const hexKeys: string[] = [] - - if (submittedBy) { - hexKeys.push(npubToHex(submittedBy)!) - } - hexKeys.push(...signers.map((signer) => npubToHex(signer)!)) - - const metadataController = new MetadataController() - hexKeys.forEach((key) => { - if (!(key in profiles)) { - const handleMetadataEvent = (event: Event) => { - const metadataContent = - metadataController.extractProfileMetadataContent(event) - - if (metadataContent) - setProfiles((prev) => ({ - ...prev, - [key]: metadataContent - })) - } - - metadataController.on(key, (kind: number, event: Event) => { - if (kind === kinds.Metadata) { - handleMetadataEvent(event) - } - }) - - metadataController - .findMetadata(key) - .then((metadataEvent) => { - if (metadataEvent) handleMetadataEvent(metadataEvent) - }) - .catch((err) => { - console.error(`error occurred in finding metadata for: ${key}`, err) - }) - } - }) - }, [submittedBy, signers]) - - const handleNavigation = () => { - if (signedStatus === SignedStatus.Complete) { - navigate(appPublicRoutes.verify, { state: { meta } }) - } else { - navigate(appPrivateRoutes.sign, { state: { meta } }) - } - } - - return ( - - - - - {title} - - {submittedBy && - (function () { - const profile = profiles[submittedBy] - return ( - - ) - })()} - - - {createdAt} - - - - {signers.map((signer) => { - const pubkey = npubToHex(signer)! - const profile = profiles[pubkey] - - return ( - - ) - })} - - - ) -} - -enum SignStatus { - Signed = 'Signed', - Pending = 'Pending', - Invalid = 'Invalid Sign' -} - -type DisplaySignerProps = { - meta: Meta - profile: ProfileMetadata - pubkey: string -} - -const DisplaySigner = ({ meta, profile, pubkey }: DisplaySignerProps) => { - const [signStatus, setSignedStatus] = useState() - - useEffect(() => { - const updateSignStatus = async () => { - const npub = hexToNpub(pubkey) - if (npub in meta.docSignatures) { - parseJson(meta.docSignatures[npub]) - .then((event) => { - const isValidSignature = verifyEvent(event) - if (isValidSignature) { - setSignedStatus(SignStatus.Signed) - } else { - setSignedStatus(SignStatus.Invalid) - } - }) - .catch((err) => { - console.log(`err in parsing the docSignatures for ${npub}:>> `, err) - setSignedStatus(SignStatus.Invalid) - }) - } else { - setSignedStatus(SignStatus.Pending) - } - } - - updateSignStatus() - }, [meta, pubkey]) - - return ( - - - {signStatus} - - - - ) -} diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index 132097e..21aeb30 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -1,94 +1,78 @@ +@import '../../styles/colors.scss'; + .container { display: flex; flex-direction: column; gap: 25px; + container-type: inline-size; } .header { display: flex; + gap: 10px; + align-items: center; - .title { - color: var(--mui-palette-primary-light); - flex: 1; + @container (width < 610px) { + flex-wrap: wrap; + } +} + +.actionButtons { + display: flex; + justify-content: center; + align-items: center; + gap: 10px; + margin-left: auto; + padding: 1.5px 0; +} + +.search { + display: flex; + align-items: center; + justify-content: end; + + height: 34px; + overflow: hidden; + border-radius: 4px; + outline: solid 1px #dddddd; + background: white; + + &:focus-within { + outline-color: $primary-main; + } +} + +.dropzone { + background-color: $overlay-background-color; + height: 250px; + transition: padding ease 0.2s; + padding: 15px; + + &:hover { + padding: 10px; + + > div { + background: rgba(0, 0, 0, 0.15); + } } - .actionButtons { + > div { + transition: background-color ease 0.2s; + display: flex; + flex-direction: column; justify-content: center; align-items: center; - gap: 10px; + background: rgba(0, 0, 0, 0.1); + color: rgba(0, 0, 0, 0.25); + height: 100%; + border-radius: 2px; + border: dashed 3px rgba(0, 0, 0, 0.1); + font-size: 16px; } } .submissions { - display: flex; - flex-direction: column; - gap: 10px; - - .item { - display: flex; - gap: 10px; - background-color: #efeae6; - border-radius: 1rem; - cursor: pointer; - - .titleBox { - display: flex; - flex: 4; - flex-direction: column; - align-items: center; - overflow-wrap: anywhere; - gap: 10px; - padding: 10px; - background-color: #cdc8c499; - border-top-left-radius: inherit; - - .title { - display: flex; - justify-content: center; - align-items: center; - color: var(--mui-palette-primary-light); - font-size: 1.5rem; - - svg { - font-size: 1.5rem; - } - } - - .date { - display: flex; - justify-content: center; - align-items: center; - color: var(--mui-palette-primary-light); - font-size: 1rem; - - svg { - font-size: 1rem; - } - } - } - - .signers { - display: flex; - flex-direction: column; - flex: 6; - justify-content: center; - gap: 10px; - padding: 10px; - color: var(--mui-palette-primary-light); - - .signerItem { - display: flex; - justify-content: center; - align-items: center; - gap: 10px; - - .status { - border-radius: 2rem; - width: 100px; - text-align: center; - background-color: var(--mui-palette-info-light); - } - } - } - } + display: grid; + gap: 25px; + grid-template-columns: repeat(auto-fit, minmax(365px, 1fr)); } From 21caaa7009e49cd7cedc54104ab9438c330ed708 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 12:54:01 +0200 Subject: [PATCH 07/49] fix: sigit links and outline --- src/components/DisplaySigit/style.module.scss | 3 +++ src/components/UserAvatar/index.tsx | 6 +++++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/components/DisplaySigit/style.module.scss b/src/components/DisplaySigit/style.module.scss index a615713..1b029be 100644 --- a/src/components/DisplaySigit/style.module.scss +++ b/src/components/DisplaySigit/style.module.scss @@ -6,6 +6,8 @@ background-color: $overlay-background-color; border-radius: 4px; + outline: none !important; + display: flex; padding: 15px; gap: 15px; @@ -79,6 +81,7 @@ .title { font-size: 20px; + color: $text-color; } .users { diff --git a/src/components/UserAvatar/index.tsx b/src/components/UserAvatar/index.tsx index 5382475..9ae60ce 100644 --- a/src/components/UserAvatar/index.tsx +++ b/src/components/UserAvatar/index.tsx @@ -16,7 +16,11 @@ interface UserAvatarProps { */ export const UserAvatar = ({ pubkey, name, image }: UserAvatarProps) => { return ( - + Date: Tue, 6 Aug 2024 13:02:20 +0200 Subject: [PATCH 08/49] docs: add comment for TooltipChild --- src/components/TooltipChild.tsx | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/components/TooltipChild.tsx b/src/components/TooltipChild.tsx index d70f916..4b41b72 100644 --- a/src/components/TooltipChild.tsx +++ b/src/components/TooltipChild.tsx @@ -1,5 +1,10 @@ import { forwardRef, PropsWithChildren } from 'react' +/** + * Helper wrapper for custom child components when using `@mui/material/tooltips`. + * Mui Tooltip works out-the-box with other `@mui` components but when using custom they require ref. + * @source https://mui.com/material-ui/react-tooltip/#custom-child-element + */ export const TooltipChild = forwardRef( ({ children, ...rest }, ref) => { return ( From c3f60b1e643ff2e9ccf8f483a971b1970cf7d786 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 13:29:43 +0200 Subject: [PATCH 09/49] feat: extension icon label util component --- src/components/DisplaySigit/index.tsx | 41 +++++-------- src/components/getExtensionIconLabel.tsx | 76 ++++++++++++++++++++++++ src/pages/home/index.tsx | 2 - 3 files changed, 91 insertions(+), 28 deletions(-) create mode 100644 src/components/getExtensionIconLabel.tsx diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 2b49f2f..99637ad 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -13,7 +13,7 @@ import { faCalendar, faCopy, faEye, - faFilePdf + faFile } from '@fortawesome/free-solid-svg-icons' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { UserAvatar } from '../UserAvatar' @@ -21,6 +21,7 @@ import { UserAvatarGroup } from '../UserAvatarGroup' import styles from './style.module.scss' import { TooltipChild } from '../TooltipChild' +import { getExtensionIconLabel } from '../getExtensionIconLabel' type SigitProps = { meta: Meta @@ -28,30 +29,14 @@ type SigitProps = { setProfiles: Dispatch> } -// function -// const ExtensionIconMapper = new Map([ -// [ -// 'pdf', -// <> -// PDF -// -// ], -// [ -// 'csv', -// <> -// CSV -// -// ] -// ]) - export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { const { title, createdAt, submittedBy, signers, - signedStatus - // fileExtensions + signedStatus, + fileExtensions } = useSigitMeta(meta) useEffect(() => { @@ -164,13 +149,17 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { {signedStatus} - - {/* {fileExtensions.map(ext => - - return - )} */} - {'PDF'} - + {fileExtensions.length > 0 ? ( + + {fileExtensions.length > 1 ? ( + <> + Multiple File Types + + ) : ( + getExtensionIconLabel(fileExtensions[0]) + )} + + ) : null}
diff --git a/src/components/getExtensionIconLabel.tsx b/src/components/getExtensionIconLabel.tsx new file mode 100644 index 0000000..d745814 --- /dev/null +++ b/src/components/getExtensionIconLabel.tsx @@ -0,0 +1,76 @@ +import { + faFilePdf, + faFileExcel, + faFileWord, + faFilePowerpoint, + faFileZipper, + faFileCsv, + faFileLines, + faFileImage, + faFile, + IconDefinition +} from '@fortawesome/free-solid-svg-icons' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' + +export const getExtensionIconLabel = (extension: string) => { + let icon: IconDefinition + switch (extension.toLowerCase()) { + case 'pdf': + icon = faFilePdf + break + case 'json': + icon = faFilePdf + break + + case 'xslx': + case 'xsl': + icon = faFileExcel + break + + case 'doc': + case 'docx': + icon = faFileWord + break + + case 'ppt': + case 'pptx': + icon = faFilePowerpoint + break + + case 'zip': + case '7z': + case 'rar': + case 'tar': + case 'gz': + icon = faFileZipper + break + + case 'csv': + icon = faFileCsv + break + + case 'txt': + icon = faFileLines + break + + case 'png': + case 'jpg': + case 'jpeg': + case 'gif': + case 'svg': + case 'bmp': + case 'ico': + icon = faFileImage + break + + default: + icon = faFile + return + } + + return ( + <> + {extension.toUpperCase()} + + ) +} diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 07304b6..e765110 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -103,8 +103,6 @@ export const HomePage = () => { const [isFilterVisible, setIsFilterVisible] = useState(true) const [sort, setSort] = useState('asc') - console.log(filter, sort) - return (
From e4a7fa4892b05f648dfb988b898fb0658d2f22d4 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 13:38:33 +0200 Subject: [PATCH 10/49] fix: nested a links in card --- src/components/DisplaySigit/index.tsx | 21 ++++++++++--------- src/components/DisplaySigit/style.module.scss | 10 +++++---- 2 files changed, 17 insertions(+), 14 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index 99637ad..a4a7418 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -80,15 +80,16 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { }, [submittedBy, signers, profiles, setProfiles]) return ( - +
+

{title}

{submittedBy && @@ -187,6 +188,6 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
- +
) } diff --git a/src/components/DisplaySigit/style.module.scss b/src/components/DisplaySigit/style.module.scss index 1b029be..7544fc4 100644 --- a/src/components/DisplaySigit/style.module.scss +++ b/src/components/DisplaySigit/style.module.scss @@ -6,15 +6,11 @@ background-color: $overlay-background-color; border-radius: 4px; - outline: none !important; - display: flex; padding: 15px; gap: 15px; flex-direction: column; - cursor: pointer; - &:only-child { max-width: 600px; } @@ -44,6 +40,12 @@ } } +.insetLink { + position: absolute; + inset: 0; + outline: none; +} + .itemActions { display: flex; gap: 10px; From d0e3704ed6a2c08eede388e353bcf76880873411 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 14:07:41 +0200 Subject: [PATCH 11/49] fix: missing id/name on custom select input --- src/components/Select/index.tsx | 8 +++++++- src/pages/home/index.tsx | 2 ++ 2 files changed, 9 insertions(+), 1 deletion(-) diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index 4667def..c5c39e8 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -62,11 +62,15 @@ interface SelectItemProps { interface SelectProps { setValue: React.Dispatch> options: SelectItemProps[] + name?: string + id?: string } export function Select({ setValue, - options + options, + name, + id }: SelectProps) { const handleChange = (event: SelectChangeEvent) => { setValue(event.target.value as T) @@ -75,6 +79,8 @@ export function Select({ return ( { {isFilterVisible && ( <> { return { ...s } From 6b5a8a7375d528ce4f8e53dd595e1bbde27ea433 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 7 Aug 2024 11:10:32 +0200 Subject: [PATCH 12/49] fix(sigit): excel extension typo, more excel types --- src/components/getExtensionIconLabel.tsx | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/components/getExtensionIconLabel.tsx b/src/components/getExtensionIconLabel.tsx index d745814..e8a14eb 100644 --- a/src/components/getExtensionIconLabel.tsx +++ b/src/components/getExtensionIconLabel.tsx @@ -22,8 +22,10 @@ export const getExtensionIconLabel = (extension: string) => { icon = faFilePdf break - case 'xslx': - case 'xsl': + case 'xlsx': + case 'xls': + case 'xlsb': + case 'xlsm': icon = faFileExcel break From 272fcf93c64005b06249690815bb320ad66b9798 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 7 Aug 2024 11:11:07 +0200 Subject: [PATCH 13/49] fix: search bar scaling --- src/pages/home/style.module.scss | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index 21aeb30..bd0dc08 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -19,11 +19,11 @@ .actionButtons { display: flex; - justify-content: center; + justify-content: end; align-items: center; gap: 10px; - margin-left: auto; padding: 1.5px 0; + flex-grow: 1; } .search { @@ -37,6 +37,12 @@ outline: solid 1px #dddddd; background: white; + width: 100%; + + @container (width >= 610px) { + max-width: 246px; + } + &:focus-within { outline-color: $primary-main; } From becd02153c9cecb45041ab7e0b05b8a8cfbcb08a Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 7 Aug 2024 14:15:20 +0200 Subject: [PATCH 14/49] feat(dashboard): add sigits filtering, sorting, searching --- src/components/DisplaySigit/index.tsx | 16 ++- src/components/Select/index.tsx | 4 +- src/hooks/useSigitMeta.tsx | 137 +++++++++++-------- src/pages/home/index.tsx | 183 ++++++++++++++------------ src/pages/home/style.module.scss | 8 +- 5 files changed, 203 insertions(+), 145 deletions(-) diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index a4a7418..e930b37 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -1,10 +1,10 @@ import { Dispatch, SetStateAction, useEffect } from 'react' import { Meta, ProfileMetadata } from '../../types' -import { SignedStatus, useSigitMeta } from '../../hooks/useSigitMeta' +import { SigitInfo, SignedStatus } from '../../hooks/useSigitMeta' import { Event, kinds } from 'nostr-tools' import { Link } from 'react-router-dom' import { MetadataController } from '../../controllers' -import { hexToNpub, npubToHex, shorten } from '../../utils' +import { formatTimestamp, hexToNpub, npubToHex, shorten } from '../../utils' import { appPublicRoutes, appPrivateRoutes } from '../../routes' import { Button, Divider, Tooltip } from '@mui/material' import { DisplaySigner } from '../DisplaySigner' @@ -25,11 +25,17 @@ import { getExtensionIconLabel } from '../getExtensionIconLabel' type SigitProps = { meta: Meta + parsedMeta: SigitInfo profiles: { [key: string]: ProfileMetadata } setProfiles: Dispatch> } -export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { +export const DisplaySigit = ({ + meta, + parsedMeta, + profiles, + setProfiles +}: SigitProps) => { const { title, createdAt, @@ -37,7 +43,7 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => { signers, signedStatus, fileExtensions - } = useSigitMeta(meta) + } = parsedMeta useEffect(() => { const hexKeys: string[] = [] @@ -144,7 +150,7 @@ export const DisplaySigit = ({ meta, profiles, setProfiles }: SigitProps) => {
- {createdAt} + {createdAt ? formatTimestamp(createdAt) : null}
diff --git a/src/components/Select/index.tsx b/src/components/Select/index.tsx index c5c39e8..9901fa1 100644 --- a/src/components/Select/index.tsx +++ b/src/components/Select/index.tsx @@ -60,6 +60,7 @@ interface SelectItemProps { } interface SelectProps { + value: T setValue: React.Dispatch> options: SelectItemProps[] name?: string @@ -67,6 +68,7 @@ interface SelectProps { } export function Select({ + value, setValue, options, name, @@ -83,7 +85,7 @@ export function Select({ name={name} size="small" variant="outlined" - defaultValue={options[0].value as string} + value={value} onChange={handleChange} MenuProps={{ MenuListProps: { diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index b87f3b3..e798ef0 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -1,78 +1,109 @@ import { useEffect, useState } from 'react' import { toast } from 'react-toastify' import { CreateSignatureEventContent, Meta } from '../types' -import { parseJson, formatTimestamp } from '../utils' +import { parseJson } from '../utils' import { Event } from 'nostr-tools' +type npub = `npub1${string}` + export enum SignedStatus { Partial = 'In-Progress', Complete = 'Completed' } +export interface SigitInfo { + createdAt?: number + title?: string + submittedBy?: string + signers: npub[] + fileExtensions: string[] + signedStatus: SignedStatus +} + +export const extractSigitInfo = async (meta: Meta) => { + if (!meta?.createSignature) return + + const sigitInfo: SigitInfo = { + signers: [], + fileExtensions: [], + signedStatus: SignedStatus.Partial + } + + const createSignatureEvent = await parseJson( + meta.createSignature + ).catch((err) => { + console.log('err in parsing the createSignature event:>> ', err) + toast.error( + err.message || 'error occurred in parsing the create signature event' + ) + return + }) + + if (!createSignatureEvent) return + + // created_at in nostr events are stored in seconds + sigitInfo.createdAt = createSignatureEvent.created_at * 1000 + + const createSignatureContent = await parseJson( + createSignatureEvent.content + ).catch((err) => { + console.log(`err in parsing the createSignature event's content :>> `, err) + return + }) + + if (!createSignatureContent) return + + const files = Object.keys(createSignatureContent.fileHashes) + const extensions = files.reduce((result: string[], file: string) => { + const extension = file.split('.').pop() + if (extension) { + result.push(extension) + } + return result + }, []) + + const signedBy = Object.keys(meta.docSignatures) as npub[] + const isCompletelySigned = createSignatureContent.signers.every((signer) => + signedBy.includes(signer) + ) + + sigitInfo.title = createSignatureContent.title + sigitInfo.submittedBy = createSignatureEvent.pubkey + sigitInfo.signers = createSignatureContent.signers + sigitInfo.fileExtensions = extensions + + if (isCompletelySigned) { + sigitInfo.signedStatus = SignedStatus.Complete + } + + return sigitInfo +} + export const useSigitMeta = (meta: Meta) => { const [title, setTitle] = useState() - const [createdAt, setCreatedAt] = useState('') + const [createdAt, setCreatedAt] = useState() const [submittedBy, setSubmittedBy] = useState() - const [signers, setSigners] = useState<`npub1${string}`[]>([]) + const [signers, setSigners] = useState([]) const [signedStatus, setSignedStatus] = useState( SignedStatus.Partial ) const [fileExtensions, setFileExtensions] = useState([]) useEffect(() => { - const extractInfo = async () => { - const createSignatureEvent = await parseJson( - meta.createSignature - ).catch((err) => { - console.log('err in parsing the createSignature event:>> ', err) - toast.error( - err.message || 'error occurred in parsing the create signature event' - ) - return null - }) + const getSigitInfo = async () => { + const sigitInfo = await extractSigitInfo(meta) - if (!createSignatureEvent) return + if (!sigitInfo) return - // created_at in nostr events are stored in seconds - // convert it to ms before formatting - setCreatedAt(formatTimestamp(createSignatureEvent.created_at * 1000)) - - const createSignatureContent = - await parseJson( - createSignatureEvent.content - ).catch((err) => { - console.log( - `err in parsing the createSignature event's content :>> `, - err - ) - return null - }) - - if (!createSignatureContent) return - - const files = Object.keys(createSignatureContent.fileHashes) - const extensions = files.reduce((result: string[], file: string) => { - const extension = file.split('.').pop() - if (extension) { - result.push(extension) - } - return result - }, []) - - setTitle(createSignatureContent.title) - setSubmittedBy(createSignatureEvent.pubkey) - setSigners(createSignatureContent.signers) - setFileExtensions(extensions) - - const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] - const isCompletelySigned = createSignatureContent.signers.every( - (signer) => signedBy.includes(signer) - ) - if (isCompletelySigned) { - setSignedStatus(SignedStatus.Complete) - } + setTitle(sigitInfo.title) + setCreatedAt(sigitInfo.createdAt) + setSubmittedBy(sigitInfo.submittedBy) + setSigners(sigitInfo.signers) + setSignedStatus(sigitInfo.signedStatus) + setFileExtensions(sigitInfo.fileExtensions) } - extractInfo() + + getSigitInfo() }, [meta]) return { diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 37951f0..d728a78 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,4 +1,4 @@ -import { Button, Divider, TextField, Tooltip } from '@mui/material' +import { Button, TextField } from '@mui/material' import JSZip from 'jszip' import { useEffect, useRef, useState } from 'react' import { useNavigate } from 'react-router-dom' @@ -7,25 +7,25 @@ import { useAppSelector } from '../../hooks' import { appPrivateRoutes, appPublicRoutes } from '../../routes' import { Meta, ProfileMetadata } from '../../types' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { - faAdd, - faFilter, - faFilterCircleXmark, - faSearch -} from '@fortawesome/free-solid-svg-icons' +import { faSearch } from '@fortawesome/free-solid-svg-icons' import { Select } from '../../components/Select' import { DisplaySigit } from '../../components/DisplaySigit' import { Container } from '../../components/Container' import styles from './style.module.scss' +import { + extractSigitInfo, + SigitInfo, + SignedStatus +} from '../../hooks/useSigitMeta' // Unsupported Filter options are commented const FILTERS = [ 'Show all', // 'Drafts', 'In-progress', - 'Completed' - // 'Archived' + 'Completed', + 'Archived' ] as const type Filter = (typeof FILTERS)[number] @@ -41,7 +41,11 @@ type Sort = (typeof SORT_BY)[number]['value'] export const HomePage = () => { const navigate = useNavigate() const fileInputRef = useRef(null) - const [sigits, setSigits] = useState([]) + + const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) + const [parsedSigits, setParsedSigits] = useState<{ + [key: string]: SigitInfo + }>({}) const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>( {} ) @@ -49,7 +53,24 @@ export const HomePage = () => { useEffect(() => { if (usersAppData) { - setSigits(Object.values(usersAppData.sigits)) + const getSigitInfo = async () => { + for (const key in usersAppData.sigits) { + if (Object.prototype.hasOwnProperty.call(usersAppData.sigits, key)) { + const sigitInfo = await extractSigitInfo(usersAppData.sigits[key]) + if (sigitInfo) { + setParsedSigits((prev) => { + return { + ...prev, + [key]: sigitInfo + } + }) + } + } + } + } + + setSigits(usersAppData.sigits) + getSigitInfo() } }, [usersAppData]) @@ -99,42 +120,46 @@ export const HomePage = () => { } } + const [search, setSearch] = useState('') const [filter, setFilter] = useState('Show all') - const [isFilterVisible, setIsFilterVisible] = useState(true) - const [sort, setSort] = useState('asc') + const [sort, setSort] = useState('desc') return (
- {isFilterVisible && ( - <> - { - return { ...s } - })} - /> - - )} +
+ { + return { ...s } + })} + /> +
{ + setSearch(e.currentTarget.value) + }} size="small" sx={{ + width: '100%', fontSize: '16px', - height: '34px', borderTopLeftRadius: 'var(----mui-shape-borderRadius)', borderBottomLeftRadius: 'var(----mui-shape-borderRadius)', '& .MuiInputBase-root': { @@ -142,7 +167,7 @@ export const HomePage = () => { borderBottomRightRadius: 0 }, '& .MuiInputBase-input': { - padding: '5.5px 14px' + padding: '7px 14px' }, '& .MuiOutlinedInput-notchedOutline': { display: 'none' @@ -152,7 +177,7 @@ export const HomePage = () => {
- - - - - - - -
-
+
+
Click or drag files to upload!
- {sigits.map((sigit, index) => ( - - ))} + {Object.keys(parsedSigits) + .filter((s) => { + const { title, signedStatus } = parsedSigits[s] + const isMatch = title?.toLowerCase().includes(search.toLowerCase()) + switch (filter) { + case 'Completed': + return signedStatus === SignedStatus.Complete && isMatch + case 'In-progress': + return signedStatus === SignedStatus.Partial && isMatch + case 'Show all': + return isMatch + default: + console.error('Filter case not handled.') + } + }) + .sort((a, b) => { + const x = parsedSigits[a].createdAt ?? 0 + const y = parsedSigits[b].createdAt ?? 0 + return sort === 'desc' ? y - x : x - y + }) + .map((key) => ( + + ))}
) diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index bd0dc08..9a1b4c0 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -10,13 +10,17 @@ .header { display: flex; gap: 10px; - align-items: center; @container (width < 610px) { - flex-wrap: wrap; + flex-direction: column-reverse; } } +.filters { + display: flex; + gap: 10px; +} + .actionButtons { display: flex; justify-content: end; From 52fe523196986d651c7fa14224e2b4324683a6f4 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 15:52:14 +0300 Subject: [PATCH 15/49] fix: amends the relay look up method to return default relay set --- src/controllers/MetadataController.ts | 119 ++++++-------------------- src/utils/const.ts | 8 +- src/utils/relays.ts | 108 +++++++++++++++++++++++ 3 files changed, 141 insertions(+), 94 deletions(-) create mode 100644 src/utils/relays.ts diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index 88f8a75..e2f6852 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -16,6 +16,12 @@ import { queryNip05 } from '../utils' import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' import { EventEmitter } from 'tseep' import { localCache } from '../services' +import { + findRelayListAndUpdateCache, + findRelayListInCache, + getDefaultRelaySet, + getUserRelaySet +} from '../utils/relays.ts' export class MetadataController extends EventEmitter { private nostrController: NostrController @@ -144,100 +150,27 @@ export class MetadataController extends EventEmitter { return this.checkForMoreRecentMetadata(hexKey, null) } - public findRelayListMetadata = async (hexKey: string) => { - let relayEvent: Event | null = null + /** + * Based on the hexKey of the current user, this method attempts to retrieve a relay set. + * @func findRelayListInCache first checks if there is already an up-to-date + * relay list available in cache; if not - + * @func findRelayListAndUpdateCache checks if the relevant relay event is available from + * the purple pages relay; + * @func findRelayListAndUpdateCache will run again if the previous two calls return null and + * check if the relevant relay event can be obtained from 'most popular relays' + * If relay event is found, it will be saved in cache for future use + * @param hexKey of the current user + * @return RelaySet which will contain either relays extracted from the user Relay Event + * or a fallback RelaySet with Sigit's Relay + */ + public findRelayListMetadata = async (hexKey: string): Promise => { + const relayEvent = await findRelayListInCache(hexKey) + || await findRelayListAndUpdateCache([this.specialMetadataRelay], hexKey) + || await findRelayListAndUpdateCache(await this.nostrController.getMostPopularRelays(), hexKey) - // Attempt to retrieve the metadata event from the local cache - const cachedRelayListMetadataEvent = - await localCache.getUserRelayListMetadata(hexKey) - - if (cachedRelayListMetadataEvent) { - const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week - - // Check if the cached event is not older than one week - if (Date.now() - cachedRelayListMetadataEvent.cachedAt < oneWeekInMS) { - relayEvent = cachedRelayListMetadataEvent.event - } - } - - // define filter for relay list - const eventFilter: Filter = { - kinds: [kinds.RelayList], - authors: [hexKey] - } - - const pool = new SimplePool() - - // Try to get the relayList event from a special relay (wss://purplepag.es) - if (!relayEvent) { - relayEvent = await pool - .get([this.specialMetadataRelay], eventFilter) - .then((event) => { - if (event) { - // update the event in local cache - localCache.addUserRelayListMetadata(event) - } - return event - }) - .catch((err) => { - console.error(err) - return null - }) - } - - if (!relayEvent) { - // If no valid relayList event is found from the special relay, get the most popular relays - const mostPopularRelays = - await this.nostrController.getMostPopularRelays() - - // Query the most popular relays for relayList event - relayEvent = await pool - .get(mostPopularRelays, eventFilter) - .then((event) => { - if (event) { - // update the event in local cache - localCache.addUserRelayListMetadata(event) - } - return event - }) - .catch((err) => { - console.error(err) - return null - }) - } - - if (relayEvent) { - const relaySet: RelaySet = { - read: [], - write: [] - } - - // a list of r tags with relay URIs and a read or write marker. - const relayTags = relayEvent.tags.filter((tag) => tag[0] === 'r') - - // Relays marked as read / write are called READ / WRITE relays, respectively - relayTags.forEach((tag) => { - if (tag.length >= 3) { - const marker = tag[2] - - if (marker === 'read') { - relaySet.read.push(tag[1]) - } else if (marker === 'write') { - relaySet.write.push(tag[1]) - } - } - - // If the marker is omitted, the relay is used for both purposes - if (tag.length === 2) { - relaySet.read.push(tag[1]) - relaySet.write.push(tag[1]) - } - }) - - return relaySet - } - - throw new Error('No relay list metadata found.') + return relayEvent + ? getUserRelaySet(relayEvent.tags) + : getDefaultRelaySet() } public extractProfileMetadataContent = (event: Event) => { diff --git a/src/utils/const.ts b/src/utils/const.ts index 4f8c233..aed1807 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -3,4 +3,10 @@ import { MarkType } from '../types/drawing.ts' export const EMPTY: string = '' export const MARK_TYPE_TRANSLATION: { [key: string]: string } = { [MarkType.FULLNAME.valueOf()]: 'Full Name' -} \ No newline at end of file +} +/** + * Number of milliseconds in one week. + * Calc based on: 7 * 24 * 60 * 60 * 1000 + */ +export const ONE_WEEK_IN_MS: number = 604800000 +export const SIGIT_RELAY: string = 'wss://relay.sigit.io' \ No newline at end of file diff --git a/src/utils/relays.ts b/src/utils/relays.ts new file mode 100644 index 0000000..676a59a --- /dev/null +++ b/src/utils/relays.ts @@ -0,0 +1,108 @@ +import { Filter, SimplePool } from 'nostr-tools' +import { RelayList } from 'nostr-tools/kinds' +import { Event } from 'nostr-tools' +import { localCache } from '../services' +import { ONE_WEEK_IN_MS, SIGIT_RELAY } from './const.ts' +import { RelayMap, RelaySet } from '../types' + +const READ_MARKER = "read" +const WRITE_MARKET = "write" + +/** + * Attempts to find a relay list from the provided lookUpRelays. + * If the relay list is found, it will be added to the user relay list metadata. + * @param lookUpRelays + * @param hexKey + * @return found relay list or null + */ +const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: string): Promise => { + try { + const eventFilter: Filter = { + kinds: [RelayList], + authors: [hexKey] + } + const pool = new SimplePool() + const event = await pool.get(lookUpRelays, eventFilter) + if (event) { + await localCache.addUserRelayListMetadata(event) + } + return event + } catch (error) { + console.error(error) + return null + } +} + +/** + * Attempts to find a relay list in cache. If it is present, it will check that the cached event is not + * older than one week. + * @param hexKey + * @return RelayList event if it's not older than a week; otherwise null + */ +const findRelayListInCache = async (hexKey: string): Promise => { + try { + // Attempt to retrieve the metadata event from the local cache + const cachedRelayListMetadataEvent = await localCache.getUserRelayListMetadata(hexKey) + + // Check if the cached event is not older than one week + if (cachedRelayListMetadataEvent && isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)) { + return cachedRelayListMetadataEvent.event + } + + return null + } catch (error) { + console.error(error) + return null + } +} + +/** + * Transforms a list of relay tags from a Nostr Event to a RelaySet. + * @param tags + */ +const getUserRelaySet = (tags: string[][]): RelaySet => { + return tags + .filter(isRelayTag) + .reduce(toRelaySet, getDefaultRelaySet()) +} + +const getDefaultRelaySet = (): RelaySet => ({ + read: [SIGIT_RELAY], + write: [SIGIT_RELAY] +}) + +const getDefaultRelayMap = (): RelayMap => ({ + [SIGIT_RELAY]: { write: true, read: true } +}) + +const isOlderThanOneWeek = (cachedAt: number) => { + return Date.now() - cachedAt < ONE_WEEK_IN_MS +} + +const isRelayTag = (tag: string[]): boolean => tag[0] === 'r' + +const toRelaySet = (obj: RelaySet, tag: string[]): RelaySet => { + if (tag.length >= 3) { + const marker = tag[2] + + if (marker === READ_MARKER) { + obj.read.push(tag[1]) + } else if (marker === WRITE_MARKET) { + obj.write.push(tag[1]) + } + } + if (tag.length === 2) { + obj.read.push(tag[1]) + obj.write.push(tag[1]) + } + + return obj +} + +export { + findRelayListAndUpdateCache, + findRelayListInCache, + getUserRelaySet, + getDefaultRelaySet, + getDefaultRelayMap, +} \ No newline at end of file From 2355da02d2c760f94509bcb4e375e75b61a2ce9f Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 16:04:56 +0300 Subject: [PATCH 16/49] fix: amends RelayMap to return a default sigit relay when no other relays are found --- src/controllers/NostrController.ts | 5 +++-- src/pages/settings/relays/index.tsx | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index 13787e5..fa558de 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -44,6 +44,7 @@ import { getNsecBunkerDelegatedKey, verifySignedEvent } from '../utils' +import { getDefaultRelayMap } from '../utils/relays.ts' export class NostrController extends EventEmitter { private static instance: NostrController @@ -650,7 +651,7 @@ export class NostrController extends EventEmitter { */ getRelayMap = async ( npub: string - ): Promise<{ map: RelayMap; mapUpdated: number }> => { + ): Promise<{ map: RelayMap; mapUpdated?: number }> => { const mostPopularRelays = await this.getMostPopularRelays() const pool = new SimplePool() @@ -691,7 +692,7 @@ export class NostrController extends EventEmitter { return Promise.resolve({ map: relaysMap, mapUpdated: event.created_at }) } else { - return Promise.reject('User relays were not found.') + return Promise.resolve({ map: getDefaultRelayMap() }) } } diff --git a/src/pages/settings/relays/index.tsx b/src/pages/settings/relays/index.tsx index 1894fc4..b06908a 100644 --- a/src/pages/settings/relays/index.tsx +++ b/src/pages/settings/relays/index.tsx @@ -91,7 +91,7 @@ export const RelaysPage = () => { if (isMounted) { if ( !relaysState?.mapUpdated || - newRelayMap.mapUpdated > relaysState?.mapUpdated + newRelayMap?.mapUpdated !== undefined && (newRelayMap?.mapUpdated > relaysState?.mapUpdated) ) { if ( !relaysState?.map || From 70f625ffd128f132cc3f92a4465b7e4d73a9ed97 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 16:01:54 +0200 Subject: [PATCH 17/49] feat(ci): add git hooks --- .git-hooks/commit-msg | 18 ++++++++++++++++++ .git-hooks/pre-commit | 11 +++++++++++ package.json | 3 ++- 3 files changed, 31 insertions(+), 1 deletion(-) create mode 100644 .git-hooks/commit-msg create mode 100644 .git-hooks/pre-commit diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg new file mode 100644 index 0000000..d12ec2f --- /dev/null +++ b/.git-hooks/commit-msg @@ -0,0 +1,18 @@ +#!/bin/sh +RED="\033[1;31m" +GREEN="\033[1;32m" + +# Get the commit message (the parameter we're given is just the path to the +# temporary file which holds the message). +commit_message=$(cat "$1") + +if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-]+\))?!?: .+$") then + echo "${GREEN} ✔ Commit message meets Conventional Commit standards" + exit 0 +fi + +echo "${RED}❌ Commit message does not meet the Conventional Commit standard!" +echo "An example of a valid message is:" +echo " feat(login): add the 'remember me' button" +echo "ℹ More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary" +exit 1 \ No newline at end of file diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit new file mode 100644 index 0000000..b3dfacf --- /dev/null +++ b/.git-hooks/pre-commit @@ -0,0 +1,11 @@ +#!/bin/sh + +# Avoid commits to the master branch +BRANCH=`git rev-parse --abbrev-ref HEAD` +REGEX="^(master|main|staging|development)$" + +if [[ "$BRANCH" =~ $REGEX ]]; then + echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?" + echo "If so, commit with -n to bypass the pre-commit hook." + exit 1 +fi \ No newline at end of file diff --git a/package.json b/package.json index 0041c32..62771d5 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,8 @@ "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "formatter:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", - "preview": "vite preview" + "preview": "vite preview", + "preinstall": "git config core.hooksPath .git-hooks" }, "dependencies": { "@emotion/react": "11.11.4", From ea7fde4b38234128920de116609ad05e367bf9dd Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 16:14:48 +0200 Subject: [PATCH 18/49] fix(ci): fix hook colors --- .git-hooks/commit-msg | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg index d12ec2f..c4c6ce6 100644 --- a/.git-hooks/commit-msg +++ b/.git-hooks/commit-msg @@ -1,17 +1,18 @@ #!/bin/sh -RED="\033[1;31m" -GREEN="\033[1;32m" - # Get the commit message (the parameter we're given is just the path to the # temporary file which holds the message). commit_message=$(cat "$1") if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-]+\))?!?: .+$") then - echo "${GREEN} ✔ Commit message meets Conventional Commit standards" + tput setaf 2; + echo -e "${GREEN} ✔ Commit message meets Conventional Commit standards" + tput sgr0; exit 0 fi -echo "${RED}❌ Commit message does not meet the Conventional Commit standard!" +tput setaf 1; +echo -e "${RED}❌ Commit message does not meet the Conventional Commit standard!" +tput sgr0; echo "An example of a valid message is:" echo " feat(login): add the 'remember me' button" echo "ℹ More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary" From 7a0e1c90528336260a3b13a28a822db11a892f73 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 16:16:20 +0200 Subject: [PATCH 19/49] chore(ci): add audit, lint and prettier checks to staging workflow --- .gitea/workflows/release-staging.yaml | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 793c70c..5fbc817 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -17,9 +17,18 @@ jobs: with: node-version: 18 + - name: Audit + run: npm audit + - name: Install Dependencies run: npm ci + - name: Lint check + run: npm run lint + + - name: Formatter check + run: npm run formatter:check + - name: Create .env File run: echo "VITE_MOST_POPULAR_RELAYS=${{ vars.VITE_MOST_POPULAR_RELAYS }}" > .env @@ -29,6 +38,6 @@ jobs: - name: Release Build run: | npm -g install cloudron-surfer - surfer config --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io + surfer config --token ${{ secrets.STAGING_CLOUDRON_SURFER_TOKEN }} --server staging.sigit.io surfer put dist/* / --all -d - surfer put dist/.well-known / --all + surfer put dist/.well-known / --all From 9ca75c7a52d862e1cfaf2eee1445568276de0823 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 16:17:24 +0200 Subject: [PATCH 20/49] chore(ci): add licenseChecker and check in staging workflow --- .eslintrc.cjs | 2 +- licenseChecker.cjs | 28 ++++ package-lock.json | 376 ++++++++++++++++++++++++++++++++++++++++++++- package.json | 4 +- 4 files changed, 400 insertions(+), 10 deletions(-) create mode 100644 licenseChecker.cjs diff --git a/.eslintrc.cjs b/.eslintrc.cjs index adc902d..43a751c 100644 --- a/.eslintrc.cjs +++ b/.eslintrc.cjs @@ -6,7 +6,7 @@ module.exports = { 'plugin:@typescript-eslint/recommended', 'plugin:react-hooks/recommended' ], - ignorePatterns: ['dist', '.eslintrc.cjs'], + ignorePatterns: ['dist', '.eslintrc.cjs', 'licenseChecker.cjs'], parser: '@typescript-eslint/parser', plugins: ['react-refresh'], rules: { diff --git a/licenseChecker.cjs b/licenseChecker.cjs new file mode 100644 index 0000000..9baf89b --- /dev/null +++ b/licenseChecker.cjs @@ -0,0 +1,28 @@ +const process = require('node:process') +const licenseChecker = require('license-checker') + +const check = (cwd) => { + return new Promise((resolve, reject) => { + licenseChecker.init( + { + production: true, + start: cwd, + excludePrivatePackages: true, + onlyAllow: + 'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;', + excludePackages: '' + }, + (error, json) => { + if (error) { + reject(error) + } else { + resolve(json) + } + } + ) + }) +} + +check(process.cwd(), true) + .then(() => console.log('All packages are licensed properly')) + .catch((err) => console.log('license checker err', err)) diff --git a/package-lock.json b/package-lock.json index 366bbd3..bf51dd9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,6 +7,7 @@ "": { "name": "web", "version": "0.0.0", + "hasInstallScript": true, "dependencies": { "@emotion/react": "11.11.4", "@emotion/styled": "11.11.0", @@ -55,6 +56,7 @@ "eslint": "^8.56.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "license-checker": "^25.0.1", "prettier": "3.2.5", "ts-css-modules-vite-plugin": "1.0.20", "typescript": "^5.2.2", @@ -2199,13 +2201,14 @@ "dev": true }, "node_modules/@types/node": { - "version": "20.11.20", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.20.tgz", - "integrity": "sha512-7/rR21OS+fq8IyHTgtLkDK949uzsa6n8BkziAKtPVpugIkO6D+/ooXMvzXxDnZrmtXVfjb1bKQafYpb8s89LOg==", + "version": "22.1.0", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.1.0.tgz", + "integrity": "sha512-AOmuRF0R2/5j1knA3c6G3HOk523Ga+l+ZXltX8SF1+5oqcXijjfTd8fY3XRZqSihEu9XhtQnKYLmkFaoxgsJHw==", "devOptional": true, + "license": "MIT", "peer": true, "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.13.0" } }, "node_modules/@types/parse-json": { @@ -2490,7 +2493,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", - "optional": true + "devOptional": true }, "node_modules/acorn": { "version": "8.11.3", @@ -2629,6 +2632,16 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/array-find-index": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-find-index/-/array-find-index-1.0.2.tgz", + "integrity": "sha512-M1HQyIXcBGtVywBt8WVdim+lrNaK7VHp99Qt5pSNziXznKHViIBbXWtfRTpEFpF/c4FdfxNAsCCwPp5phBYJtw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/array-union": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", @@ -2638,6 +2651,13 @@ "node": ">=8" } }, + "node_modules/asap": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", + "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", + "dev": true, + "license": "MIT" + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3010,6 +3030,17 @@ } } }, + "node_modules/debuglog": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/debuglog/-/debuglog-1.0.1.tgz", + "integrity": "sha512-syBZ+rnAK3EgMsH2aYEOLUW7mZSY9Gb+0wUMCFsZvcmiz+HigA0LOcq/HoQqVuGG+EKykunc7QG2bzrponfaSw==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/decompress-response": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", @@ -3051,6 +3082,17 @@ "node": ">=8" } }, + "node_modules/dezalgo": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", + "integrity": "sha512-rXSP0bf+5n0Qonsb+SVVfNfIsimO4HEtmnIpPHY8Q1UCzKlQrDMfdobr8nJOOsRgWCyMRqeSBQzmWUMq7zvVig==", + "dev": true, + "license": "ISC", + "dependencies": { + "asap": "^2.0.0", + "wrappy": "1" + } + }, "node_modules/diff": { "version": "4.0.2", "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", @@ -3909,6 +3951,13 @@ "integrity": "sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==", "dev": true }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "dev": true, + "license": "ISC" + }, "node_modules/graphemer": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", @@ -3953,6 +4002,13 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz", "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==" }, + "node_modules/hosted-git-info": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", + "dev": true, + "license": "ISC" + }, "node_modules/https-proxy-agent": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", @@ -4228,6 +4284,75 @@ "node": ">= 0.8.0" } }, + "node_modules/license-checker": { + "version": "25.0.1", + "resolved": "https://registry.npmjs.org/license-checker/-/license-checker-25.0.1.tgz", + "integrity": "sha512-mET5AIwl7MR2IAKYYoVBBpV0OnkKQ1xGj2IMMeEFIs42QAkEVjRtFZGWmQ28WeU7MP779iAgOaOy93Mn44mn6g==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "chalk": "^2.4.1", + "debug": "^3.1.0", + "mkdirp": "^0.5.1", + "nopt": "^4.0.1", + "read-installed": "~4.0.3", + "semver": "^5.5.0", + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-satisfies": "^4.0.0", + "treeify": "^1.1.0" + }, + "bin": { + "license-checker": "bin/license-checker" + } + }, + "node_modules/license-checker/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/license-checker/node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/license-checker/node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, + "node_modules/license-checker/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/lie": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/lie/-/lie-3.3.0.tgz", @@ -4393,6 +4518,16 @@ "url": "https://github.com/sponsors/isaacs" } }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minipass": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", @@ -4572,6 +4707,29 @@ "node": ">=6" } }, + "node_modules/normalize-package-data": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", + "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==", + "dev": true, + "license": "BSD-2-Clause", + "dependencies": { + "hosted-git-info": "^2.1.4", + "resolve": "^1.10.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "node_modules/normalize-package-data/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -4622,6 +4780,13 @@ "integrity": "sha512-78BTryCLcLYv96ONU8Ws3Q1JzjlAt+43pWQhIl86xZmWeegYCNLPml7yQ+gG3vR6V5h4XGj+TxO+SS5dsThQIA==", "optional": true }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true, + "license": "ISC" + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -4669,6 +4834,38 @@ "node": ">= 0.8.0" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha512-B5JU3cabzk8c67mRRd3ECmROafjYMXbuzlwtqdM8IbS8ktlTix8aFGb2bAGKrSRIlnfKwovGUUr72JUPyOb6kQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -5116,6 +5313,49 @@ "react-dom": ">=16.6.0" } }, + "node_modules/read-installed": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/read-installed/-/read-installed-4.0.3.tgz", + "integrity": "sha512-O03wg/IYuV/VtnK2h/KXEt9VIbMUFbk3ERG0Iu4FhLZw0EP0T9znqrYDGn6ncbEsXUFaUjiVAWXHzxwt3lhRPQ==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "read-package-json": "^2.0.0", + "readdir-scoped-modules": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "slide": "~1.1.3", + "util-extend": "^1.0.1" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.2" + } + }, + "node_modules/read-installed/node_modules/semver": { + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz", + "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/read-package-json": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/read-package-json/-/read-package-json-2.1.2.tgz", + "integrity": "sha512-D1KmuLQr6ZSJS0tW8hf3WGpRlwszJOXZ3E8Yd/DNRaM5d+1wVRZdHlpGBLAuovjr28LbWvjpWkBHMxpRGGjzNA==", + "deprecated": "This package is no longer supported. Please use @npmcli/package-json instead.", + "dev": true, + "license": "ISC", + "dependencies": { + "glob": "^7.1.1", + "json-parse-even-better-errors": "^2.3.0", + "normalize-package-data": "^2.0.0", + "npm-normalize-package-bin": "^1.0.0" + } + }, "node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", @@ -5130,6 +5370,20 @@ "util-deprecate": "~1.0.1" } }, + "node_modules/readdir-scoped-modules": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/readdir-scoped-modules/-/readdir-scoped-modules-1.1.0.tgz", + "integrity": "sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "dev": true, + "license": "ISC", + "dependencies": { + "debuglog": "^1.0.1", + "dezalgo": "^1.0.0", + "graceful-fs": "^4.1.2", + "once": "^1.3.0" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -5413,6 +5667,16 @@ "node": ">=8" } }, + "node_modules/slide": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", + "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", + "dev": true, + "license": "ISC", + "engines": { + "node": "*" + } + }, "node_modules/source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -5430,6 +5694,73 @@ "node": ">=0.10.0" } }, + "node_modules/spdx-compare": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/spdx-compare/-/spdx-compare-1.0.0.tgz", + "integrity": "sha512-C1mDZOX0hnu0ep9dfmuoi03+eOdDoz2yvK79RxbcrVEG1NO1Ph35yW102DHWKN4pk80nwCgeMmSY5L25VE4D9A==", + "dev": true, + "license": "MIT", + "dependencies": { + "array-find-index": "^1.0.2", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, + "node_modules/spdx-correct": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz", + "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-exceptions": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz", + "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==", + "dev": true, + "license": "CC-BY-3.0" + }, + "node_modules/spdx-expression-parse": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz", + "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "node_modules/spdx-license-ids": { + "version": "3.0.18", + "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.18.tgz", + "integrity": "sha512-xxRs31BqRYHwiMzudOrpSiHtZ8i/GeionCBDSilhYRj+9gIcI8wCZTlXZKu9vZIVqViP3dcp9qE5G6AlIaD+TQ==", + "dev": true, + "license": "CC0-1.0" + }, + "node_modules/spdx-ranges": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/spdx-ranges/-/spdx-ranges-2.1.1.tgz", + "integrity": "sha512-mcdpQFV7UDAgLpXEE/jOMqvK4LBoO0uTQg0uvXUewmEFhpiZx5yJSZITHB8w1ZahKdhfZqP5GPEOKLyEq5p8XA==", + "dev": true, + "license": "(MIT AND CC-BY-3.0)" + }, + "node_modules/spdx-satisfies": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/spdx-satisfies/-/spdx-satisfies-4.0.1.tgz", + "integrity": "sha512-WVzZ/cXAzoNmjCWiEluEA3BjHp5tiUmmhn9MK+X0tBbR9sOqtC6UQwmgCNrAIZvNlMuBUYAaHYfb2oqlF9SwKA==", + "dev": true, + "license": "MIT", + "dependencies": { + "spdx-compare": "^1.0.0", + "spdx-expression-parse": "^3.0.0", + "spdx-ranges": "^2.0.0" + } + }, "node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", @@ -5558,6 +5889,16 @@ "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", "optional": true }, + "node_modules/treeify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/treeify/-/treeify-1.1.0.tgz", + "integrity": "sha512-1m4RA7xVAJrSGrrXGs0L3YTwyvBs2S8PbRHaLZAkFw7JR8oIFwYtysxlBZhYIa7xSyiYJKZ3iGrrk55cGA3i9A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, "node_modules/ts-api-utils": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.2.1.tgz", @@ -5716,10 +6057,11 @@ "integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==" }, "node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.13.0.tgz", + "integrity": "sha512-xtFJHudx8S2DSoujjMd1WeWvn7KKWFRESZTMeL1RptAYERu29D6jphMjjY+vn96jvN3kVPDNxU/E13VTaXj6jg==", "devOptional": true, + "license": "MIT", "peer": true }, "node_modules/update-browserslist-db": { @@ -5794,12 +6136,30 @@ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, + "node_modules/util-extend": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/util-extend/-/util-extend-1.0.3.tgz", + "integrity": "sha512-mLs5zAK+ctllYBj+iAQvlDCwoxU/WDOUaJkcFudeiAX6OajC6BKXJUa9a+tbtkC11dz2Ufb7h0lyvIOVn4LADA==", + "dev": true, + "license": "MIT" + }, "node_modules/v8-compile-cache-lib": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", "integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==", "dev": true }, + "node_modules/validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, "node_modules/vite": { "version": "5.2.12", "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.12.tgz", diff --git a/package.json b/package.json index 62771d5..7d56991 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "formatter:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "preview": "vite preview", - "preinstall": "git config core.hooksPath .git-hooks" + "preinstall": "git config core.hooksPath .git-hooks", + "license-checker": "node licenseChecker.cjs" }, "dependencies": { "@emotion/react": "11.11.4", @@ -62,6 +63,7 @@ "eslint": "^8.56.0", "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", + "license-checker": "^25.0.1", "prettier": "3.2.5", "ts-css-modules-vite-plugin": "1.0.20", "typescript": "^5.2.2", From 4af578133c17bbb5f3c4f60873dd1ae37e679e18 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 16:17:57 +0200 Subject: [PATCH 21/49] fix(ci): add license check in staging workflow --- .gitea/workflows/release-staging.yaml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitea/workflows/release-staging.yaml b/.gitea/workflows/release-staging.yaml index 5fbc817..04f4fd7 100644 --- a/.gitea/workflows/release-staging.yaml +++ b/.gitea/workflows/release-staging.yaml @@ -23,6 +23,9 @@ jobs: - name: Install Dependencies run: npm ci + - name: License check + run: npm run license-checker + - name: Lint check run: npm run lint From 5290dda52a76093b0e99ecbfc340fa6fae99f728 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 16:46:54 +0200 Subject: [PATCH 22/49] feat(ci): add open pr workflow --- .gitea/workflows/staging-pull-request.yaml | 34 ++++++++++++++++++++++ 1 file changed, 34 insertions(+) create mode 100644 .gitea/workflows/staging-pull-request.yaml diff --git a/.gitea/workflows/staging-pull-request.yaml b/.gitea/workflows/staging-pull-request.yaml new file mode 100644 index 0000000..c541cff --- /dev/null +++ b/.gitea/workflows/staging-pull-request.yaml @@ -0,0 +1,34 @@ +name: Open PR on Staging +on: + pull_request: + types: [opened, edited] + branches: + - staging + +jobs: + audit_and_check: + runs-on: ubuntu-latest + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v2 + with: + node-version: 18 + + - name: Audit + run: npm audit + + - name: Install Dependencies + run: npm ci + + - name: License check + run: npm run license-checker + + - name: Lint check + run: npm run lint + + - name: Formatter check + run: npm run formatter:check From 84d13793ffd5fab5c2f0148fb7825c21f23431a0 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 17:02:52 +0200 Subject: [PATCH 23/49] feat(ci): add lint-staged in pre-commit --- .git-hooks/pre-commit | 4 +- package-lock.json | 718 +++++++++++++++++++++++++++++++++++++++++- package.json | 10 +- 3 files changed, 723 insertions(+), 9 deletions(-) diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit index b3dfacf..bd9faed 100644 --- a/.git-hooks/pre-commit +++ b/.git-hooks/pre-commit @@ -8,4 +8,6 @@ if [[ "$BRANCH" =~ $REGEX ]]; then echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?" echo "If so, commit with -n to bypass the pre-commit hook." exit 1 -fi \ No newline at end of file +else + npm run lint-staged +fi diff --git a/package-lock.json b/package-lock.json index bf51dd9..7c16799 100644 --- a/package-lock.json +++ b/package-lock.json @@ -57,6 +57,7 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "license-checker": "^25.0.1", + "lint-staged": "^15.2.8", "prettier": "3.2.5", "ts-css-modules-vite-plugin": "1.0.20", "typescript": "^5.2.2", @@ -2553,6 +2554,22 @@ "url": "https://github.com/sponsors/epoberezkin" } }, + "node_modules/ansi-escapes": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.0.0.tgz", + "integrity": "sha512-GdYO7a61mR0fOlAsvC9/rIHf7L96sBc6dEWzeOu+KAea5bZyQRPIpojrVoI4AXGJS/ycu/fBTdLrUkA4ODrvjw==", + "dev": true, + "license": "MIT", + "dependencies": { + "environment": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", @@ -2877,6 +2894,93 @@ "node": ">=10" } }, + "node_modules/cli-cursor": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz", + "integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==", + "dev": true, + "license": "MIT", + "dependencies": { + "restore-cursor": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz", + "integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==", + "dev": true, + "license": "MIT", + "dependencies": { + "slice-ansi": "^5.0.0", + "string-width": "^7.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/cli-truncate/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/cli-truncate/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-truncate/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/clsx": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/clsx/-/clsx-2.1.0.tgz", @@ -2907,6 +3011,13 @@ "color-support": "bin.js" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", @@ -2918,6 +3029,16 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -3015,9 +3136,10 @@ } }, "node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.6.tgz", + "integrity": "sha512-O/09Bd4Z1fBrU4VzkhFqVgpPzaGbw6Sm9FEkBT1A/YBXQFGuuSxa1dN2nxgxS34JmKXqYx8CZAwEVoJFImUXIg==", + "license": "MIT", "dependencies": { "ms": "2.1.2" }, @@ -3165,6 +3287,19 @@ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "optional": true }, + "node_modules/environment": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz", + "integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/error-ex": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", @@ -3572,6 +3707,50 @@ "es5-ext": "~0.10.14" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "dev": true, + "license": "MIT" + }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, + "node_modules/execa/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/ext": { "version": "1.7.0", "resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz", @@ -3862,6 +4041,32 @@ "node": ">=6.9.0" } }, + "node_modules/get-east-asian-width": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.2.0.tgz", + "integrity": "sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/glob": { "version": "7.2.3", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", @@ -4022,6 +4227,16 @@ "node": ">= 6" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/idb": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/idb/-/idb-8.0.0.tgz", @@ -4171,6 +4386,19 @@ "node": ">=8" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", @@ -4369,11 +4597,96 @@ "@scure/base": "1.1.1" } }, + "node_modules/lilconfig": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.2.tgz", + "integrity": "sha512-eop+wDAvpItUys0FWkHIKeC9ybYrTGbU41U5K7+bttZZeohvnY7M9dZ5kB21GNWiFT2q1OoPTvncPCgSOVO5ow==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, "node_modules/lines-and-columns": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" }, + "node_modules/lint-staged": { + "version": "15.2.8", + "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.2.8.tgz", + "integrity": "sha512-PUWFf2zQzsd9EFU+kM1d7UP+AZDbKFKuj+9JNVTBkhUFhbg4MAt6WfyMMwBfM4lYqd4D2Jwac5iuTu9rVj4zCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "chalk": "~5.3.0", + "commander": "~12.1.0", + "debug": "~4.3.6", + "execa": "~8.0.1", + "lilconfig": "~3.1.2", + "listr2": "~8.2.4", + "micromatch": "~4.0.7", + "pidtree": "~0.6.0", + "string-argv": "~0.3.2", + "yaml": "~2.5.0" + }, + "bin": { + "lint-staged": "bin/lint-staged.js" + }, + "engines": { + "node": ">=18.12.0" + }, + "funding": { + "url": "https://opencollective.com/lint-staged" + } + }, + "node_modules/lint-staged/node_modules/chalk": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.3.0.tgz", + "integrity": "sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/lint-staged/node_modules/yaml": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.5.0.tgz", + "integrity": "sha512-2wWLbGbYDiSqqIKoPjar3MPgB94ErzCtrNE1FdqGuaO0pi2JGjmE8aW8TDZwzU7vuxcGRdL/4gPQwQ7hD5AMSw==", + "dev": true, + "license": "ISC", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/listr2": { + "version": "8.2.4", + "resolved": "https://registry.npmjs.org/listr2/-/listr2-8.2.4.tgz", + "integrity": "sha512-opevsywziHd3zHCVQGAj8zu+Z3yHNkkoYhWIGnq54RrCVwLz0MozotJEDnKsIBLvkfLGN6BLOyAeRrYI0pKA4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "cli-truncate": "^4.0.0", + "colorette": "^2.0.20", + "eventemitter3": "^5.0.1", + "log-update": "^6.1.0", + "rfdc": "^1.4.1", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -4400,6 +4713,101 @@ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", "dev": true }, + "node_modules/log-update": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz", + "integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-escapes": "^7.0.0", + "cli-cursor": "^5.0.0", + "slice-ansi": "^7.1.0", + "strip-ansi": "^7.1.0", + "wrap-ansi": "^9.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/log-update/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/log-update/node_modules/is-fullwidth-code-point": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.0.0.tgz", + "integrity": "sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-east-asian-width": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.0.tgz", + "integrity": "sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "is-fullwidth-code-point": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -4450,6 +4858,13 @@ "integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==", "dev": true }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -4460,12 +4875,13 @@ } }, "node_modules/micromatch": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", - "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.7.tgz", + "integrity": "sha512-LPP/3KorzCwBxfeUuZmaR6bG2kdeHSbe0P2tY3FLRU4vYrjYz5hI4QZwV0njUx3jeuKe67YukQ1LSPZBKDqO/Q==", "dev": true, + "license": "MIT", "dependencies": { - "braces": "^3.0.2", + "braces": "^3.0.3", "picomatch": "^2.3.1" }, "engines": { @@ -4491,6 +4907,32 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mimic-function": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz", + "integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/mimic-response": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", @@ -4787,6 +5229,35 @@ "dev": true, "license": "ISC" }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/npmlog": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", @@ -4817,6 +5288,22 @@ "wrappy": "1" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/optionator": { "version": "0.9.3", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", @@ -5020,6 +5507,19 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, + "node_modules/pidtree": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz", + "integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==", + "dev": true, + "license": "MIT", + "bin": { + "pidtree": "bin/pidtree.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/postcss": { "version": "8.4.38", "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", @@ -5443,6 +5943,52 @@ "node": ">=4" } }, + "node_modules/restore-cursor": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz", + "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", + "dev": true, + "license": "MIT", + "dependencies": { + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/onetime": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz", + "integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-function": "^5.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/restore-cursor/node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/reusify": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", @@ -5453,6 +5999,13 @@ "node": ">=0.10.0" } }, + "node_modules/rfdc": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz", + "integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==", + "dev": true, + "license": "MIT" + }, "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", @@ -5667,6 +6220,49 @@ "node": ">=8" } }, + "node_modules/slice-ansi": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz", + "integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.0.0", + "is-fullwidth-code-point": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/slice-ansi/node_modules/is-fullwidth-code-point": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz", + "integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/slide": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", @@ -5769,6 +6365,16 @@ "safe-buffer": "~5.1.0" } }, + "node_modules/string-argv": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz", + "integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.6.19" + } + }, "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", @@ -5795,6 +6401,19 @@ "node": ">=8" } }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -6320,6 +6939,91 @@ "string-width": "^1.0.2 || 2 || 3 || 4" } }, + "node_modules/wrap-ansi": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.0.tgz", + "integrity": "sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-styles": "^6.2.1", + "string-width": "^7.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi/node_modules/emoji-regex": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.3.0.tgz", + "integrity": "sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==", + "dev": true, + "license": "MIT" + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", + "integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "emoji-regex": "^10.3.0", + "get-east-asian-width": "^1.0.0", + "strip-ansi": "^7.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", diff --git a/package.json b/package.json index 7d56991..cecb839 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "formatter:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "preview": "vite preview", "preinstall": "git config core.hooksPath .git-hooks", - "license-checker": "node licenseChecker.cjs" + "license-checker": "node licenseChecker.cjs", + "lint-staged": "lint-staged" }, "dependencies": { "@emotion/react": "11.11.4", @@ -64,10 +65,17 @@ "eslint-plugin-react-hooks": "^4.6.0", "eslint-plugin-react-refresh": "^0.4.5", "license-checker": "^25.0.1", + "lint-staged": "^15.2.8", "prettier": "3.2.5", "ts-css-modules-vite-plugin": "1.0.20", "typescript": "^5.2.2", "vite": "^5.1.4", "vite-tsconfig-paths": "4.3.2" + }, + "lint-staged": { + "*.{js,jsx,ts,tsx}": [ + "eslint . --ext ts,tsx --report-unused-disable-directives --quiet --max-warnings 0 --fix" + ], + "**/*": "prettier . --write --ignore-unknown" } } From d43067f70ebb5d52c08452402540406ca9ff4cfa Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 17:17:39 +0200 Subject: [PATCH 24/49] fix(ci): run lint-staged always, fix lint-stage commands --- .git-hooks/pre-commit | 4 ++-- package.json | 6 ++++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit index bd9faed..2aa6dae 100644 --- a/.git-hooks/pre-commit +++ b/.git-hooks/pre-commit @@ -8,6 +8,6 @@ if [[ "$BRANCH" =~ $REGEX ]]; then echo "You are on branch $BRANCH. Are you sure you want to commit to this branch?" echo "If so, commit with -n to bypass the pre-commit hook." exit 1 -else - npm run lint-staged fi + +npm run lint-staged diff --git a/package.json b/package.json index cecb839..6fabe5b 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,10 @@ "build": "tsc && vite build", "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", "formatter:fix": "prettier --write \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", + "formatter:staged": "prettier --write --ignore-unknown", "preview": "vite preview", "preinstall": "git config core.hooksPath .git-hooks", "license-checker": "node licenseChecker.cjs", @@ -74,8 +76,8 @@ }, "lint-staged": { "*.{js,jsx,ts,tsx}": [ - "eslint . --ext ts,tsx --report-unused-disable-directives --quiet --max-warnings 0 --fix" + "npm run lint:staged" ], - "**/*": "prettier . --write --ignore-unknown" + "*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}": "npm run formatter:staged" } } From dde019d364833258e038ee7912e3583690f79ee2 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 17:25:54 +0200 Subject: [PATCH 25/49] chore(ci): add license --- licenseChecker.cjs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/licenseChecker.cjs b/licenseChecker.cjs index 9baf89b..294dacc 100644 --- a/licenseChecker.cjs +++ b/licenseChecker.cjs @@ -9,7 +9,7 @@ const check = (cwd) => { start: cwd, excludePrivatePackages: true, onlyAllow: - 'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;', + 'AFLv2.1;Apache 2.0;Apache-2.0;Apache*;Artistic-2.0;0BSD;BSD*;BSD-2-Clause;BSD-3-Clause;BSD 3-Clause;CC0-1.0;CC-BY-3.0;CC-BY-4.0;ISC;MIT;MPL-2.0;ODC-By-1.0;Python-2.0;Unlicense;', excludePackages: '' }, (error, json) => { From 312ec3881733ee91d8804870a49e6093b57982f4 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 17:44:50 +0200 Subject: [PATCH 26/49] ci: add synchronize to PR triggers --- .gitea/workflows/staging-pull-request.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitea/workflows/staging-pull-request.yaml b/.gitea/workflows/staging-pull-request.yaml index c541cff..2bebcd4 100644 --- a/.gitea/workflows/staging-pull-request.yaml +++ b/.gitea/workflows/staging-pull-request.yaml @@ -1,7 +1,7 @@ name: Open PR on Staging on: pull_request: - types: [opened, edited] + types: [opened, edited, synchronize] branches: - staging From 57aba9dd42962939070a8030ae25ea99bf4a5c8d Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 17:49:39 +0200 Subject: [PATCH 27/49] chore(lint): increase max warnings to 41 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6fabe5b..6a8af40 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 0", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 41", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", From e3eb1f37c1584d13b84cdecf885346b923f6aff5 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 6 Aug 2024 17:53:55 +0200 Subject: [PATCH 28/49] ci(prettier): changes after running npm run formatter:fix --- src/components/DrawPDFFields/index.tsx | 244 +++++++++++++++++-------- src/components/PDFView/PdfItem.tsx | 44 +++-- src/components/PDFView/PdfMarkItem.tsx | 20 +- src/components/PDFView/PdfMarking.tsx | 67 +++---- src/components/PDFView/PdfPageItem.tsx | 32 ++-- src/components/PDFView/index.tsx | 47 +++-- src/index.css | 18 +- src/pages/create/index.tsx | 55 +++--- src/pages/sign/MarkFormField.tsx | 6 +- src/pages/sign/index.tsx | 74 ++++---- src/pages/verify/index.tsx | 31 ++-- src/types/drawing.ts | 6 +- src/types/mark.ts | 24 +-- src/types/zip.ts | 24 +-- src/utils/const.ts | 2 +- src/utils/mark.ts | 34 ++-- src/utils/pdf.ts | 143 ++++++++------- src/utils/sign.ts | 11 +- src/utils/utils.ts | 4 +- src/utils/zip.ts | 11 +- 20 files changed, 523 insertions(+), 374 deletions(-) diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index 148e70f..e7880e4 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -1,15 +1,43 @@ -import { AccessTime, CalendarMonth, ExpandMore, Gesture, PictureAsPdf, Badge, Work, Close } from '@mui/icons-material' -import { Box, Typography, Accordion, AccordionDetails, AccordionSummary, CircularProgress, FormControl, InputLabel, MenuItem, Select } from '@mui/material' +import { + AccessTime, + CalendarMonth, + ExpandMore, + Gesture, + PictureAsPdf, + Badge, + Work, + Close +} from '@mui/icons-material' +import { + Box, + Typography, + Accordion, + AccordionDetails, + AccordionSummary, + CircularProgress, + FormControl, + InputLabel, + MenuItem, + Select +} from '@mui/material' import styles from './style.module.scss' import { useEffect, useState } from 'react' -import * as PDFJS from "pdfjs-dist"; -import { ProfileMetadata, User } from '../../types'; -import { PdfFile, DrawTool, MouseState, PdfPage, DrawnField, MarkType } from '../../types/drawing'; -import { truncate } from 'lodash'; -import { hexToNpub } from '../../utils'; +import * as PDFJS from 'pdfjs-dist' +import { ProfileMetadata, User } from '../../types' +import { + PdfFile, + DrawTool, + MouseState, + PdfPage, + DrawnField, + MarkType +} from '../../types/drawing' +import { truncate } from 'lodash' +import { hexToNpub } from '../../utils' import { toPdfFiles } from '../../utils/pdf.ts' -PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; +PDFJS.GlobalWorkerOptions.workerSrc = + 'node_modules/pdfjs-dist/build/pdf.worker.mjs' interface Props { selectedFiles: File[] @@ -20,44 +48,43 @@ interface Props { export const DrawPDFFields = (props: Props) => { const { selectedFiles } = props - + const [pdfFiles, setPdfFiles] = useState([]) const [parsingPdf, setParsingPdf] = useState(false) const [showDrawToolBox, setShowDrawToolBox] = useState(false) - + const [selectedTool, setSelectedTool] = useState() const [toolbox] = useState([ { identifier: MarkType.SIGNATURE, - icon: , + icon: , label: 'Signature', active: false - }, { identifier: MarkType.FULLNAME, - icon: , + icon: , label: 'Full Name', active: true }, { identifier: MarkType.JOBTITLE, - icon: , + icon: , label: 'Job Title', active: false }, { identifier: MarkType.DATE, - icon: , + icon: , label: 'Date', active: false }, { identifier: MarkType.DATETIME, - icon: , + icon: , label: 'Datetime', active: false - }, + } ]) const [mouseState, setMouseState] = useState({ @@ -67,7 +94,7 @@ export const DrawPDFFields = (props: Props) => { useEffect(() => { if (selectedFiles) { setParsingPdf(true) - + parsePdfPages().finally(() => { setParsingPdf(false) }) @@ -81,13 +108,13 @@ export const DrawPDFFields = (props: Props) => { /** * Drawing events */ - useEffect(() => { + useEffect(() => { // window.addEventListener('mousedown', onMouseDown); - window.addEventListener('mouseup', onMouseUp); - + window.addEventListener('mouseup', onMouseUp) + return () => { // window.removeEventListener('mousedown', onMouseDown); - window.removeEventListener('mouseup', onMouseUp); + window.removeEventListener('mouseup', onMouseUp) } }, []) @@ -106,7 +133,7 @@ export const DrawPDFFields = (props: Props) => { const onMouseDown = (event: any, page: PdfPage) => { // Proceed only if left click if (event.button !== 0) return - + // Only allow drawing if mouse is not over other drawn element const isOverPdfImageWrapper = event.target.tagName === 'IMG' @@ -158,11 +185,11 @@ export const DrawPDFFields = (props: Props) => { */ const onMouseMove = (event: any, page: PdfPage) => { if (mouseState.clicked && selectedTool) { - const lastElementIndex = page.drawnFields.length -1 + const lastElementIndex = page.drawnFields.length - 1 const lastDrawnField = page.drawnFields[lastElementIndex] const { mouseX, mouseY } = getMouseCoordinates(event) - + const width = mouseX - lastDrawnField.left const height = mouseY - lastDrawnField.top @@ -172,10 +199,10 @@ export const DrawPDFFields = (props: Props) => { const currentDrawnFields = page.drawnFields currentDrawnFields[lastElementIndex] = lastDrawnField - + refreshPdfFiles() } - } + } /** * Fired when event happens on the drawn element which will be moved @@ -189,7 +216,7 @@ export const DrawPDFFields = (props: Props) => { */ const onDrawnFieldMouseDown = (event: any) => { event.stopPropagation() - + // Proceed only if left click if (event.button !== 0) return @@ -212,7 +239,10 @@ export const DrawPDFFields = (props: Props) => { */ const onDranwFieldMouseMove = (event: any, drawnField: DrawnField) => { if (mouseState.dragging) { - const { mouseX, mouseY, rect } = getMouseCoordinates(event, event.target.parentNode) + const { mouseX, mouseY, rect } = getMouseCoordinates( + event, + event.target.parentNode + ) const coordsOffset = mouseState.coordsInWrapper if (coordsOffset) { @@ -258,8 +288,11 @@ export const DrawPDFFields = (props: Props) => { */ const onResizeHandleMouseMove = (event: any, drawnField: DrawnField) => { if (mouseState.resizing) { - const { mouseX, mouseY } = getMouseCoordinates(event, event.target.parentNode.parentNode) - + const { mouseX, mouseY } = getMouseCoordinates( + event, + event.target.parentNode.parentNode + ) + const width = mouseX - drawnField.left const height = mouseY - drawnField.top @@ -277,10 +310,18 @@ export const DrawPDFFields = (props: Props) => { * @param pdfPageIndex pdf page index * @param drawnFileIndex drawn file index */ - const onRemoveHandleMouseDown = (event: any, pdfFileIndex: number, pdfPageIndex: number, drawnFileIndex: number) => { + const onRemoveHandleMouseDown = ( + event: any, + pdfFileIndex: number, + pdfPageIndex: number, + drawnFileIndex: number + ) => { event.stopPropagation() - - pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice(drawnFileIndex, 1) + + pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice( + drawnFileIndex, + 1 + ) } /** @@ -300,9 +341,9 @@ export const DrawPDFFields = (props: Props) => { */ const getMouseCoordinates = (event: any, customTarget?: any) => { const target = customTarget ? customTarget : event.target - const rect = target.getBoundingClientRect(); - const mouseX = event.clientX - rect.left; //x position within the element. - const mouseY = event.clientY - rect.top; //y position within the element. + const rect = target.getBoundingClientRect() + const mouseX = event.clientX - rect.left //x position within the element. + const mouseY = event.clientY - rect.top //y position within the element. return { mouseX, @@ -316,8 +357,8 @@ export const DrawPDFFields = (props: Props) => { * creates the pdfFiles object and sets to a state */ const parsePdfPages = async () => { - const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles); - + const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles) + setPdfFiles(pdfFiles) } @@ -326,7 +367,7 @@ export const DrawPDFFields = (props: Props) => { * @returns if expanded pdf accordion is present */ const hasExpandedPdf = () => { - return !!pdfFiles.filter(pdfFile => !!pdfFile.expanded).length + return !!pdfFiles.filter((pdfFile) => !!pdfFile.expanded).length } const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => { @@ -355,9 +396,11 @@ export const DrawPDFFields = (props: Props) => { */ const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => { return ( - + {pdfFile.pages.map((page, pdfPageIndex: number) => { return (
{ marginBottom: '10px' }} className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`} - onMouseMove={(event) => {onMouseMove(event, page)}} - onMouseDown={(event) => {onMouseDown(event, page)}} + onMouseMove={(event) => { + onMouseMove(event, page) + }} + onMouseDown={(event) => { + onMouseDown(event, page) + }} > - + {page.drawnFields.map((drawnField, drawnFieldIndex: number) => { return (
{ onDranwFieldMouseMove(event, drawnField)}} + onMouseMove={(event) => { + onDranwFieldMouseMove(event, drawnField) + }} className={styles.drawingRectangle} style={{ left: `${drawnField.left}px`, @@ -389,41 +442,68 @@ export const DrawPDFFields = (props: Props) => { > {onResizeHandleMouseMove(event, drawnField)}} + onMouseMove={(event) => { + onResizeHandleMouseMove(event, drawnField) + }} className={styles.resizeHandle} > {onRemoveHandleMouseDown(event, pdfFileIndex, pdfPageIndex, drawnFieldIndex)}} + onMouseDown={(event) => { + onRemoveHandleMouseDown( + event, + pdfFileIndex, + pdfPageIndex, + drawnFieldIndex + ) + }} className={styles.removeHandle} > - + -
- + Counterpart @@ -435,13 +515,13 @@ export const DrawPDFFields = (props: Props) => { ) })} - ) + ) } if (parsingPdf) { return ( - + ) } @@ -454,22 +534,28 @@ export const DrawPDFFields = (props: Props) => { Draw fields on the PDFs: - + {pdfFiles.map((pdfFile, pdfFileIndex: number) => { return ( - {handleAccordionExpandChange(expanded, pdfFile)}}> + { + handleAccordionExpandChange(expanded, pdfFile) + }} + > } aria-controls={`panel${pdfFileIndex}-content`} id={`panel${pdfFileIndex}header`} > - + {pdfFile.file.name} {getPdfPages(pdfFile, pdfFileIndex)} - + ) })} @@ -477,16 +563,22 @@ export const DrawPDFFields = (props: Props) => { {showDrawToolBox && ( - {toolbox.filter(drawTool => drawTool.active).map((drawTool: DrawTool, index: number) => { - return ( - {handleToolSelect(drawTool)}} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}> - { drawTool.icon } - { drawTool.label } - - ) - })} + {toolbox + .filter((drawTool) => drawTool.active) + .map((drawTool: DrawTool, index: number) => { + return ( + { + handleToolSelect(drawTool) + }} + className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`} + > + {drawTool.icon} + {drawTool.label} + + ) + })} )} diff --git a/src/components/PDFView/PdfItem.tsx b/src/components/PDFView/PdfItem.tsx index eb5ceff..6e8aa64 100644 --- a/src/components/PDFView/PdfItem.tsx +++ b/src/components/PDFView/PdfItem.tsx @@ -1,6 +1,6 @@ import { PdfFile } from '../../types/drawing.ts' import { CurrentUserMark } from '../../types/mark.ts' -import PdfPageItem from './PdfPageItem.tsx'; +import PdfPageItem from './PdfPageItem.tsx' interface PdfItemProps { pdfFile: PdfFile @@ -13,23 +13,31 @@ interface PdfItemProps { /** * Responsible for displaying pages of a single Pdf File. */ -const PdfItem = ({ pdfFile, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfItemProps) => { - const filterByPage = (marks: CurrentUserMark[], page: number): CurrentUserMark[] => { - return marks.filter((m) => m.mark.location.page === page); +const PdfItem = ({ + pdfFile, + currentUserMarks, + handleMarkClick, + selectedMarkValue, + selectedMark +}: PdfItemProps) => { + const filterByPage = ( + marks: CurrentUserMark[], + page: number + ): CurrentUserMark[] => { + return marks.filter((m) => m.mark.location.page === page) } - return ( - pdfFile.pages.map((page, i) => { - return ( - - ) - })) + return pdfFile.pages.map((page, i) => { + return ( + + ) + }) } -export default PdfItem \ No newline at end of file +export default PdfItem diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index 7a2b24b..90caa87 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -12,14 +12,18 @@ interface PdfMarkItemProps { /** * Responsible for display an individual Pdf Mark. */ -const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMark }: PdfMarkItemProps) => { - const { location } = userMark.mark; - const handleClick = () => handleMarkClick(userMark.mark.id); - const getMarkValue = () => ( +const PdfMarkItem = ({ + selectedMark, + handleMarkClick, + selectedMarkValue, + userMark +}: PdfMarkItemProps) => { + const { location } = userMark.mark + const handleClick = () => handleMarkClick(userMark.mark.id) + const getMarkValue = () => selectedMark?.mark.id === userMark.mark.id ? selectedMarkValue : userMark.mark.value - ) return (
{getMarkValue()}
+ > + {getMarkValue()} +
) } -export default PdfMarkItem \ No newline at end of file +export default PdfMarkItem diff --git a/src/components/PDFView/PdfMarking.tsx b/src/components/PDFView/PdfMarking.tsx index a6dc1a4..0e47ab1 100644 --- a/src/components/PDFView/PdfMarking.tsx +++ b/src/components/PDFView/PdfMarking.tsx @@ -6,17 +6,17 @@ import React, { useState, useEffect } from 'react' import { findNextCurrentUserMark, isCurrentUserMarksComplete, - updateCurrentUserMarks, + updateCurrentUserMarks } from '../../utils' import { EMPTY } from '../../utils/const.ts' import { Container } from '../Container' import styles from '../../pages/sign/style.module.scss' interface PdfMarkingProps { - files: { pdfFile: PdfFile, filename: string, hash: string | null }[], - currentUserMarks: CurrentUserMark[], - setIsReadyToSign: (isReadyToSign: boolean) => void, - setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void, + files: { pdfFile: PdfFile; filename: string; hash: string | null }[] + currentUserMarks: CurrentUserMark[] + setIsReadyToSign: (isReadyToSign: boolean) => void + setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void setUpdatedMarks: (markToUpdate: Mark) => void } @@ -35,21 +35,21 @@ const PdfMarking = (props: PdfMarkingProps) => { setUpdatedMarks } = props const [selectedMark, setSelectedMark] = useState(null) - const [selectedMarkValue, setSelectedMarkValue] = useState("") + const [selectedMarkValue, setSelectedMarkValue] = useState('') useEffect(() => { setSelectedMark(findNextCurrentUserMark(currentUserMarks) || null) }, [currentUserMarks]) const handleMarkClick = (id: number) => { - const nextMark = currentUserMarks.find((mark) => mark.mark.id === id); - setSelectedMark(nextMark!); - setSelectedMarkValue(nextMark?.mark.value ?? EMPTY); + const nextMark = currentUserMarks.find((mark) => mark.mark.id === id) + setSelectedMark(nextMark!) + setSelectedMarkValue(nextMark?.mark.value ?? EMPTY) } const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - if (!selectedMarkValue || !selectedMark) return; + event.preventDefault() + if (!selectedMarkValue || !selectedMark) return const updatedMark: CurrentUserMark = { ...selectedMark, @@ -61,7 +61,10 @@ const PdfMarking = (props: PdfMarkingProps) => { } setSelectedMarkValue(EMPTY) - const updatedCurrentUserMarks = updateCurrentUserMarks(currentUserMarks, updatedMark); + const updatedCurrentUserMarks = updateCurrentUserMarks( + currentUserMarks, + updatedMark + ) setCurrentUserMarks(updatedCurrentUserMarks) setSelectedMark(findNextCurrentUserMark(updatedCurrentUserMarks) || null) console.log(isCurrentUserMarksComplete(updatedCurrentUserMarks)) @@ -69,32 +72,32 @@ const PdfMarking = (props: PdfMarkingProps) => { setUpdatedMarks(updatedMark.mark) } - const handleChange = (event: React.ChangeEvent) => setSelectedMarkValue(event.target.value) + const handleChange = (event: React.ChangeEvent) => + setSelectedMarkValue(event.target.value) return ( <> - { - currentUserMarks?.length > 0 && ( - )} - { - selectedMark !== null && ( - - )} + {currentUserMarks?.length > 0 && ( + + )} + {selectedMark !== null && ( + + )} ) } -export default PdfMarking \ No newline at end of file +export default PdfMarking diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx index d289a6e..241474e 100644 --- a/src/components/PDFView/PdfPageItem.tsx +++ b/src/components/PDFView/PdfPageItem.tsx @@ -13,7 +13,13 @@ interface PdfPageProps { /** * Responsible for rendering a single Pdf Page and its Marks */ -const PdfPageItem = ({ page, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfPageProps) => { +const PdfPageItem = ({ + page, + currentUserMarks, + handleMarkClick, + selectedMarkValue, + selectedMark +}: PdfPageProps) => { return (
- - { - currentUserMarks.map((m, i) => ( - + {currentUserMarks.map((m, i) => ( + ))}
) } -export default PdfPageItem \ No newline at end of file +export default PdfPageItem diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 14834a3..8a14e55 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -4,7 +4,7 @@ import PdfItem from './PdfItem.tsx' import { CurrentUserMark } from '../../types/mark.ts' interface PdfViewProps { - files: { pdfFile: PdfFile, filename: string, hash: string | null }[] + files: { pdfFile: PdfFile; filename: string; hash: string | null }[] currentUserMarks: CurrentUserMark[] handleMarkClick: (id: number) => void selectedMarkValue: string @@ -14,29 +14,38 @@ interface PdfViewProps { /** * Responsible for rendering Pdf files. */ -const PdfView = ({ files, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfViewProps) => { - const filterByFile = (currentUserMarks: CurrentUserMark[], hash: string): CurrentUserMark[] => { - return currentUserMarks.filter((currentUserMark) => currentUserMark.mark.pdfFileHash === hash) +const PdfView = ({ + files, + currentUserMarks, + handleMarkClick, + selectedMarkValue, + selectedMark +}: PdfViewProps) => { + const filterByFile = ( + currentUserMarks: CurrentUserMark[], + hash: string + ): CurrentUserMark[] => { + return currentUserMarks.filter( + (currentUserMark) => currentUserMark.mark.pdfFileHash === hash + ) } return ( - { - files.map(({ pdfFile, hash }, i) => { - if (!hash) return - return ( - { + if (!hash) return + return ( + - ) - }) - } + ) + })} ) } -export default PdfView; \ No newline at end of file +export default PdfView diff --git a/src/index.css b/src/index.css index 6a734df..76373ff 100644 --- a/src/index.css +++ b/src/index.css @@ -111,7 +111,9 @@ button:disabled { /* Fonts */ @font-face { font-family: 'Roboto'; - src: local('Roboto Medium'), local('Roboto-Medium'), + src: + local('Roboto Medium'), + local('Roboto-Medium'), url('assets/fonts/roboto-medium.woff2') format('woff2'), url('assets/fonts/roboto-medium.woff') format('woff'); font-weight: 500; @@ -121,7 +123,9 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto Light'), local('Roboto-Light'), + src: + local('Roboto Light'), + local('Roboto-Light'), url('assets/fonts/roboto-light.woff2') format('woff2'), url('assets/fonts/roboto-light.woff') format('woff'); font-weight: 300; @@ -131,7 +135,9 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto Bold'), local('Roboto-Bold'), + src: + local('Roboto Bold'), + local('Roboto-Bold'), url('assets/fonts/roboto-bold.woff2') format('woff2'), url('assets/fonts/roboto-bold.woff') format('woff'); font-weight: bold; @@ -141,10 +147,12 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto'), local('Roboto-Regular'), + src: + local('Roboto'), + local('Roboto-Regular'), url('assets/fonts/roboto-regular.woff2') format('woff2'), url('assets/fonts/roboto-regular.woff') format('woff'); font-weight: normal; font-style: normal; font-display: swap; -} \ No newline at end of file +} diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 830f9ef..023dd02 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -341,29 +341,30 @@ export const CreatePage = () => { return fileHashes } - const createMarks = (fileHashes: { [key: string]: string }) : Mark[] => { - return drawnPdfs.flatMap((drawnPdf) => { - const fileHash = fileHashes[drawnPdf.file.name]; - return drawnPdf.pages.flatMap((page, index) => { - return page.drawnFields.map((drawnField) => { - return { - type: drawnField.type, - location: { - page: index, - top: drawnField.top, - left: drawnField.left, - height: drawnField.height, - width: drawnField.width, - }, - npub: drawnField.counterpart, - pdfFileHash: fileHash - } + const createMarks = (fileHashes: { [key: string]: string }): Mark[] => { + return drawnPdfs + .flatMap((drawnPdf) => { + const fileHash = fileHashes[drawnPdf.file.name] + return drawnPdf.pages.flatMap((page, index) => { + return page.drawnFields.map((drawnField) => { + return { + type: drawnField.type, + location: { + page: index, + top: drawnField.top, + left: drawnField.left, + height: drawnField.height, + width: drawnField.width + }, + npub: drawnField.counterpart, + pdfFileHash: fileHash + } + }) }) }) - }) .map((mark, index) => { - return {...mark, id: index } - }); + return { ...mark, id: index } + }) } // Handle errors during zip file generation @@ -431,13 +432,9 @@ export const CreatePage = () => { if (!arraybuffer) return null - return new File( - [new Blob([arraybuffer])], - `${unixNow}.sigit.zip`, - { - type: 'application/zip' - } - ) + return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { + type: 'application/zip' + }) } // Handle errors during file upload @@ -545,9 +542,7 @@ export const CreatePage = () => { : viewers.map((viewer) => viewer.pubkey) ).filter((receiver) => receiver !== usersPubkey) - return receivers.map((receiver) => - sendNotification(receiver, meta) - ) + return receivers.map((receiver) => sendNotification(receiver, meta)) } const handleCreate = async () => { diff --git a/src/pages/sign/MarkFormField.tsx b/src/pages/sign/MarkFormField.tsx index 4de82a4..4cb943b 100644 --- a/src/pages/sign/MarkFormField.tsx +++ b/src/pages/sign/MarkFormField.tsx @@ -15,8 +15,8 @@ interface MarkFormFieldProps { * Responsible for rendering a form field connected to a mark and keeping track of its value. */ const MarkFormField = (props: MarkFormFieldProps) => { - const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props; - const getSubmitButton = () => selectedMark.isLast ? 'Complete' : 'Next'; + const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props + const getSubmitButton = () => (selectedMark.isLast ? 'Complete' : 'Next') return (
@@ -34,4 +34,4 @@ const MarkFormField = (props: MarkFormFieldProps) => { ) } -export default MarkFormField; \ No newline at end of file +export default MarkFormField diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 07c385e..6f3231b 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -16,13 +16,16 @@ import { State } from '../../store/rootReducer' import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types' import { decryptArrayBuffer, - encryptArrayBuffer, extractMarksFromSignedMeta, + encryptArrayBuffer, + extractMarksFromSignedMeta, extractZipUrlAndEncryptionKey, generateEncryptionKey, - generateKeysFile, getFilesWithHashes, + generateKeysFile, + getFilesWithHashes, getHash, hexToNpub, - isOnline, loadZip, + isOnline, + loadZip, now, npubToHex, parseJson, @@ -41,7 +44,8 @@ import { getLastSignersSig } from '../../utils/sign.ts' import { filterMarksByPubkey, getCurrentUserMarks, - isCurrentUserMarksComplete, updateMarks + isCurrentUserMarksComplete, + updateMarks } from '../../utils' import PdfMarking from '../../components/PDFView/PdfMarking.tsx' enum SignedStatus { @@ -81,7 +85,7 @@ export const SignPage = () => { const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [viewers, setViewers] = useState<`npub1${string}`[]>([]) - const [marks, setMarks] = useState([]) + const [marks, setMarks] = useState([]) const [creatorFileHashes, setCreatorFileHashes] = useState<{ [key: string]: string }>({}) @@ -100,8 +104,10 @@ export const SignPage = () => { const [authUrl, setAuthUrl] = useState() const nostrController = NostrController.getInstance() - const [currentUserMarks, setCurrentUserMarks] = useState([]); - const [isReadyToSign, setIsReadyToSign] = useState(false); + const [currentUserMarks, setCurrentUserMarks] = useState( + [] + ) + const [isReadyToSign, setIsReadyToSign] = useState(false) useEffect(() => { if (signers.length > 0) { @@ -192,13 +198,16 @@ export const SignPage = () => { setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) setSubmittedBy(createSignatureEvent.pubkey) - setMarks(createSignatureContent.markConfig); + setMarks(createSignatureContent.markConfig) if (usersPubkey) { - const metaMarks = filterMarksByPubkey(createSignatureContent.markConfig, usersPubkey!) + const metaMarks = filterMarksByPubkey( + createSignatureContent.markConfig, + usersPubkey! + ) const signedMarks = extractMarksFromSignedMeta(meta) - const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks); - setCurrentUserMarks(currentUserMarks); + const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks) + setCurrentUserMarks(currentUserMarks) // setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null) setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks)) } @@ -307,7 +316,7 @@ export const SignPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName); + files[fileName] = await convertToPdfFile(arrayBuffer, fileName) const hash = await getHash(arrayBuffer) if (hash) { @@ -348,7 +357,7 @@ export const SignPage = () => { const decrypt = async (file: File) => { setLoadingSpinnerDesc('Decrypting file') - const zip = await loadZip(file); + const zip = await loadZip(file) if (!zip) return const parsedKeysJson = await parseKeysJson(zip) @@ -439,7 +448,7 @@ export const SignPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName); + files[fileName] = await convertToPdfFile(arrayBuffer, fileName) const hash = await getHash(arrayBuffer) if (hash) { @@ -520,7 +529,10 @@ export const SignPage = () => { } // Sign the event for the meta file - const signEventForMeta = async (signerContent: { prevSig: string, marks: Mark[] }) => { + const signEventForMeta = async (signerContent: { + prevSig: string + marks: Mark[] + }) => { return await signEventForMetaFile( JSON.stringify(signerContent), nostrController, @@ -529,8 +541,8 @@ export const SignPage = () => { } const getSignerMarksForMeta = (): Mark[] | undefined => { - if (currentUserMarks.length === 0) return; - return currentUserMarks.map(( { mark }: CurrentUserMark) => mark); + if (currentUserMarks.length === 0) return + return currentUserMarks.map(({ mark }: CurrentUserMark) => mark) } // Update the meta signatures @@ -600,13 +612,9 @@ export const SignPage = () => { if (!arraybuffer) return null - return new File( - [new Blob([arraybuffer])], - `${unixNow}.sigit.zip`, - { - type: 'application/zip' - } - ) + return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { + type: 'application/zip' + }) } // Handle errors during zip file generation @@ -694,7 +702,7 @@ export const SignPage = () => { setIsLoading(true) setLoadingSpinnerDesc('Signing nostr event') - if (!meta) return; + if (!meta) return const prevSig = getLastSignersSig(meta, signers) if (!prevSig) return @@ -918,11 +926,13 @@ export const SignPage = () => { ) } - return + return ( + + ) } diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 6290c02..c5a0e8e 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -23,14 +23,17 @@ import { SignedEventContent } from '../../types' import { - decryptArrayBuffer, extractMarksFromSignedMeta, + decryptArrayBuffer, + extractMarksFromSignedMeta, extractZipUrlAndEncryptionKey, getHash, - hexToNpub, now, + hexToNpub, + now, npubToHex, parseJson, readContentOfZipEntry, - shorten, signEventForMetaFile + shorten, + signEventForMetaFile } from '../../utils' import styles from './style.module.scss' import { Cancel, CheckCircle } from '@mui/icons-material' @@ -41,7 +44,7 @@ import { addMarks, convertToPdfBlob, convertToPdfFile, - groupMarksByPage, + groupMarksByPage } from '../../utils/pdf.ts' import { State } from '../../store/rootReducer.ts' import { useSelector } from 'react-redux' @@ -78,7 +81,7 @@ export const VerifyPage = () => { const [currentFileHashes, setCurrentFileHashes] = useState<{ [key: string]: string | null }>({}) - const [files, setFiles] = useState<{ [filename: string]: PdfFile}>({}) + const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({}) const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>( {} @@ -155,7 +158,10 @@ export const VerifyPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName!) + files[fileName] = await convertToPdfFile( + arrayBuffer, + fileName! + ) const hash = await getHash(arrayBuffer) if (hash) { @@ -169,7 +175,6 @@ export const VerifyPage = () => { setCurrentFileHashes(fileHashes) setFiles(files) - setSigners(createSignatureContent.signers) setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) @@ -177,8 +182,6 @@ export const VerifyPage = () => { setMeta(metaInNavState) setIsLoading(false) - - } }) .catch((err) => { @@ -381,7 +384,7 @@ export const VerifyPage = () => { } const handleExport = async () => { - if (Object.entries(files).length === 0 ||!meta ||!usersPubkey) return; + if (Object.entries(files).length === 0 || !meta || !usersPubkey) return const usersNpub = hexToNpub(usersPubkey) if ( @@ -395,10 +398,10 @@ export const VerifyPage = () => { setIsLoading(true) setLoadingSpinnerDesc('Signing nostr event') - if (!meta) return; + if (!meta) return const prevSig = getLastSignersSig(meta, signers) - if (!prevSig) return; + if (!prevSig) return const signedEvent = await signEventForMetaFile( JSON.stringify({ prevSig }), @@ -406,10 +409,10 @@ export const VerifyPage = () => { setIsLoading ) - if (!signedEvent) return; + if (!signedEvent) return const exportSignature = JSON.stringify(signedEvent, null, 2) - const updatedMeta = {...meta, exportSignature } + const updatedMeta = { ...meta, exportSignature } const stringifiedMeta = JSON.stringify(updatedMeta, null, 2) const zip = new JSZip() diff --git a/src/types/drawing.ts b/src/types/drawing.ts index 702343f..f14dbea 100644 --- a/src/types/drawing.ts +++ b/src/types/drawing.ts @@ -9,7 +9,7 @@ export interface MouseState { } export interface PdfFile { - file: File, + file: File pages: PdfPage[] expanded?: boolean } @@ -34,7 +34,7 @@ export interface DrawnField { export interface DrawTool { identifier: MarkType label: string - icon: JSX.Element, + icon: JSX.Element defaultValue?: string selected?: boolean active?: boolean @@ -46,4 +46,4 @@ export enum MarkType { FULLNAME = 'FULLNAME', DATE = 'DATE', DATETIME = 'DATETIME' -} \ No newline at end of file +} diff --git a/src/types/mark.ts b/src/types/mark.ts index 3184f95..9a6a545 100644 --- a/src/types/mark.ts +++ b/src/types/mark.ts @@ -1,4 +1,4 @@ -import { MarkType } from "./drawing"; +import { MarkType } from './drawing' export interface CurrentUserMark { mark: Mark @@ -7,18 +7,18 @@ export interface CurrentUserMark { } export interface Mark { - id: number; - npub: string; - pdfFileHash: string; - type: MarkType; - location: MarkLocation; - value?: string; + id: number + npub: string + pdfFileHash: string + type: MarkType + location: MarkLocation + value?: string } export interface MarkLocation { - top: number; - left: number; - height: number; - width: number; - page: number; + top: number + left: number + height: number + width: number + page: number } diff --git a/src/types/zip.ts b/src/types/zip.ts index b4c97ac..7405067 100644 --- a/src/types/zip.ts +++ b/src/types/zip.ts @@ -1,4 +1,4 @@ - export interface OutputByType { +export interface OutputByType { base64: string string: string text: string @@ -11,16 +11,18 @@ } interface InputByType { - base64: string; - string: string; - text: string; - binarystring: string; - array: number[]; - uint8array: Uint8Array; - arraybuffer: ArrayBuffer; - blob: Blob; - stream: NodeJS.ReadableStream; + base64: string + string: string + text: string + binarystring: string + array: number[] + uint8array: Uint8Array + arraybuffer: ArrayBuffer + blob: Blob + stream: NodeJS.ReadableStream } export type OutputType = keyof OutputByType -export type InputFileFormat = InputByType[keyof InputByType] | Promise; \ No newline at end of file +export type InputFileFormat = + | InputByType[keyof InputByType] + | Promise diff --git a/src/utils/const.ts b/src/utils/const.ts index 4f8c233..ae5207f 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -3,4 +3,4 @@ import { MarkType } from '../types/drawing.ts' export const EMPTY: string = '' export const MARK_TYPE_TRANSLATION: { [key: string]: string } = { [MarkType.FULLNAME.valueOf()]: 'Full Name' -} \ No newline at end of file +} diff --git a/src/utils/mark.ts b/src/utils/mark.ts index 13cff84..60868be 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -9,9 +9,12 @@ import { Event } from 'nostr-tools' * @param marks - default Marks extracted from Meta * @param signedMetaMarks - signed user Marks extracted from DocSignatures */ -const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUserMark[] => { +const getCurrentUserMarks = ( + marks: Mark[], + signedMetaMarks: Mark[] +): CurrentUserMark[] => { return marks.map((mark, index, arr) => { - const signedMark = signedMetaMarks.find((m) => m.id === mark.id); + const signedMark = signedMetaMarks.find((m) => m.id === mark.id) if (signedMark && !!signedMark.value) { mark.value = signedMark.value } @@ -27,8 +30,10 @@ const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUse * Returns next incomplete CurrentUserMark if there is one * @param usersMarks */ -const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark | undefined => { - return usersMarks.find((mark) => !mark.isCompleted); +const findNextCurrentUserMark = ( + usersMarks: CurrentUserMark[] +): CurrentUserMark | undefined => { + return usersMarks.find((mark) => !mark.isCompleted) } /** @@ -37,7 +42,7 @@ const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark * @param pubkey */ const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => { - return marks.filter(mark => mark.npub === hexToNpub(pubkey)) + return marks.filter((mark) => mark.npub === hexToNpub(pubkey)) } /** @@ -57,7 +62,9 @@ const extractMarksFromSignedMeta = (meta: Meta): Mark[] => { * marked as complete. * @param currentUserMarks */ -const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolean => { +const isCurrentUserMarksComplete = ( + currentUserMarks: CurrentUserMark[] +): boolean => { return currentUserMarks.every((mark) => mark.isCompleted) } @@ -68,7 +75,7 @@ const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolea * @param markToUpdate */ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { - const indexToUpdate = marks.findIndex(mark => mark.id === markToUpdate.id); + const indexToUpdate = marks.findIndex((mark) => mark.id === markToUpdate.id) return [ ...marks.slice(0, indexToUpdate), markToUpdate, @@ -76,8 +83,13 @@ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { ] } -const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdate: CurrentUserMark): CurrentUserMark[] => { - const indexToUpdate = currentUserMarks.findIndex((m) => m.mark.id === markToUpdate.mark.id) +const updateCurrentUserMarks = ( + currentUserMarks: CurrentUserMark[], + markToUpdate: CurrentUserMark +): CurrentUserMark[] => { + const indexToUpdate = currentUserMarks.findIndex( + (m) => m.mark.id === markToUpdate.mark.id + ) return [ ...currentUserMarks.slice(0, indexToUpdate), markToUpdate, @@ -85,7 +97,7 @@ const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdat ] } -const isLast = (index: number, arr: T[]) => (index === (arr.length -1)) +const isLast = (index: number, arr: T[]) => index === arr.length - 1 export { getCurrentUserMarks, @@ -94,5 +106,5 @@ export { isCurrentUserMarksComplete, findNextCurrentUserMark, updateMarks, - updateCurrentUserMarks, + updateCurrentUserMarks } diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 70b6539..2263737 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -3,13 +3,14 @@ import * as PDFJS from 'pdfjs-dist' import { PDFDocument } from 'pdf-lib' import { Mark } from '../types/mark.ts' -PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; +PDFJS.GlobalWorkerOptions.workerSrc = + 'node_modules/pdfjs-dist/build/pdf.worker.mjs' /** * Scale between the PDF page's natural size and rendered size * @constant {number} */ -const SCALE: number = 3; +const SCALE: number = 3 /** * Defined font size used when generating a PDF. Currently it is difficult to fully * correlate font size used at the time of filling in / drawing on the PDF @@ -17,20 +18,20 @@ const SCALE: number = 3; * This should be fixed going forward. * Switching to PDF-Lib will most likely make this problem redundant. */ -const FONT_SIZE: number = 40; +const FONT_SIZE: number = 40 /** * Current font type used when generating a PDF. */ -const FONT_TYPE: string = 'Arial'; +const FONT_TYPE: string = 'Arial' /** * Converts a PDF ArrayBuffer to a generic PDF File * @param arrayBuffer of a PDF * @param fileName identifier of the pdf file */ -const toFile = (arrayBuffer: ArrayBuffer, fileName: string) : File => { - const blob = new Blob([arrayBuffer], { type: "application/pdf" }); - return new File([blob], fileName, { type: "application/pdf" }); +const toFile = (arrayBuffer: ArrayBuffer, fileName: string): File => { + const blob = new Blob([arrayBuffer], { type: 'application/pdf' }) + return new File([blob], fileName, { type: 'application/pdf' }) } /** @@ -50,42 +51,40 @@ const toPdfFile = async (file: File): Promise => { * @return PdfFile[] - an array of Sigit's internal Pdf File type */ const toPdfFiles = async (selectedFiles: File[]): Promise => { - return Promise.all(selectedFiles - .filter(isPdf) - .map(toPdfFile)); + return Promise.all(selectedFiles.filter(isPdf).map(toPdfFile)) } /** * A utility that transforms a drawing coordinate number into a CSS-compatible string * @param coordinate */ -const inPx = (coordinate: number): string => `${coordinate}px`; +const inPx = (coordinate: number): string => `${coordinate}px` /** * A utility that checks if a given file is of the pdf type * @param file */ -const isPdf = (file: File) => file.type.toLowerCase().includes('pdf'); +const isPdf = (file: File) => file.type.toLowerCase().includes('pdf') /** * Reads the pdf file binaries */ const readPdf = (file: File): Promise => { return new Promise((resolve, reject) => { - const reader = new FileReader(); + const reader = new FileReader() reader.onload = (e: any) => { const data = e.target.result resolve(data) - }; + } reader.onerror = (err) => { console.error('err', err) reject(err) - }; + } - reader.readAsDataURL(file); + reader.readAsDataURL(file) }) } @@ -94,26 +93,28 @@ const readPdf = (file: File): Promise => { * @param data pdf file bytes */ const pdfToImages = async (data: any): Promise => { - const images: string[] = []; - const pdf = await PDFJS.getDocument(data).promise; - const canvas = document.createElement("canvas"); + const images: string[] = [] + const pdf = await PDFJS.getDocument(data).promise + const canvas = document.createElement('canvas') for (let i = 0; i < pdf.numPages; i++) { - const page = await pdf.getPage(i + 1); - const viewport = page.getViewport({ scale: SCALE }); - const context = canvas.getContext("2d"); - canvas.height = viewport.height; - canvas.width = viewport.width; - await page.render({ canvasContext: context!, viewport: viewport }).promise; - images.push(canvas.toDataURL()); + const page = await pdf.getPage(i + 1) + const viewport = page.getViewport({ scale: SCALE }) + const context = canvas.getContext('2d') + canvas.height = viewport.height + canvas.width = viewport.width + await page.render({ canvasContext: context!, viewport: viewport }).promise + images.push(canvas.toDataURL()) } - return Promise.resolve(images.map((image) => { - return { - image, - drawnFields: [] - } - })) + return Promise.resolve( + images.map((image) => { + return { + image, + drawnFields: [] + } + }) + ) } /** @@ -121,34 +122,37 @@ const pdfToImages = async (data: any): Promise => { * Returns an array of encoded images where each image is a representation * of a PDF page with completed and signed marks from all users */ -const addMarks = async (file: File, marksPerPage: {[key: string]: Mark[]}) => { - const p = await readPdf(file); - const pdf = await PDFJS.getDocument(p).promise; - const canvas = document.createElement("canvas"); +const addMarks = async ( + file: File, + marksPerPage: { [key: string]: Mark[] } +) => { + const p = await readPdf(file) + const pdf = await PDFJS.getDocument(p).promise + const canvas = document.createElement('canvas') - const images: string[] = []; + const images: string[] = [] - for (let i = 0; i< pdf.numPages; i++) { - const page = await pdf.getPage(i+1) - const viewport = page.getViewport({ scale: SCALE }); - const context = canvas.getContext("2d"); - canvas.height = viewport.height; - canvas.width = viewport.width; - await page.render({ canvasContext: context!, viewport: viewport }).promise; + for (let i = 0; i < pdf.numPages; i++) { + const page = await pdf.getPage(i + 1) + const viewport = page.getViewport({ scale: SCALE }) + const context = canvas.getContext('2d') + canvas.height = viewport.height + canvas.width = viewport.width + await page.render({ canvasContext: context!, viewport: viewport }).promise - marksPerPage[i].forEach(mark => draw(mark, context!)) + marksPerPage[i].forEach((mark) => draw(mark, context!)) - images.push(canvas.toDataURL()); + images.push(canvas.toDataURL()) } - return Promise.resolve(images); + return Promise.resolve(images) } /** * Utility to scale mark in line with the PDF-to-PNG scale */ const scaleMark = (mark: Mark): Mark => { - const { location } = mark; + const { location } = mark return { ...mark, location: { @@ -165,7 +169,7 @@ const scaleMark = (mark: Mark): Mark => { * Utility to check if a Mark has value * @param mark */ -const hasValue = (mark: Mark): boolean => !!mark.value; +const hasValue = (mark: Mark): boolean => !!mark.value /** * Draws a Mark on a Canvas representation of a PDF Page @@ -173,14 +177,14 @@ const hasValue = (mark: Mark): boolean => !!mark.value; * @param ctx a Canvas representation of a specific PDF Page */ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { - const { location } = mark; + const { location } = mark - ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE; - ctx!.fillStyle = 'black'; - const textMetrics = ctx!.measureText(mark.value!); - const textX = location.left + (location.width - textMetrics.width) / 2; - const textY = location.top + (location.height + parseInt(ctx!.font)) / 2; - ctx!.fillText(mark.value!, textX, textY); + ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE + ctx!.fillStyle = 'black' + const textMetrics = ctx!.measureText(mark.value!) + const textX = location.left + (location.width - textMetrics.width) / 2 + const textY = location.top + (location.height + parseInt(ctx!.font)) / 2 + ctx!.fillText(mark.value!, textX, textY) } /** @@ -188,7 +192,7 @@ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { * @param markedPdfPages */ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { - const pdfDoc = await PDFDocument.create(); + const pdfDoc = await PDFDocument.create() for (const page of markedPdfPages) { const pngImage = await pdfDoc.embedPng(page) @@ -203,7 +207,6 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { const pdfBytes = await pdfDoc.save() return new Blob([pdfBytes], { type: 'application/pdf' }) - } /** @@ -211,9 +214,12 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { * @param arrayBuffer * @param fileName */ -const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string): Promise => { - const file = toFile(arrayBuffer, fileName); - return toPdfFile(file); +const convertToPdfFile = async ( + arrayBuffer: ArrayBuffer, + fileName: string +): Promise => { + const file = toFile(arrayBuffer, fileName) + return toPdfFile(file) } /** @@ -226,7 +232,7 @@ const groupMarksByPage = (marks: Mark[]) => { return marks .filter(hasValue) .map(scaleMark) - .reduce<{[key: number]: Mark[]}>(byPage, {}) + .reduce<{ [key: number]: Mark[] }>(byPage, {}) } /** @@ -237,11 +243,10 @@ const groupMarksByPage = (marks: Mark[]) => { * @param obj - accumulator in the reducer callback * @param mark - current value, i.e. Mark being examined */ -const byPage = (obj: { [key: number]: Mark[]}, mark: Mark) => { - const key = mark.location.page; - const curGroup = obj[key] ?? []; - return { ...obj, [key]: [...curGroup, mark] - } +const byPage = (obj: { [key: number]: Mark[] }, mark: Mark) => { + const key = mark.location.page + const curGroup = obj[key] ?? [] + return { ...obj, [key]: [...curGroup, mark] } } export { @@ -252,5 +257,5 @@ export { convertToPdfFile, addMarks, convertToPdfBlob, - groupMarksByPage, -} \ No newline at end of file + groupMarksByPage +} diff --git a/src/utils/sign.ts b/src/utils/sign.ts index 7b2dd84..3369c54 100644 --- a/src/utils/sign.ts +++ b/src/utils/sign.ts @@ -5,7 +5,10 @@ import { Meta } from '../types' * This function returns the signature of last signer * It will be used in the content of export signature's signedEvent */ -const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | null => { +const getLastSignersSig = ( + meta: Meta, + signers: `npub1${string}`[] +): string | null => { // if there're no signers then use creator's signature if (signers.length === 0) { try { @@ -21,13 +24,11 @@ const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | nu // get the signature of last signer try { - const lastSignatureEvent: Event = JSON.parse( - meta.docSignatures[lastSigner] - ) + const lastSignatureEvent: Event = JSON.parse(meta.docSignatures[lastSigner]) return lastSignatureEvent.sig } catch (error) { return null } } -export { getLastSignersSig } \ No newline at end of file +export { getLastSignersSig } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ed830a2..e68e687 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -73,9 +73,9 @@ export const timeout = (ms: number = 60000) => { * @param fileHashes */ export const getFilesWithHashes = ( - files: { [filename: string ]: PdfFile }, + files: { [filename: string]: PdfFile }, fileHashes: { [key: string]: string | null } - ) => { +) => { return Object.entries(files).map(([filename, pdfFile]) => { return { pdfFile, filename, hash: fileHashes[filename] } }) diff --git a/src/utils/zip.ts b/src/utils/zip.ts index 2d33aa5..7a7a49f 100644 --- a/src/utils/zip.ts +++ b/src/utils/zip.ts @@ -36,17 +36,12 @@ const readContentOfZipEntry = async ( const loadZip = async (data: InputFileFormat): Promise => { try { - return await JSZip.loadAsync(data); + return await JSZip.loadAsync(data) } catch (err: any) { console.log('err in loading zip file :>> ', err) toast.error(err.message || 'An error occurred in loading zip file.') - return null; + return null } } -export { - readContentOfZipEntry, - loadZip -} - - +export { readContentOfZipEntry, loadZip } From 1297acc7dbfab4e2f6a49fc3e6aa224131b269fd Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 7 Aug 2024 14:51:44 +0200 Subject: [PATCH 29/49] refactor: PR review changes and lint fixes --- src/pages/sign/index.tsx | 154 ++++++++++++++++++++------------------- 1 file changed, 79 insertions(+), 75 deletions(-) diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 6f3231b..3798863 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -5,7 +5,7 @@ import JSZip from 'jszip' import _ from 'lodash' import { MuiFileInput } from 'mui-file-input' import { Event, verifyEvent } from 'nostr-tools' -import { useEffect, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useSelector } from 'react-redux' import { useLocation, useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' @@ -208,7 +208,6 @@ export const SignPage = () => { const signedMarks = extractMarksFromSignedMeta(meta) const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks) setCurrentUserMarks(currentUserMarks) - // setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null) setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks)) } @@ -218,7 +217,79 @@ export const SignPage = () => { if (meta) { handleUpdatedMeta(meta) } - }, [meta]) + }, [meta, usersPubkey]) + + const decrypt = useCallback( + async (file: File) => { + setLoadingSpinnerDesc('Decrypting file') + + const zip = await loadZip(file) + if (!zip) return + + const parsedKeysJson = await parseKeysJson(zip) + if (!parsedKeysJson) return + + const encryptedArrayBuffer = await readContentOfZipEntry( + zip, + 'compressed.sigit', + 'arraybuffer' + ) + + if (!encryptedArrayBuffer) return + + const { keys, sender } = parsedKeysJson + + for (const key of keys) { + // Set up event listener for authentication event + nostrController.on('nsecbunker-auth', (url) => { + setAuthUrl(url) + }) + + // Set up timeout promise to handle encryption timeout + const timeoutPromise = new Promise((_, reject) => { + setTimeout(() => { + reject(new Error('Timeout occurred')) + }, 60000) // Timeout duration = 60 seconds + }) + + // decrypt the encryptionKey, with timeout + const encryptionKey = await Promise.race([ + nostrController.nip04Decrypt(sender, key), + timeoutPromise + ]) + .then((res) => { + return res + }) + .catch((err) => { + console.log('err :>> ', err) + return null + }) + .finally(() => { + setAuthUrl(undefined) // Clear authentication URL + }) + + // Return if encryption failed + if (!encryptionKey) continue + + const arrayBuffer = await decryptArrayBuffer( + encryptedArrayBuffer, + encryptionKey + ) + .catch((err) => { + console.log('err in decryption:>> ', err) + return null + }) + .finally(() => { + setIsLoading(false) + }) + + if (arrayBuffer) return arrayBuffer + } + + return null + }, + [nostrController] + ) useEffect(() => { // online mode - from create and home page views @@ -276,7 +347,7 @@ export const SignPage = () => { setIsLoading(false) setDisplayInput(true) } - }, [decryptedArrayBuffer, uploadedZip, metaInNavState]) + }, [decryptedArrayBuffer, uploadedZip, metaInNavState, decrypt]) const handleArrayBufferFromBlossom = async ( arrayBuffer: ArrayBuffer, @@ -354,75 +425,6 @@ export const SignPage = () => { }) } - const decrypt = async (file: File) => { - setLoadingSpinnerDesc('Decrypting file') - - const zip = await loadZip(file) - if (!zip) return - - const parsedKeysJson = await parseKeysJson(zip) - if (!parsedKeysJson) return - - const encryptedArrayBuffer = await readContentOfZipEntry( - zip, - 'compressed.sigit', - 'arraybuffer' - ) - - if (!encryptedArrayBuffer) return - - const { keys, sender } = parsedKeysJson - - for (const key of keys) { - // Set up event listener for authentication event - nostrController.on('nsecbunker-auth', (url) => { - setAuthUrl(url) - }) - - // Set up timeout promise to handle encryption timeout - const timeoutPromise = new Promise((_, reject) => { - setTimeout(() => { - reject(new Error('Timeout occurred')) - }, 60000) // Timeout duration = 60 seconds - }) - - // decrypt the encryptionKey, with timeout - const encryptionKey = await Promise.race([ - nostrController.nip04Decrypt(sender, key), - timeoutPromise - ]) - .then((res) => { - return res - }) - .catch((err) => { - console.log('err :>> ', err) - return null - }) - .finally(() => { - setAuthUrl(undefined) // Clear authentication URL - }) - - // Return if encryption failed - if (!encryptionKey) continue - - const arrayBuffer = await decryptArrayBuffer( - encryptedArrayBuffer, - encryptionKey - ) - .catch((err) => { - console.log('err in decryption:>> ', err) - return null - }) - .finally(() => { - setIsLoading(false) - }) - - if (arrayBuffer) return arrayBuffer - } - - return null - } - const handleDecryptedArrayBuffer = async (arrayBuffer: ArrayBuffer) => { const decryptedZipFile = new File([arrayBuffer], 'decrypted.zip') @@ -618,10 +620,12 @@ export const SignPage = () => { } // Handle errors during zip file generation - const handleZipError = (err: any) => { + const handleZipError = (err: unknown) => { console.log('Error in zip:>> ', err) setIsLoading(false) - toast.error(err.message || 'Error occurred in generating zip file') + if (err instanceof Error) { + toast.error(err.message || 'Error occurred in generating zip file') + } return null } From ff1e12788825bce1a47974e5d5bff39c84cc4746 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 17:42:53 +0300 Subject: [PATCH 30/49] chore: fixes prettier rule --- src/utils/const.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/utils/const.ts b/src/utils/const.ts index aed1807..33befbf 100644 --- a/src/utils/const.ts +++ b/src/utils/const.ts @@ -9,4 +9,4 @@ export const MARK_TYPE_TRANSLATION: { [key: string]: string } = { * Calc based on: 7 * 24 * 60 * 60 * 1000 */ export const ONE_WEEK_IN_MS: number = 604800000 -export const SIGIT_RELAY: string = 'wss://relay.sigit.io' \ No newline at end of file +export const SIGIT_RELAY: string = 'wss://relay.sigit.io' From 7e420ef5832967ab89bef3faf430b4749395bf51 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 17:49:13 +0300 Subject: [PATCH 31/49] chore: prettier --- src/components/DrawPDFFields/index.tsx | 244 +++++++++++++++++-------- src/components/PDFView/PdfItem.tsx | 44 +++-- src/components/PDFView/PdfMarkItem.tsx | 20 +- src/components/PDFView/PdfMarking.tsx | 67 +++---- src/components/PDFView/PdfPageItem.tsx | 32 ++-- src/components/PDFView/index.tsx | 47 +++-- src/controllers/MetadataController.ts | 17 +- src/index.css | 18 +- src/pages/create/index.tsx | 55 +++--- src/pages/settings/relays/index.tsx | 3 +- src/pages/sign/MarkFormField.tsx | 6 +- src/pages/sign/index.tsx | 74 ++++---- src/pages/verify/index.tsx | 31 ++-- src/types/drawing.ts | 6 +- src/types/mark.ts | 24 +-- src/types/zip.ts | 24 +-- src/utils/mark.ts | 34 ++-- src/utils/pdf.ts | 143 ++++++++------- src/utils/relays.ts | 21 ++- src/utils/sign.ts | 11 +- src/utils/utils.ts | 4 +- src/utils/zip.ts | 11 +- 22 files changed, 549 insertions(+), 387 deletions(-) diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index 148e70f..e7880e4 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -1,15 +1,43 @@ -import { AccessTime, CalendarMonth, ExpandMore, Gesture, PictureAsPdf, Badge, Work, Close } from '@mui/icons-material' -import { Box, Typography, Accordion, AccordionDetails, AccordionSummary, CircularProgress, FormControl, InputLabel, MenuItem, Select } from '@mui/material' +import { + AccessTime, + CalendarMonth, + ExpandMore, + Gesture, + PictureAsPdf, + Badge, + Work, + Close +} from '@mui/icons-material' +import { + Box, + Typography, + Accordion, + AccordionDetails, + AccordionSummary, + CircularProgress, + FormControl, + InputLabel, + MenuItem, + Select +} from '@mui/material' import styles from './style.module.scss' import { useEffect, useState } from 'react' -import * as PDFJS from "pdfjs-dist"; -import { ProfileMetadata, User } from '../../types'; -import { PdfFile, DrawTool, MouseState, PdfPage, DrawnField, MarkType } from '../../types/drawing'; -import { truncate } from 'lodash'; -import { hexToNpub } from '../../utils'; +import * as PDFJS from 'pdfjs-dist' +import { ProfileMetadata, User } from '../../types' +import { + PdfFile, + DrawTool, + MouseState, + PdfPage, + DrawnField, + MarkType +} from '../../types/drawing' +import { truncate } from 'lodash' +import { hexToNpub } from '../../utils' import { toPdfFiles } from '../../utils/pdf.ts' -PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; +PDFJS.GlobalWorkerOptions.workerSrc = + 'node_modules/pdfjs-dist/build/pdf.worker.mjs' interface Props { selectedFiles: File[] @@ -20,44 +48,43 @@ interface Props { export const DrawPDFFields = (props: Props) => { const { selectedFiles } = props - + const [pdfFiles, setPdfFiles] = useState([]) const [parsingPdf, setParsingPdf] = useState(false) const [showDrawToolBox, setShowDrawToolBox] = useState(false) - + const [selectedTool, setSelectedTool] = useState() const [toolbox] = useState([ { identifier: MarkType.SIGNATURE, - icon: , + icon: , label: 'Signature', active: false - }, { identifier: MarkType.FULLNAME, - icon: , + icon: , label: 'Full Name', active: true }, { identifier: MarkType.JOBTITLE, - icon: , + icon: , label: 'Job Title', active: false }, { identifier: MarkType.DATE, - icon: , + icon: , label: 'Date', active: false }, { identifier: MarkType.DATETIME, - icon: , + icon: , label: 'Datetime', active: false - }, + } ]) const [mouseState, setMouseState] = useState({ @@ -67,7 +94,7 @@ export const DrawPDFFields = (props: Props) => { useEffect(() => { if (selectedFiles) { setParsingPdf(true) - + parsePdfPages().finally(() => { setParsingPdf(false) }) @@ -81,13 +108,13 @@ export const DrawPDFFields = (props: Props) => { /** * Drawing events */ - useEffect(() => { + useEffect(() => { // window.addEventListener('mousedown', onMouseDown); - window.addEventListener('mouseup', onMouseUp); - + window.addEventListener('mouseup', onMouseUp) + return () => { // window.removeEventListener('mousedown', onMouseDown); - window.removeEventListener('mouseup', onMouseUp); + window.removeEventListener('mouseup', onMouseUp) } }, []) @@ -106,7 +133,7 @@ export const DrawPDFFields = (props: Props) => { const onMouseDown = (event: any, page: PdfPage) => { // Proceed only if left click if (event.button !== 0) return - + // Only allow drawing if mouse is not over other drawn element const isOverPdfImageWrapper = event.target.tagName === 'IMG' @@ -158,11 +185,11 @@ export const DrawPDFFields = (props: Props) => { */ const onMouseMove = (event: any, page: PdfPage) => { if (mouseState.clicked && selectedTool) { - const lastElementIndex = page.drawnFields.length -1 + const lastElementIndex = page.drawnFields.length - 1 const lastDrawnField = page.drawnFields[lastElementIndex] const { mouseX, mouseY } = getMouseCoordinates(event) - + const width = mouseX - lastDrawnField.left const height = mouseY - lastDrawnField.top @@ -172,10 +199,10 @@ export const DrawPDFFields = (props: Props) => { const currentDrawnFields = page.drawnFields currentDrawnFields[lastElementIndex] = lastDrawnField - + refreshPdfFiles() } - } + } /** * Fired when event happens on the drawn element which will be moved @@ -189,7 +216,7 @@ export const DrawPDFFields = (props: Props) => { */ const onDrawnFieldMouseDown = (event: any) => { event.stopPropagation() - + // Proceed only if left click if (event.button !== 0) return @@ -212,7 +239,10 @@ export const DrawPDFFields = (props: Props) => { */ const onDranwFieldMouseMove = (event: any, drawnField: DrawnField) => { if (mouseState.dragging) { - const { mouseX, mouseY, rect } = getMouseCoordinates(event, event.target.parentNode) + const { mouseX, mouseY, rect } = getMouseCoordinates( + event, + event.target.parentNode + ) const coordsOffset = mouseState.coordsInWrapper if (coordsOffset) { @@ -258,8 +288,11 @@ export const DrawPDFFields = (props: Props) => { */ const onResizeHandleMouseMove = (event: any, drawnField: DrawnField) => { if (mouseState.resizing) { - const { mouseX, mouseY } = getMouseCoordinates(event, event.target.parentNode.parentNode) - + const { mouseX, mouseY } = getMouseCoordinates( + event, + event.target.parentNode.parentNode + ) + const width = mouseX - drawnField.left const height = mouseY - drawnField.top @@ -277,10 +310,18 @@ export const DrawPDFFields = (props: Props) => { * @param pdfPageIndex pdf page index * @param drawnFileIndex drawn file index */ - const onRemoveHandleMouseDown = (event: any, pdfFileIndex: number, pdfPageIndex: number, drawnFileIndex: number) => { + const onRemoveHandleMouseDown = ( + event: any, + pdfFileIndex: number, + pdfPageIndex: number, + drawnFileIndex: number + ) => { event.stopPropagation() - - pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice(drawnFileIndex, 1) + + pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice( + drawnFileIndex, + 1 + ) } /** @@ -300,9 +341,9 @@ export const DrawPDFFields = (props: Props) => { */ const getMouseCoordinates = (event: any, customTarget?: any) => { const target = customTarget ? customTarget : event.target - const rect = target.getBoundingClientRect(); - const mouseX = event.clientX - rect.left; //x position within the element. - const mouseY = event.clientY - rect.top; //y position within the element. + const rect = target.getBoundingClientRect() + const mouseX = event.clientX - rect.left //x position within the element. + const mouseY = event.clientY - rect.top //y position within the element. return { mouseX, @@ -316,8 +357,8 @@ export const DrawPDFFields = (props: Props) => { * creates the pdfFiles object and sets to a state */ const parsePdfPages = async () => { - const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles); - + const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles) + setPdfFiles(pdfFiles) } @@ -326,7 +367,7 @@ export const DrawPDFFields = (props: Props) => { * @returns if expanded pdf accordion is present */ const hasExpandedPdf = () => { - return !!pdfFiles.filter(pdfFile => !!pdfFile.expanded).length + return !!pdfFiles.filter((pdfFile) => !!pdfFile.expanded).length } const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => { @@ -355,9 +396,11 @@ export const DrawPDFFields = (props: Props) => { */ const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => { return ( - + {pdfFile.pages.map((page, pdfPageIndex: number) => { return (
{ marginBottom: '10px' }} className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`} - onMouseMove={(event) => {onMouseMove(event, page)}} - onMouseDown={(event) => {onMouseDown(event, page)}} + onMouseMove={(event) => { + onMouseMove(event, page) + }} + onMouseDown={(event) => { + onMouseDown(event, page) + }} > - + {page.drawnFields.map((drawnField, drawnFieldIndex: number) => { return (
{ onDranwFieldMouseMove(event, drawnField)}} + onMouseMove={(event) => { + onDranwFieldMouseMove(event, drawnField) + }} className={styles.drawingRectangle} style={{ left: `${drawnField.left}px`, @@ -389,41 +442,68 @@ export const DrawPDFFields = (props: Props) => { > {onResizeHandleMouseMove(event, drawnField)}} + onMouseMove={(event) => { + onResizeHandleMouseMove(event, drawnField) + }} className={styles.resizeHandle} > {onRemoveHandleMouseDown(event, pdfFileIndex, pdfPageIndex, drawnFieldIndex)}} + onMouseDown={(event) => { + onRemoveHandleMouseDown( + event, + pdfFileIndex, + pdfPageIndex, + drawnFieldIndex + ) + }} className={styles.removeHandle} > - + -
- + Counterpart @@ -435,13 +515,13 @@ export const DrawPDFFields = (props: Props) => { ) })} - ) + ) } if (parsingPdf) { return ( - + ) } @@ -454,22 +534,28 @@ export const DrawPDFFields = (props: Props) => { Draw fields on the PDFs: - + {pdfFiles.map((pdfFile, pdfFileIndex: number) => { return ( - {handleAccordionExpandChange(expanded, pdfFile)}}> + { + handleAccordionExpandChange(expanded, pdfFile) + }} + > } aria-controls={`panel${pdfFileIndex}-content`} id={`panel${pdfFileIndex}header`} > - + {pdfFile.file.name} {getPdfPages(pdfFile, pdfFileIndex)} - + ) })} @@ -477,16 +563,22 @@ export const DrawPDFFields = (props: Props) => { {showDrawToolBox && ( - {toolbox.filter(drawTool => drawTool.active).map((drawTool: DrawTool, index: number) => { - return ( - {handleToolSelect(drawTool)}} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}> - { drawTool.icon } - { drawTool.label } - - ) - })} + {toolbox + .filter((drawTool) => drawTool.active) + .map((drawTool: DrawTool, index: number) => { + return ( + { + handleToolSelect(drawTool) + }} + className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`} + > + {drawTool.icon} + {drawTool.label} + + ) + })} )} diff --git a/src/components/PDFView/PdfItem.tsx b/src/components/PDFView/PdfItem.tsx index eb5ceff..6e8aa64 100644 --- a/src/components/PDFView/PdfItem.tsx +++ b/src/components/PDFView/PdfItem.tsx @@ -1,6 +1,6 @@ import { PdfFile } from '../../types/drawing.ts' import { CurrentUserMark } from '../../types/mark.ts' -import PdfPageItem from './PdfPageItem.tsx'; +import PdfPageItem from './PdfPageItem.tsx' interface PdfItemProps { pdfFile: PdfFile @@ -13,23 +13,31 @@ interface PdfItemProps { /** * Responsible for displaying pages of a single Pdf File. */ -const PdfItem = ({ pdfFile, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfItemProps) => { - const filterByPage = (marks: CurrentUserMark[], page: number): CurrentUserMark[] => { - return marks.filter((m) => m.mark.location.page === page); +const PdfItem = ({ + pdfFile, + currentUserMarks, + handleMarkClick, + selectedMarkValue, + selectedMark +}: PdfItemProps) => { + const filterByPage = ( + marks: CurrentUserMark[], + page: number + ): CurrentUserMark[] => { + return marks.filter((m) => m.mark.location.page === page) } - return ( - pdfFile.pages.map((page, i) => { - return ( - - ) - })) + return pdfFile.pages.map((page, i) => { + return ( + + ) + }) } -export default PdfItem \ No newline at end of file +export default PdfItem diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index 7a2b24b..90caa87 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -12,14 +12,18 @@ interface PdfMarkItemProps { /** * Responsible for display an individual Pdf Mark. */ -const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMark }: PdfMarkItemProps) => { - const { location } = userMark.mark; - const handleClick = () => handleMarkClick(userMark.mark.id); - const getMarkValue = () => ( +const PdfMarkItem = ({ + selectedMark, + handleMarkClick, + selectedMarkValue, + userMark +}: PdfMarkItemProps) => { + const { location } = userMark.mark + const handleClick = () => handleMarkClick(userMark.mark.id) + const getMarkValue = () => selectedMark?.mark.id === userMark.mark.id ? selectedMarkValue : userMark.mark.value - ) return (
{getMarkValue()}
+ > + {getMarkValue()} +
) } -export default PdfMarkItem \ No newline at end of file +export default PdfMarkItem diff --git a/src/components/PDFView/PdfMarking.tsx b/src/components/PDFView/PdfMarking.tsx index a6dc1a4..0e47ab1 100644 --- a/src/components/PDFView/PdfMarking.tsx +++ b/src/components/PDFView/PdfMarking.tsx @@ -6,17 +6,17 @@ import React, { useState, useEffect } from 'react' import { findNextCurrentUserMark, isCurrentUserMarksComplete, - updateCurrentUserMarks, + updateCurrentUserMarks } from '../../utils' import { EMPTY } from '../../utils/const.ts' import { Container } from '../Container' import styles from '../../pages/sign/style.module.scss' interface PdfMarkingProps { - files: { pdfFile: PdfFile, filename: string, hash: string | null }[], - currentUserMarks: CurrentUserMark[], - setIsReadyToSign: (isReadyToSign: boolean) => void, - setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void, + files: { pdfFile: PdfFile; filename: string; hash: string | null }[] + currentUserMarks: CurrentUserMark[] + setIsReadyToSign: (isReadyToSign: boolean) => void + setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void setUpdatedMarks: (markToUpdate: Mark) => void } @@ -35,21 +35,21 @@ const PdfMarking = (props: PdfMarkingProps) => { setUpdatedMarks } = props const [selectedMark, setSelectedMark] = useState(null) - const [selectedMarkValue, setSelectedMarkValue] = useState("") + const [selectedMarkValue, setSelectedMarkValue] = useState('') useEffect(() => { setSelectedMark(findNextCurrentUserMark(currentUserMarks) || null) }, [currentUserMarks]) const handleMarkClick = (id: number) => { - const nextMark = currentUserMarks.find((mark) => mark.mark.id === id); - setSelectedMark(nextMark!); - setSelectedMarkValue(nextMark?.mark.value ?? EMPTY); + const nextMark = currentUserMarks.find((mark) => mark.mark.id === id) + setSelectedMark(nextMark!) + setSelectedMarkValue(nextMark?.mark.value ?? EMPTY) } const handleSubmit = (event: React.FormEvent) => { - event.preventDefault(); - if (!selectedMarkValue || !selectedMark) return; + event.preventDefault() + if (!selectedMarkValue || !selectedMark) return const updatedMark: CurrentUserMark = { ...selectedMark, @@ -61,7 +61,10 @@ const PdfMarking = (props: PdfMarkingProps) => { } setSelectedMarkValue(EMPTY) - const updatedCurrentUserMarks = updateCurrentUserMarks(currentUserMarks, updatedMark); + const updatedCurrentUserMarks = updateCurrentUserMarks( + currentUserMarks, + updatedMark + ) setCurrentUserMarks(updatedCurrentUserMarks) setSelectedMark(findNextCurrentUserMark(updatedCurrentUserMarks) || null) console.log(isCurrentUserMarksComplete(updatedCurrentUserMarks)) @@ -69,32 +72,32 @@ const PdfMarking = (props: PdfMarkingProps) => { setUpdatedMarks(updatedMark.mark) } - const handleChange = (event: React.ChangeEvent) => setSelectedMarkValue(event.target.value) + const handleChange = (event: React.ChangeEvent) => + setSelectedMarkValue(event.target.value) return ( <> - { - currentUserMarks?.length > 0 && ( - )} - { - selectedMark !== null && ( - - )} + {currentUserMarks?.length > 0 && ( + + )} + {selectedMark !== null && ( + + )} ) } -export default PdfMarking \ No newline at end of file +export default PdfMarking diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx index d289a6e..241474e 100644 --- a/src/components/PDFView/PdfPageItem.tsx +++ b/src/components/PDFView/PdfPageItem.tsx @@ -13,7 +13,13 @@ interface PdfPageProps { /** * Responsible for rendering a single Pdf Page and its Marks */ -const PdfPageItem = ({ page, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfPageProps) => { +const PdfPageItem = ({ + page, + currentUserMarks, + handleMarkClick, + selectedMarkValue, + selectedMark +}: PdfPageProps) => { return (
- - { - currentUserMarks.map((m, i) => ( - + {currentUserMarks.map((m, i) => ( + ))}
) } -export default PdfPageItem \ No newline at end of file +export default PdfPageItem diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 14834a3..8a14e55 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -4,7 +4,7 @@ import PdfItem from './PdfItem.tsx' import { CurrentUserMark } from '../../types/mark.ts' interface PdfViewProps { - files: { pdfFile: PdfFile, filename: string, hash: string | null }[] + files: { pdfFile: PdfFile; filename: string; hash: string | null }[] currentUserMarks: CurrentUserMark[] handleMarkClick: (id: number) => void selectedMarkValue: string @@ -14,29 +14,38 @@ interface PdfViewProps { /** * Responsible for rendering Pdf files. */ -const PdfView = ({ files, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfViewProps) => { - const filterByFile = (currentUserMarks: CurrentUserMark[], hash: string): CurrentUserMark[] => { - return currentUserMarks.filter((currentUserMark) => currentUserMark.mark.pdfFileHash === hash) +const PdfView = ({ + files, + currentUserMarks, + handleMarkClick, + selectedMarkValue, + selectedMark +}: PdfViewProps) => { + const filterByFile = ( + currentUserMarks: CurrentUserMark[], + hash: string + ): CurrentUserMark[] => { + return currentUserMarks.filter( + (currentUserMark) => currentUserMark.mark.pdfFileHash === hash + ) } return ( - { - files.map(({ pdfFile, hash }, i) => { - if (!hash) return - return ( - { + if (!hash) return + return ( + - ) - }) - } + ) + })} ) } -export default PdfView; \ No newline at end of file +export default PdfView diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index e2f6852..cd3d819 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -164,13 +164,18 @@ export class MetadataController extends EventEmitter { * or a fallback RelaySet with Sigit's Relay */ public findRelayListMetadata = async (hexKey: string): Promise => { - const relayEvent = await findRelayListInCache(hexKey) - || await findRelayListAndUpdateCache([this.specialMetadataRelay], hexKey) - || await findRelayListAndUpdateCache(await this.nostrController.getMostPopularRelays(), hexKey) + const relayEvent = + (await findRelayListInCache(hexKey)) || + (await findRelayListAndUpdateCache( + [this.specialMetadataRelay], + hexKey + )) || + (await findRelayListAndUpdateCache( + await this.nostrController.getMostPopularRelays(), + hexKey + )) - return relayEvent - ? getUserRelaySet(relayEvent.tags) - : getDefaultRelaySet() + return relayEvent ? getUserRelaySet(relayEvent.tags) : getDefaultRelaySet() } public extractProfileMetadataContent = (event: Event) => { diff --git a/src/index.css b/src/index.css index 6a734df..76373ff 100644 --- a/src/index.css +++ b/src/index.css @@ -111,7 +111,9 @@ button:disabled { /* Fonts */ @font-face { font-family: 'Roboto'; - src: local('Roboto Medium'), local('Roboto-Medium'), + src: + local('Roboto Medium'), + local('Roboto-Medium'), url('assets/fonts/roboto-medium.woff2') format('woff2'), url('assets/fonts/roboto-medium.woff') format('woff'); font-weight: 500; @@ -121,7 +123,9 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto Light'), local('Roboto-Light'), + src: + local('Roboto Light'), + local('Roboto-Light'), url('assets/fonts/roboto-light.woff2') format('woff2'), url('assets/fonts/roboto-light.woff') format('woff'); font-weight: 300; @@ -131,7 +135,9 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto Bold'), local('Roboto-Bold'), + src: + local('Roboto Bold'), + local('Roboto-Bold'), url('assets/fonts/roboto-bold.woff2') format('woff2'), url('assets/fonts/roboto-bold.woff') format('woff'); font-weight: bold; @@ -141,10 +147,12 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: local('Roboto'), local('Roboto-Regular'), + src: + local('Roboto'), + local('Roboto-Regular'), url('assets/fonts/roboto-regular.woff2') format('woff2'), url('assets/fonts/roboto-regular.woff') format('woff'); font-weight: normal; font-style: normal; font-display: swap; -} \ No newline at end of file +} diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 830f9ef..023dd02 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -341,29 +341,30 @@ export const CreatePage = () => { return fileHashes } - const createMarks = (fileHashes: { [key: string]: string }) : Mark[] => { - return drawnPdfs.flatMap((drawnPdf) => { - const fileHash = fileHashes[drawnPdf.file.name]; - return drawnPdf.pages.flatMap((page, index) => { - return page.drawnFields.map((drawnField) => { - return { - type: drawnField.type, - location: { - page: index, - top: drawnField.top, - left: drawnField.left, - height: drawnField.height, - width: drawnField.width, - }, - npub: drawnField.counterpart, - pdfFileHash: fileHash - } + const createMarks = (fileHashes: { [key: string]: string }): Mark[] => { + return drawnPdfs + .flatMap((drawnPdf) => { + const fileHash = fileHashes[drawnPdf.file.name] + return drawnPdf.pages.flatMap((page, index) => { + return page.drawnFields.map((drawnField) => { + return { + type: drawnField.type, + location: { + page: index, + top: drawnField.top, + left: drawnField.left, + height: drawnField.height, + width: drawnField.width + }, + npub: drawnField.counterpart, + pdfFileHash: fileHash + } + }) }) }) - }) .map((mark, index) => { - return {...mark, id: index } - }); + return { ...mark, id: index } + }) } // Handle errors during zip file generation @@ -431,13 +432,9 @@ export const CreatePage = () => { if (!arraybuffer) return null - return new File( - [new Blob([arraybuffer])], - `${unixNow}.sigit.zip`, - { - type: 'application/zip' - } - ) + return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { + type: 'application/zip' + }) } // Handle errors during file upload @@ -545,9 +542,7 @@ export const CreatePage = () => { : viewers.map((viewer) => viewer.pubkey) ).filter((receiver) => receiver !== usersPubkey) - return receivers.map((receiver) => - sendNotification(receiver, meta) - ) + return receivers.map((receiver) => sendNotification(receiver, meta)) } const handleCreate = async () => { diff --git a/src/pages/settings/relays/index.tsx b/src/pages/settings/relays/index.tsx index b06908a..929a093 100644 --- a/src/pages/settings/relays/index.tsx +++ b/src/pages/settings/relays/index.tsx @@ -91,7 +91,8 @@ export const RelaysPage = () => { if (isMounted) { if ( !relaysState?.mapUpdated || - newRelayMap?.mapUpdated !== undefined && (newRelayMap?.mapUpdated > relaysState?.mapUpdated) + (newRelayMap?.mapUpdated !== undefined && + newRelayMap?.mapUpdated > relaysState?.mapUpdated) ) { if ( !relaysState?.map || diff --git a/src/pages/sign/MarkFormField.tsx b/src/pages/sign/MarkFormField.tsx index 4de82a4..4cb943b 100644 --- a/src/pages/sign/MarkFormField.tsx +++ b/src/pages/sign/MarkFormField.tsx @@ -15,8 +15,8 @@ interface MarkFormFieldProps { * Responsible for rendering a form field connected to a mark and keeping track of its value. */ const MarkFormField = (props: MarkFormFieldProps) => { - const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props; - const getSubmitButton = () => selectedMark.isLast ? 'Complete' : 'Next'; + const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props + const getSubmitButton = () => (selectedMark.isLast ? 'Complete' : 'Next') return (
@@ -34,4 +34,4 @@ const MarkFormField = (props: MarkFormFieldProps) => { ) } -export default MarkFormField; \ No newline at end of file +export default MarkFormField diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 07c385e..6f3231b 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -16,13 +16,16 @@ import { State } from '../../store/rootReducer' import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types' import { decryptArrayBuffer, - encryptArrayBuffer, extractMarksFromSignedMeta, + encryptArrayBuffer, + extractMarksFromSignedMeta, extractZipUrlAndEncryptionKey, generateEncryptionKey, - generateKeysFile, getFilesWithHashes, + generateKeysFile, + getFilesWithHashes, getHash, hexToNpub, - isOnline, loadZip, + isOnline, + loadZip, now, npubToHex, parseJson, @@ -41,7 +44,8 @@ import { getLastSignersSig } from '../../utils/sign.ts' import { filterMarksByPubkey, getCurrentUserMarks, - isCurrentUserMarksComplete, updateMarks + isCurrentUserMarksComplete, + updateMarks } from '../../utils' import PdfMarking from '../../components/PDFView/PdfMarking.tsx' enum SignedStatus { @@ -81,7 +85,7 @@ export const SignPage = () => { const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [viewers, setViewers] = useState<`npub1${string}`[]>([]) - const [marks, setMarks] = useState([]) + const [marks, setMarks] = useState([]) const [creatorFileHashes, setCreatorFileHashes] = useState<{ [key: string]: string }>({}) @@ -100,8 +104,10 @@ export const SignPage = () => { const [authUrl, setAuthUrl] = useState() const nostrController = NostrController.getInstance() - const [currentUserMarks, setCurrentUserMarks] = useState([]); - const [isReadyToSign, setIsReadyToSign] = useState(false); + const [currentUserMarks, setCurrentUserMarks] = useState( + [] + ) + const [isReadyToSign, setIsReadyToSign] = useState(false) useEffect(() => { if (signers.length > 0) { @@ -192,13 +198,16 @@ export const SignPage = () => { setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) setSubmittedBy(createSignatureEvent.pubkey) - setMarks(createSignatureContent.markConfig); + setMarks(createSignatureContent.markConfig) if (usersPubkey) { - const metaMarks = filterMarksByPubkey(createSignatureContent.markConfig, usersPubkey!) + const metaMarks = filterMarksByPubkey( + createSignatureContent.markConfig, + usersPubkey! + ) const signedMarks = extractMarksFromSignedMeta(meta) - const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks); - setCurrentUserMarks(currentUserMarks); + const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks) + setCurrentUserMarks(currentUserMarks) // setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null) setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks)) } @@ -307,7 +316,7 @@ export const SignPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName); + files[fileName] = await convertToPdfFile(arrayBuffer, fileName) const hash = await getHash(arrayBuffer) if (hash) { @@ -348,7 +357,7 @@ export const SignPage = () => { const decrypt = async (file: File) => { setLoadingSpinnerDesc('Decrypting file') - const zip = await loadZip(file); + const zip = await loadZip(file) if (!zip) return const parsedKeysJson = await parseKeysJson(zip) @@ -439,7 +448,7 @@ export const SignPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName); + files[fileName] = await convertToPdfFile(arrayBuffer, fileName) const hash = await getHash(arrayBuffer) if (hash) { @@ -520,7 +529,10 @@ export const SignPage = () => { } // Sign the event for the meta file - const signEventForMeta = async (signerContent: { prevSig: string, marks: Mark[] }) => { + const signEventForMeta = async (signerContent: { + prevSig: string + marks: Mark[] + }) => { return await signEventForMetaFile( JSON.stringify(signerContent), nostrController, @@ -529,8 +541,8 @@ export const SignPage = () => { } const getSignerMarksForMeta = (): Mark[] | undefined => { - if (currentUserMarks.length === 0) return; - return currentUserMarks.map(( { mark }: CurrentUserMark) => mark); + if (currentUserMarks.length === 0) return + return currentUserMarks.map(({ mark }: CurrentUserMark) => mark) } // Update the meta signatures @@ -600,13 +612,9 @@ export const SignPage = () => { if (!arraybuffer) return null - return new File( - [new Blob([arraybuffer])], - `${unixNow}.sigit.zip`, - { - type: 'application/zip' - } - ) + return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { + type: 'application/zip' + }) } // Handle errors during zip file generation @@ -694,7 +702,7 @@ export const SignPage = () => { setIsLoading(true) setLoadingSpinnerDesc('Signing nostr event') - if (!meta) return; + if (!meta) return const prevSig = getLastSignersSig(meta, signers) if (!prevSig) return @@ -918,11 +926,13 @@ export const SignPage = () => { ) } - return + return ( + + ) } diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index 6290c02..c5a0e8e 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -23,14 +23,17 @@ import { SignedEventContent } from '../../types' import { - decryptArrayBuffer, extractMarksFromSignedMeta, + decryptArrayBuffer, + extractMarksFromSignedMeta, extractZipUrlAndEncryptionKey, getHash, - hexToNpub, now, + hexToNpub, + now, npubToHex, parseJson, readContentOfZipEntry, - shorten, signEventForMetaFile + shorten, + signEventForMetaFile } from '../../utils' import styles from './style.module.scss' import { Cancel, CheckCircle } from '@mui/icons-material' @@ -41,7 +44,7 @@ import { addMarks, convertToPdfBlob, convertToPdfFile, - groupMarksByPage, + groupMarksByPage } from '../../utils/pdf.ts' import { State } from '../../store/rootReducer.ts' import { useSelector } from 'react-redux' @@ -78,7 +81,7 @@ export const VerifyPage = () => { const [currentFileHashes, setCurrentFileHashes] = useState<{ [key: string]: string | null }>({}) - const [files, setFiles] = useState<{ [filename: string]: PdfFile}>({}) + const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({}) const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>( {} @@ -155,7 +158,10 @@ export const VerifyPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName!) + files[fileName] = await convertToPdfFile( + arrayBuffer, + fileName! + ) const hash = await getHash(arrayBuffer) if (hash) { @@ -169,7 +175,6 @@ export const VerifyPage = () => { setCurrentFileHashes(fileHashes) setFiles(files) - setSigners(createSignatureContent.signers) setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) @@ -177,8 +182,6 @@ export const VerifyPage = () => { setMeta(metaInNavState) setIsLoading(false) - - } }) .catch((err) => { @@ -381,7 +384,7 @@ export const VerifyPage = () => { } const handleExport = async () => { - if (Object.entries(files).length === 0 ||!meta ||!usersPubkey) return; + if (Object.entries(files).length === 0 || !meta || !usersPubkey) return const usersNpub = hexToNpub(usersPubkey) if ( @@ -395,10 +398,10 @@ export const VerifyPage = () => { setIsLoading(true) setLoadingSpinnerDesc('Signing nostr event') - if (!meta) return; + if (!meta) return const prevSig = getLastSignersSig(meta, signers) - if (!prevSig) return; + if (!prevSig) return const signedEvent = await signEventForMetaFile( JSON.stringify({ prevSig }), @@ -406,10 +409,10 @@ export const VerifyPage = () => { setIsLoading ) - if (!signedEvent) return; + if (!signedEvent) return const exportSignature = JSON.stringify(signedEvent, null, 2) - const updatedMeta = {...meta, exportSignature } + const updatedMeta = { ...meta, exportSignature } const stringifiedMeta = JSON.stringify(updatedMeta, null, 2) const zip = new JSZip() diff --git a/src/types/drawing.ts b/src/types/drawing.ts index 702343f..f14dbea 100644 --- a/src/types/drawing.ts +++ b/src/types/drawing.ts @@ -9,7 +9,7 @@ export interface MouseState { } export interface PdfFile { - file: File, + file: File pages: PdfPage[] expanded?: boolean } @@ -34,7 +34,7 @@ export interface DrawnField { export interface DrawTool { identifier: MarkType label: string - icon: JSX.Element, + icon: JSX.Element defaultValue?: string selected?: boolean active?: boolean @@ -46,4 +46,4 @@ export enum MarkType { FULLNAME = 'FULLNAME', DATE = 'DATE', DATETIME = 'DATETIME' -} \ No newline at end of file +} diff --git a/src/types/mark.ts b/src/types/mark.ts index 3184f95..9a6a545 100644 --- a/src/types/mark.ts +++ b/src/types/mark.ts @@ -1,4 +1,4 @@ -import { MarkType } from "./drawing"; +import { MarkType } from './drawing' export interface CurrentUserMark { mark: Mark @@ -7,18 +7,18 @@ export interface CurrentUserMark { } export interface Mark { - id: number; - npub: string; - pdfFileHash: string; - type: MarkType; - location: MarkLocation; - value?: string; + id: number + npub: string + pdfFileHash: string + type: MarkType + location: MarkLocation + value?: string } export interface MarkLocation { - top: number; - left: number; - height: number; - width: number; - page: number; + top: number + left: number + height: number + width: number + page: number } diff --git a/src/types/zip.ts b/src/types/zip.ts index b4c97ac..7405067 100644 --- a/src/types/zip.ts +++ b/src/types/zip.ts @@ -1,4 +1,4 @@ - export interface OutputByType { +export interface OutputByType { base64: string string: string text: string @@ -11,16 +11,18 @@ } interface InputByType { - base64: string; - string: string; - text: string; - binarystring: string; - array: number[]; - uint8array: Uint8Array; - arraybuffer: ArrayBuffer; - blob: Blob; - stream: NodeJS.ReadableStream; + base64: string + string: string + text: string + binarystring: string + array: number[] + uint8array: Uint8Array + arraybuffer: ArrayBuffer + blob: Blob + stream: NodeJS.ReadableStream } export type OutputType = keyof OutputByType -export type InputFileFormat = InputByType[keyof InputByType] | Promise; \ No newline at end of file +export type InputFileFormat = + | InputByType[keyof InputByType] + | Promise diff --git a/src/utils/mark.ts b/src/utils/mark.ts index 13cff84..60868be 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -9,9 +9,12 @@ import { Event } from 'nostr-tools' * @param marks - default Marks extracted from Meta * @param signedMetaMarks - signed user Marks extracted from DocSignatures */ -const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUserMark[] => { +const getCurrentUserMarks = ( + marks: Mark[], + signedMetaMarks: Mark[] +): CurrentUserMark[] => { return marks.map((mark, index, arr) => { - const signedMark = signedMetaMarks.find((m) => m.id === mark.id); + const signedMark = signedMetaMarks.find((m) => m.id === mark.id) if (signedMark && !!signedMark.value) { mark.value = signedMark.value } @@ -27,8 +30,10 @@ const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUse * Returns next incomplete CurrentUserMark if there is one * @param usersMarks */ -const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark | undefined => { - return usersMarks.find((mark) => !mark.isCompleted); +const findNextCurrentUserMark = ( + usersMarks: CurrentUserMark[] +): CurrentUserMark | undefined => { + return usersMarks.find((mark) => !mark.isCompleted) } /** @@ -37,7 +42,7 @@ const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark * @param pubkey */ const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => { - return marks.filter(mark => mark.npub === hexToNpub(pubkey)) + return marks.filter((mark) => mark.npub === hexToNpub(pubkey)) } /** @@ -57,7 +62,9 @@ const extractMarksFromSignedMeta = (meta: Meta): Mark[] => { * marked as complete. * @param currentUserMarks */ -const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolean => { +const isCurrentUserMarksComplete = ( + currentUserMarks: CurrentUserMark[] +): boolean => { return currentUserMarks.every((mark) => mark.isCompleted) } @@ -68,7 +75,7 @@ const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolea * @param markToUpdate */ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { - const indexToUpdate = marks.findIndex(mark => mark.id === markToUpdate.id); + const indexToUpdate = marks.findIndex((mark) => mark.id === markToUpdate.id) return [ ...marks.slice(0, indexToUpdate), markToUpdate, @@ -76,8 +83,13 @@ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { ] } -const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdate: CurrentUserMark): CurrentUserMark[] => { - const indexToUpdate = currentUserMarks.findIndex((m) => m.mark.id === markToUpdate.mark.id) +const updateCurrentUserMarks = ( + currentUserMarks: CurrentUserMark[], + markToUpdate: CurrentUserMark +): CurrentUserMark[] => { + const indexToUpdate = currentUserMarks.findIndex( + (m) => m.mark.id === markToUpdate.mark.id + ) return [ ...currentUserMarks.slice(0, indexToUpdate), markToUpdate, @@ -85,7 +97,7 @@ const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdat ] } -const isLast = (index: number, arr: T[]) => (index === (arr.length -1)) +const isLast = (index: number, arr: T[]) => index === arr.length - 1 export { getCurrentUserMarks, @@ -94,5 +106,5 @@ export { isCurrentUserMarksComplete, findNextCurrentUserMark, updateMarks, - updateCurrentUserMarks, + updateCurrentUserMarks } diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 70b6539..2263737 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -3,13 +3,14 @@ import * as PDFJS from 'pdfjs-dist' import { PDFDocument } from 'pdf-lib' import { Mark } from '../types/mark.ts' -PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; +PDFJS.GlobalWorkerOptions.workerSrc = + 'node_modules/pdfjs-dist/build/pdf.worker.mjs' /** * Scale between the PDF page's natural size and rendered size * @constant {number} */ -const SCALE: number = 3; +const SCALE: number = 3 /** * Defined font size used when generating a PDF. Currently it is difficult to fully * correlate font size used at the time of filling in / drawing on the PDF @@ -17,20 +18,20 @@ const SCALE: number = 3; * This should be fixed going forward. * Switching to PDF-Lib will most likely make this problem redundant. */ -const FONT_SIZE: number = 40; +const FONT_SIZE: number = 40 /** * Current font type used when generating a PDF. */ -const FONT_TYPE: string = 'Arial'; +const FONT_TYPE: string = 'Arial' /** * Converts a PDF ArrayBuffer to a generic PDF File * @param arrayBuffer of a PDF * @param fileName identifier of the pdf file */ -const toFile = (arrayBuffer: ArrayBuffer, fileName: string) : File => { - const blob = new Blob([arrayBuffer], { type: "application/pdf" }); - return new File([blob], fileName, { type: "application/pdf" }); +const toFile = (arrayBuffer: ArrayBuffer, fileName: string): File => { + const blob = new Blob([arrayBuffer], { type: 'application/pdf' }) + return new File([blob], fileName, { type: 'application/pdf' }) } /** @@ -50,42 +51,40 @@ const toPdfFile = async (file: File): Promise => { * @return PdfFile[] - an array of Sigit's internal Pdf File type */ const toPdfFiles = async (selectedFiles: File[]): Promise => { - return Promise.all(selectedFiles - .filter(isPdf) - .map(toPdfFile)); + return Promise.all(selectedFiles.filter(isPdf).map(toPdfFile)) } /** * A utility that transforms a drawing coordinate number into a CSS-compatible string * @param coordinate */ -const inPx = (coordinate: number): string => `${coordinate}px`; +const inPx = (coordinate: number): string => `${coordinate}px` /** * A utility that checks if a given file is of the pdf type * @param file */ -const isPdf = (file: File) => file.type.toLowerCase().includes('pdf'); +const isPdf = (file: File) => file.type.toLowerCase().includes('pdf') /** * Reads the pdf file binaries */ const readPdf = (file: File): Promise => { return new Promise((resolve, reject) => { - const reader = new FileReader(); + const reader = new FileReader() reader.onload = (e: any) => { const data = e.target.result resolve(data) - }; + } reader.onerror = (err) => { console.error('err', err) reject(err) - }; + } - reader.readAsDataURL(file); + reader.readAsDataURL(file) }) } @@ -94,26 +93,28 @@ const readPdf = (file: File): Promise => { * @param data pdf file bytes */ const pdfToImages = async (data: any): Promise => { - const images: string[] = []; - const pdf = await PDFJS.getDocument(data).promise; - const canvas = document.createElement("canvas"); + const images: string[] = [] + const pdf = await PDFJS.getDocument(data).promise + const canvas = document.createElement('canvas') for (let i = 0; i < pdf.numPages; i++) { - const page = await pdf.getPage(i + 1); - const viewport = page.getViewport({ scale: SCALE }); - const context = canvas.getContext("2d"); - canvas.height = viewport.height; - canvas.width = viewport.width; - await page.render({ canvasContext: context!, viewport: viewport }).promise; - images.push(canvas.toDataURL()); + const page = await pdf.getPage(i + 1) + const viewport = page.getViewport({ scale: SCALE }) + const context = canvas.getContext('2d') + canvas.height = viewport.height + canvas.width = viewport.width + await page.render({ canvasContext: context!, viewport: viewport }).promise + images.push(canvas.toDataURL()) } - return Promise.resolve(images.map((image) => { - return { - image, - drawnFields: [] - } - })) + return Promise.resolve( + images.map((image) => { + return { + image, + drawnFields: [] + } + }) + ) } /** @@ -121,34 +122,37 @@ const pdfToImages = async (data: any): Promise => { * Returns an array of encoded images where each image is a representation * of a PDF page with completed and signed marks from all users */ -const addMarks = async (file: File, marksPerPage: {[key: string]: Mark[]}) => { - const p = await readPdf(file); - const pdf = await PDFJS.getDocument(p).promise; - const canvas = document.createElement("canvas"); +const addMarks = async ( + file: File, + marksPerPage: { [key: string]: Mark[] } +) => { + const p = await readPdf(file) + const pdf = await PDFJS.getDocument(p).promise + const canvas = document.createElement('canvas') - const images: string[] = []; + const images: string[] = [] - for (let i = 0; i< pdf.numPages; i++) { - const page = await pdf.getPage(i+1) - const viewport = page.getViewport({ scale: SCALE }); - const context = canvas.getContext("2d"); - canvas.height = viewport.height; - canvas.width = viewport.width; - await page.render({ canvasContext: context!, viewport: viewport }).promise; + for (let i = 0; i < pdf.numPages; i++) { + const page = await pdf.getPage(i + 1) + const viewport = page.getViewport({ scale: SCALE }) + const context = canvas.getContext('2d') + canvas.height = viewport.height + canvas.width = viewport.width + await page.render({ canvasContext: context!, viewport: viewport }).promise - marksPerPage[i].forEach(mark => draw(mark, context!)) + marksPerPage[i].forEach((mark) => draw(mark, context!)) - images.push(canvas.toDataURL()); + images.push(canvas.toDataURL()) } - return Promise.resolve(images); + return Promise.resolve(images) } /** * Utility to scale mark in line with the PDF-to-PNG scale */ const scaleMark = (mark: Mark): Mark => { - const { location } = mark; + const { location } = mark return { ...mark, location: { @@ -165,7 +169,7 @@ const scaleMark = (mark: Mark): Mark => { * Utility to check if a Mark has value * @param mark */ -const hasValue = (mark: Mark): boolean => !!mark.value; +const hasValue = (mark: Mark): boolean => !!mark.value /** * Draws a Mark on a Canvas representation of a PDF Page @@ -173,14 +177,14 @@ const hasValue = (mark: Mark): boolean => !!mark.value; * @param ctx a Canvas representation of a specific PDF Page */ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { - const { location } = mark; + const { location } = mark - ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE; - ctx!.fillStyle = 'black'; - const textMetrics = ctx!.measureText(mark.value!); - const textX = location.left + (location.width - textMetrics.width) / 2; - const textY = location.top + (location.height + parseInt(ctx!.font)) / 2; - ctx!.fillText(mark.value!, textX, textY); + ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE + ctx!.fillStyle = 'black' + const textMetrics = ctx!.measureText(mark.value!) + const textX = location.left + (location.width - textMetrics.width) / 2 + const textY = location.top + (location.height + parseInt(ctx!.font)) / 2 + ctx!.fillText(mark.value!, textX, textY) } /** @@ -188,7 +192,7 @@ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { * @param markedPdfPages */ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { - const pdfDoc = await PDFDocument.create(); + const pdfDoc = await PDFDocument.create() for (const page of markedPdfPages) { const pngImage = await pdfDoc.embedPng(page) @@ -203,7 +207,6 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { const pdfBytes = await pdfDoc.save() return new Blob([pdfBytes], { type: 'application/pdf' }) - } /** @@ -211,9 +214,12 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { * @param arrayBuffer * @param fileName */ -const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string): Promise => { - const file = toFile(arrayBuffer, fileName); - return toPdfFile(file); +const convertToPdfFile = async ( + arrayBuffer: ArrayBuffer, + fileName: string +): Promise => { + const file = toFile(arrayBuffer, fileName) + return toPdfFile(file) } /** @@ -226,7 +232,7 @@ const groupMarksByPage = (marks: Mark[]) => { return marks .filter(hasValue) .map(scaleMark) - .reduce<{[key: number]: Mark[]}>(byPage, {}) + .reduce<{ [key: number]: Mark[] }>(byPage, {}) } /** @@ -237,11 +243,10 @@ const groupMarksByPage = (marks: Mark[]) => { * @param obj - accumulator in the reducer callback * @param mark - current value, i.e. Mark being examined */ -const byPage = (obj: { [key: number]: Mark[]}, mark: Mark) => { - const key = mark.location.page; - const curGroup = obj[key] ?? []; - return { ...obj, [key]: [...curGroup, mark] - } +const byPage = (obj: { [key: number]: Mark[] }, mark: Mark) => { + const key = mark.location.page + const curGroup = obj[key] ?? [] + return { ...obj, [key]: [...curGroup, mark] } } export { @@ -252,5 +257,5 @@ export { convertToPdfFile, addMarks, convertToPdfBlob, - groupMarksByPage, -} \ No newline at end of file + groupMarksByPage +} diff --git a/src/utils/relays.ts b/src/utils/relays.ts index 676a59a..a3e7a9c 100644 --- a/src/utils/relays.ts +++ b/src/utils/relays.ts @@ -5,8 +5,8 @@ import { localCache } from '../services' import { ONE_WEEK_IN_MS, SIGIT_RELAY } from './const.ts' import { RelayMap, RelaySet } from '../types' -const READ_MARKER = "read" -const WRITE_MARKET = "write" +const READ_MARKER = 'read' +const WRITE_MARKET = 'write' /** * Attempts to find a relay list from the provided lookUpRelays. @@ -15,7 +15,10 @@ const WRITE_MARKET = "write" * @param hexKey * @return found relay list or null */ -const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: string): Promise => { +const findRelayListAndUpdateCache = async ( + lookUpRelays: string[], + hexKey: string +): Promise => { try { const eventFilter: Filter = { kinds: [RelayList], @@ -42,10 +45,14 @@ const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: strin const findRelayListInCache = async (hexKey: string): Promise => { try { // Attempt to retrieve the metadata event from the local cache - const cachedRelayListMetadataEvent = await localCache.getUserRelayListMetadata(hexKey) + const cachedRelayListMetadataEvent = + await localCache.getUserRelayListMetadata(hexKey) // Check if the cached event is not older than one week - if (cachedRelayListMetadataEvent && isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)) { + if ( + cachedRelayListMetadataEvent && + isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt) + ) { return cachedRelayListMetadataEvent.event } @@ -104,5 +111,5 @@ export { findRelayListInCache, getUserRelaySet, getDefaultRelaySet, - getDefaultRelayMap, -} \ No newline at end of file + getDefaultRelayMap +} diff --git a/src/utils/sign.ts b/src/utils/sign.ts index 7b2dd84..3369c54 100644 --- a/src/utils/sign.ts +++ b/src/utils/sign.ts @@ -5,7 +5,10 @@ import { Meta } from '../types' * This function returns the signature of last signer * It will be used in the content of export signature's signedEvent */ -const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | null => { +const getLastSignersSig = ( + meta: Meta, + signers: `npub1${string}`[] +): string | null => { // if there're no signers then use creator's signature if (signers.length === 0) { try { @@ -21,13 +24,11 @@ const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | nu // get the signature of last signer try { - const lastSignatureEvent: Event = JSON.parse( - meta.docSignatures[lastSigner] - ) + const lastSignatureEvent: Event = JSON.parse(meta.docSignatures[lastSigner]) return lastSignatureEvent.sig } catch (error) { return null } } -export { getLastSignersSig } \ No newline at end of file +export { getLastSignersSig } diff --git a/src/utils/utils.ts b/src/utils/utils.ts index ed830a2..e68e687 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -73,9 +73,9 @@ export const timeout = (ms: number = 60000) => { * @param fileHashes */ export const getFilesWithHashes = ( - files: { [filename: string ]: PdfFile }, + files: { [filename: string]: PdfFile }, fileHashes: { [key: string]: string | null } - ) => { +) => { return Object.entries(files).map(([filename, pdfFile]) => { return { pdfFile, filename, hash: fileHashes[filename] } }) diff --git a/src/utils/zip.ts b/src/utils/zip.ts index 2d33aa5..7a7a49f 100644 --- a/src/utils/zip.ts +++ b/src/utils/zip.ts @@ -36,17 +36,12 @@ const readContentOfZipEntry = async ( const loadZip = async (data: InputFileFormat): Promise => { try { - return await JSZip.loadAsync(data); + return await JSZip.loadAsync(data) } catch (err: any) { console.log('err in loading zip file :>> ', err) toast.error(err.message || 'An error occurred in loading zip file.') - return null; + return null } } -export { - readContentOfZipEntry, - loadZip -} - - +export { readContentOfZipEntry, loadZip } From 8d683de88c210b8bad4a0b072dbb1ec925931e59 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 17:54:45 +0300 Subject: [PATCH 32/49] Revert "chore: prettier" This reverts commit 7e420ef5832967ab89bef3faf430b4749395bf51. --- src/components/DrawPDFFields/index.tsx | 244 ++++++++----------------- src/components/PDFView/PdfItem.tsx | 44 ++--- src/components/PDFView/PdfMarkItem.tsx | 20 +- src/components/PDFView/PdfMarking.tsx | 67 ++++--- src/components/PDFView/PdfPageItem.tsx | 32 ++-- src/components/PDFView/index.tsx | 47 ++--- src/controllers/MetadataController.ts | 17 +- src/index.css | 18 +- src/pages/create/index.tsx | 55 +++--- src/pages/settings/relays/index.tsx | 3 +- src/pages/sign/MarkFormField.tsx | 6 +- src/pages/sign/index.tsx | 74 ++++---- src/pages/verify/index.tsx | 31 ++-- src/types/drawing.ts | 6 +- src/types/mark.ts | 24 +-- src/types/zip.ts | 24 ++- src/utils/mark.ts | 34 ++-- src/utils/pdf.ts | 143 +++++++-------- src/utils/relays.ts | 21 +-- src/utils/sign.ts | 11 +- src/utils/utils.ts | 4 +- src/utils/zip.ts | 11 +- 22 files changed, 387 insertions(+), 549 deletions(-) diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index e7880e4..148e70f 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -1,43 +1,15 @@ -import { - AccessTime, - CalendarMonth, - ExpandMore, - Gesture, - PictureAsPdf, - Badge, - Work, - Close -} from '@mui/icons-material' -import { - Box, - Typography, - Accordion, - AccordionDetails, - AccordionSummary, - CircularProgress, - FormControl, - InputLabel, - MenuItem, - Select -} from '@mui/material' +import { AccessTime, CalendarMonth, ExpandMore, Gesture, PictureAsPdf, Badge, Work, Close } from '@mui/icons-material' +import { Box, Typography, Accordion, AccordionDetails, AccordionSummary, CircularProgress, FormControl, InputLabel, MenuItem, Select } from '@mui/material' import styles from './style.module.scss' import { useEffect, useState } from 'react' -import * as PDFJS from 'pdfjs-dist' -import { ProfileMetadata, User } from '../../types' -import { - PdfFile, - DrawTool, - MouseState, - PdfPage, - DrawnField, - MarkType -} from '../../types/drawing' -import { truncate } from 'lodash' -import { hexToNpub } from '../../utils' +import * as PDFJS from "pdfjs-dist"; +import { ProfileMetadata, User } from '../../types'; +import { PdfFile, DrawTool, MouseState, PdfPage, DrawnField, MarkType } from '../../types/drawing'; +import { truncate } from 'lodash'; +import { hexToNpub } from '../../utils'; import { toPdfFiles } from '../../utils/pdf.ts' -PDFJS.GlobalWorkerOptions.workerSrc = - 'node_modules/pdfjs-dist/build/pdf.worker.mjs' +PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; interface Props { selectedFiles: File[] @@ -48,43 +20,44 @@ interface Props { export const DrawPDFFields = (props: Props) => { const { selectedFiles } = props - + const [pdfFiles, setPdfFiles] = useState([]) const [parsingPdf, setParsingPdf] = useState(false) const [showDrawToolBox, setShowDrawToolBox] = useState(false) - + const [selectedTool, setSelectedTool] = useState() const [toolbox] = useState([ { identifier: MarkType.SIGNATURE, - icon: , + icon: , label: 'Signature', active: false + }, { identifier: MarkType.FULLNAME, - icon: , + icon: , label: 'Full Name', active: true }, { identifier: MarkType.JOBTITLE, - icon: , + icon: , label: 'Job Title', active: false }, { identifier: MarkType.DATE, - icon: , + icon: , label: 'Date', active: false }, { identifier: MarkType.DATETIME, - icon: , + icon: , label: 'Datetime', active: false - } + }, ]) const [mouseState, setMouseState] = useState({ @@ -94,7 +67,7 @@ export const DrawPDFFields = (props: Props) => { useEffect(() => { if (selectedFiles) { setParsingPdf(true) - + parsePdfPages().finally(() => { setParsingPdf(false) }) @@ -108,13 +81,13 @@ export const DrawPDFFields = (props: Props) => { /** * Drawing events */ - useEffect(() => { + useEffect(() => { // window.addEventListener('mousedown', onMouseDown); - window.addEventListener('mouseup', onMouseUp) - + window.addEventListener('mouseup', onMouseUp); + return () => { // window.removeEventListener('mousedown', onMouseDown); - window.removeEventListener('mouseup', onMouseUp) + window.removeEventListener('mouseup', onMouseUp); } }, []) @@ -133,7 +106,7 @@ export const DrawPDFFields = (props: Props) => { const onMouseDown = (event: any, page: PdfPage) => { // Proceed only if left click if (event.button !== 0) return - + // Only allow drawing if mouse is not over other drawn element const isOverPdfImageWrapper = event.target.tagName === 'IMG' @@ -185,11 +158,11 @@ export const DrawPDFFields = (props: Props) => { */ const onMouseMove = (event: any, page: PdfPage) => { if (mouseState.clicked && selectedTool) { - const lastElementIndex = page.drawnFields.length - 1 + const lastElementIndex = page.drawnFields.length -1 const lastDrawnField = page.drawnFields[lastElementIndex] const { mouseX, mouseY } = getMouseCoordinates(event) - + const width = mouseX - lastDrawnField.left const height = mouseY - lastDrawnField.top @@ -199,10 +172,10 @@ export const DrawPDFFields = (props: Props) => { const currentDrawnFields = page.drawnFields currentDrawnFields[lastElementIndex] = lastDrawnField - + refreshPdfFiles() } - } + } /** * Fired when event happens on the drawn element which will be moved @@ -216,7 +189,7 @@ export const DrawPDFFields = (props: Props) => { */ const onDrawnFieldMouseDown = (event: any) => { event.stopPropagation() - + // Proceed only if left click if (event.button !== 0) return @@ -239,10 +212,7 @@ export const DrawPDFFields = (props: Props) => { */ const onDranwFieldMouseMove = (event: any, drawnField: DrawnField) => { if (mouseState.dragging) { - const { mouseX, mouseY, rect } = getMouseCoordinates( - event, - event.target.parentNode - ) + const { mouseX, mouseY, rect } = getMouseCoordinates(event, event.target.parentNode) const coordsOffset = mouseState.coordsInWrapper if (coordsOffset) { @@ -288,11 +258,8 @@ export const DrawPDFFields = (props: Props) => { */ const onResizeHandleMouseMove = (event: any, drawnField: DrawnField) => { if (mouseState.resizing) { - const { mouseX, mouseY } = getMouseCoordinates( - event, - event.target.parentNode.parentNode - ) - + const { mouseX, mouseY } = getMouseCoordinates(event, event.target.parentNode.parentNode) + const width = mouseX - drawnField.left const height = mouseY - drawnField.top @@ -310,18 +277,10 @@ export const DrawPDFFields = (props: Props) => { * @param pdfPageIndex pdf page index * @param drawnFileIndex drawn file index */ - const onRemoveHandleMouseDown = ( - event: any, - pdfFileIndex: number, - pdfPageIndex: number, - drawnFileIndex: number - ) => { + const onRemoveHandleMouseDown = (event: any, pdfFileIndex: number, pdfPageIndex: number, drawnFileIndex: number) => { event.stopPropagation() - - pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice( - drawnFileIndex, - 1 - ) + + pdfFiles[pdfFileIndex].pages[pdfPageIndex].drawnFields.splice(drawnFileIndex, 1) } /** @@ -341,9 +300,9 @@ export const DrawPDFFields = (props: Props) => { */ const getMouseCoordinates = (event: any, customTarget?: any) => { const target = customTarget ? customTarget : event.target - const rect = target.getBoundingClientRect() - const mouseX = event.clientX - rect.left //x position within the element. - const mouseY = event.clientY - rect.top //y position within the element. + const rect = target.getBoundingClientRect(); + const mouseX = event.clientX - rect.left; //x position within the element. + const mouseY = event.clientY - rect.top; //y position within the element. return { mouseX, @@ -357,8 +316,8 @@ export const DrawPDFFields = (props: Props) => { * creates the pdfFiles object and sets to a state */ const parsePdfPages = async () => { - const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles) - + const pdfFiles: PdfFile[] = await toPdfFiles(selectedFiles); + setPdfFiles(pdfFiles) } @@ -367,7 +326,7 @@ export const DrawPDFFields = (props: Props) => { * @returns if expanded pdf accordion is present */ const hasExpandedPdf = () => { - return !!pdfFiles.filter((pdfFile) => !!pdfFile.expanded).length + return !!pdfFiles.filter(pdfFile => !!pdfFile.expanded).length } const handleAccordionExpandChange = (expanded: boolean, pdfFile: PdfFile) => { @@ -396,11 +355,9 @@ export const DrawPDFFields = (props: Props) => { */ const getPdfPages = (pdfFile: PdfFile, pdfFileIndex: number) => { return ( - + {pdfFile.pages.map((page, pdfPageIndex: number) => { return (
{ marginBottom: '10px' }} className={`${styles.pdfImageWrapper} ${selectedTool ? styles.drawing : ''}`} - onMouseMove={(event) => { - onMouseMove(event, page) - }} - onMouseDown={(event) => { - onMouseDown(event, page) - }} + onMouseMove={(event) => {onMouseMove(event, page)}} + onMouseDown={(event) => {onMouseDown(event, page)}} > - + {page.drawnFields.map((drawnField, drawnFieldIndex: number) => { return (
{ - onDranwFieldMouseMove(event, drawnField) - }} + onMouseMove={(event) => { onDranwFieldMouseMove(event, drawnField)}} className={styles.drawingRectangle} style={{ left: `${drawnField.left}px`, @@ -442,68 +389,41 @@ export const DrawPDFFields = (props: Props) => { > { - onResizeHandleMouseMove(event, drawnField) - }} + onMouseMove={(event) => {onResizeHandleMouseMove(event, drawnField)}} className={styles.resizeHandle} > { - onRemoveHandleMouseDown( - event, - pdfFileIndex, - pdfPageIndex, - drawnFieldIndex - ) - }} + onMouseDown={(event) => {onRemoveHandleMouseDown(event, pdfFileIndex, pdfPageIndex, drawnFieldIndex)}} className={styles.removeHandle} > - + -
- + Counterpart @@ -515,13 +435,13 @@ export const DrawPDFFields = (props: Props) => { ) })} - ) + ) } if (parsingPdf) { return ( - + ) } @@ -534,28 +454,22 @@ export const DrawPDFFields = (props: Props) => { Draw fields on the PDFs: - + {pdfFiles.map((pdfFile, pdfFileIndex: number) => { return ( - { - handleAccordionExpandChange(expanded, pdfFile) - }} - > + {handleAccordionExpandChange(expanded, pdfFile)}}> } aria-controls={`panel${pdfFileIndex}-content`} id={`panel${pdfFileIndex}header`} > - + {pdfFile.file.name} {getPdfPages(pdfFile, pdfFileIndex)} - + ) })} @@ -563,22 +477,16 @@ export const DrawPDFFields = (props: Props) => { {showDrawToolBox && ( - {toolbox - .filter((drawTool) => drawTool.active) - .map((drawTool: DrawTool, index: number) => { - return ( - { - handleToolSelect(drawTool) - }} - className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`} - > - {drawTool.icon} - {drawTool.label} - - ) - })} + {toolbox.filter(drawTool => drawTool.active).map((drawTool: DrawTool, index: number) => { + return ( + {handleToolSelect(drawTool)}} className={`${styles.toolItem} ${selectedTool?.identifier === drawTool.identifier ? styles.selected : ''}`}> + { drawTool.icon } + { drawTool.label } + + ) + })} )} diff --git a/src/components/PDFView/PdfItem.tsx b/src/components/PDFView/PdfItem.tsx index 6e8aa64..eb5ceff 100644 --- a/src/components/PDFView/PdfItem.tsx +++ b/src/components/PDFView/PdfItem.tsx @@ -1,6 +1,6 @@ import { PdfFile } from '../../types/drawing.ts' import { CurrentUserMark } from '../../types/mark.ts' -import PdfPageItem from './PdfPageItem.tsx' +import PdfPageItem from './PdfPageItem.tsx'; interface PdfItemProps { pdfFile: PdfFile @@ -13,31 +13,23 @@ interface PdfItemProps { /** * Responsible for displaying pages of a single Pdf File. */ -const PdfItem = ({ - pdfFile, - currentUserMarks, - handleMarkClick, - selectedMarkValue, - selectedMark -}: PdfItemProps) => { - const filterByPage = ( - marks: CurrentUserMark[], - page: number - ): CurrentUserMark[] => { - return marks.filter((m) => m.mark.location.page === page) +const PdfItem = ({ pdfFile, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfItemProps) => { + const filterByPage = (marks: CurrentUserMark[], page: number): CurrentUserMark[] => { + return marks.filter((m) => m.mark.location.page === page); } - return pdfFile.pages.map((page, i) => { - return ( - - ) - }) + return ( + pdfFile.pages.map((page, i) => { + return ( + + ) + })) } -export default PdfItem +export default PdfItem \ No newline at end of file diff --git a/src/components/PDFView/PdfMarkItem.tsx b/src/components/PDFView/PdfMarkItem.tsx index 90caa87..7a2b24b 100644 --- a/src/components/PDFView/PdfMarkItem.tsx +++ b/src/components/PDFView/PdfMarkItem.tsx @@ -12,18 +12,14 @@ interface PdfMarkItemProps { /** * Responsible for display an individual Pdf Mark. */ -const PdfMarkItem = ({ - selectedMark, - handleMarkClick, - selectedMarkValue, - userMark -}: PdfMarkItemProps) => { - const { location } = userMark.mark - const handleClick = () => handleMarkClick(userMark.mark.id) - const getMarkValue = () => +const PdfMarkItem = ({ selectedMark, handleMarkClick, selectedMarkValue, userMark }: PdfMarkItemProps) => { + const { location } = userMark.mark; + const handleClick = () => handleMarkClick(userMark.mark.id); + const getMarkValue = () => ( selectedMark?.mark.id === userMark.mark.id ? selectedMarkValue : userMark.mark.value + ) return (
- {getMarkValue()} -
+ >{getMarkValue()}
) } -export default PdfMarkItem +export default PdfMarkItem \ No newline at end of file diff --git a/src/components/PDFView/PdfMarking.tsx b/src/components/PDFView/PdfMarking.tsx index 0e47ab1..a6dc1a4 100644 --- a/src/components/PDFView/PdfMarking.tsx +++ b/src/components/PDFView/PdfMarking.tsx @@ -6,17 +6,17 @@ import React, { useState, useEffect } from 'react' import { findNextCurrentUserMark, isCurrentUserMarksComplete, - updateCurrentUserMarks + updateCurrentUserMarks, } from '../../utils' import { EMPTY } from '../../utils/const.ts' import { Container } from '../Container' import styles from '../../pages/sign/style.module.scss' interface PdfMarkingProps { - files: { pdfFile: PdfFile; filename: string; hash: string | null }[] - currentUserMarks: CurrentUserMark[] - setIsReadyToSign: (isReadyToSign: boolean) => void - setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void + files: { pdfFile: PdfFile, filename: string, hash: string | null }[], + currentUserMarks: CurrentUserMark[], + setIsReadyToSign: (isReadyToSign: boolean) => void, + setCurrentUserMarks: (currentUserMarks: CurrentUserMark[]) => void, setUpdatedMarks: (markToUpdate: Mark) => void } @@ -35,21 +35,21 @@ const PdfMarking = (props: PdfMarkingProps) => { setUpdatedMarks } = props const [selectedMark, setSelectedMark] = useState(null) - const [selectedMarkValue, setSelectedMarkValue] = useState('') + const [selectedMarkValue, setSelectedMarkValue] = useState("") useEffect(() => { setSelectedMark(findNextCurrentUserMark(currentUserMarks) || null) }, [currentUserMarks]) const handleMarkClick = (id: number) => { - const nextMark = currentUserMarks.find((mark) => mark.mark.id === id) - setSelectedMark(nextMark!) - setSelectedMarkValue(nextMark?.mark.value ?? EMPTY) + const nextMark = currentUserMarks.find((mark) => mark.mark.id === id); + setSelectedMark(nextMark!); + setSelectedMarkValue(nextMark?.mark.value ?? EMPTY); } const handleSubmit = (event: React.FormEvent) => { - event.preventDefault() - if (!selectedMarkValue || !selectedMark) return + event.preventDefault(); + if (!selectedMarkValue || !selectedMark) return; const updatedMark: CurrentUserMark = { ...selectedMark, @@ -61,10 +61,7 @@ const PdfMarking = (props: PdfMarkingProps) => { } setSelectedMarkValue(EMPTY) - const updatedCurrentUserMarks = updateCurrentUserMarks( - currentUserMarks, - updatedMark - ) + const updatedCurrentUserMarks = updateCurrentUserMarks(currentUserMarks, updatedMark); setCurrentUserMarks(updatedCurrentUserMarks) setSelectedMark(findNextCurrentUserMark(updatedCurrentUserMarks) || null) console.log(isCurrentUserMarksComplete(updatedCurrentUserMarks)) @@ -72,32 +69,32 @@ const PdfMarking = (props: PdfMarkingProps) => { setUpdatedMarks(updatedMark.mark) } - const handleChange = (event: React.ChangeEvent) => - setSelectedMarkValue(event.target.value) + const handleChange = (event: React.ChangeEvent) => setSelectedMarkValue(event.target.value) return ( <> - {currentUserMarks?.length > 0 && ( - - )} - {selectedMark !== null && ( - - )} + { + currentUserMarks?.length > 0 && ( + )} + { + selectedMark !== null && ( + + )} ) } -export default PdfMarking +export default PdfMarking \ No newline at end of file diff --git a/src/components/PDFView/PdfPageItem.tsx b/src/components/PDFView/PdfPageItem.tsx index 241474e..d289a6e 100644 --- a/src/components/PDFView/PdfPageItem.tsx +++ b/src/components/PDFView/PdfPageItem.tsx @@ -13,13 +13,7 @@ interface PdfPageProps { /** * Responsible for rendering a single Pdf Page and its Marks */ -const PdfPageItem = ({ - page, - currentUserMarks, - handleMarkClick, - selectedMarkValue, - selectedMark -}: PdfPageProps) => { +const PdfPageItem = ({ page, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfPageProps) => { return (
- - {currentUserMarks.map((m, i) => ( - + { + currentUserMarks.map((m, i) => ( + ))}
) } -export default PdfPageItem +export default PdfPageItem \ No newline at end of file diff --git a/src/components/PDFView/index.tsx b/src/components/PDFView/index.tsx index 8a14e55..14834a3 100644 --- a/src/components/PDFView/index.tsx +++ b/src/components/PDFView/index.tsx @@ -4,7 +4,7 @@ import PdfItem from './PdfItem.tsx' import { CurrentUserMark } from '../../types/mark.ts' interface PdfViewProps { - files: { pdfFile: PdfFile; filename: string; hash: string | null }[] + files: { pdfFile: PdfFile, filename: string, hash: string | null }[] currentUserMarks: CurrentUserMark[] handleMarkClick: (id: number) => void selectedMarkValue: string @@ -14,38 +14,29 @@ interface PdfViewProps { /** * Responsible for rendering Pdf files. */ -const PdfView = ({ - files, - currentUserMarks, - handleMarkClick, - selectedMarkValue, - selectedMark -}: PdfViewProps) => { - const filterByFile = ( - currentUserMarks: CurrentUserMark[], - hash: string - ): CurrentUserMark[] => { - return currentUserMarks.filter( - (currentUserMark) => currentUserMark.mark.pdfFileHash === hash - ) +const PdfView = ({ files, currentUserMarks, handleMarkClick, selectedMarkValue, selectedMark }: PdfViewProps) => { + const filterByFile = (currentUserMarks: CurrentUserMark[], hash: string): CurrentUserMark[] => { + return currentUserMarks.filter((currentUserMark) => currentUserMark.mark.pdfFileHash === hash) } return ( - {files.map(({ pdfFile, hash }, i) => { - if (!hash) return - return ( - { + if (!hash) return + return ( + - ) - })} + ) + }) + } ) } -export default PdfView +export default PdfView; \ No newline at end of file diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index cd3d819..e2f6852 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -164,18 +164,13 @@ export class MetadataController extends EventEmitter { * or a fallback RelaySet with Sigit's Relay */ public findRelayListMetadata = async (hexKey: string): Promise => { - const relayEvent = - (await findRelayListInCache(hexKey)) || - (await findRelayListAndUpdateCache( - [this.specialMetadataRelay], - hexKey - )) || - (await findRelayListAndUpdateCache( - await this.nostrController.getMostPopularRelays(), - hexKey - )) + const relayEvent = await findRelayListInCache(hexKey) + || await findRelayListAndUpdateCache([this.specialMetadataRelay], hexKey) + || await findRelayListAndUpdateCache(await this.nostrController.getMostPopularRelays(), hexKey) - return relayEvent ? getUserRelaySet(relayEvent.tags) : getDefaultRelaySet() + return relayEvent + ? getUserRelaySet(relayEvent.tags) + : getDefaultRelaySet() } public extractProfileMetadataContent = (event: Event) => { diff --git a/src/index.css b/src/index.css index 76373ff..6a734df 100644 --- a/src/index.css +++ b/src/index.css @@ -111,9 +111,7 @@ button:disabled { /* Fonts */ @font-face { font-family: 'Roboto'; - src: - local('Roboto Medium'), - local('Roboto-Medium'), + src: local('Roboto Medium'), local('Roboto-Medium'), url('assets/fonts/roboto-medium.woff2') format('woff2'), url('assets/fonts/roboto-medium.woff') format('woff'); font-weight: 500; @@ -123,9 +121,7 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: - local('Roboto Light'), - local('Roboto-Light'), + src: local('Roboto Light'), local('Roboto-Light'), url('assets/fonts/roboto-light.woff2') format('woff2'), url('assets/fonts/roboto-light.woff') format('woff'); font-weight: 300; @@ -135,9 +131,7 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: - local('Roboto Bold'), - local('Roboto-Bold'), + src: local('Roboto Bold'), local('Roboto-Bold'), url('assets/fonts/roboto-bold.woff2') format('woff2'), url('assets/fonts/roboto-bold.woff') format('woff'); font-weight: bold; @@ -147,12 +141,10 @@ button:disabled { @font-face { font-family: 'Roboto'; - src: - local('Roboto'), - local('Roboto-Regular'), + src: local('Roboto'), local('Roboto-Regular'), url('assets/fonts/roboto-regular.woff2') format('woff2'), url('assets/fonts/roboto-regular.woff') format('woff'); font-weight: normal; font-style: normal; font-display: swap; -} +} \ No newline at end of file diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 023dd02..830f9ef 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -341,30 +341,29 @@ export const CreatePage = () => { return fileHashes } - const createMarks = (fileHashes: { [key: string]: string }): Mark[] => { - return drawnPdfs - .flatMap((drawnPdf) => { - const fileHash = fileHashes[drawnPdf.file.name] - return drawnPdf.pages.flatMap((page, index) => { - return page.drawnFields.map((drawnField) => { - return { - type: drawnField.type, - location: { - page: index, - top: drawnField.top, - left: drawnField.left, - height: drawnField.height, - width: drawnField.width - }, - npub: drawnField.counterpart, - pdfFileHash: fileHash - } - }) + const createMarks = (fileHashes: { [key: string]: string }) : Mark[] => { + return drawnPdfs.flatMap((drawnPdf) => { + const fileHash = fileHashes[drawnPdf.file.name]; + return drawnPdf.pages.flatMap((page, index) => { + return page.drawnFields.map((drawnField) => { + return { + type: drawnField.type, + location: { + page: index, + top: drawnField.top, + left: drawnField.left, + height: drawnField.height, + width: drawnField.width, + }, + npub: drawnField.counterpart, + pdfFileHash: fileHash + } }) }) + }) .map((mark, index) => { - return { ...mark, id: index } - }) + return {...mark, id: index } + }); } // Handle errors during zip file generation @@ -432,9 +431,13 @@ export const CreatePage = () => { if (!arraybuffer) return null - return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { - type: 'application/zip' - }) + return new File( + [new Blob([arraybuffer])], + `${unixNow}.sigit.zip`, + { + type: 'application/zip' + } + ) } // Handle errors during file upload @@ -542,7 +545,9 @@ export const CreatePage = () => { : viewers.map((viewer) => viewer.pubkey) ).filter((receiver) => receiver !== usersPubkey) - return receivers.map((receiver) => sendNotification(receiver, meta)) + return receivers.map((receiver) => + sendNotification(receiver, meta) + ) } const handleCreate = async () => { diff --git a/src/pages/settings/relays/index.tsx b/src/pages/settings/relays/index.tsx index 929a093..b06908a 100644 --- a/src/pages/settings/relays/index.tsx +++ b/src/pages/settings/relays/index.tsx @@ -91,8 +91,7 @@ export const RelaysPage = () => { if (isMounted) { if ( !relaysState?.mapUpdated || - (newRelayMap?.mapUpdated !== undefined && - newRelayMap?.mapUpdated > relaysState?.mapUpdated) + newRelayMap?.mapUpdated !== undefined && (newRelayMap?.mapUpdated > relaysState?.mapUpdated) ) { if ( !relaysState?.map || diff --git a/src/pages/sign/MarkFormField.tsx b/src/pages/sign/MarkFormField.tsx index 4cb943b..4de82a4 100644 --- a/src/pages/sign/MarkFormField.tsx +++ b/src/pages/sign/MarkFormField.tsx @@ -15,8 +15,8 @@ interface MarkFormFieldProps { * Responsible for rendering a form field connected to a mark and keeping track of its value. */ const MarkFormField = (props: MarkFormFieldProps) => { - const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props - const getSubmitButton = () => (selectedMark.isLast ? 'Complete' : 'Next') + const { handleSubmit, handleChange, selectedMark, selectedMarkValue } = props; + const getSubmitButton = () => selectedMark.isLast ? 'Complete' : 'Next'; return (
@@ -34,4 +34,4 @@ const MarkFormField = (props: MarkFormFieldProps) => { ) } -export default MarkFormField +export default MarkFormField; \ No newline at end of file diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 6f3231b..07c385e 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -16,16 +16,13 @@ import { State } from '../../store/rootReducer' import { CreateSignatureEventContent, Meta, SignedEvent } from '../../types' import { decryptArrayBuffer, - encryptArrayBuffer, - extractMarksFromSignedMeta, + encryptArrayBuffer, extractMarksFromSignedMeta, extractZipUrlAndEncryptionKey, generateEncryptionKey, - generateKeysFile, - getFilesWithHashes, + generateKeysFile, getFilesWithHashes, getHash, hexToNpub, - isOnline, - loadZip, + isOnline, loadZip, now, npubToHex, parseJson, @@ -44,8 +41,7 @@ import { getLastSignersSig } from '../../utils/sign.ts' import { filterMarksByPubkey, getCurrentUserMarks, - isCurrentUserMarksComplete, - updateMarks + isCurrentUserMarksComplete, updateMarks } from '../../utils' import PdfMarking from '../../components/PDFView/PdfMarking.tsx' enum SignedStatus { @@ -85,7 +81,7 @@ export const SignPage = () => { const [signers, setSigners] = useState<`npub1${string}`[]>([]) const [viewers, setViewers] = useState<`npub1${string}`[]>([]) - const [marks, setMarks] = useState([]) + const [marks, setMarks] = useState([]) const [creatorFileHashes, setCreatorFileHashes] = useState<{ [key: string]: string }>({}) @@ -104,10 +100,8 @@ export const SignPage = () => { const [authUrl, setAuthUrl] = useState() const nostrController = NostrController.getInstance() - const [currentUserMarks, setCurrentUserMarks] = useState( - [] - ) - const [isReadyToSign, setIsReadyToSign] = useState(false) + const [currentUserMarks, setCurrentUserMarks] = useState([]); + const [isReadyToSign, setIsReadyToSign] = useState(false); useEffect(() => { if (signers.length > 0) { @@ -198,16 +192,13 @@ export const SignPage = () => { setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) setSubmittedBy(createSignatureEvent.pubkey) - setMarks(createSignatureContent.markConfig) + setMarks(createSignatureContent.markConfig); if (usersPubkey) { - const metaMarks = filterMarksByPubkey( - createSignatureContent.markConfig, - usersPubkey! - ) + const metaMarks = filterMarksByPubkey(createSignatureContent.markConfig, usersPubkey!) const signedMarks = extractMarksFromSignedMeta(meta) - const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks) - setCurrentUserMarks(currentUserMarks) + const currentUserMarks = getCurrentUserMarks(metaMarks, signedMarks); + setCurrentUserMarks(currentUserMarks); // setCurrentUserMark(findNextCurrentUserMark(currentUserMarks) || null) setIsReadyToSign(isCurrentUserMarksComplete(currentUserMarks)) } @@ -316,7 +307,7 @@ export const SignPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName) + files[fileName] = await convertToPdfFile(arrayBuffer, fileName); const hash = await getHash(arrayBuffer) if (hash) { @@ -357,7 +348,7 @@ export const SignPage = () => { const decrypt = async (file: File) => { setLoadingSpinnerDesc('Decrypting file') - const zip = await loadZip(file) + const zip = await loadZip(file); if (!zip) return const parsedKeysJson = await parseKeysJson(zip) @@ -448,7 +439,7 @@ export const SignPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile(arrayBuffer, fileName) + files[fileName] = await convertToPdfFile(arrayBuffer, fileName); const hash = await getHash(arrayBuffer) if (hash) { @@ -529,10 +520,7 @@ export const SignPage = () => { } // Sign the event for the meta file - const signEventForMeta = async (signerContent: { - prevSig: string - marks: Mark[] - }) => { + const signEventForMeta = async (signerContent: { prevSig: string, marks: Mark[] }) => { return await signEventForMetaFile( JSON.stringify(signerContent), nostrController, @@ -541,8 +529,8 @@ export const SignPage = () => { } const getSignerMarksForMeta = (): Mark[] | undefined => { - if (currentUserMarks.length === 0) return - return currentUserMarks.map(({ mark }: CurrentUserMark) => mark) + if (currentUserMarks.length === 0) return; + return currentUserMarks.map(( { mark }: CurrentUserMark) => mark); } // Update the meta signatures @@ -612,9 +600,13 @@ export const SignPage = () => { if (!arraybuffer) return null - return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { - type: 'application/zip' - }) + return new File( + [new Blob([arraybuffer])], + `${unixNow}.sigit.zip`, + { + type: 'application/zip' + } + ) } // Handle errors during zip file generation @@ -702,7 +694,7 @@ export const SignPage = () => { setIsLoading(true) setLoadingSpinnerDesc('Signing nostr event') - if (!meta) return + if (!meta) return; const prevSig = getLastSignersSig(meta, signers) if (!prevSig) return @@ -926,13 +918,11 @@ export const SignPage = () => { ) } - return ( - - ) + return } diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index c5a0e8e..6290c02 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -23,17 +23,14 @@ import { SignedEventContent } from '../../types' import { - decryptArrayBuffer, - extractMarksFromSignedMeta, + decryptArrayBuffer, extractMarksFromSignedMeta, extractZipUrlAndEncryptionKey, getHash, - hexToNpub, - now, + hexToNpub, now, npubToHex, parseJson, readContentOfZipEntry, - shorten, - signEventForMetaFile + shorten, signEventForMetaFile } from '../../utils' import styles from './style.module.scss' import { Cancel, CheckCircle } from '@mui/icons-material' @@ -44,7 +41,7 @@ import { addMarks, convertToPdfBlob, convertToPdfFile, - groupMarksByPage + groupMarksByPage, } from '../../utils/pdf.ts' import { State } from '../../store/rootReducer.ts' import { useSelector } from 'react-redux' @@ -81,7 +78,7 @@ export const VerifyPage = () => { const [currentFileHashes, setCurrentFileHashes] = useState<{ [key: string]: string | null }>({}) - const [files, setFiles] = useState<{ [filename: string]: PdfFile }>({}) + const [files, setFiles] = useState<{ [filename: string]: PdfFile}>({}) const [metadata, setMetadata] = useState<{ [key: string]: ProfileMetadata }>( {} @@ -158,10 +155,7 @@ export const VerifyPage = () => { ) if (arrayBuffer) { - files[fileName] = await convertToPdfFile( - arrayBuffer, - fileName! - ) + files[fileName] = await convertToPdfFile(arrayBuffer, fileName!) const hash = await getHash(arrayBuffer) if (hash) { @@ -175,6 +169,7 @@ export const VerifyPage = () => { setCurrentFileHashes(fileHashes) setFiles(files) + setSigners(createSignatureContent.signers) setViewers(createSignatureContent.viewers) setCreatorFileHashes(createSignatureContent.fileHashes) @@ -182,6 +177,8 @@ export const VerifyPage = () => { setMeta(metaInNavState) setIsLoading(false) + + } }) .catch((err) => { @@ -384,7 +381,7 @@ export const VerifyPage = () => { } const handleExport = async () => { - if (Object.entries(files).length === 0 || !meta || !usersPubkey) return + if (Object.entries(files).length === 0 ||!meta ||!usersPubkey) return; const usersNpub = hexToNpub(usersPubkey) if ( @@ -398,10 +395,10 @@ export const VerifyPage = () => { setIsLoading(true) setLoadingSpinnerDesc('Signing nostr event') - if (!meta) return + if (!meta) return; const prevSig = getLastSignersSig(meta, signers) - if (!prevSig) return + if (!prevSig) return; const signedEvent = await signEventForMetaFile( JSON.stringify({ prevSig }), @@ -409,10 +406,10 @@ export const VerifyPage = () => { setIsLoading ) - if (!signedEvent) return + if (!signedEvent) return; const exportSignature = JSON.stringify(signedEvent, null, 2) - const updatedMeta = { ...meta, exportSignature } + const updatedMeta = {...meta, exportSignature } const stringifiedMeta = JSON.stringify(updatedMeta, null, 2) const zip = new JSZip() diff --git a/src/types/drawing.ts b/src/types/drawing.ts index f14dbea..702343f 100644 --- a/src/types/drawing.ts +++ b/src/types/drawing.ts @@ -9,7 +9,7 @@ export interface MouseState { } export interface PdfFile { - file: File + file: File, pages: PdfPage[] expanded?: boolean } @@ -34,7 +34,7 @@ export interface DrawnField { export interface DrawTool { identifier: MarkType label: string - icon: JSX.Element + icon: JSX.Element, defaultValue?: string selected?: boolean active?: boolean @@ -46,4 +46,4 @@ export enum MarkType { FULLNAME = 'FULLNAME', DATE = 'DATE', DATETIME = 'DATETIME' -} +} \ No newline at end of file diff --git a/src/types/mark.ts b/src/types/mark.ts index 9a6a545..3184f95 100644 --- a/src/types/mark.ts +++ b/src/types/mark.ts @@ -1,4 +1,4 @@ -import { MarkType } from './drawing' +import { MarkType } from "./drawing"; export interface CurrentUserMark { mark: Mark @@ -7,18 +7,18 @@ export interface CurrentUserMark { } export interface Mark { - id: number - npub: string - pdfFileHash: string - type: MarkType - location: MarkLocation - value?: string + id: number; + npub: string; + pdfFileHash: string; + type: MarkType; + location: MarkLocation; + value?: string; } export interface MarkLocation { - top: number - left: number - height: number - width: number - page: number + top: number; + left: number; + height: number; + width: number; + page: number; } diff --git a/src/types/zip.ts b/src/types/zip.ts index 7405067..b4c97ac 100644 --- a/src/types/zip.ts +++ b/src/types/zip.ts @@ -1,4 +1,4 @@ -export interface OutputByType { + export interface OutputByType { base64: string string: string text: string @@ -11,18 +11,16 @@ export interface OutputByType { } interface InputByType { - base64: string - string: string - text: string - binarystring: string - array: number[] - uint8array: Uint8Array - arraybuffer: ArrayBuffer - blob: Blob - stream: NodeJS.ReadableStream + base64: string; + string: string; + text: string; + binarystring: string; + array: number[]; + uint8array: Uint8Array; + arraybuffer: ArrayBuffer; + blob: Blob; + stream: NodeJS.ReadableStream; } export type OutputType = keyof OutputByType -export type InputFileFormat = - | InputByType[keyof InputByType] - | Promise +export type InputFileFormat = InputByType[keyof InputByType] | Promise; \ No newline at end of file diff --git a/src/utils/mark.ts b/src/utils/mark.ts index 60868be..13cff84 100644 --- a/src/utils/mark.ts +++ b/src/utils/mark.ts @@ -9,12 +9,9 @@ import { Event } from 'nostr-tools' * @param marks - default Marks extracted from Meta * @param signedMetaMarks - signed user Marks extracted from DocSignatures */ -const getCurrentUserMarks = ( - marks: Mark[], - signedMetaMarks: Mark[] -): CurrentUserMark[] => { +const getCurrentUserMarks = (marks: Mark[], signedMetaMarks: Mark[]): CurrentUserMark[] => { return marks.map((mark, index, arr) => { - const signedMark = signedMetaMarks.find((m) => m.id === mark.id) + const signedMark = signedMetaMarks.find((m) => m.id === mark.id); if (signedMark && !!signedMark.value) { mark.value = signedMark.value } @@ -30,10 +27,8 @@ const getCurrentUserMarks = ( * Returns next incomplete CurrentUserMark if there is one * @param usersMarks */ -const findNextCurrentUserMark = ( - usersMarks: CurrentUserMark[] -): CurrentUserMark | undefined => { - return usersMarks.find((mark) => !mark.isCompleted) +const findNextCurrentUserMark = (usersMarks: CurrentUserMark[]): CurrentUserMark | undefined => { + return usersMarks.find((mark) => !mark.isCompleted); } /** @@ -42,7 +37,7 @@ const findNextCurrentUserMark = ( * @param pubkey */ const filterMarksByPubkey = (marks: Mark[], pubkey: string): Mark[] => { - return marks.filter((mark) => mark.npub === hexToNpub(pubkey)) + return marks.filter(mark => mark.npub === hexToNpub(pubkey)) } /** @@ -62,9 +57,7 @@ const extractMarksFromSignedMeta = (meta: Meta): Mark[] => { * marked as complete. * @param currentUserMarks */ -const isCurrentUserMarksComplete = ( - currentUserMarks: CurrentUserMark[] -): boolean => { +const isCurrentUserMarksComplete = (currentUserMarks: CurrentUserMark[]): boolean => { return currentUserMarks.every((mark) => mark.isCompleted) } @@ -75,7 +68,7 @@ const isCurrentUserMarksComplete = ( * @param markToUpdate */ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { - const indexToUpdate = marks.findIndex((mark) => mark.id === markToUpdate.id) + const indexToUpdate = marks.findIndex(mark => mark.id === markToUpdate.id); return [ ...marks.slice(0, indexToUpdate), markToUpdate, @@ -83,13 +76,8 @@ const updateMarks = (marks: Mark[], markToUpdate: Mark): Mark[] => { ] } -const updateCurrentUserMarks = ( - currentUserMarks: CurrentUserMark[], - markToUpdate: CurrentUserMark -): CurrentUserMark[] => { - const indexToUpdate = currentUserMarks.findIndex( - (m) => m.mark.id === markToUpdate.mark.id - ) +const updateCurrentUserMarks = (currentUserMarks: CurrentUserMark[], markToUpdate: CurrentUserMark): CurrentUserMark[] => { + const indexToUpdate = currentUserMarks.findIndex((m) => m.mark.id === markToUpdate.mark.id) return [ ...currentUserMarks.slice(0, indexToUpdate), markToUpdate, @@ -97,7 +85,7 @@ const updateCurrentUserMarks = ( ] } -const isLast = (index: number, arr: T[]) => index === arr.length - 1 +const isLast = (index: number, arr: T[]) => (index === (arr.length -1)) export { getCurrentUserMarks, @@ -106,5 +94,5 @@ export { isCurrentUserMarksComplete, findNextCurrentUserMark, updateMarks, - updateCurrentUserMarks + updateCurrentUserMarks, } diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 2263737..70b6539 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -3,14 +3,13 @@ import * as PDFJS from 'pdfjs-dist' import { PDFDocument } from 'pdf-lib' import { Mark } from '../types/mark.ts' -PDFJS.GlobalWorkerOptions.workerSrc = - 'node_modules/pdfjs-dist/build/pdf.worker.mjs' +PDFJS.GlobalWorkerOptions.workerSrc = 'node_modules/pdfjs-dist/build/pdf.worker.mjs'; /** * Scale between the PDF page's natural size and rendered size * @constant {number} */ -const SCALE: number = 3 +const SCALE: number = 3; /** * Defined font size used when generating a PDF. Currently it is difficult to fully * correlate font size used at the time of filling in / drawing on the PDF @@ -18,20 +17,20 @@ const SCALE: number = 3 * This should be fixed going forward. * Switching to PDF-Lib will most likely make this problem redundant. */ -const FONT_SIZE: number = 40 +const FONT_SIZE: number = 40; /** * Current font type used when generating a PDF. */ -const FONT_TYPE: string = 'Arial' +const FONT_TYPE: string = 'Arial'; /** * Converts a PDF ArrayBuffer to a generic PDF File * @param arrayBuffer of a PDF * @param fileName identifier of the pdf file */ -const toFile = (arrayBuffer: ArrayBuffer, fileName: string): File => { - const blob = new Blob([arrayBuffer], { type: 'application/pdf' }) - return new File([blob], fileName, { type: 'application/pdf' }) +const toFile = (arrayBuffer: ArrayBuffer, fileName: string) : File => { + const blob = new Blob([arrayBuffer], { type: "application/pdf" }); + return new File([blob], fileName, { type: "application/pdf" }); } /** @@ -51,40 +50,42 @@ const toPdfFile = async (file: File): Promise => { * @return PdfFile[] - an array of Sigit's internal Pdf File type */ const toPdfFiles = async (selectedFiles: File[]): Promise => { - return Promise.all(selectedFiles.filter(isPdf).map(toPdfFile)) + return Promise.all(selectedFiles + .filter(isPdf) + .map(toPdfFile)); } /** * A utility that transforms a drawing coordinate number into a CSS-compatible string * @param coordinate */ -const inPx = (coordinate: number): string => `${coordinate}px` +const inPx = (coordinate: number): string => `${coordinate}px`; /** * A utility that checks if a given file is of the pdf type * @param file */ -const isPdf = (file: File) => file.type.toLowerCase().includes('pdf') +const isPdf = (file: File) => file.type.toLowerCase().includes('pdf'); /** * Reads the pdf file binaries */ const readPdf = (file: File): Promise => { return new Promise((resolve, reject) => { - const reader = new FileReader() + const reader = new FileReader(); reader.onload = (e: any) => { const data = e.target.result resolve(data) - } + }; reader.onerror = (err) => { console.error('err', err) reject(err) - } + }; - reader.readAsDataURL(file) + reader.readAsDataURL(file); }) } @@ -93,28 +94,26 @@ const readPdf = (file: File): Promise => { * @param data pdf file bytes */ const pdfToImages = async (data: any): Promise => { - const images: string[] = [] - const pdf = await PDFJS.getDocument(data).promise - const canvas = document.createElement('canvas') + const images: string[] = []; + const pdf = await PDFJS.getDocument(data).promise; + const canvas = document.createElement("canvas"); for (let i = 0; i < pdf.numPages; i++) { - const page = await pdf.getPage(i + 1) - const viewport = page.getViewport({ scale: SCALE }) - const context = canvas.getContext('2d') - canvas.height = viewport.height - canvas.width = viewport.width - await page.render({ canvasContext: context!, viewport: viewport }).promise - images.push(canvas.toDataURL()) + const page = await pdf.getPage(i + 1); + const viewport = page.getViewport({ scale: SCALE }); + const context = canvas.getContext("2d"); + canvas.height = viewport.height; + canvas.width = viewport.width; + await page.render({ canvasContext: context!, viewport: viewport }).promise; + images.push(canvas.toDataURL()); } - return Promise.resolve( - images.map((image) => { - return { - image, - drawnFields: [] - } - }) - ) + return Promise.resolve(images.map((image) => { + return { + image, + drawnFields: [] + } + })) } /** @@ -122,37 +121,34 @@ const pdfToImages = async (data: any): Promise => { * Returns an array of encoded images where each image is a representation * of a PDF page with completed and signed marks from all users */ -const addMarks = async ( - file: File, - marksPerPage: { [key: string]: Mark[] } -) => { - const p = await readPdf(file) - const pdf = await PDFJS.getDocument(p).promise - const canvas = document.createElement('canvas') +const addMarks = async (file: File, marksPerPage: {[key: string]: Mark[]}) => { + const p = await readPdf(file); + const pdf = await PDFJS.getDocument(p).promise; + const canvas = document.createElement("canvas"); - const images: string[] = [] + const images: string[] = []; - for (let i = 0; i < pdf.numPages; i++) { - const page = await pdf.getPage(i + 1) - const viewport = page.getViewport({ scale: SCALE }) - const context = canvas.getContext('2d') - canvas.height = viewport.height - canvas.width = viewport.width - await page.render({ canvasContext: context!, viewport: viewport }).promise + for (let i = 0; i< pdf.numPages; i++) { + const page = await pdf.getPage(i+1) + const viewport = page.getViewport({ scale: SCALE }); + const context = canvas.getContext("2d"); + canvas.height = viewport.height; + canvas.width = viewport.width; + await page.render({ canvasContext: context!, viewport: viewport }).promise; - marksPerPage[i].forEach((mark) => draw(mark, context!)) + marksPerPage[i].forEach(mark => draw(mark, context!)) - images.push(canvas.toDataURL()) + images.push(canvas.toDataURL()); } - return Promise.resolve(images) + return Promise.resolve(images); } /** * Utility to scale mark in line with the PDF-to-PNG scale */ const scaleMark = (mark: Mark): Mark => { - const { location } = mark + const { location } = mark; return { ...mark, location: { @@ -169,7 +165,7 @@ const scaleMark = (mark: Mark): Mark => { * Utility to check if a Mark has value * @param mark */ -const hasValue = (mark: Mark): boolean => !!mark.value +const hasValue = (mark: Mark): boolean => !!mark.value; /** * Draws a Mark on a Canvas representation of a PDF Page @@ -177,14 +173,14 @@ const hasValue = (mark: Mark): boolean => !!mark.value * @param ctx a Canvas representation of a specific PDF Page */ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { - const { location } = mark + const { location } = mark; - ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE - ctx!.fillStyle = 'black' - const textMetrics = ctx!.measureText(mark.value!) - const textX = location.left + (location.width - textMetrics.width) / 2 - const textY = location.top + (location.height + parseInt(ctx!.font)) / 2 - ctx!.fillText(mark.value!, textX, textY) + ctx!.font = FONT_SIZE + 'px ' + FONT_TYPE; + ctx!.fillStyle = 'black'; + const textMetrics = ctx!.measureText(mark.value!); + const textX = location.left + (location.width - textMetrics.width) / 2; + const textY = location.top + (location.height + parseInt(ctx!.font)) / 2; + ctx!.fillText(mark.value!, textX, textY); } /** @@ -192,7 +188,7 @@ const draw = (mark: Mark, ctx: CanvasRenderingContext2D) => { * @param markedPdfPages */ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { - const pdfDoc = await PDFDocument.create() + const pdfDoc = await PDFDocument.create(); for (const page of markedPdfPages) { const pngImage = await pdfDoc.embedPng(page) @@ -207,6 +203,7 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { const pdfBytes = await pdfDoc.save() return new Blob([pdfBytes], { type: 'application/pdf' }) + } /** @@ -214,12 +211,9 @@ const convertToPdfBlob = async (markedPdfPages: string[]): Promise => { * @param arrayBuffer * @param fileName */ -const convertToPdfFile = async ( - arrayBuffer: ArrayBuffer, - fileName: string -): Promise => { - const file = toFile(arrayBuffer, fileName) - return toPdfFile(file) +const convertToPdfFile = async (arrayBuffer: ArrayBuffer, fileName: string): Promise => { + const file = toFile(arrayBuffer, fileName); + return toPdfFile(file); } /** @@ -232,7 +226,7 @@ const groupMarksByPage = (marks: Mark[]) => { return marks .filter(hasValue) .map(scaleMark) - .reduce<{ [key: number]: Mark[] }>(byPage, {}) + .reduce<{[key: number]: Mark[]}>(byPage, {}) } /** @@ -243,10 +237,11 @@ const groupMarksByPage = (marks: Mark[]) => { * @param obj - accumulator in the reducer callback * @param mark - current value, i.e. Mark being examined */ -const byPage = (obj: { [key: number]: Mark[] }, mark: Mark) => { - const key = mark.location.page - const curGroup = obj[key] ?? [] - return { ...obj, [key]: [...curGroup, mark] } +const byPage = (obj: { [key: number]: Mark[]}, mark: Mark) => { + const key = mark.location.page; + const curGroup = obj[key] ?? []; + return { ...obj, [key]: [...curGroup, mark] + } } export { @@ -257,5 +252,5 @@ export { convertToPdfFile, addMarks, convertToPdfBlob, - groupMarksByPage -} + groupMarksByPage, +} \ No newline at end of file diff --git a/src/utils/relays.ts b/src/utils/relays.ts index a3e7a9c..676a59a 100644 --- a/src/utils/relays.ts +++ b/src/utils/relays.ts @@ -5,8 +5,8 @@ import { localCache } from '../services' import { ONE_WEEK_IN_MS, SIGIT_RELAY } from './const.ts' import { RelayMap, RelaySet } from '../types' -const READ_MARKER = 'read' -const WRITE_MARKET = 'write' +const READ_MARKER = "read" +const WRITE_MARKET = "write" /** * Attempts to find a relay list from the provided lookUpRelays. @@ -15,10 +15,7 @@ const WRITE_MARKET = 'write' * @param hexKey * @return found relay list or null */ -const findRelayListAndUpdateCache = async ( - lookUpRelays: string[], - hexKey: string -): Promise => { +const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: string): Promise => { try { const eventFilter: Filter = { kinds: [RelayList], @@ -45,14 +42,10 @@ const findRelayListAndUpdateCache = async ( const findRelayListInCache = async (hexKey: string): Promise => { try { // Attempt to retrieve the metadata event from the local cache - const cachedRelayListMetadataEvent = - await localCache.getUserRelayListMetadata(hexKey) + const cachedRelayListMetadataEvent = await localCache.getUserRelayListMetadata(hexKey) // Check if the cached event is not older than one week - if ( - cachedRelayListMetadataEvent && - isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt) - ) { + if (cachedRelayListMetadataEvent && isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)) { return cachedRelayListMetadataEvent.event } @@ -111,5 +104,5 @@ export { findRelayListInCache, getUserRelaySet, getDefaultRelaySet, - getDefaultRelayMap -} + getDefaultRelayMap, +} \ No newline at end of file diff --git a/src/utils/sign.ts b/src/utils/sign.ts index 3369c54..7b2dd84 100644 --- a/src/utils/sign.ts +++ b/src/utils/sign.ts @@ -5,10 +5,7 @@ import { Meta } from '../types' * This function returns the signature of last signer * It will be used in the content of export signature's signedEvent */ -const getLastSignersSig = ( - meta: Meta, - signers: `npub1${string}`[] -): string | null => { +const getLastSignersSig = (meta: Meta, signers: `npub1${string}`[]): string | null => { // if there're no signers then use creator's signature if (signers.length === 0) { try { @@ -24,11 +21,13 @@ const getLastSignersSig = ( // get the signature of last signer try { - const lastSignatureEvent: Event = JSON.parse(meta.docSignatures[lastSigner]) + const lastSignatureEvent: Event = JSON.parse( + meta.docSignatures[lastSigner] + ) return lastSignatureEvent.sig } catch (error) { return null } } -export { getLastSignersSig } +export { getLastSignersSig } \ No newline at end of file diff --git a/src/utils/utils.ts b/src/utils/utils.ts index e68e687..ed830a2 100644 --- a/src/utils/utils.ts +++ b/src/utils/utils.ts @@ -73,9 +73,9 @@ export const timeout = (ms: number = 60000) => { * @param fileHashes */ export const getFilesWithHashes = ( - files: { [filename: string]: PdfFile }, + files: { [filename: string ]: PdfFile }, fileHashes: { [key: string]: string | null } -) => { + ) => { return Object.entries(files).map(([filename, pdfFile]) => { return { pdfFile, filename, hash: fileHashes[filename] } }) diff --git a/src/utils/zip.ts b/src/utils/zip.ts index 7a7a49f..2d33aa5 100644 --- a/src/utils/zip.ts +++ b/src/utils/zip.ts @@ -36,12 +36,17 @@ const readContentOfZipEntry = async ( const loadZip = async (data: InputFileFormat): Promise => { try { - return await JSZip.loadAsync(data) + return await JSZip.loadAsync(data); } catch (err: any) { console.log('err in loading zip file :>> ', err) toast.error(err.message || 'An error occurred in loading zip file.') - return null + return null; } } -export { readContentOfZipEntry, loadZip } +export { + readContentOfZipEntry, + loadZip +} + + From 2acb353535ad88b672946550067638d7b0a09b03 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 18:24:12 +0300 Subject: [PATCH 33/49] refactor: post-PR review changes --- src/controllers/MetadataController.ts | 24 ++++++++++++++---------- src/utils/relays.ts | 22 +++++++++++++++------- 2 files changed, 29 insertions(+), 17 deletions(-) diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index e2f6852..2360275 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -20,7 +20,8 @@ import { findRelayListAndUpdateCache, findRelayListInCache, getDefaultRelaySet, - getUserRelaySet + getUserRelaySet, + isOlderThanOneWeek } from '../utils/relays.ts' export class MetadataController extends EventEmitter { @@ -133,10 +134,8 @@ export class MetadataController extends EventEmitter { // If cached metadata is found, check its validity if (cachedMetadataEvent) { - const oneWeekInMS = 7 * 24 * 60 * 60 * 1000 // Number of milliseconds in one week - // Check if the cached metadata is older than one week - if (Date.now() - cachedMetadataEvent.cachedAt > oneWeekInMS) { + if (isOlderThanOneWeek(cachedMetadataEvent.cachedAt)) { // If older than one week, find the metadata from relays in background this.checkForMoreRecentMetadata(hexKey, cachedMetadataEvent.event) @@ -164,13 +163,18 @@ export class MetadataController extends EventEmitter { * or a fallback RelaySet with Sigit's Relay */ public findRelayListMetadata = async (hexKey: string): Promise => { - const relayEvent = await findRelayListInCache(hexKey) - || await findRelayListAndUpdateCache([this.specialMetadataRelay], hexKey) - || await findRelayListAndUpdateCache(await this.nostrController.getMostPopularRelays(), hexKey) + const relayEvent = + (await findRelayListInCache(hexKey)) || + (await findRelayListAndUpdateCache( + [this.specialMetadataRelay], + hexKey + )) || + (await findRelayListAndUpdateCache( + await this.nostrController.getMostPopularRelays(), + hexKey + )) - return relayEvent - ? getUserRelaySet(relayEvent.tags) - : getDefaultRelaySet() + return relayEvent ? getUserRelaySet(relayEvent.tags) : getDefaultRelaySet() } public extractProfileMetadataContent = (event: Event) => { diff --git a/src/utils/relays.ts b/src/utils/relays.ts index 676a59a..a7a8227 100644 --- a/src/utils/relays.ts +++ b/src/utils/relays.ts @@ -5,8 +5,8 @@ import { localCache } from '../services' import { ONE_WEEK_IN_MS, SIGIT_RELAY } from './const.ts' import { RelayMap, RelaySet } from '../types' -const READ_MARKER = "read" -const WRITE_MARKET = "write" +const READ_MARKER = 'read' +const WRITE_MARKER = 'write' /** * Attempts to find a relay list from the provided lookUpRelays. @@ -15,7 +15,10 @@ const WRITE_MARKET = "write" * @param hexKey * @return found relay list or null */ -const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: string): Promise => { +const findRelayListAndUpdateCache = async ( + lookUpRelays: string[], + hexKey: string +): Promise => { try { const eventFilter: Filter = { kinds: [RelayList], @@ -42,10 +45,14 @@ const findRelayListAndUpdateCache = async (lookUpRelays: string[], hexKey: strin const findRelayListInCache = async (hexKey: string): Promise => { try { // Attempt to retrieve the metadata event from the local cache - const cachedRelayListMetadataEvent = await localCache.getUserRelayListMetadata(hexKey) + const cachedRelayListMetadataEvent = + await localCache.getUserRelayListMetadata(hexKey) // Check if the cached event is not older than one week - if (cachedRelayListMetadataEvent && isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt)) { + if ( + cachedRelayListMetadataEvent && + isOlderThanOneWeek(cachedRelayListMetadataEvent.cachedAt) + ) { return cachedRelayListMetadataEvent.event } @@ -87,7 +94,7 @@ const toRelaySet = (obj: RelaySet, tag: string[]): RelaySet => { if (marker === READ_MARKER) { obj.read.push(tag[1]) - } else if (marker === WRITE_MARKET) { + } else if (marker === WRITE_MARKER) { obj.write.push(tag[1]) } } @@ -105,4 +112,5 @@ export { getUserRelaySet, getDefaultRelaySet, getDefaultRelayMap, -} \ No newline at end of file + isOlderThanOneWeek +} From f80d4d7b67463f59dd3eb7fd331fa547da5be454 Mon Sep 17 00:00:00 2001 From: Eugene Date: Wed, 7 Aug 2024 18:31:16 +0300 Subject: [PATCH 34/49] chore: prettier --- src/pages/settings/relays/index.tsx | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/pages/settings/relays/index.tsx b/src/pages/settings/relays/index.tsx index b06908a..929a093 100644 --- a/src/pages/settings/relays/index.tsx +++ b/src/pages/settings/relays/index.tsx @@ -91,7 +91,8 @@ export const RelaysPage = () => { if (isMounted) { if ( !relaysState?.mapUpdated || - newRelayMap?.mapUpdated !== undefined && (newRelayMap?.mapUpdated > relaysState?.mapUpdated) + (newRelayMap?.mapUpdated !== undefined && + newRelayMap?.mapUpdated > relaysState?.mapUpdated) ) { if ( !relaysState?.map || From d5e07696926f554894bb316df2e6a49e00983229 Mon Sep 17 00:00:00 2001 From: Eugene Date: Thu, 8 Aug 2024 12:39:29 +0300 Subject: [PATCH 35/49] fix: pdfjs import --- src/components/DrawPDFFields/index.tsx | 6 ++++-- src/utils/pdf.ts | 6 ++++-- vite.config.ts | 11 ++++++++++- 3 files changed, 18 insertions(+), 5 deletions(-) diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index e7880e4..63eb9ee 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -36,8 +36,10 @@ import { import { truncate } from 'lodash' import { hexToNpub } from '../../utils' import { toPdfFiles } from '../../utils/pdf.ts' -PDFJS.GlobalWorkerOptions.workerSrc = - 'node_modules/pdfjs-dist/build/pdf.worker.mjs' +PDFJS.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.mjs', + import.meta.url +).toString() interface Props { selectedFiles: File[] diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 2263737..78bab85 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -3,8 +3,10 @@ import * as PDFJS from 'pdfjs-dist' import { PDFDocument } from 'pdf-lib' import { Mark } from '../types/mark.ts' -PDFJS.GlobalWorkerOptions.workerSrc = - 'node_modules/pdfjs-dist/build/pdf.worker.mjs' +PDFJS.GlobalWorkerOptions.workerSrc = new URL( + 'pdfjs-dist/build/pdf.worker.mjs', + import.meta.url +).toString() /** * Scale between the PDF page's natural size and rendered size diff --git a/vite.config.ts b/vite.config.ts index 3d09617..4275495 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,6 +5,15 @@ import tsconfigPaths from 'vite-tsconfig-paths' export default defineConfig({ plugins: [react(), tsconfigPaths()], build: { - target: 'ES2022' + target: 'ES2022', + rollupOptions: { + output: { + manualChunks(id) { + if (id.includes('pdfjs-dist/build/pdf.worker.mjs')) { + return 'pdf.worker' + } + } + } + } } }) From 738a6b87f56758e8673d55d0529e34362f3254ac Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 12:37:47 +0200 Subject: [PATCH 36/49] refactor(pdf): use minified and remove unnecessary vite config --- src/components/DrawPDFFields/index.tsx | 2 +- src/utils/pdf.ts | 2 +- vite.config.ts | 11 +---------- 3 files changed, 3 insertions(+), 12 deletions(-) diff --git a/src/components/DrawPDFFields/index.tsx b/src/components/DrawPDFFields/index.tsx index 63eb9ee..e98187c 100644 --- a/src/components/DrawPDFFields/index.tsx +++ b/src/components/DrawPDFFields/index.tsx @@ -37,7 +37,7 @@ import { truncate } from 'lodash' import { hexToNpub } from '../../utils' import { toPdfFiles } from '../../utils/pdf.ts' PDFJS.GlobalWorkerOptions.workerSrc = new URL( - 'pdfjs-dist/build/pdf.worker.mjs', + 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url ).toString() diff --git a/src/utils/pdf.ts b/src/utils/pdf.ts index 78bab85..28bbfde 100644 --- a/src/utils/pdf.ts +++ b/src/utils/pdf.ts @@ -4,7 +4,7 @@ import { PDFDocument } from 'pdf-lib' import { Mark } from '../types/mark.ts' PDFJS.GlobalWorkerOptions.workerSrc = new URL( - 'pdfjs-dist/build/pdf.worker.mjs', + 'pdfjs-dist/build/pdf.worker.min.mjs', import.meta.url ).toString() diff --git a/vite.config.ts b/vite.config.ts index 4275495..3d09617 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,15 +5,6 @@ import tsconfigPaths from 'vite-tsconfig-paths' export default defineConfig({ plugins: [react(), tsconfigPaths()], build: { - target: 'ES2022', - rollupOptions: { - output: { - manualChunks(id) { - if (id.includes('pdfjs-dist/build/pdf.worker.mjs')) { - return 'pdf.worker' - } - } - } - } + target: 'ES2022' } }) From 7b5a12246d792672734747ca33ff77cfc05c0537 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 12:41:39 +0200 Subject: [PATCH 37/49] fix(git-hooks): add executable flag --- .git-hooks/commit-msg | 0 .git-hooks/pre-commit | 0 2 files changed, 0 insertions(+), 0 deletions(-) mode change 100644 => 100755 .git-hooks/commit-msg mode change 100644 => 100755 .git-hooks/pre-commit diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg old mode 100644 new mode 100755 diff --git a/.git-hooks/pre-commit b/.git-hooks/pre-commit old mode 100644 new mode 100755 From 5015cefe98c212dbe4df81ae08602b051022ca52 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 12:43:49 +0200 Subject: [PATCH 38/49] refactor: update loading description --- src/layouts/Main.tsx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/layouts/Main.tsx b/src/layouts/Main.tsx index c8f9f27..51c2086 100644 --- a/src/layouts/Main.tsx +++ b/src/layouts/Main.tsx @@ -136,7 +136,7 @@ export const MainLayout = () => { } setIsLoading(true) - setLoadingSpinnerDesc(`Loading SIGit History`) + setLoadingSpinnerDesc(`Loading SIGit history...`) getUsersAppData() .then((appData) => { if (appData) { @@ -145,7 +145,7 @@ export const MainLayout = () => { }) .finally(() => setIsLoading(false)) } - }, [authState]) + }, [authState, dispatch]) if (isLoading) return From 748cb16f9fe44f8fc06ac6142cdd01c348bc7c1c Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 12:49:55 +0200 Subject: [PATCH 39/49] fix: loading spinner states, timestamp the file, and lint fixes --- src/pages/create/index.tsx | 42 ++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 023dd02..e34b06d 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -24,7 +24,7 @@ import JSZip from 'jszip' import { MuiFileInput } from 'mui-file-input' import { Event, kinds } from 'nostr-tools' import { useEffect, useRef, useState } from 'react' -import { DndProvider, useDrag, useDrop } from 'react-dnd' +import { DndProvider, DragSourceMonitor, useDrag, useDrop } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import { useSelector } from 'react-redux' import { useLocation, useNavigate } from 'react-router-dom' @@ -127,7 +127,7 @@ export const CreatePage = () => { }) } }) - }, []) + }, [metadata, users]) // Set up event listener for authentication event nostrController.on('nsecbunker-auth', (url) => { setAuthUrl(url) @@ -309,14 +309,16 @@ export const CreatePage = () => { } // Handle errors during file arrayBuffer conversion - const handleFileError = (file: File) => (err: any) => { + const handleFileError = (file: File) => (err: unknown) => { console.log( `Error while getting arrayBuffer of file ${file.name} :>> `, err ) - toast.error( - err.message || `Error while getting arrayBuffer of file ${file.name}` - ) + if (err instanceof Error) { + toast.error( + err.message || `Error while getting arrayBuffer of file ${file.name}` + ) + } return null } @@ -368,10 +370,12 @@ export const CreatePage = () => { } // Handle errors during zip file generation - const handleZipError = (err: any) => { + const handleZipError = (err: unknown) => { console.log('Error in zip:>> ', err) setIsLoading(false) - toast.error(err.message || 'Error occurred in generating zip file') + if (err instanceof Error) { + toast.error(err.message || 'Error occurred in generating zip file') + } return null } @@ -438,10 +442,12 @@ export const CreatePage = () => { } // Handle errors during file upload - const handleUploadError = (err: any) => { + const handleUploadError = (err: unknown) => { console.log('Error in upload:>> ', err) setIsLoading(false) - toast.error(err.message || 'Error occurred in uploading file') + if (err instanceof Error) { + toast.error(err.message || 'Error occurred in uploading file') + } return null } @@ -474,9 +480,13 @@ export const CreatePage = () => { encryptionKey ) - if (!finalZipFile) return + if (!finalZipFile) { + setIsLoading(false) + return + } - saveAs(finalZipFile, 'request.sigit.zip') + saveAs(finalZipFile, `request-${now()}.sigit.zip`) + setIsLoading(false) } const generateFilesZip = async (): Promise => { @@ -659,9 +669,11 @@ export const CreatePage = () => { } const arrayBuffer = await generateZipFile(zip) - if (!arrayBuffer) return + if (!arrayBuffer) { + setIsLoading(false) + return + } - setLoadingSpinnerDesc('Encrypting zip file') const encryptedArrayBuffer = await encryptZipFile( arrayBuffer, encryptionKey @@ -967,7 +979,7 @@ const SignerRow = ({ item: () => { return { id: user.pubkey, index } }, - collect: (monitor: any) => ({ + collect: (monitor: DragSourceMonitor) => ({ isDragging: monitor.isDragging() }) }) From 691f060ca048e3313743c62a3ae0b339a6964e2d Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 13:02:59 +0200 Subject: [PATCH 40/49] ci: reduce warning limit --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 6a8af40..976298b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 41", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 32", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", From c18d0f6c131aab8972b24d6b37a5496661adea59 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 15:46:17 +0200 Subject: [PATCH 41/49] refactor: use search submit and clean events, instead of change --- src/pages/home/index.tsx | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index d728a78..b6a801b 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -149,14 +149,27 @@ export const HomePage = () => { />
-
+
{ + e.preventDefault() + const searchInput = e.currentTarget.elements.namedItem( + 'q' + ) as HTMLInputElement + setSearch(searchInput.value) + }} + > { - setSearch(e.currentTarget.value) - }} size="small" + type="search" + onChange={(e) => { + // Handle the case when users click native search input's clear or x + if (e.currentTarget.value === '') { + setSearch(e.currentTarget.value) + } + }} sx={{ width: '100%', fontSize: '16px', @@ -175,6 +188,7 @@ export const HomePage = () => { }} /> -
+
From 83ddc1bbc810a9f0d20dbf381cca5404cb7eb4c5 Mon Sep 17 00:00:00 2001 From: enes Date: Thu, 8 Aug 2024 17:30:49 +0200 Subject: [PATCH 42/49] feat: add dropzone and multiple files support --- package-lock.json | 45 ++++++++++++++++ package.json | 3 +- src/pages/create/index.tsx | 12 ++--- src/pages/home/index.tsx | 104 ++++++++++++++++++------------------- src/utils/nostr.ts | 1 + 5 files changed, 106 insertions(+), 59 deletions(-) diff --git a/package-lock.json b/package-lock.json index 7c16799..23a0986 100644 --- a/package-lock.json +++ b/package-lock.json @@ -37,6 +37,7 @@ "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-redux": "9.1.0", "react-router-dom": "6.22.1", "react-toastify": "10.0.4", @@ -2680,6 +2681,15 @@ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" }, + "node_modules/attr-accept": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/attr-accept/-/attr-accept-2.2.2.tgz", + "integrity": "sha512-7prDjvt9HmqiZ0cl5CRjtS84sEyhsHP2coDkaZKRKVfCDo9s7iw7ChVmar78Gu9pC4SoR/28wFu/G5JJhTnqEg==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/axios": { "version": "1.6.7", "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", @@ -3857,6 +3867,24 @@ "resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz", "integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA==" }, + "node_modules/file-selector": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/file-selector/-/file-selector-0.6.0.tgz", + "integrity": "sha512-QlZ5yJC0VxHxQQsQhXvBaC7VRJ2uaxTf+Tfpu4Z/OcVQJVpZO+DGU0rkoVW5ce2SccxugvpBJoMvUs59iILYdw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + }, + "engines": { + "node": ">= 12" + } + }, + "node_modules/file-selector/node_modules/tslib": { + "version": "2.6.3", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.3.tgz", + "integrity": "sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ==", + "license": "0BSD" + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -5716,6 +5744,23 @@ "react": "^18.2.0" } }, + "node_modules/react-dropzone": { + "version": "14.2.3", + "resolved": "https://registry.npmjs.org/react-dropzone/-/react-dropzone-14.2.3.tgz", + "integrity": "sha512-O3om8I+PkFKbxCukfIR3QAGftYXDZfOE2N1mr/7qebQJHs7U+/RSL/9xomJNpRg9kM5h9soQSdf0Gc7OHF5Fug==", + "license": "MIT", + "dependencies": { + "attr-accept": "^2.2.2", + "file-selector": "^0.6.0", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">= 10.13" + }, + "peerDependencies": { + "react": ">= 16.8 || 18.0.0" + } + }, "node_modules/react-is": { "version": "18.2.0", "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", diff --git a/package.json b/package.json index 976298b..a4ebe6f 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 32", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 29", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", @@ -47,6 +47,7 @@ "react-dnd": "16.0.1", "react-dnd-html5-backend": "16.0.1", "react-dom": "^18.2.0", + "react-dropzone": "^14.2.3", "react-redux": "9.1.0", "react-router-dom": "6.22.1", "react-toastify": "10.0.4", diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index e34b06d..9218b15 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -24,7 +24,7 @@ import JSZip from 'jszip' import { MuiFileInput } from 'mui-file-input' import { Event, kinds } from 'nostr-tools' import { useEffect, useRef, useState } from 'react' -import { DndProvider, DragSourceMonitor, useDrag, useDrop } from 'react-dnd' +import { DndProvider, useDrag, useDrop } from 'react-dnd' import { HTML5Backend } from 'react-dnd-html5-backend' import { useSelector } from 'react-redux' import { useLocation, useNavigate } from 'react-router-dom' @@ -68,7 +68,7 @@ import { Mark } from '../../types/mark.ts' export const CreatePage = () => { const navigate = useNavigate() const location = useLocation() - const { uploadedFile } = location.state || {} + const { uploadedFiles } = location.state || {} const [isLoading, setIsLoading] = useState(false) const [loadingSpinnerDesc, setLoadingSpinnerDesc] = useState('') @@ -134,10 +134,10 @@ export const CreatePage = () => { }) useEffect(() => { - if (uploadedFile) { - setSelectedFiles([uploadedFile]) + if (uploadedFiles) { + setSelectedFiles([...uploadedFiles]) } - }, [uploadedFile]) + }, [uploadedFiles]) useEffect(() => { if (usersPubkey) { @@ -979,7 +979,7 @@ const SignerRow = ({ item: () => { return { id: user.pubkey, index } }, - collect: (monitor: DragSourceMonitor) => ({ + collect: (monitor) => ({ isDragging: monitor.isDragging() }) }) diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index b6a801b..3689b3e 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,6 +1,6 @@ import { Button, TextField } from '@mui/material' import JSZip from 'jszip' -import { useEffect, useRef, useState } from 'react' +import { useCallback, useEffect, useState } from 'react' import { useNavigate } from 'react-router-dom' import { toast } from 'react-toastify' import { useAppSelector } from '../../hooks' @@ -10,7 +10,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSearch } from '@fortawesome/free-solid-svg-icons' import { Select } from '../../components/Select' import { DisplaySigit } from '../../components/DisplaySigit' - +import { useDropzone } from 'react-dropzone' import { Container } from '../../components/Container' import styles from './style.module.scss' import { @@ -24,8 +24,8 @@ const FILTERS = [ 'Show all', // 'Drafts', 'In-progress', - 'Completed', - 'Archived' + 'Completed' + // 'Archived' ] as const type Filter = (typeof FILTERS)[number] @@ -40,7 +40,6 @@ type Sort = (typeof SORT_BY)[number]['value'] export const HomePage = () => { const navigate = useNavigate() - const fileInputRef = useRef(null) const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) const [parsedSigits, setParsedSigits] = useState<{ @@ -74,51 +73,52 @@ export const HomePage = () => { } }, [usersAppData]) - const handleUploadClick = () => { - if (fileInputRef.current) { - fileInputRef.current.click() - } - } + const onDrop = useCallback( + async (acceptedFiles: File[]) => { + // When uploading single file check if it's .sigit.zip + if (acceptedFiles.length === 1) { + const file = acceptedFiles[0] - const handleFileChange = async ( - event: React.ChangeEvent - ) => { - const file = event.target.files?.[0] - if (file) { - // Check if the file extension is .sigit.zip - const fileName = file.name - const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters - if (fileExtension === '.sigit.zip') { - const zip = await JSZip.loadAsync(file).catch((err) => { - console.log('err in loading zip file :>> ', err) - toast.error(err.message || 'An error occurred in loading zip file.') - return null - }) - - if (!zip) return - - // navigate to sign page if zip contains keys.json - if ('keys.json' in zip.files) { - return navigate(appPrivateRoutes.sign, { - state: { uploadedZip: file } + // Check if the file extension is .sigit.zip + const fileName = file.name + const fileExtension = fileName.slice(-10) // ".sigit.zip" has 10 characters + if (fileExtension === '.sigit.zip') { + const zip = await JSZip.loadAsync(file).catch((err) => { + console.log('err in loading zip file :>> ', err) + toast.error(err.message || 'An error occurred in loading zip file.') + return null }) - } - // navigate to verify page if zip contains meta.json - if ('meta.json' in zip.files) { - return navigate(appPublicRoutes.verify, { - state: { uploadedZip: file } - }) - } + if (!zip) return - toast.error('Invalid zip file') - return + // navigate to sign page if zip contains keys.json + if ('keys.json' in zip.files) { + return navigate(appPrivateRoutes.sign, { + state: { uploadedZip: file } + }) + } + + // navigate to verify page if zip contains meta.json + if ('meta.json' in zip.files) { + return navigate(appPublicRoutes.verify, { + state: { uploadedZip: file } + }) + } + + toast.error('Invalid SiGit zip file') + return + } } // navigate to create page - navigate(appPrivateRoutes.create, { state: { uploadedFile: file } }) - } - } + navigate(appPrivateRoutes.create, { + state: { uploadedFiles: acceptedFiles } + }) + }, + [navigate] + ) + + const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }) const [search, setSearch] = useState('') const [filter, setFilter] = useState('Show all') @@ -202,15 +202,15 @@ export const HomePage = () => {
-
- -
Click or drag files to upload!
+
+
+ + {isDragActive ? ( +

Drop the files here ...

+ ) : ( +

Click or drag files to upload!

+ )} +
{Object.keys(parsedSigits) diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index e27601b..bec854f 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -120,6 +120,7 @@ export const queryNip05 = async ( if (!match) throw new Error('Invalid nip05') // Destructure the match result, assigning default value '_' to name if not provided + // First variable from the match destructuring is ignored const [, name = '_', domain] = match // Construct the URL to query the NIP-05 data From 93b2477839900598195bbb6ab28c82493a8abc98 Mon Sep 17 00:00:00 2001 From: enes Date: Fri, 9 Aug 2024 10:58:30 +0200 Subject: [PATCH 43/49] feat(home): add search param to address bar and sync the state with navigation --- src/pages/home/index.tsx | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 3689b3e..47a917b 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -1,7 +1,7 @@ import { Button, TextField } from '@mui/material' import JSZip from 'jszip' import { useCallback, useEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' +import { useNavigate, useSearchParams } from 'react-router-dom' import { toast } from 'react-toastify' import { useAppSelector } from '../../hooks' import { appPrivateRoutes, appPublicRoutes } from '../../routes' @@ -40,6 +40,15 @@ type Sort = (typeof SORT_BY)[number]['value'] export const HomePage = () => { const navigate = useNavigate() + const [searchParams, setSearchParams] = useSearchParams() + const q = searchParams.get('q') ?? '' + + useEffect(() => { + const searchInput = document.getElementById('q') as HTMLInputElement | null + if (searchInput) { + searchInput.value = q + } + }, [q]) const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) const [parsedSigits, setParsedSigits] = useState<{ @@ -120,7 +129,6 @@ export const HomePage = () => { const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }) - const [search, setSearch] = useState('') const [filter, setFilter] = useState('Show all') const [sort, setSort] = useState('desc') @@ -156,18 +164,22 @@ export const HomePage = () => { const searchInput = e.currentTarget.elements.namedItem( 'q' ) as HTMLInputElement - setSearch(searchInput.value) + searchParams.set('q', searchInput.value) + setSearchParams(searchParams) }} > { // Handle the case when users click native search input's clear or x if (e.currentTarget.value === '') { - setSearch(e.currentTarget.value) + searchParams.delete('q') + setSearchParams(searchParams) } }} sx={{ @@ -216,7 +228,7 @@ export const HomePage = () => { {Object.keys(parsedSigits) .filter((s) => { const { title, signedStatus } = parsedSigits[s] - const isMatch = title?.toLowerCase().includes(search.toLowerCase()) + const isMatch = title?.toLowerCase().includes(q.toLowerCase()) switch (filter) { case 'Completed': return signedStatus === SignedStatus.Complete && isMatch From 15aa98e9db0453cceea9dba7594e1fee71eb3f52 Mon Sep 17 00:00:00 2001 From: enes Date: Fri, 9 Aug 2024 11:29:00 +0200 Subject: [PATCH 44/49] refactor(home): increase dropzone area --- .../DrawPDFFields/style.module.scss | 9 +- src/pages/home/index.tsx | 245 +++++++++--------- src/pages/home/style.module.scss | 42 +-- 3 files changed, 155 insertions(+), 141 deletions(-) diff --git a/src/components/DrawPDFFields/style.module.scss b/src/components/DrawPDFFields/style.module.scss index e3e7856..08554b2 100644 --- a/src/components/DrawPDFFields/style.module.scss +++ b/src/components/DrawPDFFields/style.module.scss @@ -30,6 +30,7 @@ border: 1px solid rgba(0, 0, 0, 0.137); padding: 5px; cursor: pointer; + -webkit-user-select: none; user-select: none; &.selected { @@ -42,15 +43,15 @@ border-color: #01aaad79; } } - } } } .pdfImageWrapper { position: relative; + -webkit-user-select: none; user-select: none; - + &.drawing { cursor: crosshair; } @@ -94,7 +95,7 @@ background-color: #fff; border: 1px solid rgb(160, 160, 160); border-radius: 50%; - color: #E74C3C; + color: #e74c3c; font-size: 10px; cursor: pointer; } @@ -110,4 +111,4 @@ background: #fff; padding: 5px 0; } -} \ No newline at end of file +} diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 47a917b..4f5c560 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -127,134 +127,143 @@ export const HomePage = () => { [navigate] ) - const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop }) + const { getRootProps, getInputProps, isDragActive, open } = useDropzone({ + onDrop, + noClick: true + }) const [filter, setFilter] = useState('Show all') const [sort, setSort] = useState('desc') return ( - -
-
- { - return { ...s } - })} - /> -
-
-
{ - e.preventDefault() - const searchInput = e.currentTarget.elements.namedItem( - 'q' - ) as HTMLInputElement - searchParams.set('q', searchInput.value) - setSearchParams(searchParams) - }} - > - { - // Handle the case when users click native search input's clear or x - if (e.currentTarget.value === '') { - searchParams.delete('q') - setSearchParams(searchParams) +
+ +
+
+ - {isDragActive ? ( -

Drop the files here ...

- ) : ( -

Click or drag files to upload!

- )} +
+
+ + {isDragActive ? ( +

Drop the files here ...

+ ) : ( +

Click or drag files to upload!

+ )} +
-
-
- {Object.keys(parsedSigits) - .filter((s) => { - const { title, signedStatus } = parsedSigits[s] - const isMatch = title?.toLowerCase().includes(q.toLowerCase()) - switch (filter) { - case 'Completed': - return signedStatus === SignedStatus.Complete && isMatch - case 'In-progress': - return signedStatus === SignedStatus.Partial && isMatch - case 'Show all': - return isMatch - default: - console.error('Filter case not handled.') - } - }) - .sort((a, b) => { - const x = parsedSigits[a].createdAt ?? 0 - const y = parsedSigits[b].createdAt ?? 0 - return sort === 'desc' ? y - x : x - y - }) - .map((key) => ( - - ))} -
- +
+ {Object.keys(parsedSigits) + .filter((s) => { + const { title, signedStatus } = parsedSigits[s] + const isMatch = title?.toLowerCase().includes(q.toLowerCase()) + switch (filter) { + case 'Completed': + return signedStatus === SignedStatus.Complete && isMatch + case 'In-progress': + return signedStatus === SignedStatus.Partial && isMatch + case 'Show all': + return isMatch + default: + console.error('Filter case not handled.') + } + }) + .sort((a, b) => { + const x = parsedSigits[a].createdAt ?? 0 + const y = parsedSigits[b].createdAt ?? 0 + return sort === 'desc' ? y - x : x - y + }) + .map((key) => ( + + ))} +
+ +
) } diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index 9a1b4c0..6b74e76 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -53,31 +53,35 @@ } .dropzone { + position: relative; + + font-size: 16px; background-color: $overlay-background-color; height: 250px; - transition: padding ease 0.2s; - padding: 15px; + color: rgba(0, 0, 0, 0.25); + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; - &:hover { - padding: 10px; - - > div { - background: rgba(0, 0, 0, 0.15); - } - } - - > div { - transition: background-color ease 0.2s; - display: flex; - flex-direction: column; - justify-content: center; - align-items: center; + &::before { + content: ''; + position: absolute; + transition: + background-color ease 0.2s, + inset ease 0.2s; background: rgba(0, 0, 0, 0.1); - color: rgba(0, 0, 0, 0.25); - height: 100%; border-radius: 2px; border: dashed 3px rgba(0, 0, 0, 0.1); - font-size: 16px; + inset: 15px; + } + + &.isDragActive, + &:hover { + &::before { + inset: 10px; + background: rgba(0, 0, 0, 0.15); + } } } From 72d0e065eae580611c5a9252c8521086495dd2c5 Mon Sep 17 00:00:00 2001 From: enes Date: Fri, 9 Aug 2024 12:00:36 +0200 Subject: [PATCH 45/49] fix(home): focus outlines and decorations --- src/App.scss | 2 ++ src/pages/home/index.tsx | 25 +++++++++++++------------ src/pages/home/style.module.scss | 9 +++++++++ src/theme/index.ts | 3 +++ 4 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/App.scss b/src/App.scss index 5461a4a..6724890 100644 --- a/src/App.scss +++ b/src/App.scss @@ -56,7 +56,9 @@ a { text-decoration: none; text-decoration-color: inherit; transition: ease 0.4s; + outline: none; + &:focus, &:hover { color: $primary-light; text-decoration: underline; diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index 4f5c560..bb3115f 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -136,7 +136,7 @@ export const HomePage = () => { const [sort, setSort] = useState('desc') return ( -
+
@@ -212,26 +212,27 @@ export const HomePage = () => { borderBottomLeftRadius: 0 }} variant={'contained'} - aria-label="Submit Search" + aria-label="submit search" >
-
-
- - {isDragActive ? ( -

Drop the files here ...

- ) : ( -

Click or drag files to upload!

- )} -
-
+ + {isDragActive ? ( +

Drop the files here ...

+ ) : ( +

Click or drag files to upload!

+ )} +
{Object.keys(parsedSigits) .filter((s) => { diff --git a/src/pages/home/style.module.scss b/src/pages/home/style.module.scss index 6b74e76..63917a0 100644 --- a/src/pages/home/style.module.scss +++ b/src/pages/home/style.module.scss @@ -76,6 +76,7 @@ inset: 15px; } + &:focus, &.isDragActive, &:hover { &::before { @@ -83,6 +84,14 @@ background: rgba(0, 0, 0, 0.15); } } + + // Override button styles + padding: 0; + border: none; + outline: none; + letter-spacing: 1px; + font-weight: 500; + font-family: inherit; } .submissions { diff --git a/src/theme/index.ts b/src/theme/index.ts index a7a37d4..8cc008f 100644 --- a/src/theme/index.ts +++ b/src/theme/index.ts @@ -33,6 +33,9 @@ export const theme = extendTheme({ boxShadow: 'unset', lineHeight: 'inherit', borderRadius: '4px', + ':focus': { + textDecoration: 'none' + }, ':hover': { background: 'var(--primary-light)', boxShadow: 'unset' From f896849ffd88d98c0edfae94eda2c6eed8b884ed Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 12 Aug 2024 14:26:03 +0200 Subject: [PATCH 46/49] refactor: expand useSigitMeta and add comments --- src/components/DisplaySigit/index.tsx | 75 +++++---- src/components/DisplaySigner/index.tsx | 2 + src/hooks/useSigitMeta.tsx | 222 ++++++++++++++----------- src/pages/home/index.tsx | 37 ++--- src/utils/index.ts | 1 + src/utils/meta.ts | 181 ++++++++++++++++++++ 6 files changed, 369 insertions(+), 149 deletions(-) create mode 100644 src/utils/meta.ts diff --git a/src/components/DisplaySigit/index.tsx b/src/components/DisplaySigit/index.tsx index e930b37..0fe5c2f 100644 --- a/src/components/DisplaySigit/index.tsx +++ b/src/components/DisplaySigit/index.tsx @@ -1,6 +1,6 @@ -import { Dispatch, SetStateAction, useEffect } from 'react' +import { useEffect, useState } from 'react' import { Meta, ProfileMetadata } from '../../types' -import { SigitInfo, SignedStatus } from '../../hooks/useSigitMeta' +import { SigitCardDisplayInfo, SigitStatus } from '../../utils' import { Event, kinds } from 'nostr-tools' import { Link } from 'react-router-dom' import { MetadataController } from '../../controllers' @@ -25,17 +25,10 @@ import { getExtensionIconLabel } from '../getExtensionIconLabel' type SigitProps = { meta: Meta - parsedMeta: SigitInfo - profiles: { [key: string]: ProfileMetadata } - setProfiles: Dispatch> + parsedMeta: SigitCardDisplayInfo } -export const DisplaySigit = ({ - meta, - parsedMeta, - profiles, - setProfiles -}: SigitProps) => { +export const DisplaySigit = ({ meta, parsedMeta }: SigitProps) => { const { title, createdAt, @@ -45,51 +38,67 @@ export const DisplaySigit = ({ fileExtensions } = parsedMeta + const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>( + {} + ) + useEffect(() => { - const hexKeys: string[] = [] + const hexKeys = new Set([ + ...signers.map((signer) => npubToHex(signer)!) + ]) if (submittedBy) { - hexKeys.push(npubToHex(submittedBy)!) + hexKeys.add(npubToHex(submittedBy)!) } - hexKeys.push(...signers.map((signer) => npubToHex(signer)!)) const metadataController = new MetadataController() + + const handleMetadataEvent = (key: string) => (event: Event) => { + const metadataContent = + metadataController.extractProfileMetadataContent(event) + + if (metadataContent) { + setProfiles((prev) => ({ + ...prev, + [key]: metadataContent + })) + } + } + + const handleEventListener = + (key: string) => (kind: number, event: Event) => { + if (kind === kinds.Metadata) { + handleMetadataEvent(key)(event) + } + } + hexKeys.forEach((key) => { if (!(key in profiles)) { - const handleMetadataEvent = (event: Event) => { - const metadataContent = - metadataController.extractProfileMetadataContent(event) - - if (metadataContent) - setProfiles((prev) => ({ - ...prev, - [key]: metadataContent - })) - } - - metadataController.on(key, (kind: number, event: Event) => { - if (kind === kinds.Metadata) { - handleMetadataEvent(event) - } - }) + metadataController.on(key, handleEventListener(key)) metadataController .findMetadata(key) .then((metadataEvent) => { - if (metadataEvent) handleMetadataEvent(metadataEvent) + if (metadataEvent) handleMetadataEvent(key)(metadataEvent) }) .catch((err) => { console.error(`error occurred in finding metadata for: ${key}`, err) }) } }) - }, [submittedBy, signers, profiles, setProfiles]) + + return () => { + hexKeys.forEach((key) => { + metadataController.off(key, handleEventListener(key)) + }) + } + }, [submittedBy, signers, profiles]) return (
() useEffect(() => { + if (!meta) return + const updateSignStatus = async () => { const npub = hexToNpub(pubkey) if (npub in meta.docSignatures) { diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index e798ef0..d8ef9c2 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -1,117 +1,147 @@ import { useEffect, useState } from 'react' -import { toast } from 'react-toastify' import { CreateSignatureEventContent, Meta } from '../types' -import { parseJson } from '../utils' +import { Mark } from '../types/mark' +import { + parseCreateSignatureEvent, + parseCreateSignatureEventContent, + SigitMetaParseError, + SigitStatus, + SignStatus +} from '../utils' +import { toast } from 'react-toastify' +import { verifyEvent } from 'nostr-tools' import { Event } from 'nostr-tools' -type npub = `npub1${string}` +interface FlatMeta extends Meta, CreateSignatureEventContent, Partial { + // Validated create signature event + isValid: boolean -export enum SignedStatus { - Partial = 'In-Progress', - Complete = 'Completed' -} - -export interface SigitInfo { - createdAt?: number - title?: string - submittedBy?: string - signers: npub[] - fileExtensions: string[] - signedStatus: SignedStatus -} - -export const extractSigitInfo = async (meta: Meta) => { - if (!meta?.createSignature) return - - const sigitInfo: SigitInfo = { - signers: [], - fileExtensions: [], - signedStatus: SignedStatus.Partial + // Calculated status fields + signedStatus: SigitStatus + signersStatus: { + [signer: `npub1${string}`]: SignStatus } - - const createSignatureEvent = await parseJson( - meta.createSignature - ).catch((err) => { - console.log('err in parsing the createSignature event:>> ', err) - toast.error( - err.message || 'error occurred in parsing the create signature event' - ) - return - }) - - if (!createSignatureEvent) return - - // created_at in nostr events are stored in seconds - sigitInfo.createdAt = createSignatureEvent.created_at * 1000 - - const createSignatureContent = await parseJson( - createSignatureEvent.content - ).catch((err) => { - console.log(`err in parsing the createSignature event's content :>> `, err) - return - }) - - if (!createSignatureContent) return - - const files = Object.keys(createSignatureContent.fileHashes) - const extensions = files.reduce((result: string[], file: string) => { - const extension = file.split('.').pop() - if (extension) { - result.push(extension) - } - return result - }, []) - - const signedBy = Object.keys(meta.docSignatures) as npub[] - const isCompletelySigned = createSignatureContent.signers.every((signer) => - signedBy.includes(signer) - ) - - sigitInfo.title = createSignatureContent.title - sigitInfo.submittedBy = createSignatureEvent.pubkey - sigitInfo.signers = createSignatureContent.signers - sigitInfo.fileExtensions = extensions - - if (isCompletelySigned) { - sigitInfo.signedStatus = SignedStatus.Complete - } - - return sigitInfo } -export const useSigitMeta = (meta: Meta) => { - const [title, setTitle] = useState() - const [createdAt, setCreatedAt] = useState() - const [submittedBy, setSubmittedBy] = useState() - const [signers, setSigners] = useState([]) - const [signedStatus, setSignedStatus] = useState( - SignedStatus.Partial +/** + * Custom use hook for parsing the Sigit Meta + * @param meta Sigit Meta + * @returns flattened Meta object with calculated signed status + */ +export const useSigitMeta = (meta: Meta): FlatMeta => { + const [isValid, setIsValid] = useState(false) + const [kind, setKind] = useState() + const [tags, setTags] = useState() + const [created_at, setCreatedAt] = useState() + const [pubkey, setPubkey] = useState() // submittedBy, pubkey from nostr event + const [id, setId] = useState() + const [sig, setSig] = useState() + + const [signers, setSigners] = useState<`npub1${string}`[]>([]) + const [viewers, setViewers] = useState<`npub1${string}`[]>([]) + const [fileHashes, setFileHashes] = useState<{ + [user: `npub1${string}`]: string + }>({}) + const [markConfig, setMarkConfig] = useState([]) + const [title, setTitle] = useState('') + const [zipUrl, setZipUrl] = useState('') + + const [signedStatus, setSignedStatus] = useState( + SigitStatus.Partial ) - const [fileExtensions, setFileExtensions] = useState([]) + const [signersStatus, setSignersStatus] = useState<{ + [signer: `npub1${string}`]: SignStatus + }>({}) useEffect(() => { - const getSigitInfo = async () => { - const sigitInfo = await extractSigitInfo(meta) + if (!meta) return + ;(async function () { + try { + const createSignatureEvent = await parseCreateSignatureEvent( + meta.createSignature + ) - if (!sigitInfo) return + const { kind, tags, created_at, pubkey, id, sig, content } = + createSignatureEvent - setTitle(sigitInfo.title) - setCreatedAt(sigitInfo.createdAt) - setSubmittedBy(sigitInfo.submittedBy) - setSigners(sigitInfo.signers) - setSignedStatus(sigitInfo.signedStatus) - setFileExtensions(sigitInfo.fileExtensions) - } + setIsValid(verifyEvent(createSignatureEvent)) + setKind(kind) + setTags(tags) + // created_at in nostr events are stored in seconds + setCreatedAt(created_at * 1000) + setPubkey(pubkey) + setId(id) + setSig(sig) - getSigitInfo() + const { title, signers, viewers, fileHashes, markConfig, zipUrl } = + await parseCreateSignatureEventContent(content) + + setTitle(title) + setSigners(signers) + setViewers(viewers) + setFileHashes(fileHashes) + setMarkConfig(markConfig) + setZipUrl(zipUrl) + + // Parse each signature event and set signer status + for (const npub in meta.docSignatures) { + try { + const event = await parseCreateSignatureEvent( + meta.docSignatures[npub as `npub1${string}`] + ) + const isValidSignature = verifyEvent(event) + setSignersStatus((prev) => { + return { + ...prev, + [npub]: isValidSignature + ? SignStatus.Signed + : SignStatus.Invalid + } + }) + } catch (error) { + setSignersStatus((prev) => { + return { + ...prev, + [npub]: SignStatus.Invalid + } + }) + } + } + const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] + const isCompletelySigned = signers.every((signer) => + signedBy.includes(signer) + ) + setSignedStatus( + isCompletelySigned ? SigitStatus.Complete : SigitStatus.Partial + ) + } catch (error) { + if (error instanceof SigitMetaParseError) { + toast.error(error.message) + } + console.error(error) + } + })() }, [meta]) return { - title, - createdAt, - submittedBy, + modifiedAt: meta.modifiedAt, + createSignature: meta.createSignature, + docSignatures: meta.docSignatures, + keys: meta.keys, + isValid, + kind, + tags, + created_at, + pubkey, + id, + sig, signers, + viewers, + fileHashes, + markConfig, + title, + zipUrl, signedStatus, - fileExtensions + signersStatus } } diff --git a/src/pages/home/index.tsx b/src/pages/home/index.tsx index bb3115f..0f0329b 100644 --- a/src/pages/home/index.tsx +++ b/src/pages/home/index.tsx @@ -5,7 +5,7 @@ import { useNavigate, useSearchParams } from 'react-router-dom' import { toast } from 'react-toastify' import { useAppSelector } from '../../hooks' import { appPrivateRoutes, appPublicRoutes } from '../../routes' -import { Meta, ProfileMetadata } from '../../types' +import { Meta } from '../../types' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { faSearch } from '@fortawesome/free-solid-svg-icons' import { Select } from '../../components/Select' @@ -14,10 +14,10 @@ import { useDropzone } from 'react-dropzone' import { Container } from '../../components/Container' import styles from './style.module.scss' import { - extractSigitInfo, - SigitInfo, - SignedStatus -} from '../../hooks/useSigitMeta' + extractSigitCardDisplayInfo, + SigitCardDisplayInfo, + SigitStatus +} from '../../utils' // Unsupported Filter options are commented const FILTERS = [ @@ -52,29 +52,28 @@ export const HomePage = () => { const [sigits, setSigits] = useState<{ [key: string]: Meta }>({}) const [parsedSigits, setParsedSigits] = useState<{ - [key: string]: SigitInfo + [key: string]: SigitCardDisplayInfo }>({}) - const [profiles, setProfiles] = useState<{ [key: string]: ProfileMetadata }>( - {} - ) const usersAppData = useAppSelector((state) => state.userAppData) useEffect(() => { if (usersAppData) { const getSigitInfo = async () => { + const parsedSigits: { [key: string]: SigitCardDisplayInfo } = {} for (const key in usersAppData.sigits) { if (Object.prototype.hasOwnProperty.call(usersAppData.sigits, key)) { - const sigitInfo = await extractSigitInfo(usersAppData.sigits[key]) + const sigitInfo = await extractSigitCardDisplayInfo( + usersAppData.sigits[key] + ) if (sigitInfo) { - setParsedSigits((prev) => { - return { - ...prev, - [key]: sigitInfo - } - }) + parsedSigits[key] = sigitInfo } } } + + setParsedSigits({ + ...parsedSigits + }) } setSigits(usersAppData.sigits) @@ -240,9 +239,9 @@ export const HomePage = () => { const isMatch = title?.toLowerCase().includes(q.toLowerCase()) switch (filter) { case 'Completed': - return signedStatus === SignedStatus.Complete && isMatch + return signedStatus === SigitStatus.Complete && isMatch case 'In-progress': - return signedStatus === SignedStatus.Partial && isMatch + return signedStatus === SigitStatus.Partial && isMatch case 'Show all': return isMatch default: @@ -259,8 +258,6 @@ export const HomePage = () => { key={`sigit-${key}`} parsedMeta={parsedSigits[key]} meta={sigits[key]} - profiles={profiles} - setProfiles={setProfiles} /> ))}
diff --git a/src/utils/index.ts b/src/utils/index.ts index 1b0c133..ffac72d 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -7,3 +7,4 @@ export * from './string' export * from './zip' export * from './utils' export * from './mark' +export * from './meta' diff --git a/src/utils/meta.ts b/src/utils/meta.ts new file mode 100644 index 0000000..e9e3f13 --- /dev/null +++ b/src/utils/meta.ts @@ -0,0 +1,181 @@ +import { CreateSignatureEventContent, Meta } from '../types' +import { parseJson } from '.' +import { Event } from 'nostr-tools' +import { toast } from 'react-toastify' + +export enum SignStatus { + Signed = 'Signed', + Pending = 'Pending', + Invalid = 'Invalid Sign' +} + +export enum SigitStatus { + Partial = 'In-Progress', + Complete = 'Completed' +} + +type Jsonable = + | string + | number + | boolean + | null + | undefined + | readonly Jsonable[] + | { readonly [key: string]: Jsonable } + | { toJSON(): Jsonable } + +export class SigitMetaParseError extends Error { + public readonly context?: Jsonable + + constructor( + message: string, + options: { cause?: Error; context?: Jsonable } = {} + ) { + const { cause, context } = options + + super(message, { cause }) + this.name = this.constructor.name + + this.context = context + } +} + +/** + * Handle meta errors + * Wraps the errors without message property and stringify to a message so we can use it later + * @param error + * @returns + */ +function handleError(error: unknown): Error { + if (error instanceof Error) return error + + // No message error, wrap it and stringify + let stringified = 'Unable to stringify the thrown value' + try { + stringified = JSON.stringify(error) + } catch (error) { + console.error(stringified, error) + } + + return new Error(`[SiGit Error]: ${stringified}`) +} + +// Reuse common error messages for meta parsing +export enum SigitMetaParseErrorType { + 'PARSE_ERROR_SIGNATURE_EVENT' = 'error occurred in parsing the create signature event', + 'PARSE_ERROR_SIGNATURE_EVENT_CONTENT' = "err in parsing the createSignature event's content" +} + +export interface SigitCardDisplayInfo { + createdAt?: number + title?: string + submittedBy?: string + signers: `npub1${string}`[] + fileExtensions: string[] + signedStatus: SigitStatus +} + +/** + * Wrapper for createSignatureEvent parse that throws custom SigitMetaParseError with cause and context + * @param raw Raw string for parsing + * @returns parsed Event + */ +export const parseCreateSignatureEvent = async ( + raw: string +): Promise => { + try { + const createSignatureEvent = await parseJson(raw) + return createSignatureEvent + } catch (error) { + throw new SigitMetaParseError( + SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT, + { + cause: handleError(error), + context: raw + } + ) + } +} + +/** + * Wrapper for event content parser that throws custom SigitMetaParseError with cause and context + * @param raw Raw string for parsing + * @returns parsed CreateSignatureEventContent + */ +export const parseCreateSignatureEventContent = async ( + raw: string +): Promise => { + try { + const createSignatureEventContent = + await parseJson(raw) + return createSignatureEventContent + } catch (error) { + throw new SigitMetaParseError( + SigitMetaParseErrorType.PARSE_ERROR_SIGNATURE_EVENT_CONTENT, + { + cause: handleError(error), + context: raw + } + ) + } +} + +/** + * Extracts only necessary metadata for the card display + * @param meta Sigit metadata + * @returns SigitCardDisplayInfo + */ +export const extractSigitCardDisplayInfo = async (meta: Meta) => { + if (!meta?.createSignature) return + + const sigitInfo: SigitCardDisplayInfo = { + signers: [], + fileExtensions: [], + signedStatus: SigitStatus.Partial + } + + try { + const createSignatureEvent = await parseCreateSignatureEvent( + meta.createSignature + ) + + // created_at in nostr events are stored in seconds + sigitInfo.createdAt = createSignatureEvent.created_at * 1000 + + const createSignatureContent = await parseCreateSignatureEventContent( + createSignatureEvent.content + ) + + const files = Object.keys(createSignatureContent.fileHashes) + const extensions = files.reduce((result: string[], file: string) => { + const extension = file.split('.').pop() + if (extension) { + result.push(extension) + } + return result + }, []) + + const signedBy = Object.keys(meta.docSignatures) as `npub1${string}`[] + const isCompletelySigned = createSignatureContent.signers.every((signer) => + signedBy.includes(signer) + ) + + sigitInfo.title = createSignatureContent.title + sigitInfo.submittedBy = createSignatureEvent.pubkey + sigitInfo.signers = createSignatureContent.signers + sigitInfo.fileExtensions = extensions + + if (isCompletelySigned) { + sigitInfo.signedStatus = SigitStatus.Complete + } + + return sigitInfo + } catch (error) { + if (error instanceof SigitMetaParseError) { + toast.error(error.message) + console.error(error.name, error.message, error.cause, error.context) + } else { + console.error('Unexpected error', error) + } + } +} From 950593aeedc83afc98b6c2c6a8dab6a68f1d1102 Mon Sep 17 00:00:00 2001 From: enes Date: Mon, 12 Aug 2024 16:01:56 +0200 Subject: [PATCH 47/49] chore(git-hooks): clean commit-msg script --- .git-hooks/commit-msg | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.git-hooks/commit-msg b/.git-hooks/commit-msg index c4c6ce6..5ee2d7c 100755 --- a/.git-hooks/commit-msg +++ b/.git-hooks/commit-msg @@ -5,15 +5,15 @@ commit_message=$(cat "$1") if (echo "$commit_message" | grep -Eq "^(build|chore|ci|docs|feat|fix|perf|refactor|revert|style|test)(\([a-z0-9 \-]+\))?!?: .+$") then tput setaf 2; - echo -e "${GREEN} ✔ Commit message meets Conventional Commit standards" + echo "✔ Commit message meets Conventional Commit standards" tput sgr0; exit 0 fi tput setaf 1; -echo -e "${RED}❌ Commit message does not meet the Conventional Commit standard!" +echo "❌ Commit message does not meet the Conventional Commit standard!" tput sgr0; echo "An example of a valid message is:" echo " feat(login): add the 'remember me' button" -echo "ℹ More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary" +echo "📝 More details at: https://www.conventionalcommits.org/en/v1.0.0/#summary" exit 1 \ No newline at end of file From 6652c6519e3384a9f08e725d9c79b7d9c3cc7142 Mon Sep 17 00:00:00 2001 From: enes Date: Tue, 13 Aug 2024 11:52:05 +0200 Subject: [PATCH 48/49] refactor(review): add date util functions --- package.json | 2 +- src/controllers/AuthController.ts | 5 +++-- src/controllers/MetadataController.ts | 6 +++--- src/controllers/NostrController.ts | 25 +++++++++++++-------- src/hooks/useSigitMeta.tsx | 3 ++- src/pages/create/index.tsx | 12 +++++------ src/pages/settings/profile/index.tsx | 4 ++-- src/pages/sign/index.tsx | 13 +++++------ src/pages/verify/index.tsx | 6 +++--- src/utils/meta.ts | 4 ++-- src/utils/misc.ts | 8 +++---- src/utils/nostr.ts | 31 ++++++++++++++++++++------- 12 files changed, 69 insertions(+), 50 deletions(-) diff --git a/package.json b/package.json index a4ebe6f..5e1619e 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "scripts": { "dev": "vite", "build": "tsc && vite build", - "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 29", + "lint": "eslint . --ext ts,tsx --report-unused-disable-directives --max-warnings 25", "lint:fix": "eslint . --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "lint:staged": "eslint --fix --ext ts,tsx --report-unused-disable-directives --max-warnings 0", "formatter:check": "prettier --check \"src/**/*.{ts,tsx,js,jsx,html,css,sass,less,yml,md,graphql}\"", diff --git a/src/controllers/AuthController.ts b/src/controllers/AuthController.ts index e0d2d79..33f5c82 100644 --- a/src/controllers/AuthController.ts +++ b/src/controllers/AuthController.ts @@ -12,7 +12,8 @@ import { getAuthToken, getVisitedLink, saveAuthToken, - compareObjects + compareObjects, + unixNow } from '../utils' import { appPrivateRoutes } from '../routes' import { SignedEvent } from '../types' @@ -54,7 +55,7 @@ export class AuthController { }) // Nostr uses unix timestamps - const timestamp = Math.floor(Date.now() / 1000) + const timestamp = unixNow() const { hostname } = window.location const authEvent: EventTemplate = { diff --git a/src/controllers/MetadataController.ts b/src/controllers/MetadataController.ts index 2360275..8f4d190 100644 --- a/src/controllers/MetadataController.ts +++ b/src/controllers/MetadataController.ts @@ -12,7 +12,7 @@ import { import { NostrJoiningBlock, ProfileMetadata, RelaySet } from '../types' import { NostrController } from '.' import { toast } from 'react-toastify' -import { queryNip05 } from '../utils' +import { queryNip05, unixNow } from '../utils' import NDK, { NDKEvent, NDKSubscription } from '@nostr-dev-kit/ndk' import { EventEmitter } from 'tseep' import { localCache } from '../services' @@ -194,7 +194,7 @@ export class MetadataController extends EventEmitter { let signedMetadataEvent = event if (event.sig.length < 1) { - const timestamp = Math.floor(Date.now() / 1000) + const timestamp = unixNow() // Metadata event to publish to the wss://purplepag.es relay const newMetadataEvent: Event = { @@ -265,7 +265,7 @@ export class MetadataController extends EventEmitter { // initialize job request const jobEventTemplate: EventTemplate = { content: '', - created_at: Math.round(Date.now() / 1000), + created_at: unixNow(), kind: 68001, tags: [ ['i', `${created_at * 1000}`], diff --git a/src/controllers/NostrController.ts b/src/controllers/NostrController.ts index fa558de..19182b5 100644 --- a/src/controllers/NostrController.ts +++ b/src/controllers/NostrController.ts @@ -42,6 +42,7 @@ import { import { compareObjects, getNsecBunkerDelegatedKey, + unixNow, verifySignedEvent } from '../utils' import { getDefaultRelayMap } from '../utils/relays.ts' @@ -244,7 +245,7 @@ export class NostrController extends EventEmitter { if (!firstSuccessfulPublish) { // If no publish was successful, collect the reasons for failures - const failedPublishes: any[] = [] + const failedPublishes: unknown[] = [] const fallbackRejectionReason = 'Attempt to publish an event has been rejected with unknown reason.' @@ -504,11 +505,13 @@ export class NostrController extends EventEmitter { } else if (loginMethod === LoginMethods.extension) { const nostr = this.getNostrObject() - return (await nostr.signEvent(event as NostrEvent).catch((err: any) => { - console.log('Error while signing event: ', err) + return (await nostr + .signEvent(event as NostrEvent) + .catch((err: unknown) => { + console.log('Error while signing event: ', err) - throw err - })) as Event + throw err + })) as Event } else { return Promise.reject( `We could not sign the event, none of the signing methods are available` @@ -625,8 +628,12 @@ export class NostrController extends EventEmitter { */ capturePublicKey = async (): Promise => { const nostr = this.getNostrObject() - const pubKey = await nostr.getPublicKey().catch((err: any) => { - return Promise.reject(err.message) + const pubKey = await nostr.getPublicKey().catch((err: unknown) => { + if (err instanceof Error) { + return Promise.reject(err.message) + } else { + return Promise.reject(JSON.stringify(err)) + } }) if (!pubKey) { @@ -708,7 +715,7 @@ export class NostrController extends EventEmitter { npub: string, extraRelaysToPublish?: string[] ): Promise => { - const timestamp = Math.floor(Date.now() / 1000) + const timestamp = unixNow() const relayURIs = Object.keys(relayMap) // More info about this kind of event available https://github.com/nostr-protocol/nips/blob/master/65.md @@ -810,7 +817,7 @@ export class NostrController extends EventEmitter { // initialize job request const jobEventTemplate: EventTemplate = { content: '', - created_at: Math.round(Date.now() / 1000), + created_at: unixNow(), kind: 68001, tags: [ ['i', `${JSON.stringify(relayURIs)}`], diff --git a/src/hooks/useSigitMeta.tsx b/src/hooks/useSigitMeta.tsx index d8ef9c2..aebd791 100644 --- a/src/hooks/useSigitMeta.tsx +++ b/src/hooks/useSigitMeta.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from 'react' import { CreateSignatureEventContent, Meta } from '../types' import { Mark } from '../types/mark' import { + fromUnixTimestamp, parseCreateSignatureEvent, parseCreateSignatureEventContent, SigitMetaParseError, @@ -68,7 +69,7 @@ export const useSigitMeta = (meta: Meta): FlatMeta => { setKind(kind) setTags(tags) // created_at in nostr events are stored in seconds - setCreatedAt(created_at * 1000) + setCreatedAt(fromUnixTimestamp(created_at)) setPubkey(pubkey) setId(id) setSig(sig) diff --git a/src/pages/create/index.tsx b/src/pages/create/index.tsx index 9218b15..77b7a87 100644 --- a/src/pages/create/index.tsx +++ b/src/pages/create/index.tsx @@ -50,7 +50,7 @@ import { getHash, hexToNpub, isOnline, - now, + unixNow, npubToHex, queryNip05, sendNotification, @@ -407,7 +407,6 @@ export const CreatePage = () => { encryptionKey: string ): Promise => { // Get the current timestamp in seconds - const unixNow = now() const blob = new Blob([encryptedArrayBuffer]) // Create a File object with the Blob data const file = new File([blob], `compressed.sigit`, { @@ -455,10 +454,9 @@ export const CreatePage = () => { const uploadFile = async ( arrayBuffer: ArrayBuffer ): Promise => { - const unixNow = now() const blob = new Blob([arrayBuffer]) // Create a File object with the Blob data - const file = new File([blob], `compressed-${unixNow}.sigit`, { + const file = new File([blob], `compressed-${unixNow()}.sigit`, { type: 'application/sigit' }) @@ -485,7 +483,7 @@ export const CreatePage = () => { return } - saveAs(finalZipFile, `request-${now()}.sigit.zip`) + saveAs(finalZipFile, `request-${unixNow()}.sigit.zip`) setIsLoading(false) } @@ -615,7 +613,7 @@ export const CreatePage = () => { const meta: Meta = { createSignature, keys, - modifiedAt: now(), + modifiedAt: unixNow(), docSignatures: {} } @@ -654,7 +652,7 @@ export const CreatePage = () => { const meta: Meta = { createSignature, - modifiedAt: now(), + modifiedAt: unixNow(), docSignatures: {} } diff --git a/src/pages/settings/profile/index.tsx b/src/pages/settings/profile/index.tsx index 7d6c923..9b2fc2d 100644 --- a/src/pages/settings/profile/index.tsx +++ b/src/pages/settings/profile/index.tsx @@ -26,7 +26,7 @@ import { setMetadataEvent } from '../../../store/actions' import { LoadingSpinner } from '../../../components/LoadingSpinner' import { LoginMethods } from '../../../store/auth/types' import { SmartToy } from '@mui/icons-material' -import { getRoboHashPicture } from '../../../utils' +import { getRoboHashPicture, unixNow } from '../../../utils' import { Container } from '../../../components/Container' export const ProfileSettingsPage = () => { @@ -197,7 +197,7 @@ export const ProfileSettingsPage = () => { // Relay will reject if created_at is too late const updatedMetadataState: UnsignedEvent = { content: content, - created_at: Math.round(Date.now() / 1000), + created_at: unixNow(), kind: kinds.Metadata, pubkey: pubkey!, tags: metadataState?.tags || [] diff --git a/src/pages/sign/index.tsx b/src/pages/sign/index.tsx index 3798863..712ab51 100644 --- a/src/pages/sign/index.tsx +++ b/src/pages/sign/index.tsx @@ -26,7 +26,7 @@ import { hexToNpub, isOnline, loadZip, - now, + unixNow, npubToHex, parseJson, readContentOfZipEntry, @@ -554,7 +554,7 @@ export const SignPage = () => { ...metaCopy.docSignatures, [hexToNpub(signedEvent.pubkey)]: JSON.stringify(signedEvent, null, 2) } - metaCopy.modifiedAt = now() + metaCopy.modifiedAt = unixNow() return metaCopy } @@ -564,7 +564,6 @@ export const SignPage = () => { encryptionKey: string ): Promise => { // Get the current timestamp in seconds - const unixNow = now() const blob = new Blob([encryptedArrayBuffer]) // Create a File object with the Blob data const file = new File([blob], `compressed.sigit`, { @@ -614,7 +613,7 @@ export const SignPage = () => { if (!arraybuffer) return null - return new File([new Blob([arraybuffer])], `${unixNow}.sigit.zip`, { + return new File([new Blob([arraybuffer])], `${unixNow()}.sigit.zip`, { type: 'application/zip' }) } @@ -758,8 +757,7 @@ export const SignPage = () => { if (!arrayBuffer) return const blob = new Blob([arrayBuffer]) - const unixNow = now() - saveAs(blob, `exported-${unixNow}.sigit.zip`) + saveAs(blob, `exported-${unixNow()}.sigit.zip`) setIsLoading(false) @@ -804,8 +802,7 @@ export const SignPage = () => { const finalZipFile = await createFinalZipFile(encryptedArrayBuffer, key) if (!finalZipFile) return - const unixNow = now() - saveAs(finalZipFile, `exported-${unixNow}.sigit.zip`) + saveAs(finalZipFile, `exported-${unixNow()}.sigit.zip`) } /** diff --git a/src/pages/verify/index.tsx b/src/pages/verify/index.tsx index c5a0e8e..1f6bee1 100644 --- a/src/pages/verify/index.tsx +++ b/src/pages/verify/index.tsx @@ -28,7 +28,7 @@ import { extractZipUrlAndEncryptionKey, getHash, hexToNpub, - now, + unixNow, npubToHex, parseJson, readContentOfZipEntry, @@ -239,7 +239,7 @@ export const VerifyPage = () => { } }) } - }, [submittedBy, signers, viewers]) + }, [submittedBy, signers, viewers, metadata]) const handleVerify = async () => { if (!selectedFile) return @@ -445,7 +445,7 @@ export const VerifyPage = () => { if (!arrayBuffer) return const blob = new Blob([arrayBuffer]) - saveAs(blob, `exported-${now()}.sigit.zip`) + saveAs(blob, `exported-${unixNow()}.sigit.zip`) setIsLoading(false) } diff --git a/src/utils/meta.ts b/src/utils/meta.ts index e9e3f13..b3c0c28 100644 --- a/src/utils/meta.ts +++ b/src/utils/meta.ts @@ -1,5 +1,5 @@ import { CreateSignatureEventContent, Meta } from '../types' -import { parseJson } from '.' +import { fromUnixTimestamp, parseJson } from '.' import { Event } from 'nostr-tools' import { toast } from 'react-toastify' @@ -140,7 +140,7 @@ export const extractSigitCardDisplayInfo = async (meta: Meta) => { ) // created_at in nostr events are stored in seconds - sigitInfo.createdAt = createSignatureEvent.created_at * 1000 + sigitInfo.createdAt = fromUnixTimestamp(createSignatureEvent.created_at) const createSignatureContent = await parseCreateSignatureEventContent( createSignatureEvent.content diff --git a/src/utils/misc.ts b/src/utils/misc.ts index 728408c..f427b78 100644 --- a/src/utils/misc.ts +++ b/src/utils/misc.ts @@ -13,7 +13,7 @@ import { NostrController } from '../controllers' import { AuthState } from '../store/auth/types' import store from '../store/store' import { CreateSignatureEventContent, Meta } from '../types' -import { hexToNpub, now } from './nostr' +import { hexToNpub, unixNow } from './nostr' import { parseJson } from './string' import { hexToBytes } from '@noble/hashes/utils' @@ -28,10 +28,10 @@ export const uploadToFileStorage = async (file: File) => { const event: EventTemplate = { kind: 24242, content: 'Authorize Upload', - created_at: Math.floor(Date.now() / 1000), + created_at: unixNow(), tags: [ ['t', 'upload'], - ['expiration', String(now() + 60 * 5)], // Set expiration time to 5 minutes from now + ['expiration', String(unixNow() + 60 * 5)], // Set expiration time to 5 minutes from now ['name', file.name], ['size', String(file.size)] ] @@ -78,7 +78,7 @@ export const signEventForMetaFile = async ( const event: EventTemplate = { kind: 27235, // Event type for meta file content: content, // content for event - created_at: Math.floor(Date.now() / 1000), // Current timestamp + created_at: unixNow(), // Current timestamp tags: [['-']] // For understanding why "-" tag is used here see: https://github.com/nostr-protocol/nips/blob/protected-events-tag/70.md } diff --git a/src/utils/nostr.ts b/src/utils/nostr.ts index bec854f..e9ecc8f 100644 --- a/src/utils/nostr.ts +++ b/src/utils/nostr.ts @@ -211,7 +211,22 @@ export const getRoboHashPicture = ( return `https://robohash.org/${npub}.png?set=set${set}` } -export const now = () => Math.round(Date.now() / 1000) +export const unixNow = () => Math.round(Date.now() / 1000) +export const toUnixTimestamp = (date: number | Date) => { + let time + if (typeof date === 'number') { + time = Math.round(date / 1000) + } else if (date instanceof Date) { + time = Math.round(date.getTime() / 1000) + } else { + throw Error('Unsupported type when converting to unix timestamp') + } + + return time +} +export const fromUnixTimestamp = (unix: number) => { + return unix * 1000 +} /** * Generate nip44 conversation key @@ -288,7 +303,7 @@ export const createWrap = (unsignedEvent: UnsignedEvent, receiver: string) => { kind: 1059, // Event kind content, // Encrypted content pubkey, // Public key of the creator - created_at: now(), // Current timestamp + created_at: unixNow(), // Current timestamp tags: [ // Tags including receiver and nonce ['p', receiver], @@ -542,7 +557,7 @@ export const updateUsersAppData = async (meta: Meta) => { const updatedEvent: UnsignedEvent = { kind: kinds.Application, pubkey: usersPubkey!, - created_at: now(), + created_at: unixNow(), tags: [['d', hash]], content: encryptedContent } @@ -608,10 +623,10 @@ const deleteBlossomFile = async (url: string, privateKey: string) => { const event: EventTemplate = { kind: 24242, content: 'Authorize Upload', - created_at: now(), + created_at: unixNow(), tags: [ ['t', 'delete'], - ['expiration', String(now() + 60 * 5)], // Set expiration time to 5 minutes from now + ['expiration', String(unixNow() + 60 * 5)], // Set expiration time to 5 minutes from now ['x', hash] ] } @@ -667,10 +682,10 @@ const uploadUserAppDataToBlossom = async ( const event: EventTemplate = { kind: 24242, content: 'Authorize Upload', - created_at: now(), + created_at: unixNow(), tags: [ ['t', 'upload'], - ['expiration', String(now() + 60 * 5)], // Set expiration time to 5 minutes from now + ['expiration', String(unixNow() + 60 * 5)], // Set expiration time to 5 minutes from now ['name', file.name], ['size', String(file.size)] ] @@ -875,7 +890,7 @@ export const sendNotification = async (receiver: string, meta: Meta) => { pubkey: usersPubkey, content: JSON.stringify(meta), tags: [], - created_at: now() + created_at: unixNow() } // Wrap the unsigned event with the receiver's information From 115a3974e278c137aa2d490327bcc92e4ed0c492 Mon Sep 17 00:00:00 2001 From: enes Date: Wed, 14 Aug 2024 10:35:51 +0200 Subject: [PATCH 49/49] fix(deps): update axios Bump axios version to 1.7.4, audit reported the issues in axios >= 1.3.2 --- package-lock.json | 11 ++++++----- package.json | 2 +- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 23a0986..ef46577 100644 --- a/package-lock.json +++ b/package-lock.json @@ -21,7 +21,7 @@ "@noble/hashes": "^1.4.0", "@nostr-dev-kit/ndk": "2.5.0", "@reduxjs/toolkit": "2.2.1", - "axios": "1.6.7", + "axios": "^1.7.4", "crypto-hash": "3.0.0", "crypto-js": "^4.2.0", "dnd-core": "16.0.1", @@ -2691,11 +2691,12 @@ } }, "node_modules/axios": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz", - "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==", + "version": "1.7.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.7.4.tgz", + "integrity": "sha512-DukmaFRnY6AzAALSH4J2M3k6PkaC+MfaAGdEERRWcC9q3/TWQwLpHR8ZRLKTdQ3aBDL64EdluRDjJqKw+BPZEw==", + "license": "MIT", "dependencies": { - "follow-redirects": "^1.15.4", + "follow-redirects": "^1.15.6", "form-data": "^4.0.0", "proxy-from-env": "^1.1.0" } diff --git a/package.json b/package.json index 5e1619e..447fdda 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "@noble/hashes": "^1.4.0", "@nostr-dev-kit/ndk": "2.5.0", "@reduxjs/toolkit": "2.2.1", - "axios": "1.6.7", + "axios": "^1.7.4", "crypto-hash": "3.0.0", "crypto-js": "^4.2.0", "dnd-core": "16.0.1",