diff --git a/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.css b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.css new file mode 100644 index 0000000000..fecd6fc544 --- /dev/null +++ b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.css @@ -0,0 +1,207 @@ +@value radioButtonWidth: 340px; +@value buttonWidth: 160px; + +.row { + display: flex; + justify-content: space-between; + align-items: center; + font-size: var(--size-smallish); + font-weight: var(--weight-bold); + line-height: 18px; + color: var(--temp-grey-blue-7); + letter-spacing: 0.5px; +} + +.withoutPadding { + padding: 0; +} + +.forceRow { + align-items: center; + padding-top: 17px; + white-space: nowrap; +} + +.forceRow form { + max-width: 200px; +} + +.title { + display: flex; + justify-content: space-between; + align-items: center; + padding: 9px 0 12px; + width: 100%; + font-size: var(--size-medium-l); + font-weight: var(--weight-bold); + line-height: 24px; + color: var(--dark); + letter-spacing: 0.5px; +} + +.radioGroup { + display: flex; + flex-direction: column; + justify-content: center; + align-items: center; + font-size: var(--size-smallish); + font-weight: var(--weight-bold); + color: var(--dark); + letter-spacing: 0.5px; +} + +.radioWrapper > label { + display: flex; + align-items: center; + margin-bottom: 9px; + padding: 0 7px; + height: 40px; + width: radioButtonWidth; + border: 1px solid color-mod(var(--action-secondary) alpha(25%)); + border-radius: 2px; + background-color: color-mod(var(--action-secondary) alpha(5%)); +} + +.radioWrapper > label > span { + height: 27px; + width: 27px; + top: 0; + border: 1px solid color-mod(var(--action-secondary) alpha(25%)); + background-color: var(--colony-white); + + &::after { + height: 7px; + width: 7px; + } +} + +.selected > label { + border: 1px solid var(--primary); + background-color: color-mod(var(--primary) alpha(12%)); +} + +.textWrapper { + display: flex; + justify-content: space-between; + align-items: center; + margin-top: 11px; + margin-bottom: 8px; + width: radioButtonWidth; + color: var(--text); +} + +.textWrapper > span { + height: 16px; +} + +/* this selector is for tooltip wrapper */ +.textWrapper > span ~ div { + max-width: 250px; + white-space: normal; +} + +.message { + display: flex; + align-items: center; + margin-top: 21px; + padding: 0 15px; + height: 46px; + border-radius: 5px; + background-color: color-mod(var(--primary) alpha(15%)); + font-size: var(--size-smallish); + font-weight: var(--weight-bold); + color: var(--text); + letter-spacing: 0.5px; +} + +.messageWarning { + background-color: color-mod(var(--pink) alpha(15%)); +} + +.annotations { + margin: 24px 0 5px; + letter-spacing: 0.5px; +} + +.tokens { + height: 46px; + border-top: 1px solid var(--grey-blue-0); + border-bottom: 1px solid var(--grey-blue-0); +} + +.value { + display: flex; + align-items: center; + font-size: var(--size-smallish); + color: color-mod(var(--dark) alpha(85%)); +} + +.value span { + padding-right: 2px; + font-size: var(--size-smallish); +} + +.paddingBottom { + padding-bottom: 6px; +} + +.tokenIcon { + margin-right: 4px; + height: 16px; + width: 16px; +} + +.tokensWrapper { + text-align: right; +} + +.top { + align-items: flex-start; +} + +.motionActiveItem { + font-size: var(--size-tiny); + font-weight: var(--weight-bold); + color: var(--temp-grey-blue-7); +} + +.forceRow button > div > div { + margin-bottom: 4px; +} + +.toggleContainer i { + margin: 0 -20px 4px 21px; + + /* next lines are used for the correct tooltip arrow placement */ + transform: translateX(-25px); +} + +/* this selector is for tooltip wrapper */ +.toggleContainer > div > div:last-child { + max-width: 250px; + white-space: normal; +} + +/* the following lines center the toggle force row */ + +.toggleContainer label { + display: flex; + align-items: center; + margin-bottom: 0; +} + +.toggleContainer > div { + align-items: center; +} + +.toggleContainer > div > div:first-of-type { + height: 16px; +} + +.toggleContainer > div > span { + height: 16px; +} + +.toggleContainer > div > span > div { + height: 16px; +} diff --git a/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.css.d.ts b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.css.d.ts new file mode 100644 index 0000000000..dba11ee95a --- /dev/null +++ b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.css.d.ts @@ -0,0 +1,21 @@ +export const radioButtonWidth: string; +export const buttonWidth: string; +export const row: string; +export const withoutPadding: string; +export const forceRow: string; +export const title: string; +export const radioGroup: string; +export const radioWrapper: string; +export const selected: string; +export const textWrapper: string; +export const message: string; +export const messageWarning: string; +export const annotations: string; +export const tokens: string; +export const value: string; +export const paddingBottom: string; +export const tokenIcon: string; +export const tokensWrapper: string; +export const top: string; +export const motionActiveItem: string; +export const toggleContainer: string; diff --git a/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.tsx b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.tsx new file mode 100644 index 0000000000..35ea4834b8 --- /dev/null +++ b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/CancelIncorporationDialog.tsx @@ -0,0 +1,294 @@ +import React, { useCallback, useState } from 'react'; +import * as yup from 'yup'; +import classNames from 'classnames'; +import { FormikProps } from 'formik'; +import { defineMessages, FormattedMessage } from 'react-intl'; + +import { ActionForm, Annotations, Radio, Toggle } from '~core/Fields'; +import { ActionTypes } from '~redux/actionTypes'; +import { Colony, useLoggedInUser } from '~data/index'; +import TokenIcon from '~dashboard/HookedTokenIcon'; +import Dialog, { DialogSection } from '~core/Dialog'; +import Button from '~core/Button'; +import Heading from '~core/Heading'; +import { tokens as tokensMock } from '~dashboard/ExpenditurePage/ExpenditureSettings/constants'; +import { getTokenDecimalsWithFallback } from '~utils/tokens'; +import MotionDomainSelect from '~dashboard/MotionDomainSelect'; +import { useDialogActionPermissions } from '~utils/hooks/useDialogActionPermissions'; +import { hasRoot } from '~modules/users/checks'; +import { useTransformer } from '~utils/hooks'; +import { getAllUserRoles } from '~modules/transformers'; +import Numeral from '~core/Numeral'; +import QuestionMarkTooltip from '~core/QuestionMarkTooltip'; + +import { PenalizeType } from './types'; +import styles from './CancelIncorporationDialog.css'; + +const MSG = defineMessages({ + header: { + id: 'dashboard.CancelExpenditureDialog.CancelIncorporationDialog.header', + defaultMessage: 'Cancel incorporation', + }, + ownersStake: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.ownersStake`, + defaultMessage: `Owner's stake`, + }, + shouldBePenalized: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.shouldBePenalized`, + defaultMessage: 'Should the owner be penalized?', + }, + penalize: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.penalize`, + defaultMessage: 'Penalize', + }, + showMercy: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.showMercy`, + defaultMessage: 'Show mercy', + }, + penalizeMessage: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.penalizeMessage`, + defaultMessage: 'Owner will lose their stake and equivalent reputation.', + }, + mercyMessage: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.mercyMessage`, + defaultMessage: 'Owner will keep their stake and reputation.', + }, + createMotion: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.createMotion`, + defaultMessage: 'Create Motion', + }, + textareaLabel: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.textareaLabel`, + defaultMessage: `Explain why you're cancelling incorporation (optional)`, + }, + effectTooltip: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.effectTooltip`, + defaultMessage: `Decide what to do with the owner's stake when cancelling this incorporation.`, + }, + continue: { + id: `dashboard.CancelExpenditureDialog.CancelIncorporationDialog.continue`, + defaultMessage: 'Continue', + }, +}); + +const displayName = 'dashboard.CancelIncorporationDialog'; + +export const validationSchema = yup.object().shape({ + forceAction: yup.bool(), + effect: yup.string().required(), + annotation: yup.string(), +}); + +export interface FormValues { + forceAction: boolean; + effect: PenalizeType; + annotation: string; +} + +interface Props { + close: () => void; + colony: Colony; + onCancelIncorporation: (isForce: boolean) => void; + isVotingExtensionEnabled: boolean; +} + +const CancelIncorporationDialog = ({ + close, + colony, + onCancelIncorporation, + isVotingExtensionEnabled, +}: Props) => { + const [isForce, setIsForce] = useState(false); + + const getFormAction = useCallback( + (actionType: 'SUBMIT' | 'ERROR' | 'SUCCESS') => { + const actionEnd = actionType === 'SUBMIT' ? '' : `_${actionType}`; + + return !isForce + ? ActionTypes[`COLONY_ACTION_GENERIC${actionEnd}`] + : ActionTypes[`COLONY_ACTION_GENERIC${actionEnd}`]; + }, + [isForce], + ); + + const [token] = tokensMock; + const { walletAddress, username, ethereal } = useLoggedInUser(); + const allUserRoles = useTransformer(getAllUserRoles, [colony, walletAddress]); + + const hasRegisteredProfile = !!username && !ethereal; + const canCancelExpenditure = hasRegisteredProfile && hasRoot(allUserRoles); + + const [userHasPermission] = useDialogActionPermissions( + colony.colonyAddress, + canCancelExpenditure, + isVotingExtensionEnabled, + true, + ); + + return ( + + {(formValues: FormikProps) => { + if (formValues.values.forceAction !== isForce) { + setIsForce(formValues.values.forceAction); + } + return ( + + +
+ {colony && } + {canCancelExpenditure && isVotingExtensionEnabled && ( +
+ +
+ )} +
+ + + +
+ +
+ +
+ + + {token.symbol} +
+
+
+ +
+
+ + +
+
+ + + +
+
+ + + +
+
+
+ +
+
+ +
+ +
+
+ +
+ ); + }} +
+ ); +}; + +CancelIncorporationDialog.displayName = displayName; + +export default CancelIncorporationDialog; diff --git a/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/index.ts b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/index.ts new file mode 100644 index 0000000000..9fb4147cbc --- /dev/null +++ b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/index.ts @@ -0,0 +1 @@ +export { default } from './CancelIncorporationDialog'; diff --git a/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/types.ts b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/types.ts new file mode 100644 index 0000000000..0c47f25051 --- /dev/null +++ b/src/modules/dashboard/components/Dialogs/CancelIncorporationDialog/types.ts @@ -0,0 +1,4 @@ +export enum PenalizeType { + Penalize = 'penalize', + Mercy = 'mercy', +} diff --git a/src/modules/dashboard/components/ExpenditurePage/Stages/FormStages.tsx b/src/modules/dashboard/components/ExpenditurePage/Stages/FormStages.tsx index f6573bb4ce..70701baf91 100644 --- a/src/modules/dashboard/components/ExpenditurePage/Stages/FormStages.tsx +++ b/src/modules/dashboard/components/ExpenditurePage/Stages/FormStages.tsx @@ -43,7 +43,7 @@ interface Props { React.SetStateAction >; colony: Colony; - handleCancelExpenditure: () => void; + handleCancel: () => void; } const FormStages = ({ @@ -52,7 +52,7 @@ const FormStages = ({ setActiveStageId, setFormValues, colony, - handleCancelExpenditure, + handleCancel, }: Props) => { const { values, @@ -195,7 +195,7 @@ const FormStages = ({ handleButtonClick, handleDeleteDraft, handleSaveDraft, - handleCancelExpenditure, + handleCancel, colony, }} /> diff --git a/src/modules/dashboard/components/ExpenditurePage/Stages/LockedStages.tsx b/src/modules/dashboard/components/ExpenditurePage/Stages/LockedStages.tsx index 20aa034e4d..c15191c380 100644 --- a/src/modules/dashboard/components/ExpenditurePage/Stages/LockedStages.tsx +++ b/src/modules/dashboard/components/ExpenditurePage/Stages/LockedStages.tsx @@ -35,7 +35,7 @@ interface Props { activeStageId?: string; motion?: Motion; status?: Status; - handleCancelExpenditure?: () => void; + handleCancel?: () => void; colony: Colony; expenditureType?: ExpenditureTypes; formValues?: ValuesType; @@ -47,7 +47,7 @@ const LockedStages = ({ setActiveStageId, motion, status, - handleCancelExpenditure, + handleCancel, formValues, colony, expenditureType, @@ -100,7 +100,7 @@ const LockedStages = ({ motion={motion} colony={colony} activeStageId={activeStageId} - handleCancelExpenditure={handleCancelExpenditure} + handleCancel={handleCancel} fundingSources={formValues?.streaming?.fundingSources} /> ) : ( @@ -112,7 +112,7 @@ const LockedStages = ({ handleButtonClick, motion, status, - handleCancelExpenditure, + handleCancel, formValues, colony, expenditureType, diff --git a/src/modules/dashboard/components/ExpenditurePage/Stages/Stages.tsx b/src/modules/dashboard/components/ExpenditurePage/Stages/Stages.tsx index 00d6b8c77f..12b62c87f5 100644 --- a/src/modules/dashboard/components/ExpenditurePage/Stages/Stages.tsx +++ b/src/modules/dashboard/components/ExpenditurePage/Stages/Stages.tsx @@ -91,7 +91,7 @@ export interface Props { handleButtonClick: () => void; status?: Status; motion?: Motion; - handleCancelExpenditure?: () => void; + handleCancel?: () => void; colony: Colony; buttonDisabled?: boolean; formValues?: ValuesType; @@ -108,7 +108,7 @@ const Stages = ({ handleButtonClick, status, motion, - handleCancelExpenditure, + handleCancel, colony, buttonDisabled, formValues, @@ -301,7 +301,7 @@ const Stages = ({ [styles.iconButtonDisabled]: motion?.status === MotionStatus.Pending, })} - onClick={handleCancelExpenditure} + onClick={handleCancel} disabled={ isCancelled || motion?.status === MotionStatus.Pending } diff --git a/src/modules/dashboard/components/ExpenditurePage/Stages/StreamingStages/StreamingStagesLocked/StreamingStagesLocked.tsx b/src/modules/dashboard/components/ExpenditurePage/Stages/StreamingStages/StreamingStagesLocked/StreamingStagesLocked.tsx index 43d4cc3b87..0ec2aece51 100644 --- a/src/modules/dashboard/components/ExpenditurePage/Stages/StreamingStages/StreamingStagesLocked/StreamingStagesLocked.tsx +++ b/src/modules/dashboard/components/ExpenditurePage/Stages/StreamingStages/StreamingStagesLocked/StreamingStagesLocked.tsx @@ -102,7 +102,7 @@ export interface Props { motion?: Motion; colony?: Colony; activeStageId?: string; - handleCancelExpenditure?: () => void; + handleCancel?: () => void; fundingSources?: FundingSource[]; } @@ -111,7 +111,7 @@ const StreamingStagesLocked = ({ motion, colony, activeStageId, - handleCancelExpenditure, + handleCancel, fundingSources, }: Props) => { const [valueIsCopied, setValueIsCopied] = useState(false); @@ -290,7 +290,7 @@ const StreamingStagesLocked = ({ [styles.iconButtonDisabled]: motion?.status === MotionStatus.Pending || isCancelled, })} - onClick={handleCancelExpenditure} + onClick={handleCancel} disabled={isCancelled || motion?.status === MotionStatus.Pending} > {motion?.status === MotionStatus.Pending || isCancelled ? ( diff --git a/src/modules/pages/components/ExpenditurePage/ExpenditurePage.tsx b/src/modules/pages/components/ExpenditurePage/ExpenditurePage.tsx index 7bec7ea379..bc544c25ce 100644 --- a/src/modules/pages/components/ExpenditurePage/ExpenditurePage.tsx +++ b/src/modules/pages/components/ExpenditurePage/ExpenditurePage.tsx @@ -587,7 +587,7 @@ const ExpenditurePage = ({ match }: Props) => { activeStageId={activeStageId} setActiveStageId={setActiveStageId} setFormValues={setFormValues} - handleCancelExpenditure={handleCancelExpenditure} + handleCancel={handleCancelExpenditure} colony={colonyData.processedColony} /> ) @@ -638,7 +638,7 @@ const ExpenditurePage = ({ match }: Props) => { stages={stages} activeStageId={activeStageId} setActiveStageId={setActiveStageId} - handleCancelExpenditure={handleCancelExpenditure} + handleCancel={handleCancelExpenditure} colony={colonyData.processedColony} expenditureType={formValues?.expenditure} /> diff --git a/src/modules/pages/components/IncorporationPage/IncorporationPage.tsx b/src/modules/pages/components/IncorporationPage/IncorporationPage.tsx index e5475565aa..025911d5e7 100644 --- a/src/modules/pages/components/IncorporationPage/IncorporationPage.tsx +++ b/src/modules/pages/components/IncorporationPage/IncorporationPage.tsx @@ -10,8 +10,15 @@ import IncorporationForm from '~dashboard/Incorporation/IncorporationForm'; import Stages, { FormStages } from '~dashboard/ExpenditurePage/Stages'; import LockedIncorporationForm from '~dashboard/Incorporation/IncorporationForm/LockedIncorporationForm'; import VerificationBanner from '~dashboard/Incorporation/VerificationBanner'; -import IncorporationPaymentDialog from '~dashboard/Dialogs/IncorporationPaymentDialog'; +import { + Motion, + MotionStatus, + MotionType, + Status, +} from '~dashboard/ExpenditurePage/Stages/constants'; import { useDialog } from '~core/Dialog'; +import CancelIncorporationDialog from '~dashboard/Dialogs/CancelIncorporationDialog'; +import IncorporationPaymentDialog from '~dashboard/Dialogs/IncorporationPaymentDialog'; import { initialValues, @@ -41,6 +48,9 @@ const IncorporationPage = () => { const [shouldValidate, setShouldValidate] = useState(false); const [activeStageId, setActiveStageId] = useState(StagesEnum.Payment); const sidebarRef = useRef(null); + const openCancelIncorporationDialog = useDialog(CancelIncorporationDialog); + const [, setMotion] = useState(); + const [, setStatus] = useState(); const notVerified = true; // temporary valule @@ -92,6 +102,41 @@ const IncorporationPage = () => { } }, [shouldValidate]); + const handleCancel = (isForce: boolean) => { + if (isForce) { + // temporary action + setStatus(Status.ForceCancelled); + } else { + // setTimeout is temporary, call to backend should be added here + setMotion({ + type: MotionType.Cancel, + status: MotionStatus.Pending, + }); + setTimeout(() => { + setStatus(Status.Cancelled); + setMotion({ + type: MotionType.Cancel, + status: MotionStatus.Passed, + }); + }, 5000); + } + }; + + const handleCancelIncorporation = useCallback(() => { + if (!colonyData) { + return null; + } + + return ( + colonyData && + openCancelIncorporationDialog({ + onCancelIncorporation: (isForce: boolean) => handleCancel(isForce), + colony: colonyData.processedColony, + isVotingExtensionEnabled: true, // temporary value + }) + ); + }, [colonyData, openCancelIncorporationDialog]); + return isFormEditable ? ( { setActiveStageId={setActiveStageId} colony={colonyData.processedColony} setFormValues={setFormValues} - handleCancelExpenditure={() => {}} + handleCancel={handleCancelIncorporation} /> )} @@ -179,6 +224,7 @@ const IncorporationPage = () => { handleButtonClick={buttonAction || (() => {})} colony={colonyData?.processedColony} viewFor="incorporation" + handleCancel={handleCancelIncorporation} /> )}