2018-09-10 09:55:26 -07:00
|
|
|
import types from './types';
|
2019-02-17 18:13:24 -08:00
|
|
|
import { cloneDeep } from 'lodash';
|
|
|
|
import { pendingMoreablePage, fulfilledMoreablePage } from 'utils/helpers';
|
2019-02-05 12:34:19 -08:00
|
|
|
import {
|
|
|
|
Proposal,
|
|
|
|
ProposalUpdates,
|
|
|
|
Comment,
|
|
|
|
ProposalContributions,
|
|
|
|
LoadableProposalPage,
|
2019-02-17 18:13:24 -08:00
|
|
|
Moreable,
|
2019-02-05 12:34:19 -08:00
|
|
|
} from 'types';
|
2019-06-11 16:55:36 -07:00
|
|
|
import { PROPOSAL_SORT } from 'api/constants';
|
2018-09-10 09:55:26 -07:00
|
|
|
|
2019-02-11 21:10:09 -08:00
|
|
|
export interface ProposalDetail extends Proposal {
|
|
|
|
isRequestingPayout: boolean;
|
|
|
|
requestPayoutError: string;
|
|
|
|
isRejectingPayout: boolean;
|
|
|
|
rejectPayoutError: string;
|
|
|
|
isAcceptingPayout: boolean;
|
|
|
|
acceptPayoutError: string;
|
|
|
|
}
|
|
|
|
|
2018-09-10 09:55:26 -07:00
|
|
|
export interface ProposalState {
|
2019-02-05 12:34:19 -08:00
|
|
|
page: LoadableProposalPage;
|
|
|
|
|
2019-02-11 21:42:21 -08:00
|
|
|
detail: null | ProposalDetail;
|
2019-02-05 12:34:19 -08:00
|
|
|
isFetchingDetail: boolean;
|
|
|
|
detailError: null | string;
|
2018-09-10 09:55:26 -07:00
|
|
|
|
2019-02-17 18:13:24 -08:00
|
|
|
detailComments: Moreable<Comment>;
|
2018-09-10 09:55:26 -07:00
|
|
|
|
|
|
|
proposalUpdates: { [id: string]: ProposalUpdates };
|
|
|
|
updatesError: null | string;
|
|
|
|
isFetchingUpdates: boolean;
|
2018-09-18 15:15:01 -07:00
|
|
|
|
2019-01-09 12:48:41 -08:00
|
|
|
proposalContributions: { [id: string]: ProposalContributions };
|
|
|
|
fetchContributionsError: null | string;
|
|
|
|
isFetchingContributions: boolean;
|
|
|
|
|
2018-09-18 15:15:01 -07:00
|
|
|
isPostCommentPending: boolean;
|
|
|
|
postCommentError: null | string;
|
2019-01-09 13:32:51 -08:00
|
|
|
|
|
|
|
isDeletingContribution: boolean;
|
|
|
|
deleteContributionError: null | string;
|
2018-09-10 09:55:26 -07:00
|
|
|
}
|
|
|
|
|
2019-02-11 21:42:21 -08:00
|
|
|
export const PROPOSAL_DETAIL_INITIAL_STATE = {
|
2019-02-11 21:10:09 -08:00
|
|
|
isRequestingPayout: false,
|
|
|
|
requestPayoutError: '',
|
|
|
|
isRejectingPayout: false,
|
|
|
|
rejectPayoutError: '',
|
|
|
|
isAcceptingPayout: false,
|
|
|
|
acceptPayoutError: '',
|
|
|
|
};
|
|
|
|
|
2018-09-10 09:55:26 -07:00
|
|
|
export const INITIAL_STATE: ProposalState = {
|
2019-02-05 12:34:19 -08:00
|
|
|
page: {
|
|
|
|
page: 1,
|
|
|
|
pageSize: 0,
|
|
|
|
total: 0,
|
|
|
|
search: '',
|
|
|
|
sort: PROPOSAL_SORT.NEWEST,
|
|
|
|
filters: {
|
|
|
|
category: [],
|
2019-06-11 16:55:36 -07:00
|
|
|
stage: [],
|
2019-02-05 12:34:19 -08:00
|
|
|
},
|
|
|
|
items: [],
|
|
|
|
hasFetched: false,
|
|
|
|
isFetching: false,
|
|
|
|
fetchError: null,
|
|
|
|
fetchTime: 0,
|
|
|
|
},
|
|
|
|
|
|
|
|
detail: null,
|
|
|
|
isFetchingDetail: false,
|
|
|
|
detailError: null,
|
2018-09-10 09:55:26 -07:00
|
|
|
|
2019-02-17 18:13:24 -08:00
|
|
|
detailComments: {
|
|
|
|
pages: [],
|
|
|
|
hasMore: false,
|
|
|
|
page: 1,
|
|
|
|
pageSize: 0,
|
|
|
|
total: 0,
|
|
|
|
search: '',
|
|
|
|
sort: '',
|
|
|
|
filters: [],
|
|
|
|
isFetching: false,
|
|
|
|
hasFetched: false,
|
|
|
|
fetchError: '',
|
|
|
|
fetchTime: 0,
|
|
|
|
parentId: null,
|
|
|
|
},
|
2018-09-10 09:55:26 -07:00
|
|
|
|
|
|
|
proposalUpdates: {},
|
|
|
|
updatesError: null,
|
|
|
|
isFetchingUpdates: false,
|
2018-09-18 15:15:01 -07:00
|
|
|
|
2019-01-09 12:48:41 -08:00
|
|
|
proposalContributions: {},
|
|
|
|
fetchContributionsError: null,
|
|
|
|
isFetchingContributions: false,
|
|
|
|
|
2018-09-18 15:15:01 -07:00
|
|
|
isPostCommentPending: false,
|
|
|
|
postCommentError: null,
|
2019-01-09 13:32:51 -08:00
|
|
|
|
|
|
|
isDeletingContribution: false,
|
|
|
|
deleteContributionError: null,
|
2018-09-10 09:55:26 -07:00
|
|
|
};
|
|
|
|
|
2018-11-02 09:24:28 -07:00
|
|
|
function addUpdates(state: ProposalState, payload: ProposalUpdates) {
|
2018-09-10 09:55:26 -07:00
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
proposalUpdates: {
|
|
|
|
...state.proposalUpdates,
|
2018-11-02 09:24:28 -07:00
|
|
|
[payload.proposalId]: payload,
|
2018-09-10 09:55:26 -07:00
|
|
|
},
|
|
|
|
isFetchingUpdates: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-01-09 12:48:41 -08:00
|
|
|
function addContributions(state: ProposalState, payload: ProposalContributions) {
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
proposalContributions: {
|
|
|
|
...state.proposalContributions,
|
|
|
|
[payload.proposalId]: payload,
|
|
|
|
},
|
|
|
|
isFetchingContributions: false,
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-09-18 15:15:01 -07:00
|
|
|
interface PostCommentPayload {
|
2018-12-21 10:47:50 -08:00
|
|
|
proposalId: Proposal['proposalId'];
|
2018-09-18 15:15:01 -07:00
|
|
|
comment: Comment;
|
2018-11-08 11:14:52 -08:00
|
|
|
parentCommentId?: Comment['id'];
|
2018-09-18 15:15:01 -07:00
|
|
|
}
|
|
|
|
function addPostedComment(state: ProposalState, payload: PostCommentPayload) {
|
2019-02-17 18:13:24 -08:00
|
|
|
const { comment, parentCommentId } = payload;
|
|
|
|
// clone so we can mutate with great abandon!
|
|
|
|
const pages = cloneDeep(state.detailComments.pages);
|
|
|
|
if (!parentCommentId) {
|
|
|
|
// its a new comment, pop it into the very first position
|
|
|
|
if (pages[0]) {
|
|
|
|
pages[0].unshift(comment);
|
|
|
|
} else {
|
|
|
|
pages[0] = [comment];
|
2018-09-18 15:15:01 -07:00
|
|
|
}
|
|
|
|
} else {
|
2019-02-17 18:13:24 -08:00
|
|
|
// recursive populate replies for nested comment
|
|
|
|
const f = (id: number, p: Comment) => {
|
|
|
|
if (p.id === id) {
|
|
|
|
p.replies.unshift(comment);
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
p.replies.forEach(x => f(id, x));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// pages > page > comments
|
|
|
|
pages.forEach(p =>
|
|
|
|
p.forEach(c => {
|
|
|
|
f(parentCommentId, c);
|
|
|
|
}),
|
|
|
|
);
|
2018-09-18 15:15:01 -07:00
|
|
|
}
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
isPostCommentPending: false,
|
2019-02-17 18:13:24 -08:00
|
|
|
detailComments: {
|
|
|
|
...state.detailComments,
|
|
|
|
pages,
|
|
|
|
total: state.detailComments.total + 1,
|
2018-09-18 15:15:01 -07:00
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-02-18 14:35:21 -08:00
|
|
|
function updateCommentInStore(
|
|
|
|
state: ProposalState,
|
|
|
|
commentId: Comment['id'],
|
|
|
|
update: Partial<Comment>,
|
|
|
|
) {
|
|
|
|
// clone so we can mutate with great abandon!
|
|
|
|
const pages = cloneDeep(state.detailComments.pages);
|
|
|
|
// recursive populate replies for nested comment
|
|
|
|
const f = (id: number, p: Comment) => {
|
|
|
|
if (p.id === id) {
|
|
|
|
Object.entries(update).forEach(([k, v]) => {
|
|
|
|
(p as any)[k] = v;
|
|
|
|
});
|
|
|
|
return;
|
|
|
|
} else {
|
|
|
|
p.replies.forEach(x => f(id, x));
|
|
|
|
}
|
|
|
|
};
|
|
|
|
// pages > page > comments
|
|
|
|
pages.forEach(p =>
|
|
|
|
p.forEach(c => {
|
|
|
|
f(commentId, c);
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
isPostCommentPending: false,
|
|
|
|
detailComments: {
|
|
|
|
...state.detailComments,
|
|
|
|
pages,
|
|
|
|
total: state.detailComments.total + 1,
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2018-09-10 09:55:26 -07:00
|
|
|
export default (state = INITIAL_STATE, action: any) => {
|
|
|
|
const { payload } = action;
|
|
|
|
switch (action.type) {
|
2019-02-05 12:34:19 -08:00
|
|
|
case types.SET_PROPOSAL_PAGE:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
page: {
|
|
|
|
...state.page,
|
|
|
|
...payload,
|
|
|
|
page: payload.page || 1, // reset page to 1 for non-page changes
|
|
|
|
},
|
|
|
|
};
|
2018-09-10 09:55:26 -07:00
|
|
|
case types.PROPOSALS_DATA_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
2019-02-05 12:34:19 -08:00
|
|
|
page: {
|
|
|
|
...state.page,
|
|
|
|
isFetching: true,
|
|
|
|
fetchError: null,
|
|
|
|
},
|
2018-09-10 09:55:26 -07:00
|
|
|
};
|
|
|
|
case types.PROPOSALS_DATA_FULFILLED:
|
2019-02-05 12:34:19 -08:00
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
page: {
|
|
|
|
...payload,
|
|
|
|
isFetching: false,
|
|
|
|
hasFetched: true,
|
|
|
|
fetchTime: Date.now(),
|
|
|
|
},
|
|
|
|
};
|
2018-09-10 09:55:26 -07:00
|
|
|
case types.PROPOSALS_DATA_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
2019-02-05 12:34:19 -08:00
|
|
|
page: {
|
|
|
|
...state.page,
|
|
|
|
isFetching: false,
|
|
|
|
hasFetched: false,
|
|
|
|
fetchError: (payload && payload.message) || payload.toString(),
|
|
|
|
},
|
2018-09-10 09:55:26 -07:00
|
|
|
};
|
|
|
|
|
2019-02-05 12:34:19 -08:00
|
|
|
case types.PROPOSAL_DATA_PENDING:
|
|
|
|
// check if this proposal is in the page list, and optimistically set it
|
|
|
|
const loadedInPage = state.page.items.find(
|
|
|
|
p => p.proposalId === payload.proposalId,
|
|
|
|
);
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail:
|
|
|
|
// if requesting same proposal, leave the detail object
|
|
|
|
state.detail && state.detail.proposalId === payload.proposalId
|
|
|
|
? state.detail
|
2019-02-11 21:10:09 -08:00
|
|
|
: { ...loadedInPage, ...PROPOSAL_DETAIL_INITIAL_STATE } || null,
|
2019-02-05 12:34:19 -08:00
|
|
|
isFetchingDetail: true,
|
|
|
|
detailError: null,
|
|
|
|
};
|
2018-09-10 09:55:26 -07:00
|
|
|
case types.PROPOSAL_DATA_FULFILLED:
|
2019-02-05 12:34:19 -08:00
|
|
|
return {
|
|
|
|
...state,
|
2019-02-11 21:10:09 -08:00
|
|
|
detail: { ...payload, ...PROPOSAL_DETAIL_INITIAL_STATE },
|
2019-02-05 12:34:19 -08:00
|
|
|
isFetchingDetail: false,
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_DATA_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: null,
|
|
|
|
isFetchingDetail: false,
|
|
|
|
detailError: (payload && payload.message) || payload.toString(),
|
|
|
|
};
|
2018-09-10 09:55:26 -07:00
|
|
|
|
2019-02-11 21:10:09 -08:00
|
|
|
case types.PROPOSAL_PAYOUT_REQUEST_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: {
|
|
|
|
...state.detail,
|
|
|
|
isRequestingPayout: true,
|
|
|
|
requestPayoutError: '',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_PAYOUT_REQUEST_FULFILLED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: { ...payload, ...PROPOSAL_DETAIL_INITIAL_STATE },
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_PAYOUT_REQUEST_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: {
|
|
|
|
...state.detail,
|
|
|
|
isRequestingPayout: false,
|
|
|
|
requestPayoutError: (payload && payload.message) || payload.toString(),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
case types.PROPOSAL_PAYOUT_REJECT_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: {
|
|
|
|
...state.detail,
|
|
|
|
isRejectingPayout: true,
|
|
|
|
rejectPayoutError: '',
|
|
|
|
},
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_PAYOUT_REJECT_FULFILLED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: { ...payload, ...PROPOSAL_DETAIL_INITIAL_STATE },
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_PAYOUT_REJECT_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: {
|
|
|
|
...state.detail,
|
|
|
|
isRejectingPayout: false,
|
|
|
|
rejectPayoutError: (payload && payload.message) || payload.toString(),
|
|
|
|
},
|
|
|
|
};
|
|
|
|
|
|
|
|
case types.PROPOSAL_PAYOUT_ACCEPT_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: {
|
|
|
|
...state.detail,
|
|
|
|
isAcceptingPayout: true,
|
2019-02-13 12:30:58 -08:00
|
|
|
acceptPayoutError: '',
|
2019-02-11 21:10:09 -08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_PAYOUT_ACCEPT_FULFILLED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: { ...payload, ...PROPOSAL_DETAIL_INITIAL_STATE },
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_PAYOUT_ACCEPT_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detail: {
|
|
|
|
...state.detail,
|
|
|
|
isAcceptingPayout: false,
|
2019-02-13 12:30:58 -08:00
|
|
|
acceptPayoutError: (payload && payload.message) || payload.toString(),
|
2019-02-11 21:10:09 -08:00
|
|
|
},
|
|
|
|
};
|
|
|
|
|
2018-09-10 09:55:26 -07:00
|
|
|
case types.PROPOSAL_COMMENTS_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
2019-02-17 18:13:24 -08:00
|
|
|
detailComments: pendingMoreablePage(state.detailComments, payload),
|
2018-09-10 09:55:26 -07:00
|
|
|
};
|
|
|
|
case types.PROPOSAL_COMMENTS_FULFILLED:
|
2019-02-17 18:13:24 -08:00
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
detailComments: fulfilledMoreablePage(state.detailComments, payload),
|
|
|
|
};
|
2018-09-10 09:55:26 -07:00
|
|
|
case types.PROPOSAL_COMMENTS_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
2019-02-17 18:13:24 -08:00
|
|
|
detailComments: {
|
|
|
|
...state.detailComments,
|
|
|
|
hasFetched: true,
|
|
|
|
isFetching: false,
|
|
|
|
fetchError: (payload && payload.message) || payload.toString(),
|
|
|
|
},
|
2018-09-10 09:55:26 -07:00
|
|
|
};
|
|
|
|
|
2019-02-18 14:35:21 -08:00
|
|
|
case types.REPORT_PROPOSAL_COMMENT_FULFILLED:
|
|
|
|
return updateCommentInStore(state, payload.commentId, { reported: true });
|
|
|
|
|
2019-10-24 10:32:00 -07:00
|
|
|
case types.UPDATE_PROPOSAL_COMMENT:
|
|
|
|
return updateCommentInStore(state, payload.commentId, payload.commentUpdate);
|
|
|
|
|
2018-09-10 09:55:26 -07:00
|
|
|
case types.PROPOSAL_UPDATES_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
updatesError: null,
|
|
|
|
isFetchingUpdates: true,
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_UPDATES_FULFILLED:
|
|
|
|
return addUpdates(state, payload);
|
|
|
|
case types.PROPOSAL_UPDATES_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
2019-03-13 14:39:50 -07:00
|
|
|
updatesError: (payload && payload.message) || payload.toString(),
|
2018-09-10 09:55:26 -07:00
|
|
|
isFetchingUpdates: false,
|
|
|
|
};
|
|
|
|
|
2019-01-09 12:48:41 -08:00
|
|
|
case types.PROPOSAL_CONTRIBUTIONS_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
fetchContributionsError: null,
|
|
|
|
isFetchingContributions: true,
|
|
|
|
};
|
|
|
|
case types.PROPOSAL_CONTRIBUTIONS_FULFILLED:
|
|
|
|
return addContributions(state, payload);
|
|
|
|
case types.PROPOSAL_CONTRIBUTIONS_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
2019-03-13 14:39:50 -07:00
|
|
|
fetchContributionsError: (payload && payload.message) || payload.toString(),
|
2019-01-09 12:48:41 -08:00
|
|
|
isFetchingContributions: false,
|
|
|
|
};
|
|
|
|
|
2018-09-18 15:15:01 -07:00
|
|
|
case types.POST_PROPOSAL_COMMENT_PENDING:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
isPostCommentPending: true,
|
|
|
|
postCommentError: null,
|
|
|
|
};
|
|
|
|
case types.POST_PROPOSAL_COMMENT_FULFILLED:
|
|
|
|
return addPostedComment(state, payload);
|
|
|
|
case types.POST_PROPOSAL_COMMENT_REJECTED:
|
|
|
|
return {
|
|
|
|
...state,
|
|
|
|
isPostCommentPending: false,
|
|
|
|
postCommentError: payload,
|
|
|
|
};
|
|
|
|
|
2018-09-10 09:55:26 -07:00
|
|
|
default:
|
|
|
|
return state;
|
|
|
|
}
|
|
|
|
};
|