diff --git a/apps/builddao/widget/Bookmarks.jsx b/apps/builddao/widget/Bookmarks.jsx deleted file mode 100644 index 04a0ff56..00000000 --- a/apps/builddao/widget/Bookmarks.jsx +++ /dev/null @@ -1,70 +0,0 @@ -const { Post } = VM.require("buildhub.near/widget/components") || (() => <>); - -const accountId = props.accountId ?? context.accountId; - -const bookmarks = Social.getr(`${accountId}/graph/bookmark`, "final", { - withBlockHeight: true, -}); - -const StorageKey = "order"; -const order = Storage.privateGet(StorageKey); -const apps = useMemo(() => { - if (bookmarks === null || order === null) { - return []; - } - const starredApps = new Map(); - const path = []; - - const buildSrc = (node) => { - if (node.hasOwnProperty("")) { - starredApps.set(path.join("/"), node[":block"]); - } - Object.entries(node).forEach(([key, value]) => { - if (typeof value === "object") { - path.push(key); - buildSrc(value); - path.pop(); - } - }); - }; - - buildSrc(bookmarks ?? {}, [], starredApps); - let apps = [...starredApps.entries()]; - apps.sort((a, b) => b[1] - a[1]); - apps = apps.map((a) => a[0]); - apps.sort((a, b) => (order?.[a] || 0) - (order?.[b] || 0)); - Storage.privateSet( - StorageKey, - Object.fromEntries(apps.map((a, i) => [a, i + 1])) - ); - return apps; -}, [bookmarks, order]); - -let transformedArray = apps.map((item) => { - let splitParts = item.split("/"); - let accountId = splitParts[0]; - let lastPart = splitParts[splitParts.length - 1]; - let blockHeight = isNaN(lastPart) ? null : parseInt(lastPart); - - return { accountId, blockHeight }; -}); - -let filteredArray = transformedArray.filter( - (item) => item.blockHeight !== null -); - -return ( - <> - {(filteredArray ?? []).map((item) => ( - - ))} - {filteredArray.length === 0 && ( -

No Bookmarks Yet!

- )} - -); diff --git a/apps/builddao/widget/Compose.jsx b/apps/builddao/widget/Compose.jsx index 306fa0e4..6cf5406d 100644 --- a/apps/builddao/widget/Compose.jsx +++ b/apps/builddao/widget/Compose.jsx @@ -3,27 +3,17 @@ const { Avatar, Button } = VM.require("buildhub.near/widget/components") || { Button: () => <>, }; -const draftKey = props.feed.name || "draft"; +const draftKey = props.draftKey || "draft"; const draft = Storage.privateGet(draftKey); -const autocompleteEnabled = true; - if (draft === null) { return ""; } -State.init({ - image: {}, -}); - const [view, setView] = useState("editor"); const [postContent, setPostContent] = useState(""); const [hideAdvanced, setHideAdvanced] = useState(true); const [labels, setLabels] = useState([]); -const [showAccountAutocomplete, setShowAccountAutocomplete] = useState(false); -const [mentionsArray, setMentionsArray] = useState([]); -const [mentionInput, setMentionInput] = useState(null); -const [handler, setHandler] = useState("update"); setPostContent(draft || props.template); @@ -33,6 +23,16 @@ function generateUID() { return randomNumber.toString(16).padStart(8, "0"); } +function tagsFromLabels(labels) { + return labels.reduce( + (newLabels, label) => ({ + ...newLabels, + [label]: "", + }), + {} + ); +} + const extractMentions = (text) => { const mentionRegex = /@((?:(?:[a-z\d]+[-_])*[a-z\d]+\.)*(?:[a-z\d]+[-_])*[a-z\d]+)/gi; @@ -86,28 +86,19 @@ function checkAndAppendHashtag(input, target) { } const postToCustomFeed = ({ feed, text, labels }) => { - const postId = generateUID(); - if (!labels) labels = []; + // if (!labels) labels = []; - labels = labels.map((label) => label.toLowerCase()); - labels.push(feed.name.toLowerCase()); + // labels = labels.map((label) => label.toLowerCase()); + // labels.push(feed.name.toLowerCase()); - const requiredHashtags = ["build"]; + const requiredHashtags = props.requiredHashtags || ["build"]; if (feed.hashtag) requiredHashtags.push(feed.hashtag.toLowerCase()); requiredHashtags.push(feed.name.toLowerCase()); - text = text + `\n\n`; - requiredHashtags.forEach((hashtag) => { text = checkAndAppendHashtag(text, hashtag); }); - const content = { - type: "md", - image: state.image.cid ? { ipfs_cid: state.image.cid } : undefined, - text: text, - }; - const data = { // [feed.name]: { // [postId]: { @@ -123,7 +114,12 @@ const postToCustomFeed = ({ feed, text, labels }) => { // }, // }, post: { - main: JSON.stringify(content), + main: JSON.stringify({ + type: "md", + text, + // tags: tagsFromLabels(labels), + // postType: feed.name, + }), }, index: { post: JSON.stringify({ key: "main", value: { type: "md" } }), @@ -168,98 +164,16 @@ const postToCustomFeed = ({ feed, text, labels }) => { }); }; -function textareaInputHandler(value) { - const words = value.split(/\s+/); - const allMentiones = words - .filter((word) => word.startsWith("@")) - .map((mention) => mention.slice(1)); - const newMentiones = allMentiones.filter( - (item) => !mentionsArray.includes(item) - ); - setMentionInput(newMentiones?.[0] ?? ""); - setMentionsArray(allMentiones); - setShowAccountAutocomplete(newMentiones?.length > 0); - setPostContent(value); - setHandler("update"); - Storage.privateSet(draftKey, value || ""); -} - -function autoCompleteAccountId(id) { - let currentIndex = 0; - const updatedDescription = postContent.replace( - /(?:^|\s)(@[^\s]*)/g, - (match) => { - if (currentIndex === mentionsArray.indexOf(mentionInput)) { - currentIndex++; - return ` @${id}`; - } else { - currentIndex++; - return match; - } - } - ); - setPostContent(updatedDescription); - setShowAccountAutocomplete(false); - setMentionInput(null); - setHandler("autocompleteSelected"); - Storage.privateSet(draftKey, updatedDescription || ""); -} - const PostCreator = styled.div` display: flex; flex-direction: column; gap: 1.5rem; padding: 1rem; - background: #23242b; + background: var(--compose-bg, #23242b); border-radius: 12px; margin-bottom: 1rem; - - .upload-image-button { - display: flex; - align-items: center; - justify-content: center; - background: #f1f3f5; - color: #11181c; - border-radius: 40px; - height: 40px; - min-width: 40px; - font-size: 0; - border: none; - cursor: pointer; - transition: background 200ms, opacity 200ms; - - &::before { - font-size: 16px; - } - - &:hover, - &:focus { - background: #d7dbde; - outline: none; - } - - &:disabled { - opacity: 0.5; - pointer-events: none; - } - - span { - margin-left: 12px; - } - } - - .d-inline-block { - display: flex !important; - gap: 12px; - margin: 0 !important; - - .overflow-hidden { - width: 40px !important; - height: 40px !important; - } - } `; const TextareaWrapper = styled.div` @@ -458,36 +372,6 @@ const MarkdownPreview = styled.div` } `; -const LabelSelect = styled.div` - label { - color: #fff; - } - - .rbt-input-multi { - background: #23242b !important; - color: #fff !important; - } - - .rbt-token { - background: #202020 !important; - color: #fff !important; - } - - .rbt-menu { - background: #23242b !important; - color: #fff !important; - - .dropdown-item { - color: #fff !important; - transition: all 300ms; - - &:hover { - background: #202020; - } - } - } -`; - const avatarComponent = useMemo(() => { return (
@@ -507,29 +391,19 @@ return ( { - textareaInputHandler(content); + embedCss: props.customCSS || MarkdownEditor, + onChange: (v) => { + setPostContent(v); + Storage.privateSet(draftKey, v || ""); }, - embedCss: MarkdownEditor, }} /> - {autocompleteEnabled && showAccountAutocomplete && ( - setShowAccountAutocomplete(false), - }} - /> - )} ) : ( @@ -537,27 +411,11 @@ return ( src="devhub.near/widget/devhub.components.molecule.MarkdownViewer" props={{ text: postContent }} /> - {state.image.cid && ( - - )} )}
- {view === "editor" && ( - - )}
); diff --git a/apps/builddao/widget/JoinSection.jsx b/apps/builddao/widget/JoinSection.jsx index f1827e04..edece143 100644 --- a/apps/builddao/widget/JoinSection.jsx +++ b/apps/builddao/widget/JoinSection.jsx @@ -164,7 +164,7 @@ const proposalId = Near.view(daoId, "get_last_proposal_id") - 1; // get data from last proposal const proposal = Near.view(daoId, "get_proposal", { - id: proposalId + id: proposalId, }); if (proposal === null) { @@ -197,7 +197,7 @@ const CreateSomethingView = (props) => { {userWidgets.length === 0 ? ( <>

