diff --git a/package.json b/package.json index 727a7912..8a36b8de 100644 --- a/package.json +++ b/package.json @@ -13,6 +13,7 @@ "qr-code-styling": "^1.6.0-rc.1", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-modal": "^3.16.1", "react-redux": "^8.0.5", "react-router-dom": "^6.5.0", "react-scripts": "5.0.1", diff --git a/src/element/Note.js b/src/element/Note.js index 2d3b8219..64df3541 100644 --- a/src/element/Note.js +++ b/src/element/Note.js @@ -8,7 +8,8 @@ import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import Event from "../nostr/Event"; import ProfileImage from "./ProfileImage"; -import useEventPublisher from "../pages/feed/EventPublisher"; +import useEventPublisher from "../feed/EventPublisher"; +import { NoteCreator } from "./NoteCreator"; const UrlRegex = /((?:http|ftp|https):\/\/(?:[\w+?\.\w+])+(?:[a-zA-Z0-9\~\!\@\#\$\%\^\&\*\(\)_\-\=\+\\\/\?\.\:\;\'\,]*)?)/; const FileExtensionRegex = /\.([\w]+)$/; @@ -21,6 +22,7 @@ export default function Note(props) { const reactions = props.reactions; const publisher = useEventPublisher(); const [sig, setSig] = useState(false); + const [showReply, setShowReply] = useState(false); const users = useSelector(s => s.users?.users); const ev = dataEvent ?? Event.FromObject(data); @@ -141,7 +143,7 @@ export default function Note(props) { {transformBody()}
- {}}> + setShowReply(!showReply)}> like()}> @@ -152,6 +154,7 @@ export default function Note(props) {
+ {showReply ? setShowReply(false)}/> : null} ) } \ No newline at end of file diff --git a/src/element/NoteCreator.js b/src/element/NoteCreator.js new file mode 100644 index 00000000..832567a4 --- /dev/null +++ b/src/element/NoteCreator.js @@ -0,0 +1,32 @@ +import { useState } from "react"; +import useEventPublisher from "../feed/EventPublisher"; + +export function NoteCreator(props) { + const replyTo = props.replyTo; + const onSend = props.onSend; + const publisher = useEventPublisher(); + const [note, setNote] = useState(""); + + async function sendNote() { + let ev = replyTo ? + await publisher.reply(replyTo, note) + : await publisher.note(note); + + console.debug("Sending note: ", ev); + publisher.broadcast(ev); + setNote(""); + if(typeof onSend === "function") { + onSend(); + } + } + + return ( + <> + {replyTo ? {`Reply to: ${replyTo.Id.substring(0, 8)}`} : null} +
+ setNote(e.target.value)}> +
sendNote()}>Send
+
+ + ); +} \ No newline at end of file diff --git a/src/element/ProfileImage.js b/src/element/ProfileImage.js index beac6603..cc298edf 100644 --- a/src/element/ProfileImage.js +++ b/src/element/ProfileImage.js @@ -1,6 +1,6 @@ import "./ProfileImage.css"; import { useNavigate } from "react-router-dom"; -import useProfile from "../pages/feed/ProfileFeed"; +import useProfile from "../feed/ProfileFeed"; import Nostrich from "../nostrich.jpg"; export default function ProfileImage(props) { diff --git a/src/pages/feed/EventPublisher.js b/src/feed/EventPublisher.js similarity index 58% rename from src/pages/feed/EventPublisher.js rename to src/feed/EventPublisher.js index 583e9f9b..eb99602f 100644 --- a/src/pages/feed/EventPublisher.js +++ b/src/feed/EventPublisher.js @@ -1,9 +1,9 @@ import { useContext } from "react"; import { useSelector } from "react-redux"; -import { NostrContext } from "../.."; -import Event from "../../nostr/Event"; -import EventKind from "../../nostr/EventKind"; -import Tag from "../../nostr/Tag"; +import { NostrContext } from ".."; +import Event from "../nostr/Event"; +import EventKind from "../nostr/EventKind"; +import Tag from "../nostr/Tag"; export default function useEventPublisher() { const system = useContext(NostrContext); @@ -19,7 +19,7 @@ export default function useEventPublisher() { * @returns */ async function signEvent(ev, privKey) { - if(nip07 === true && hasNip07) { + if (nip07 === true && hasNip07) { ev.Id = await ev.CreateId(); let tmpEv = await window.nostr.signEvent(ev.ToObject()); console.log(tmpEv); @@ -42,7 +42,7 @@ export default function useEventPublisher() { return await signEvent(ev, privKey); }, note: async (msg) => { - if(typeof msg !== "string") { + if (typeof msg !== "string") { throw "Must be text!"; } let ev = Event.ForPubKey(pubKey); @@ -50,6 +50,38 @@ export default function useEventPublisher() { ev.Content = msg; return await signEvent(ev, privKey); }, + /** + * Reply to a note + * @param {Event} replyTo + * @param {String} msg + * @returns + */ + reply: async (replyTo, msg) => { + if (typeof msg !== "string") { + throw "Must be text!"; + } + let ev = Event.ForPubKey(pubKey); + ev.Kind = EventKind.TextNote; + ev.Content = msg; + + let thread = replyTo.GetThread(); + if (thread) { + if (thread.Root) { + ev.Tags.push(new Tag(["e", thread.Root.Event, "", "root"], ev.Tags.length)); + } + if (thread.Reply) { + ev.Tags.push(new Tag(["e", thread.Reply.Id, "", "reply"], ev.Tags.length)); + } + ev.Tags.push(new Tag(["p", replyTo.PubKey], ev.Tags.length)); + for (let pk in thread.PubKeys) { + ev.Tags.push(new Tag(["p", pk], ev.Tags.length)); + } + } else { + ev.Tags.push(new Tag(["e", replyTo.Id, "", "reply"], 0)); + ev.Tags.push(new Tag(["p", replyTo.PubKey], 1)); + } + return await signEvent(ev, privKey); + }, like: async (evRef) => { let ev = Event.ForPubKey(pubKey); ev.Kind = EventKind.Reaction; diff --git a/src/pages/feed/LoginFeed.js b/src/feed/LoginFeed.js similarity index 81% rename from src/pages/feed/LoginFeed.js rename to src/feed/LoginFeed.js index 5ac3f604..62f96d31 100644 --- a/src/pages/feed/LoginFeed.js +++ b/src/feed/LoginFeed.js @@ -1,10 +1,10 @@ import { useContext, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from "../.."; -import Event from "../../nostr/Event"; -import EventKind from "../../nostr/EventKind"; -import { Subscriptions } from "../../nostr/Subscriptions"; -import { setFollows, setRelays } from "../../state/Login"; +import { NostrContext } from ".."; +import Event from "../nostr/Event"; +import EventKind from "../nostr/EventKind"; +import { Subscriptions } from "../nostr/Subscriptions"; +import { setFollows, setRelays } from "../state/Login"; /** * Managed loading data for the current logged in user diff --git a/src/pages/feed/ProfileFeed.js b/src/feed/ProfileFeed.js similarity index 85% rename from src/pages/feed/ProfileFeed.js rename to src/feed/ProfileFeed.js index f00820a5..60bf77cd 100644 --- a/src/pages/feed/ProfileFeed.js +++ b/src/feed/ProfileFeed.js @@ -1,7 +1,7 @@ import { useContext, useEffect } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from "../.."; -import { addPubKey } from "../../state/Users"; +import { NostrContext } from ".."; +import { addPubKey } from "../state/Users"; export default function useProfile(pubKey) { const dispatch = useDispatch(); diff --git a/src/pages/feed/ThreadFeed.js b/src/feed/ThreadFeed.js similarity index 90% rename from src/pages/feed/ThreadFeed.js rename to src/feed/ThreadFeed.js index a06a6369..afbd3bb3 100644 --- a/src/pages/feed/ThreadFeed.js +++ b/src/feed/ThreadFeed.js @@ -1,10 +1,10 @@ import { useContext, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from "../.."; -import Event from "../../nostr/Event"; -import { Subscriptions } from "../../nostr/Subscriptions"; -import { addNote, reset } from "../../state/Thread"; -import { addPubKey } from "../../state/Users"; +import { NostrContext } from ".."; +import Event from "../nostr/Event"; +import { Subscriptions } from "../nostr/Subscriptions"; +import { addNote, reset } from "../state/Thread"; +import { addPubKey } from "../state/Users"; export default function useThreadFeed(id) { const dispatch = useDispatch(); diff --git a/src/pages/feed/TimelineFeed.js b/src/feed/TimelineFeed.js similarity index 86% rename from src/pages/feed/TimelineFeed.js rename to src/feed/TimelineFeed.js index 21556890..a8945242 100644 --- a/src/pages/feed/TimelineFeed.js +++ b/src/feed/TimelineFeed.js @@ -1,7 +1,7 @@ import { useContext, useEffect, useState } from "react"; -import { NostrContext } from "../../index"; -import EventKind from "../../nostr/EventKind"; -import { Subscriptions } from "../../nostr/Subscriptions"; +import { NostrContext } from ".."; +import EventKind from "../nostr/EventKind"; +import { Subscriptions } from "../nostr/Subscriptions"; export default function useTimelineFeed(pubKeys) { const system = useContext(NostrContext); diff --git a/src/pages/feed/UsersFeed.js b/src/feed/UsersFeed.js similarity index 90% rename from src/pages/feed/UsersFeed.js rename to src/feed/UsersFeed.js index f2d0a37b..1e40e500 100644 --- a/src/pages/feed/UsersFeed.js +++ b/src/feed/UsersFeed.js @@ -1,10 +1,10 @@ import { useContext, useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { NostrContext } from "../../index"; -import Event from "../../nostr/Event"; -import EventKind from "../../nostr/EventKind"; -import { Subscriptions } from "../../nostr/Subscriptions"; -import { setUserData } from "../../state/Users"; +import { NostrContext } from ".."; +import Event from "../nostr/Event"; +import EventKind from "../nostr/EventKind"; +import { Subscriptions } from "../nostr/Subscriptions"; +import { setUserData } from "../state/Users"; export default function useUsersCache() { const dispatch = useDispatch(); diff --git a/src/index.css b/src/index.css index 06a10021..a699eaf4 100644 --- a/src/index.css +++ b/src/index.css @@ -71,6 +71,10 @@ input[type="text"], input[type="password"] { display: flex; } +.f-center { + align-items: center; +} + .f-grow { flex-grow: 1; } @@ -112,4 +116,29 @@ div.form-group > div { div.form-group > div:first-child { flex-grow: 1; +} + +.modal { + position: absolute; + width: 100vw; + height: 100vh; + top: 0; + left: 0; + background-color: rgba(0,0,0,0.8); +} + +.modal .modal-content { + display: flex; + justify-content: center; +} + +.modal .modal-content > div { + padding: 10px; + border-radius: 10px; + background-color: #333; + margin-top: 5vh; +} + +.ReactModal__Body--open { + overflow: hidden; } \ No newline at end of file diff --git a/src/index.js b/src/index.js index c394b156..2931746c 100644 --- a/src/index.js +++ b/src/index.js @@ -29,7 +29,7 @@ root.render( - } /> + } /> } /> } /> } /> diff --git a/src/nostr/Connection.js b/src/nostr/Connection.js index 9f460733..8288fe23 100644 --- a/src/nostr/Connection.js +++ b/src/nostr/Connection.js @@ -128,7 +128,6 @@ export default class Connection { return; } let json = JSON.stringify(obj); - console.debug(`[${this.Address}] >> ${json}`); this.Socket.send(json); } diff --git a/src/nostr/Thread.js b/src/nostr/Thread.js index 339f4893..0ebb438f 100644 --- a/src/nostr/Thread.js +++ b/src/nostr/Thread.js @@ -42,7 +42,6 @@ export default class Thread { ret.Mentions = eTags.filter(a => a.Marker === "mention"); } ret.PubKeys = ev.Tags.filter(a => a.Key === "p").map(a => a.PubKey); - return ret; } } \ No newline at end of file diff --git a/src/pages/EventPage.js b/src/pages/EventPage.js index ab27f395..bfccaa06 100644 --- a/src/pages/EventPage.js +++ b/src/pages/EventPage.js @@ -1,6 +1,6 @@ import { useParams } from "react-router-dom"; import Thread from "../element/Thread"; -import useThreadFeed from "./feed/ThreadFeed"; +import useThreadFeed from "../feed/ThreadFeed"; export default function EventPage() { const params = useParams(); diff --git a/src/pages/Layout.js b/src/pages/Layout.js index 1a8be80a..8e791e26 100644 --- a/src/pages/Layout.js +++ b/src/pages/Layout.js @@ -7,8 +7,8 @@ import { faBell } from "@fortawesome/free-solid-svg-icons"; import { NostrContext } from ".." import ProfileImage from "../element/ProfileImage"; import { init } from "../state/Login"; -import useLoginFeed from "./feed/LoginFeed"; -import useUsersCache from "./feed/UsersFeed"; +import useLoginFeed from "../feed/LoginFeed"; +import useUsersCache from "../feed/UsersFeed"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; export default function Layout(props) { diff --git a/src/pages/Login.js b/src/pages/Login.js index 7175b183..420730eb 100644 --- a/src/pages/Login.js +++ b/src/pages/Login.js @@ -1,9 +1,10 @@ import { useEffect, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { setPrivateKey, setNip07PubKey } from "../state/Login"; +import { useNavigate } from "react-router-dom"; import * as secp from '@noble/secp256k1'; import { bech32 } from "bech32"; -import { useNavigate } from "react-router-dom"; + +import { setPrivateKey, setNip07PubKey } from "../state/Login"; export default function LoginPage() { const dispatch = useDispatch(); diff --git a/src/pages/ProfilePage.css b/src/pages/ProfilePage.css index a806d757..dbd1b297 100644 --- a/src/pages/ProfilePage.css +++ b/src/pages/ProfilePage.css @@ -8,10 +8,11 @@ } .profile .avatar { - width: 128px; - height: 128px; + width: 256px; + height: 256px; background-size: contain; cursor: pointer; + border-radius: 10px; } .profile .avatar .edit { diff --git a/src/pages/ProfilePage.js b/src/pages/ProfilePage.js index f58e3fda..707ebe6b 100644 --- a/src/pages/ProfilePage.js +++ b/src/pages/ProfilePage.js @@ -1,16 +1,19 @@ import "./ProfilePage.css"; +import { useEffect, useRef, useState } from "react"; import { useDispatch, useSelector } from "react-redux"; -import { useParams } from "react-router-dom"; -import useProfile from "./feed/ProfileFeed"; -import { useEffect, useState } from "react"; -import { resetProfile } from "../state/Users"; -import Nostrich from "../nostrich.jpg"; -import useEventPublisher from "./feed/EventPublisher"; -import useTimelineFeed from "./feed/TimelineFeed"; -import Note from "../element/Note"; import { bech32 } from "bech32"; import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; import { faQrcode } from "@fortawesome/free-solid-svg-icons"; +import { useParams } from "react-router-dom"; + +import useProfile from "../feed/ProfileFeed"; +import { resetProfile } from "../state/Users"; +import Nostrich from "../nostrich.jpg"; +import useEventPublisher from "../feed/EventPublisher"; +import useTimelineFeed from "../feed/TimelineFeed"; +import Note from "../element/Note"; +import QRCodeStyling from "qr-code-styling"; +import ReactModal from "react-modal"; export default function ProfilePage() { const dispatch = useDispatch(); @@ -21,13 +24,15 @@ export default function ProfilePage() { const { notes } = useTimelineFeed([id]); const loginPubKey = useSelector(s => s.login.publicKey); const isMe = loginPubKey === id; + const qrRef = useRef(); - let [name, setName] = useState(""); - let [picture, setPicture] = useState(""); - let [about, setAbout] = useState(""); - let [website, setWebsite] = useState(""); - let [nip05, setNip05] = useState(""); - let [lud16, setLud16] = useState(""); + const [name, setName] = useState(""); + const [picture, setPicture] = useState(""); + const [about, setAbout] = useState(""); + const [website, setWebsite] = useState(""); + const [nip05, setNip05] = useState(""); + const [lud16, setLud16] = useState(""); + const [showLnQr, setShowLnQr] = useState(false); useEffect(() => { if (user) { @@ -57,6 +62,17 @@ export default function ProfilePage() { } }, [lud16]); + useEffect(() => { + if (qrRef.current && showLnQr) { + let qr = new QRCodeStyling({ + data: "", + type: "canvas" + }); + qrRef.current.innerHTML = ""; + qr.append(qrRef.current); + } + }, [showLnQr]); + async function saveProfile() { let ev = await publisher.metadata({ name, @@ -117,42 +133,19 @@ export default function ProfilePage() { function details() { return ( <> -
-
Name:
-
- {name} +

{name}

+

{about}

+ {website ? {website} : null} + {lud16 ?
+
setShowLnQr(true)}> +
-
-
-
About:
-
- {about} -
-
- {website ? -
-
Website:
-
- {website} -
-
: null} - {nip05 ? -
-
NIP-05:
-
- {nip05} -
-
: null} - {lud16 ? -
-
LN Address:
-
- {lud16}  -
{ }}> - -
-
-
: null} +
  ⚡️ {lud16}
+
: null} + {showLnQr === true ? + setShowLnQr(false)} overlayClassName="modal" className="modal-content" preventScroll={true}> +
QR
+
: null} ) } @@ -160,19 +153,22 @@ export default function ProfilePage() { return ( <>
-
- {isMe ? -
-
Edit
-
- : null - } +
+
+ {isMe ? +
+
Edit
+
+ : null + } +
{isMe ? editor() : details()}
-

Notes

+
+
Notes
{notes?.sort((a, b) => b.created_at - a.created_at).map(a => )} ) diff --git a/src/pages/Root.js b/src/pages/Root.js index 6859be34..4840f1cb 100644 --- a/src/pages/Root.js +++ b/src/pages/Root.js @@ -1,36 +1,31 @@ import "./Root.css"; import { useSelector } from "react-redux"; -import { useState } from "react"; -import Timeline from "./Timeline"; -import useEventPublisher from "./feed/EventPublisher"; +import Note from "../element/Note"; +import useTimelineFeed from "../feed/TimelineFeed"; +import { NoteCreator } from "../element/NoteCreator"; export default function RootPage() { - const publisher = useEventPublisher(); const pubKey = useSelector(s => s.login.publicKey); + const follows = useSelector(a => a.login.follows) + const { notes } = useTimelineFeed(follows); - const [note, setNote] = useState(""); - - async function sendNote() { - let ev = await publisher.note(note); - - console.debug("Sending note: ", ev); - publisher.broadcast(ev); - setNote(""); - } - - function noteSigner() { - return ( -
- setNote(e.target.value)}> -
sendNote()}>Send
-
- ); + function followHints() { + if (follows?.length === 0 && pubKey) { + return ( + <> +

Hmm you're not following anybody?

+ + ); + } } return ( <> - {pubKey ? noteSigner() : null} - + {pubKey ? : null} + {followHints()} +
+ {notes?.sort((a, b) => b.created_at - a.created_at).map(e => )} +
); } \ No newline at end of file diff --git a/src/pages/Timeline.js b/src/pages/Timeline.js deleted file mode 100644 index 645923cb..00000000 --- a/src/pages/Timeline.js +++ /dev/null @@ -1,14 +0,0 @@ -import { useSelector } from "react-redux"; -import Note from "../element/Note"; -import useTimelineFeed from "./feed/TimelineFeed"; - -export default function Timeline() { - const follows = useSelector(a => a.login.follows) - const { notes } = useTimelineFeed(follows); - - return ( -
- {notes?.sort((a, b) => b.created_at - a.created_at).map(e => )} -
- ); -} \ No newline at end of file diff --git a/src/state/Login.js b/src/state/Login.js index 3588856c..125927d1 100644 --- a/src/state/Login.js +++ b/src/state/Login.js @@ -2,6 +2,7 @@ import { createSlice } from '@reduxjs/toolkit' import * as secp from '@noble/secp256k1'; const PrivateKeyItem = "secret"; +const Nip07PublicKeyItem = "nip07:pubkey"; const LoginSlice = createSlice({ name: "Login", @@ -35,6 +36,7 @@ const LoginSlice = createSlice({ init: (state) => { state.privateKey = window.localStorage.getItem(PrivateKeyItem); if (state.privateKey) { + window.localStorage.removeItem(Nip07PublicKeyItem); // reset nip07 if using private key state.publicKey = secp.utils.bytesToHex(secp.schnorr.getPublicKey(state.privateKey, true)); } state.relays = { @@ -43,6 +45,13 @@ const LoginSlice = createSlice({ "wss://relay.damus.io": { read: true, write: true }, "wss://nostr-pub.wellorder.net": { read: true, write: true } }; + + // check nip07 pub key + let nip07PubKey = window.localStorage.getItem(Nip07PublicKeyItem); + if(nip07PubKey && !state.privateKey) { + state.publicKey = nip07PubKey; + state.nip07 = true; + } }, setPrivateKey: (state, action) => { state.privateKey = action.payload; @@ -53,6 +62,7 @@ const LoginSlice = createSlice({ state.publicKey = action.payload; }, setNip07PubKey: (state, action) => { + window.localStorage.setItem(Nip07PublicKeyItem, action.payload); state.publicKey = action.payload; state.nip07 = true; }, diff --git a/src/state/Users.js b/src/state/Users.js index b7ca0c5f..93b7d074 100644 --- a/src/state/Users.js +++ b/src/state/Users.js @@ -40,7 +40,7 @@ const UsersSlice = createSlice({ if (!Array.isArray(ud)) { ud = [ud]; } - console.debug("Set user profiles: ", ud); + for (let x of ud) { let existing = state.users[x.pubkey]; if (existing) { diff --git a/yarn.lock b/yarn.lock index 0ca22cec..50c5a571 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4124,6 +4124,11 @@ execa@^5.0.0: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +exenv@^1.2.0: + version "1.2.2" + resolved "https://registry.yarnpkg.com/exenv/-/exenv-1.2.2.tgz#2ae78e85d9894158670b03d47bec1f03bd91bb9d" + integrity sha512-Z+ktTxTwv9ILfgKCk32OX3n/doe+OcLTRtqK9pcL+JsP3J1/VW8Uvl4ZjLlKqeW4rzK4oesDOGMEMRIZqtP4Iw== + exit@^0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c" @@ -5838,7 +5843,7 @@ lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -loose-envify@^1.1.0, loose-envify@^1.4.0: +loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/loose-envify/-/loose-envify-1.4.0.tgz#71ee51fa7be4caec1a63839f7e682d8132d30caf" integrity sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q== @@ -7030,7 +7035,7 @@ prompts@^2.0.1, prompts@^2.4.2: kleur "^3.0.3" sisteransi "^1.0.5" -prop-types@^15.8.1: +prop-types@^15.7.2, prop-types@^15.8.1: version "15.8.1" resolved "https://registry.yarnpkg.com/prop-types/-/prop-types-15.8.1.tgz#67d87bf1a694f48435cf332c24af10214a3140b5" integrity sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg== @@ -7195,6 +7200,21 @@ react-is@^18.0.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== +react-lifecycles-compat@^3.0.0: + version "3.0.4" + resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" + integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== + +react-modal@^3.16.1: + version "3.16.1" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.16.1.tgz#34018528fc206561b1a5467fc3beeaddafb39b2b" + integrity sha512-VStHgI3BVcGo7OXczvnJN7yT2TWHJPDXZWyI/a0ssFNhGZWsPmB8cF0z33ewDXq4VfYMO1vXgiv/g8Nj9NDyWg== + dependencies: + exenv "^1.2.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + react-redux@^8.0.5: version "8.0.5" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-8.0.5.tgz#e5fb8331993a019b8aaf2e167a93d10af469c7bd" @@ -8491,6 +8511,13 @@ walker@^1.0.7: dependencies: makeerror "1.0.12" +warning@^4.0.3: + version "4.0.3" + resolved "https://registry.yarnpkg.com/warning/-/warning-4.0.3.tgz#16e9e077eb8a86d6af7d64aa1e05fd85b4678ca3" + integrity sha512-rpJyN222KWIvHJ/F53XSZv0Zl/accqHR8et1kpaMTD/fLCRxtV8iX8czMzY7sVZupTI3zcUTg8eycS2kNF9l6w== + dependencies: + loose-envify "^1.0.0" + watchpack@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.4.0.tgz#fa33032374962c78113f93c7f2fb4c54c9862a5d"