Merge remote-tracking branch 'origin/release/v1.1.1' into release/v1.1.2

This commit is contained in:
Jon Häggblad
2022-11-28 12:57:54 +01:00
40 changed files with 714 additions and 272 deletions
+16 -16
View File
@@ -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
+10 -10
View File
@@ -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 (
<Fonts>
<ClientContextProvider>
<NymMixnetTheme>
<MockProvider>
<NymMixnetTheme mode="dark">
<Story {...context} />
</NymMixnetTheme>
</ClientContextProvider>
</MockProvider>
</Fonts>
)
}
);
};
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$/,
},
},
}
};
+2 -2
View File
@@ -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
+1 -1
View File
@@ -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"] }
+2 -2
View File
@@ -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,
@@ -6,9 +6,11 @@ static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
#[tauri::command]
pub async fn get_services() -> Result<Vec<DirectoryService>> {
log::trace!("Fetching services");
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
.await?
.json::<Vec<DirectoryService>>()
.await?;
log::trace!("Received: {:#?}", res);
Ok(res)
}
+33 -2
View File
@@ -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();
}
}
+11 -12
View File
@@ -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": {
+19 -4
View File
@@ -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<boolean>();
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 <HelpGuideLayout />;
if (
context.connectionStatus === ConnectionStatusKind.disconnected ||
context.connectionStatus === ConnectionStatusKind.connecting
) {
return (
<DefaultLayout
error={context.error}
clearError={context.clearError}
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
@@ -43,6 +56,8 @@ export const App: React.FC = () => {
return (
<ConnectedLayout
showInfoModal={showInfoModal}
handleCloseInfoModal={() => setShowInfoModal(false)}
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
Binary file not shown.

After

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 35 KiB

@@ -1,6 +1,6 @@
import React from 'react';
import { Box } from '@mui/material';
import { NymWordmark } from '@nymproject/react/logo/NymWordmark';
import { CustomTitleBar } from './CustomTitleBar';
export const AppWindowFrame: React.FC = ({ children }) => (
<Box
@@ -9,13 +9,12 @@ export const AppWindowFrame: React.FC = ({ children }) => (
borderRadius: '12px',
padding: '12px 16px',
display: 'grid',
gridTemplateRows: '30px auto',
width: '240px',
gridTemplateRows: '40px 1fr',
bgcolor: 'nym.background.dark',
height: '100vh',
}}
>
<Box display="flex" justifyContent="space-between" alignItems="center">
<NymWordmark width={22} />
</Box>
{children}
<CustomTitleBar />
<Box style={{ padding: '16px' }}>{children}</Box>
</Box>
);
+33 -40
View File
@@ -19,16 +19,16 @@ const getStatusFillColor = (status: ConnectionStatusKind, hover: boolean, isErro
switch (status) {
case ConnectionStatusKind.disconnected:
if (hover) {
return '#21D072';
return '#FFFF33';
}
return '#F4B02D';
return '#FFE600';
case ConnectionStatusKind.connecting:
case ConnectionStatusKind.disconnecting:
return '#F4B02D';
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)}
>
<g transform="translate(-46 -46)">
<g
transform="translate(-27, -27)"
onMouseEnter={() => !disabled && setHover(true)}
onMouseLeave={() => !disabled && setHover(false)}
>
<g onClick={handleClick} style={{ cursor: disabled ? 'not-allowed' : 'pointer' }}>
<g filter="url(#filter0_f_2_303)">
<circle cx="150" cy="150" r="70" fill="#3B445F" />
<g filter="url(#filter0_f_639_9730)">
<circle cx="131" cy="131" r="51" fill="url(#paint0_radial_639_9730)" />
</g>
<g filter="url(#filter1_d_2_303)">
<circle cx="150" cy="150" r="65" fill="url(#paint0_radial_2_303)" />
<circle cx="150" cy="150" r="61.5" stroke={statusFillColor} strokeWidth="7">
{busy && (
<animate
attributeName="stroke"
values={`${statusFillColor};${getBusyFillColor(statusFillColor)};${statusFillColor}`}
dur="1.5s"
repeatCount="indefinite"
/>
)}
</circle>
<circle cx="131" cy="131" r="65" fill="#1C1B1F" />
<g filter="url(#filter1_d_639_9730)">
<circle cx="131" cy="131" r="64" stroke={statusFillColor} strokeWidth="2" />
</g>
<circle cx="150" cy="150" r="73.5" stroke="white" strokeOpacity="0.2" />
<circle cx="131" cy="131" r="73.5" stroke={statusFillColor} strokeOpacity="0.5" />
{status === ConnectionStatusKind.connected && hover ? (
<path
d="M136.264 135.833C136.264 133.838 137.92 132.217 139.957 132.217H144.723V130H139.957C136.669 130 134 132.613 134 135.833C134 139.053 136.669 141.667 139.957 141.667H144.723V139.45H139.957C137.92 139.45 136.264 137.828 136.264 135.833ZM145.234 137H154.766V134.667H145.234V137ZM160.043 130H155.277V132.217H160.043C162.08 132.217 163.736 133.838 163.736 135.833C163.736 137.828 162.08 139.45 160.043 139.45H155.277V141.667H160.043C163.331 141.667 166 139.053 166 135.833C166 132.613 163.331 130 160.043 130Z"
d="M120.217 119.833C120.217 117.838 121.838 116.217 123.833 116.217H128.5V114H123.833C120.613 114 118 116.613 118 119.833C118 123.053 120.613 125.667 123.833 125.667H128.5V123.45H123.833C121.838 123.45 120.217 121.828 120.217 119.833ZM127 121H136.333V118.667H127V121ZM139.5 114H134.833V116.217H139.505C141.5 116.217 143.117 117.838 143.117 119.833C143.117 121.828 141.495 123.45 139.5 123.45H134.833V125.667H139.5C142.72 125.667 145.333 123.053 145.333 119.833C145.333 116.613 142.72 114 139.5 114Z"
fill="white"
/>
) : (
<path
d="M140.217 135.833C140.217 133.838 141.838 132.217 143.833 132.217H148.5V130H143.833C140.613 130 138 132.613 138 135.833C138 139.053 140.613 141.667 143.833 141.667H148.5V139.45H143.833C141.838 139.45 140.217 137.828 140.217 135.833ZM145 137H154.333V134.667H145V137ZM155.5 130H150.833V132.217H155.505C157.5 132.217 159.117 133.838 159.117 135.833C159.117 137.828 157.495 139.45 155.5 139.45H150.833V141.667H155.5C158.72 141.667 161.333 139.053 161.333 135.833C161.333 132.613 158.72 130 155.5 130Z"
d="M122.217 119.833C122.217 117.838 123.838 116.217 125.833 116.217H130.5V114H125.833C122.613 114 120 116.613 120 119.833C120 123.053 122.613 125.667 125.833 125.667H130.5V123.45H125.833C123.838 123.45 122.217 121.828 122.217 119.833ZM127 121H136.333V118.667H127V121ZM137.5 114H132.833V116.217H137.505C139.5 116.217 141.117 117.838 141.117 119.833C141.117 121.828 139.495 123.45 137.5 123.45H132.833V125.667H137.5C140.72 125.667 143.333 123.053 143.333 119.833C143.333 116.613 140.72 114 137.5 114Z"
fill="white"
/>
)}
<text
className="button_text"
x={150}
y={160}
x={131}
y={146}
fill={statusTextColor}
dominantBaseline="middle"
textAnchor="middle"
fontWeight="700"
fontSize="14px"
fontSize="16px"
>
{statusText}
</text>
<defs>
<filter
id="filter0_f_2_303"
id="filter0_f_639_9730"
x="0"
y="0"
width="300"
height="300"
width="262"
height="262"
filterUnits="userSpaceOnUse"
colorInterpolationFilters="sRGB"
>
<feFlood floodOpacity="0" result="BackgroundImageFix" />
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape" />
<feGaussianBlur stdDeviation="20" result="effect1_foregroundBlur_2_303" />
<feGaussianBlur stdDeviation="25" result="effect1_foregroundBlur_639_9730" />
</filter>
<filter
id="filter1_d_2_303"
x="70"
y="76"
id="filter1_d_639_9730"
x="51"
y="57"
width="160"
height="160"
filterUnits="userSpaceOnUse"
@@ -160,19 +153,19 @@ export const ConnectionButton: React.FC<{
<feGaussianBlur stdDeviation="7.5" />
<feComposite in2="hardAlpha" operator="out" />
<feColorMatrix type="matrix" values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.3 0" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_2_303" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_2_303" result="shape" />
<feBlend mode="normal" in2="BackgroundImageFix" result="effect1_dropShadow_639_9730" />
<feBlend mode="normal" in="SourceGraphic" in2="effect1_dropShadow_639_9730" result="shape" />
</filter>
<radialGradient
id="paint0_radial_2_303"
id="paint0_radial_639_9730"
cx="0"
cy="0"
r="1"
gradientUnits="userSpaceOnUse"
gradientTransform="translate(150 150) rotate(90) scale(65)"
gradientTransform="translate(131 131) rotate(90) scale(51)"
>
<stop stopColor="#283046" />
<stop offset="1" stopColor="#121727" />
<stop stopColor="#1C1C1F" />
<stop offset="1" stopColor={statusFillColor} />
</radialGradient>
</defs>
</g>
+32 -39
View File
@@ -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 (
<>
<CheckCircleOutlineIcon />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} ml={1}>
Connected
</Typography>
</>
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} textAlign="center">
Connected
</Typography>
);
case ConnectionStatusKind.disconnecting:
return (
<>
<Box display="flex" alignItems="center" justifyContent="center">
<CircularProgress size={FONT_SIZE} color="inherit" />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} ml={1}>
Disconnecting...
</Typography>
</>
</Box>
);
case ConnectionStatusKind.connecting:
return (
<>
<Box display="flex" alignItems="center" justifyContent="center">
<CircularProgress size={FONT_SIZE} color="inherit" />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} ml={1}>
Connecting...
</Typography>
</>
</Box>
);
case ConnectionStatusKind.disconnected:
return (
<>
<CircleOutlinedIcon />
<Typography fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE} ml={1}>
Disconnected
</Typography>
</>
<Typography
fontWeight={FONT_WEIGHT}
fontStyle={FONT_STYLE}
ml={1}
textTransform="uppercase"
textAlign="center"
fontSize="10px"
sx={{ wordSpacing: 3, letterSpacing: 2 }}
>
You are not protected
</Typography>
);
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<string>();
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 (
<>
<Box display="flex" justifyContent="space-between">
<Box color={color} fontSize={FONT_SIZE} display="flex" alignItems="center">
<ConnectionStatusContent status={status} />
</Box>
<Typography color={color} fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
{status === ConnectionStatusKind.connected && duration}
</Typography>
<Box color={color} fontSize={FONT_SIZE} sx={{ mb: 1 }}>
<ConnectionStatusContent status={status} />
</Box>
<Box>
{serviceProvider && (
<Typography fontSize={12} textAlign="center">
To {serviceProvider.description}
</Typography>
)}
</Box>
<Box>{serviceProvider && <Typography fontSize={12}>{serviceProvider.description}</Typography>}</Box>
</>
);
};
@@ -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<string>();
useEffect(() => {
const intervalId = setInterval(() => {
if (connectedSince) {
setDuration(DateTime.now().diff(connectedSince).toFormat('hh:mm:ss'));
}
}, 500);
return () => {
clearInterval(intervalId);
};
}, [connectedSince]);
return (
<Stack alignItems="center">
<Typography variant="caption" sx={{ color: 'grey.600' }}>
Connection time
</Typography>
<Typography letterSpacing="0.15em">{duration || '00:00:00'}</Typography>
</Stack>
);
};
@@ -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<any>; onClick: () => void }) => (
<IconButton size="small" style={{ padding: 0 }} onClick={onClick}>
<Icon style={{ fontSize: 16 }} />
</IconButton>
);
export const CustomTitleBar = () => {
const { showHelp, handleShowHelp } = useClientContext();
return (
<Box data-tauri-drag-region style={customTitleBarStyles.titlebar}>
<CustomButton
Icon={!showHelp ? HelpOutline : ArrowBack}
onClick={() => {
handleShowHelp();
}}
/>
<NymWordmark width={36} />
<CustomButton Icon={Close} onClick={() => appWindow.close()} />
</Box>
);
};
+33
View File
@@ -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;
}) => (
<Stack justifyContent="space-between" sx={{ height: '100%' }}>
<Stack gap={3}>
<StepIndicator step={step} />
<Typography variant="body2" color="white" fontWeight="bold">
How to connect guide {step}/4
</Typography>
<Typography variant="body2" sx={{ color: 'grey.400' }}>
{description}
</Typography>
<HelpImage img={img} imageDescription="select a provider" />
</Stack>
<HelpPageActions onNext={onNext} onPrev={onPrev} />
</Stack>
);
@@ -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 }) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{onPrev ? (
<Button onClick={onPrev} color="inherit" startIcon={<ArrowBack color="inherit" style={{ fontSize: 16 }} />}>
Back
</Button>
) : (
<div />
)}
{onNext && (
<Button onClick={onNext} endIcon={<ArrowForward style={{ fontSize: 16 }} />}>
Next
</Button>
)}
</Box>
);
@@ -0,0 +1,5 @@
import React from 'react';
export const HelpImage = ({ img, imageDescription }: { img: any; imageDescription: string }) => (
<img src={img} alt={imageDescription} />
);
@@ -0,0 +1,15 @@
import { Box } from '@mui/material';
import React from 'react';
const Step = ({ highlight }: { highlight: boolean }) => (
<Box sx={{ width: '48px', height: '1px', bgcolor: highlight ? 'nym.highlight' : 'grey.600' }} />
);
export const StepIndicator = ({ step }: { step: number }) => (
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-evenly' }}>
<Step highlight />
<Step highlight={step >= 2} />
<Step highlight={step >= 3} />
<Step highlight={step >= 4} />
</Box>
);
+69
View File
@@ -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 }) => (
<Box textAlign="center" mt={withCloseIcon ? -2 : 0}>
<ErrorOutline sx={{ color: 'warning.main' }} />
<Typography variant="body2" textAlign="center" sx={{ color: 'warning.main' }}>
{title}
</Typography>
</Box>
);
const ModalBody = ({ description, children }: { description: string; children?: React.ReactElement }) => (
<Box textAlign="center" mt={1}>
{children}
<Typography fontSize="small" sx={{ mt: 1, overflowWrap: 'anywhere', color: 'grey.300' }}>
{description}
</Typography>
</Box>
);
export const InfoModal = ({
title,
description,
show,
children,
Action,
onClose,
}: {
title: string;
description: string;
show: boolean;
children?: React.ReactElement;
Action?: React.ReactNode;
onClose?: () => void;
}) => (
<Modal open={show} onClose={onClose}>
<Box sx={styles}>
{onClose && (
<Box display="flex" justifyContent="flex-end">
<IconButton size="small" onClick={onClose} sx={{ padding: 0 }}>
<Close sx={{ fontSize: 14 }} />
</IconButton>
</Box>
)}
<ModalTitle title={title} withCloseIcon={Boolean(onClose)} />
<ModalBody description={description}>{children}</ModalBody>
{Action && (
<Box mt={1} textAlign="center">
{Action}
</Box>
)}
</Box>
</Modal>
);
@@ -37,8 +37,12 @@ export const IpAddressAndPort: React.FC<{
return (
<IpAddressAndPortContainer>
<Box display="flex" justifyContent="space-between" color="rgba(255,255,255,0.6)">
<Typography fontSize="14px">{label}</Typography>
<Typography fontSize="14px">Port</Typography>
<Typography fontSize="14px" sx={{ color: 'grey.600' }}>
{label}
</Typography>
<Typography fontSize="14px" sx={{ color: 'grey.600' }}>
Port
</Typography>
</Box>
<Box display="flex" justifyContent="space-between">
<Tooltip
@@ -0,0 +1,41 @@
import React from 'react';
import { Box, Button, Typography } from '@mui/material';
import { InfoModal } from './InfoModal';
import { CopyToClipboard } from './CopyToClipboard';
export const IpAddressAndPortModal = ({
show,
ipAddress,
port,
onClose,
}: {
show: boolean;
ipAddress: string;
port: number;
onClose: () => void;
}) => (
<InfoModal
show={show}
title="Almost there"
description="Copy these values to the proxy settings in your application"
Action={<Button onClick={onClose}>Done</Button>}
>
<Box sx={{ mt: 1 }}>
<Typography fontSize="14px" sx={{ color: 'grey.600' }}>
Socks5 address
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Typography>{ipAddress}</Typography>
<CopyToClipboard text={ipAddress} iconButton light />
</Box>
<Typography fontSize="14px" sx={{ color: 'grey.600', mt: 2 }}>
Port
</Typography>
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Typography>{port}</Typography>
<CopyToClipboard text={port.toString()} iconButton light />
</Box>
</Box>
</InfoModal>
);
@@ -1,11 +1,6 @@
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 KeyboardArrowDownRoundedIcon from '@mui/icons-material/KeyboardArrowDownRounded';
import KeyboardArrowUpRoundedIcon from '@mui/icons-material/KeyboardArrowUpRounded';
import { Box, CircularProgress, Stack, Tooltip, Typography, ListItemIcon } from '@mui/material';
import Check from '@mui/icons-material/Check';
import { Box, CircularProgress, Input, Stack, TextField, Tooltip, Typography, MenuItem, ListItemIcon } from '@mui/material';
import { Check } from '@mui/icons-material';
import { ServiceProvider, Service, Services } from '../types/directory';
type ServiceWithRandomSp = {
@@ -19,11 +14,8 @@ export const ServiceProviderSelector: React.FC<{
services?: Services;
currentSp?: ServiceProvider;
}> = ({ services, currentSp, onChange }) => {
const [service, setService] = React.useState<Service>();
const [service, setService] = React.useState<Service>({ id: '', description: '', items: [] });
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>(currentSp);
const textEl = React.useRef<null | HTMLElement>(null);
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
const open = Boolean(anchorEl);
useEffect(() => {
if (!serviceProvider && currentSp) {
@@ -34,38 +26,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 (
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
<Box display="flex" alignItems="center" justifyContent="center" sx={{ my: 3 }}>
<Typography fontSize={14} fontWeight={700} color={(theme) => theme.palette.common.white}>
<CircularProgress size={14} sx={{ mr: 1 }} color="inherit" />
Loading services...
</Typography>
<IconButton id="service-provider-button" disabled>
{open ? <KeyboardArrowUpRoundedIcon /> : <KeyboardArrowDownRoundedIcon />}
</IconButton>
</Box>
);
}
@@ -80,68 +68,44 @@ export const ServiceProviderSelector: React.FC<{
[services],
);
if (!service) return null;
return (
<>
<Box
display="flex"
alignItems="center"
justifyContent="space-between"
sx={{ mt: 3, borderBottom: (theme) => `1px solid ${theme.palette.info.main}` }}
>
<Typography ref={textEl} fontSize={14} fontWeight={700} color={(theme) => theme.palette.info.main}>
{!service ? 'Select a service' : service.description}
</Typography>
<IconButton
id="service-provider-button"
aria-controls={open ? 'basic-menu' : undefined}
aria-haspopup="true"
aria-expanded={open ? 'true' : undefined}
onClick={handleClick}
color="info"
size="small"
sx={{ padding: 0 }}
>
{open ? <KeyboardArrowUpRoundedIcon /> : <KeyboardArrowDownRoundedIcon />}
</IconButton>
</Box>
<Menu
id="service-provider-menu"
anchorEl={anchorEl}
open={open}
onClose={() => handleClose()}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'right',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
PaperProps={{
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ my: 3 }}>
<TextField
variant="filled"
select
fullWidth
value={service.description}
label="Select a service"
InputLabelProps={{
sx: {
border: '1px solid rgba(96, 214, 239, 0.4)',
color: 'grey.500',
'&.Mui-focused': {
color: 'grey.500',
},
},
}}
MenuListProps={{
'aria-labelledby': 'service-provider-button',
sx: {
minWidth: 160,
SelectProps={{
MenuProps: {
PaperProps: {
sx: {
background: '#383C41',
borderTopLeftRadius: 0,
borderTopRightRadius: 0,
'&& .Mui-selected': {
backgroundColor: '#FFFFFF33',
},
'&& .Mui-focusVisible': {
backgroundColor: '#FFFFFF33',
},
},
},
},
}}
>
{servicesWithRandomSp.map(({ id, description, sp }) => (
<MenuItem
dense
autoFocus={id === service?.id}
key={id}
sx={{
fontSize: 'small',
fontWeight: 'bold',
minWidth: '208px',
'&.Mui-focusVisible': { bgcolor: 'transparent' },
}}
onClick={() => handleClose(sp)}
>
<MenuItem key={id} value={description} onClick={() => handleSelectSp(sp)}>
<Tooltip
title={
<Stack direction="column">
@@ -176,7 +140,7 @@ export const ServiceProviderSelector: React.FC<{
)}
</MenuItem>
))}
</Menu>
</>
</TextField>
</Box>
);
};
+51 -9
View File
@@ -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<DateTime>();
const [services, setServices] = React.useState<Services>([]);
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
const [showHelp, setShowHelp] = useState(false);
const [error, setError] = useState<Error>();
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 <ClientContext.Provider value={contextValue}>{children}</ClientContext.Provider>;
+24
View File
@@ -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 <ClientContext.Provider value={mockValues}>{children}</ClientContext.Provider>;
};
+5 -3
View File
@@ -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(
<ErrorBoundary FallbackComponent={ErrorFallback}>
<ClientContextProvider>
<NymMixnetTheme>
<App />
<NymMixnetTheme mode="dark">
<AppWindowFrame>
<App />
</AppWindowFrame>
</NymMixnetTheme>
</ClientContextProvider>
</ErrorBoundary>,
+25 -12
View File
@@ -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 }) => (
<AppWindowFrame>
}> = ({
status,
showInfoModal,
handleCloseInfoModal,
ipAddress,
port,
connectedSince,
busy,
isError,
serviceProvider,
onConnectClick,
}) => (
<>
<IpAddressAndPortModal show={showInfoModal} onClose={handleCloseInfoModal} ipAddress={ipAddress} port={port} />
<Box pb={4}>
<ConnectionStatus status={status} connectedSince={connectedSince} serviceProvider={serviceProvider} />
</Box>
<Box pb={4}>
<IpAddressAndPort label="SOCKS5 Proxy" ipAddress={ipAddress} port={port} />
<ConnectionStatus status={ConnectionStatusKind.connected} serviceProvider={serviceProvider} />
</Box>
<IpAddressAndPort label="Socks5 address" ipAddress={ipAddress} port={port} />
<Divider sx={{ my: 3 }} />
{/* <ConnectionStats stats={stats} /> */}
<ConnectionTimer connectedSince={connectedSince} />
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
<NeedHelp />
</AppWindowFrame>
</>
);
+19 -19
View File
@@ -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<ServiceProvider | undefined>();
}> = ({ status, error, services, busy, isError, onConnectClick, onServiceProviderChange, clearError }) => {
const handleServiceProviderChange = (newServiceProvider: ServiceProvider) => {
setServiceProvider(newServiceProvider);
onServiceProviderChange?.(newServiceProvider);
};
const { serviceProvider: currentSp } = useClientContext();
return (
<AppWindowFrame>
<Typography fontWeight="400" fontSize="12px" textAlign="center" sx={{ opacity: 0.6 }}>
This is experimental software. <br />
Do not rely on it for strong anonymity (yet).
</Typography>
<Typography fontWeight="700" fontSize="14px" textAlign="center" pt={2}>
Connect to the
<br />
Nym mixnet for privacy.
<Box pt={1}>
{error && <InfoModal show title={error.title} description={error.message} onClose={clearError} />}
<ConnectionStatus status={ConnectionStatusKind.disconnected} />
<Typography fontWeight="400" fontSize="16px" textAlign="center" pt={2}>
Connect to the Nym <br /> mixnet for privacy.
</Typography>
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} currentSp={currentSp} />
<ConnectionTimer />
<ConnectionButton
status={status}
disabled={serviceProvider === undefined && currentSp === undefined}
disabled={currentSp === undefined}
busy={busy}
isError={isError}
onClick={onConnectClick}
/>
<NeedHelp />
</AppWindowFrame>
</Box>
);
};
@@ -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 (
<HelpPage
step={step}
description="Select your service provider from the dropdown menu."
img={Image1}
onNext={() => setStep(2)}
/>
);
if (step === 2)
return (
<HelpPage
step={step}
description="Click yellow button and connect to a service provider."
img={Image2}
onPrev={() => setStep(1)}
onNext={() => setStep(3)}
/>
);
if (step === 3)
return (
<HelpPage
step={step}
description="Click on IP and Port to copy their values to the clipboard."
img={Image3}
onPrev={() => setStep(2)}
onNext={() => setStep(4)}
/>
);
if (step === 4)
return (
<HelpPage
step={step}
description="Go to the settings of your selected app, under Proxy select run via SOCKS5 proxy then paste the IP and Port values given by NymConnect."
img={Image4}
onPrev={() => setStep(3)}
/>
);
return (
<Stack gap={1}>
<Button variant="text" color="inherit" onClick={() => setStep(1)}>
How to connect guide
</Button>
<Button
LinkComponent={Link}
variant="text"
color="inherit"
href="https://nymtech.net/docs/stable/quickstart/nym-connect/"
target="_blank"
>
Docs
</Button>
</Stack>
);
};
+9 -4
View File
@@ -67,20 +67,25 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
context.connectionStatus === ConnectionStatusKind.connecting
) {
return (
<Box p={4} sx={{ background: 'white' }}>
<AppWindowFrame>
<DefaultLayout
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
services={services}
clearError={() => {}}
/>
</Box>
</AppWindowFrame>
);
}
return (
<Box p={4} sx={{ background: 'white' }}>
<AppWindowFrame>
<ConnectedLayout
showInfoModal={false}
handleCloseInfoModal={() => {
return undefined;
}}
status={context.connectionStatus}
busy={busy}
onConnectClick={handleConnectClick}
@@ -101,6 +106,6 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
},
]}
/>
</Box>
</AppWindowFrame>
);
};
@@ -11,8 +11,12 @@ export default {
} as ComponentMeta<typeof ConnectedLayout>;
export const Default: ComponentStory<typeof ConnectedLayout> = () => (
<Box p={4} sx={{ background: 'white' }}>
<Box p={2} width={242} sx={{ bgcolor: 'nym.background.dark' }}>
<ConnectedLayout
showInfoModal={false}
handleCloseInfoModal={() => {
return undefined;
}}
status={ConnectionStatusKind.connected}
connectedSince={DateTime.now()}
ipAddress="127.0.0.1"
@@ -10,7 +10,24 @@ export default {
} as ComponentMeta<typeof DefaultLayout>;
export const Default: ComponentStory<typeof DefaultLayout> = () => (
<Box p={4} sx={{ background: 'white' }}>
<DefaultLayout status={ConnectionStatusKind.disconnected} />
<Box p={1} width={230} sx={{ bgcolor: 'nym.background.dark' }}>
<DefaultLayout status={ConnectionStatusKind.disconnected} clearError={() => {}} error={undefined} />
</Box>
);
export const WithServices: ComponentStory<typeof DefaultLayout> = () => (
<Box p={1} width={230} sx={{ bgcolor: 'nym.background.dark' }}>
<DefaultLayout
status={ConnectionStatusKind.disconnected}
services={[
{
id: '1',
description: 'Keybase service',
items: [{ id: '1', description: 'Keybase service 1', gateway: 'abc123', address: '123abc' }],
},
]}
clearError={() => {}}
error={undefined}
/>
</Box>
);
+3 -3
View File
@@ -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 (
<ThemeProvider theme={theme}>
+1
View File
@@ -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 };
+4
View File
@@ -23,6 +23,7 @@ const nymPalette: NymPalette = {
highlight: '#FB6E4E',
success: '#21D073',
info: '#60D7EF',
warning: '#FFE600',
fee: '#967FF0',
background: { light: '#F4F6F8', dark: '#1D2125' },
text: {
@@ -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,
+4
View File
@@ -0,0 +1,4 @@
export type Error = {
title: string;
message: string;
};
+6
View File
@@ -0,0 +1,6 @@
export type TauriEvent = {
payload: {
title: string;
message: string;
};
};