diff --git a/gno/p/dao_voting_group/voting_group_test.gno b/gno/p/dao_voting_group/voting_group_test.gno index cfb40125bd..379770d31d 100644 --- a/gno/p/dao_voting_group/voting_group_test.gno +++ b/gno/p/dao_voting_group/voting_group_test.gno @@ -1,7 +1,6 @@ package dao_voting_group import ( - "std" "testing" dao_interfaces "gno.land/p/teritori/dao_interfaces" diff --git a/gno/p/markdown_utils/markdown_utils.gno b/gno/p/markdown_utils/markdown_utils.gno index b593884a83..cc1f134526 100644 --- a/gno/p/markdown_utils/markdown_utils.gno +++ b/gno/p/markdown_utils/markdown_utils.gno @@ -25,4 +25,4 @@ func Indent(markdown string) string { // thanks copilot this is perfect xD // I just renamed it, AddIndentationLevelToMarkdownTitles was too long -// blockchain + ai, invest quick!!!! \ No newline at end of file +// blockchain + ai, invest quick!!!! diff --git a/gno/r/dao_realm/dao_realm.gno b/gno/r/dao_realm/dao_realm.gno index 886fbe4547..859fc27d5d 100644 --- a/gno/r/dao_realm/dao_realm.gno +++ b/gno/r/dao_realm/dao_realm.gno @@ -10,6 +10,7 @@ import ( "gno.land/p/teritori/dao_utils" voting_group "gno.land/p/teritori/dao_voting_group" "gno.land/r/teritori/dao_registry" + "gno.land/r/teritori/social_feeds" "gno.land/r/teritori/tori" ) @@ -66,6 +67,9 @@ func init() { func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler { return tori.NewChangeAdminHandler() }, + func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler { + return social_feeds.NewCreatePostHandler() + }, } daoCore = dao_core.NewDAOCore(votingModuleFactory, proposalModulesFactories, messageHandlersFactories) diff --git a/gno/r/dao_realm/dao_realm_test.gno b/gno/r/dao_realm/dao_realm_test.gno index 7338a03d31..202b4772a7 100644 --- a/gno/r/dao_realm/dao_realm_test.gno +++ b/gno/r/dao_realm/dao_realm_test.gno @@ -2,16 +2,11 @@ package dao_realm import ( "fmt" - "std" "testing" "gno.land/p/demo/json" - dao_core "gno.land/p/teritori/dao_core" - dao_interfaces "gno.land/p/teritori/dao_interfaces" - proposal_single "gno.land/p/teritori/dao_proposal_single" "gno.land/p/teritori/dao_voting_group" "gno.land/p/teritori/havl" - "gno.land/r/demo/users" ) func TestInit(t *testing.T) { diff --git a/gno/r/dao_realm/gno.mod b/gno/r/dao_realm/gno.mod index 9547f1573d..580eea7310 100644 --- a/gno/r/dao_realm/gno.mod +++ b/gno/r/dao_realm/gno.mod @@ -8,7 +8,7 @@ require ( gno.land/p/teritori/dao_utils v0.0.0-latest gno.land/p/teritori/dao_voting_group v0.0.0-latest gno.land/p/teritori/havl v0.0.0-latest - gno.land/r/demo/users v0.0.0-latest gno.land/r/teritori/dao_registry v0.0.0-latest + gno.land/r/teritori/social_feeds v0.0.0-latest gno.land/r/teritori/tori v0.0.0-latest ) diff --git a/gno/r/social_feeds/feeds.gno b/gno/r/social_feeds/feeds.gno index 352a9555cd..675b3c51e0 100644 --- a/gno/r/social_feeds/feeds.gno +++ b/gno/r/social_feeds/feeds.gno @@ -18,4 +18,5 @@ var ( //---------------------------------------- // Constants +// Feed name must be 3-30 characters long, starting with a lowercase letter var reName = regexp.MustCompile(`^[a-z]+[_a-z0-9]{2,29}$`) diff --git a/gno/r/social_feeds/feeds_test.gno b/gno/r/social_feeds/feeds_test.gno index 532a131926..5d85e732c5 100644 --- a/gno/r/social_feeds/feeds_test.gno +++ b/gno/r/social_feeds/feeds_test.gno @@ -1,14 +1,10 @@ package social_feeds import ( - "encoding/base64" - "fmt" "std" - "strconv" "strings" "testing" - "gno.land/p/demo/avl" "gno.land/p/demo/testutils" ujson "gno.land/p/teritori/ujson" ) diff --git a/gno/r/social_feeds/messages.gno b/gno/r/social_feeds/messages.gno index a60bf8e803..cb95fe4ca6 100644 --- a/gno/r/social_feeds/messages.gno +++ b/gno/r/social_feeds/messages.gno @@ -1,6 +1,7 @@ package social_feeds import ( + "strconv" "strings" "gno.land/p/demo/json" @@ -63,8 +64,7 @@ func (msg *ExecutableMessageBanPost) String() string { return strings.Join(ss, "\n---\n") } -type BanPostHandler struct { -} +type BanPostHandler struct{} var _ dao_interfaces.MessageHandler = (*BanPostHandler)(nil) @@ -84,3 +84,76 @@ func (h BanPostHandler) Type() string { func (h BanPostHandler) Instantiate() dao_interfaces.ExecutableMessage { return &ExecutableMessageBanPost{} } + +// Create a new post +type ExecutableMessageCreatePost struct { + FeedID FeedID + ParentID PostID + Category uint64 + Metadata string +} + +var _ dao_interfaces.ExecutableMessage = (*ExecutableMessageCreatePost)(nil) + +func (msg ExecutableMessageCreatePost) Type() string { + return "gno.land/r/teritori/social_feeds.CreatePost" +} + +func (msg *ExecutableMessageCreatePost) ToJSON() *json.Node { + return json.ObjectNode("", map[string]*json.Node{ + "feedId": jsonutil.IntNode(int(msg.FeedID)), + "parentId": jsonutil.IntNode(int(msg.ParentID)), + "category": jsonutil.IntNode(int(msg.Category)), + "metadata": json.StringNode("", msg.Metadata), + }) +} + +func (msg *ExecutableMessageCreatePost) FromJSON(ast *json.Node) { + obj := ast.MustObject() + msg.FeedID = FeedID(jsonutil.MustInt(obj["feedId"])) + msg.ParentID = PostID(jsonutil.MustInt(obj["parentId"])) + msg.Category = uint64(jsonutil.MustInt(obj["category"])) + msg.Metadata = obj["metadata"].MustString() +} + +func (msg *ExecutableMessageCreatePost) String() string { + var ss []string + ss = append(ss, msg.Type()) + + feed := getFeed(msg.FeedID) + s := "" + + if feed != nil { + s += "Feed: " + feed.name + " (" + feed.id.String() + ")" + s += "\nParent: " + msg.ParentID.String() + s += "\nCategory: " + strconv.Itoa(int(msg.Category)) + s += "\nMetadata: " + msg.Metadata + } else { + s += "Feed: " + msg.FeedID.String() + " (not found)" + } + + ss = append(ss, s) + + return strings.Join(ss, "\n---\n") +} + +type CreatePostHandler struct{} + +var _ dao_interfaces.MessageHandler = (*CreatePostHandler)(nil) + +func NewCreatePostHandler() *CreatePostHandler { + return &CreatePostHandler{} +} + +func (h *CreatePostHandler) Execute(iMsg dao_interfaces.ExecutableMessage) { + msg := iMsg.(*ExecutableMessageCreatePost) + CreatePost(msg.FeedID, msg.ParentID, msg.Category, msg.Metadata) +} + +func (h CreatePostHandler) Type() string { + return ExecutableMessageCreatePost{}.Type() +} + +func (h CreatePostHandler) Instantiate() dao_interfaces.ExecutableMessage { + return &ExecutableMessageCreatePost{} +} diff --git a/packages/components/dao/ProposalActions.tsx b/packages/components/dao/ProposalActions.tsx index 54ec940e56..edc58024fe 100644 --- a/packages/components/dao/ProposalActions.tsx +++ b/packages/components/dao/ProposalActions.tsx @@ -1,3 +1,4 @@ +import { upperFirst } from "lodash"; import { View } from "react-native"; import { useFeedbacks } from "../../context/FeedbacksProvider"; @@ -10,15 +11,15 @@ import { useIsDAOMember } from "../../hooks/dao/useDAOMember"; import { useDAOFirstProposalModule } from "../../hooks/dao/useDAOProposalModules"; import { useInvalidateDAOProposals } from "../../hooks/dao/useDAOProposals"; import { - useInvalidateDAOVoteInfo, useDAOVoteInfo, + useInvalidateDAOVoteInfo, } from "../../hooks/dao/useDAOVoteInfo"; import { useSelectedNetworkId } from "../../hooks/useSelectedNetwork"; import useSelectedWallet from "../../hooks/useSelectedWallet"; import { NetworkKind, getNetwork, parseUserId } from "../../networks"; import { adenaVMCall } from "../../utils/gno"; import { GnoDAOVoteRequest } from "../../utils/gnodao/messages"; -import { neutral77, primaryColor, errorColor } from "../../utils/style/colors"; +import { errorColor, neutral77, primaryColor } from "../../utils/style/colors"; import { fontSemibold14 } from "../../utils/style/fonts"; import { BrandText } from "../BrandText"; import { PrimaryButton } from "../buttons/PrimaryButton"; @@ -80,34 +81,24 @@ export const ProposalActions: React.FC<{ case NetworkKind.Gno: { const walletAddress = selectedWallet.address; const [, pkgPath] = parseUserId(daoId); - let gnoVote; - switch (v) { - case "yes": { - gnoVote = 0; - break; - } - case "no": { - gnoVote = 1; - break; - } - case "abstain": { - gnoVote = 2; - break; - } - default: - throw new Error("invalid vote"); + if (["yes", "no", "abstain"].indexOf(v) === -1) { + throw new Error("invalid vote"); } const msg: GnoDAOVoteRequest = { - vote: gnoVote, + vote: upperFirst(v), rationale: "Me like it", }; - await adenaVMCall(networkId, { - caller: walletAddress, - send: "", - pkg_path: pkgPath, - func: "VoteJSON", - args: ["0", proposal.id.toString(), JSON.stringify(msg)], - }); + await adenaVMCall( + networkId, + { + caller: walletAddress, + send: "", + pkg_path: pkgPath, + func: "VoteJSON", + args: ["0", proposal.id.toString(), JSON.stringify(msg)], + }, + { gasWanted: 10000000 }, + ); break; } default: diff --git a/packages/components/modals/NotEnoughFundModal.tsx b/packages/components/modals/NotEnoughFundModal.tsx index 90b9cebb35..39b9bc47c8 100644 --- a/packages/components/modals/NotEnoughFundModal.tsx +++ b/packages/components/modals/NotEnoughFundModal.tsx @@ -6,7 +6,12 @@ import { Coin } from "../../api/teritori-chain/cosmos/base/v1beta1/coin"; import { useBalances } from "../../hooks/useBalances"; import { useSelectedNetworkInfo } from "../../hooks/useSelectedNetwork"; import useSelectedWallet from "../../hooks/useSelectedWallet"; -import { getCurrency, getNativeCurrency, getNetwork } from "../../networks"; +import { + getCurrency, + getNativeCurrency, + getNetwork, + parseUserId, +} from "../../networks"; import { prettyPrice } from "../../utils/coins"; import { neutral77 } from "../../utils/style/colors"; import { fontSemibold14, fontSemibold20 } from "../../utils/style/fonts"; @@ -28,13 +33,15 @@ export const NotEnoughFundsModal: FC<{ cost?: Coin; label?: string; visible?: boolean; + userId?: string; onClose?: () => void; -}> = ({ label = "Not enough funds", cost, visible, onClose }) => { +}> = ({ label = "Not enough funds", cost, visible, onClose, userId }) => { const selectedWallet = useSelectedWallet(); const selectedNetwork = useSelectedNetworkInfo(); + const [userNetwork, userAddress] = parseUserId(userId); const { balances } = useBalances( - selectedNetwork?.id, - selectedWallet?.address, + userNetwork?.id || selectedNetwork?.id, + userAddress || selectedWallet?.address, ); const costBalance = balances.find((bal) => bal.denom === cost?.denom); const currency = getCurrency(selectedNetwork?.id, cost?.denom); @@ -75,7 +82,7 @@ export const NotEnoughFundsModal: FC<{ - {cost && costBalance && selectedNetwork && ( + {cost && selectedNetwork && ( <> {prettyPrice( selectedNetwork.id, - costBalance.amount, - costBalance.denom, + costBalance?.amount || "0", + cost.denom, )} diff --git a/packages/components/socialFeed/NewsFeed/NewsFeed.tsx b/packages/components/socialFeed/NewsFeed/NewsFeed.tsx index 43172b9cf2..d37a4d73e3 100644 --- a/packages/components/socialFeed/NewsFeed/NewsFeed.tsx +++ b/packages/components/socialFeed/NewsFeed/NewsFeed.tsx @@ -1,8 +1,8 @@ import React, { useCallback, useEffect, useMemo, useState } from "react"; import { LayoutChangeEvent, - View, useWindowDimensions, + View, ViewStyle, } from "react-native"; import Animated, { diff --git a/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx b/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx index 99288b0254..3139edde4c 100644 --- a/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx +++ b/packages/components/socialFeed/NewsFeed/NewsFeedInput.tsx @@ -41,9 +41,9 @@ import { useIpfs } from "@/hooks/useIpfs"; import { useMaxResolution } from "@/hooks/useMaxResolution"; import { useSelectedNetworkInfo } from "@/hooks/useSelectedNetwork"; import useSelectedWallet from "@/hooks/useSelectedWallet"; -import { getNetworkFeature, getUserId, NetworkFeature } from "@/networks"; +import { NetworkFeature, getNetworkFeature } from "@/networks"; import { selectNFTStorageAPI } from "@/store/slices/settings"; -import { feedPostingStep, FeedPostingStepId } from "@/utils/feed/posting"; +import { FeedPostingStepId, feedPostingStep } from "@/utils/feed/posting"; import { generatePostMetadata, getPostCategory } from "@/utils/feed/queries"; import { generateIpfsKey } from "@/utils/ipfs"; import { @@ -141,7 +141,7 @@ export const NewsFeedInput = React.forwardRef< const selectedNetwork = useSelectedNetworkInfo(); const selectedNetworkId = selectedNetwork?.id || "teritori"; const selectedWallet = useSelectedWallet(); - const userId = getUserId(selectedNetworkId, selectedWallet?.address); + const authorId = daoId || selectedWallet?.userId; const inputRef = useRef(null); const { setToastError } = useFeedbacks(); const [isUploadLoading, setIsUploadLoading] = useState(false); @@ -182,7 +182,7 @@ export const NewsFeedInput = React.forwardRef< setStep, } = useFeedPosting( selectedNetwork?.id, - userId, + authorId, postCategory, onPostCreationSuccess, ); @@ -210,16 +210,19 @@ export const NewsFeedInput = React.forwardRef< }); return; } + if (!canPayForPost) { showNotEnoughFundsModal({ action, cost: { - amount: publishingFee.toString(), + amount: publishingFee.amount.toString(), denom: publishingFee.denom || "", }, + userId: authorId, }); return; } + setIsUploadLoading(true); setIsProgressBarShown(true); onSubmitInProgress && onSubmitInProgress(); @@ -258,7 +261,7 @@ export const NewsFeedInput = React.forwardRef< setStep(feedPostingStep(FeedPostingStepId.GENERATING_KEY)); const pinataJWTKey = - userIPFSKey || (await generateIpfsKey(selectedNetworkId, userId)); + userIPFSKey || (await generateIpfsKey(selectedNetworkId, authorId)); if (pinataJWTKey) { setStep(feedPostingStep(FeedPostingStepId.UPLOADING_FILES)); diff --git a/packages/components/socialFeed/modals/FlagConfirmModal.tsx b/packages/components/socialFeed/modals/FlagConfirmModal.tsx index 5a6d576825..05a21853e8 100644 --- a/packages/components/socialFeed/modals/FlagConfirmModal.tsx +++ b/packages/components/socialFeed/modals/FlagConfirmModal.tsx @@ -63,7 +63,7 @@ export const FlagConfirmModal: React.FC = ({ const moduleIndex = "0"; const voteJSON: GnoDAOVoteRequest = { - vote: vote === "banPost" ? 0 : 1, + vote: vote === "banPost" ? "yes" : "no", rationale: "", }; diff --git a/packages/context/WalletControlProvider.tsx b/packages/context/WalletControlProvider.tsx index 61d982f121..b19c030fd0 100644 --- a/packages/context/WalletControlProvider.tsx +++ b/packages/context/WalletControlProvider.tsx @@ -8,6 +8,7 @@ import { NetworkFeature } from "../networks"; interface ControlledWalletFundsParams { cost: Coin; action: string; + userId?: string; } interface ControlledWalletConnectedParams { @@ -37,6 +38,7 @@ export const WalletControlContextProvider: React.FC<{ useState(false); const [isConnectWalletVisible, setIsConnectWalletVisible] = useState(false); const [cost, setCost] = useState(); + const [userId, setUserId] = useState(); const showConnectWalletModal = ({ forceNetworkFeature, @@ -50,9 +52,11 @@ export const WalletControlContextProvider: React.FC<{ const showNotEnoughFundsModal = ({ cost, action, + userId, }: ControlledWalletFundsParams) => { setCost(cost); setAction(action); + setUserId(userId); setIsNotEnoughFundModalVisible(true); setIsConnectWalletVisible(false); }; @@ -69,6 +73,7 @@ export const WalletControlContextProvider: React.FC<{ cost={cost} visible={isNotEnoughFundModalVisible} onClose={() => setIsNotEnoughFundModalVisible(false)} + userId={userId} /> { "${network.daoUtilsPkgPath}" voting_group "${network.votingGroupPkgPath}" "${network.daoRegistryPkgPath}" + "${network.socialFeedsPkgPath}" ) var ( @@ -79,6 +80,9 @@ func init() { propMod := core.ProposalModules()[0] return proposal_single.NewUpdateSettingsHandler(propMod.Module.(*proposal_single.DAOProposalSingle)) }, + func(core dao_interfaces.IDAOCore) dao_interfaces.MessageHandler { + return social_feeds.NewCreatePostHandler() + }, } daoCore = dao_core.NewDAOCore(votingModuleFactory, proposalModulesFactories, messageHandlersFactories) diff --git a/packages/utils/gnodao/messages.ts b/packages/utils/gnodao/messages.ts index 4f79c61ef7..3818ca3ed2 100644 --- a/packages/utils/gnodao/messages.ts +++ b/packages/utils/gnodao/messages.ts @@ -7,7 +7,7 @@ export interface GnoSingleChoiceProposal { } export interface GnoDAOVoteRequest { - vote: number; + vote: string; rationale: string; } @@ -61,6 +61,16 @@ export interface GnoBanPostMessage { }; } +export interface GnoCreatePostMessage { + type: "gno.land/r/teritori/social_feeds.CreatePost"; + payload: { + feedId: string; + parentId: string; + category: string; + metadata: string; + }; +} + export interface GnoAddMemberMessage { type: "gno.land/r/teritori/groups.AddMember"; payload: {