Skip to content

Commit a757f5a

Browse files
authored
Merge pull request #2927 from ecency/nt/asking-for-vote
Nt/asking for vote
2 parents f7f0a22 + d4b2e59 commit a757f5a

File tree

15 files changed

+398
-1
lines changed

15 files changed

+398
-1
lines changed

src/components/index.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ import { SearchInput } from './searchInput';
4343
import { SearchModal } from './searchModal';
4444
import { SettingsItem } from './settingsItem';
4545
import { SideMenu } from './sideMenu';
46+
import { ProposalVoteRequest } from './proposalVoteRequest';
4647

4748
import CommunityCard from './communityCard';
4849

@@ -272,4 +273,6 @@ export {
272273
PostTranslationModal,
273274
ImageViewer,
274275
WalkthroughMarker,
276+
ProposalVoteRequest
277+
275278
};
Lines changed: 133 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,133 @@
1+
import React, { useMemo, useState } from 'react';
2+
import { Image, Text, View } from "react-native";
3+
import styles from "../styles/ProposalVoteRequest.styles";
4+
import { TextButton } from "../../../components/buttons";
5+
import { MainButton } from "../../../components/mainButton";
6+
import { useDispatch, useSelector } from "react-redux";
7+
import { showActionModal } from "../../../redux/actions/uiAction";
8+
import { ButtonTypes } from "../../../components/actionModal/container/actionModalContainer";
9+
import { useProposalVotedQuery, useProposalVoteMutation } from '../../../providers/queries';
10+
import { updateProposalVoteMeta } from '../../../redux/actions/cacheActions';
11+
import { useIntl } from 'react-intl';
12+
13+
const ECENCY_PROPOSAL_ID = 283;
14+
const RE_REQUEST_INTERVAL = 259200000; //3 days;
15+
16+
export const ProposalVoteRequest = () => {
17+
const intl = useIntl();
18+
const dispatch = useDispatch();
19+
20+
const proposalVotedQuery = useProposalVotedQuery(ECENCY_PROPOSAL_ID);
21+
const proposalVoteMutation = useProposalVoteMutation();
22+
23+
const currentAccount = useSelector(state => state.account.currentAccount);
24+
25+
//assess if user should be promopted to vote proposal
26+
//makes sure this logic is only calculated once on launch
27+
const [skipOnLaunch] = useState(!currentAccount ||
28+
proposalVotedQuery.data ||
29+
proposalVotedQuery.meta?.processed);
30+
31+
32+
//render or no render based on dimiss action performed
33+
const skipRender = useMemo(() => {
34+
if (!skipOnLaunch && proposalVotedQuery.meta) {
35+
const curTime = new Date().getTime();
36+
const nextRequestTime = proposalVotedQuery.meta.dismissedAt + RE_REQUEST_INTERVAL
37+
return nextRequestTime > curTime;
38+
}
39+
return skipOnLaunch;
40+
}, [proposalVotedQuery.meta])
41+
42+
43+
if (skipRender) {
44+
return null;
45+
}
46+
47+
const voteCasted = proposalVoteMutation.isSuccess;
48+
49+
const _voteAction = () => {
50+
proposalVoteMutation.mutate({ proposalId: ECENCY_PROPOSAL_ID })
51+
}
52+
53+
54+
const _remindLater = () => {
55+
dispatch(showActionModal({
56+
title: intl.formatMessage({id:'proposal.title-action-dismiss'}) ,// "Dismiss Vote Request",
57+
buttons: [
58+
{
59+
text: intl.formatMessage({id:'proposal.btn-ignore'}),
60+
type: ButtonTypes.CANCEL,
61+
onPress: () => {
62+
console.log('Ignore');
63+
dispatch(updateProposalVoteMeta(
64+
ECENCY_PROPOSAL_ID,
65+
currentAccount.username,
66+
true,
67+
new Date().getTime()
68+
))
69+
},
70+
},
71+
{
72+
text: intl.formatMessage({id:'proposal.btn-later'}),
73+
onPress: () => {
74+
dispatch(updateProposalVoteMeta(
75+
ECENCY_PROPOSAL_ID,
76+
currentAccount.username,
77+
false,
78+
new Date().getTime()
79+
))
80+
},
81+
},
82+
],
83+
}))
84+
};
85+
86+
87+
const _actionPanel = () => {
88+
return (
89+
<View style={styles.actionPanel}>
90+
<MainButton
91+
onPress={_voteAction}
92+
style={{ height: 40 }}
93+
textStyle={styles.voteBtnTitle}
94+
text={intl.formatMessage({id:'proposal.btn-vote'})}
95+
isLoading={proposalVoteMutation.isLoading}
96+
97+
/>
98+
<TextButton
99+
onPress={_remindLater}
100+
style={{ marginLeft: 8 }}
101+
text={intl.formatMessage({id:'proposal.btn-dismiss'})}
102+
/>
103+
</View>
104+
);
105+
}
106+
107+
const titleTextId = voteCasted ? "proposal.title-voted" : "proposal.title";
108+
const descTextId = voteCasted ? "proposal.desc-voted" : "proposal.desc"
109+
110+
return (
111+
<View style={styles.container}>
112+
<View style={styles.content} >
113+
<View style={{ flex: 1 }}>
114+
<Text style={styles.title} >
115+
{intl.formatMessage({id:titleTextId})}
116+
</Text>
117+
118+
<Text style={styles.description} >
119+
{intl.formatMessage({id:descTextId})}
120+
</Text>
121+
</View>
122+
123+
<Image
124+
resizeMode="contain"
125+
style={{ width: 56, marginHorizontal: 12 }}
126+
source={require('../../../assets/ecency_logo_transparent.png')}
127+
/>
128+
129+
</View>
130+
{!voteCasted && _actionPanel()}
131+
</View>
132+
)
133+
};
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export * from './container/proposalVoteRequest';
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
import { TextStyle, ViewStyle } from "react-native";
2+
import EStyleSheet from "react-native-extended-stylesheet";
3+
4+
export default EStyleSheet.create({
5+
container:{
6+
backgroundColor:"$primaryLightBackground",
7+
marginTop:16,
8+
marginBottom:8,
9+
padding:16,
10+
flex: 1
11+
} as ViewStyle,
12+
content:{
13+
flexDirection:'row',
14+
alignItems:'center',
15+
} as ViewStyle,
16+
title:{
17+
color:'$primaryDarkText',
18+
fontSize: 18,
19+
fontWeight: '600'
20+
} as TextStyle,
21+
description:{
22+
color:'$primaryDarkText',
23+
marginVertical:8
24+
} as TextStyle,
25+
actionPanel:{
26+
flexDirection:'row',
27+
alignItems:'center',
28+
marginTop:8
29+
} as ViewStyle,
30+
voteBtnTitle:{
31+
// color: '$primaryBlue'
32+
} as TextStyle
33+
})

