From d004db803726f845c6ad1f535eed22caeda4a26c Mon Sep 17 00:00:00 2001 From: Fouad Date: Mon, 28 Nov 2022 11:22:11 +0000 Subject: [PATCH] Feature/nym connect UI updates (#1784) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * create custom titlebar * create help page * create generic modal component * create separate connection time component * link to shipyard docs * move timer to separate component and update connection status component usage * use separate component for copying ip and port details * only show infomodal once after connection * set service provider on tauri side * Emit events when stopped * listen and unlisten for tauri events * connect: add trace log to get_services * Add back CI notifications * Update README Co-authored-by: Jon Häggblad Co-authored-by: Mark Sinclair <14054343+mmsinclair@users.noreply.github.com> --- .github/workflows/nym-connect.yml | 32 ++--- nym-connect/.storybook/preview.js | 20 ++-- nym-connect/Cargo.lock | 2 +- nym-connect/README.md | 4 +- nym-connect/src-tauri/Cargo.toml | 2 +- nym-connect/src-tauri/src/models/mod.rs | 4 +- .../src-tauri/src/operations/directory/mod.rs | 2 + nym-connect/src-tauri/src/tasks.rs | 35 +++++- nym-connect/src-tauri/tauri.conf.json | 23 ++-- nym-connect/src/App.tsx | 23 +++- nym-connect/src/assets/help-step-four.png | Bin 0 -> 8530 bytes nym-connect/src/assets/help-step-one.png | Bin 0 -> 14403 bytes nym-connect/src/assets/help-step-three.png | Bin 0 -> 18990 bytes nym-connect/src/assets/help-step-two.png | Bin 0 -> 35631 bytes nym-connect/src/components/AppWindowFrame.tsx | 17 ++- .../src/components/ConnectionButton.tsx | 73 ++++++------ .../src/components/ConnectionStatus.tsx | 71 +++++------ .../src/components/ConntectionTimer.tsx | 26 +++++ nym-connect/src/components/CustomTitleBar.tsx | 41 +++++++ nym-connect/src/components/HelpPage.tsx | 33 ++++++ .../src/components/HelpPageActions.tsx | 20 ++++ nym-connect/src/components/HelpPageImage.tsx | 5 + .../src/components/HelpPageStepIndicator.tsx | 15 +++ nym-connect/src/components/InfoModal.tsx | 69 +++++++++++ .../src/components/IpAddressAndPort.tsx | 8 +- .../src/components/IpAddressAndPortModal.tsx | 41 +++++++ .../components/ServiceProviderSelector.tsx | 110 ++++++++---------- nym-connect/src/context/main.tsx | 60 ++++++++-- nym-connect/src/context/mocks/main.tsx | 24 ++++ nym-connect/src/index.tsx | 8 +- nym-connect/src/layouts/ConnectedLayout.tsx | 37 ++++-- nym-connect/src/layouts/DefaultLayout.tsx | 38 +++--- nym-connect/src/layouts/HelpGuideLayout.tsx | 70 +++++++++++ nym-connect/src/stories/AppFlow.stories.tsx | 13 ++- .../src/stories/ConnectedLayout.stories.tsx | 6 +- .../src/stories/DefaultLayout.stories.tsx | 21 +++- nym-connect/src/theme/index.tsx | 6 +- nym-connect/src/theme/mui-theme.d.ts | 1 + nym-connect/src/theme/theme.tsx | 6 +- nym-connect/src/types/error.ts | 4 + nym-connect/src/types/event.ts | 6 + 41 files changed, 718 insertions(+), 258 deletions(-) create mode 100644 nym-connect/src/assets/help-step-four.png create mode 100644 nym-connect/src/assets/help-step-one.png create mode 100644 nym-connect/src/assets/help-step-three.png create mode 100644 nym-connect/src/assets/help-step-two.png create mode 100644 nym-connect/src/components/ConntectionTimer.tsx create mode 100644 nym-connect/src/components/CustomTitleBar.tsx create mode 100644 nym-connect/src/components/HelpPage.tsx create mode 100644 nym-connect/src/components/HelpPageActions.tsx create mode 100644 nym-connect/src/components/HelpPageImage.tsx create mode 100644 nym-connect/src/components/HelpPageStepIndicator.tsx create mode 100644 nym-connect/src/components/InfoModal.tsx create mode 100644 nym-connect/src/components/IpAddressAndPortModal.tsx create mode 100644 nym-connect/src/context/mocks/main.tsx create mode 100644 nym-connect/src/layouts/HelpGuideLayout.tsx create mode 100644 nym-connect/src/types/error.ts create mode 100644 nym-connect/src/types/event.ts diff --git a/.github/workflows/nym-connect.yml b/.github/workflows/nym-connect.yml index 3a08744fc9..5a652e92d2 100644 --- a/.github/workflows/nym-connect.yml +++ b/.github/workflows/nym-connect.yml @@ -41,19 +41,19 @@ jobs: - name: Keybase - Node Install run: npm install working-directory: .github/workflows/support-files -# - name: Keybase - Send Notification -# env: -# NYM_NOTIFICATION_KIND: nym-connect -# NYM_PROJECT_NAME: "nym-connect" -# NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" -# NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}" -# GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" -# GIT_BRANCH: "${GITHUB_REF##*/}" -# KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" -# KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" -# KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" -# KEYBASE_NYM_CHANNEL: "ci-nym-connect" -# IS_SUCCESS: "${{ job.status == 'success' }}" -# uses: docker://keybaseio/client:stable-node -# with: -# args: .github/workflows/support-files/notifications/entry_point.sh + - name: Keybase - Send Notification + env: + NYM_NOTIFICATION_KIND: nym-connect + NYM_PROJECT_NAME: "nym-connect" + NYM_CI_WWW_BASE: "${{ secrets.NYM_CI_WWW_BASE }}" + NYM_CI_WWW_LOCATION: "nym-connect-${{ env.GITHUB_REF_SLUG }}" + GIT_COMMIT_MESSAGE: "${{ github.event.head_commit.message }}" + GIT_BRANCH: "${GITHUB_REF##*/}" + KEYBASE_NYMBOT_USERNAME: "${{ secrets.KEYBASE_NYMBOT_USERNAME }}" + KEYBASE_NYMBOT_PAPERKEY: "${{ secrets.KEYBASE_NYMBOT_PAPERKEY }}" + KEYBASE_NYMBOT_TEAM: "${{ secrets.KEYBASE_NYMBOT_TEAM }}" + KEYBASE_NYM_CHANNEL: "ci-nym-connect" + IS_SUCCESS: "${{ job.status == 'success' }}" + uses: docker://keybaseio/client:stable-node + with: + args: .github/workflows/support-files/notifications/entry_point.sh diff --git a/nym-connect/.storybook/preview.js b/nym-connect/.storybook/preview.js index 2dbe041198..9c14c3ec99 100644 --- a/nym-connect/.storybook/preview.js +++ b/nym-connect/.storybook/preview.js @@ -1,26 +1,26 @@ import { NymMixnetTheme } from '../src/theme'; -import { ClientContextProvider } from '../src/context/main'; import { Fonts } from './preview-fonts'; - -const withThemeProvider= (Story, context) =>{ +import { MockProvider } from '../src/context/mocks/main'; +const withThemeProvider = (Story, context) => { return ( - - + + - + - ) -} + ); +}; + export const decorators = [withThemeProvider]; export const parameters = { - actions: { argTypesRegex: "^on[A-Z].*" }, + actions: { argTypesRegex: '^on[A-Z].*' }, controls: { matchers: { color: /(background|color)$/i, date: /Date$/, }, }, -} \ No newline at end of file +}; diff --git a/nym-connect/Cargo.lock b/nym-connect/Cargo.lock index 0d0022042e..db5454b1bc 100644 --- a/nym-connect/Cargo.lock +++ b/nym-connect/Cargo.lock @@ -596,7 +596,7 @@ dependencies = [ [[package]] name = "client-core" -version = "1.0.1" +version = "1.1.0" dependencies = [ "config", "crypto", diff --git a/nym-connect/README.md b/nym-connect/README.md index 5a247235ab..bbd9b55bbb 100644 --- a/nym-connect/README.md +++ b/nym-connect/README.md @@ -12,8 +12,8 @@ Nym Connects sets up a SOCKS5 proxy for local applications to use. ## Installation prerequisites - Linux / Mac - `Yarn` -- `NodeJS >= v16.8.0` -- `Rust & cargo >= v1.56` +- `NodeJS >= v16` +- `Rust & cargo` ## Installation prerequisites - Windows diff --git a/nym-connect/src-tauri/Cargo.toml b/nym-connect/src-tauri/Cargo.toml index 11f79849b7..dfaf452dd1 100644 --- a/nym-connect/src-tauri/Cargo.toml +++ b/nym-connect/src-tauri/Cargo.toml @@ -32,7 +32,7 @@ reqwest = { version = "0.11", features = ["json"] } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" tap = "1.0.1" -tauri = { version = "^1.1.1", features = ["clipboard-write-text", "shell-open", "system-tray", "updater"] } +tauri = { version = "^1.1.1", features = ["clipboard-write-text", "macos-private-api", "shell-open", "system-tray", "updater", "window-close", "window-start-dragging"] } tendermint-rpc = "0.23.0" thiserror = "1.0" tokio = { version = "1.21.2", features = ["sync", "time"] } diff --git a/nym-connect/src-tauri/src/models/mod.rs b/nym-connect/src-tauri/src/models/mod.rs index ded6a479fc..e5076ac5c0 100644 --- a/nym-connect/src-tauri/src/models/mod.rs +++ b/nym-connect/src-tauri/src/models/mod.rs @@ -45,7 +45,7 @@ pub struct AppEventConnectionStatusChangedPayload { } #[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct DirectoryService { pub id: String, pub description: String, @@ -53,7 +53,7 @@ pub struct DirectoryService { } #[cfg_attr(test, derive(ts_rs::TS))] -#[derive(Clone, serde::Serialize, serde::Deserialize)] +#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)] pub struct DirectoryServiceProvider { pub id: String, pub description: String, diff --git a/nym-connect/src-tauri/src/operations/directory/mod.rs b/nym-connect/src-tauri/src/operations/directory/mod.rs index aaac2276d9..b6c45fa8c0 100644 --- a/nym-connect/src-tauri/src/operations/directory/mod.rs +++ b/nym-connect/src-tauri/src/operations/directory/mod.rs @@ -6,9 +6,11 @@ static SERVICE_PROVIDER_WELLKNOWN_URL: &str = #[tauri::command] pub async fn get_services() -> Result> { + log::trace!("Fetching services"); let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL) .await? .json::>() .await?; + log::trace!("Received: {:#?}", res); Ok(res) } diff --git a/nym-connect/src-tauri/src/tasks.rs b/nym-connect/src-tauri/src/tasks.rs index 751f7c5566..0d13913af1 100644 --- a/nym-connect/src-tauri/src/tasks.rs +++ b/nym-connect/src-tauri/src/tasks.rs @@ -66,6 +66,12 @@ pub fn start_nym_socks5_client( Ok((socks5_ctrl_tx, socks5_status_rx, used_gateway)) } +#[derive(Clone, serde::Serialize)] +struct Payload { + title: String, + message: String, +} + /// The disconnect listener listens to the channel setup between the socks5 proxy task and the main /// tauri task. Primarily it listens for shutdown messages, and updates the state accordingly. pub fn start_disconnect_listener( @@ -78,14 +84,39 @@ pub fn start_disconnect_listener( match status_receiver.await { Ok(Socks5StatusMessage::Stopped) => { log::info!("SOCKS5 task reported it has finished"); + window + .emit( + "socks5-event", + Payload { + title: "SOCKS5 finished".into(), + message: "SOCKS5 task reported it has finished".into(), + }, + ) + .unwrap(); } Ok(Socks5StatusMessage::FailedToStart) => { log::info!("SOCKS5 task reported it failed to start"); + window + .emit( + "socks5-event", + Payload { + title: "SOCKS5 error".into(), + message: "SOCKS5 failed to start".into(), + }, + ) + .unwrap(); } Err(_) => { log::info!("SOCKS5 task appears to have stopped abruptly"); - // TODO: we should probably generate some events here, or otherwise signal to the - // frontend. + window + .emit( + "socks5-event", + Payload { + title: "SOCKS5 error".into(), + message: "SOCKS5 stopped abruptly".into(), + }, + ) + .unwrap(); } } diff --git a/nym-connect/src-tauri/tauri.conf.json b/nym-connect/src-tauri/tauri.conf.json index c4fce76f06..b9cbfd8233 100644 --- a/nym-connect/src-tauri/tauri.conf.json +++ b/nym-connect/src-tauri/tauri.conf.json @@ -10,6 +10,7 @@ "beforeBuildCommand": "" }, "tauri": { + "macOSPrivateApi": true, "systemTray": { "iconPath": "icons/tray_icon.png", "iconAsTemplate": true @@ -18,13 +19,7 @@ "active": true, "targets": "all", "identifier": "net.nymtech.connect", - "icon": [ - "icons/32x32.png", - "icons/128x128.png", - "icons/128x128@2x.png", - "icons/icon.icns", - "icons/icon.ico" - ], + "icon": ["icons/32x32.png", "icons/128x128.png", "icons/128x128@2x.png", "icons/icon.icns", "icons/icon.ico"], "resources": [], "externalBin": [], "copyright": "Copyright © 2021-2022 Nym Technologies SA", @@ -49,9 +44,7 @@ }, "updater": { "active": true, - "endpoints": [ - "https://nymtech.net/.wellknown/connect/updater.json" - ], + "endpoints": ["https://nymtech.net/.wellknown/connect/updater.json"], "dialog": true, "pubkey": "dW50cnVzdGVkIGNvbW1lbnQ6IG1pbmlzaWduIHB1YmxpYyBrZXk6IENCNzQ2M0E5N0VFODE2NApSV1JrZ2U2WE9rYTNETTg1OTBKdE5uWUEra0hML2syOVUvQ2lxZmFZRzZ1T3NWbGM0eVRzUTVhVwo=" }, @@ -61,14 +54,20 @@ }, "clipboard": { "writeText": true + }, + "window": { + "startDragging": true, + "close": true } }, "windows": [ { "title": "NymConnect", "width": 240, - "height": 500, - "resizable": false + "height": 540, + "resizable": false, + "decorations": false, + "transparent": true } ], "security": { diff --git a/nym-connect/src/App.tsx b/nym-connect/src/App.tsx index 10a2995f34..90dd1a4230 100644 --- a/nym-connect/src/App.tsx +++ b/nym-connect/src/App.tsx @@ -1,37 +1,50 @@ -import React from 'react'; +import React, { useEffect } from 'react'; +import { DateTime } from 'luxon'; import { ConnectionStatusKind } from './types'; import { useClientContext } from './context/main'; import { DefaultLayout } from './layouts/DefaultLayout'; import { ConnectedLayout } from './layouts/ConnectedLayout'; +import { HelpGuideLayout } from './layouts/HelpGuideLayout'; export const App: React.FC = () => { const context = useClientContext(); const [busy, setBusy] = React.useState(); + const [showInfoModal, setShowInfoModal] = React.useState(false); const handleConnectClick = React.useCallback(async () => { - const oldStatus = context.connectionStatus; - if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) { + const currentStatus = context.connectionStatus; + if (currentStatus === ConnectionStatusKind.connected || currentStatus === ConnectionStatusKind.disconnected) { setBusy(true); // eslint-disable-next-line default-case - switch (oldStatus) { + switch (currentStatus) { case ConnectionStatusKind.disconnected: await context.startConnecting(); + context.setConnectedSince(DateTime.now()); break; case ConnectionStatusKind.connected: await context.startDisconnecting(); + context.setConnectedSince(undefined); break; } setBusy(false); } }, [context.connectionStatus]); + useEffect(() => { + if (context.connectionStatus === ConnectionStatusKind.connected) setShowInfoModal(true); + }, [context.connectionStatus]); + + if (context.showHelp) return ; + if ( context.connectionStatus === ConnectionStatusKind.disconnected || context.connectionStatus === ConnectionStatusKind.connecting ) { return ( { return ( setShowInfoModal(false)} status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} diff --git a/nym-connect/src/assets/help-step-four.png b/nym-connect/src/assets/help-step-four.png new file mode 100644 index 0000000000000000000000000000000000000000..55079628923c10eaecf9243a6b99f8124d84d916 GIT binary patch literal 8530 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 fPTkx*p?>VHb2K*s*> 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�t3F0PA2oun6An$m>7S{la;2+sY51dti~ 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 literal 0 HcmV?d00001 diff --git a/nym-connect/src/assets/help-step-two.png b/nym-connect/src/assets/help-step-two.png new file mode 100644 index 0000000000000000000000000000000000000000..f2910c98cf06b4d401e010be1e8ec43871171d3f GIT binary patch literal 35631 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ð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 ( - - - - {children} + + {children} ); diff --git a/nym-connect/src/components/ConnectionButton.tsx b/nym-connect/src/components/ConnectionButton.tsx index 4fd093a569..1a6499cf64 100644 --- a/nym-connect/src/components/ConnectionButton.tsx +++ b/nym-connect/src/components/ConnectionButton.tsx @@ -19,16 +19,16 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro switch (status) { case ConnectionStatusKind.disconnected: if (hover) { - return '#21D072'; + return '#FFFF33'; } - return '#60D6EF'; + return '#FFE600'; case ConnectionStatusKind.connecting: case ConnectionStatusKind.disconnecting: - return '#60D6EF'; + return '#FFE600'; default: // connected if (hover) { - return '#DA465B'; + return '#E43E3E'; } return '#21D072'; } @@ -81,69 +81,62 @@ export const ConnectionButton: React.FC<{ viewBox="0 0 208 208" fill="none" xmlns="http://www.w3.org/2000/svg" - onMouseEnter={() => !disabled && setHover(true)} - onMouseLeave={() => !disabled && setHover(false)} > - + !disabled && setHover(true)} + onMouseLeave={() => !disabled && setHover(false)} + > - - + + - - - - {busy && ( - - )} - + + + - + {status === ConnectionStatusKind.connected && hover ? ( ) : ( )} {statusText} - + - - + + - - + + diff --git a/nym-connect/src/components/ConnectionStatus.tsx b/nym-connect/src/components/ConnectionStatus.tsx index a866f8521d..c2bfcc82f0 100644 --- a/nym-connect/src/components/ConnectionStatus.tsx +++ b/nym-connect/src/components/ConnectionStatus.tsx @@ -1,12 +1,10 @@ import React from 'react'; import { Box, CircularProgress, Typography } from '@mui/material'; -import CheckCircleOutlineIcon from '@mui/icons-material/CheckCircleOutline'; -import CircleOutlinedIcon from '@mui/icons-material/CircleOutlined'; import { DateTime } from 'luxon'; import { ConnectionStatusKind } from '../types'; import { ServiceProvider } from '../types/directory'; -const FONT_SIZE = '16px'; +const FONT_SIZE = '10px'; const FONT_WEIGHT = '600'; const FONT_STYLE = 'normal'; @@ -16,39 +14,41 @@ const ConnectionStatusContent: React.FC<{ switch (status) { case ConnectionStatusKind.connected: return ( - <> - - - Connected - - + + Connected + ); case ConnectionStatusKind.disconnecting: return ( - <> + Disconnecting... - + ); case ConnectionStatusKind.connecting: return ( - <> + Connecting... - + ); case ConnectionStatusKind.disconnected: return ( - <> - - - Disconnected - - + + You are not protected + ); default: return null; @@ -61,29 +61,22 @@ export const ConnectionStatus: React.FC<{ serviceProvider?: ServiceProvider; }> = ({ status, connectedSince, serviceProvider }) => { const color = - status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting ? '#21D072' : '#888'; - const [duration, setDuration] = React.useState(); - React.useEffect(() => { - const intervalId = setInterval(() => { - if (connectedSince) { - setDuration(DateTime.now().diff(connectedSince).toFormat('hh:mm:ss')); - } - }, 500); - return () => { - clearInterval(intervalId); - }; - }, [status, connectedSince]); + status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting + ? '#21D072' + : 'warning.main'; + return ( <> - - - - - - {status === ConnectionStatusKind.connected && duration} - + + + + + {serviceProvider && ( + + To {serviceProvider.description} + + )} - {serviceProvider && {serviceProvider.description}} ); }; diff --git a/nym-connect/src/components/ConntectionTimer.tsx b/nym-connect/src/components/ConntectionTimer.tsx new file mode 100644 index 0000000000..25e2af8d9c --- /dev/null +++ b/nym-connect/src/components/ConntectionTimer.tsx @@ -0,0 +1,26 @@ +import React, { useEffect } from 'react'; +import { Stack, Typography } from '@mui/material'; +import { DateTime } from 'luxon'; + +export const ConnectionTimer = ({ connectedSince }: { connectedSince?: DateTime }) => { + const [duration, setDuration] = React.useState(); + useEffect(() => { + const intervalId = setInterval(() => { + if (connectedSince) { + setDuration(DateTime.now().diff(connectedSince).toFormat('hh:mm:ss')); + } + }, 500); + return () => { + clearInterval(intervalId); + }; + }, [connectedSince]); + + return ( + + + Connection time + + {duration || '00:00:00'} + + ); +}; diff --git a/nym-connect/src/components/CustomTitleBar.tsx b/nym-connect/src/components/CustomTitleBar.tsx new file mode 100644 index 0000000000..dc4e8c0061 --- /dev/null +++ b/nym-connect/src/components/CustomTitleBar.tsx @@ -0,0 +1,41 @@ +import React from 'react'; +import { ArrowBack, Close, HelpOutline } from '@mui/icons-material'; +import { Box, IconButton } from '@mui/material'; +import { NymWordmark } from '@nymproject/react/logo/NymWordmark'; +import { appWindow } from '@tauri-apps/api/window'; +import { useClientContext } from 'src/context/main'; + +const customTitleBarStyles = { + titlebar: { + background: '#1D2125', + display: 'flex', + justifyContent: 'space-between', + alignItems: 'center', + padding: '16px', + paddingBottom: '0px', + borderTopLeftRadius: '12px', + borderTopRightRadius: '12px', + }, +}; + +const CustomButton = ({ Icon, onClick }: { Icon: React.JSXElementConstructor; onClick: () => void }) => ( + + + +); + +export const CustomTitleBar = () => { + const { showHelp, handleShowHelp } = useClientContext(); + return ( + + { + handleShowHelp(); + }} + /> + + appWindow.close()} /> + + ); +}; diff --git a/nym-connect/src/components/HelpPage.tsx b/nym-connect/src/components/HelpPage.tsx new file mode 100644 index 0000000000..788f7264d5 --- /dev/null +++ b/nym-connect/src/components/HelpPage.tsx @@ -0,0 +1,33 @@ +import React from 'react'; +import { Stack, Typography } from '@mui/material'; +import { HelpPageActions } from './HelpPageActions'; +import { HelpImage } from './HelpPageImage'; +import { StepIndicator } from './HelpPageStepIndicator'; + +export const HelpPage = ({ + step, + description, + img, + onNext, + onPrev, +}: { + step: number; + description: string; + img: any; + onNext?: () => void; + onPrev?: () => void; +}) => ( + + + + + How to connect guide {step}/4 + + + {description} + + + + + +); diff --git a/nym-connect/src/components/HelpPageActions.tsx b/nym-connect/src/components/HelpPageActions.tsx new file mode 100644 index 0000000000..17e513120f --- /dev/null +++ b/nym-connect/src/components/HelpPageActions.tsx @@ -0,0 +1,20 @@ +import { ArrowBack, ArrowForward } from '@mui/icons-material'; +import { Box, Button } from '@mui/material'; +import React from 'react'; + +export const HelpPageActions = ({ onNext, onPrev }: { onNext?: () => void; onPrev?: () => void }) => ( + + {onPrev ? ( + + ) : ( +

+ )} + {onNext && ( + + )} + +); diff --git a/nym-connect/src/components/HelpPageImage.tsx b/nym-connect/src/components/HelpPageImage.tsx new file mode 100644 index 0000000000..27a8dcdca3 --- /dev/null +++ b/nym-connect/src/components/HelpPageImage.tsx @@ -0,0 +1,5 @@ +import React from 'react'; + +export const HelpImage = ({ img, imageDescription }: { img: any; imageDescription: string }) => ( + {imageDescription} +); diff --git a/nym-connect/src/components/HelpPageStepIndicator.tsx b/nym-connect/src/components/HelpPageStepIndicator.tsx new file mode 100644 index 0000000000..b5dff42b34 --- /dev/null +++ b/nym-connect/src/components/HelpPageStepIndicator.tsx @@ -0,0 +1,15 @@ +import { Box } from '@mui/material'; +import React from 'react'; + +const Step = ({ highlight }: { highlight: boolean }) => ( + +); + +export const StepIndicator = ({ step }: { step: number }) => ( + + + = 2} /> + = 3} /> + = 4} /> + +); diff --git a/nym-connect/src/components/InfoModal.tsx b/nym-connect/src/components/InfoModal.tsx new file mode 100644 index 0000000000..ed628de7eb --- /dev/null +++ b/nym-connect/src/components/InfoModal.tsx @@ -0,0 +1,69 @@ +import { Close, ErrorOutline } from '@mui/icons-material'; +import { Box, IconButton, Modal, Theme, Typography } from '@mui/material'; +import React from 'react'; + +const styles = { + position: 'absolute', + top: '50%', + left: '50%', + transform: 'translate(-50%, -50%)', + width: 200, + bgcolor: '#292E34', + p: 1.5, + borderRadius: 0.5, + height: 'fit-content', + border: (theme: Theme) => `1px solid ${theme.palette.grey[700]}`, +}; + +const ModalTitle = ({ title, withCloseIcon }: { title: string; withCloseIcon: boolean }) => ( + + + + {title} + + +); + +const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => ( + + {children} + + {description} + + +); + +export const InfoModal = ({ + title, + description, + show, + children, + Action, + onClose, +}: { + title: string; + description: string; + show: boolean; + children?: React.ReactElement; + Action?: React.ReactNode; + onClose?: () => void; +}) => ( + + + {onClose && ( + + + + + + )} + + {children} + {Action && ( + + {Action} + + )} + + +); diff --git a/nym-connect/src/components/IpAddressAndPort.tsx b/nym-connect/src/components/IpAddressAndPort.tsx index 435cf926fb..8d7d79c1f2 100644 --- a/nym-connect/src/components/IpAddressAndPort.tsx +++ b/nym-connect/src/components/IpAddressAndPort.tsx @@ -37,8 +37,12 @@ export const IpAddressAndPort: React.FC<{ return ( - {label} - Port + + {label} + + + Port + void; +}) => ( + Done} + > + + + Socks5 address + + + {ipAddress} + + + + + Port + + + {port} + + + + +); diff --git a/nym-connect/src/components/ServiceProviderSelector.tsx b/nym-connect/src/components/ServiceProviderSelector.tsx index b2fd29f42b..8eede5726a 100644 --- a/nym-connect/src/components/ServiceProviderSelector.tsx +++ b/nym-connect/src/components/ServiceProviderSelector.tsx @@ -1,9 +1,5 @@ import React, { useEffect, useMemo } from 'react'; -import IconButton from '@mui/material/IconButton'; -import Menu from '@mui/material/Menu'; -import MenuItem from '@mui/material/MenuItem'; -import ArrowDropDownCircleIcon from '@mui/icons-material/ArrowDropDownCircle'; -import { Box, CircularProgress, Stack, Tooltip, Typography } from '@mui/material'; +import { Box, CircularProgress, Input, Stack, TextField, Tooltip, Typography, MenuItem } from '@mui/material'; import { ServiceProvider, Service, Services } from '../types/directory'; type ServiceWithRandomSp = { @@ -17,11 +13,8 @@ export const ServiceProviderSelector: React.FC<{ services?: Services; currentSp?: ServiceProvider; }> = ({ services, currentSp, onChange }) => { - const [service, setService] = React.useState(); + const [service, setService] = React.useState({ id: '', description: '', items: [] }); const [serviceProvider, setServiceProvider] = React.useState(currentSp); - const textEl = React.useRef(null); - const [anchorEl, setAnchorEl] = React.useState(null); - const open = Boolean(anchorEl); useEffect(() => { if (!serviceProvider && currentSp) { @@ -32,38 +25,34 @@ export const ServiceProviderSelector: React.FC<{ useEffect(() => { if (services && serviceProvider) { // retrieve the service corresponding to this service provider - setService( - services.find((s) => - s.items.some( - ({ id, address, gateway }) => - id === serviceProvider.id && address === serviceProvider.address && gateway === serviceProvider.gateway, - ), + + const match = services.find((s) => + s.items.some( + ({ id, address, gateway }) => + id === serviceProvider.id && address === serviceProvider.address && gateway === serviceProvider.gateway, ), ); + + if (match) { + setService(match); + } } }, [serviceProvider, services]); - const handleClick = () => { - setAnchorEl(textEl.current); - }; - const handleClose = (newServiceProvider?: ServiceProvider) => { + const handleSelectSp = (newServiceProvider?: ServiceProvider) => { if (newServiceProvider && newServiceProvider !== currentSp) { setServiceProvider(newServiceProvider); onChange?.(newServiceProvider); } - setAnchorEl(null); }; if (!services) { return ( - + theme.palette.common.white}> Loading services... - - - ); } @@ -78,49 +67,44 @@ export const ServiceProviderSelector: React.FC<{ [services], ); + if (!service) return null; + return ( - <> - - (serviceProvider ? undefined : theme.palette.primary.main)} - > - {service ? service.description : 'Select a service'} - - - - - - handleClose()} - anchorOrigin={{ - vertical: 'bottom', - horizontal: 'right', - }} - transformOrigin={{ - vertical: 'top', - horizontal: 'left', - }} - MenuListProps={{ - 'aria-labelledby': 'service-provider-button', + + {servicesWithRandomSp.map(({ id, description, sp }) => ( - handleClose(sp)}> + handleSelectSp(sp)}> @@ -145,7 +129,7 @@ export const ServiceProviderSelector: React.FC<{ ))} - - + + ); }; diff --git a/nym-connect/src/context/main.tsx b/nym-connect/src/context/main.tsx index 42bfed638e..5d2b049bf9 100644 --- a/nym-connect/src/context/main.tsx +++ b/nym-connect/src/context/main.tsx @@ -7,20 +7,26 @@ import { forage } from '@tauri-apps/tauri-forage'; import { ConnectionStatusKind } from '../types'; import { ConnectionStatsItem } from '../components/ConnectionStats'; import { ServiceProvider, Services } from '../types/directory'; +import { Error } from 'src/types/error'; +import { TauriEvent } from 'src/types/event'; const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed'; type ModeType = 'light' | 'dark'; -type TClientContext = { +export type TClientContext = { mode: ModeType; connectionStatus: ConnectionStatusKind; connectionStats?: ConnectionStatsItem[]; connectedSince?: DateTime; services?: Services; serviceProvider?: ServiceProvider; + showHelp: boolean; + error?: Error; setMode: (mode: ModeType) => void; + clearError: () => void; + handleShowHelp: () => void; setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void; setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void; setConnectedSince: (connectedSince: DateTime | undefined) => void; @@ -39,6 +45,8 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const [connectedSince, setConnectedSince] = useState(); const [services, setServices] = React.useState([]); const [serviceProvider, setRawServiceProvider] = React.useState(); + const [showHelp, setShowHelp] = useState(false); + const [error, setError] = useState(); useEffect(() => { invoke('get_services').then((result) => { @@ -47,30 +55,45 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode }, []); useEffect(() => { - let unlisten: UnlistenFn | undefined; + const unlisten: UnlistenFn[] = []; // TODO: fix typings listen(TAURI_EVENT_STATUS_CHANGED, (event) => { const { status } = event.payload as any; console.log(TAURI_EVENT_STATUS_CHANGED, { status, event }); setConnectionStatus(status); + }) + .then((result) => { + unlisten.push(result); + }) + .catch((e) => console.log(e)); + + listen('socks5-event', (e: TauriEvent) => { + setError(e.payload); }).then((result) => { - unlisten = result; + unlisten.push(result); }); return () => { - if (unlisten) { - unlisten(); - } + unlisten.forEach((unsubscribe) => unsubscribe()); }; }, []); const startConnecting = useCallback(async () => { - await invoke('start_connecting'); + try { + await invoke('start_connecting'); + } catch (e) { + setError({ title: 'Could not connect', message: e as string }); + console.log(e); + } }, []); const startDisconnecting = useCallback(async () => { - await invoke('start_disconnecting'); + try { + await invoke('start_disconnecting'); + } catch (e) { + console.log(e); + } }, []); const setSpInStorage = async (sp: ServiceProvider) => { @@ -92,12 +115,17 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode const spFromStorage = await forage.getItem({ key: 'nym-connect-sp' })(); if (spFromStorage) { setRawServiceProvider(spFromStorage); + setServiceProvider(spFromStorage); } } catch (e) { console.warn(e); } }; + const handleShowHelp = () => setShowHelp((show) => !show); + + const clearError = () => setError(undefined); + useEffect(() => { const validityCheck = async () => { if (services.length > 0 && serviceProvider) { @@ -122,6 +150,8 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode () => ({ mode, setMode, + error, + clearError, connectionStatus, setConnectionStatus, connectionStats, @@ -133,8 +163,20 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode services, serviceProvider, setServiceProvider, + showHelp, + handleShowHelp, }), - [mode, connectedSince, connectionStatus, connectionStats, connectedSince, services, serviceProvider], + [ + mode, + error, + connectedSince, + showHelp, + connectionStatus, + connectionStats, + connectedSince, + services, + serviceProvider, + ], ); return {children}; diff --git a/nym-connect/src/context/mocks/main.tsx b/nym-connect/src/context/mocks/main.tsx new file mode 100644 index 0000000000..e2974413eb --- /dev/null +++ b/nym-connect/src/context/mocks/main.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { ConnectionStatusKind } from 'src/types'; +import { ClientContext, TClientContext } from '../main'; + +const mockValues: TClientContext = { + mode: 'dark', + connectionStatus: ConnectionStatusKind.disconnected, + services: [], + showHelp: false, + serviceProvider: { id: '1', description: 'Keybase service provider', gateway: 'abc123', address: '123abc' }, + setMode: () => {}, + clearError: () => {}, + handleShowHelp: () => {}, + setConnectedSince: () => {}, + setConnectionStats: () => {}, + setConnectionStatus: () => {}, + setServiceProvider: () => {}, + startConnecting: async () => {}, + startDisconnecting: async () => {}, +}; + +export const MockProvider = ({ children }: { children: React.ReactNode }) => { + return {children}; +}; diff --git a/nym-connect/src/index.tsx b/nym-connect/src/index.tsx index 2938840fff..66a3e11d17 100644 --- a/nym-connect/src/index.tsx +++ b/nym-connect/src/index.tsx @@ -4,16 +4,18 @@ import { ErrorBoundary } from 'react-error-boundary'; import { ClientContextProvider } from './context/main'; import { ErrorFallback } from './components/Error'; import { NymMixnetTheme } from './theme'; -import './fonts/fonts.css'; import { App } from './App'; +import { AppWindowFrame } from './components/AppWindowFrame'; const root = document.getElementById('root'); ReactDOM.render( - - + + + + , diff --git a/nym-connect/src/layouts/ConnectedLayout.tsx b/nym-connect/src/layouts/ConnectedLayout.tsx index a031f18a56..8e34415b39 100644 --- a/nym-connect/src/layouts/ConnectedLayout.tsx +++ b/nym-connect/src/layouts/ConnectedLayout.tsx @@ -1,11 +1,11 @@ import React from 'react'; -import { Box } from '@mui/material'; +import { Box, Divider } from '@mui/material'; import { DateTime } from 'luxon'; -import { AppWindowFrame } from '../components/AppWindowFrame'; +import { IpAddressAndPortModal } from 'src/components/IpAddressAndPortModal'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; import { ConnectionStatus } from '../components/ConnectionStatus'; import { ConnectionStatusKind } from '../types'; -import { ConnectionStats, ConnectionStatsItem } from '../components/ConnectionStats'; -import { NeedHelp } from '../components/NeedHelp'; +import { ConnectionStatsItem } from '../components/ConnectionStats'; import { ConnectionButton } from '../components/ConnectionButton'; import { IpAddressAndPort } from '../components/IpAddressAndPort'; import { ServiceProvider } from '../types/directory'; @@ -17,19 +17,32 @@ export const ConnectedLayout: React.FC<{ port: number; connectedSince?: DateTime; busy?: boolean; + showInfoModal: boolean; isError?: boolean; + handleCloseInfoModal: () => void; onConnectClick?: (status: ConnectionStatusKind) => void; serviceProvider?: ServiceProvider; -}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, serviceProvider, onConnectClick }) => ( - +}> = ({ + status, + showInfoModal, + handleCloseInfoModal, + ipAddress, + port, + connectedSince, + busy, + isError, + serviceProvider, + onConnectClick, +}) => ( + <> + - - - - + + + {/* */} + - - + ); diff --git a/nym-connect/src/layouts/DefaultLayout.tsx b/nym-connect/src/layouts/DefaultLayout.tsx index ba59b7f95d..ed42aad5fe 100644 --- a/nym-connect/src/layouts/DefaultLayout.tsx +++ b/nym-connect/src/layouts/DefaultLayout.tsx @@ -1,48 +1,48 @@ import React from 'react'; import { Typography } from '@mui/material'; -import { AppWindowFrame } from '../components/AppWindowFrame'; +import { Box } from '@mui/material'; +import { ConnectionStatus } from 'src/components/ConnectionStatus'; +import { ConnectionTimer } from 'src/components/ConntectionTimer'; +import { InfoModal } from 'src/components/InfoModal'; +import { Error } from 'src/types/error'; import { ConnectionButton } from '../components/ConnectionButton'; -import { ConnectionStatusKind } from '../types'; -import { NeedHelp } from '../components/NeedHelp'; import { ServiceProviderSelector } from '../components/ServiceProviderSelector'; -import { ServiceProvider, Services } from '../types/directory'; import { useClientContext } from '../context/main'; +import { ConnectionStatusKind } from '../types'; +import { ServiceProvider, Services } from '../types/directory'; export const DefaultLayout: React.FC<{ + error?: Error; status: ConnectionStatusKind; services?: Services; busy?: boolean; isError?: boolean; + clearError: () => void; onConnectClick?: (status: ConnectionStatusKind) => void; onServiceProviderChange?: (serviceProvider: ServiceProvider) => void; -}> = ({ status, services, busy, isError, onConnectClick, onServiceProviderChange }) => { - const [serviceProvider, setServiceProvider] = React.useState(); +}> = ({ status, error, services, busy, isError, onConnectClick, onServiceProviderChange, clearError }) => { const handleServiceProviderChange = (newServiceProvider: ServiceProvider) => { - setServiceProvider(newServiceProvider); onServiceProviderChange?.(newServiceProvider); }; + const { serviceProvider: currentSp } = useClientContext(); return ( - - - This is experimental software.
- Do not rely on it for strong anonymity (yet). -
- - Connect to the -
- Nym mixnet for privacy. + + {error && } + + + Connect to the Nym
mixnet for privacy.
+ - -
+
); }; diff --git a/nym-connect/src/layouts/HelpGuideLayout.tsx b/nym-connect/src/layouts/HelpGuideLayout.tsx new file mode 100644 index 0000000000..d56fd6b0ae --- /dev/null +++ b/nym-connect/src/layouts/HelpGuideLayout.tsx @@ -0,0 +1,70 @@ +import React, { useState } from 'react'; +import { HelpPage } from 'src/components/HelpPage'; +import { Button, Link, Stack } from '@mui/material'; +import Image1 from '../assets/help-step-one.png'; +import Image2 from '../assets/help-step-two.png'; +import Image3 from '../assets/help-step-three.png'; +import Image4 from '../assets/help-step-four.png'; + +export const HelpGuideLayout = () => { + const [step, setStep] = useState(0); + + if (step === 1) + return ( + setStep(2)} + /> + ); + + if (step === 2) + return ( + setStep(1)} + onNext={() => setStep(3)} + /> + ); + + if (step === 3) + return ( + setStep(2)} + onNext={() => setStep(4)} + /> + ); + + if (step === 4) + return ( + setStep(3)} + /> + ); + + return ( + + + + + ); +}; diff --git a/nym-connect/src/stories/AppFlow.stories.tsx b/nym-connect/src/stories/AppFlow.stories.tsx index ef7a1ea434..34064e6202 100644 --- a/nym-connect/src/stories/AppFlow.stories.tsx +++ b/nym-connect/src/stories/AppFlow.stories.tsx @@ -67,20 +67,25 @@ export const Mock: ComponentStory = () => { context.connectionStatus === ConnectionStatusKind.connecting ) { return ( - + {}} /> - + ); } return ( - + { + return undefined; + }} status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} @@ -101,6 +106,6 @@ export const Mock: ComponentStory = () => { }, ]} /> - + ); }; diff --git a/nym-connect/src/stories/ConnectedLayout.stories.tsx b/nym-connect/src/stories/ConnectedLayout.stories.tsx index 6063f83c28..53538457bf 100644 --- a/nym-connect/src/stories/ConnectedLayout.stories.tsx +++ b/nym-connect/src/stories/ConnectedLayout.stories.tsx @@ -11,8 +11,12 @@ export default { } as ComponentMeta; export const Default: ComponentStory = () => ( - + { + return undefined; + }} status={ConnectionStatusKind.connected} connectedSince={DateTime.now()} ipAddress="127.0.0.1" diff --git a/nym-connect/src/stories/DefaultLayout.stories.tsx b/nym-connect/src/stories/DefaultLayout.stories.tsx index 2c4ef513c2..ba311a410b 100644 --- a/nym-connect/src/stories/DefaultLayout.stories.tsx +++ b/nym-connect/src/stories/DefaultLayout.stories.tsx @@ -10,7 +10,24 @@ export default { } as ComponentMeta; export const Default: ComponentStory = () => ( - - + + {}} error={undefined} /> + +); + +export const WithServices: ComponentStory = () => ( + + {}} + error={undefined} + /> ); diff --git a/nym-connect/src/theme/index.tsx b/nym-connect/src/theme/index.tsx index 63f9db42fa..075aa7920a 100644 --- a/nym-connect/src/theme/index.tsx +++ b/nym-connect/src/theme/index.tsx @@ -3,12 +3,12 @@ import { createTheme, ThemeProvider } from '@mui/material/styles'; import { CssBaseline } from '@mui/material'; import { getDesignTokens } from './theme'; import { ClientContext } from '../context/main'; +import '../../../assets/fonts/non-variable/fonts.css'; /** - * Provides the theme for the Network Explorer by reacting to the light/dark mode choice stored in the app context. + * Provides the theme for Nym Connect by reacting to the light/dark mode choice stored in the app context. */ -export const NymMixnetTheme: React.FC = ({ children }) => { - const { mode } = useContext(ClientContext); +export const NymMixnetTheme: React.FC<{ mode: 'light' | 'dark' }> = ({ children, mode }) => { const theme = React.useMemo(() => createTheme(getDesignTokens(mode)), [mode]); return ( diff --git a/nym-connect/src/theme/mui-theme.d.ts b/nym-connect/src/theme/mui-theme.d.ts index a5b92073b3..ef042f6305 100644 --- a/nym-connect/src/theme/mui-theme.d.ts +++ b/nym-connect/src/theme/mui-theme.d.ts @@ -30,6 +30,7 @@ declare module '@mui/material/styles' { interface NymPalette { highlight: string; success: string; + warning: string; info: string; fee: string; background: { light: string; dark: string }; diff --git a/nym-connect/src/theme/theme.tsx b/nym-connect/src/theme/theme.tsx index 18064bf504..3c07506bf6 100644 --- a/nym-connect/src/theme/theme.tsx +++ b/nym-connect/src/theme/theme.tsx @@ -23,8 +23,9 @@ const nymPalette: NymPalette = { highlight: '#FB6E4E', success: '#21D073', info: '#60D7EF', + warning: '#FFE600', fee: '#967FF0', - background: { light: '#F4F6F8', dark: '#121726' }, + background: { light: '#F4F6F8', dark: '#1D2125' }, text: { light: '#F2F2F2', dark: '#121726', @@ -93,6 +94,9 @@ const variantToMUIPalette = (variant: NymPaletteVariant): PaletteOptions => ({ info: { main: nymPalette.info, }, + warning: { + main: nymPalette.warning, + }, background: { default: variant.background.main, paper: variant.background.paper, diff --git a/nym-connect/src/types/error.ts b/nym-connect/src/types/error.ts new file mode 100644 index 0000000000..599b959c39 --- /dev/null +++ b/nym-connect/src/types/error.ts @@ -0,0 +1,4 @@ +export type Error = { + title: string; + message: string; +}; diff --git a/nym-connect/src/types/event.ts b/nym-connect/src/types/event.ts new file mode 100644 index 0000000000..66d1e514a6 --- /dev/null +++ b/nym-connect/src/types/event.ts @@ -0,0 +1,6 @@ +export type TauriEvent = { + payload: { + title: string; + message: string; + }; +};