diff --git a/app/package.json b/app/package.json index 80389865b..f1d8cfdce 100644 --- a/app/package.json +++ b/app/package.json @@ -26,6 +26,7 @@ "emotion-theming": "10.0.27", "i18next": "19.4.4", "i18next-browser-languagedetector": "4.1.1", + "lottie-web": "5.6.8", "mobx": "5.15.4", "mobx-react-lite": "2.0.6", "react": "^16.13.1", @@ -58,6 +59,7 @@ "eslint-plugin-prettier": "3.1.3", "eslint-plugin-react": "7.19.0", "google-protobuf": "3.11.4", + "jest-canvas-mock": "2.2.0", "jest-environment-jsdom-sixteen": "1.0.3", "node-sass": "4.14.1", "prettier": "2.0.5", diff --git a/app/src/__tests__/components/loop/LoopPage.spec.tsx b/app/src/__tests__/components/loop/LoopPage.spec.tsx index 673d1095c..82385ae58 100644 --- a/app/src/__tests__/components/loop/LoopPage.spec.tsx +++ b/app/src/__tests__/components/loop/LoopPage.spec.tsx @@ -109,7 +109,7 @@ describe('LoopPage component', () => { fireEvent.click(getByText('Next')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('Confirm')); - expect(getByText(/Swap Processing/)).toBeInTheDocument(); + expect(getByText('Configuring Loops')).toBeInTheDocument(); await waitFor(() => { expect(grpcMock.unary).toHaveBeenCalledWith( expect.objectContaining({ methodName: 'LoopOut' }), @@ -128,7 +128,7 @@ describe('LoopPage component', () => { fireEvent.click(getByText('Next')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('Confirm')); - expect(getByText(/Swap Processing/)).toBeInTheDocument(); + expect(getByText('Configuring Loops')).toBeInTheDocument(); expect(store.buildSwapStore.processingTimeout).toBeDefined(); fireEvent.click(getByText('arrow-left.svg')); expect(getByText('Review the quote')).toBeInTheDocument(); diff --git a/app/src/__tests__/components/loop/SwapWizard.spec.tsx b/app/src/__tests__/components/loop/SwapWizard.spec.tsx index 62a2e7c19..b935eda07 100644 --- a/app/src/__tests__/components/loop/SwapWizard.spec.tsx +++ b/app/src/__tests__/components/loop/SwapWizard.spec.tsx @@ -58,7 +58,7 @@ describe('SwapWizard component', () => { fireEvent.click(getByText('Next')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('Confirm')); - expect(getByText(/Swap Processing/)).toBeInTheDocument(); + expect(getByText('Configuring Loops')).toBeInTheDocument(); fireEvent.click(getByText('arrow-left.svg')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('arrow-left.svg')); @@ -128,7 +128,7 @@ describe('SwapWizard component', () => { it('should display the description label', () => { const { getByText } = renderWrap(); - expect(getByText(/Swap Processing/)).toBeInTheDocument(); + expect(getByText('Configuring Loops')).toBeInTheDocument(); }); it('should display an error message', () => { diff --git a/app/src/assets/animations/loading.json b/app/src/assets/animations/loading.json new file mode 100644 index 000000000..96779677b --- /dev/null +++ b/app/src/assets/animations/loading.json @@ -0,0 +1,322 @@ +{ + "v": "4.6.3", + "fr": 29.9700012207031, + "ip": 0, + "op": 35.0000014255792, + "w": 400, + "h": 400, + "nm": "Comp 1", + "ddd": 0, + "assets": [], + "layers": [ + { + "ddd": 0, + "ind": 1, + "ty": 4, + "nm": "Shape Layer 1", + "ks": { + "o": { "a": 0, "k": 100 }, + "r": { "a": 0, "k": 0 }, + "p": { "a": 0, "k": [200, 205, 0] }, + "a": { "a": 0, "k": [0, 0, 0] }, + "s": { "a": 0, "k": [77.411, 77.411, 100] } + }, + "ao": 0, + "shapes": [ + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [288.949, 288.949] }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [-2.959] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_-2p959_0p333_0"], + "t": 0, + "s": [6], + "e": [7.98] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0.226] }, + "n": ["0p667_1_0p333_0p226"], + "t": 4, + "s": [7.98], + "e": [34] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 7, + "s": [34], + "e": [54] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 12, + "s": [54], + "e": [100] + }, + { + "i": { "x": [0.667], "y": [0.667] }, + "o": { "x": [0.333], "y": [0.333] }, + "n": ["0p667_0p667_0p333_0p333"], + "t": 19, + "s": [100], + "e": [100] + }, + { "t": 35.0000014255792 } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 0, + "s": [0], + "e": [47] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 24, + "s": [47], + "e": [92] + }, + { "t": 35.0000014255792 } + ], + "ix": 2 + }, + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 0, + "s": [-70], + "e": [46] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 7, + "s": [46], + "e": [115] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 12, + "s": [115], + "e": [144] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 19, + "s": [144], + "e": [173] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 24, + "s": [173], + "e": [309] + }, + { "t": 35.0000014255792 } + ], + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim" + }, + { + "ty": "st", + "c": { "a": 0, "k": [0, 0.6784313725490196, 0.7764705882352941, 1] }, + "o": { "a": 0, "k": 100 }, + "w": { "a": 0, "k": 14 }, + "lc": 2, + "lj": 1, + "ml": 4, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke" + }, + { + "ty": "tr", + "p": { "a": 0, "k": [2.549, -5.717], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [108.139, 108.139], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "big_circle", + "np": 4, + "cix": 2, + "ix": 1, + "mn": "ADBE Vector Group" + }, + { + "ty": "gr", + "it": [ + { + "d": 1, + "ty": "el", + "s": { "a": 0, "k": [288.949, 288.949] }, + "p": { "a": 0, "k": [0, 0] }, + "nm": "Ellipse Path 1", + "mn": "ADBE Vector Shape - Ellipse" + }, + { + "ty": "tm", + "s": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 1, + "s": [0], + "e": [22.284] + }, + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 15, + "s": [22.284], + "e": [84] + }, + { "t": 35.0000014255792 } + ], + "ix": 1 + }, + "e": { + "a": 1, + "k": [ + { + "i": { "x": [0.667], "y": [1] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p667_1_0p333_0"], + "t": 0, + "s": [13], + "e": [100] + }, + { + "i": { "x": [0.667], "y": [0.667] }, + "o": { "x": [0.333], "y": [0.333] }, + "n": ["0p667_0p667_0p333_0p333"], + "t": 15, + "s": [100], + "e": [100] + }, + { + "i": { "x": [0.667], "y": [0.667] }, + "o": { "x": [0.333], "y": [0.333] }, + "n": ["0p667_0p667_0p333_0p333"], + "t": 29, + "s": [100], + "e": [100] + }, + { "t": 35.0000014255792 } + ], + "ix": 2 + }, + "o": { + "a": 1, + "k": [ + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.333], "y": [0] }, + "n": ["0p833_0p833_0p333_0"], + "t": 1, + "s": [-22], + "e": [11] + }, + { + "i": { "x": [0.833], "y": [0.833] }, + "o": { "x": [0.167], "y": [0.167] }, + "n": ["0p833_0p833_0p167_0p167"], + "t": 33, + "s": [11], + "e": [22] + }, + { "t": 35.0000014255792 } + ], + "ix": 3 + }, + "m": 1, + "ix": 2, + "nm": "Trim Paths 1", + "mn": "ADBE Vector Filter - Trim" + }, + { + "ty": "st", + "c": { "a": 0, "k": [0, 0.4117647058823529, 0.6941176470588235, 1] }, + "o": { "a": 0, "k": 100 }, + "w": { "a": 0, "k": 19 }, + "lc": 2, + "lj": 1, + "ml": 4, + "nm": "Stroke 1", + "mn": "ADBE Vector Graphic - Stroke" + }, + { + "ty": "tr", + "p": { "a": 0, "k": [2.549, -5.717], "ix": 2 }, + "a": { "a": 0, "k": [0, 0], "ix": 1 }, + "s": { "a": 0, "k": [72.226, 72.226], "ix": 3 }, + "r": { "a": 0, "k": 0, "ix": 6 }, + "o": { "a": 0, "k": 100, "ix": 7 }, + "sk": { "a": 0, "k": 0, "ix": 4 }, + "sa": { "a": 0, "k": 0, "ix": 5 }, + "nm": "Transform" + } + ], + "nm": "small_circle", + "np": 4, + "cix": 2, + "ix": 2, + "mn": "ADBE Vector Group" + } + ], + "ip": 0, + "op": 900.000036657751, + "st": 0, + "bm": 0, + "sr": 1 + } + ] +} diff --git a/app/src/components/common/Animation.tsx b/app/src/components/common/Animation.tsx new file mode 100644 index 000000000..8f878443c --- /dev/null +++ b/app/src/components/common/Animation.tsx @@ -0,0 +1,27 @@ +import React, { useEffect, useRef } from 'react'; +import lottie from 'lottie-web'; + +interface Props { + className?: string; + animationData: any; +} + +const Animation: React.FC = ({ className, animationData }) => { + const containerRef = useRef(null); + + useEffect(() => { + if (!containerRef.current) return; + + lottie.loadAnimation({ + container: containerRef.current, + renderer: 'svg', + loop: true, + autoplay: true, + animationData, + }); + }, [animationData]); + + return
; +}; + +export default Animation; diff --git a/app/src/components/loop/swap/StepSummary.tsx b/app/src/components/loop/swap/StepSummary.tsx index 2f4daf07a..1ee8fc622 100644 --- a/app/src/components/loop/swap/StepSummary.tsx +++ b/app/src/components/loop/swap/StepSummary.tsx @@ -31,7 +31,7 @@ interface Props { } const StepSummary: React.FC = ({ title, heading, description, channelCount }) => { - const { l } = usePrefixedTranslation('cmps.loop.swaps.StepSummary'); + const { l } = usePrefixedTranslation('cmps.loop.swap.StepSummary'); const { Wrapper, Heading, Description } = Styled; return ( diff --git a/app/src/components/loop/swap/SwapConfigStep.tsx b/app/src/components/loop/swap/SwapConfigStep.tsx index 688f33147..5fbbdaeac 100644 --- a/app/src/components/loop/swap/SwapConfigStep.tsx +++ b/app/src/components/loop/swap/SwapConfigStep.tsx @@ -45,7 +45,7 @@ const SwapConfigStep: React.FC = ({ onNext, onCancel, }) => { - const { l } = usePrefixedTranslation('cmps.loop.swaps.SwapConfigStep'); + const { l } = usePrefixedTranslation('cmps.loop.swap.SwapConfigStep'); const { Wrapper, Summary, Config } = Styled; return ( diff --git a/app/src/components/loop/swap/SwapProcessing.tsx b/app/src/components/loop/swap/SwapProcessing.tsx index 85fadc476..c02c9f445 100644 --- a/app/src/components/loop/swap/SwapProcessing.tsx +++ b/app/src/components/loop/swap/SwapProcessing.tsx @@ -1,4 +1,8 @@ import React from 'react'; +import loadingJson from 'assets/animations/loading.json'; +import { usePrefixedTranslation } from 'hooks'; +import Animation from 'components/common/Animation'; +import { Title } from 'components/common/text'; import { styled } from 'components/theme'; const Styled = { @@ -9,6 +13,13 @@ const Styled = { align-items: center; justify-content: center; `, + Loader: styled(Animation)` + width: 150px; + height: 150px; + `, + LoadingMessage: styled.div` + text-align: center; + `, ErrorMessage: styled.div` padding: 10px; color: red; @@ -20,14 +31,14 @@ interface Props { } const SwapProcessingStep: React.FC = ({ swapError }) => { - const { Wrapper, ErrorMessage } = Styled; + const { l } = usePrefixedTranslation('cmps.loop.swap.SwapProcessingStep'); + const { Wrapper, Loader, LoadingMessage, ErrorMessage } = Styled; return ( - - Swap Processing. Please wait... -
- ** show loader here ** -
+ + + {l('loadingMsg')} + {swapError && {swapError.message}}
); diff --git a/app/src/components/loop/swap/SwapReviewStep.tsx b/app/src/components/loop/swap/SwapReviewStep.tsx index 04acf4204..161d36a39 100644 --- a/app/src/components/loop/swap/SwapReviewStep.tsx +++ b/app/src/components/loop/swap/SwapReviewStep.tsx @@ -53,7 +53,7 @@ const SwapReviewStep: React.FC = ({ onNext, onCancel, }) => { - const { l } = usePrefixedTranslation('cmps.loop.swaps.SwapReviewStep'); + const { l } = usePrefixedTranslation('cmps.loop.swap.SwapReviewStep'); const { Wrapper, Summary, Invoice, InvoiceRow, Divider } = Styled; return ( diff --git a/app/src/i18n/locales/en-US.json b/app/src/i18n/locales/en-US.json index 2db53fc30..bfc86fab1 100644 --- a/app/src/i18n/locales/en-US.json +++ b/app/src/i18n/locales/en-US.json @@ -12,16 +12,17 @@ "cmps.loop.swap.StepButtons.cancel": "Cancel", "cmps.loop.swap.StepButtons.next": "Next", "cmps.loop.swap.StepButtons.confirm": "Confirm", - "cmps.loop.swaps.StepSummary.channelsSelected": "channels selected", - "cmps.loop.swaps.SwapConfigStep.title": "Step 1 of 2", - "cmps.loop.swaps.SwapConfigStep.heading": "Set Liquidity Parameters", - "cmps.loop.swaps.SwapConfigStep.description": "Use the slider to prevent errors receiving or forwarding payments on the channels selected below.", - "cmps.loop.swaps.SwapReviewStep.title": "Step 2 of 2", - "cmps.loop.swaps.SwapReviewStep.heading": "Review the quote", - "cmps.loop.swaps.SwapReviewStep.description": "Confirm. the invoice total for increasing your inbound liquidity. If you'd like to make adjustments, click the back arrow above.", - "cmps.loop.swaps.SwapReviewStep.amount": "{{type}} Amount", - "cmps.loop.swaps.SwapReviewStep.fees": "Fees", - "cmps.loop.swaps.SwapReviewStep.total": "Invoice Total", + "cmps.loop.swap.StepSummary.channelsSelected": "channels selected", + "cmps.loop.swap.SwapConfigStep.title": "Step 1 of 2", + "cmps.loop.swap.SwapConfigStep.heading": "Set Liquidity Parameters", + "cmps.loop.swap.SwapConfigStep.description": "Use the slider to prevent errors receiving or forwarding payments on the channels selected below.", + "cmps.loop.swap.SwapProcessingStep.loadingMsg": "Configuring Loops", + "cmps.loop.swap.SwapReviewStep.title": "Step 2 of 2", + "cmps.loop.swap.SwapReviewStep.heading": "Review the quote", + "cmps.loop.swap.SwapReviewStep.description": "Confirm. the invoice total for increasing your inbound liquidity. If you'd like to make adjustments, click the back arrow above.", + "cmps.loop.swap.SwapReviewStep.amount": "{{type}} Amount", + "cmps.loop.swap.SwapReviewStep.fees": "Fees", + "cmps.loop.swap.SwapReviewStep.total": "Invoice Total", "cmps.layout.NavMenu.menu": "Menu", "cmps.layout.NavMenu.loop": "Lightning Loop", "cmps.layout.NavMenu.settings": "Settings", diff --git a/app/src/setupTests.ts b/app/src/setupTests.ts index 1ff493d19..d5f188840 100644 --- a/app/src/setupTests.ts +++ b/app/src/setupTests.ts @@ -7,6 +7,8 @@ import 'mobx-react-lite/batchingForReactDom'; import '@testing-library/jest-dom/extend-expect'; // enable i18n translations in unit tests import './i18n'; +// adds support for lottie-web animations in unit test env +import 'jest-canvas-mock'; beforeEach(() => { jest.clearAllMocks(); diff --git a/app/yarn.lock b/app/yarn.lock index c7abe9e9b..9ed2e321e 100644 --- a/app/yarn.lock +++ b/app/yarn.lock @@ -4631,6 +4631,11 @@ color-convert@^2.0.1: dependencies: color-name "~1.1.4" +color-convert@~0.5.0: + version "0.5.3" + resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-0.5.3.tgz#bdb6c69ce660fadffe0b0007cc447e1b9f7282bd" + integrity sha1-vbbGnOZg+t/+CwAHzER+G59ygr0= + color-name@1.1.3: version "1.1.3" resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25" @@ -5159,6 +5164,11 @@ cssesc@^3.0.0: resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== +cssfontparser@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/cssfontparser/-/cssfontparser-1.2.1.tgz#f4022fc8f9700c68029d542084afbaf425a3f3e3" + integrity sha1-9AIvyPlwDGgCnVQghK+69CWj8+M= + cssnano-preset-default@^4.0.7: version "4.0.7" resolved "https://registry.yarnpkg.com/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76" @@ -8249,6 +8259,14 @@ iterate-value@^1.0.0: es-get-iterator "^1.0.2" iterate-iterator "^1.0.1" +jest-canvas-mock@2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/jest-canvas-mock/-/jest-canvas-mock-2.2.0.tgz#45fbc58589c6ce9df50dc90bd8adce747cbdada7" + integrity sha512-DcJdchb7eWFZkt6pvyceWWnu3lsp5QWbUeXiKgEMhwB3sMm5qHM1GQhDajvJgBeiYpgKcojbzZ53d/nz6tXvJw== + dependencies: + cssfontparser "^1.2.1" + parse-color "^1.0.0" + jest-changed-files@^24.9.0: version "24.9.0" resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-24.9.0.tgz#08d8c15eb79a7fa3fc98269bc14b451ee82f8039" @@ -9240,6 +9258,11 @@ loose-envify@^1.0.0, loose-envify@^1.1.0, loose-envify@^1.3.0, loose-envify@^1.4 dependencies: js-tokens "^3.0.0 || ^4.0.0" +lottie-web@5.6.8: + version "5.6.8" + resolved "https://registry.yarnpkg.com/lottie-web/-/lottie-web-5.6.8.tgz#4e19c4985076368ace3e2140ba599fdc4d927c52" + integrity sha512-2b7KDEIzpp3Y+u9BtFMHBkUFWe6TFgVdYvXvpfmbbxAL9XiwHUywAoWH1GhCCMmGqWqkxw8v4IxnOq/3hiOtJg== + loud-rejection@^1.0.0: version "1.6.0" resolved "https://registry.yarnpkg.com/loud-rejection/-/loud-rejection-1.6.0.tgz#5b46f80147edee578870f086d04821cf998e551f" @@ -10360,6 +10383,13 @@ parse-asn1@^5.0.0: pbkdf2 "^3.0.3" safe-buffer "^5.1.1" +parse-color@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/parse-color/-/parse-color-1.0.0.tgz#7b748b95a83f03f16a94f535e52d7f3d94658619" + integrity sha1-e3SLlag/A/FqlPU15S1/PZRlhhk= + dependencies: + color-convert "~0.5.0" + parse-entities@^1.1.0, parse-entities@^1.1.2: version "1.2.2" resolved "https://registry.yarnpkg.com/parse-entities/-/parse-entities-1.2.2.tgz#c31bf0f653b6661354f8973559cb86dd1d5edf50"