src/components/tabbedPosts/view/postsTabContent.tsx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import {
1313
usePromotedPostsQuery,
1414
} from '../../../providers/queries/postQueries/feedQueries';
1515
import { NewPostsPopup, ScrollTopPopup } from '../../atoms';
16+
import { ProposalVoteRequest } from '../..';
1617

1718
let scrollOffset = 0;
1819
let blockPopup = false;
@@ -137,6 +138,15 @@ const PostsTabContent = ({
137138
}
138139
};
139140

141+
142+
const _renderHeader = () => {
143+
if (pageType === 'main' && isInitialTab) {
144+
return (
145+
<ProposalVoteRequest />
146+
)
147+
}
148+
149+
}
140150
// view rendereres
141151
const _renderEmptyContent = () => {
142152
const _isNoPost = !feedQuery.isLoading && feedQuery.data.length == 0;
@@ -194,6 +204,7 @@ const PostsTabContent = ({
194204
ListEmptyComponent={_renderEmptyContent}
195205
pageType={pageType}
196206
showQuickReplyModal={_showQuickReplyModal}
207+
ListHeaderComponent={_renderHeader}
197208
/>
198209
<NewPostsPopup
199210
popupAvatars={feedQuery.latestPosts.map((post) => post.avatar || '')}

src/config/locales/en-US.json

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -634,6 +634,7 @@
634634
"continue": "Continue",
635635
"okay": "Okay",
636636
"done": "Done",
637+
"thankyou": "Thank You!",
637638
"notice": "Notice!",
638639
"close": "CLOSE",
639640
"later": "Later",
@@ -1148,5 +1149,16 @@
11481149
"missing-authority": "This operation requires Active private key or authority.",
11491150
"missing-owner-authority": "This operation requires Owner private key or authority.",
11501151
"insufficient_fund": "Insufficient Funds"
1152+
},
1153+
"proposal":{
1154+
"title":"Enjoying Ecency!",
1155+
"title-voted":"We are grateful!",
1156+
"desc":"Support proposal by voting, be part of our continuous efforts to improve experience on Ecency.",
1157+
"desc-voted":"Your support means everything to us",
1158+
"title-action-dismiss":"Dismiss Vote Request",
1159+
"btn-vote":"Cast My Vote",
1160+
"btn-dismiss":"Dismiss",
1161+
"btn-later":"Remind Later",
1162+
"btn-ignore":"Forever"
11511163
}
11521164
}

src/providers/hive/dhive.js

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -641,6 +641,33 @@ export const ignoreUser = async (currentAccount, pin, data) => {
641641
);
642642
};
643643

