diff --git a/README.md b/README.md index 06ca1d73a..df0e49d5c 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,11 @@ only need to download one executable and run one command to get LiT up and runni include the CLI binaries `lncli`, `loop` and `frcli` for convenience in the downloadable archives as well. +## Usage + +Read the [Walkthrough](doc/WALKTHROUGH.md) document to learn more about how to use +Lightning Terminal. + ## Installation There are two options for installing LiT: download the published binaries for your @@ -53,17 +58,12 @@ on your computer. To compile from source code, you'll need to have some prerequisite developer tooling installed on your machine. -- **Go**: LiT's backend web server is written in Go. Instructions for installing Go for - your operating system can be found on the - [golang install](https://golang.org/doc/install) page. The minimum version supported is - Go v1.13. -- **NodeJS**: LiT's frontend is written in TypeScript and built on top of the React JS web - framework. To bundle the assets into Javascript & CSS compatible with web browsers, - NodeJS is required. It can be downloaded and installed by following the instructions on - the [NodeJS download](https://nodejs.org/en/download/) page. -- **Yarn**: a popular package manager for NodeJS application dependencies. Installation - information can be found on the - [Yarn Installation](https://classic.yarnpkg.com/en/docs/install) page. +| Dependency | Description | +| --------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | +| [golang](https://golang.org/doc/install) | LiT's backend web server is written in Go. The minimum version supported is Go v1.13. | +| [protoc](https://grpc.io/docs/protoc-installation/) | Required to compile LND & Loop gRPC proto files at build time | +| [nodejs](https://nodejs.org/en/download/) | LiT's frontend is written in TypeScript and built on top of the React JS web framework. To bundle the assets into Javascript & CSS compatible with web browsers, NodeJS is required. | +| [yarn](https://classic.yarnpkg.com/en/docs/install) | a popular package manager for NodeJS application dependencies | Once you have the necessary prerequisites, LiT can be compiled by running the following commands: @@ -71,7 +71,7 @@ commands: ``` git clone https://github.com/lightninglabs/lightning-terminal.git cd lightning-terminal -make && make install +make install ``` This will produce the `litd` executable and add it to your `GOPATH`. diff --git a/app/src/__tests__/Routes.spec.tsx b/app/src/__tests__/Routes.spec.tsx index 0506de368..5115cdadb 100644 --- a/app/src/__tests__/Routes.spec.tsx +++ b/app/src/__tests__/Routes.spec.tsx @@ -27,7 +27,7 @@ describe('Routes Component', () => { it('should display the History page', async () => { const { findByText, store } = await render(); store.uiStore.goToHistory(); - expect(await findByText('Loop History')).toBeInTheDocument(); + expect(await findByText('History')).toBeInTheDocument(); expect(store.router.location.pathname).toBe('/history'); }); diff --git a/app/src/__tests__/components/history/HistoryPage.spec.tsx b/app/src/__tests__/components/history/HistoryPage.spec.tsx index 2a73c9802..ad994de74 100644 --- a/app/src/__tests__/components/history/HistoryPage.spec.tsx +++ b/app/src/__tests__/components/history/HistoryPage.spec.tsx @@ -18,17 +18,7 @@ describe('HistoryPage', () => { it('should display the title', () => { const { getByText } = render(); - expect(getByText('Loop History')).toBeInTheDocument(); - }); - - it('should display the back link', () => { - const { getByText } = render(); - expect(getByText('Lightning Loop')).toBeInTheDocument(); - }); - - it('should display the back icon', () => { - const { getByText } = render(); - expect(getByText('arrow-left.svg')).toBeInTheDocument(); + expect(getByText('History')).toBeInTheDocument(); }); it('should display the export icon', () => { @@ -45,12 +35,6 @@ describe('HistoryPage', () => { expect(getByText('Updated')).toBeInTheDocument(); }); - it('should navigate back to the Loop Page', () => { - const { getByText } = render(); - fireEvent.click(getByText('arrow-left.svg')); - expect(store.router.location.pathname).toBe('/loop'); - }); - it('should export channels', () => { const { getByText } = render(); fireEvent.click(getByText('download.svg')); diff --git a/app/src/__tests__/components/layout/Layout.spec.tsx b/app/src/__tests__/components/layout/Layout.spec.tsx index acc9aee28..aaf8f1b0f 100644 --- a/app/src/__tests__/components/layout/Layout.spec.tsx +++ b/app/src/__tests__/components/layout/Layout.spec.tsx @@ -39,9 +39,9 @@ describe('Layout component', () => { fireEvent.click(getByText('History')); expect(store.router.location.pathname).toBe('/history'); expect(getByText('History').parentElement).toHaveClass('active'); - fireEvent.click(getByText('Lightning Loop')); + fireEvent.click(getByText('Lightning Terminal')); expect(store.router.location.pathname).toBe('/loop'); - expect(getByText('Lightning Loop').parentElement).toHaveClass('active'); + expect(getByText('Lightning Terminal').parentElement).toHaveClass('active'); }); it('should navigate to the Settings page', () => { @@ -50,8 +50,8 @@ describe('Layout component', () => { fireEvent.click(getByText('Settings')); expect(store.router.location.pathname).toBe('/settings'); expect(getByText('Settings').parentElement).toHaveClass('active'); - fireEvent.click(getByText('Lightning Loop')); + fireEvent.click(getByText('Lightning Terminal')); expect(store.router.location.pathname).toBe('/loop'); - expect(getByText('Lightning Loop').parentElement).toHaveClass('active'); + expect(getByText('Lightning Terminal').parentElement).toHaveClass('active'); }); }); diff --git a/app/src/__tests__/components/loop/LoopPage.spec.tsx b/app/src/__tests__/components/loop/LoopPage.spec.tsx index b73bb8f66..c928f5da6 100644 --- a/app/src/__tests__/components/loop/LoopPage.spec.tsx +++ b/app/src/__tests__/components/loop/LoopPage.spec.tsx @@ -146,7 +146,7 @@ describe('LoopPage component', () => { fireEvent.click(getByText('Next')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('Confirm')); - expect(getByText('Configuring Loops')).toBeInTheDocument(); + expect(getByText('Submitting Loop')).toBeInTheDocument(); await waitFor(() => { expect(grpcMock.unary).toHaveBeenCalledWith( expect.objectContaining({ methodName: 'LoopOut' }), @@ -167,7 +167,7 @@ describe('LoopPage component', () => { fireEvent.click(getByText('Next')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('Confirm')); - expect(getByText('Configuring Loops')).toBeInTheDocument(); + expect(getByText('Submitting Loop')).toBeInTheDocument(); expect(store.buildSwapStore.processingTimeout).toBeDefined(); fireEvent.click(getByText('arrow-left.svg')); expect(getByText('Review Loop amount and fee')).toBeInTheDocument(); diff --git a/app/src/__tests__/components/loop/ProcessingSwaps.spec.tsx b/app/src/__tests__/components/loop/ProcessingSwaps.spec.tsx index 5acb2195a..0591269eb 100644 --- a/app/src/__tests__/components/loop/ProcessingSwaps.spec.tsx +++ b/app/src/__tests__/components/loop/ProcessingSwaps.spec.tsx @@ -84,11 +84,11 @@ describe('ProcessingSwaps component', () => { }); it('should display an FAILED Loop In', () => { - const { getByText } = render(); + const { getByText, getAllByText } = render(); const swap = addSwap(LOOP_IN, FAILED); expect(getByText(swap.ellipsedId)).toBeInTheDocument(); expect(getByText(swap.stateLabel)).toBeInTheDocument(); - expect(getByText('close.svg')).toBeInTheDocument(); + expect(getAllByText('close.svg')).toHaveLength(2); }); it('should display an INITIATED Loop Out', () => { @@ -122,18 +122,18 @@ describe('ProcessingSwaps component', () => { }); it('should display an FAILED Loop Out', () => { - const { getByText } = render(); + const { getByText, getAllByText } = render(); const swap = addSwap(LOOP_OUT, FAILED); expect(getByText(swap.ellipsedId)).toBeInTheDocument(); expect(getByText(swap.stateLabel)).toBeInTheDocument(); - expect(getByText('close.svg')).toBeInTheDocument(); + expect(getAllByText('close.svg')).toHaveLength(2); }); it('should dismiss a failed Loop', () => { - const { getByText } = render(); + const { getAllByText } = render(); addSwap(LOOP_OUT, FAILED); expect(store.swapStore.dismissedSwapIds).toHaveLength(0); - fireEvent.click(getByText('close.svg')); + fireEvent.click(getAllByText('close.svg')[1]); expect(store.swapStore.dismissedSwapIds).toHaveLength(1); }); }); diff --git a/app/src/__tests__/components/loop/SwapWizard.spec.tsx b/app/src/__tests__/components/loop/SwapWizard.spec.tsx index 2c610e76e..ce8928b46 100644 --- a/app/src/__tests__/components/loop/SwapWizard.spec.tsx +++ b/app/src/__tests__/components/loop/SwapWizard.spec.tsx @@ -38,7 +38,7 @@ describe('SwapWizard component', () => { fireEvent.click(getByText('Next')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('Confirm')); - expect(getByText('Configuring Loops')).toBeInTheDocument(); + expect(getByText('Submitting Loop')).toBeInTheDocument(); fireEvent.click(getByText('arrow-left.svg')); expect(getByText('Step 2 of 2')).toBeInTheDocument(); fireEvent.click(getByText('arrow-left.svg')); @@ -106,7 +106,7 @@ describe('SwapWizard component', () => { it('should display the description label', () => { const { getByText } = render(); - expect(getByText('Configuring Loops')).toBeInTheDocument(); + expect(getByText('Submitting Loop')).toBeInTheDocument(); }); }); }); diff --git a/app/src/__tests__/components/tour/TourHost.spec.tsx b/app/src/__tests__/components/tour/TourHost.spec.tsx index 8dd05626c..1184445c4 100644 --- a/app/src/__tests__/components/tour/TourHost.spec.tsx +++ b/app/src/__tests__/components/tour/TourHost.spec.tsx @@ -46,6 +46,7 @@ describe('TourHost component', () => { expect(getByText('Welcome to Lightning Terminal!')).toBeInTheDocument(); fireEvent.click(getByText("Yes! Let's Go")); + fireEvent.click(getByText('Next')); expect(store.settingsStore.sidebarVisible).toBe(true); fireEvent.click(getByText('Next')); @@ -60,13 +61,16 @@ describe('TourHost component', () => { expect(getByText('Welcome to Lightning Terminal!')).toBeInTheDocument(); fireEvent.click(getByText("Yes! Let's Go")); + expect(getByText('New to Loop?')).toBeInTheDocument(); + + fireEvent.click(getByText('Next')); expect(getByText(l('nodeStatus'))).toBeInTheDocument(); // sample data is fetch after step #1 and we need to wait for it await waitFor(() => expect(store.swapStore.sortedSwaps).toHaveLength(7)); fireEvent.click(getByText('Next')); - expect(getByText(l('history'))).toBeInTheDocument(); + expect(getByText(firstLine(l('history')))).toBeInTheDocument(); fireEvent.click(getByText('Next')); expect(getByText(l('inbound'))).toBeInTheDocument(); @@ -75,7 +79,7 @@ describe('TourHost component', () => { expect(getByText(l('outbound'))).toBeInTheDocument(); fireEvent.click(getByText('Next')); - expect(getByText(l('channelList'))).toBeInTheDocument(); + expect(getByText('channel needs your immediate attention')).toBeInTheDocument(); fireEvent.click(getByText('Next')); expect(getByText(l('channelListReceive'))).toBeInTheDocument(); @@ -84,13 +88,13 @@ describe('TourHost component', () => { expect(getByText(l('channelListSend'))).toBeInTheDocument(); fireEvent.click(getByText('Next')); - expect(getByText(l('channelListFee'))).toBeInTheDocument(); + expect(getByText(firstLine(l('channelListFee')))).toBeInTheDocument(); fireEvent.click(getByText('Next')); expect(getByText(l('channelListUptime'))).toBeInTheDocument(); fireEvent.click(getByText('Next')); - expect(getByText(l('channelListPeer'))).toBeInTheDocument(); + expect(getByText(firstLine(l('channelListPeer')))).toBeInTheDocument(); fireEvent.click(getByText('Next')); expect(getByText(l('channelListCapacity'))).toBeInTheDocument(); @@ -99,10 +103,10 @@ describe('TourHost component', () => { expect(getByText(l('export'))).toBeInTheDocument(); fireEvent.click(getByText('Next')); - expect(getByText(firstLine(l('loop')))).toBeInTheDocument(); + expect(getByText("Let's perform a Loop!")).toBeInTheDocument(); fireEvent.click(getByText('Loop', { selector: 'button' })); - expect(getByText(l('loopActions'))).toBeInTheDocument(); + expect(getByText(firstLine(l('loopActions')))).toBeInTheDocument(); fireEvent.click(getByText('Next')); expect(getByText(firstLine(l('channelListSelect')))).toBeInTheDocument(); @@ -126,10 +130,7 @@ describe('TourHost component', () => { fireEvent.click(getByText('Next')); expect(getByText(l('swapProgress'))).toBeInTheDocument(); - fireEvent.click(getByText('Next')); - expect(getByText(firstLine(l('swapMinimize')))).toBeInTheDocument(); - - fireEvent.click(getByText('minimize.svg')); + fireEvent.click(getByText('close.svg')); expect(getByText('Congratulations!')).toBeInTheDocument(); fireEvent.click(getByText('Close')); diff --git a/app/src/assets/images/screenshot.png b/app/src/assets/images/screenshot.png index 83e45fea8..66ae3b9de 100644 Binary files a/app/src/assets/images/screenshot.png and b/app/src/assets/images/screenshot.png differ diff --git a/app/src/components/common/PageHeader.tsx b/app/src/components/common/PageHeader.tsx index 8729ba941..efd910bd8 100644 --- a/app/src/components/common/PageHeader.tsx +++ b/app/src/components/common/PageHeader.tsx @@ -1,7 +1,6 @@ import React, { ReactNode } from 'react'; import { observer } from 'mobx-react-lite'; import { usePrefixedTranslation } from 'hooks'; -import { useStore } from 'store'; import { styled } from 'components/theme'; import { ArrowLeft, Download, HeaderThree, HelpCircle } from '../base'; import Tip from './Tip'; @@ -11,10 +10,9 @@ const Styled = { display: flex; justify-content: space-between; `, - Left: styled.span<{ sidebar?: boolean }>` + Left: styled.span` flex: 1; text-align: left; - padding-left: ${props => (props.sidebar ? '0' : '40px')}; `, Center: styled.span` flex: 1; @@ -56,12 +54,11 @@ const PageHeader: React.FC = ({ onExportClick, }) => { const { l } = usePrefixedTranslation('cmps.common.PageHeader'); - const { settingsStore } = useStore(); const { Wrapper, Left, Center, Right, BackLink } = Styled; return ( - + {onBackClick && ( diff --git a/app/src/components/history/HistoryPage.tsx b/app/src/components/history/HistoryPage.tsx index a5303581b..f655ba80f 100644 --- a/app/src/components/history/HistoryPage.tsx +++ b/app/src/components/history/HistoryPage.tsx @@ -14,17 +14,12 @@ const Styled = { const HistoryPage: React.FC = () => { const { l } = usePrefixedTranslation('cmps.history.HistoryPage'); - const { uiStore, swapStore } = useStore(); + const { swapStore } = useStore(); const { Wrapper } = Styled; return ( - + ); diff --git a/app/src/components/layout/Layout.tsx b/app/src/components/layout/Layout.tsx index f5c26cb28..744a3770a 100644 --- a/app/src/components/layout/Layout.tsx +++ b/app/src/components/layout/Layout.tsx @@ -42,7 +42,7 @@ const Styled = { /* change sidebar dimensions based on collapsed toggle */ width: ${props => (props.collapsed ? '0' : '285px')}; - padding: ${props => (props.collapsed ? '0' : '15px')}; + padding: ${props => (props.collapsed ? '0' : '0 15px')}; transition: all 0.2s; /* set a width on the child to improve the collapse animation */ diff --git a/app/src/components/loop/LoopPage.tsx b/app/src/components/loop/LoopPage.tsx index 93c418b88..189426465 100644 --- a/app/src/components/loop/LoopPage.tsx +++ b/app/src/components/loop/LoopPage.tsx @@ -25,9 +25,9 @@ const LoopPage: React.FC = () => { <> {l('pageTitle')} {nodeStore.network !== 'mainnet' && ( - +
{nodeStore.network} - +
)} ); diff --git a/app/src/components/loop/processing/ProcessingSwaps.tsx b/app/src/components/loop/processing/ProcessingSwaps.tsx index fcbc36775..4873b3b8a 100644 --- a/app/src/components/loop/processing/ProcessingSwaps.tsx +++ b/app/src/components/loop/processing/ProcessingSwaps.tsx @@ -3,7 +3,7 @@ import { observer } from 'mobx-react-lite'; import confirmJson from 'assets/animations/confirm.json'; import { usePrefixedTranslation } from 'hooks'; import { useStore } from 'store'; -import { HeaderFour, Minimize } from 'components/base'; +import { Close, HeaderFour } from 'components/base'; import Animation from 'components/common/Animation'; import Tip from 'components/common/Tip'; import { styled } from 'components/theme'; @@ -49,8 +49,8 @@ const ProcessingSwaps: React.FC = () => {
{l('title')} - - + +
diff --git a/app/src/components/loop/swap/StepSummary.tsx b/app/src/components/loop/swap/StepSummary.tsx index 21be691cc..f9d5cf385 100644 --- a/app/src/components/loop/swap/StepSummary.tsx +++ b/app/src/components/loop/swap/StepSummary.tsx @@ -10,7 +10,7 @@ const Styled = { display: flex; flex-direction: column; justify-content: space-between; - max-width: 265px; + max-width: 300px; `, Description: styled(Small)` opacity: 0.5; diff --git a/app/src/components/loop/swap/SwapConfigStep.tsx b/app/src/components/loop/swap/SwapConfigStep.tsx index 4c5010757..3cab9db41 100644 --- a/app/src/components/loop/swap/SwapConfigStep.tsx +++ b/app/src/components/loop/swap/SwapConfigStep.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { observer } from 'mobx-react-lite'; +import { SwapDirection } from 'types/state'; import styled from '@emotion/styled'; import { usePrefixedTranslation } from 'hooks'; import { useStore } from 'store'; @@ -39,7 +40,11 @@ const SwapConfigStep: React.FC = () => { diff --git a/app/src/components/tour/ChannelListStep.tsx b/app/src/components/tour/ChannelListStep.tsx new file mode 100644 index 000000000..c4d9d01c8 --- /dev/null +++ b/app/src/components/tour/ChannelListStep.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ReactourStepContentArgs } from 'reactour'; +import { observer } from 'mobx-react-lite'; +import { usePrefixedTranslation } from 'hooks'; +import StatusDot from 'components/common/StatusDot'; +import TextStep from './TextStep'; + +const ChannelListStep: React.FC = props => { + const { l } = usePrefixedTranslation('cmps.tour.ChannelListStep'); + + return ( + +

{l('desc')}

+

{l('traffic')}

+ + + {['error', 'warn', 'success'].map(s => ( + + + + + ))} + +
+ + {l(s)}
+
+ ); +}; + +export default observer(ChannelListStep); diff --git a/app/src/components/tour/LoopInfoStep.tsx b/app/src/components/tour/LoopInfoStep.tsx new file mode 100644 index 000000000..7bea1f768 --- /dev/null +++ b/app/src/components/tour/LoopInfoStep.tsx @@ -0,0 +1,31 @@ +import React from 'react'; +import { ReactourStepContentArgs } from 'reactour'; +import { observer } from 'mobx-react-lite'; +import { usePrefixedTranslation } from 'hooks'; +import TextStep from './TextStep'; + +const LoopInfoStep: React.FC = props => { + const { l } = usePrefixedTranslation('cmps.tour.LoopInfoStep'); + + return ( + +

+ {l('new')} +

+

{l('desc')}

+

+ {l('learn1')}{' '} + + {l('learn2')} + {' '} + {l('learn3')} +

+
+ ); +}; + +export default observer(LoopInfoStep); diff --git a/app/src/components/tour/NodeStatusStep.tsx b/app/src/components/tour/NodeStatusStep.tsx new file mode 100644 index 000000000..64831b4be --- /dev/null +++ b/app/src/components/tour/NodeStatusStep.tsx @@ -0,0 +1,51 @@ +import React from 'react'; +import { ReactourStepContentArgs } from 'reactour'; +import { observer } from 'mobx-react-lite'; +import { usePrefixedTranslation } from 'hooks'; +import { Bitcoin, Bolt } from 'components/base'; +import { styled } from 'components/theme'; +import TextStep from './TextStep'; + +const Styled = { + Legend: styled.p` + > span { + display: flex; + margin-top: 10px; + } + `, + BoltIcon: styled(Bolt)` + background-color: ${props => props.theme.colors.darkBlue}; + color: ${props => props.theme.colors.white}; + border-radius: 32px; + margin-right: 10px; + `, + BitcoinIcon: styled(Bitcoin)` + background-color: ${props => props.theme.colors.darkBlue}; + color: ${props => props.theme.colors.white}; + border-radius: 32px; + margin-right: 10px; + `, +}; + +const NodeStatusStep: React.FC = props => { + const { l } = usePrefixedTranslation('cmps.tour.NodeStatusStep'); + + const { Legend, BoltIcon, BitcoinIcon } = Styled; + return ( + +

{l('desc')}

+ + + + {l('ln')} + + + + {l('onchain')} + + +
+ ); +}; + +export default observer(NodeStatusStep); diff --git a/app/src/components/tour/TourHost.tsx b/app/src/components/tour/TourHost.tsx index fe6fcd223..90ca4507c 100644 --- a/app/src/components/tour/TourHost.tsx +++ b/app/src/components/tour/TourHost.tsx @@ -1,22 +1,29 @@ +/* eslint-disable react/display-name */ import React from 'react'; import Tour, { ReactourStep } from 'reactour'; import { observer } from 'mobx-react-lite'; import { useTheme } from 'emotion-theming'; import { useStore } from 'store'; import { styled, Theme } from 'components/theme'; +import ChannelListStep from './ChannelListStep'; +import LoopInfoStep from './LoopInfoStep'; +import NodeStatusStep from './NodeStatusStep'; import SuccessStep from './SuccessStep'; import { createTextStep } from './TextStep'; import WelcomeStep from './WelcomeStep'; const tourSteps: ReactourStep[] = [ { - // eslint-disable-next-line react/display-name content: p => , style: { maxWidth: 900 }, }, + { + content: p => , + style: { maxWidth: 900 }, + }, { selector: '[data-tour="node-status"]', - content: createTextStep('nodeStatus'), + content: p => , }, { selector: '[data-tour="history"]', @@ -33,7 +40,8 @@ const tourSteps: ReactourStep[] = [ }, { selector: '[data-tour="channel-list"]', - content: createTextStep('channelList'), + content: p => , + style: { maxWidth: 600 }, }, { selector: '[data-tour="channel-list-receive"]', @@ -66,10 +74,12 @@ const tourSteps: ReactourStep[] = [ { selector: '[data-tour="loop"]', content: createTextStep('loop', false), + style: { maxWidth: 500 }, }, { selector: '[data-tour="loop-actions"]', content: createTextStep('loopActions'), + style: { maxWidth: 500 }, stepInteraction: false, }, { @@ -105,11 +115,6 @@ const tourSteps: ReactourStep[] = [ content: createTextStep('swapProgress'), }, { - selector: '[data-tour="swap-minimize"]', - content: createTextStep('swapMinimize', false), - }, - { - // eslint-disable-next-line react/display-name content: p => , style: { maxWidth: 900 }, }, @@ -117,6 +122,10 @@ const tourSteps: ReactourStep[] = [ const Styled = { Tour: styled(Tour)` + &.reactour__helper { + border-radius: 10px; + } + [data-tour-elem='badge'] { font-family: ${props => props.theme.fonts.open.regular}; font-size: ${props => props.theme.sizes.xxs}; diff --git a/app/src/components/tour/WelcomeStep.tsx b/app/src/components/tour/WelcomeStep.tsx index 969d66ab0..92b425d20 100644 --- a/app/src/components/tour/WelcomeStep.tsx +++ b/app/src/components/tour/WelcomeStep.tsx @@ -11,17 +11,17 @@ const Styled = { font-size: ${props => props.theme.sizes.xs}; font-style: italic; opacity: 0.8; - margin-bottom: 50px; + margin-bottom: 40px; `, Footer: styled.div` - display: flex; - justify-content: space-between; + margin-top: 30px; `, LinkButton: styled(Button)` - color: ${props => props.theme.colors.darkBlue}; + color: ${props => props.theme.colors.gray}; padding: 0; min-width: auto; height: auto; + margin-left: 40px; &:hover { color: ${props => props.theme.colors.blue}; @@ -47,10 +47,11 @@ const WelcomeStep: React.FC = props => { return (

{l('desc')}

+

{l('tour')}

{l('walkthrough1')}{' '} diff --git a/app/src/i18n/locales/en-US.json b/app/src/i18n/locales/en-US.json index ffb636225..47f2f18a4 100644 --- a/app/src/i18n/locales/en-US.json +++ b/app/src/i18n/locales/en-US.json @@ -14,7 +14,7 @@ "cmps.common.PageHeader.exportTip": "Download CSV", "cmps.common.PageHeader.helpTip": "Take a Tour", "cmps.history.HistoryPage.backText": "Lightning Loop", - "cmps.history.HistoryPage.pageTitle": "Loop History", + "cmps.history.HistoryPage.pageTitle": "History", "cmps.history.HistoryRowHeader.status": "Status", "cmps.history.HistoryRowHeader.amount": "Amount", "cmps.history.HistoryRowHeader.type": "Type", @@ -27,7 +27,7 @@ "cmps.loop.ChannelRowHeader.canSend": "Can Send", "cmps.loop.ChannelRowHeader.feeRate": "In Fee %", "cmps.loop.ChannelRowHeader.feeRateTip": "The fee charged by the peer to route inbound payments through the channel", - "cmps.loop.ChannelRowHeader.upTime": "Up Time %", + "cmps.loop.ChannelRowHeader.upTime": "Uptime %", "cmps.loop.ChannelRowHeader.peer": "Peer/Alias", "cmps.loop.ChannelRowHeader.capacity": "Capacity", "cmps.loop.SelectedChannels.channelSelected": "channel selected", @@ -42,15 +42,16 @@ "cmps.loop.LoopTiles.outbound": "Total Outbound Liquidity", "cmps.loop.processing.FailedSwap.dismissTip": "Dismiss Failed Loop", "cmps.loop.processing.ProcessingSwaps.title": "Processing Loops", - "cmps.loop.processing.ProcessingSwaps.minimizeTip": "Minimize", + "cmps.loop.processing.ProcessingSwaps.closeTip": "Close", "cmps.loop.processing.ProcessingSwaps.complete": "Loops Complete", "cmps.loop.swap.StepButtons.cancel": "Cancel", "cmps.loop.swap.StepButtons.next": "Next", "cmps.loop.swap.StepButtons.confirm": "Confirm", "cmps.loop.swap.SwapConfigStep.title": "Step 1 of 2", "cmps.loop.swap.SwapConfigStep.heading": "{{type}} Amount", - "cmps.loop.swap.SwapConfigStep.description": "Loop moves funds in and out of the Lightning Network, simplifying the sending and receiving of payments via Lightning. Use the slider to select the amount to Loop In or Loop Out of the Lightning Network", - "cmps.loop.swap.SwapProcessingStep.loadingMsg": "Configuring Loops", + "cmps.loop.swap.SwapConfigStep.loopInDesc": "Loop In moves funds onto the Lightning Network from an on-chain wallet to provide outbound capacity to send funds over Lightning. Use the slider to select the amount to Loop In or Loop Out of the Lightning Network", + "cmps.loop.swap.SwapConfigStep.loopOutDesc": "Loop Out moves funds off of the Lightning Network into an on-chain address while providing inbound capacity to receive funds on Lightning. Use the slider to select the amount to Loop In or Loop Out of the Lightning Network", + "cmps.loop.swap.SwapProcessingStep.loadingMsg": "Submitting Loop", "cmps.loop.swap.SwapReviewStep.title": "Step 2 of 2", "cmps.loop.swap.SwapReviewStep.heading": "Review Loop amount and fee", "cmps.loop.swap.SwapReviewStep.description": "Confirm the fee for improving your channel liquidity. If you'd like to make adjustments, click the back arrow above.", @@ -59,7 +60,7 @@ "cmps.loop.swap.SwapReviewStep.total": "Total", "cmps.loop.swap.SwapWizard.backTip": "Back to Previous", "cmps.layout.NavMenu.menu": "Menu", - "cmps.layout.NavMenu.loop": "Lightning Loop", + "cmps.layout.NavMenu.loop": "Lightning Terminal", "cmps.layout.NavMenu.history": "History", "cmps.layout.NavMenu.settings": "Settings", "cmps.NodeStatus.title": "Node Status", @@ -82,29 +83,28 @@ "cmps.tour.TextStep.next": "Next", "cmps.tour.TextStep.nodeStatus": "Here are the confirmed balances on the Lightning Network and on-chain", "cmps.tour.TextStep.export": "Click here if you want to export your channels as a CSV file", - "cmps.tour.TextStep.history": "This tile displays the two most recent swaps that have been initiated", + "cmps.tour.TextStep.history": "This tile displays the two most recent loops that have been initiated. It is perfectly safe to perform multiple loops at one time.\n Clicking on the Expand icon will show you more details about the currently processing Loops.", "cmps.tour.TextStep.inbound": "This tile displays the amount of Bitcoin that this node can receive over your Lightning channels", "cmps.tour.TextStep.outbound": "This tile displays the amount of Bitcoin that this node can send over your Lightning channels", - "cmps.tour.TextStep.channelList": "This is the list of open channels that this node has with other peers on the Lightning Network", - "cmps.tour.TextStep.channelListReceive": "The amount that can be received over the channel", - "cmps.tour.TextStep.channelListSend": "The amount that can be sent over this channel", - "cmps.tour.TextStep.channelListFee": "The routing fee rate charged by the peer to receive payments over the channel. The percent is rounded to two decimal places. Hover over the value to see the exact rate expressed as parts-per-million", + "cmps.tour.TextStep.channelListReceive": "The amount that can be received over each channel", + "cmps.tour.TextStep.channelListSend": "The amount that can be sent over each channel", + "cmps.tour.TextStep.channelListFee": "The routing fee rate charged by the peer to receive payments over the channel.\n The percent is rounded to two decimal places. Hover over the value to see the exact rate expressed as parts-per-million.", "cmps.tour.TextStep.channelListUptime": "The uptime percentage of the channel peer", - "cmps.tour.TextStep.channelListPeer": "The alias of the channel peer. Hover over this field to see the peer's pubkey", + "cmps.tour.TextStep.channelListPeer": "The alias of the channel peer.\n Hover over this field to see the peer's pubkey.", "cmps.tour.TextStep.channelListCapacity": "The total capacity of the channel", "cmps.tour.TextStep.loop": "Let's perform a Loop!\n Click on the Loop button to start.", "cmps.tour.TextStep.channelListSelect": "Optionally, you can select one or more channels if you want the swap to only transfer funds over those chosen channels. This is helpful if you want to only adjust the balance of specific channels.\n If you choose more than one, there is no guarantee how much funds will be transferred over each channel. LND will use its knowledge of the network to determine how to best split up the payment, if necessary. It is possible that only one of the channels will be used if the payment can be successfully routed through it.\n If you do not care which channels to use, then do not select any. The Lightning payment will be routed through any channel(s) with enough balance to allow it to succeed.", - "cmps.tour.TextStep.loopActions": "The action bar displays information about the selected channels and the buttons to Loop Out or Loop In.", + "cmps.tour.TextStep.loopActions": "The action bar displays information about the selected channels and the buttons to Loop Out or Loop In.\n Use Loop Out when you want to transfer funds from your Lightning channels to your on-chain wallet.\n Use Loop In when you want to transfer funds from your on-chain wallet into your Lightning channels.", "cmps.tour.TextStep.loopOut": "We will use Loop Out to transfer funds from your channel balances to your on-chain wallet.\n Click on the Loop Out button to continue.", "cmps.tour.TextStep.loopAmount": "You now need to specify the amount you would like to Loop Out. Drag the slider to adjust the amount.\n Click the Next button to continue.", "cmps.tour.TextStep.loopReview": "Review the Loop amount and the fee. Keep in mind that you will only be charged the fee. The Loop amount will remain on your node once the swap completes. The balance will just be shifted from your channel balance to your on-chain wallet balance.\n Click the Confirm button to continue.", "cmps.tour.TextStep.loopProgress": "Wait for the swap to be submitted to the server", "cmps.tour.TextStep.processingSwaps": "Your swap will now be displayed in the Processing Loops section. It shows you the swap id, amount, and current status as a progress bar.\n Swaps require on-chain transactions, so you will need to wait for confirmations before they will complete successfully.", - "cmps.tour.TextStep.swapMinimize": "You can minimize the Processing Loops section any time by clicking on this minimize icon.\n It is perfectly safe to perform multiple swaps at one time.\n Click in the icon to continue.", "cmps.tour.TextStep.swapProgress": "Once the progress bar turns green, your swap has been completed successfully.", "cmps.tour.TextStep.congrats": "Congratulations!\n You have completed the tour.\n Happy Looping!", "cmps.tour.WelcomeStep.header": "Welcome to Lightning Terminal!", - "cmps.tour.WelcomeStep.desc": "This tour will walk you through the different sections of the dashboard and show you how to perform a Loop.", + "cmps.tour.WelcomeStep.desc": "A browser-based interface for Loop, presenting a visual representation of your of your channels and balances. Easily identify inefficient channels, perform loops with just a few clicks, and monitor the progress of your ongoing Loops.", + "cmps.tour.WelcomeStep.tour": "This tour will walk you through the different sections of the dashboard and show you how to perform a Loop.", "cmps.tour.WelcomeStep.walkthrough1": "You may also reference the ", "cmps.tour.WelcomeStep.walkthrough2": "Walkthrough", "cmps.tour.WelcomeStep.walkthrough3": " document for additional guidance on how to use the product.", @@ -112,6 +112,19 @@ "cmps.tour.WelcomeStep.question": "Would you like to take a tour?", "cmps.tour.WelcomeStep.noThanks": "No Thanks", "cmps.tour.WelcomeStep.yes": "Yes! Let's Go", + "cmps.tour.LoopInfoStep.new": "New to Loop?", + "cmps.tour.LoopInfoStep.desc": "Lightning Loop can be used to transfer your Bitcoin balance between the Lightning Network and the on-chain Bitcoin network. This allows you to easily alter the balances of your channels without needing to close and reopen them, which extends the lifetime of your channels, and also enables operations to be batched as well.", + "cmps.tour.LoopInfoStep.learn1": "To learn more, visit the", + "cmps.tour.LoopInfoStep.learn2": "Loop", + "cmps.tour.LoopInfoStep.learn3": "website. Otherwise, continue with the tour to try it out now.", + "cmps.tour.NodeStatusStep.desc": "Here are the confirmed balances on the Lightning Network and on-chain", + "cmps.tour.NodeStatusStep.ln": "Lightning Network", + "cmps.tour.NodeStatusStep.onchain": "On-chain", + "cmps.tour.ChannelListStep.desc": "This is the list of open channels that this node has with other peers on the Lightning Network.", + "cmps.tour.ChannelListStep.traffic": "It uses a traffic light system to help you prioritize the channels that need your attention.", + "cmps.tour.ChannelListStep.error": "channel needs your immediate attention", + "cmps.tour.ChannelListStep.warn": "you should attend to the channel soon", + "cmps.tour.ChannelListStep.success": "all is well, and no extra attention is required", "cmps.tour.SuccessStep.header": "Congratulations!", "cmps.tour.SuccessStep.complete": "You have completed the tour. Happy Looping!", "cmps.tour.SuccessStep.close": "Close", diff --git a/app/src/store/stores/buildSwapStore.ts b/app/src/store/stores/buildSwapStore.ts index dde4ed2b0..12b6c256f 100644 --- a/app/src/store/stores/buildSwapStore.ts +++ b/app/src/store/stores/buildSwapStore.ts @@ -408,7 +408,12 @@ class BuildSwapStore { */ @action.bound requestSwap() { - const delay = process.env.NODE_ENV !== 'test' ? SWAP_ABORT_DELAY : 1; + const delay = + process.env.NODE_ENV === 'test' + ? 1 // use a 1 ms delay for unit tests + : this._store.uiStore.tourVisible + ? 1500 // use a 1.5 second delay during the tour + : SWAP_ABORT_DELAY; const { amount, direction, quote } = this; this._store.log.info( `executing ${direction} for ${amount} sats (delaying for ${delay}ms)`, diff --git a/app/src/store/stores/uiStore.ts b/app/src/store/stores/uiStore.ts index 584ae1c55..1523a1105 100644 --- a/app/src/store/stores/uiStore.ts +++ b/app/src/store/stores/uiStore.ts @@ -106,7 +106,7 @@ export default class UiStore { setTourActiveStep(step: number) { this.tourActiveStep = step; - if (step === 1) { + if (step === 2) { // #1 is the node-status step // show the sidebar if autoCollapse is enabled if (!this._store.settingsStore.sidebarVisible) { @@ -121,21 +121,28 @@ export default class UiStore { this._store.unsubscribeFromStreams(); // fetch all the sample data from the backend this._store.fetchAllData(); - } else if (step === 2) { - // #2 is the export icon + } else if (step === 3) { + // #3 is the export icon // hide the sidebar if autoCollapse is enabled if (this._store.settingsStore.autoCollapse) { this._store.settingsStore.sidebarVisible = false; } - } else if (step === 3) { - // #3 is the history step + } else if (step === 4) { + // #4 is the history step // change the most recent swap to be pending this._store.swapStore.sortedSwaps[0].state = SwapState.INITIATED; - this._store.swapStore.sortedSwaps[0].lastUpdateTime = Date.now() * 1000 * 1000; - } else if (step === 21 /* swap-progress */) { - // #21 is the swap-progress step + // set the timestamp far in the future so it doesn't automatically disappear + // from the Processing Loops list after 5 mins + const tomorrow = Date.now() + 24 * 60 * 60 * 1000; + this._store.swapStore.sortedSwaps[0].lastUpdateTime = tomorrow * 1000 * 1000; + } else if (step === 22 /* swap-progress */) { + // #22 is the swap-progress step // force the swap to be 100% complete this._store.swapStore.sortedSwaps[0].state = SwapState.SUCCESS; + } else if (step === 23 /* congrats */) { + // #23 is the congrats step + // hide the processing swaps section + this.processingSwapsVisible = false; } }