diff --git a/build/content-helper/dashboard-page.asset.php b/build/content-helper/dashboard-page.asset.php index 111f7f443..d95036f79 100644 --- a/build/content-helper/dashboard-page.asset.php +++ b/build/content-helper/dashboard-page.asset.php @@ -1 +1 @@ - array('react', 'wp-dom-ready', 'wp-element'), 'version' => '12fe4372400030ba8741'); + array('react', 'react-dom', 'wp-dom-ready', 'wp-element'), 'version' => '18278c930ac085311870'); diff --git a/build/content-helper/dashboard-page.js b/build/content-helper/dashboard-page.js index 2b3290cc5..3ed664bad 100644 --- a/build/content-helper/dashboard-page.js +++ b/build/content-helper/dashboard-page.js @@ -1 +1 @@ -!function(){"use strict";var e,r,t,n,o={20:function(e,r,t){var n=t(609),o=Symbol.for("react.element"),a=Symbol.for("react.fragment"),u=Object.prototype.hasOwnProperty,c=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,f={key:!0,ref:!0,__self:!0,__source:!0};function i(e,r,t){var n,a={},i=null,s=null;for(n in void 0!==t&&(i=""+t),void 0!==r.key&&(i=""+r.key),void 0!==r.ref&&(s=r.ref),r)u.call(r,n)&&!f.hasOwnProperty(n)&&(a[n]=r[n]);if(e&&e.defaultProps)for(n in r=e.defaultProps)void 0===a[n]&&(a[n]=r[n]);return{$$typeof:o,type:e,key:i,ref:s,props:a,_owner:c.current}}r.Fragment=a,r.jsx=i,r.jsxs=i},848:function(e,r,t){e.exports=t(20)},609:function(e){e.exports=window.React}},a={};function u(e){var r=a[e];if(void 0!==r)return r.exports;var t=a[e]={exports:{}};return o[e](t,t.exports,u),t.exports}u.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return u.d(r,{a:r}),r},u.d=function(e,r){for(var t in r)u.o(r,t)&&!u.o(e,t)&&Object.defineProperty(e,t,{enumerable:!0,get:r[t]})},u.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},e=u(848),r=window.wp.domReady,t=u.n(r),n=window.wp.element,t()((function(){(0,n.createRoot)(document.getElementById("parsely-dashboard-page")).render((0,e.jsxs)(e.Fragment,{children:[(0,e.jsx)("h1",{children:"Parse.ly"}),(0,e.jsx)("p",{children:"Welcome to the Parse.ly Dashboard page!"})]}))}))}(); \ No newline at end of file +!function(){"use strict";var e={20:function(e,t,r){var n=r(609),a=Symbol.for("react.element"),o=Symbol.for("react.fragment"),i=Object.prototype.hasOwnProperty,l=n.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.ReactCurrentOwner,s={key:!0,ref:!0,__self:!0,__source:!0};function u(e,t,r){var n,o={},u=null,c=null;for(n in void 0!==r&&(u=""+r),void 0!==t.key&&(u=""+t.key),void 0!==t.ref&&(c=t.ref),t)i.call(t,n)&&!s.hasOwnProperty(n)&&(o[n]=t[n]);if(e&&e.defaultProps)for(n in t=e.defaultProps)void 0===o[n]&&(o[n]=t[n]);return{$$typeof:a,type:e,key:u,ref:c,props:o,_owner:l.current}}t.Fragment=o,t.jsx=u,t.jsxs=u},848:function(e,t,r){e.exports=r(20)},609:function(e){e.exports=window.React}},t={};function r(n){var a=t[n];if(void 0!==a)return a.exports;var o=t[n]={exports:{}};return e[n](o,o.exports,r),o.exports}r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,{a:t}),t},r.d=function(e,t){for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},function(){var e,t=r(848),n=window.wp.domReady,a=r.n(n),o=window.wp.element,i=r(609),l=window.ReactDOM;function s(){return s=Object.assign?Object.assign.bind():function(e){for(var t=1;t=0&&(t.hash=e.substr(r),e=e.substr(0,r));let n=e.indexOf("?");n>=0&&(t.search=e.substr(n),e=e.substr(0,n)),e&&(t.pathname=e)}return t}var m;function g(e,t,r){return void 0===r&&(r="/"),function(e,t,r,n){let a=F(("string"==typeof t?v(t):t).pathname||"/",r);if(null==a)return null;let o=y(e);!function(e){e.sort(((e,t)=>e.score!==t.score?t.score-e.score:function(e,t){return e.length===t.length&&e.slice(0,-1).every(((e,r)=>e===t[r]))?e[e.length-1]-t[t.length-1]:0}(e.routesMeta.map((e=>e.childrenIndex)),t.routesMeta.map((e=>e.childrenIndex)))))}(o);let i=null;for(let e=0;null==i&&e{let i={relativePath:void 0===o?e.path||"":o,caseSensitive:!0===e.caseSensitive,childrenIndex:a,route:e};i.relativePath.startsWith("/")&&(c(i.relativePath.startsWith(n),'Absolute route path "'+i.relativePath+'" nested under path "'+n+'" is not valid. An absolute child route path must start with the combined path of all its parent routes.'),i.relativePath=i.relativePath.slice(n.length));let l=T([n,i.relativePath]),s=r.concat(i);e.children&&e.children.length>0&&(c(!0!==e.index,'Index routes must not have child routes. Please remove all child routes from route path "'+l+'".'),y(e.children,t,s,l)),(null!=e.path||e.index)&&t.push({path:l,score:C(l,e.index),routesMeta:s})};return e.forEach(((e,t)=>{var r;if(""!==e.path&&null!=(r=e.path)&&r.includes("?"))for(let r of w(e.path))a(e,t,r);else a(e,t)})),t}function w(e){let t=e.split("/");if(0===t.length)return[];let[r,...n]=t,a=r.endsWith("?"),o=r.replace(/\?$/,"");if(0===n.length)return a?[o,""]:[o];let i=w(n.join("/")),l=[];return l.push(...i.map((e=>""===e?o:[o,e].join("/")))),a&&l.push(...i),l.map((t=>e.startsWith("/")&&""===t?"/":t))}!function(e){e.data="data",e.deferred="deferred",e.redirect="redirect",e.error="error"}(m||(m={})),new Set(["lazy","caseSensitive","path","id","index","children"]);const x=/^:[\w-]+$/,b=3,E=2,S=1,R=10,_=-2,P=e=>"*"===e;function C(e,t){let r=e.split("/"),n=r.length;return r.some(P)&&(n+=_),t&&(n+=E),r.filter((e=>!P(e))).reduce(((e,t)=>e+(x.test(t)?b:""===t?S:R)),n)}function j(e,t,r){void 0===r&&(r=!1);let{routesMeta:n}=e,a={},o="/",i=[];for(let e=0;e(n.push({paramName:t,isOptional:null!=r}),r?"/?([^\\/]+)?":"/([^\\/]+)")));return e.endsWith("*")?(n.push({paramName:"*"}),a+="*"===e||"/*"===e?"(.*)$":"(?:\\/(.+)|\\/*)$"):r?a+="\\/*$":""!==e&&"/"!==e&&(a+="(?:(?=\\/|$))"),[new RegExp(a,t?void 0:"i"),n]}(e.path,e.caseSensitive,e.end),a=t.match(r);if(!a)return null;let o=a[0],i=o.replace(/(.)\/+$/,"$1"),l=a.slice(1);return{params:n.reduce(((e,t,r)=>{let{paramName:n,isOptional:a}=t;if("*"===n){let e=l[r]||"";i=o.slice(0,o.length-e.length).replace(/(.)\/+$/,"$1")}const s=l[r];return e[n]=a&&!s?void 0:(s||"").replace(/%2F/g,"/"),e}),{}),pathname:o,pathnameBase:i,pattern:e}}function U(e){try{return e.split("/").map((e=>decodeURIComponent(e).replace(/\//g,"%2F"))).join("/")}catch(t){return h(!1,'The URL path "'+e+'" could not be decoded because it is is a malformed URL segment. This is probably due to a bad percent encoding ('+t+")."),e}}function F(e,t){if("/"===t)return e;if(!e.toLowerCase().startsWith(t.toLowerCase()))return null;let r=t.endsWith("/")?t.length-1:t.length,n=e.charAt(r);return n&&"/"!==n?null:e.slice(r)||"/"}const T=e=>e.join("/").replace(/\/\/+/g,"/"),k=e=>e.replace(/\/+$/,"").replace(/^\/*/,"/");Error;const L=["post","put","patch","delete"],B=(new Set(L),["get",...L]);function D(){return D=Object.assign?Object.assign.bind():function(e){for(var t=1;t0))return null;e=r.matches}}let l=e,s=null==(a=r)?void 0:a.errors;if(null!=s){let e=l.findIndex((e=>e.route.id&&void 0!==(null==s?void 0:s[e.route.id])));e>=0||c(!1),l=l.slice(0,Math.min(l.length,e+1))}let u=!1,h=-1;if(r&&n&&n.v7_partialHydration)for(let e=0;e=0?l.slice(0,h+1):[l[0]];break}}}return l.reduceRight(((e,n,a)=>{let o,c=!1,p=null,d=null;var f;r&&(o=s&&n.route.id?s[n.route.id]:void 0,p=n.route.errorElement||J,u&&(h<0&&0===a?(Q[f="route-fallback"]||(Q[f]=!0),c=!0,d=null):h===a&&(c=!0,d=n.route.hydrateFallbackElement||null)));let v=t.concat(l.slice(0,a+1)),m=()=>{let t;return t=o?p:c?d:n.route.Component?i.createElement(n.route.Component,null):n.route.element?n.route.element:e,i.createElement(G,{match:n,routeContext:{outlet:e,matches:v,isDataRoute:null!=r},children:t})};return r&&(n.route.ErrorBoundary||n.route.errorElement||0===a)?i.createElement(Y,{location:r.location,revalidation:r.revalidation,component:p,error:o,children:m(),routeContext:{outlet:null,matches:v,isDataRoute:!0}}):m()}),null)}(w&&w.map((e=>Object.assign({},e,{params:Object.assign({},u,e.params),pathname:T([h,o.encodeLocation?o.encodeLocation(e.pathname).pathname:e.pathname]),pathnameBase:"/"===e.pathnameBase?h:T([h,o.encodeLocation?o.encodeLocation(e.pathnameBase).pathname:e.pathnameBase])}))),l,n,a);return r&&x?i.createElement(W.Provider,{value:{location:D({pathname:"/",search:"",hash:"",state:null,key:"default"},p),navigationType:e.Pop}},x):x}function q(){let e=function(){var e;let t=i.useContext(N),r=function(){let e=i.useContext(A);return e||c(!1),e}(K.UseRouteError),n=function(){let e=function(){let e=i.useContext(I);return e||c(!1),e}(),t=e.matches[e.matches.length-1];return t.route.id||c(!1),t.route.id}(K.UseRouteError);return void 0!==t?t:null==(e=r.errors)?void 0:e[n]}(),t=function(e){return null!=e&&"number"==typeof e.status&&"string"==typeof e.statusText&&"boolean"==typeof e.internal&&"data"in e}(e)?e.status+" "+e.statusText:e instanceof Error?e.message:JSON.stringify(e),r=e instanceof Error?e.stack:null,n={padding:"0.5rem",backgroundColor:"rgba(200,200,200, 0.5)"};return i.createElement(i.Fragment,null,i.createElement("h2",null,"Unexpected Application Error!"),i.createElement("h3",{style:{fontStyle:"italic"}},t),r?i.createElement("pre",{style:n},r):null,null)}const J=i.createElement(q,null);class Y extends i.Component{constructor(e){super(e),this.state={location:e.location,revalidation:e.revalidation,error:e.error}}static getDerivedStateFromError(e){return{error:e}}static getDerivedStateFromProps(e,t){return t.location!==e.location||"idle"!==t.revalidation&&"idle"===e.revalidation?{error:e.error,location:e.location,revalidation:e.revalidation}:{error:void 0!==e.error?e.error:t.error,location:t.location,revalidation:e.revalidation||t.revalidation}}componentDidCatch(e,t){console.error("React Router caught the following error during render",e,t)}render(){return void 0!==this.state.error?i.createElement(I.Provider,{value:this.props.routeContext},i.createElement(N.Provider,{value:this.state.error,children:this.props.component})):this.props.children}}function G(e){let{routeContext:t,match:r,children:n}=e,a=i.useContext($);return a&&a.static&&a.staticContext&&(r.route.errorElement||r.route.ErrorBoundary)&&(a.staticContext._deepestRenderedBoundaryId=r.route.id),i.createElement(I.Provider,{value:t},n)}var K=function(e){return e.UseBlocker="useBlocker",e.UseLoaderData="useLoaderData",e.UseActionData="useActionData",e.UseRouteError="useRouteError",e.UseNavigation="useNavigation",e.UseRouteLoaderData="useRouteLoaderData",e.UseMatches="useMatches",e.UseRevalidator="useRevalidator",e.UseNavigateStable="useNavigate",e.UseRouteId="useRouteId",e}(K||{});const Q={},X={},Z=(e,t,r)=>{var n;X[n="⚠️ React Router Future Flag Warning: "+t+". You can use the `"+e+"` future flag to opt-in early. For more information, see "+r+"."]||(X[n]=!0,console.warn(n))};function ee(e){c(!1)}function te(t){let{basename:r="/",children:n=null,location:a,navigationType:o=e.Pop,navigator:l,static:s=!1,future:u}=t;z()&&c(!1);let h=r.replace(/^\/*/,"/"),p=i.useMemo((()=>({basename:h,navigator:l,static:s,future:D({v7_relativeSplatPath:!1},u)})),[h,u,l,s]);"string"==typeof a&&(a=v(a));let{pathname:d="/",search:f="",hash:m="",state:g=null,key:y="default"}=a,w=i.useMemo((()=>{let e=F(d,h);return null==e?null:{location:{pathname:e,search:f,hash:m,state:g,key:y},navigationType:o}}),[h,d,f,m,g,y,o]);return null==w?null:i.createElement(M.Provider,{value:p},i.createElement(W.Provider,{children:n,value:w}))}function re(e){let{children:t,location:r}=e;return V(ne(t),r)}function ne(e,t){void 0===t&&(t=[]);let r=[];return i.Children.forEach(e,((e,n)=>{if(!i.isValidElement(e))return;let a=[...t,n];if(e.type===i.Fragment)return void r.push.apply(r,ne(e.props.children,a));e.type!==ee&&c(!1),e.props.index&&e.props.children&&c(!1);let o={id:e.props.id||a.join("-"),caseSensitive:e.props.caseSensitive,element:e.props.element,Component:e.props.Component,index:e.props.index,path:e.props.path,loader:e.props.loader,action:e.props.action,errorElement:e.props.errorElement,ErrorBoundary:e.props.ErrorBoundary,hasErrorBoundary:null!=e.props.ErrorBoundary||null!=e.props.errorElement,shouldRevalidate:e.props.shouldRevalidate,handle:e.props.handle,lazy:e.props.lazy};e.props.children&&(o.children=ne(e.props.children,a)),r.push(o)})),r}i.startTransition,new Promise((()=>{})),i.Component,new Set(["application/x-www-form-urlencoded","multipart/form-data","text/plain"]);try{window.__reactRouterVersion="6"}catch(e){}new Map;const ae=i.startTransition;function oe(t){let{basename:r,children:n,future:a,window:o}=t,l=i.useRef();var m;null==l.current&&(l.current=(void 0===(m={window:o,v5Compat:!0})&&(m={}),function(t,r,n,a){void 0===a&&(a={});let{window:o=document.defaultView,v5Compat:i=!1}=a,l=o.history,h=e.Pop,v=null,m=g();function g(){return(l.state||{idx:null}).idx}function y(){h=e.Pop;let t=g(),r=null==t?null:t-m;m=t,v&&v({action:h,location:x.location,delta:r})}function w(e){let t="null"!==o.location.origin?o.location.origin:o.location.href,r="string"==typeof e?e:f(e);return r=r.replace(/ $/,"%20"),c(t,"No window.location.(origin|href) available to create URL for href: "+r),new URL(r,t)}null==m&&(m=0,l.replaceState(s({},l.state,{idx:m}),""));let x={get action(){return h},get location(){return t(o,l)},listen(e){if(v)throw new Error("A history only accepts one active listener");return o.addEventListener(u,y),v=e,()=>{o.removeEventListener(u,y),v=null}},createHref(e){return r(o,e)},createURL:w,encodeLocation(e){let t=w(e);return{pathname:t.pathname,search:t.search,hash:t.hash}},push:function(t,r){h=e.Push;let a=d(x.location,t,r);n&&n(a,t),m=g()+1;let s=p(a,m),u=x.createHref(a);try{l.pushState(s,"",u)}catch(e){if(e instanceof DOMException&&"DataCloneError"===e.name)throw e;o.location.assign(u)}i&&v&&v({action:h,location:x.location,delta:1})},replace:function(t,r){h=e.Replace;let a=d(x.location,t,r);n&&n(a,t),m=g();let o=p(a,m),s=x.createHref(a);l.replaceState(o,"",s),i&&v&&v({action:h,location:x.location,delta:0})},go(e){return l.go(e)}};return x}((function(e,t){let{pathname:r="/",search:n="",hash:a=""}=v(e.location.hash.substr(1));return r.startsWith("/")||r.startsWith(".")||(r="/"+r),d("",{pathname:r,search:n,hash:a},t.state&&t.state.usr||null,t.state&&t.state.key||"default")}),(function(e,t){let r=e.document.querySelector("base"),n="";if(r&&r.getAttribute("href")){let t=e.location.href,r=t.indexOf("#");n=-1===r?t:t.slice(0,r)}return n+"#"+("string"==typeof t?t:f(t))}),(function(e,t){h("/"===e.pathname.charAt(0),"relative pathnames are not supported in hash history.push("+JSON.stringify(t)+")")}),m)));let g=l.current,[y,w]=i.useState({action:g.action,location:g.location}),{v7_startTransition:x}=a||{},b=i.useCallback((e=>{x&&ae?ae((()=>w(e))):w(e)}),[w,x]);return i.useLayoutEffect((()=>g.listen(b)),[g,b]),i.useEffect((()=>{return null!=(e=a)&&e.v7_startTransition||Z("v7_startTransition","React Router will begin wrapping state updates in `React.startTransition` in v7","https://reactrouter.com/v6/upgrading/future#v7_starttransition"),null!=e&&e.v7_relativeSplatPath||t&&t.v7_relativeSplatPath||Z("v7_relativeSplatPath","Relative route resolution within Splat routes is changing in v7","https://reactrouter.com/v6/upgrading/future#v7_relativesplatpath"),void(t&&(t.v7_fetcherPersist||Z("v7_fetcherPersist","The persistence behavior of fetchers is changing in v7","https://reactrouter.com/v6/upgrading/future#v7_fetcherpersist"),t.v7_normalizeFormMethod||Z("v7_normalizeFormMethod","Casing of `formMethod` fields is being normalized to uppercase in v7","https://reactrouter.com/v6/upgrading/future#v7_normalizeformmethod"),t.v7_partialHydration||Z("v7_partialHydration","`RouterProvider` hydration behavior is changing in v7","https://reactrouter.com/v6/upgrading/future#v7_partialhydration"),t.v7_skipActionErrorRevalidation||Z("v7_skipActionErrorRevalidation","The revalidation behavior after 4xx/5xx `action` responses is changing in v7","https://reactrouter.com/v6/upgrading/future#v7_skipactionerrorrevalidation")));var e,t}),[a]),i.createElement(te,{basename:r,children:n,location:y.location,navigationType:y.action,navigator:g,future:a})}var ie,le,se;l.flushSync,i.useId,"undefined"!=typeof window&&void 0!==window.document&&window.document.createElement,(se=ie||(ie={})).UseScrollRestoration="useScrollRestoration",se.UseSubmit="useSubmit",se.UseSubmitFetcher="useSubmitFetcher",se.UseFetcher="useFetcher",se.useViewTransitionState="useViewTransitionState",function(e){e.UseFetcher="useFetcher",e.UseFetchers="useFetchers",e.UseScrollRestoration="useScrollRestoration"}(le||(le={}));var ue=function(){return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h1",{children:"Parse.ly Dashboard"}),(0,t.jsx)("p",{children:"Welcome to the main Parse.ly dashboard page."})]})},ce=function(){return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h1",{children:"Traffic Boost"}),(0,t.jsx)("p",{children:"This is where the amazing Traffic Boost implementation will live."})]})},he=function(){return(0,t.jsxs)(t.Fragment,{children:[(0,t.jsx)("h1",{children:"Parse.ly Settings"}),(0,t.jsx)("p",{children:"This is a page for settings."})]})};a()((function(){(0,o.createRoot)(document.getElementById("parsely-dashboard-page")).render((0,t.jsx)(oe,{future:{v7_relativeSplatPath:!0,v7_startTransition:!0},children:(0,t.jsx)(pe,{})}))}));var pe=function(){var e=H();return(0,o.useEffect)((function(){var e=document.querySelector("#toplevel_page_parsely-dashboard-page .wp-submenu li a.wp-first-item");e&&e.setAttribute("href",window.location.pathname+window.location.search+"#/")}),[]),(0,o.useEffect)((function(){document.querySelectorAll("#toplevel_page_parsely-dashboard-page .wp-submenu li").forEach((function(t){var r,n=t.querySelector("a");(null===(r=null==n?void 0:n.getAttribute("href"))||void 0===r?void 0:r.split("#")[1])===e.pathname?(t.classList.add("current"),null==n||n.blur()):t.classList.remove("current")}))}),[e]),(0,t.jsxs)(re,{children:[(0,t.jsx)(ee,{path:"/",element:(0,t.jsx)(ue,{})}),(0,t.jsx)(ee,{path:"/traffic-boost",element:(0,t.jsx)(ce,{})}),(0,t.jsx)(ee,{path:"/settings",element:(0,t.jsx)(he,{})})]})}}()}(); \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 2589cd506..005f626d1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,9 +10,11 @@ "license": "GPL-2.0-or-later", "dependencies": { "@types/js-cookie": "^3.0.6", + "@types/react-router-dom": "^5.3.3", "@wordpress/dom-ready": "^4.11.0", "js-cookie": "^3.0.5", - "lodash.debounce": "^4.0.8" + "lodash.debounce": "^4.0.8", + "react-router-dom": "^6.28.0" }, "devDependencies": { "@playwright/test": "^1.48.2", @@ -4113,6 +4115,14 @@ "react-dom": "^16.8.0 || ^17.0.0 || ^18.0.0" } }, + "node_modules/@remix-run/router": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.21.0.tgz", + "integrity": "sha512-xfSkCAchbdG5PnbrKqFWwia4Bi61nH+wm8wLEqfHDyp7Y3dZzgqS2itV8i4gAq9pC2HsTpwyBC6Ds8VHZ96JlA==", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@rtsao/scc": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", @@ -5157,6 +5167,11 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/history": { + "version": "4.7.11", + "resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz", + "integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==" + }, "node_modules/@types/http-cache-semantics": { "version": "4.0.4", "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.4.tgz", @@ -5371,7 +5386,6 @@ "version": "15.7.13", "resolved": "https://registry.npmjs.org/@types/prop-types/-/prop-types-15.7.13.tgz", "integrity": "sha512-hCZTSvwbzWGvhqxp/RqVqwU999pBf2vp7hzIjiYOsl8wqOmUxkQ6ddw1cV3l8811+kdUFus/q4d1Y3E3SyEifA==", - "dev": true, "license": "MIT" }, "node_modules/@types/qs": { @@ -5392,7 +5406,6 @@ "version": "18.3.11", "resolved": "https://registry.npmjs.org/@types/react/-/react-18.3.11.tgz", "integrity": "sha512-r6QZ069rFTjrEYgFdOck1gK7FLVsgJE7tTz0pQBczlBNUhBNk0MQH4UbnFSwjpQLMkLzgqvBBa+qGpLje16eTQ==", - "dev": true, "license": "MIT", "dependencies": { "@types/prop-types": "*", @@ -5409,6 +5422,25 @@ "@types/react": "*" } }, + "node_modules/@types/react-router": { + "version": "5.1.20", + "resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz", + "integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*" + } + }, + "node_modules/@types/react-router-dom": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz", + "integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==", + "dependencies": { + "@types/history": "^4.7.11", + "@types/react": "*", + "@types/react-router": "*" + } + }, "node_modules/@types/responselike": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.3.tgz", @@ -12521,7 +12553,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true, "license": "MIT" }, "node_modules/cwd": { @@ -19666,7 +19697,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "dev": true, "license": "MIT" }, "node_modules/js-yaml": { @@ -20326,7 +20356,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "dev": true, "license": "MIT", "dependencies": { "js-tokens": "^3.0.0 || ^4.0.0" @@ -23487,7 +23516,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" @@ -23527,7 +23555,6 @@ "version": "18.3.1", "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0", @@ -23618,6 +23645,36 @@ } } }, + "node_modules/react-router": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-6.28.0.tgz", + "integrity": "sha512-HrYdIFqdrnhDw0PqG/AKjAqEqM7AvxCz0DQ4h2W8k6nqmc5uRBYDag0SBxx9iYz5G8gnuNVLzUe13wl9eAsXXg==", + "dependencies": { + "@remix-run/router": "1.21.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8" + } + }, + "node_modules/react-router-dom": { + "version": "6.28.0", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.28.0.tgz", + "integrity": "sha512-kQ7Unsl5YdyOltsPGl31zOjLrDv+m2VcIEcIHqYYD3Lp0UppLjrzcfJqDJwXxFw3TH/yvapbnUvPlAj7Kx5nbg==", + "dependencies": { + "@remix-run/router": "1.21.0", + "react-router": "6.28.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "react": ">=16.8", + "react-dom": ">=16.8" + } + }, "node_modules/react-style-singleton": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.1.tgz", @@ -24436,7 +24493,6 @@ "version": "0.23.2", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz", "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==", - "dev": true, "license": "MIT", "dependencies": { "loose-envify": "^1.1.0" diff --git a/package.json b/package.json index 8639df0eb..bcffdfa23 100644 --- a/package.json +++ b/package.json @@ -37,9 +37,11 @@ ], "dependencies": { "@types/js-cookie": "^3.0.6", + "@types/react-router-dom": "^5.3.3", "@wordpress/dom-ready": "^4.11.0", "js-cookie": "^3.0.5", - "lodash.debounce": "^4.0.8" + "lodash.debounce": "^4.0.8", + "react-router-dom": "^6.28.0" }, "devDependencies": { "@playwright/test": "^1.48.2", diff --git a/src/UI/class-dashboard-page.php b/src/UI/class-dashboard-page.php index 405f418f5..3602175b6 100644 --- a/src/UI/class-dashboard-page.php +++ b/src/UI/class-dashboard-page.php @@ -29,6 +29,7 @@ final class Dashboard_Page { public function run(): void { add_action( 'admin_menu', array( $this, 'add_dashboard_page_to_menu' ) ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_dashboard_page_scripts' ) ); + add_filter( 'parent_file', array( $this, 'fix_submenu_highlighting' ) ); } /** @@ -50,6 +51,34 @@ public function add_dashboard_page_to_menu(): void { $icon, 30 ); + + // Register the subpages. + add_submenu_page( + 'parsely-dashboard-page', + 'Parse.ly Dashboard Page', + 'Dashboard', + Parsely::CAPABILITY, // phpcs:ignore WordPress.WP.Capabilities.Undetermined + 'parsely-dashboard-page', + '__return_null' + ); + + add_submenu_page( + 'parsely-dashboard-page', + 'Parse.ly Traffic Boost', + 'Traffic Boost', + Parsely::CAPABILITY, // phpcs:ignore WordPress.WP.Capabilities.Undetermined + 'parsely-dashboard-page#/traffic-boost', + '__return_null' + ); + + add_submenu_page( + 'parsely-dashboard-page', + 'Parse.ly Settings', + 'Settings', + Parsely::CAPABILITY, // phpcs:ignore WordPress.WP.Capabilities.Undetermined + 'parsely-dashboard-page#/settings', + '__return_null' + ); } /** @@ -61,6 +90,28 @@ public function add_dashboard_page_placeholder(): void { echo '
'; } + /** + * Fixes the highlighting of the submenu items. + * + * This removes the highlighting from the submenu items when the dashboard page is active, so it can + * later be added by the React app. + * + * @since 3.18.0 + * + * @param string $parent_file The parent file. + * @return string The parent file. + */ + public function fix_submenu_highlighting( string $parent_file ): string { + global $submenu_file, $current_screen; + + if ( 'toplevel_page_parsely-dashboard-page' === $current_screen->base ) { + // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited + $submenu_file = 'non-existent-slug'; + } + + return $parent_file; + } + /** * Enqueues all needed scripts and styles for the dashboard page. * diff --git a/src/content-helper/dashboard-page/dashboard-page.tsx b/src/content-helper/dashboard-page/dashboard-page.tsx index bde2c651f..b7ef076b1 100644 --- a/src/content-helper/dashboard-page/dashboard-page.tsx +++ b/src/content-helper/dashboard-page/dashboard-page.tsx @@ -2,7 +2,17 @@ * WordPress dependencies */ import domReady from '@wordpress/dom-ready'; -import { createRoot } from '@wordpress/element'; +import { createRoot, useEffect } from '@wordpress/element'; + +/** + * External dependencies + */ +import { HashRouter as Router, Route, Routes, useLocation } from 'react-router-dom'; + +/** + * Internal dependencies + */ +import { DashboardPage, SettingsPage, TrafficBoostPage } from './pages'; domReady( () => { const root = createRoot( @@ -10,9 +20,66 @@ domReady( () => { ); root.render( - <> -

Parse.ly

-

Welcome to the Parse.ly Dashboard page!

- + + + ); } ); + +/** + * Main component for the Parse.ly dashboard. + * + * @since 3.18.0 + * + * @class + */ +const ParselyDashboard = () => { + const location = useLocation(); + + /** + * Replaces the first link to have the hash router link. + * + * @since 3.18.0 + */ + useEffect( () => { + const firstLink = document.querySelector( '#toplevel_page_parsely-dashboard-page .wp-submenu li a.wp-first-item' ); + if ( firstLink ) { + firstLink.setAttribute( 'href', window.location.pathname + window.location.search + '#/' ); + } + }, [] ); + + /** + * Changes the submenus highlight based on the current page. + * + * @since 3.18.0 + */ + useEffect( () => { + const submenuItems = document.querySelectorAll( '#toplevel_page_parsely-dashboard-page .wp-submenu li' ); + + submenuItems.forEach( ( item ) => { + const link = item.querySelector( 'a' ); + const hashPath = link?.getAttribute( 'href' )?.split( '#' )[ 1 ]; + + if ( hashPath === location.pathname ) { + item.classList.add( 'current' ); + link?.blur(); + } else { + item.classList.remove( 'current' ); + } + } ); + }, [ location ] ); + + return ( + + } /> + } /> + } /> + + ); +}; + diff --git a/src/content-helper/dashboard-page/pages/dashboard/page-component.tsx b/src/content-helper/dashboard-page/pages/dashboard/page-component.tsx new file mode 100644 index 000000000..4f5a4ab34 --- /dev/null +++ b/src/content-helper/dashboard-page/pages/dashboard/page-component.tsx @@ -0,0 +1,13 @@ +/** + * The main dashboard page component. + * + * @since 3.18.0 + */ +export const DashboardPage = () => { + return ( + <> +

Parse.ly Dashboard

+

Welcome to the main Parse.ly dashboard page.

+ + ); +}; diff --git a/src/content-helper/dashboard-page/pages/index.tsx b/src/content-helper/dashboard-page/pages/index.tsx new file mode 100644 index 000000000..a60dc0c75 --- /dev/null +++ b/src/content-helper/dashboard-page/pages/index.tsx @@ -0,0 +1,3 @@ +export { DashboardPage } from './dashboard/page-component'; +export { TrafficBoostPage } from './traffic-boost/page-component'; +export { SettingsPage } from './settings/page-component'; diff --git a/src/content-helper/dashboard-page/pages/settings/page-component.tsx b/src/content-helper/dashboard-page/pages/settings/page-component.tsx new file mode 100644 index 000000000..925ec087a --- /dev/null +++ b/src/content-helper/dashboard-page/pages/settings/page-component.tsx @@ -0,0 +1,13 @@ +/** + * Settings page component. + * + * @since 3.18.0 + */ +export const SettingsPage = () => { + return ( + <> +

Parse.ly Settings

+

This is a page for settings.

+ + ); +}; diff --git a/src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx b/src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx new file mode 100644 index 000000000..83e53080e --- /dev/null +++ b/src/content-helper/dashboard-page/pages/traffic-boost/page-component.tsx @@ -0,0 +1,13 @@ +/** + * Traffic Boost page component. + * + * @since 3.18.0 + */ +export const TrafficBoostPage = () => { + return ( + <> +

Traffic Boost

+

This is where the amazing Traffic Boost implementation will live.

+ + ); +};