diff --git a/src/common/api/breakaway.ts b/src/common/api/breakaway.ts index 0323289f5fa..bf5643e1ec2 100644 --- a/src/common/api/breakaway.ts +++ b/src/common/api/breakaway.ts @@ -88,11 +88,12 @@ export const getBaUserPoints = async (username: string, community: string): Pro } }; -export const updateUserPoints = async (username: string, community: string, pointType: string) => { +export const updateUserPoints = async (username: string, community: string, communityId: string, pointType: string) => { try { const requestData = { username, community, + communityId, pointType, }; diff --git a/src/common/api/bridge.ts b/src/common/api/bridge.ts index 46ab40fc2d7..e2caf0f48ad 100644 --- a/src/common/api/bridge.ts +++ b/src/common/api/bridge.ts @@ -62,8 +62,8 @@ export const getPostsRanked = ( } export const getAccountPosts = ( - sort: string, - account: string, + sort?: string, + account?: string, start_author: string = "", start_permlink: string = "", limit: number = dataLimit, diff --git a/src/common/components/authors-and-tags/index.tsx b/src/common/components/authors-and-tags/index.tsx new file mode 100644 index 00000000000..671b6b064ae --- /dev/null +++ b/src/common/components/authors-and-tags/index.tsx @@ -0,0 +1,72 @@ +import React from 'react'; +import EntryListContent from '../entry-list'; +import {History, Location} from "history"; +import _ from 'lodash' + +import {Global} from "../../store/global/types"; +import {Account} from "../../store/accounts/types"; +import {DynamicProps} from "../../store/dynamic-props/types"; +import { Entry } from "../../store/entries/types"; +import { Community, Communities } from "../../store/communities/types"; +import { User } from "../../store/users/types"; +import { ActiveUser } from "../../store/active-user/types"; +import { Reblogs } from "../../store/reblogs/types"; +import { UI, ToggleType } from "../../store/ui/types"; + +import { EntryPinTracker } from "../../store/entry-pin-tracker/types"; +import { _t } from "../../i18n"; + +interface AuthorsPostsProps { + global: any; + community: any; + promoted: any[]; + loading: boolean; + entries: any; + promotedEntries: any; + authorsPosts: any; +} +interface Props { + history: History; + location: Location; + global: Global; + dynamicProps: DynamicProps; + entries: Entry[]; + promotedEntries: Entry[]; + communities: Communities; + community?: Community | null; + users: User[]; + activeUser: ActiveUser | null; + reblogs: Reblogs; + loading: boolean; + ui: UI; + entryPinTracker: EntryPinTracker; + signingKey: string; + addAccount: (data: Account) => void; + updateEntry: (entry: Entry) => void; + setActiveUser: (username: string | null) => void; + updateActiveUser: (data?: Account) => void; + deleteUser: (username: string) => void; + fetchReblogs: () => void; + addReblog: (author: string, permlink: string) => void; + deleteReblog: (author: string, permlink: string) => void; + toggleUIProp: (what: ToggleType) => void; + addCommunity: (data: Community) => void; + trackEntryPin: (entry: Entry) => void; + setSigningKey: (key: string) => void; + setEntryPin: (entry: Entry, pin: boolean) => void; +} + +const AuthorsPosts: React.FC = (props: any) => { + + return ( +
+
+ +
+
+ ); +}; + +export default AuthorsPosts; diff --git a/src/common/components/bitcoin-ordinals/index.tsx b/src/common/components/bitcoin-ordinals/index.tsx new file mode 100644 index 00000000000..40e6bf5f810 --- /dev/null +++ b/src/common/components/bitcoin-ordinals/index.tsx @@ -0,0 +1,253 @@ +import React, { useState, useRef } from 'react' +import { _t } from '../../i18n'; +import { Button, Form } from 'react-bootstrap'; +import { handleInvalid, handleOnInput } from "../../util/input-util"; +import { Link } from 'react-router-dom'; +// import { GetAddressConfig, getAddress, signMessage } from 'sats-connect'; +// import { getAddress, signMessage } from '@sats-connect/core'; +// import * as Test from "sats-connect" + +const spkLogo = require("../../img/spklogo.png"); +const btcLogo = require("../../img/btc-ord.jpeg"); + +interface WalletAddress { + address: string; +} + +interface SignMessageResponse { + signature: string; +} + +interface ServerResponse { + [key: string]: any; +} + +export const BitcoinOrdinals = (props: any) => { + const { inProgress, spinner, step, setStep, setInProgress } = props; + const form = useRef(null); + + const [email, setEmail] = useState("") + const [error, setError] = useState(""); + const [username, setUsername] = useState(''); + const [walletAddress, setWalletAddress] = useState(null); + const [signedMessage, setSignedMessage] = useState(null); + const [serverResponse, setServerResponse] = useState(null); + + const emailChanged =(e: { target: { value: React.SetStateAction; }; })=>{ + setEmail(e.target.value) + } + + const usernameChanged =(e: { target: { value: React.SetStateAction; }; })=>{ + setUsername(e.target.value) + // const geta: GetAddressConfig | any = "hgsddhjhjfdhjdef" + } + + const handleCreateAccount = async () => { + // event.preventDefault(); + + try { + const walletAddresses = await getWalletAddress(); + + console.log('Wallet Addresses:', walletAddresses); + + if (walletAddresses && walletAddresses.length > 0) { + const bitcoinAddress = walletAddresses[0].address; + const messageToSign = `Hive:${username}`; + + const signedMessageResponse = await signMessageFromWallet(messageToSign, bitcoinAddress); + + setWalletAddress(bitcoinAddress); + setSignedMessage(signedMessageResponse); + + console.log('Bitcoin Address:', bitcoinAddress); + console.log('Signed Message:', signedMessageResponse); + + // Send data to the server + const response = await sendToServer(username, bitcoinAddress, messageToSign, signedMessageResponse.signature); + setServerResponse(response); + + } else { + throw new Error('No addresses found in the response.'); + } + } catch (error: any) { + console.error('An error occurred:', error); + setServerResponse(`Error: ${error.message}`); + } + }; + + const getWalletAddress = (): Promise => { + return new Promise((resolve, reject) => { + const getAddressOptions: any = { + payload: { + purposes: ['payment'], + message: 'Address for creating Hive account', + network: { + type: 'Mainnet' + }, + }, + onFinish: (response: { addresses: WalletAddress[] }) => { + console.log('onFinish response:', response); + resolve(response.addresses); + }, + onCancel: () => reject(new Error('Request canceled')), + }; + + // getAddress(getAddressOptions); + }); + }; + + const signMessageFromWallet = (message: string, address: string): Promise => { + return new Promise((resolve, reject) => { + const signMessageOptions: any = { + payload: { + network: { + type: 'Mainnet', + }, + address: address, + message: message, + }, + onFinish: (response: SignMessageResponse) => { + console.log('Signature response:', response); + resolve(response); + }, + onCancel: () => reject(new Error('Signing canceled')), + }; + + // signMessage(signMessageOptions); + }); + }; + + const sendToServer = async (username: string, address: string, message: string, signature: string): Promise => { + try { + const response = await fetch('http://localhost:7000/create-account', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + address, + message, + signature, + }), + }); + + if (!response.ok) { + throw new Error(`HTTP error! status: ${response.status}`); + } + + const data: ServerResponse = await response.json(); + return data; + } catch (error) { + console.error('Error sending data to server:', error); + throw error; + } + }; + + return ( + <> + {step === 1 &&
+
+
Register with Btc Ordinals
+
{_t("sign-up.description")}
+
+ SpkNetwork + btc +
+ {(() => { + return ( +
+
+ + + handleInvalid(e, "sign-up.", "validation-username") + } + onInput={handleOnInput} + /> + + + + handleInvalid(e, "sign-up.", "validation-email") + } + onInput={handleOnInput} + /> + +
+ +
+
+ + +
+ ); + })()} +
+
} + {step == 2 && !error && ( +
+
+

+ Account created succesfully +

+ Login +
+
+ )} + {step == 2 && error && ( +
+
+

+ {error} +

+ +
+
+ )} + + ) +} diff --git a/src/common/components/comment/index.tsx b/src/common/components/comment/index.tsx index 9df7201ea8d..f0c18cd62a7 100644 --- a/src/common/components/comment/index.tsx +++ b/src/common/components/comment/index.tsx @@ -137,7 +137,7 @@ export class Comment extends Component { const {onSubmit, activeUser} = this.props; try { onSubmit(text); - const res = await updateUserPoints(activeUser!.username, communityData.title, "comments") + const res = await updateUserPoints(activeUser!.username, communityData.title, communityData.name, "comments") } catch (error) { } diff --git a/src/common/components/community-menu/index.tsx b/src/common/components/community-menu/index.tsx index 40cc9296b7f..f81ce1d707c 100644 --- a/src/common/components/community-menu/index.tsx +++ b/src/common/components/community-menu/index.tsx @@ -59,6 +59,8 @@ export class CommunityMenu extends Component { EntryFilter.created, EntryFilter.payout, EntryFilter.muted, + EntryFilter.tags, + EntryFilter.authors, ].map((x) => { return { label: _t(`entry-filter.filter-${x}`), diff --git a/src/common/components/entry-reblog-btn/index.tsx b/src/common/components/entry-reblog-btn/index.tsx index 592fa6cbe25..92d268466cd 100644 --- a/src/common/components/entry-reblog-btn/index.tsx +++ b/src/common/components/entry-reblog-btn/index.tsx @@ -77,7 +77,7 @@ export class EntryReblogBtn extends BaseComponent { reblog(activeUser?.username!, entry.author, entry.permlink) .then(async () => { addReblog(entry.author, entry.permlink); - const baResponse = await updateUserPoints(activeUser!.username, communityData.title, "reblog") + const baResponse = await updateUserPoints(activeUser!.username, communityData.title, communityData.name, "reblog") success(_t("entry-reblog.success")); }) .catch((e) => { diff --git a/src/common/components/entry-vote-btn/index.tsx b/src/common/components/entry-vote-btn/index.tsx index 6246ee0df9f..9e2d46c09ac 100644 --- a/src/common/components/entry-vote-btn/index.tsx +++ b/src/common/components/entry-vote-btn/index.tsx @@ -400,7 +400,7 @@ export class EntryVoteBtn extends BaseComponent { afterVote(votes, estimated); updateActiveUser(); // refresh voting power - const baResponse = await updateUserPoints(activeUser!.username, communityData.title, "upvote") + const baResponse = await updateUserPoints(activeUser!.username, communityData.title,communityData.name, "upvote") }) .catch((e) => { error(formatError(e)); diff --git a/src/common/helper/test-helper.ts b/src/common/helper/test-helper.ts index d8cc4377e9a..2fd634f8990 100644 --- a/src/common/helper/test-helper.ts +++ b/src/common/helper/test-helper.ts @@ -127,7 +127,9 @@ export const globalInstance: Global = { hive_id: "", ctheme: "", baseApiUrl: "", - communityTitle: "" + communityTitle: "", + baAuthors: [], + communityType: "" }; export const TrendingTagsInstance: TrendingTags = { diff --git a/src/common/i18n/locales/en-US.json b/src/common/i18n/locales/en-US.json index 3b55267a9f1..228ba3b29c6 100644 --- a/src/common/i18n/locales/en-US.json +++ b/src/common/i18n/locales/en-US.json @@ -74,6 +74,8 @@ "filter-promoted": "Promoted", "filter-payout": "Payouts", "filter-muted": "Muted", + "filter-tags": "Tags", + "filter-authors": "Authors", "filter-feed": "Feed", "filter-feed-friends": "Friends", "filter-feed-subscriptions": "Communities", diff --git a/src/common/img/btc-ord.jpeg b/src/common/img/btc-ord.jpeg new file mode 100644 index 00000000000..2aa6c805b07 Binary files /dev/null and b/src/common/img/btc-ord.jpeg differ diff --git a/src/common/pages/community.tsx b/src/common/pages/community.tsx index 6fc5e0bb710..183ae0694e1 100644 --- a/src/common/pages/community.tsx +++ b/src/common/pages/community.tsx @@ -35,7 +35,7 @@ import CommunityActivities from "../components/community-activities"; import CommunityRoles from "../components/community-roles"; import ScrollToTop from "../components/scroll-to-top"; -import { getCommunity, getSubscriptions } from "../api/bridge"; +import { getAccountPosts, getCommunity, getSubscriptions } from "../api/bridge"; import { _t } from "../i18n"; @@ -45,6 +45,9 @@ import capitalize from "../util/capitalize"; import defaults from "../constants/defaults.json"; import SearchBox from "../components/search-box"; import { setupConfig } from "../../setup"; +import { FormControl } from "react-bootstrap"; +import { Entry } from "../store/entries/types"; +import AuthorsPosts from "../components/authors-and-tags"; interface MatchParams { filter: string; @@ -61,6 +64,10 @@ interface State { search: string; searchDataLoading: boolean; searchData: SearchResult[]; + authorsPosts: any; + baAuthor: string; + baTag: string; + loadingb: boolean; } class CommunityPage extends BaseComponent { @@ -70,6 +77,10 @@ class CommunityPage extends BaseComponent { search: "", searchDataLoading: false, searchData: [], + authorsPosts: [], + baAuthor: this.props.global.baAuthors[0], + baTag: this.props.global.tags[0], + loadingb: false, }; constructor(props: Props) { @@ -90,6 +101,10 @@ class CommunityPage extends BaseComponent { search: searchParam, searchDataLoading: searchParam.length > 0, searchData: [], + authorsPosts: [], + baAuthor: this.props.global.baAuthors[1], + baTag: this.props.global.tags[0], + loadingb: false, }; } @@ -110,6 +125,7 @@ class CommunityPage extends BaseComponent { if (r) updateSubscriptions(r); }); } + this.getPostsByUser(); } componentDidUpdate(prevProps: Readonly): void { @@ -126,7 +142,7 @@ class CommunityPage extends BaseComponent { // community or filter changed if ( - (filter !== prevParams.filter || name !== prevParams.name) && + (filter !== prevParams?.filter || name !== prevParams.name) && EntryFilter[filter as EntryFilter] ) { fetchEntries(match.params.filter, match.params.name, false); @@ -241,13 +257,67 @@ class CommunityPage extends BaseComponent { this.delayedSearch(value); }; + getPostsByUser = async () => { + this.setState({ loadingb: true }); + + const authors = this.props.global.baAuthors; + console.log(authors) + let allPosts: any = []; + + try { + // Fetch posts for each author + for (const author of authors) { + const authorPosts: any = await getAccountPosts("posts", author); + allPosts = [...allPosts, ...authorPosts]; // Merge posts into a single array + } + console.log("allPosts.......", allPosts); + this.setState({ authorsPosts: allPosts, loadingb: false }); + } catch (error) { + console.log(error); + this.setState({ loadingb: false }); + } + }; + + authorsChanged = async (e: any) => { + e.preventDefault(); + e.stopPropagation(); + const baAuthor = e.target.value; + + this.setState({ baAuthor }, () => { + this.getPostsByUser(); + }); +} + + tagsChanged = (e: { target: { value: any; }; })=>{ + const baTag = e.target.value + console.log(e.target.value) + this.setState({baTag}) + } + + ////might not be needed again + interleaveArrays = (arr1: any, arr2: any) => { + const newArray = [...arr1, ...arr2] + // const length = newArray.length + + // for (let start = 0; start < length; start++) { + // const randomPosition = Math.floor((newArray.length - start) * Math.random()) + // const randomItem = newArray.splice(randomPosition, 1) + + // newArray.push(...randomItem) + // } + // console.log(arr2, "_.shuffle(newArray)") + return _.shuffle(newArray) + } + + render() { const { global, entries, communities, accounts, match } = this.props; - const { loading, search, searchData, searchDataLoading, typing } = + const { loading, search, searchData, searchDataLoading, typing, authorsPosts, loadingb } = this.state; - const { filter } = match.params; - const { hive_id: name, tags } = global; + const { hive_id: name, tags, baAuthors, communityType } = global; + console.log(global) + // console.log(tags) const community = communities.find((x) => x.name === name); const account = accounts.find((x) => x.name === name); @@ -349,13 +419,62 @@ class CommunityPage extends BaseComponent { if (filter === "roles") { return ; } + + ///might not need this anymore + if (filter === "authors") return ( + <> + + {/* */} + {global.baAuthors.map(x => ( + + ))} + + { loadingb ? :
+ +
} + + ) const groupKey = makeGroupKey(filter, name); const data = entries[groupKey]; if (data !== undefined) { const entryList = data?.entries; + // console.log("object....", entryList) const loading = data?.loading; + const interleavedEntries = this.interleaveArrays(authorsPosts, entryList); + + //////IF COMMUNITY TYPE === AUTHORS + const postToRender = [...authorsPosts, ...entryList].sort((a, b) => Number(new Date(b.created)) - Number(new Date(a.created))); + console.log(postToRender) + + /////might not need this also anymore + if (filter === "tags") return ( + <> + + + {global.tags.map(x => ( + + ))} + +
+ +
+ + ) return ( <> @@ -417,13 +536,15 @@ class CommunityPage extends BaseComponent { {loading && entryList.length === 0 && ( )} - {EntryListContent({ - ...this.props, - entries: entryList, - promotedEntries: promoted, - community, - loading, - })} + )} diff --git a/src/common/pages/profile.tsx b/src/common/pages/profile.tsx index 220de703ed8..7217de3fdb0 100644 --- a/src/common/pages/profile.tsx +++ b/src/common/pages/profile.tsx @@ -472,7 +472,12 @@ class ProfilePage extends BaseComponent {
{loading && entryList.length === 0 && } - {EntryListContent({...this.props, entries: entryList, promotedEntries: [], loading})} +
{loading && entryList.length > 0 ? : ""} diff --git a/src/common/pages/sign-up.tsx b/src/common/pages/sign-up.tsx index 296eb66cbb3..3e6433124bf 100644 --- a/src/common/pages/sign-up.tsx +++ b/src/common/pages/sign-up.tsx @@ -29,6 +29,7 @@ import { getAccount } from "../api/hive"; import { OffchainUser } from "../components/offchain-users"; import QRCode from "react-qr-code"; import { History } from "history"; +import { BitcoinOrdinals } from "../components/bitcoin-ordinals"; const HiveLogo = require("../img/hive-logo.jpeg"); const solanaLogo = require("../img/solanaLogo.png"); @@ -43,6 +44,7 @@ interface Props { const SignUpPage = (props: Props | any) => { const form = useRef(null); const { global, communities, activeUser, history } = props; + console.log(global) const [username, setUsername] = useState(""); const [email, setEmail] = useState(""); @@ -55,7 +57,7 @@ const SignUpPage = (props: Props | any) => { const [step, setStep] = useState(1); const [error, setError] = useState(""); const [isDownloaded, setIsDownloaded] = useState(false); - const [accountType, setAccountType] = useState("Hive"); + const [accountType, setAccountType] = useState(global.communityType === "btc_ordinals" ? "btc_ordinals" : "Hive"); useEffect(() => { getCurrentCommunity(); @@ -240,7 +242,7 @@ const SignUpPage = (props: Props | any) => { : NavBar({ ...props })}
- {step === 1 && ( + {(step === 1 && global.communityType !== "btc_ordinals") && (
{/*

Sign up with

*/}
{
)}
- {accountType === "Hive" && ( + {(accountType === "Hive" && global.communityType !== "btc_ordinals" )&& ( <> {step == 1 && (
@@ -499,6 +501,16 @@ const SignUpPage = (props: Props | any) => { /> )} + {(step === 1 && accountType === "btc_ordinals") && ( + <> + + + )}
diff --git a/src/common/pages/submit.tsx b/src/common/pages/submit.tsx index cc4ccbb7e04..c633d9249a3 100644 --- a/src/common/pages/submit.tsx +++ b/src/common/pages/submit.tsx @@ -603,7 +603,7 @@ class SubmitPage extends BaseComponent { percent_hbd: options.percent_hbd }; addEntry(entry); - const baResponse = await updateUserPoints(activeUser!.username, communityData.title, "posts"); + const baResponse = await updateUserPoints(activeUser!.username, communityData.title,communityData.name, "posts"); success(_t("submit.published")); this.clear(); diff --git a/src/common/store/global/index.ts b/src/common/store/global/index.ts index 0a491fa0efb..48a65a57ef5 100644 --- a/src/common/store/global/index.ts +++ b/src/common/store/global/index.ts @@ -56,7 +56,9 @@ export const initialState: Global = { tags: ["spk", "3speak"], hive_id: "hive-112019", baseApiUrl: "https://account-creator.3speak.tv/api", - communityTitle: "" + communityTitle: "", + baAuthors: [], + communityType: "standard", }; export default (state: Global = initialState, action: Actions): Global => { diff --git a/src/common/store/global/types.ts b/src/common/store/global/types.ts index c69ed9a1dc0..dfd91dd13f1 100644 --- a/src/common/store/global/types.ts +++ b/src/common/store/global/types.ts @@ -19,7 +19,9 @@ export enum EntryFilter { created = "created", payout = "payout", payout_comments = "payout_comments", - muted = "muted" + muted = "muted", + tags = "tags", + authors = "authors" } export enum ProfileFilter { @@ -69,6 +71,8 @@ export interface Global { hive_id: string; tags: string[]; communityTitle: string; + baAuthors: string[]; + communityType: string | undefined; } export enum ActionTypes { diff --git a/src/config.ts b/src/config.ts index a4fc88ffc0c..1e4fba62088 100644 --- a/src/config.ts +++ b/src/config.ts @@ -7,5 +7,7 @@ export default { theme: process.env.THEME || "", tags: process.env.TAGS?.split(",") || "", availibleAccounts: process.env.ACCOUNTS ? +process.env.ACCOUNTS : 0, - baseApiUrl: process.env.API_URL || "https://account-creator.3speak.tv/api" + baseApiUrl: process.env.API_URL || "https://account-creator.3speak.tv/api", + authors: process.env.AUTHORS?.split(",") || "", + communityType: process.env.COMMUNITY_TYPE, }; diff --git a/src/server/state.ts b/src/server/state.ts index 08da00ddd53..179eea7978b 100644 --- a/src/server/state.ts +++ b/src/server/state.ts @@ -56,7 +56,9 @@ export const makePreloadedState = async ( ctheme: config.theme, tags: [...config.tags], baseApiUrl: config.baseApiUrl, - communityTitle: communityData!.title + baAuthors: [...config.authors], + communityTitle: communityData!.title, + communityType: config.communityType }; const dynamicProps = await getDynamicProps();