644+
export const getProposalsVoted = async (username) => {
645+
try {
646+
if (!username) {
647+
throw new Error('invalid parameters');
648+
}
649+
650+
console.log('Getting proposals voted:', username);
651+
652+
const votedProposals = await client.call(
653+
'condenser_api',
654+
'list_proposal_votes',
655+
[[username], 100, "by_voter_proposal", "ascending", "active"]);
656+
657+
if (!Array.isArray(votedProposals)) {
658+
throw new Error('invalid data');
659+
}
660+
661+
const filteredProposals = votedProposals.filter(item => item.voter === username);
662+
663+
console.log(`Returning filtered proposals`, filteredProposals);
664+
return filteredProposals;
665+
} catch (error) {
666+
bugsnapInstance.notify(error);
667+
return [];
668+
}
669+
}
670+
644671
export const getActiveVotes = (author, permlink) =>
645672
new Promise((resolve, reject) => {
646673
try {
@@ -978,6 +1005,64 @@ const _vote = (currentAccount, pin, author, permlink, weight) => {
9781005
);
9791006
};
9801007

1008+
1009+
/**
1010+
* Update Hive Proposal Vote with current account as voter
1011+
* @param {*} currentAccount
1012+
* @param {*} pin
1013+
* @param {*} proposalId
1014+
* @returns
1015+
*/
1016+
export const voteProposal = (currentAccount, pinHash, proposalId) => {
1017+
const digitPinCode = getDigitPinCode(pinHash);
1018+
const key = getAnyPrivateKey(currentAccount.local, digitPinCode);
1019+
1020+
const voter = currentAccount.name;
1021+
const opArray = [
1022+
[
1023+
"update_proposal_votes",
1024+
{
1025+
voter: voter,
1026+
proposal_ids: [proposalId],
1027+
approve: true,
1028+
extensions: []
1029+
}
1030+
],
1031+
];
1032+
1033+
if (currentAccount.local.authType === AUTH_TYPE.STEEM_CONNECT) {
1034+
const token = decryptKey(currentAccount.local.accessToken, digitPinCode);
1035+
const api = new hsClient({
1036+
accessToken: token,
1037+
});
1038+
1039+
return api.broadcast(opArray).then((resp) => resp.result);
1040+
}
1041+
1042+
if (key) {
1043+
const privateKey = PrivateKey.fromString(key);
1044+
1045+
return new Promise((resolve, reject) => {
1046+
sendHiveOperations(opArray, privateKey)
1047+
.then((result) => {
1048+
console.log('vote result', result);
1049+
resolve(result);
1050+
})
1051+
.catch((err) => {
1052+
if (err && get(err, 'jse_info.code') === 4030100) {
1053+
err.message = getDsteemDateErrorMessage(err);
1054+
}
1055+
bugsnagInstance.notify(err);
1056+
reject(err);
1057+
});
1058+
});
1059+
}
1060+
1061+
return Promise.reject(
1062+
new Error('Check private key permission! Required private posting key or above.'),
1063+
);
1064+
};
1065+
9811066
/**
9821067
* @method upvoteAmount estimate upvote amount
9831068
*/

src/providers/queries/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,4 @@ export * from './walletQueries';
5959
export * from './leaderboardQueries';
6060
export * from './settingsQueries';
6161
export * from './announcementsQueries';
62+
export * from './proposalQueries';

0 commit comments

Comments
 (0)