diff --git a/components/agility-common/LoadingWidget.js b/components/agility-common/LoadingWidget.js new file mode 100644 index 000000000..a963b5b9f --- /dev/null +++ b/components/agility-common/LoadingWidget.js @@ -0,0 +1,15 @@ +import { CgSpinner } from "react-icons/cg"; + +const Widget = ({ message }) => { + return ( +
+ +

{message}

+
+ ); +}; + +export default Widget; diff --git a/components/agility-common/PreviewBar.js b/components/agility-common/PreviewBar.js new file mode 100644 index 000000000..9c504059c --- /dev/null +++ b/components/agility-common/PreviewBar.js @@ -0,0 +1,155 @@ +import React, { useState } from "react"; +import { + FaInfoCircle, + FaGithub, + FaChevronDown, + FaChevronUp, +} from "react-icons/fa"; + +/** + * This is a preview bar that is enabled by default to handle viewing content in preview & live mode, remove this for production use. + **/ + +const PreviewBar = ({ isPreview, isDevelopmentMode }) => { + const [open, setOpen] = useState(false); + + // handle view function to determine preview / live mode + const handleView = () => { + if (isDevelopmentMode) { + alert("You are currently in Development Mode, Live Mode is unavailable."); + } else { + if (!isDevelopmentMode && !isPreview) { + const xhr = new XMLHttpRequest(); + + xhr.onload = function () { + // Process our return data + if (xhr.status >= 200 && xhr.status < 300) { + // What do when the request is successful + const previewKey = xhr.responseText; + + window.location.replace( + `${window.location.pathname}?agilitypreviewkey=${escape( + previewKey + )}` + ); + } + }; + // Create and send a GET request + xhr.open("GET", "/api/generatePreviewKey"); + xhr.send(); + } else { + const exit = confirm("Would you like to exit Preview Mode?"); + if (exit === true) { + window.location.href = `/api/exitPreview?slug=${window.location.pathname}`; + } else return; + } + } + }; + + return ( +
+
+
+ + + Agility CMS + Agility CMS + + +
+ +
+ +

Help Center

+
+
+
+
+ +
+ +

View on GitHub

+
+
+
+
+
+ {isPreview ? ( +

+ Previewing Latest Changes +

+ ) : ( +

+ Viewing Published Content +

+ )} +
setOpen(!open)} + > + {open ? ( + + ) : ( + + )} +
+
+ {isPreview ? ( +

+ Previewing Latest Changes +

+ ) : ( +

+ Viewing Published Content +

+ )} + +
+
+
+
+ ); +}; + +export default PreviewBar; diff --git a/components/common/Layout/Layout.tsx b/components/common/Layout/Layout.tsx index c7c35d767..065768302 100644 --- a/components/common/Layout/Layout.tsx +++ b/components/common/Layout/Layout.tsx @@ -13,122 +13,138 @@ import { useAcceptCookies } from '@lib/hooks/useAcceptCookies' import { Sidebar, Button, Modal, LoadingDots } from '@components/ui' import PaymentMethodView from '@components/checkout/PaymentMethodView' import CheckoutSidebarView from '@components/checkout/CheckoutSidebarView' +import { handlePreview } from "@agility/nextjs"; import LoginView from '@components/auth/LoginView' import s from './Layout.module.css' +import LoadingWidget from '@components/agility-common/LoadingWidget' +import PreviewBar from '@components/agility-common/PreviewBar' const Loading = () => ( -
- -
+
+ +
) const dynamicProps = { - loading: () => , + loading: () => , } const SignUpView = dynamic( - () => import('@components/auth/SignUpView'), - dynamicProps + () => import('@components/auth/SignUpView'), + dynamicProps ) const ForgotPassword = dynamic( - () => import('@components/auth/ForgotPassword'), - dynamicProps + () => import('@components/auth/ForgotPassword'), + dynamicProps ) const FeatureBar = dynamic( - () => import('@components/common/FeatureBar'), - dynamicProps + () => import('@components/common/FeatureBar'), + dynamicProps ) interface Props { - pageProps: { - pages?: Page[] - categories: Category[], - agilityProps: any - } + pageProps: { + pages?: Page[] + categories: Category[], + agilityProps: any + } } const ModalView: FC<{ modalView: string; closeModal(): any }> = ({ - modalView, - closeModal, + modalView, + closeModal, }) => { - return ( - - {modalView === 'LOGIN_VIEW' && } - {modalView === 'SIGNUP_VIEW' && } - {modalView === 'FORGOT_VIEW' && } - - ) + return ( + + {modalView === 'LOGIN_VIEW' && } + {modalView === 'SIGNUP_VIEW' && } + {modalView === 'FORGOT_VIEW' && } + + ) } const ModalUI: FC = () => { - const { displayModal, closeModal, modalView } = useUI() - return displayModal ? ( - - ) : null + const { displayModal, closeModal, modalView } = useUI() + return displayModal ? ( + + ) : null } const SidebarView: FC<{ sidebarView: string; closeSidebar(): any }> = ({ - sidebarView, - closeSidebar, + sidebarView, + closeSidebar, }) => { - return ( - - {sidebarView === 'CART_VIEW' && } - {sidebarView === 'CHECKOUT_VIEW' && } - {sidebarView === 'PAYMENT_VIEW' && } - {sidebarView === 'SHIPPING_VIEW' && } - - ) + return ( + + {sidebarView === 'CART_VIEW' && } + {sidebarView === 'CHECKOUT_VIEW' && } + {sidebarView === 'PAYMENT_VIEW' && } + {sidebarView === 'SHIPPING_VIEW' && } + + ) } const SidebarUI: FC = () => { - const { displaySidebar, closeSidebar, sidebarView } = useUI() - return displaySidebar ? ( - - ) : null + const { displaySidebar, closeSidebar, sidebarView } = useUI() + return displaySidebar ? ( + + ) : null } const Layout: FC = (props) => { + // set up handle preview + const isPreviewLoading = handlePreview(); + const { children, pageProps: { agilityProps, ...pageProps }, - } = props + } = props - const { acceptedCookies, onAcceptCookies } = useAcceptCookies() - const { locale = 'en-US' } = useRouter() + const { acceptedCookies, onAcceptCookies } = useAcceptCookies() + const { locale = 'en-US' } = useRouter() - const categories = agilityProps?.globalData?.sitedata?.categoryLinks || [] + const categories = agilityProps?.globalData?.sitedata?.categoryLinks || [] - const navBarlinks = categories.slice(0, 2).map((c:any) => ({ - label: c.name, - href: `/search/${c.slug}`, - })) + const navBarlinks = categories.slice(0, 2).map((c: any) => ({ + label: c.name, + href: `/search/${c.slug}`, + })) - return ( - -
- -
{children}
-
- - - onAcceptCookies()}> - Accept cookies - - } - /> -
-
- ) + return ( + + +
+ + {isPreviewLoading && + + } + {!isPreviewLoading && + <> + + +
{children}
+
+ + + onAcceptCookies()}> + Accept cookies + + } + /> + + } +
+
+ ) } export default Layout diff --git a/package.json b/package.json index 3c51b10c8..04d24cdc9 100644 --- a/package.json +++ b/package.json @@ -53,6 +53,7 @@ "react": "^17.0.2", "react-dom": "^17.0.2", "react-fast-marquee": "^1.1.3", + "react-icons": "^4.2.0", "react-merge-refs": "^1.1.0", "react-use-measure": "^2.0.4", "shopify-buy": "^2.11.0", diff --git a/pages/api/exitPreview.js b/pages/api/exitPreview.js new file mode 100644 index 000000000..4e3b168d3 --- /dev/null +++ b/pages/api/exitPreview.js @@ -0,0 +1,9 @@ +export default async (req, res) => { + // Clears the preview mode cookies. + // This function accepts no arguments. + res.clearPreviewData() + + // Redirect to the slug + res.writeHead(307, { Location: req.query.slug }) + res.end() +} \ No newline at end of file diff --git a/pages/api/generatePreviewKey.js b/pages/api/generatePreviewKey.js new file mode 100644 index 000000000..0759814e0 --- /dev/null +++ b/pages/api/generatePreviewKey.js @@ -0,0 +1,12 @@ +import { generatePreviewKey } from "@agility/nextjs/node" + +export default async (req, res) => { + + //TODO: Only generate the preview link if you are already in Preview! + //how can I check if i'm in preview mode already? + + const previewKey = generatePreviewKey(); + + //Return a valid preview key + res.end(previewKey) +} \ No newline at end of file diff --git a/pages/api/preview.js b/pages/api/preview.js new file mode 100644 index 000000000..4cb0c386d --- /dev/null +++ b/pages/api/preview.js @@ -0,0 +1,35 @@ +import { validatePreview, getDynamicPageURL } from '@agility/nextjs/node' + +// A simple example for testing it manually from your browser. +// If this is located at pages/api/preview.js, then +// open /api/preview from your browser. +export default async (req, res) => { + + //validate our preview key, also validate the requested page to preview exists + const validationResp = await validatePreview({ + agilityPreviewKey: req.query.agilitypreviewkey, + slug: req.query.slug + }); + + if (validationResp.error) { + return res.status(401).end(`${validationResp.message}`) + } + + let previewUrl = req.query.slug; + + //TODO: these kinds of dynamic links should work by default (even outside of preview) + if(req.query.ContentID) { + const dynamicPath = await getDynamicPageURL({contentID: req.query.ContentID, preview: true, slug: req.query.slug}); + if(dynamicPath) { + previewUrl = dynamicPath; + } + } + + //enable preview mode + res.setPreviewData({}) + + // Redirect to the slug + res.writeHead(307, { Location: previewUrl }) + res.end() + +} \ No newline at end of file diff --git a/public/assets/agility-logo.svg b/public/assets/agility-logo.svg new file mode 100644 index 000000000..cb577be95 --- /dev/null +++ b/public/assets/agility-logo.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/public/assets/agility-preview-logo.svg b/public/assets/agility-preview-logo.svg new file mode 100644 index 000000000..53271d929 --- /dev/null +++ b/public/assets/agility-preview-logo.svg @@ -0,0 +1 @@ +Asset 1 \ No newline at end of file diff --git a/tailwind.config.js b/tailwind.config.js index 8104d675a..030054843 100644 --- a/tailwind.config.js +++ b/tailwind.config.js @@ -1,71 +1,78 @@ module.exports = { - future: { - purgeLayersByDefault: true, - applyComplexClasses: true, - }, - purge: { - content: [ - './pages/**/*.{js,ts,jsx,tsx}', - './components/**/*.{js,ts,jsx,tsx}', - ], - options: { - safelist: { - standard: ['outline-none'], - }, - }, - }, - theme: { - extend: { - maxWidth: { - '8xl': '1920px', - }, - colors: { - primary: 'var(--primary)', - 'primary-2': 'var(--primary-2)', - secondary: 'var(--secondary)', - 'secondary-2': 'var(--secondary-2)', - hover: 'var(--hover)', - 'hover-1': 'var(--hover-1)', - 'hover-2': 'var(--hover-2)', - 'accent-0': 'var(--accent-0)', - 'accent-1': 'var(--accent-1)', - 'accent-2': 'var(--accent-2)', - 'accent-3': 'var(--accent-3)', - 'accent-4': 'var(--accent-4)', - 'accent-5': 'var(--accent-5)', - 'accent-6': 'var(--accent-6)', - 'accent-7': 'var(--accent-7)', - 'accent-8': 'var(--accent-8)', - 'accent-9': 'var(--accent-9)', - violet: 'var(--violet)', - 'violet-light': 'var(--violet-light)', - 'violet-dark': 'var(--violet-dark)', - pink: 'var(--pink)', - 'pink-light': 'var(--pink-light)', - cyan: 'var(--cyan)', - blue: 'var(--blue)', - green: 'var(--green)', - red: 'var(--red)', - }, - textColor: { - base: 'var(--text-base)', - primary: 'var(--text-primary)', - secondary: 'var(--text-secondary)', - }, - boxShadow: { - 'outline-normal': '0 0 0 2px var(--accent-2)', - magical: - 'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px', - }, - lineHeight: { - 'extra-loose': '2.2', - }, - scale: { - 120: '1.2', - }, - }, - }, - plugins: [ - require('@tailwindcss/typography'), - ], + future: { + purgeLayersByDefault: true, + applyComplexClasses: true, + }, + purge: { + content: [ + './pages/**/*.{js,ts,jsx,tsx}', + './components/**/*.{js,ts,jsx,tsx}', + ], + options: { + safelist: { + standard: ['outline-none'], + }, + }, + }, + theme: { + extend: { + maxWidth: { + '8xl': '1920px', + }, + colors: { + transparent: "transparent", + + black: "#000", + white: "#fff", + + agility: "#222", + + primary: 'var(--primary)', + 'primary-2': 'var(--primary-2)', + secondary: 'var(--secondary)', + 'secondary-2': 'var(--secondary-2)', + hover: 'var(--hover)', + 'hover-1': 'var(--hover-1)', + 'hover-2': 'var(--hover-2)', + 'accent-0': 'var(--accent-0)', + 'accent-1': 'var(--accent-1)', + 'accent-2': 'var(--accent-2)', + 'accent-3': 'var(--accent-3)', + 'accent-4': 'var(--accent-4)', + 'accent-5': 'var(--accent-5)', + 'accent-6': 'var(--accent-6)', + 'accent-7': 'var(--accent-7)', + 'accent-8': 'var(--accent-8)', + 'accent-9': 'var(--accent-9)', + violet: 'var(--violet)', + 'violet-light': 'var(--violet-light)', + 'violet-dark': 'var(--violet-dark)', + pink: 'var(--pink)', + 'pink-light': 'var(--pink-light)', + cyan: 'var(--cyan)', + blue: 'var(--blue)', + green: 'var(--green)', + red: 'var(--red)', + }, + textColor: { + base: 'var(--text-base)', + primary: 'var(--text-primary)', + secondary: 'var(--text-secondary)', + }, + boxShadow: { + 'outline-normal': '0 0 0 2px var(--accent-2)', + magical: + 'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px', + }, + lineHeight: { + 'extra-loose': '2.2', + }, + scale: { + 120: '1.2', + }, + }, + }, + plugins: [ + require('@tailwindcss/typography'), + ], } diff --git a/yarn.lock b/yarn.lock index 007523185..ac2caa342 100644 --- a/yarn.lock +++ b/yarn.lock @@ -5744,6 +5744,11 @@ react-fast-marquee@^1.1.3: resolved "https://registry.yarnpkg.com/react-fast-marquee/-/react-fast-marquee-1.1.3.tgz#8f1f397c64258bdee0e915147d503828ab74d695" integrity sha512-DIfrpWMgIHEoEPWKYzs5dnV5ZSMuu0+4ddRG4qB6Ee2zDi5LuPAGc1gnOceG8j8tvnuSrdkaqYYQwLZ61VzU5w== +react-icons@^4.2.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/react-icons/-/react-icons-4.2.0.tgz#6dda80c8a8f338ff96a1851424d63083282630d0" + integrity sha512-rmzEDFt+AVXRzD7zDE21gcxyBizD/3NqjbX6cmViAgdqfJ2UiLer8927/QhhrXQV7dEj/1EGuOTPp7JnLYVJKQ== + react-is@16.13.1, react-is@^16.8.1: version "16.13.1" resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.13.1.tgz#789729a4dc36de2999dc156dd6c1d9c18cea56a4"