diff --git a/package.json b/package.json index 6d42886fcd..505f508d61 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "raw-loader": "^4.0.2", "react": "^17.0.1", "react-dom": "^17.0.1", + "react-modal": "^3.14.4", "url-loader": "^4.1.1" }, "devDependencies": { diff --git a/src/components/Modal.module.css b/src/components/Modal.module.css new file mode 100644 index 0000000000..b363666350 --- /dev/null +++ b/src/components/Modal.module.css @@ -0,0 +1,46 @@ +.mainContainer { + max-width: 420px; + margin: auto; + background: #f9fbfc; + padding: 2em; + border-radius: 6px; + box-shadow: 0px 5px 10px rgba(0, 0, 0, 0.25); + display: flex; + flex-direction: column; + justify-self: center; + align-self: center; +} + +html[data-theme="dark"] .mainContainer { + background-color: var(--ifm-background-surface-color); +} + +.buttonsContainer { + flex: auto; + text-align: right; + font-weight: 600; + margin-top: 1rem; +} + +.buttonsContainer > * { + margin-right: 1rem; +} + +.overlay { + position: fixed; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: rgba(255, 255, 255, 0.75); + z-index: 999; + display: flex; +} + +html[data-theme="dark"] .overlay { + background-color: rgba(0, 0, 0, 0.75); +} + +input[type="checkbox"] { + margin-right: 0.5rem; +} diff --git a/src/components/Modal.tsx b/src/components/Modal.tsx new file mode 100644 index 0000000000..70ac8b7867 --- /dev/null +++ b/src/components/Modal.tsx @@ -0,0 +1,129 @@ +import React, { useEffect } from "react" +import ModalCmp from "react-modal" +import { DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY } from "../theme/Root" +import styles from "./Modal.module.css" + +interface ModalProps { + externalLink: string + showModal: boolean + children?: React.ReactNode + shouldCloseOnEsc?: boolean + shouldAcceptOnEnter?: boolean + shouldCloseOnOverlayClick?: boolean + handleCancelRequest: () => void + handleAcceptRequest?: () => void +} + +export const idOfNoticeLink = "notice" + +if (typeof window !== "undefined") { + ModalCmp.setAppElement("body") +} + +export const SubscriptionNoticeModal: React.FC = ({ + externalLink, + showModal, + shouldCloseOnEsc = true, + shouldAcceptOnEnter = false, + shouldCloseOnOverlayClick = true, + handleCancelRequest, + handleAcceptRequest, +}) => { + const onRequestClose = () => { + // If the user checked to never see this notice but subsequently cancels we will disregard their selection. We will + // only stop showing this notice if they check the box and then proceed to GitHub + if(window.localStorage.getItem(DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY)) { + window.localStorage.removeItem(DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY) + } + + handleCancelRequest() + } + + const gitHubRepoName = externalLink.match( + /https:\/\/github.com\/gruntwork-io\/(.*?)\/.*/ + ) + + // function to check if there's any active button (focus on the button) to avoid conflicts with shouldAcceptOnEnter property + const checkIfAnyActiveButton = () => { + const activeElement = document.activeElement + if (activeElement && activeElement.tagName.toLowerCase() === "button") { + return true + } + return false + } + + useEffect(() => { + if (showModal) { + document.body.style.overflow = "hidden" + } + const timer = setTimeout(() => { + if (!showModal) { + document.body.style.overflow = "unset" + } + }, 300) + return () => clearTimeout(timer) + }, [showModal]) + + useEffect(() => { + const listener = (e: any) => { + const code = e.key + if ( + code === "Enter" && + handleAcceptRequest && + shouldAcceptOnEnter && + showModal && + !checkIfAnyActiveButton() + ) { + e.preventDefault() + handleAcceptRequest() + } + } + document.addEventListener("keydown", listener, false) + return () => { + document.removeEventListener("keydown", listener, false) + } + }) + + const setDontWarnMe = (event) => { + event.stopPropagation() + if(!window.localStorage.getItem(DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY)) { + window.localStorage.setItem(DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY, "true") + } else { + window.localStorage.removeItem(DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY) + } + } + + return ( + +

For Subscribers Only

+

+ This link leads to the private{" "} + {gitHubRepoName && gitHubRepoName.length >= 1 && ( + {gitHubRepoName[1]} + )}{" "} + repository visible only to subscribers; everyone else will see a 404. +

+
+ + +
+
+ onRequestClose()} href="#"> + Cancel + + + Continue to GitHub + +
+
+ ) +} diff --git a/src/css/custom.css b/src/css/custom.css index b2c7f9e923..7eef8022f0 100644 --- a/src/css/custom.css +++ b/src/css/custom.css @@ -230,6 +230,25 @@ html[data-theme="dark"] .menu__link--sublist { border-left: 2px solid var(--ifm-color-primary); } +/* BUTTONS */ +.link-button { + display: inline-block; + padding: 0.5rem 2rem; + background: #5849a6; + color: white; + font-weight: 600; + box-shadow: 0 0 0 rgba(0, 0, 0, 0); + border-radius: 100vh; + transition: 0.2s ease-in-out; +} + +.link-button:hover { + color: white; + text-decoration: none; + filter: brightness(1.1); + box-shadow: 0 10px 10px rgba(0, 0, 0, 0.25); +} + /* FOOTER */ /* --ifm-footer-background-color doesn't appear to work */ diff --git a/src/theme/Root.js b/src/theme/Root.js new file mode 100644 index 0000000000..3d4be98982 --- /dev/null +++ b/src/theme/Root.js @@ -0,0 +1,59 @@ +import React, { useState, useEffect } from "react" +import { + SubscriptionNoticeModal, + idOfNoticeLink, +} from "/src/components/Modal.tsx" + +const gruntworkRepos = "https://github.com/gruntwork-io" + +export const DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY = "dontWarnGitHubLinks" + +function Root({ children }) { + const [displaySubscriberNotice, setDisplaySubscriberNotice] = useState(false) + const [externalLink, setExternalLink] = useState("") + + useEffect(() => { + const listener = (event) => { + if (event.target.id === idOfNoticeLink) { + setDisplaySubscriberNotice(false) + return + } + const dontWarn = window.localStorage.getItem( + DONT_SHOW_PRIVATE_GITHUB_WARNING_KEY + ) + + if (dontWarn) { + setDisplaySubscriberNotice(false) + return + } + + if (event.target.href && event.target.href.includes(gruntworkRepos)) { + event.preventDefault() + console.log + setExternalLink(event.target.href) + setDisplaySubscriberNotice(true) + } + } + + window.addEventListener("click", listener) + + // use-effect cleanup + return () => window.removeEventListener("click", listener) + }, []) + + return ( + <> + { + setDisplaySubscriberNotice(false) + setExternalLink("") + }} + /> + {children} + + ) +} + +export default Root diff --git a/yarn.lock b/yarn.lock index ab3ffbbf18..c46e2b1cd1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5048,6 +5048,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 sha1-KueOhdmJQVhnCwPUe+wfA72Ru50= + express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -7927,7 +7932,7 @@ react-json-view@^1.21.3: react-lifecycles-compat "^3.0.4" react-textarea-autosize "^8.3.2" -react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: 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== @@ -7939,6 +7944,16 @@ react-loadable-ssr-addon-v5-slorber@^1.0.1: dependencies: "@babel/runtime" "^7.10.3" +react-modal@^3.14.4: + version "3.14.4" + resolved "https://registry.yarnpkg.com/react-modal/-/react-modal-3.14.4.tgz#2ca7e8e9a180955e5c9508c228b73167c1e6f6a3" + integrity sha512-8surmulejafYCH9wfUmFyj4UfbSJwjcgbS9gf3oOItu4Hwd6ivJyVBETI0yHRhpJKCLZMUtnhzk76wXTsNL6Qg== + dependencies: + exenv "^1.2.0" + prop-types "^15.7.2" + react-lifecycles-compat "^3.0.0" + warning "^4.0.3" + react-router-config@^5.1.1: version "5.1.1" resolved "https://registry.yarnpkg.com/react-router-config/-/react-router-config-5.1.1.tgz#0f4263d1a80c6b2dc7b9c1902c9526478194a988" @@ -9382,6 +9397,13 @@ wait-on@^6.0.0: minimist "^1.2.5" rxjs "^7.1.0" +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.2.0: version "2.2.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-2.2.0.tgz#47d78f5415fe550ecd740f99fe2882323a58b1ce"