In order to join Build DAO, you need to create a widget

- + Create Something{" "} { fill="black" /> - + ) : ( { ), className: "custom-button", - showActivity: true + showActivity: true, }} /> )}{" "} diff --git a/apps/builddao/widget/OrderedGraphFeed.jsx b/apps/builddao/widget/OrderedGraphFeed.jsx new file mode 100644 index 00000000..a5308554 --- /dev/null +++ b/apps/builddao/widget/OrderedGraphFeed.jsx @@ -0,0 +1,67 @@ +const accountId = props.accountId ?? context.accountId; +const itemType = props.itemType; +const renderItem = props.renderItem; + +if (!itemType) { + return

No graph item type passed.

; +} + +const items = Social.getr(`${accountId}/graph/${itemType}`, "final", { + withBlockHeight: true, +}); + +const StorageKey = "order"; +const order = Storage.privateGet(StorageKey); +const graphItems = useMemo(() => { + if (items === null || order === null) { + return []; + } + const itemMap = new Map(); + const path = []; + + const buildSrc = (node) => { + if (node.hasOwnProperty("")) { + itemMap.set(path.join("/"), node[":block"]); + } + Object.entries(node).forEach(([key, value]) => { + if (typeof value === "object") { + path.push(key); + buildSrc(value); + path.pop(); + } + }); + }; + + buildSrc(items ?? {}, [], itemMap); + let entries = [...itemMap.entries()]; + entries.sort((a, b) => b[1] - a[1]); + entries = entries.map((a) => a[0]); + entries.sort((a, b) => (order?.[a] || 0) - (order?.[b] || 0)); + Storage.privateSet( + StorageKey, + Object.fromEntries(entries.map((a, i) => [a, i + 1])) + ); + return entries; +}, [items, order]); + +let transformedArray = graphItems.map((item) => { + let splitParts = item.split("/"); + let accountId = splitParts[0]; + let lastPart = splitParts[splitParts.length - 1]; + let blockHeight = isNaN(lastPart) ? null : parseInt(lastPart); + + return { accountId, blockHeight }; +}); + +let filteredArray = transformedArray.filter( + (item) => item.blockHeight !== null +); + +return ( + <> + {(filteredArray ?? []).map((item) => renderItem(item))} + {filteredArray.length === 0 && ( +

No {itemType}s!

+ )} + +); diff --git a/apps/builddao/widget/Post.jsx b/apps/builddao/widget/Post.jsx deleted file mode 100644 index 94901be7..00000000 --- a/apps/builddao/widget/Post.jsx +++ /dev/null @@ -1,372 +0,0 @@ -const accountId = props.accountId; -if (!accountId) { - return "No accountId"; -} -const blockHeight = - props.blockHeight === "now" ? "now" : parseInt(props.blockHeight); -const pinned = !!props.pinned; -const hideMenu = !!props.hideMenu; -const hideButtons = !!props.hideButtons; -const content = - props.content ?? - JSON.parse(Social.get(`${accountId}/post/main`, blockHeight) ?? "null"); -const subscribe = !!props.subscribe; -const raw = !!props.raw; -const groupId = props.groupId ?? content.groupId; -const indexKey = props.indexKey; -const permissions = props.permissions; -const fullPostLink = props.fullPostLink; -const postPage = props.postPage ?? "mob.near/widget/MainPage.N.Post.Page"; - -const notifyAccountId = accountId; -const item = { - type: "social", - path: `${accountId}/post/main`, - blockHeight, -}; - -const link = - props.link ?? - props.fullPostLink ?? - `/${postPage}?accountId=${accountId}&blockHeight=${blockHeight}`; - -const StyledPost = styled.div` - margin-bottom: 1rem; - .post { - border-radius: 16px; - border: 1px solid var(--Stroke-color, rgba(255, 255, 255, 0.2)); - color: #b6b6b8; - padding: 24px !important; - background-color: #0b0c14; - transition: all 300ms; - - &:hover { - background-color: #171929 !important; - .expand-post { - background-image: linear-gradient( - to bottom, - rgb(23, 25, 41, 0), - rgb(23, 25, 41, 1) 25% - ) !important; - } - } - - .post-header { - span, - .text-muted { - color: #fff !important; - } - } - - .buttons { - border-top: 1px solid #3c3d43; - padding: 0.5rem; - } - - .expand-post { - background-image: linear-gradient( - to bottom, - rgb(11, 12, 20, 0), - rgb(11, 12, 20, 1) 25% - ) !important; - } - } - - .dropdown-menu { - background-color: #0b0c14 !important; - color: #fff !important; - - li.dropdown-item { - color: #fff !important; - &:hover { - a { - color: #0b0c14 !important; - } - } - } - - .link-dark, - .dropdown-item { - color: #fff !important; - - &:hover { - color: #0b0c14 !important; - - span { - color: #0b0c14 !important; - } - } - } - } - - textarea { - color: #b6b6b8 !important; - } -`; - -const Wrapper = styled.div` - margin: 0 -12px; - line-height: normal; - - .post { - position: relative; - padding: 12px; - padding-bottom: 4px; - display: flex; - h1, - h2, - h3, - h4, - h5, - h6 { - font-size: 16px !important; - } - @media (max-width: 767px) { - font-size: 15px !important; - h1, - h2, - h3, - h4, - h5, - h6 { - font-size: 15px !important; - } - } - - h1, - h2, - h3, - h4, - h5, - h6, - strong, - b { - font-weight: 500 !important; - } - ol, - ul, - dl { - margin-bottom: 0.5rem; - white-space: inherit; - } - p { - margin-bottom: 0.5rem; - } - hr { - display: none; - } - img { - border-radius: var(--bs-border-radius-lg); - max-height: 40em; - } - th { - min-width: 5em; - } - - .table > :not(caption) > * > * { - padding: 0.3rem; - } - - &:hover { - background-color: rgba(0, 0, 0, 0.03); - .expand-post { - background-image: linear-gradient( - to bottom, - rgba(0, 0, 0, 0), - rgba(247.35, 247.35, 247.35, 1) 25% - ); - } - } - - .post-header { - margin: 4px 0; - } - } - - .post:not(:last-child):before { - content: ""; - position: absolute; - left: 30px; - top: 56px; - bottom: 0; - width: 2px; - background-color: #ddd; - z-index: -1; - } - - .post:not(:first-child):after { - content: ""; - position: absolute; - left: 30px; - top: 0; - width: 2px; - height: 8px; - background-color: #ddd; - z-index: -1; - } - - .left { - margin-right: 12px; - min-width: 40px; - width: 40px; - overflow: hidden; - } - .right { - margin-top: -4px; - flex-grow: 1; - min-width: 0; - } - - .buttons-placeholder { - padding-bottom: 10px; - } - - .buttons { - margin-top: 10px; - margin-bottom: 6px; - column-gap: 4px; - color: #888; - } - - .reposted { - padding-top: 30px; - } -`; - -const contentWidget = ( - - } - src="mob.near/widget/MainPage.N.Post.Content" - props={{ - content, - raw, - truncateContent: props.truncateContent, - noEmbed: props.noEmbed, - }} - /> -); - -return ( - - -
-
- } - src="/*__@appAccount__*//widget/Post.Header" - props={{ - accountId, - blockHeight, - pinned, - hideMenu, - link, - postType: "post", - flagItem: item, - }} - /> - {fullPostLink ? ( - - {contentWidget} - - ) : ( - contentWidget - )} - {props.customButtons ? ( - props.customButtons - ) : !pinned && !hideButtons && blockHeight !== "now" ? ( -
- State.update({ showReply: !state.showReply }), - }} - /> - - - -
- ) : ( -
- )} -
-
- {state.showReply && ( -
- State.update({ showReply: false }), - }} - /> -
- )} - {props.customComments - ? props.customComments - : !props.hideComments && ( -
- -
- )} - - -); diff --git a/apps/builddao/widget/Post/Header.jsx b/apps/builddao/widget/Post/Header.jsx deleted file mode 100644 index d08ddf0b..00000000 --- a/apps/builddao/widget/Post/Header.jsx +++ /dev/null @@ -1,169 +0,0 @@ -const accountId = props.accountId; -const blockHeight = props.blockHeight; -const pinned = !!props.pinned; -const hideMenu = !!props.hideMenu; -const name = Social.get(`${accountId}/profile/name`); - -const postType = props.postType ?? "post"; -const link = props.link; -const isPremium = !!props.isPremium; - -const Overlay = (props) => ( - - - -); - -const DotsSvg = ( - - - -); - -const Button = styled.div` - line-height: 20px; - min-height: 20px; - display: inline-flex; - align-items: center; - justify-content: left; - background: inherit; - color: #6c757d; - font-size: 16px; - .icon { - position: relative; - &:before { - margin: -8px; - content: ""; - position: absolute; - top: 0; - left: 0; - bottom: 0; - right: 0; - border-radius: 50%; - } - } - - &:not([disabled]) { - cursor: pointer; - } - - &:not([disabled]):hover { - opacity: 1 !important; - color: DeepSkyBlue; - - .icon:before { - background: rgba(0, 191, 255, 0.1); - } - } -`; - -return ( -
-
-
-
- -
-
-
- {name && ( - -
{name}
-
- )} -
- -
-
-
-
- -
@{accountId}
-
-
- {!pinned && ( -
- {blockHeight === "now" ? ( - "now" - ) : ( - - - - )} -
- )} -
-
-
-
- {pinned && ( - - - - )} - {!pinned && !hideMenu && blockHeight !== "now" && ( - - - - - )} -
-); diff --git a/apps/builddao/widget/Proposals.jsx b/apps/builddao/widget/Proposals.jsx new file mode 100644 index 00000000..081047fc --- /dev/null +++ b/apps/builddao/widget/Proposals.jsx @@ -0,0 +1,236 @@ +const { Button } = VM.require("buildhub.near/widget/components.Button") || { + Button: <> +}; +const DaoSDK = VM.require("sdks.near/widget/SDKs.Sputnik.DaoSDK"); + +if (!DaoSDK) { + return <>; +} +const resPerPage = 10; +const daoId = props.daoId ?? "build.sputnik-dao.near"; +const proposalId = props.proposalId ?? null; +const sdk = DaoSDK(daoId); +const [currentPage, setCurrentPage] = useState(0); +const accountId = context.accountId; + +const [showProposalModal, setShowModal] = useState(false); +const lastProposalId = sdk.getLastProposalId(); +const reversedProposals = proposalId + ? [ + sdk.getProposalById({ + proposalId + }) + ] || [] + : sdk.getProposals({ + offset: + currentPage === 0 + ? lastProposalId > 10 + ? lastProposalId - resPerPage + : lastProposalId ?? 10 + : lastProposalId - currentPage * resPerPage, + limit: resPerPage + }) || []; + +const proposals = reversedProposals.reverse(); +const PaginationThemeContainer = props.PaginationThemeContainer; + +const ThemeContainer = + props.ThemeContainer || + styled.div` + --primary-bg-color: #23242b; + --secondary-bg-color: #ffffff1a; + --primary-border-color: #fff; + --primary-text-color: #ffffff; + --secondary-text-color: #b0b0b0; + --primary-btn-bg-color: #ffaf51; + --primary-btn-text-color: #000; + --approve-bg-color: #82e299; + --reject-bg-color: #c23f38; + --spam-bg-color: #f5c518; + --vote-button-color: #ffffff; + --success-badge-bg-color: #38c7931a; + --success-badge-text-color: #38c793; + --primary-badge-bg-color: #ffaf5133; + --primary-badge-text-color: #ffaf51; + --info-badge-bg-color: #51b6ff33; + --info-badge-text-color: #51b6ff; + --danger-badge-bg-color: #fd2a5c1a; + --danger-badge-text-color: #fd2a5c; + --black-badge-bg-color: #ffffff1a; + --black-badge-text-color: #fff; + `; + +const Container = styled.div` + .ndc-card { + border: none; + background-color: var(--primary-bg-color); + color: var(--primary-text-color) !important; + padding: 2rem; + } +`; + +const handleVote = ({ action, proposalId, proposer }) => { + const customAction = action.replace("Vote", ""); + const notification = { + [accountId]: { + index: { + notify: JSON.stringify([ + { + key: proposer, + value: { + message: `${accountId} voted to ${customAction} your proposal for ${daoId} (Proposal ID: ${proposalId})`, + params: { + daoId: daoId, + proposalId: proposalId + }, + type: "custom", + widget: "buildhub.near/widget/Proposals" + } + } + ]) + } + } + }; + + sdk.actProposal({ + proposalId, + action, + gas: 200000000000000, + additionalCalls: [ + { + contractName: "social.near", + methodName: "set", + args: { data: notification }, + deposit: Big(JSON.stringify(notification).length * 16) + .mul(Big(10).pow(20)) + .toString() + } + ] + }); +}; + +const policy = sdk.getPolicy(); +const proposalKinds = sdk.proposalKinds; +const actions = sdk.voteActions; +const userRoles = []; +if (Array.isArray(policy.roles)) { + for (const role of policy.roles) { + if (role.kind === "Everyone") { + userRoles.push(role); + continue; + } + if (!role.kind.Group) continue; + if (accountId && role.kind.Group && role.kind.Group.includes(accountId)) { + userRoles.push(role); + } + } +} + +const proposalPeriod = policy.proposal_period; + +const proposalsComponent = useMemo(() => { + return ( +
+ {Array.isArray(proposals) ? ( + proposals.map((item) => { + const kindName = + typeof item.kind === "string" + ? item.kind + : Object.keys(item.kind)[0]; + + const comments = sdk.getCommentsByProposalId({ proposalId: item.id }); + + const isAllowedToVote = [ + sdk.hasPermission({ + accountId, + kindName, + actionType: actions.VoteApprove + }), + sdk.hasPermission({ + accountId, + kindName, + actionType: actions.VoteReject + }), + + sdk.hasPermission({ + accountId, + kindName, + actionType: actions.VoteRemove + }) + ]; + + const { thresholdVoteCount } = + sdk.getVotersAndThresholdForProposalKind({ + kindName + }); + const totalVotes = sdk.calculateVoteCountByType({ + votes: item.votes + }); + let expirationTime = sdk.getProposalExpirationTime({ + submissionTime: item.submission_time + }); + + return ( + + ); + }) + ) : ( + <> + )} +
+ ); +}, [proposals]); + +return ( + + + setShowModal(!showProposalModal) + }} + /> +
+

