-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #1430 from massenergize/introducing-custom-pages
Introducing custom pages
- Loading branch information
Showing
8 changed files
with
338 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, { useEffect } from "react"; | ||
import { useParams } from "react-router-dom/cjs/react-router-dom.min"; | ||
import { useApiRequest } from "../../../hooks/useApiRequest"; | ||
import LoadingCircle from "../../Shared/LoadingCircle"; | ||
import PBPublishedRender from "./render/PBPublishedRender"; | ||
|
||
function RenderCustomPage() { | ||
const { pageId } = useParams(); | ||
|
||
const [pageLoadHandler] = useApiRequest([ | ||
{ | ||
key: "pageLoad", | ||
url: "/custom.pages.getForUser", | ||
}, | ||
]); | ||
|
||
const [fetchPageInfo, data, error, loading] = pageLoadHandler || []; | ||
|
||
useEffect(() => { | ||
fetchPageInfo({ id: pageId }); | ||
}, [pageId]); | ||
|
||
if (loading) return <LoadingCircle />; | ||
|
||
if (error) | ||
return ( | ||
<div style={{ height: "100vh" }}> | ||
<p | ||
style={{ | ||
width: "100%", | ||
display: "flex", | ||
flexDirection: "row", | ||
alignItems: "center", | ||
justifyContent: "center", | ||
padding: "20px", | ||
color: "#d05c5c", | ||
}} | ||
> | ||
{error} | ||
</p> | ||
</div> | ||
); | ||
|
||
return ( | ||
<div style={{ height: "100vh" }}> | ||
<PBPublishedRender sections={data?.content || []} /> | ||
</div> | ||
); | ||
} | ||
|
||
export default RenderCustomPage; |
51 changes: 51 additions & 0 deletions
51
src/components/Pages/Custom Pages/render/PBPublishedRender.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import React, { useEffect, useMemo, useRef } from "react"; | ||
import { renderSection, serializeBlock } from "./engine/engine"; | ||
|
||
function PBPublishedRender({ sections }) { | ||
const iframeRef = useRef(); | ||
const contRef = useRef(); | ||
|
||
const html = useMemo( | ||
() => | ||
sections | ||
.map(({ block }) => { | ||
return serializeBlock(block?.template); | ||
}) | ||
?.join(""), | ||
[sections] | ||
); | ||
|
||
useEffect(() => { | ||
if (iframeRef?.current) { | ||
const doc = iframeRef.current?.contentDocument || iframeRef.current?.contentWindow?.document; | ||
doc.open(); | ||
doc.write(` | ||
<html> | ||
<head> | ||
<style> | ||
@import url("https://fonts.googleapis.com/css?family=Google+Sans:400,400i,500,500i,600,600i,700,700i,900,900i"); | ||
@import url("https://fonts.googleapis.com/css?family=Roboto:400,400i,500,500i,700,700i"); | ||
@import url("https://fonts.googleapis.com/css?family=Nunito:400,500,700"); | ||
body { | ||
font-family: "Google Sans", "Roboto", sans-serif; | ||
} | ||
</style> | ||
</head> | ||
<body>${html}</body> | ||
</html> | ||
`); | ||
doc.close(); | ||
} | ||
}, [html]); | ||
|
||
return ( | ||
<div ref={contRef} style={{ width: "100%", overflowY: "scroll", overflowX: "hidden", height: "100vh" }}> | ||
<iframe | ||
ref={iframeRef} | ||
style={{ width: "100%", borderWidth: 0, overflowY: "scroll", overflowX: "hidden", height: "100vh" }} | ||
/> | ||
</div> | ||
); | ||
} | ||
|
||
export default PBPublishedRender; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
export const Tags = { | ||
div: { type: "div" }, | ||
img: { type: "img" }, | ||
p: { type: "p" }, | ||
span: { type: "span" }, | ||
h2: { type: "h2" }, | ||
video: { type: "iframe" }, | ||
link: { type: "a" }, | ||
icon: { type: "i" }, | ||
richtext: { type: "div" }, | ||
button: { | ||
type: "a", | ||
style: { | ||
"border-radius": "4px", | ||
cursor: "pointer", | ||
background: "#9fddeb47", | ||
border: "solid 0px #0b9edc", | ||
padding: "10px 20px", | ||
color: "#0b9edc", | ||
"font-weight": "bold", | ||
}, | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import { Tags } from "./blocks"; | ||
import React from "react"; | ||
import { serializeCss } from "./serialize-css"; | ||
const X = "x"; | ||
const Y = "y"; | ||
export const DIRECTIONS = { X, Y }; | ||
|
||
export function debounce(func, delay) { | ||
let timeout; | ||
|
||
return function (...args) { | ||
clearTimeout(timeout); // Clear the previous timeout | ||
timeout = setTimeout(() => { | ||
func.apply(this, args); // Call the function after the delay | ||
}, delay); | ||
}; | ||
} | ||
const layoutFlow = (direction, serialize = false) => { | ||
// let directionKey = serialize ? "flex-direction" : "flexDirection"; | ||
|
||
return { display: "flex", flexDirection: direction === DIRECTIONS.X ? "row" : "column" }; | ||
}; | ||
|
||
|
||
export const serializeBlock = (block) => { | ||
const { direction, element, content, children: childElements } = block || {}; | ||
const { type } = element || {}; | ||
const { text, style, ...props } = element?.props || {}; | ||
if (!element) return ""; | ||
|
||
// Determine the tag to use | ||
const Tag = Tags[type]?.type || "div"; | ||
const defaultTagStyle = Tags[type]?.style || {}; | ||
|
||
// Convert style object to inline style string | ||
const styleTogether = { ...defaultTagStyle, ...style, ...layoutFlow(direction, true) }; | ||
const styleString = serializeCss(styleTogether); | ||
|
||
// Serialize props (excluding style and text) | ||
const propsString = Object.entries(props || {}) | ||
.map(([key, value]) => `${key}="${value}"`) | ||
.join(" "); | ||
|
||
const isRich = type === "richtext"; | ||
const isVideo = type === "video"; | ||
const isButton = type === "button"; | ||
|
||
// If the block is rich text, return the inner HTML | ||
const richText = `<div style="${styleString}"> ${props?.__html} </div>`; | ||
|
||
if (isButton) { | ||
const { alignItems, justifyContent, color, ...btnRest } = styleTogether || {}; | ||
const obj = { alignItems, justifyContent }; | ||
const rootStyles = serializeCss(obj); | ||
const colorString = color ? `color:${color};` : ""; | ||
const btnRestString = serializeCss(btnRest); | ||
return `<div style="width:100%;display:flex; flex-direction:column;${rootStyles}"><a style ="text-align:center;${colorString}${btnRestString}" ${propsString}>${text}</a></div>`; | ||
} | ||
if (isRich) return richText; | ||
if (isVideo) return serializeVideoBlock({ src: props?.src, styleString: serializeCss(styleTogether), propsString }); | ||
|
||
// Serialize children recursively | ||
const innerHTML = | ||
content || | ||
(childElements && | ||
childElements | ||
.map((el) => serializeBlock(el)) // Recursively serialize children | ||
.join("")) || | ||
""; | ||
|
||
// Serialize the block | ||
return `<${Tag} ${styleString ? `style="${styleString}"` : ""} ${propsString}> | ||
${text || ""} | ||
${innerHTML} | ||
</${Tag}>`; | ||
}; | ||
|
||
const serializeVideoBlock = ({ src, styleString, propsString }) => { | ||
return ` | ||
<div> | ||
<iframe | ||
src = "https://www.youtube.com/embed/${src}" | ||
title="YouTube video" | ||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" | ||
allowFullScreen | ||
style="width:100%;border:none;${styleString}" | ||
${propsString} | ||
> | ||
</iframe> | ||
</div> | ||
`; | ||
}; | ||
|
||
// src={${src}} | ||
// style="width:100%;height: auto;border: none;" |
25 changes: 25 additions & 0 deletions
25
src/components/Pages/Custom Pages/render/engine/serialize-css.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
const INLINE_KEYS = { | ||
alignItems: "align-items", | ||
flexDirection: "flex-direction", | ||
justifyContent: "justify-content", | ||
flexWrap: "flex-wrap", | ||
flexBasis: "flex-basis", | ||
objectFit: "object-fit", | ||
marginTop: "margin-top", | ||
marginBottom: "margin-bottom", | ||
marginLeft: "margin-left", | ||
marginRight: "margin-right", | ||
paddingTop: "padding-top", | ||
paddingBottom: "padding-bottom", | ||
paddingLeft: "padding-left", | ||
paddingRight: "padding-right", | ||
fontSize: "font-size", | ||
}; | ||
export const serializeCss = (inLinObj) => { | ||
return Object.entries(inLinObj) | ||
.map(([key, value]) => { | ||
key = INLINE_KEYS[key] || key; | ||
return `${key}: ${value};`; | ||
}) | ||
.join(" "); | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,5 +2,5 @@ | |
"IS_LOCAL": false, | ||
"IS_PROD": false, | ||
"IS_CANARY": false, | ||
"BUILD_VERSION": "4.14.8" | ||
"BUILD_VERSION": "4.14.11" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
import { useState } from "react"; | ||
import { apiCall } from "../api/functions"; | ||
|
||
/** | ||
* | ||
* @param {*} objArrays | ||
* @returns Array of objects(requestHandlers). Each of the handlers is an array containing the following: | ||
* 1. apiRequest: Function to make the API request | ||
* 2. data: The data returned from the API request | ||
* 3. error: The error returned from the API request | ||
* 4. loading: The loading state of the API request | ||
* 5. response: The response returned from the API request | ||
* | ||
*/ | ||
export const useApiRequest = (objArrays) => { | ||
const [loading, setLoading] = useState({}); | ||
const [data, setData] = useState({}); | ||
const [response, setResponse] = useState({}); | ||
const [error, setError] = useState({}); | ||
|
||
const handleResponse = (key, response) => { | ||
setDataValue(key, response?.data); | ||
setResponseValue(key, response); | ||
}; | ||
const setLoadingValue = (key, value) => { | ||
setLoading({ ...loading, [key]: value }); | ||
}; | ||
const setDataValue = (key, value) => { | ||
setData({ ...data, [key]: value }); | ||
}; | ||
const setErrorValue = (key, value) => { | ||
setError({ ...error, [key]: value }); | ||
}; | ||
const setResponseValue = (key, value) => { | ||
setResponse({ ...response, [key]: value }); | ||
}; | ||
|
||
const apiRequest = (body, cb, options) => { | ||
const { url, key } = options || {}; | ||
setLoadingValue(key, true); | ||
setErrorValue(key, null); | ||
apiCall(url, body) | ||
.then((response) => { | ||
setLoadingValue(key, false); | ||
if (!response?.success) { | ||
setErrorValue(key, response?.error); | ||
} | ||
handleResponse(key, response); | ||
cb && cb(response); | ||
}) | ||
.catch((e) => { | ||
console.log(`useApiError: ${key} => `, e); | ||
setLoadingValue(key, false); | ||
setErrorValue(key, e?.toString()); | ||
cb && cb(null, e?.toString()); | ||
}); | ||
}; | ||
|
||
// return objArrays.map((obj) => { | ||
// const key = obj?.key; | ||
// return { | ||
// error: error[key], | ||
// loading: loading[key], | ||
// apiRequest: (externalProps) => apiRequest({ ...obj, ...(externalProps || {}), key }), | ||
// data: data[key], | ||
// response: response[key] | ||
// }; | ||
// }); | ||
|
||
return objArrays.map((obj) => { | ||
const key = obj?.key; | ||
return [ | ||
(body, cb, options = {}) => apiRequest(body, cb, { ...obj, ...(options || {}), key }), | ||
data[key], | ||
error[key], | ||
loading[key], | ||
(error) => setErrorValue(key, error), | ||
(value) => setLoadingValue(key, value), | ||
(data) => setDataValue(key, data), | ||
response[key] | ||
]; | ||
}); | ||
}; |