From a10223465bbd721ce1c5a8e24206172c3635834b Mon Sep 17 00:00:00 2001 From: Victor Emmanuel <33874323+vrrayz@users.noreply.github.com> Date: Tue, 30 Jan 2024 12:33:19 +0100 Subject: [PATCH 1/3] Change openings reward display (#4745) * change reward per blocks display * update rewaeds display --- .../ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx | 8 ++------ .../src/app/pages/WorkingGroups/WorkingGroupsOpening.tsx | 8 ++------ .../components/OpeningsList/Opening/OpeningDetails.tsx | 7 +++---- .../components/OpeningsList/Opening/OpeningListItem.tsx | 7 +++---- packages/ui/src/working-groups/model/asWeeklyRewards.ts | 9 +++++++++ 5 files changed, 19 insertions(+), 20 deletions(-) create mode 100644 packages/ui/src/working-groups/model/asWeeklyRewards.ts diff --git a/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx b/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx index c0588c9bf6..48821af58a 100644 --- a/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx +++ b/packages/ui/src/app/pages/WorkingGroups/UpcomingOpening.tsx @@ -17,13 +17,12 @@ import { NumericValueStat } from '@/common/components/statistics/NumericValueSta import { TextSmall } from '@/common/components/typography' import { ApplicationStatusWrapper } from '@/working-groups/components/ApplicationStatusWrapper' import { OpeningIcon } from '@/working-groups/components/OpeningIcon' -import { useRewardPeriod } from '@/working-groups/hooks/useRewardPeriod' import { useUpcomingOpening } from '@/working-groups/hooks/useUpcomingOpening' +import { asWeeklyRewards } from '@/working-groups/model/asWeeklyRewards' export const UpcomingOpening = () => { const { id } = useParams<{ id: string }>() const { isLoading, opening } = useUpcomingOpening(id) - const rewardPeriod = useRewardPeriod(opening?.groupId) if (isLoading || !opening) { return ( @@ -82,10 +81,7 @@ export const UpcomingOpening = () => { value={opening.expectedEnding} from={opening.expectedStart} /> - + {opening.hiringLimit ? ( ) : ( diff --git a/packages/ui/src/app/pages/WorkingGroups/WorkingGroupsOpening.tsx b/packages/ui/src/app/pages/WorkingGroups/WorkingGroupsOpening.tsx index 57e32ec0b4..80cff8fc40 100644 --- a/packages/ui/src/app/pages/WorkingGroups/WorkingGroupsOpening.tsx +++ b/packages/ui/src/app/pages/WorkingGroups/WorkingGroupsOpening.tsx @@ -35,8 +35,8 @@ import { ApplicationStatusWrapper } from '@/working-groups/components/Applicatio import { OpeningIcon } from '@/working-groups/components/OpeningIcon' import { MappedStatuses, OpeningStatuses, WorkingGroupsRoutes } from '@/working-groups/constants' import { useOpening } from '@/working-groups/hooks/useOpening' -import { useRewardPeriod } from '@/working-groups/hooks/useRewardPeriod' import { ApplyForRoleModalCall } from '@/working-groups/modals/ApplyForRoleModal' +import { asWeeklyRewards } from '@/working-groups/model/asWeeklyRewards' import { urlParamToOpeningId } from '@/working-groups/model/workingGroupName' import { WorkingGroupOpening as WorkingGroupOpeningType } from '@/working-groups/types' @@ -62,7 +62,6 @@ export const WorkingGroupOpening = () => { return activeApplications.find(({ id }) => id === activeMembership?.id) } }, [opening?.id, activeMembership?.id]) - const rewardPeriod = useRewardPeriod(opening?.groupId) if (isLoading || !opening) { return ( @@ -150,10 +149,7 @@ export const WorkingGroupOpening = () => { - + { const { showModal } = useModal() - const rewardPeriod = useRewardPeriod(opening.groupId) const groupName = groupNameToURLParam(nameMapping(opening.groupName)) const openingRoute = `/working-groups/openings/${groupName}-${opening.runtimeId}` @@ -42,9 +41,9 @@ export const OpeningDetails = ({ opening, onClick, past }: OpeningListItemProps) - + - Reward per {rewardPeriod?.toString()} blocks + Reward per week diff --git a/packages/ui/src/working-groups/components/OpeningsList/Opening/OpeningListItem.tsx b/packages/ui/src/working-groups/components/OpeningsList/Opening/OpeningListItem.tsx index 05272778f4..f1172ac654 100644 --- a/packages/ui/src/working-groups/components/OpeningsList/Opening/OpeningListItem.tsx +++ b/packages/ui/src/working-groups/components/OpeningsList/Opening/OpeningListItem.tsx @@ -15,7 +15,7 @@ import { ToggleableItemWrap, OpenItemSummaryColumn, } from '@/working-groups/components/ToggleableItemStyledComponents' -import { useRewardPeriod } from '@/working-groups/hooks/useRewardPeriod' +import { asWeeklyRewards } from '@/working-groups/model/asWeeklyRewards' import { WorkingGroupOpening } from '@/working-groups/types' export type OpeningListItemProps = { @@ -25,7 +25,6 @@ export type OpeningListItemProps = { } export const OpeningListItem = ({ opening, past, onClick }: OpeningListItemProps) => { - const rewardPeriod = useRewardPeriod(opening.groupId) const hiringTarget = opening.hiring.limit || 1 return ( @@ -44,9 +43,9 @@ export const OpeningListItem = ({ opening, past, onClick }: OpeningListItemProps - + - Reward per {rewardPeriod?.toString()} blocks. + Reward per week. {opening.applicants} diff --git a/packages/ui/src/working-groups/model/asWeeklyRewards.ts b/packages/ui/src/working-groups/model/asWeeklyRewards.ts new file mode 100644 index 0000000000..bd9cb6ddb6 --- /dev/null +++ b/packages/ui/src/working-groups/model/asWeeklyRewards.ts @@ -0,0 +1,9 @@ +import BN from 'bn.js' + +import { A_WEEK } from '@/common/constants' +import { MILLISECONDS_PER_BLOCK } from '@/common/model/formatters' + +export const asWeeklyRewards = (rewardPerBlock: BN) => { + const BLOCKS_PER_WEEK = A_WEEK / MILLISECONDS_PER_BLOCK + return rewardPerBlock.muln(BLOCKS_PER_WEEK) +} From 3c166ec350530b58df15c8bc6fbeadc600734074 Mon Sep 17 00:00:00 2001 From: Victor Emmanuel <33874323+vrrayz@users.noreply.github.com> Date: Tue, 30 Jan 2024 16:54:49 +0100 Subject: [PATCH 2/3] Cancel proposal feature (#4750) * cancel proposal feature * cancel proposal story * Update packages/ui/src/app/pages/Proposals/ProposalPreview.tsx Co-authored-by: Theophile Sandoz --------- Co-authored-by: Theophile Sandoz --- packages/ui/src/app/GlobalModals.tsx | 3 + .../Proposals/ProposalPreview.stories.tsx | 37 ++++++++++ .../app/pages/Proposals/ProposalPreview.tsx | 6 ++ .../components/CancelProposalButton.tsx | 27 +++++++ .../CancelProposal/CancelProposalModal.tsx | 70 +++++++++++++++++++ .../proposals/modals/CancelProposal/index.ts | 2 + .../proposals/modals/CancelProposal/types.ts | 4 ++ 7 files changed, 149 insertions(+) create mode 100644 packages/ui/src/proposals/components/CancelProposalButton.tsx create mode 100644 packages/ui/src/proposals/modals/CancelProposal/CancelProposalModal.tsx create mode 100644 packages/ui/src/proposals/modals/CancelProposal/index.ts create mode 100644 packages/ui/src/proposals/modals/CancelProposal/types.ts diff --git a/packages/ui/src/app/GlobalModals.tsx b/packages/ui/src/app/GlobalModals.tsx index 8b86bbf08d..f3bfdc6a4d 100644 --- a/packages/ui/src/app/GlobalModals.tsx +++ b/packages/ui/src/app/GlobalModals.tsx @@ -67,6 +67,7 @@ import { SwitchMemberModal, SwitchMemberModalCall } from '@/memberships/modals/S import { TransferInviteModal, TransferInvitesModalCall } from '@/memberships/modals/TransferInviteModal' import { UpdateMembershipModal, UpdateMembershipModalCall } from '@/memberships/modals/UpdateMembershipModal' import { AddNewProposalModal, AddNewProposalModalCall } from '@/proposals/modals/AddNewProposal' +import { CancelProposalModal, CancelProposalModalCall } from '@/proposals/modals/CancelProposal' import { VoteForProposalModal, VoteForProposalModalCall } from '@/proposals/modals/VoteForProposal' import { VoteRationaleModalCall } from '@/proposals/modals/VoteRationale/types' import { VoteRationale } from '@/proposals/modals/VoteRationale/VoteRationale' @@ -133,6 +134,7 @@ export type ModalNames = | ModalName | ModalName | ModalName + | ModalName const modals: Record = { Member: , @@ -186,6 +188,7 @@ const modals: Record = { EmailSubscriptionModal: , EmailConfirmationModal: , NominatingRedirect: , + CancelProposalModal: , } const GUEST_ACCESSIBLE_MODALS: ModalNames[] = [ diff --git a/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx b/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx index e8244641a6..0ba815ce41 100644 --- a/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx +++ b/packages/ui/src/app/pages/Proposals/ProposalPreview.stories.tsx @@ -51,6 +51,7 @@ type Args = { vote2: VoteArg vote3: VoteArg onVote: jest.Mock + onCancel: jest.Mock } type Story = StoryObj> @@ -65,6 +66,7 @@ export default { vote2: { control: { type: 'inline-radio' }, options: voteArgs }, vote3: { control: { type: 'inline-radio' }, options: voteArgs }, onVote: { action: 'ProposalsEngine.Voted' }, + onCancel: { action: 'ProposalsEngine.Cancelled' }, }, args: { @@ -142,6 +144,11 @@ export default { onSend: args.onVote, failure: parameters.txFailure, }, + cancelProposal: { + event: 'Cancelled', + onSend: args.onCancel, + failure: parameters.txFailure, + }, }, }, }, @@ -608,3 +615,33 @@ export const TestVoteTxFailure: Story = { expect(await modal.findByText('Some error message')) }, } + +export const TestCancelProposalHappy: Story = { + args: { type: 'SignalProposalDetails', isCouncilMember: false, isProposer: true }, + + name: 'Test CancelProposal Happy', + + play: async ({ canvasElement, step, args: { onCancel } }) => { + const activeMember = member('alice') + + const screen = within(canvasElement) + const modal = withinModal(canvasElement) + + await step('Cancel', async () => { + await userEvent.click(screen.getByText('Cancel Proposal')) + + await step('Sign', async () => { + expect(await modal.findByText('Authorize transaction')) + expect(modal.getByText('You intend to cancel your proposal.')) + + await userEvent.click(modal.getByText(/^Sign And Cancel Proposal/)) + }) + + await step('Confirm', async () => { + expect(await modal.findByText('Your propsal has been cancelled.')) + + expect(onCancel).toHaveBeenLastCalledWith(activeMember.id, PROPOSAL_DATA.id) + }) + }) + }, +} diff --git a/packages/ui/src/app/pages/Proposals/ProposalPreview.tsx b/packages/ui/src/app/pages/Proposals/ProposalPreview.tsx index d2714fe570..3f084c52cc 100644 --- a/packages/ui/src/app/pages/Proposals/ProposalPreview.tsx +++ b/packages/ui/src/app/pages/Proposals/ProposalPreview.tsx @@ -23,6 +23,7 @@ import { getUrl } from '@/common/utils/getUrl' import { useElectedCouncil } from '@/council/hooks/useElectedCouncil' import { MemberInfo } from '@/memberships/components' import { useMyMemberships } from '@/memberships/hooks/useMyMemberships' +import { CancelProposalButton } from '@/proposals/components/CancelProposalButton' import { ProposalDetails } from '@/proposals/components/ProposalDetails/ProposalDetails' import { ProposalDiscussions } from '@/proposals/components/ProposalDiscussions' import { ProposalHistory } from '@/proposals/components/ProposalHistory' @@ -117,6 +118,11 @@ export const ProposalPreview = () => { {proposal.title} + {active?.id === proposal.proposer.id && + proposal.votes.length === 0 && + (proposal.status === 'deciding' || proposal.status === 'dormant') && ( + + )} {active?.isCouncilMember && proposal.status === 'deciding' && (!hasVoted ? ( diff --git a/packages/ui/src/proposals/components/CancelProposalButton.tsx b/packages/ui/src/proposals/components/CancelProposalButton.tsx new file mode 100644 index 0000000000..6f38d32a13 --- /dev/null +++ b/packages/ui/src/proposals/components/CancelProposalButton.tsx @@ -0,0 +1,27 @@ +import React, { useCallback } from 'react' + +import { ButtonSecondary } from '@/common/components/buttons' +import { useModal } from '@/common/hooks/useModal' +import { Member } from '@/memberships/types' + +import { CancelProposalModalCall } from '../modals/CancelProposal' + +interface Props { + member: Member + proposalId: string +} + +export const CancelProposalButton = ({ member, proposalId }: Props) => { + const { showModal } = useModal() + const cancelProposalModal = useCallback(() => { + showModal({ + modal: 'CancelProposalModal', + data: { member, proposalId }, + }) + }, []) + return ( + + Cancel Proposal + + ) +} diff --git a/packages/ui/src/proposals/modals/CancelProposal/CancelProposalModal.tsx b/packages/ui/src/proposals/modals/CancelProposal/CancelProposalModal.tsx new file mode 100644 index 0000000000..17c0d682ce --- /dev/null +++ b/packages/ui/src/proposals/modals/CancelProposal/CancelProposalModal.tsx @@ -0,0 +1,70 @@ +import React, { useEffect, useMemo } from 'react' + +import { useTransactionFee } from '@/accounts/hooks/useTransactionFee' +import { InsufficientFundsModal } from '@/accounts/modals/InsufficientFundsModal' +import { useApi } from '@/api/hooks/useApi' +import { TextMedium } from '@/common/components/typography' +import { useMachine } from '@/common/hooks/useMachine' +import { useModal } from '@/common/hooks/useModal' +import { SignTransactionModal } from '@/common/modals/SignTransactionModal/SignTransactionModal' +import { defaultTransactionModalMachine } from '@/common/model/machines/defaultTransactionModalMachine' + +import { CancelProposalModalCall } from './types' + +export const CancelProposalModal = () => { + const { hideModal, modalData } = useModal() + const { member, proposalId } = modalData + const machine = useMemo( + () => + defaultTransactionModalMachine( + 'There was a problem cancelling your proposal.', + 'Your propsal has been cancelled.' + ), + [] + ) + const [state, send] = useMachine(machine, { context: { validateBeforeTransaction: true } }) + const { api, isConnected } = useApi() + + const { transaction, feeInfo } = useTransactionFee( + member.controllerAccount, + () => { + if (api && isConnected) { + return api.tx.proposalsEngine.cancelProposal(member.id, proposalId) + } + }, + [modalData, isConnected] + ) + + useEffect(() => { + if (state.matches('requirementsVerification')) { + if (transaction && feeInfo) { + feeInfo.canAfford && send('PASS') + !feeInfo.canAfford && send('FAIL') + } + } + + if (state.matches('beforeTransaction')) { + send(feeInfo?.canAfford ? 'PASS' : 'FAIL') + } + }, [state.value, member, transaction, feeInfo?.canAfford]) + + if (state.matches('transaction') && transaction && member) { + return ( + + You intend to cancel your proposal. + + ) + } + + if (state.matches('requirementsFailed') && member && feeInfo) { + return ( + + ) + } + return null +} diff --git a/packages/ui/src/proposals/modals/CancelProposal/index.ts b/packages/ui/src/proposals/modals/CancelProposal/index.ts new file mode 100644 index 0000000000..3ae4dd6f55 --- /dev/null +++ b/packages/ui/src/proposals/modals/CancelProposal/index.ts @@ -0,0 +1,2 @@ +export type { CancelProposalModalCall } from './types' +export * from './CancelProposalModal' diff --git a/packages/ui/src/proposals/modals/CancelProposal/types.ts b/packages/ui/src/proposals/modals/CancelProposal/types.ts new file mode 100644 index 0000000000..f506ea0e0c --- /dev/null +++ b/packages/ui/src/proposals/modals/CancelProposal/types.ts @@ -0,0 +1,4 @@ +import { ModalWithDataCall } from '@/common/providers/modal/types' +import { Member } from '@/memberships/types' + +export type CancelProposalModalCall = ModalWithDataCall<'CancelProposalModal', { member: Member; proposalId: string }> From 42b99837f393904eeebfe4f15c92a53db6b14a3c Mon Sep 17 00:00:00 2001 From: Theophile Sandoz Date: Mon, 5 Feb 2024 18:43:23 +0100 Subject: [PATCH 3/3] Update version to `2.5.0` --- CHANGELOG.md | 17 ++++++++++++++++- packages/ui/package.json | 2 +- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 010c1aca5a..fb4efdc772 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,19 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.5.0] - 2024-02-05 + +### Added +- Cancel proposal button. + +### Changed +- Display weekly opening rewards instead of daily. + +## [2.4.2] - 2024-01-29 + +### Fixed +- Fix infinite proposal page reload for CMs. + ## [2.4.1] - 2024-01-29 ### Fixed @@ -292,7 +305,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [0.1.1] - 2022-12-02 -[unreleased]: https://github.com/Joystream/pioneer/compare/v2.4.1...HEAD +[unreleased]: https://github.com/Joystream/pioneer/compare/v2.5.0...HEAD +[2.5.0]: https://github.com/Joystream/pioneer/compare/v2.4.2...v2.5.0 +[2.4.2]: https://github.com/Joystream/pioneer/compare/v2.4.1...v2.4.2 [2.4.1]: https://github.com/Joystream/pioneer/compare/v2.4.0...v2.4.1 [2.4.0]: https://github.com/Joystream/pioneer/compare/v2.3.1...v2.4.0 [2.3.1]: https://github.com/Joystream/pioneer/compare/v2.3.0...v2.3.1 diff --git a/packages/ui/package.json b/packages/ui/package.json index 1a3482abd6..6f51f38871 100644 --- a/packages/ui/package.json +++ b/packages/ui/package.json @@ -1,6 +1,6 @@ { "name": "@joystream/pioneer", - "version": "2.4.1", + "version": "2.5.0", "license": "GPL-3.0-only", "scripts": { "build": "node --max_old_space_size=4096 ./build.js",