Proposals

+ +
+
{proposalsComponent}
+ {!proposalId && ( +
+ setCurrentPage(v), + selectedPage: currentPage, + ThemeContainer: PaginationThemeContainer + }} + /> +
+ )} +
+
+); diff --git a/apps/builddao/widget/app.jsx b/apps/builddao/widget/app.jsx new file mode 100644 index 00000000..6cf10cae --- /dev/null +++ b/apps/builddao/widget/app.jsx @@ -0,0 +1,102 @@ +const { page, tab, ...passProps } = props; + +const { routes } = VM.require("buildhub.near/widget/config.routes") ?? { + routes: {}, +}; + +const { AppLayout } = VM.require("buildhub.near/widget/template.AppLayout") || { + AppLayout: () => <>Layout loading..., +}; + +if (!page) page = Object.keys(routes)[0] || "home"; + +const Root = styled.div` + --stroke-color: rgba(255, 255, 255, 0.2); + --bg-1: #0b0c14; + --bg-1-hover: #17181c; + --bg-1-hover-transparent: rgba(23, 24, 28, 0); + --bg-2: ##23242b; + --label-color: #fff; + --font-color: #fff; + --font-muted-color: #cdd0d5; + --black: #000; + --system-red: #fd2a5c; + --yellow: #ffaf51; + + --compose-bg: #23242b; + + --post-bg: #0b0c14; + --post-bg-hover: #17181c; + --post-bg-transparent: rgba(23, 24, 28, 0); + + --button-primary-bg: #ffaf51; + --button-outline-bg: transparent; + --button-default-bg: #23242b; + + --button-primary-color: #000; + --button-outline-color: #cdd0d5; + --button-default-color: #cdd0d5; + + --button-primary-hover-bg: #e49b48; + --button-outline-hover-bg: rgba(255, 255, 255, 0.2); + --button-default-hover-bg: #17181c; +`; + +function Router({ active, routes }) { + // this may be converted to a module at devs.near/widget/Router + const routeParts = active.split("."); + + let currentRoute = routes; + let src = ""; + let defaultProps = {}; + + for (let part of routeParts) { + if (currentRoute[part]) { + currentRoute = currentRoute[part]; + src = currentRoute.path; + + if (currentRoute.init) { + defaultProps = { ...defaultProps, ...currentRoute.init }; + } + } else { + // Handle 404 or default case for unknown routes + return

404 Not Found

; + } + } + + return ( +
+ +
+ ); +} + +const Container = styled.div` + display: flex; + height: 100%; +`; + +const Content = styled.div` + width: 100%; + height: 100%; +`; + +return ( + + + + + + + + + +); diff --git a/apps/builddao/widget/components.jsx b/apps/builddao/widget/components.jsx index b73c4264..65f404f0 100644 --- a/apps/builddao/widget/components.jsx +++ b/apps/builddao/widget/components.jsx @@ -11,12 +11,14 @@ const { TextBox } = VM.require("buildhub.near/widget/components.TextBox"); const { TextEditor } = VM.require("buildhub.near/widget/components.TextEditor"); const { Checkbox } = VM.require("buildhub.near/widget/components.Checkbox"); const { Avatar } = VM.require("buildhub.near/widget/components.Avatar"); +const { Modal } = VM.require("buildhub.near/widget/components.Modal"); function Pagination({ totalPages, maxVisiblePages, onPageClick, selectedPage, + ThemeContainer, }) { return ( ); @@ -34,8 +37,8 @@ function Pagination({ function Post(props) { return ( } + src={"buildhub.near/widget/components.Post"} props={{ ...props }} /> ); @@ -43,7 +46,11 @@ function Post(props) { function User(props) { return ( - + } + src="buildhub.near/widget/components.User" + props={{ ...props }} + /> ); } @@ -52,6 +59,7 @@ return { Pagination, Post, ProgressState, + Modal, Step, InputField, UploadField, diff --git a/apps/builddao/widget/components/AccountAutocomplete.jsx b/apps/builddao/widget/components/AccountAutocomplete.jsx deleted file mode 100644 index 5823f397..00000000 --- a/apps/builddao/widget/components/AccountAutocomplete.jsx +++ /dev/null @@ -1,147 +0,0 @@ -if (!context.accountId || !props.term) return <>; - -let results = []; -const filterAccounts = props.filterAccounts ?? []; // hide certain accounts from the list -const profilesData = Social.get("*/profile/name", "final") || {}; -const followingData = Social.get( - `${context.accountId}/graph/follow/**`, - "final" -); -if (!profilesData) return <>; -const profiles = Object.entries(profilesData); -const term = (props.term || "").replace(/\W/g, "").toLowerCase(); -const limit = 5; - -for (let i = 0; i < profiles.length; i++) { - let score = 0; - const accountId = profiles[i][0]; - const accountIdSearch = profiles[i][0].replace(/\W/g, "").toLowerCase(); - const nameSearch = (profiles[i][1]?.profile?.name || "") - .replace(/\W/g, "") - .toLowerCase(); - const accountIdSearchIndex = accountIdSearch.indexOf(term); - const nameSearchIndex = nameSearch.indexOf(term); - - if (accountIdSearchIndex > -1 || nameSearchIndex > -1) { - score += 10; - - if (accountIdSearchIndex === 0) { - score += 10; - } - if (nameSearchIndex === 0) { - score += 10; - } - if (followingData[accountId] === "") { - score += 30; - } - - results.push({ - accountId, - score - }); - } -} - -results.sort((a, b) => b.score - a.score); -results = results.slice(0, limit); -if (filterAccounts?.length > 0) { - results = results.filter((item) => !filterAccounts?.includes(item.accountId)); -} - -function onResultClick(id) { - props.onSelect && props.onSelect(id); -} - -const Wrapper = styled.div` - position: relative; - - &::before { - content: ""; - display: block; - position: absolute; - right: 0; - width: 6px; - height: 100%; - z-index: 10; - } -`; - -const Scroller = styled.div` - position: relative; - display: flex; - padding: 6px; - gap: 6px; - overflow: auto; - scroll-behavior: smooth; - align-items: center; - scrollbar-width: none; - -ms-overflow-style: none; - &::-webkit-scrollbar { - display: none; - } - - > * { - max-width: 175px; - flex-grow: 0; - flex-shrink: 0; - - button { - border: 1px solid #eceef0; - background: #fff !important; - border-radius: 6px; - padding: 3px 6px; - transition: all 200ms; - - &:focus, - &:hover { - border-color: #687076; - } - } - } -`; - -const CloseButton = styled.button` - background: none; - border: none; - display: block; - padding: 12px; - color white; - transition: all 200ms; - - &:hover { - transform:scale(1.2); - } -`; - -const ProfileCardWrapper = styled.div` - opacity: 0.8; -`; - -if (results.length === 0) return <>; - -return ( - - - - - - - {results.map((result) => { - return ( - - - - ); - })} - - -); diff --git a/apps/builddao/widget/components/AsideWithMainContent.jsx b/apps/builddao/widget/components/AsideWithMainContent.jsx deleted file mode 100644 index c1abfd8a..00000000 --- a/apps/builddao/widget/components/AsideWithMainContent.jsx +++ /dev/null @@ -1,50 +0,0 @@ -const { routes, active, setActiveRoute, mainContent, sideContent } = props; - -const Container = styled.div` - display: grid; - grid-template-columns: repeat(5, minmax(0, 1fr)); - gap: 1rem; - - @media screen and (max-width: 768px) { - display: flex; - flex-direction: column; - } -`; - -const AsideContainer = styled.div` - border-radius: 16px; - border: 1px solid var(--Stroke-color, rgba(255, 255, 255, 0.2)); - background: var(--bg-1, #0b0c14); - width: 100%; - - display: flex; - padding: 24px 12px; - flex-direction: column; - align-items: flex-start; - gap: 16px; - margin-bottom: 1rem; - height: calc(min(100vh - 64px, 100%)); - - @media screen and (max-width: 768px) { - border: 0px; - flex-direction: row; - overflow-x: auto; - } -`; - -const Aside = styled.div` - grid-column: span 1 / span 1; -`; - -const MainContentContainer = styled.div` - grid-column: span 4 / span 4; -`; - -return ( - - - {mainContent} - -); diff --git a/apps/builddao/widget/components/Avatar.jsx b/apps/builddao/widget/components/Avatar.jsx index 536eb972..94f7afb3 100644 --- a/apps/builddao/widget/components/Avatar.jsx +++ b/apps/builddao/widget/components/Avatar.jsx @@ -1,35 +1,32 @@ - function Avatar(props) { const accountId = props.accountId ?? context.accountId; const ImageWrapper = styled.div` - img { - width: ${(props) => - props.variant === "mobile" ? "40px" : "52px"} !important; - height: ${(props) => - props.variant === "mobile" ? "40px" : "52px"} !important; - flex-shrink: 0 !important; - border-radius: 100px !important; - background: lightgray 50% / cover no-repeat !important; - } + img { + width: ${(props) => + props.variant === "mobile" ? "40px" : "52px"} !important; + height: ${(props) => + props.variant === "mobile" ? "40px" : "52px"} !important; + flex-shrink: 0 !important; + border-radius: 100px !important; + } - .profile-image { - width: auto !important; - height: auto !important; - } + .profile-image { + width: auto !important; + height: auto !important; + } - @media screen and (max-width: 768px) { - ${(props) => - !props.variant && - ` + @media screen and (max-width: 768px) { + ${(props) => + !props.variant && + ` img { width: 40px !important; height: 40px !important; } `} - } -`; - + } + `; return ( @@ -38,4 +35,4 @@ function Avatar(props) { ); } -return { Avatar }; \ No newline at end of file +return { Avatar }; diff --git a/apps/builddao/widget/components/Button.jsx b/apps/builddao/widget/components/Button.jsx index 885bfd4d..f20a4e0b 100644 --- a/apps/builddao/widget/components/Button.jsx +++ b/apps/builddao/widget/components/Button.jsx @@ -24,47 +24,88 @@ const StyledButton = styled.button` background: ${(props) => { switch (props.variant) { case "primary": - return "#FFAF51"; + return "var(--button-primary-bg, #FFAF51)"; case "outline": - return "transparent"; + return "var(--button-outline-bg, transparent)"; default: - return "#23242B"; + return "var(--button-default-bg, #23242B)"; } }}; color: ${(props) => { switch (props.variant) { case "primary": - return "#000"; + return "var(--button-primary-color, #000)"; case "outline": - return "#fff"; + return "var(--button-outline-color, #fff)"; default: - return "#CDD0D5"; + return "var(--button-default-color, #CDD0D5)"; } }}; border: ${(props) => - props.variant === "outline" ? "1px solid rgba(255, 255, 255, 0.20)" : ""}; + props.variant === "outline" + ? "1px solid var(--stroke-color, rgba(255, 255, 255, 0.20))" + : ""}; /* Hover states */ &:hover { background: ${(props) => { switch (props.variant) { case "primary": - return "#e49b48"; + return "var(--button-primary-hover-bg, #e49b48)"; case "outline": - return "rgba(255, 255, 255, 0.20)"; + return "var(--button-outline-hover-bg, rgba(255, 255, 255, 0.20))"; default: - return "#17181c"; + return "var(--button-default-hover-bg, #17181c)"; } }}; } + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } `; -function Button({ id, children, variant, type, onClick, className, style }) { +function Button({ + id, + disabled, + children, + variant, + type, + onClick, + className, + linkClassName, + href, + style, +}) { + if (href) { + return ( + + + {children} + + + ); + } + return ( - props.type === "icon" && - ` - display: flex; - width: 40px; - height: 40px; - padding: 5px; - flex-shrink: 0; - border-radius: 50%; - `} - - /* Colors based on variant prop */ - background: ${(props) => { - switch (props.variant) { - case "primary": - return "#FFAF51"; - case "outline": - return "transparent"; - default: - return "#23242B"; - } - }}; - - color: ${(props) => { - switch (props.variant) { - case "primary": - return "#000"; - case "outline": - return "#fff"; - default: - return "#CDD0D5"; - } - }}; - - border: ${(props) => - props.variant === "outline" ? "1px solid rgba(255, 255, 255, 0.20)" : ""}; - - /* Hover states */ - &:hover { - background: ${(props) => { - switch (props.variant) { - case "primary": - return "#e49b48"; - case "outline": - return "rgba(255, 255, 255, 0.20)"; - default: - return "#17181c"; - } - }}; - } -`; - -function ButtonLink({ id, children, variant, type, href, className, style }) { - return ( - - {children} - - ); -} - -return { ButtonLink }; diff --git a/apps/builddao/widget/components/Checkbox.jsx b/apps/builddao/widget/components/Checkbox.jsx index 27387079..97aec9e4 100644 --- a/apps/builddao/widget/components/Checkbox.jsx +++ b/apps/builddao/widget/components/Checkbox.jsx @@ -8,7 +8,7 @@ const CheckboxLabel = styled.label` align-items: center; gap: 8px; - color: #fff; + color: var(--label-color, #fff); font-size: 16px; font-style: normal; font-weight: 400; @@ -31,4 +31,4 @@ function Checkbox({ value, onChange, label }) { ); } -return { Checkbox }; \ No newline at end of file +return { Checkbox }; diff --git a/apps/builddao/widget/components/InputField.jsx b/apps/builddao/widget/components/InputField.jsx index ab6fcff0..deacecd2 100644 --- a/apps/builddao/widget/components/InputField.jsx +++ b/apps/builddao/widget/components/InputField.jsx @@ -6,7 +6,7 @@ const InputContainer = styled.div` `; const Label = styled.label` - color: var(--White-100, #fff); + color: var(--label-color, #fff); /* Body/16px */ font-size: 16px; @@ -24,12 +24,12 @@ const Input = styled.input` gap: 10px; border-radius: 8px; - border: 1px solid var(--Stroke-color, rgba(255, 255, 255, 0.2)); - background: var(--Bg-1, #0b0c14); + border: 1px solid var(--stroke-color, rgba(255, 255, 255, 0.2)); + background: var(--bg-1, #0b0c14); flex: 1 0 0; - color: var(--White-50, #cdd0d5); + color: var(--font-muted-color, #cdd0d5); /* Body/16px */ font-size: 16px; @@ -62,4 +62,4 @@ function InputField({ ); } -return { InputField }; \ No newline at end of file +return { InputField }; diff --git a/apps/builddao/widget/components/Library.jsx b/apps/builddao/widget/components/Library.jsx index fc5ff7c4..e626315d 100644 --- a/apps/builddao/widget/components/Library.jsx +++ b/apps/builddao/widget/components/Library.jsx @@ -147,12 +147,12 @@ const components = [ }, preview: ( <> - {/*
+
1 1 1 1 -
*/} +
), embedCode: ` @@ -274,7 +274,11 @@ const components = [ preview: ( <>
- + setChecked(!checked)} + label="Checkbox" + />
), @@ -672,13 +676,7 @@ const Wrapper = styled.div` const renderMenuItem = (c, i) => { const prev = i ? components[i - 1] : null; const res = []; - if (!prev || prev.category !== c.category) { - res.push( -
- {c.category} -
- ); - } + const id = c.name.toLowerCase().replaceAll(" ", "-"); res.push(
@@ -691,6 +689,7 @@ const renderMenuItem = (c, i) => { const Grid = styled.div` display: grid; grid-template-columns: repeat(5, minmax(0, 1fr)); + gap: 1rem; .main { grid-column: span 4 / span 4; @@ -698,17 +697,74 @@ const Grid = styled.div` .aside { grid-column: span 1 / span 1; + border-radius: 16px; + border: 1px solid var(--stroke-color, rgba(255, 255, 255, 0.2)); + background: var(--bg-1, #0b0c14); + width: 100%; + min-height: 80vh; + display: flex; + padding: 24px 12px; + flex-direction: column; + align-items: flex-start; + gap: 16px; + margin-bottom: 1rem; + + .menu-item { + width: 100%; + display: flex; + } + + a { + all: unset; + display: inline-flex; + padding: 8px 12px; + justify-content: flex-start; + align-items: center; + gap: 4px; + border-radius: 8px; + font-size: 14px; + font-weight: 500; + transition: all 300ms; + background: var(--button-outline-bg, transparent); + color: var(--button-outline-color, #fff); + border: 1px solid var(--stroke-color, rgba(255, 255, 255, 0.2)); + cursor: pointer; + align-self: stretch; + width: 100%; + text-align: left; + + &:hover { + background: var(--button-outline-hover-bg, rgba(255, 255, 255, 0.1)); + color: var(--button-outline-hover-color, #fff); + } + } } @media screen and (max-width: 768px) { display: flex; flex-direction: column; gap: 1rem; + + .aside { + flex-direction: row; + border: none; + overflow-x: auto; + min-height: auto; + gap: 2rem; + + .menu-item { + width: max-content; + flex-shrink: 0; + a { + flex-shrink: 0; + } + } + } } `; return ( - +
{components.map((component, index) => renderMenuItem(component, index))}
diff --git a/apps/builddao/widget/components/MarkdownEditorIframe.jsx b/apps/builddao/widget/components/MarkdownEditorIframe.jsx deleted file mode 100644 index 38901c46..00000000 --- a/apps/builddao/widget/components/MarkdownEditorIframe.jsx +++ /dev/null @@ -1,64 +0,0 @@ -const data = props.data ?? "# Hello World\n\n"; -const embedCss = props.embedCss || ""; - -const code = ` - - - - - - - -
- - -`; -return ( -