Skip to content
This repository has been archived by the owner on Apr 10, 2024. It is now read-only.

Commit

Permalink
Adds a standardized input component and fixes validation of input dat…
Browse files Browse the repository at this point in the history
…a POC
  • Loading branch information
fellmirr committed Jan 12, 2021
1 parent e32a8c7 commit f2b242e
Show file tree
Hide file tree
Showing 23 changed files with 379 additions and 420 deletions.
1 change: 1 addition & 0 deletions .eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
"@typescript-eslint/no-explicit-any": "error",
"radix": "off",
"import/prefer-default-export": "off",
"react/jsx-props-no-spreading": "off",
"react/prop-types": "off",
"no-param-reassign": "off",
"no-console": [
Expand Down
5 changes: 5 additions & 0 deletions src/components/panes/DonationPane/DonationPane.style.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import styled from "styled-components";

export const SumWrapper = styled.div`
padding-bottom: 14px;
`;
85 changes: 30 additions & 55 deletions src/components/panes/DonationPane/DonationPane.tsx
Original file line number Diff line number Diff line change
@@ -1,41 +1,34 @@
/* eslint-disable @typescript-eslint/no-unused-expressions */
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { useDispatch, useSelector } from "react-redux";
import { useForm } from "react-hook-form";
import Validator from "validator";
import {
registerDonationAction,
setSum,
setShareType,
} from "../../../store/donation/actions";
import { setShareType } from "../../../store/layout/actions";
import { Pane, PaneContainer } from "../Panes.style";
import { State } from "../../../store/state";
import { TextField } from "../Forms.style";
import ErrorField from "../../shared/Error/ErrorField";
import { PaymentMethod, ShareType } from "../../../types/Enums";
import { RichSelectOption } from "../../shared/RichSelect/RichSelectOption";
import { RichSelect } from "../../shared/RichSelect/RichSelect";
import { NextButton } from "../../shared/Buttons/NavigationButtons.style";
import { SharesSelection } from "./ShareSelection";
import { TextInput } from "../../shared/Input/TextInput";
import { SumWrapper } from "./DonationPane.style";
import { SharesSum } from "./SharesSum";

interface DonationFormValues {
recurring: string;
customShare: string;
sum: string;
}

// TODO: Add loading animation after submitting

export const DonationPane: React.FC = () => {
const dispatch = useDispatch();
const [nextDisabled, setNextDisabled] = useState(false);
const [sumErrorAnimation, setSumErrorAnimation] = useState(false);
const shareType = useSelector((state: State) => state.layout.shareType);
const shareType = useSelector((state: State) => state.donation.shareType);
const donationMethod = useSelector((state: State) => state.donation.method);
const donor = useSelector((state: State) => state.donation.donor);
const currentPaymentMethod = useSelector(
(state: State) => state.donation.method
);
const donationValid = useSelector((state: State) => state.donation.isValid);

const {
register,
Expand All @@ -46,59 +39,40 @@ export const DonationPane: React.FC = () => {
const watchAllFields = watch();

useEffect(() => {
errors.sum ? setSumErrorAnimation(true) : setSumErrorAnimation(false);

if (Object.keys(errors).length === 0) {
setNextDisabled(false);
} else {
setNextDisabled(true);
}
/**
* TODO:
* Handle errors, set donation valid
*/

const values = watchAllFields;
if (values.sum)
dispatch(setSum(Validator.isInt(values.sum) ? parseInt(values.sum) : 0));
}, [dispatch, errors, watchAllFields]);

function onSubmit() {
if (Object.keys(errors).length === 0) {
if (donor) {
if (
donor.name &&
donor.email &&
donor.newsletter !== undefined &&
currentPaymentMethod
) {
dispatch(registerDonationAction.started(undefined));
}
}
}
}

let sumField = null;
if (
donationMethod === PaymentMethod.PAYPAL ||
donationMethod === PaymentMethod.VIPPS
) {
sumField = (
<TextField
name="sum"
maxLength={10}
type="tel"
placeholder="0"
ref={register({
required: true,
validate: (val) => Validator.isInt(val) && val > 0,
})}
/>
);
dispatch(registerDonationAction.started(undefined));
}

return (
<Pane>
<PaneContainer>
<form onSubmit={handleSubmit(onSubmit)}>
{sumErrorAnimation && <ErrorField text="Ugyldig sum" />}
{sumField}
{(donationMethod === PaymentMethod.VIPPS ||
donationMethod === PaymentMethod.PAYPAL) && (
<SumWrapper>
<TextInput
label="Sum"
denomination="kr"
name="sum"
type="tel"
placeholder="0"
innerRef={register({
required: true,
validate: (val) => Validator.isInt(val) && val > 0,
})}
/>
</SumWrapper>
)}

<RichSelect
selected={shareType}
Expand All @@ -115,9 +89,10 @@ export const DonationPane: React.FC = () => {
value={ShareType.CUSTOM}
>
<SharesSelection />
<SharesSum />
</RichSelectOption>
</RichSelect>
<NextButton type="submit" disabled={nextDisabled}>
<NextButton type="submit" disabled={!donationValid}>
Neste
</NextButton>
</form>
Expand Down
179 changes: 34 additions & 145 deletions src/components/panes/DonationPane/ShareSelection.tsx
Original file line number Diff line number Diff line change
@@ -1,170 +1,59 @@
import React, { useEffect, useState } from "react";
import React, { useEffect } from "react";
import { useForm } from "react-hook-form";
import { useDispatch, useSelector } from "react-redux";
import Validator from "validator";
import { Organization } from "../../../types/Organization";
import { setShares } from "../../../store/donation/actions";
import { State } from "../../../store/state";
import { ToolTip } from "../../shared/ToolTip/ToolTip";

import {
OrganizationName,
PercentageText,
ShareInput,
ShareInputContainer,
} from "./ShareSelection.style";
import { TextInput } from "../../shared/Input/TextInput";
import { OrganizationShare } from "../../../types/Temp";

const tooltipLink = "https://gieffektivt.no/organisasjoner";

export const SharesSelection: React.FC = () => {
const dispatch = useDispatch();
const [submitLoading, setSubmitLoading] = useState(false);
const donorName = useSelector((state: State) => state.donation.donor?.name);
const donorEmail = useSelector((state: State) => state.donation.donor?.email);
// const donorSSN = useSelector((state: State) => state.donation.donor?.ssn);
const donorNewsletter = useSelector(
(state: State) => state.donation.donor?.newsletter
);
// const donationSum = useSelector((state: State) => state.donation.sum);
const donationMethod = useSelector((state: State) => state.donation.method);
const organizations = useSelector(
(state: State) => state.layout.organizations
);
const [percentageErrorAnimation, setPercentageErrorAnimation] = useState(
false
);
const { register, watch, handleSubmit, setValue } = useForm({ mode: "all" });
const { register, watch } = useForm({ mode: "all" });
const watchAllFields = watch();

function getTotalPercentage() {
let totalPercentage = 0;
let detectedNegativeShare = false;
Object.keys(watchAllFields).forEach((property) => {
const share = watchAllFields[property];

if (share !== "") totalPercentage += parseInt(watchAllFields[property]);
if (share === "0") setValue(property, "");
if (parseInt(watchAllFields[property], 10) < 0)
detectedNegativeShare = true;
});
return {
totalPercentage,
detectedNegativeShare,
};
}

function setupOrganizationInput(org: Organization) {
return (
<ShareInputContainer key={org.id}>
<div>
<OrganizationName>{org.name}</OrganizationName>
<ToolTip text={org.shortDesc} link={tooltipLink} />
</div>
<div>
<ShareInput
type="number"
inputMode="decimal"
placeholder="0"
name={org.id.toString()}
defaultValue={org.standardShare ? org.standardShare : 0}
ref={register}
/>
<PercentageText>%</PercentageText>
</div>
</ShareInputContainer>
);
}

useEffect(() => {
const total = getTotalPercentage().totalPercentage;
const negative = getTotalPercentage().detectedNegativeShare;
if (total === 100) {
// setNextDisabled(false);
setPercentageErrorAnimation(false);
} else if (organizations) {
// setNextDisabled(true);
setPercentageErrorAnimation(true);
}
if (negative) {
// setNextDisabled(true);
setPercentageErrorAnimation(true);
}
}, [watchAllFields]);

/**
* Check if donation is valid on input and set state
* TODO:
* Extract mappings to _mapping.ts
*/
function onSubmit() {
dispatch(setShares(watchAllFields));
if (getTotalPercentage().totalPercentage === 100) {
setSubmitLoading(true);
if (
donorName &&
donorEmail &&
donationMethod &&
donorNewsletter !== undefined
) {
const orgShares: Array<OrganizationShare> = [];

Object.keys(watchAllFields).forEach((property) => {
const Share = watchAllFields[property];
if (Share > 0 && organizations) {
const orgShare: OrganizationShare = { id: 0, share: 0, name: "" };
orgShare.id = parseInt(property);
orgShare.share = parseInt(watchAllFields[property]);
organizations.forEach((org: Organization) => {
if (orgShare.id === org.id) {
orgShare.name = org.name;
}
});
orgShares.push(orgShare);
}
});

/*
const postData: DonationData = {
donor: {
name: donorName,
email: donorEmail,
newsletter: donorNewsletter,
},
// TODO: Send payment method as string (not number)
method: "",
organizations: orgShares,
};
if (donationSum) postData.amount = donationSum;
if (donorSSN) postData.donor.ssn = donorSSN.toString();
// TODO: Move dispatches from network.ts to here
postDonation(postData, dispatch).then(() => {
setSubmitLoading(false);
});
*/
}
useEffect(() => {
if (Object.keys(watchAllFields).length > 0) {
const shares = Object.keys(watchAllFields).map(
(key): OrganizationShare => ({
id: parseInt(key),
share: Validator.isInt(watchAllFields[key])
? parseInt(watchAllFields[key])
: 0,
})
);
dispatch(setShares(shares));
}
}
}, [watchAllFields]);

if (!organizations) return <div>Ingen organisasjoner</div>;

return (
<div>
{!submitLoading ? (
<form onSubmit={handleSubmit(onSubmit)}>
<div>
{organizations.map((org: Organization) =>
setupOrganizationInput(org)
)}
</div>
{percentageErrorAnimation && (
<p>
Du har fordelt
{`${getTotalPercentage().totalPercentage} / 100%`}
</p>
)}
</form>
) : (
<p>Laster...</p>
)}
<form>
<div>
{organizations.map((org: Organization) => (
<TextInput
label={org.name}
name={org.id.toString()}
key={org.id}
type="tel"
defaultValue={org.standardShare ? org.standardShare : 0}
denomination="%"
selectOnClick
innerRef={register}
/>
))}
</div>
</form>
</div>
);
};
12 changes: 12 additions & 0 deletions src/components/panes/DonationPane/SharesSum.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import React from "react";
import { useSelector } from "react-redux";
import { State } from "../../../store/state";

export const SharesSum: React.FC = () => {
const shares = useSelector((state: State) => state.donation.shares);
const sum = shares.reduce((acc, curr) => acc + curr.share, 0);

if (sum === 100) return null;

return <p>{`Du har fordelt ${sum} / 100%`}</p>;
};
Loading

0 comments on commit f2b242e

Please sign in to comment.