diff --git a/src/app.jsx b/src/app.jsx index fbd9c061e..5a009f016 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -112,6 +112,15 @@ function Page() { /> ); } + // ?page=blog + case "blog": { + return ( + + ); + } default: { // TODO: 404 page return

404

; diff --git a/src/devhub/components/molecule/Input.jsx b/src/devhub/components/molecule/Input.jsx index 69b27c7e2..65b8c9d78 100644 --- a/src/devhub/components/molecule/Input.jsx +++ b/src/devhub/components/molecule/Input.jsx @@ -14,7 +14,7 @@ const TextInput = ({ ...otherProps }) => { const typeAttribute = - type === "text" || type === "password" || type === "number" ? type : "text"; + type === "text" || type === "password" || type === "number" || type === "date" ? type : "text"; const renderedLabels = [ (label?.length ?? 0) > 0 ? ( diff --git a/src/devhub/entity/addon/blog/Card.jsx b/src/devhub/entity/addon/blog/Card.jsx index 31d408504..d37f3a3f6 100644 --- a/src/devhub/entity/addon/blog/Card.jsx +++ b/src/devhub/entity/addon/blog/Card.jsx @@ -1,39 +1,148 @@ const cidToURL = (cid) => `https://ipfs.near.social/ipfs/${cid}`; -function Card({ title, content, author, image, community, tags }) { - return ( -
- {image && ( - Blog image - )} - -
-
{title}
- -

- Author: {author || "AUTHOR"} -

- -
- {(tags || []).map((tag) => ( - - ))} -
+const Container = styled.div` + width: 100%; + height: 100%; + padding: 24px; + background: #fffefe; + border-radius: 16px; + overflow: hidden; + border: 1px rgba(129, 129, 129, 0.3) solid; + display: inline-flex; + flex-direction: column; + justify-content: flex-start; + align-items: flex-start; + gap: 24px; +`; + +const InfoContainer = styled.div` + padding-right: 16px; + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 16px; +`; + +const InfoText = styled.div` + color: ${(props) => props.color || "#818181"}; + font-size: 16px; + font-family: ${(props) => props.fontFamily || "Aeonik Fono"}; + font-weight: ${(props) => props.fontWeight || "400"}; + line-height: 20px; + word-wrap: break-word; +`; + +const TitleContainer = styled.div` + width: 344px; + padding-right: 16px; + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 8px; +`; + +const Title = styled.div` + width: 422px; + color: #151515; + font-size: 36px; + font-family: "Aeonik"; + font-weight: 700; + line-height: 39.6px; + word-wrap: break-word; +`; + +const DescriptionContainer = styled.div` + align-self: stretch; + height: 155px; + display: flex; + flex-direction: column; + justify-content: center; + align-items: flex-start; +`; -

Community: {community || "COMMUNITY"}

+const Description = styled.div` + align-self: stretch; + height: 103px; + padding-bottom: 16px; + display: flex; + flex-direction: column; + justify-content: flex-start; + align-items: center; + gap: 16px; +`; - - Read More - -
-
+const DescriptionText = styled.div` + align-self: stretch; + color: #151515; + font-size: 24px; + font-family: "Aeonik"; + font-weight: 400; + line-height: 28.8px; + word-wrap: break-word; +`; + +const TagsContainer = styled.div` + padding: 16px; + border-radius: 360px; + overflow: hidden; + display: inline-flex; + justify-content: flex-start; + align-items: center; + gap: 16px; +`; + +const Separator = styled.div` + color: #8a8e93; + font-size: 16px; + font-family: "Circular Std"; + font-weight: 400; + line-height: 19.2px; + word-wrap: break-word; +`; + +function Card({ labels, data }) { + const { title, subtitle, description, category, author, image, community, date } = data; + + function formatDate(date) { + const options = { + weekday: "short", + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + timeZoneName: "short", + }; + return date.toLocaleString("en-US", options).replace(",", ""); + } + + return ( + + + + {category && category.toUpperCase()} + + · + {date && formatDate(date)} + + + {title} + + + + {description} + + + {(labels || []).map((label, index) => ( +
+ {index > 0 && } + {label} +
+ ))} +
+
+
); } diff --git a/src/devhub/entity/addon/blog/Creator.jsx b/src/devhub/entity/addon/blog/Creator.jsx index 29d3e9b78..283def3f3 100644 --- a/src/devhub/entity/addon/blog/Creator.jsx +++ b/src/devhub/entity/addon/blog/Creator.jsx @@ -2,6 +2,17 @@ const { Card } = VM.require("${REPL_DEVHUB}/widget/devhub.entity.addon.blog.Card") || (() => <>); +const categories = [ + { + label: "Guide", + value: "guide", + }, + { + label: "News", + value: "news", + }, +]; + const Banner = styled.div` border-radius: var(--bs-border-radius-xl) !important; height: 100%; @@ -52,8 +63,12 @@ const initialData = data; // TODO: Check Storage API const [content, setContent] = useState(initialData.content || ""); const [title, setTitle] = useState(initialData.title || ""); +const [subtitle, setSubtitle] = useState(initialData.subtitle || ""); +const [description, setDescription] = useState(initialData.description || ""); const [author, setAuthor] = useState(initialData.author || ""); const [previewMode, setPreviewMode] = useState("card"); // "card" or "page" +const [date, setDate] = useState(new Date().toISOString().split("T")[0]); +const [category, setCategory] = useState("guide"); // Legacy State.init for IpfsUploader State.init({ @@ -89,6 +104,9 @@ const handlePublish = () => { post_type: "Comment", description: JSON.stringify({ title, + subtitle, + description, + date, content, author, image: state.image.cid, @@ -109,10 +127,13 @@ function Preview() { return ( ); @@ -123,10 +144,13 @@ function Preview() { src="${REPL_DEVHUB}/widget/devhub.entity.addon.blog.Page" props={{ title, + subtitle, + description, + date, content, author, image: state.image, - tags: data.includeLabels, + tags: data.includeLabels, // filter out "blog" and community handle? community: handle, }} /> @@ -192,8 +216,7 @@ return (
Title
setTitle(e.target.value), @@ -203,12 +226,62 @@ return ( />
+
+
Subtitle
+
+ setSubtitle(e.target.value), + value: subtitle, + placeholder: "Subtitle", + }} + /> +
+
+
+
Category
+
+ ({ + label: it.label, + value: it.value, + })), + }, + ], + rootProps: { + value: category, + placeholder: "Select a category", + onValueChange: (v) => setCategory(v), + }, + }} + /> +
+
+
+
Description
+
+ setDescription(e.target.value), + value: description, + placeholder: "Description", + }} + /> +
+
Author
setAuthor(e.target.value), @@ -225,6 +298,14 @@ return ( props={{ data: { content }, onChange: setContent }} />
+
+
Date
+ setDate(e.target.value)} + /> +
{}); + const { Layout, Item } = props; const Container = styled.div``; @@ -47,7 +50,7 @@ const query = `query DevhubPostsQuery($limit: Int = 100, $offset: Int = 0, $wher } `; -const [postIds, setPostIds] = useState([]); +// const [postIds, setPostIds] = useState([]); const [loading, setLoading] = useState(false); const [cachedItems, setCachedItems] = useState({}); const [hasNext, setHasNext] = useState(true); @@ -96,9 +99,9 @@ const fetchPostIds = (offset) => { ); }; -useEffect(() => { - fetchPostIds(); -}, [props.author, props.term, props.tag, props.recency]); +// useEffect(() => { +// fetchPostIds(); +// }, [props.author, props.term, props.tag, props.recency]); const handleLoadMore = () => { if (!hasNext) return; @@ -121,6 +124,8 @@ const cachedRenderItem = (postId) => { return cachedItems[postId]; }; +const postIds = Near.view("${REPL_DEVHUB_CONTRACT}", "get_children_ids"); + return ( {loading && renderLoader()} diff --git a/src/devhub/entity/addon/blog/Page.jsx b/src/devhub/entity/addon/blog/Page.jsx index cecb4d34c..b5fcf7470 100644 --- a/src/devhub/entity/addon/blog/Page.jsx +++ b/src/devhub/entity/addon/blog/Page.jsx @@ -2,37 +2,67 @@ const { title, content, author, image, community, tags } = props; const cidToURL = (cid) => `https://ipfs.near.social/ipfs/${cid}`; -return ( -
- {image && ( +function Page({ labels, data }) { + const { + title, + subtitle, + description, + category, + author, + image, + community, + date, + } = data; + + function formatDate(date) { + const options = { + weekday: "short", + year: "numeric", + month: "short", + day: "numeric", + hour: "2-digit", + minute: "2-digit", + second: "2-digit", + timeZoneName: "short", + }; + return date.toLocaleString("en-US", options).replace(",", ""); + } + + return ( +
+ {image && ( +
+ Blog Header Image +
+ )} +

{title}

+

+ Written by {author || "AUTHOR"} in{" "} + {community || "COMMUNITY"} +

- Blog Header Image + {(tags || []).map((tag) => ( + + ))}
- )} -

{title}

-

- Written by {author || "AUTHOR"} in{" "} - {community || "COMMUNITY"} -

-
- {(tags || []).map((tag) => ( +
- ))} -
-
- +
-
-); + ); +} + + +return { Page }; \ No newline at end of file diff --git a/src/devhub/entity/addon/blog/Viewer.jsx b/src/devhub/entity/addon/blog/Viewer.jsx index 0437c65ad..f6e050cec 100644 --- a/src/devhub/entity/addon/blog/Viewer.jsx +++ b/src/devhub/entity/addon/blog/Viewer.jsx @@ -1,6 +1,9 @@ const { Card } = VM.require("${REPL_DEVHUB}/widget/devhub.entity.addon.blog.Card") || (() => <>); + +const { href } = VM.require("${REPL_DEVHUB}/widget/core.lib.url") || (() => {}); + const { includeLabels, excludeLabels, layout } = props; const Grid = styled.div` @@ -15,10 +18,18 @@ const Grid = styled.div` function BlogCard(postId) { return ( - }} // I wonder if this could take list of types, their templates, normalizer functions, etc... and have this all as a module - /> // so then you could swap between devhub contract or social contract sources, it doesn't matter. + + }} // I wonder if this could take list of types, their templates, normalizer functions, etc... and have this all as a module + /> + {/* // so then you could swap between devhub contract or social contract sources, it doesn't matter. */} + ); } diff --git a/src/devhub/entity/post/Postv2.jsx b/src/devhub/entity/post/Postv2.jsx index c386c8258..63c890c61 100644 --- a/src/devhub/entity/post/Postv2.jsx +++ b/src/devhub/entity/post/Postv2.jsx @@ -4,7 +4,7 @@ const { getPost } = const { postKey, template } = props; -const post = getPost({ post_id: postKey }); +const post = getPost({ post_id: parseInt(postKey) }); if (!post) { return
Loading ...
; @@ -12,4 +12,4 @@ if (!post) { const Template = template || (() => <>); -return