From e3ca82e35568728a4dd1d332a2f48cc9e37f2200 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Wed, 15 Jan 2020 15:39:46 +0100 Subject: [PATCH 1/8] Add snackbar component baseline --- .../components/atoms/SnackBarCloseButton.jsx | 18 +++++++ .../components/atoms/SnackBarContainer.tsx | 51 +++++++++++++++++++ .../src/components/atoms/SnackBarSymbol.jsx | 6 +++ frontend/src/stories/index.stories.tsx | 9 ++++ 4 files changed, 84 insertions(+) create mode 100644 frontend/src/components/atoms/SnackBarCloseButton.jsx create mode 100644 frontend/src/components/atoms/SnackBarContainer.tsx create mode 100644 frontend/src/components/atoms/SnackBarSymbol.jsx diff --git a/frontend/src/components/atoms/SnackBarCloseButton.jsx b/frontend/src/components/atoms/SnackBarCloseButton.jsx new file mode 100644 index 0000000..7cabeba --- /dev/null +++ b/frontend/src/components/atoms/SnackBarCloseButton.jsx @@ -0,0 +1,18 @@ +import React from 'react'; +import styled from 'styled-components'; +import { theme } from '../../styling/theme'; + +export default styled.span` + padding-right: 0.5em; + float: right; + color: ${theme.palette.primary.contrast}; + font-weight: 600; + transition: 0.1s linear; + margin-left: 1em; + + &:hover { + color: red; + transition: 0.1s linear; + cursor: pointer; + } +`; diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx new file mode 100644 index 0000000..59be773 --- /dev/null +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -0,0 +1,51 @@ +import React from 'react'; + +import styled from 'styled-components'; +import SnackBarCloseButton from './SnackBarCloseButton'; +import { theme } from '../../styling/theme'; +import SnackBarSymbol from './SnackBarSymbol'; + +interface ISnackBarProps { + content: string; + good: boolean; + className?: string; +} + +const SnackBarContainer: React.FC = ({ + content, + good, + className, +}) => { + return ( +
+

+ {good ? '✓' : '!'} + {content} + X +

+
+ ); +}; + +export default styled(SnackBarContainer)` + color: ${props => (props.good ? 'green' : 'red')}; + background-color: ${props => + props.good ? theme.palette.success : theme.palette.error}; + padding: 0.5em; + border-radius: 2px; + border: 3px solid; + border-color: ${props => (props.good ? 'green' : 'red')}; + position: fixed; + bottom: 20px; + left: 20px; + display: inline-block; + min-width: 200px; + padding-left: 1em; + -webkit-box-shadow: 10px 10px 16px -7px rgba(0, 0, 0, 0.75); + -moz-box-shadow: 10px 10px 16px -7px rgba(0, 0, 0, 0.75); + box-shadow: 10px 10px 16px -7px rgba(0, 0, 0, 0.75); + + & > p { + width: 100%; + } +`; diff --git a/frontend/src/components/atoms/SnackBarSymbol.jsx b/frontend/src/components/atoms/SnackBarSymbol.jsx new file mode 100644 index 0000000..e61cd65 --- /dev/null +++ b/frontend/src/components/atoms/SnackBarSymbol.jsx @@ -0,0 +1,6 @@ +import React from 'react'; +import styled from 'styled-components'; + +export default styled.span` + margin-right: 0.9em; +`; diff --git a/frontend/src/stories/index.stories.tsx b/frontend/src/stories/index.stories.tsx index 8a4900d..b73f00f 100644 --- a/frontend/src/stories/index.stories.tsx +++ b/frontend/src/stories/index.stories.tsx @@ -25,6 +25,7 @@ import { ITransaction } from '../declarations/transaction'; import GlobalWrapper from '../helpers/GlobalWrapper'; import { navbarWidth } from '../styling/sizes'; import { theme } from '../styling/theme'; +import SnackBarContainer from '../components/atoms/SnackBarContainer'; addDecorator(storyFn => {storyFn()}); @@ -299,3 +300,11 @@ storiesOf('Input/Select', module) ); }); + +storiesOf('SnackBarContainer', module) + .addDecorator(fn => ( +
+ {fn()} +
+ )) + .add('Default', () => ); From 692c18a0a9f853f34e46bce2364f2d17d41a2cc0 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Tue, 21 Jan 2020 10:39:41 +0100 Subject: [PATCH 2/8] Add loading bar on bottom of snackbar and change out span for button on closing button --- .../src/components/atoms/ProjectionRow.tsx | 1 + .../components/atoms/SnackBarCloseButton.jsx | 7 ++++- .../components/atoms/SnackBarContainer.tsx | 9 ++++++- .../src/components/atoms/SnackBarLoader.jsx | 27 +++++++++++++++++++ frontend/src/stories/index.stories.tsx | 15 ++++++++++- 5 files changed, 56 insertions(+), 3 deletions(-) create mode 100644 frontend/src/components/atoms/SnackBarLoader.jsx diff --git a/frontend/src/components/atoms/ProjectionRow.tsx b/frontend/src/components/atoms/ProjectionRow.tsx index 1e30853..58215aa 100644 --- a/frontend/src/components/atoms/ProjectionRow.tsx +++ b/frontend/src/components/atoms/ProjectionRow.tsx @@ -20,6 +20,7 @@ const ProjectionRow = styled.div` align-self: center; margin-top: 0.2em; margin-bottom: 0.2em; + word-break: break-word; } p:nth-last-child(-n + 3), diff --git a/frontend/src/components/atoms/SnackBarCloseButton.jsx b/frontend/src/components/atoms/SnackBarCloseButton.jsx index 7cabeba..d42f166 100644 --- a/frontend/src/components/atoms/SnackBarCloseButton.jsx +++ b/frontend/src/components/atoms/SnackBarCloseButton.jsx @@ -2,9 +2,14 @@ import React from 'react'; import styled from 'styled-components'; import { theme } from '../../styling/theme'; -export default styled.span` +export default styled.button` + background-color: ${theme.palette.primary.main}; + border: none; padding-right: 0.5em; float: right; + position: absolute; + right: 10px; + top: 1.2em; color: ${theme.palette.primary.contrast}; font-weight: 600; transition: 0.1s linear; diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx index 59be773..02f97b1 100644 --- a/frontend/src/components/atoms/SnackBarContainer.tsx +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -4,6 +4,7 @@ import styled from 'styled-components'; import SnackBarCloseButton from './SnackBarCloseButton'; import { theme } from '../../styling/theme'; import SnackBarSymbol from './SnackBarSymbol'; +import SnackBarLoader from './SnackBarLoader'; interface ISnackBarProps { content: string; @@ -19,10 +20,14 @@ const SnackBarContainer: React.FC = ({ return (

+ {/* + Symbol if wanted, but it seems to look better without and it follows the standard for snackbars better. {good ? '✓' : '!'} + */} {content} - X

+ X +
); }; @@ -40,6 +45,7 @@ export default styled(SnackBarContainer)` left: 20px; display: inline-block; min-width: 200px; + max-width: 70vw; padding-left: 1em; -webkit-box-shadow: 10px 10px 16px -7px rgba(0, 0, 0, 0.75); -moz-box-shadow: 10px 10px 16px -7px rgba(0, 0, 0, 0.75); @@ -47,5 +53,6 @@ export default styled(SnackBarContainer)` & > p { width: 100%; + padding-right: 4em; } `; diff --git a/frontend/src/components/atoms/SnackBarLoader.jsx b/frontend/src/components/atoms/SnackBarLoader.jsx new file mode 100644 index 0000000..6eb1619 --- /dev/null +++ b/frontend/src/components/atoms/SnackBarLoader.jsx @@ -0,0 +1,27 @@ +import React from 'react'; +import styled from 'styled-components'; +import { keyframes } from 'styled-components'; + +const shrinkBar = keyframes` +from { + width: 100%; +} +to { + width: 0%; +} +`; + +export default styled.div` + width: 100%; + height: 5px; + background-color: grey; + padding: 0; + margin: 0; + position: absolute; + bottom: 0px; + left: 0px; + animation-name: ${shrinkBar}; + animation-duration: 8s; + animation-iteration-count: 1; + animation-timing-function: linear; +`; diff --git a/frontend/src/stories/index.stories.tsx b/frontend/src/stories/index.stories.tsx index b73f00f..db6bc38 100644 --- a/frontend/src/stories/index.stories.tsx +++ b/frontend/src/stories/index.stories.tsx @@ -307,4 +307,17 @@ storiesOf('SnackBarContainer', module) {fn()} )) - .add('Default', () => ); + .add('Long', () => ( + + )); + +storiesOf('SnackBarContainer', module) + .addDecorator(fn => ( +
+ {fn()} +
+ )) + .add('Default', () => ); From a10d7762d9305ff9e9df62b335fae9494616a0a0 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Wed, 22 Jan 2020 12:47:51 +0100 Subject: [PATCH 3/8] Change snackbar to use reducers and implementing it in adding transactions --- .../components/atoms/SnackBarCloseButton.jsx | 4 +- .../components/atoms/SnackBarContainer.tsx | 40 +++++--- .../src/components/atoms/SnackBarLoader.jsx | 6 +- .../components/molecules/AddTransaction.tsx | 97 ++++++++++++++----- .../organism/__test__/Authentication.test.tsx | 5 +- frontend/src/stories/index.stories.tsx | 18 +++- 6 files changed, 120 insertions(+), 50 deletions(-) diff --git a/frontend/src/components/atoms/SnackBarCloseButton.jsx b/frontend/src/components/atoms/SnackBarCloseButton.jsx index d42f166..e86fa71 100644 --- a/frontend/src/components/atoms/SnackBarCloseButton.jsx +++ b/frontend/src/components/atoms/SnackBarCloseButton.jsx @@ -1,4 +1,3 @@ -import React from 'react'; import styled from 'styled-components'; import { theme } from '../../styling/theme'; @@ -12,11 +11,12 @@ export default styled.button` top: 1.2em; color: ${theme.palette.primary.contrast}; font-weight: 600; + text-transform: uppercase; transition: 0.1s linear; margin-left: 1em; &:hover { - color: red; + color: ${theme.palette.danger.main}; transition: 0.1s linear; cursor: pointer; } diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx index 02f97b1..4b18298 100644 --- a/frontend/src/components/atoms/SnackBarContainer.tsx +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -1,45 +1,53 @@ import React from 'react'; - import styled from 'styled-components'; import SnackBarCloseButton from './SnackBarCloseButton'; import { theme } from '../../styling/theme'; -import SnackBarSymbol from './SnackBarSymbol'; import SnackBarLoader from './SnackBarLoader'; interface ISnackBarProps { content: string; good: boolean; className?: string; + speed?: string; } +const LoaderSpeed = (speed = '6s') => { + if (speed == 'fast') { + return '4s'; + } else if (speed == 'slow') { + return '8s'; + } else { + return '6s'; + } +}; + const SnackBarContainer: React.FC = ({ content, good, className, + speed, }) => { return (
-

- {/* - Symbol if wanted, but it seems to look better without and it follows the standard for snackbars better. - {good ? '✓' : '!'} - */} - {content} -

- X - +

{content}

+ x +
); }; export default styled(SnackBarContainer)` - color: ${props => (props.good ? 'green' : 'red')}; - background-color: ${props => - props.good ? theme.palette.success : theme.palette.error}; + color: ${theme.palette.primary.contrast}; + background-color: ${theme.palette.background.default}; padding: 0.5em; - border-radius: 2px; + border-radius: 3px; border: 3px solid; - border-color: ${props => (props.good ? 'green' : 'red')}; + border-color: ${props => + props.good ? theme.palette.success.main : theme.palette.danger.main}; position: fixed; bottom: 20px; left: 20px; diff --git a/frontend/src/components/atoms/SnackBarLoader.jsx b/frontend/src/components/atoms/SnackBarLoader.jsx index 6eb1619..5d8dc1b 100644 --- a/frontend/src/components/atoms/SnackBarLoader.jsx +++ b/frontend/src/components/atoms/SnackBarLoader.jsx @@ -1,6 +1,6 @@ -import React from 'react'; import styled from 'styled-components'; import { keyframes } from 'styled-components'; +import { theme } from '../../styling/theme'; const shrinkBar = keyframes` from { @@ -12,9 +12,8 @@ to { `; export default styled.div` - width: 100%; height: 5px; - background-color: grey; + background-color: ${theme.palette.primary.contrast}; padding: 0; margin: 0; position: absolute; @@ -24,4 +23,5 @@ export default styled.div` animation-duration: 8s; animation-iteration-count: 1; animation-timing-function: linear; + border-radius: 0 3px 3px 0; `; diff --git a/frontend/src/components/molecules/AddTransaction.tsx b/frontend/src/components/molecules/AddTransaction.tsx index 8fd2b19..d29656b 100644 --- a/frontend/src/components/molecules/AddTransaction.tsx +++ b/frontend/src/components/molecules/AddTransaction.tsx @@ -1,15 +1,50 @@ import moment from 'moment'; -import React from 'react'; +import React, { useReducer, ReactElement } from 'react'; import styled from 'styled-components'; import { useAuthState } from '../../store/contexts/auth'; import { useTransactionDispatch } from '../../store/contexts/transactions'; import { TransactionActions } from '../../store/reducers/transactions'; import Collapsable from '../atoms/Collapsable'; import Form from './Form'; +import SnackBarContainer from '../atoms/SnackBarContainer'; + +const initialState = { snax:
, content: '' }; + +type stateType = { + snax: ReactElement; + content: string; +}; + +type ActionType = { + type: 'clear' | 'good' | 'bad'; +}; + +const reducer = (state: stateType, action: ActionType): stateType => { + switch (action.type) { + case 'clear': + return initialState; + case 'good': + return { + snax: , + content: state.content, + }; + case 'bad': + return { + snax: , + content: state.content, + }; + default: + return initialState; + } +}; const AddTransaction: React.FC<{ className?: string }> = props => { const dispatch = useTransactionDispatch(); const auth = useAuthState(); + const [state, snaxDispatch] = useReducer(reducer, { + snax:
, + content: '', + }); const onSubmit = async ({ recurring, @@ -22,39 +57,49 @@ const AddTransaction: React.FC<{ className?: string }> = props => { interval_type, interval, }: any) => { - if (!recurring) { - await TransactionActions.doCreateTransaction( - { - company_id: auth!.selectedCompany!, - date, - description, - money: money * 100, - notes, - type, - }, - dispatch - ); - } else { - await TransactionActions.doCreateRecurringTransaction( - { - company_id: auth!.selectedCompany!, - end_date, - interval, - interval_type, - start_date: date, - template: { + if (!(description.length > 140) && !(notes.length > 140)) { + state.content = 'Transaction added successfully'; + snaxDispatch({ type: 'good' }); + setTimeout(() => snaxDispatch({ type: 'clear' }), 6000); + console.log(state.snax); + if (!recurring) { + await TransactionActions.doCreateTransaction( + { + company_id: auth!.selectedCompany!, + date, description, money: money * 100, + notes, type, }, - }, - dispatch - ); + dispatch + ); + } else { + await TransactionActions.doCreateRecurringTransaction( + { + company_id: auth!.selectedCompany!, + end_date, + interval, + interval_type, + start_date: date, + template: { + description, + money: money * 100, + type, + }, + }, + dispatch + ); + } + } else { + state.content = 'Too many characters.'; + snaxDispatch({ type: 'bad' }); + setTimeout(() => snaxDispatch({ type: 'clear' }), 6000); } }; - return ( Add new transaction}> +
{state.snax}
{ @@ -15,9 +15,10 @@ test('Login form renders with correct headertext, aria-labels and placeholders', }); test('Register form renders with correct headertext, aria-labels and placeholders', () => { - const { getByTestId, getByLabelText } = render(); + const { getByTestId, getByLabelText } = render( ); expect(getByTestId('authform-header')).toHaveTextContent(/^Sign up$/); expect(getByLabelText('email')).toHaveAttribute('aria-label', 'email'); expect(getByLabelText('password')).toHaveAttribute('aria-label', 'password'); }); +*/ diff --git a/frontend/src/stories/index.stories.tsx b/frontend/src/stories/index.stories.tsx index db6bc38..4ca2ee4 100644 --- a/frontend/src/stories/index.stories.tsx +++ b/frontend/src/stories/index.stories.tsx @@ -311,6 +311,7 @@ storiesOf('SnackBarContainer', module) )); @@ -320,4 +321,19 @@ storiesOf('SnackBarContainer', module) {fn()}
)) - .add('Default', () => ); + .add('Fast', () => ( + + )); +storiesOf('SnackBarContainer', module) + .addDecorator(fn => ( +
+ {fn()} +
+ )) + .add('Medium', () => ( + + )); From 2612d3b90eb99cd38b210286518d1922ad0142d0 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Wed, 22 Jan 2020 12:56:45 +0100 Subject: [PATCH 4/8] Remove SnackBarSymbol and uncomment authentication tests --- frontend/src/components/atoms/SnackBarSymbol.jsx | 6 ------ .../components/organism/__test__/Authentication.test.tsx | 5 ++--- 2 files changed, 2 insertions(+), 9 deletions(-) delete mode 100644 frontend/src/components/atoms/SnackBarSymbol.jsx diff --git a/frontend/src/components/atoms/SnackBarSymbol.jsx b/frontend/src/components/atoms/SnackBarSymbol.jsx deleted file mode 100644 index e61cd65..0000000 --- a/frontend/src/components/atoms/SnackBarSymbol.jsx +++ /dev/null @@ -1,6 +0,0 @@ -import React from 'react'; -import styled from 'styled-components'; - -export default styled.span` - margin-right: 0.9em; -`; diff --git a/frontend/src/components/organism/__test__/Authentication.test.tsx b/frontend/src/components/organism/__test__/Authentication.test.tsx index 667c285..75cc2be 100644 --- a/frontend/src/components/organism/__test__/Authentication.test.tsx +++ b/frontend/src/components/organism/__test__/Authentication.test.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { cleanup, render } from '../../../helpers/test-utils'; import Login from '../../organism/authentication/Login'; import Register from '../../organism/authentication/Register'; -/* + afterEach(cleanup); test('Login form renders with correct headertext, aria-labels and placeholders', () => { @@ -15,10 +15,9 @@ test('Login form renders with correct headertext, aria-labels and placeholders', }); test('Register form renders with correct headertext, aria-labels and placeholders', () => { - const { getByTestId, getByLabelText } = render( ); + const { getByTestId, getByLabelText } = render(); expect(getByTestId('authform-header')).toHaveTextContent(/^Sign up$/); expect(getByLabelText('email')).toHaveAttribute('aria-label', 'email'); expect(getByLabelText('password')).toHaveAttribute('aria-label', 'password'); }); -*/ From b922aa451bad472b47f3a21f655e029cc294df26 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Tue, 28 Jan 2020 10:39:38 +0100 Subject: [PATCH 5/8] Fixed error snackbar from not closing on click --- .../components/atoms/SnackBarContainer.tsx | 8 +++--- .../components/molecules/AddTransaction.tsx | 26 ++++++++++++++++--- 2 files changed, 27 insertions(+), 7 deletions(-) diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx index 4b18298..855a3f3 100644 --- a/frontend/src/components/atoms/SnackBarContainer.tsx +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -9,12 +9,13 @@ interface ISnackBarProps { good: boolean; className?: string; speed?: string; + clicker?: () => void; } const LoaderSpeed = (speed = '6s') => { - if (speed == 'fast') { + if (speed === 'fast') { return '4s'; - } else if (speed == 'slow') { + } else if (speed === 'slow') { return '8s'; } else { return '6s'; @@ -26,11 +27,12 @@ const SnackBarContainer: React.FC = ({ good, className, speed, + clicker, }) => { return (

{content}

- x + x
, content: '' }; type stateType = { snax: ReactElement; content: string; + clicker?: () => void; }; type ActionType = { @@ -25,12 +26,24 @@ const reducer = (state: stateType, action: ActionType): stateType => { return initialState; case 'good': return { - snax: , + snax: ( + + ), content: state.content, }; case 'bad': return { - snax: , + snax: ( + + ), content: state.content, }; default: @@ -46,6 +59,10 @@ const AddTransaction: React.FC<{ className?: string }> = props => { content: '', }); + const onButtonClickHandler = () => { + snaxDispatch({ type: 'clear' }); + }; + const onSubmit = async ({ recurring, money, @@ -59,9 +76,9 @@ const AddTransaction: React.FC<{ className?: string }> = props => { }: any) => { if (!(description.length > 140) && !(notes.length > 140)) { state.content = 'Transaction added successfully'; + state.clicker = onButtonClickHandler; snaxDispatch({ type: 'good' }); - setTimeout(() => snaxDispatch({ type: 'clear' }), 6000); - console.log(state.snax); + if (!recurring) { await TransactionActions.doCreateTransaction( { @@ -92,6 +109,7 @@ const AddTransaction: React.FC<{ className?: string }> = props => { ); } } else { + state.clicker = onButtonClickHandler; state.content = 'Too many characters.'; snaxDispatch({ type: 'bad' }); setTimeout(() => snaxDispatch({ type: 'clear' }), 6000); From 684453403bb32120a0b7621be461228940a2c149 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Tue, 28 Jan 2020 10:57:24 +0100 Subject: [PATCH 6/8] linting update --- .../src/components/atoms/SnackBarContainer.tsx | 12 ++++++------ .../components/molecules/AddTransaction.tsx | 18 +++++++++--------- frontend/src/stories/index.stories.tsx | 2 +- 3 files changed, 16 insertions(+), 16 deletions(-) diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx index 855a3f3..3f9f37f 100644 --- a/frontend/src/components/atoms/SnackBarContainer.tsx +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -1,15 +1,15 @@ import React from 'react'; -import styled from 'styled-components'; import SnackBarCloseButton from './SnackBarCloseButton'; -import { theme } from '../../styling/theme'; import SnackBarLoader from './SnackBarLoader'; +import styled from 'styled-components'; +import { theme } from '../../styling/theme'; interface ISnackBarProps { + className?: string; + clicker?: () => void; content: string; good: boolean; - className?: string; speed?: string; - clicker?: () => void; } const LoaderSpeed = (speed = '6s') => { @@ -23,11 +23,11 @@ const LoaderSpeed = (speed = '6s') => { }; const SnackBarContainer: React.FC = ({ + className, + clicker, content, good, - className, speed, - clicker, }) => { return (
diff --git a/frontend/src/components/molecules/AddTransaction.tsx b/frontend/src/components/molecules/AddTransaction.tsx index ba63ae2..7a885ef 100644 --- a/frontend/src/components/molecules/AddTransaction.tsx +++ b/frontend/src/components/molecules/AddTransaction.tsx @@ -8,24 +8,25 @@ import Collapsable from '../atoms/Collapsable'; import Form from './Form'; import SnackBarContainer from '../atoms/SnackBarContainer'; -const initialState = { snax:
, content: '' }; +const initialState = { snax:
, content: '' }; -type stateType = { +interface IState { snax: ReactElement; content: string; clicker?: () => void; -}; +} -type ActionType = { +interface IAction { type: 'clear' | 'good' | 'bad'; -}; +} -const reducer = (state: stateType, action: ActionType): stateType => { +const reducer = (state: IState, action: IAction): IState => { switch (action.type) { case 'clear': return initialState; case 'good': return { + content: state.content, snax: ( { content={state.content} /> ), - content: state.content, }; case 'bad': return { + content: state.content, snax: ( { content={state.content} /> ), - content: state.content, }; default: return initialState; @@ -55,8 +55,8 @@ const AddTransaction: React.FC<{ className?: string }> = props => { const dispatch = useTransactionDispatch(); const auth = useAuthState(); const [state, snaxDispatch] = useReducer(reducer, { - snax:
, content: '', + snax:
, }); const onButtonClickHandler = () => { diff --git a/frontend/src/stories/index.stories.tsx b/frontend/src/stories/index.stories.tsx index 4ca2ee4..72c1089 100644 --- a/frontend/src/stories/index.stories.tsx +++ b/frontend/src/stories/index.stories.tsx @@ -9,6 +9,7 @@ import OutlinedButton from '../components/atoms/OutlinedButton'; import RecurringTransactionOptions, { IntervalType, } from '../components/atoms/RecurringTransactionOptions'; +import SnackBarContainer from '../components/atoms/SnackBarContainer'; import Select from '../components/atoms/Select'; import TextArea from '../components/atoms/TextArea'; import TransactionEntry from '../components/atoms/TransactionEntry'; @@ -25,7 +26,6 @@ import { ITransaction } from '../declarations/transaction'; import GlobalWrapper from '../helpers/GlobalWrapper'; import { navbarWidth } from '../styling/sizes'; import { theme } from '../styling/theme'; -import SnackBarContainer from '../components/atoms/SnackBarContainer'; addDecorator(storyFn => {storyFn()}); From de8d8bf1c1b9b9440a87da43cd94adfe4178bc6b Mon Sep 17 00:00:00 2001 From: erikskaar Date: Tue, 28 Jan 2020 11:21:42 +0100 Subject: [PATCH 7/8] even more linting fixes --- frontend/src/components/atoms/SnackBarContainer.tsx | 4 ++-- frontend/src/components/molecules/AddTransaction.tsx | 4 ++-- frontend/src/stories/index.stories.tsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx index 3f9f37f..b58b6e9 100644 --- a/frontend/src/components/atoms/SnackBarContainer.tsx +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -1,8 +1,8 @@ import React from 'react'; -import SnackBarCloseButton from './SnackBarCloseButton'; -import SnackBarLoader from './SnackBarLoader'; import styled from 'styled-components'; import { theme } from '../../styling/theme'; +import SnackBarCloseButton from './SnackBarCloseButton'; +import SnackBarLoader from './SnackBarLoader'; interface ISnackBarProps { className?: string; diff --git a/frontend/src/components/molecules/AddTransaction.tsx b/frontend/src/components/molecules/AddTransaction.tsx index 7a885ef..4e02d0c 100644 --- a/frontend/src/components/molecules/AddTransaction.tsx +++ b/frontend/src/components/molecules/AddTransaction.tsx @@ -1,12 +1,12 @@ import moment from 'moment'; -import React, { useReducer, ReactElement } from 'react'; +import React, { ReactElement, useReducer } from 'react'; import styled from 'styled-components'; import { useAuthState } from '../../store/contexts/auth'; import { useTransactionDispatch } from '../../store/contexts/transactions'; import { TransactionActions } from '../../store/reducers/transactions'; import Collapsable from '../atoms/Collapsable'; -import Form from './Form'; import SnackBarContainer from '../atoms/SnackBarContainer'; +import Form from './Form'; const initialState = { snax:
, content: '' }; diff --git a/frontend/src/stories/index.stories.tsx b/frontend/src/stories/index.stories.tsx index 72c1089..c5daec3 100644 --- a/frontend/src/stories/index.stories.tsx +++ b/frontend/src/stories/index.stories.tsx @@ -9,8 +9,8 @@ import OutlinedButton from '../components/atoms/OutlinedButton'; import RecurringTransactionOptions, { IntervalType, } from '../components/atoms/RecurringTransactionOptions'; -import SnackBarContainer from '../components/atoms/SnackBarContainer'; import Select from '../components/atoms/Select'; +import SnackBarContainer from '../components/atoms/SnackBarContainer'; import TextArea from '../components/atoms/TextArea'; import TransactionEntry from '../components/atoms/TransactionEntry'; import AddTransaction from '../components/molecules/AddTransaction'; From 04d35b3a2a4247a02b258f66ac346c8d94321ca7 Mon Sep 17 00:00:00 2001 From: erikskaar Date: Wed, 5 Feb 2020 13:12:09 +0100 Subject: [PATCH 8/8] Refactor based on PR review --- .../components/atoms/SnackBarCloseButton.jsx | 7 +- .../components/atoms/SnackBarContainer.tsx | 41 +++-- .../src/components/atoms/SnackBarLoader.jsx | 15 +- .../components/molecules/AddTransaction.tsx | 172 +++++++++--------- frontend/src/store/reducers/transactions.ts | 25 +++ frontend/src/stories/index.stories.tsx | 6 +- 6 files changed, 146 insertions(+), 120 deletions(-) diff --git a/frontend/src/components/atoms/SnackBarCloseButton.jsx b/frontend/src/components/atoms/SnackBarCloseButton.jsx index e86fa71..96c8927 100644 --- a/frontend/src/components/atoms/SnackBarCloseButton.jsx +++ b/frontend/src/components/atoms/SnackBarCloseButton.jsx @@ -1,22 +1,21 @@ import styled from 'styled-components'; -import { theme } from '../../styling/theme'; export default styled.button` - background-color: ${theme.palette.primary.main}; + background-color: ${props => props.theme.palette.primary.main}; border: none; padding-right: 0.5em; float: right; position: absolute; right: 10px; top: 1.2em; - color: ${theme.palette.primary.contrast}; + color: ${props => props.theme.palette.primary.contrast}; font-weight: 600; text-transform: uppercase; transition: 0.1s linear; margin-left: 1em; &:hover { - color: ${theme.palette.danger.main}; + color: ${props => props.theme.palette.danger.main}; transition: 0.1s linear; cursor: pointer; } diff --git a/frontend/src/components/atoms/SnackBarContainer.tsx b/frontend/src/components/atoms/SnackBarContainer.tsx index b58b6e9..f1663a0 100644 --- a/frontend/src/components/atoms/SnackBarContainer.tsx +++ b/frontend/src/components/atoms/SnackBarContainer.tsx @@ -1,41 +1,42 @@ import React from 'react'; import styled from 'styled-components'; -import { theme } from '../../styling/theme'; import SnackBarCloseButton from './SnackBarCloseButton'; import SnackBarLoader from './SnackBarLoader'; -interface ISnackBarProps { +interface ISnackBar { className?: string; - clicker?: () => void; + snackBarCloseHandler?: React.MouseEventHandler; content: string; good: boolean; - speed?: string; + speed?: number; } -const LoaderSpeed = (speed = '6s') => { - if (speed === 'fast') { - return '4s'; - } else if (speed === 'slow') { - return '8s'; - } else { - return '6s'; +const loaderSpeed = (speed: number = 6000) => { + switch (speed) { + case 4000: + return '4s'; + case 6000: + return '6s'; + case 8000: + return '8s'; } }; -const SnackBarContainer: React.FC = ({ +const SnackBarContainer: React.FC = ({ className, - clicker, + snackBarCloseHandler, content, - good, speed, }) => { return (

{content}

- x + + x +
@@ -43,13 +44,15 @@ const SnackBarContainer: React.FC = ({ }; export default styled(SnackBarContainer)` - color: ${theme.palette.primary.contrast}; - background-color: ${theme.palette.background.default}; + color: ${props => props.theme.palette.primary.contrast}; + background-color: ${props => props.theme.palette.background.default}; padding: 0.5em; border-radius: 3px; border: 3px solid; border-color: ${props => - props.good ? theme.palette.success.main : theme.palette.danger.main}; + props.good + ? props.theme.palette.success.main + : props.theme.palette.danger.main}; position: fixed; bottom: 20px; left: 20px; diff --git a/frontend/src/components/atoms/SnackBarLoader.jsx b/frontend/src/components/atoms/SnackBarLoader.jsx index 5d8dc1b..6d76630 100644 --- a/frontend/src/components/atoms/SnackBarLoader.jsx +++ b/frontend/src/components/atoms/SnackBarLoader.jsx @@ -1,19 +1,18 @@ import styled from 'styled-components'; import { keyframes } from 'styled-components'; -import { theme } from '../../styling/theme'; const shrinkBar = keyframes` -from { - width: 100%; -} -to { - width: 0%; -} + from { + width: 100%; + } + to { + width: 0%; + } `; export default styled.div` height: 5px; - background-color: ${theme.palette.primary.contrast}; + background-color: ${props => props.theme.palette.primary.contrast}; padding: 0; margin: 0; position: absolute; diff --git a/frontend/src/components/molecules/AddTransaction.tsx b/frontend/src/components/molecules/AddTransaction.tsx index 4e02d0c..56683d9 100644 --- a/frontend/src/components/molecules/AddTransaction.tsx +++ b/frontend/src/components/molecules/AddTransaction.tsx @@ -1,66 +1,53 @@ import moment from 'moment'; -import React, { ReactElement, useReducer } from 'react'; +import React from 'react'; import styled from 'styled-components'; import { useAuthState } from '../../store/contexts/auth'; import { useTransactionDispatch } from '../../store/contexts/transactions'; import { TransactionActions } from '../../store/reducers/transactions'; +import { snackReducer } from '../../store/reducers/transactions'; + import Collapsable from '../atoms/Collapsable'; import SnackBarContainer from '../atoms/SnackBarContainer'; import Form from './Form'; -const initialState = { snax:
, content: '' }; - -interface IState { - snax: ReactElement; - content: string; - clicker?: () => void; -} - -interface IAction { - type: 'clear' | 'good' | 'bad'; -} - -const reducer = (state: IState, action: IAction): IState => { - switch (action.type) { - case 'clear': - return initialState; - case 'good': - return { - content: state.content, - snax: ( - - ), - }; - case 'bad': - return { - content: state.content, - snax: ( - - ), - }; - default: - return initialState; - } -}; - const AddTransaction: React.FC<{ className?: string }> = props => { const dispatch = useTransactionDispatch(); const auth = useAuthState(); - const [state, snaxDispatch] = useReducer(reducer, { - content: '', - snax:
, - }); + const [store, snackDispatch] = React.useReducer(snackReducer, [] as Array<{ + content: string; + variant: boolean; + speed: number; + }>); + + let delayTimer = setTimeout(null, 0); - const onButtonClickHandler = () => { - snaxDispatch({ type: 'clear' }); + const onButtonClickHandler = ( + content: string, + speed: number, + variant: boolean + ) => { + snackDispatch({ + payload: { content, speed, variant }, + type: 'REMOVE_SNACK', + }); + // For some reason the delayTimer ID will be different if it is true or false, which is why it needs to either be +1 or -1 to make sure it gets the right ID. + if (variant === true) { + clearTimeout(delayTimer + 1); + } else { + clearTimeout(delayTimer - 1); + } + }; + const createSnack = (content: string, variant: boolean, speed: number) => { + snackDispatch({ + payload: { content, speed, variant }, + type: 'ADD_SNACK', + }); + delayTimer = setTimeout(() => { + snackDispatch({ + payload: { content, speed, variant }, + type: 'REMOVE_SNACK', + }); + }, speed); }; const onSubmit = async ({ @@ -74,50 +61,63 @@ const AddTransaction: React.FC<{ className?: string }> = props => { interval_type, interval, }: any) => { - if (!(description.length > 140) && !(notes.length > 140)) { - state.content = 'Transaction added successfully'; - state.clicker = onButtonClickHandler; - snaxDispatch({ type: 'good' }); - - if (!recurring) { - await TransactionActions.doCreateTransaction( - { - company_id: auth!.selectedCompany!, - date, + if (description.length > 140 && notes.length > 140) { + createSnack( + 'Too many characters in notes or description. Please try again', + false, + 6000 + ); + return; + } + if (!recurring) { + await TransactionActions.doCreateTransaction( + { + company_id: auth!.selectedCompany!, + date, + description, + money: money * 100, + notes, + type, + }, + dispatch + ); + } else { + await TransactionActions.doCreateRecurringTransaction( + { + company_id: auth!.selectedCompany!, + end_date, + interval, + interval_type, + start_date: date, + template: { description, money: money * 100, - notes, type, }, - dispatch - ); - } else { - await TransactionActions.doCreateRecurringTransaction( - { - company_id: auth!.selectedCompany!, - end_date, - interval, - interval_type, - start_date: date, - template: { - description, - money: money * 100, - type, - }, - }, - dispatch - ); - } - } else { - state.clicker = onButtonClickHandler; - state.content = 'Too many characters.'; - snaxDispatch({ type: 'bad' }); - setTimeout(() => snaxDispatch({ type: 'clear' }), 6000); + }, + dispatch + ); } + createSnack('Transaction added successfully', true, 6000); }; return ( Add new transaction}> -
{state.snax}
+
+ {store[0] && ( + + onButtonClickHandler( + store[0].content, + store[0].speed, + store[0].variant + ) + } + speed={store[0].speed} + /> + )} +
= [], + action: ISnackAction +) => { + switch (action.type) { + case 'ADD_SNACK': + return [...state, action.payload]; + case 'REMOVE_SNACK': + const currentSnack = state.findIndex(e => e === action.payload); + + if (currentSnack !== -1) { + return [ + ...state.slice(0, currentSnack), + ...state.slice(currentSnack + 1), + ]; + } + return []; // currently set to yeet the entire array, can probably be modified to only yeet the correct element. + } +}; + /** * The return types of all the elements in ActionCreators * NOTE: Should not be modified! diff --git a/frontend/src/stories/index.stories.tsx b/frontend/src/stories/index.stories.tsx index c5daec3..e2db7bc 100644 --- a/frontend/src/stories/index.stories.tsx +++ b/frontend/src/stories/index.stories.tsx @@ -311,7 +311,7 @@ storiesOf('SnackBarContainer', module) )); @@ -322,7 +322,7 @@ storiesOf('SnackBarContainer', module)
)) .add('Fast', () => ( - + )); storiesOf('SnackBarContainer', module) .addDecorator(fn => ( @@ -334,6 +334,6 @@ storiesOf('SnackBarContainer', module) ));