Nym Connect: fetch list of services from wellknown location and let the user choose
This commit is contained in:
committed by
Jon Häggblad
parent
ffff596d45
commit
6a01edf5fe
Generated
+1
@@ -3385,6 +3385,7 @@ dependencies = [
|
||||
"nym-socks5-client",
|
||||
"pretty_env_logger",
|
||||
"rand 0.7.3",
|
||||
"reqwest",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
|
||||
@@ -35,6 +35,7 @@ url = "2.2"
|
||||
log = "0.4"
|
||||
pretty_env_logger = "0.4.0"
|
||||
fix-path-env = { git = "https://github.com/tauri-apps/fix-path-env-rs", branch = "release"}
|
||||
reqwest = { version = "0.11", features = ["json"] }
|
||||
|
||||
client-core = { path = "../../clients/client-core" }
|
||||
config = { path = "../../common/config" }
|
||||
|
||||
@@ -14,6 +14,11 @@ pub enum BackendError {
|
||||
NoServiceProviderSet,
|
||||
#[error("No gateway provider set")]
|
||||
NoGatewaySet,
|
||||
#[error("{source}")]
|
||||
ReqwestError {
|
||||
#[from]
|
||||
source: reqwest::Error,
|
||||
},
|
||||
}
|
||||
|
||||
impl Serialize for BackendError {
|
||||
|
||||
@@ -42,6 +42,7 @@ fn main() {
|
||||
crate::operations::connection::connect::start_connecting,
|
||||
crate::operations::connection::disconnect::start_disconnecting,
|
||||
crate::operations::window::hide_window,
|
||||
crate::operations::directory::get_services,
|
||||
])
|
||||
.menu(Menu::new().add_default_app_submenu_if_macos())
|
||||
.system_tray(create_tray_menu())
|
||||
|
||||
@@ -30,3 +30,23 @@ pub const APP_EVENT_CONNECTION_STATUS_CHANGED: &str = "app:connection-status-cha
|
||||
pub struct AppEventConnectionStatusChangedPayload {
|
||||
pub status: ConnectionStatusKind,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DirectoryService {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
pub items: Vec<DirectoryServiceProvider>,
|
||||
}
|
||||
|
||||
#[cfg_attr(test, derive(ts_rs::TS))]
|
||||
#[derive(Clone, serde::Serialize, serde::Deserialize)]
|
||||
pub struct DirectoryServiceProvider {
|
||||
pub id: String,
|
||||
pub description: String,
|
||||
/// Address of the network requester in the form "<gateway_id>.<service_provider_id>"
|
||||
/// e.g. DpB3cHAchJiNBQi5FrZx2csXb1mrHkpYh9Wzf8Rjsuko.ANNWrvHqMYuertHGHUrZdBntQhpzfbWekB39qez9U2Vx@2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub address: String,
|
||||
/// Address of the gateway, e.g. 2BuMSfMW3zpeAjKXyKLhmY4QW1DXurrtSPEJ6CjX3SEh
|
||||
pub gateway: String,
|
||||
}
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
use crate::error::BackendError;
|
||||
use crate::models::DirectoryService;
|
||||
|
||||
static SERVICE_PROVIDER_WELLKNOWN_URL: &str =
|
||||
"https://nymtech.net/.wellknown/connect/service-providers.json";
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn get_services() -> Result<Vec<DirectoryService>, BackendError> {
|
||||
let res = reqwest::get(SERVICE_PROVIDER_WELLKNOWN_URL)
|
||||
.await?
|
||||
.json::<Vec<DirectoryService>>()
|
||||
.await?;
|
||||
Ok(res)
|
||||
}
|
||||
@@ -1,2 +1,3 @@
|
||||
pub mod connection;
|
||||
pub mod directory;
|
||||
pub mod window;
|
||||
|
||||
+11
-1
@@ -7,6 +7,7 @@ import { ConnectedLayout } from './layouts/ConnectedLayout';
|
||||
export const App: React.FC = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
|
||||
const handleConnectClick = React.useCallback(async () => {
|
||||
const oldStatus = context.connectionStatus;
|
||||
if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) {
|
||||
@@ -29,7 +30,15 @@ export const App: React.FC = () => {
|
||||
context.connectionStatus === ConnectionStatusKind.disconnected ||
|
||||
context.connectionStatus === ConnectionStatusKind.connecting
|
||||
) {
|
||||
return <DefaultLayout status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} />;
|
||||
return (
|
||||
<DefaultLayout
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={context.services}
|
||||
onServiceProviderChange={context.setServiceProvider}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
@@ -40,6 +49,7 @@ export const App: React.FC = () => {
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={context.serviceProvider}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import React from 'react';
|
||||
import { Box } from '@mui/material';
|
||||
import { invoke } from '@tauri-apps/api/tauri';
|
||||
|
||||
export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
<Box
|
||||
@@ -14,11 +13,10 @@ export const AppWindowFrame: React.FC = ({ children }) => (
|
||||
}}
|
||||
>
|
||||
<Box display="flex" justifyContent="space-between" alignItems="center">
|
||||
<svg width="22" height="6" viewBox="0 0 22 6" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path
|
||||
d="M6.86777 6H5.35495L1.22609 1.32517V6H0V0H1.54986L5.67872 4.67354V0H6.86777V6ZM20.4496 0L18.5658 2.13277L16.6821 0H15.1322V6H16.3578V1.32517L18.2959 3.52046C18.4457 3.68998 18.6865 3.68998 18.8363 3.52046L20.7745 1.32517V6H22V0H20.4496ZM10.4063 3.13181V6H11.6318V3.13181L14.4527 0H12.9028L11.018 2.13277L9.13421 0H7.58435L10.4063 3.13181Z"
|
||||
fill="#F2F2F2"
|
||||
/>
|
||||
<svg width="22" height="6" viewBox="0 0 210 56" fill="#F2F2F2" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M45.8829 0.142822H45.7169V0.28114V48.637L25.3289 0.225818L25.3012 0.142822H25.1905H13.6272H0.652966H0.514648V0.28114V55.7189V55.8572H0.652966H13.6272H13.7655V55.7189V7.28002L34.2365 55.7742L34.2642 55.8572H34.3748H45.8829H58.8294H58.9677V55.7189V0.28114V0.142822H58.8294H45.8829Z" />
|
||||
<path d="M209.347 0.142822H184.616H184.477L184.45 0.253483L171.78 48.8583L159.082 0.253483L159.054 0.142822H158.944H134.157H133.991V0.28114V55.7189V55.8572H134.157H147.104H147.242V55.7189V7.66731L159.774 55.7466L159.801 55.8572H159.94H183.564H183.675L183.703 55.7466L196.234 7.66731V55.7189V55.8572H196.373H209.347H209.485V55.7189V0.28114V0.142822H209.347Z" />
|
||||
<path d="M112.663 0.142822H112.58L112.552 0.198153L96.8116 27.5574L80.988 0.198153L80.9604 0.142822H80.8774H65.9114H65.6348L65.7731 0.364136L90.1447 42.5787V55.7189V55.8572H90.283H103.257H103.396V55.7189V42.5787L127.767 0.364136L127.905 0.142822H127.629H112.663Z" />
|
||||
</svg>
|
||||
</Box>
|
||||
{children}
|
||||
|
||||
@@ -3,17 +3,21 @@ import { ConnectionStatusKind } from '../types';
|
||||
|
||||
export const ConnectionButton: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
disabled?: boolean;
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, isError, onClick, busy }) => {
|
||||
}> = ({ status, disabled, isError, onClick, busy }) => {
|
||||
const [hover, setHover] = React.useState<boolean>(false);
|
||||
|
||||
const handleClick = React.useCallback(() => {
|
||||
if (disabled === true) {
|
||||
return;
|
||||
}
|
||||
if (onClick) {
|
||||
onClick(status);
|
||||
}
|
||||
}, [status]);
|
||||
}, [status, disabled]);
|
||||
|
||||
const statusText = getStatusText(status, hover);
|
||||
const statusTextColor = isError ? '#40475C' : '#FFF';
|
||||
@@ -21,16 +25,17 @@ export const ConnectionButton: React.FC<{
|
||||
|
||||
return (
|
||||
<svg
|
||||
opacity={disabled ? 0.75 : 1}
|
||||
width="208"
|
||||
height="208"
|
||||
viewBox="0 0 208 208"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
onMouseEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
onMouseEnter={() => !disabled && setHover(true)}
|
||||
onMouseLeave={() => !disabled && setHover(false)}
|
||||
>
|
||||
<g transform="translate(-46 -46)">
|
||||
<g onClick={handleClick} style={{ cursor: 'pointer' }}>
|
||||
<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>
|
||||
|
||||
@@ -4,6 +4,7 @@ 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_WEIGHT = '600';
|
||||
@@ -57,7 +58,8 @@ const ConnectionStatusContent: React.FC<{
|
||||
export const ConnectionStatus: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
connectedSince?: DateTime;
|
||||
}> = ({ status, connectedSince }) => {
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, connectedSince, serviceProvider }) => {
|
||||
const color =
|
||||
status === ConnectionStatusKind.connected || status === ConnectionStatusKind.disconnecting ? '#21D072' : '#888';
|
||||
const [duration, setDuration] = React.useState<string>();
|
||||
@@ -72,13 +74,16 @@ export const ConnectionStatus: React.FC<{
|
||||
};
|
||||
}, [status, connectedSince]);
|
||||
return (
|
||||
<Box display="flex" justifyContent="space-between">
|
||||
<Box color={color} fontSize={FONT_SIZE} display="flex" alignItems="center">
|
||||
<ConnectionStatusContent status={status} />
|
||||
<>
|
||||
<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>
|
||||
<Typography color={color} fontWeight={FONT_WEIGHT} fontStyle={FONT_STYLE}>
|
||||
{status === ConnectionStatusKind.connected && duration}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>{serviceProvider && <Typography fontSize={12}>{serviceProvider.description}</Typography>}</Box>
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,110 @@
|
||||
import React 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 { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
export const ServiceProviderSelector: React.FC<{
|
||||
onChange?: (serviceProvider: ServiceProvider) => void;
|
||||
services?: Services;
|
||||
}> = ({ services, onChange }) => {
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
|
||||
const textEl = React.useRef<null | HTMLElement>(null);
|
||||
const [anchorEl, setAnchorEl] = React.useState<null | HTMLElement>(null);
|
||||
const open = Boolean(anchorEl);
|
||||
const handleClick = () => {
|
||||
setAnchorEl(textEl.current);
|
||||
};
|
||||
const handleClose = (newServiceProvider?: ServiceProvider) => {
|
||||
if (newServiceProvider) {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onChange?.(newServiceProvider);
|
||||
}
|
||||
setAnchorEl(null);
|
||||
};
|
||||
|
||||
if (!services) {
|
||||
return (
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 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>
|
||||
<ArrowDropDownCircleIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<Box display="flex" alignItems="center" justifyContent="space-between" sx={{ mt: 3 }}>
|
||||
<Typography
|
||||
ref={textEl}
|
||||
fontSize={14}
|
||||
fontWeight={700}
|
||||
color={(theme) => (serviceProvider ? undefined : theme.palette.primary.main)}
|
||||
>
|
||||
{serviceProvider ? serviceProvider.description : 'Select a service'}
|
||||
</Typography>
|
||||
<IconButton
|
||||
id="service-provider-button"
|
||||
aria-controls={open ? 'basic-menu' : undefined}
|
||||
aria-haspopup="true"
|
||||
aria-expanded={open ? 'true' : undefined}
|
||||
onClick={handleClick}
|
||||
>
|
||||
<ArrowDropDownCircleIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Menu
|
||||
id="service-provider-menu"
|
||||
anchorEl={anchorEl}
|
||||
open={open}
|
||||
onClose={() => handleClose()}
|
||||
MenuListProps={{
|
||||
'aria-labelledby': 'service-provider-button',
|
||||
}}
|
||||
>
|
||||
{services.map((service) => (
|
||||
<>
|
||||
<MenuItem disabled dense sx={{ fontSize: 'small', fontWeight: 'bold', mb: -1 }}>
|
||||
{service.description}
|
||||
</MenuItem>
|
||||
{service.items.map((sp) => (
|
||||
<MenuItem dense sx={{ fontSize: 'small', ml: 2, height: 'auto' }} onClick={() => handleClose(sp)}>
|
||||
<Tooltip
|
||||
title={
|
||||
<Stack direction="column">
|
||||
<Typography fontSize="inherit">
|
||||
<code>{sp.id}</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit" fontWeight={700}>
|
||||
{sp.description}
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Gateway <code>{sp.gateway.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
<Typography fontSize="inherit">
|
||||
Provider <code>{sp.address.slice(0, 10)}...</code>
|
||||
</Typography>
|
||||
</Stack>
|
||||
}
|
||||
arrow
|
||||
placement="top"
|
||||
>
|
||||
<Typography fontSize="inherit" noWrap>
|
||||
{sp.description}
|
||||
</Typography>
|
||||
</Tooltip>
|
||||
</MenuItem>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</Menu>
|
||||
</>
|
||||
);
|
||||
};
|
||||
@@ -4,6 +4,7 @@ import { invoke } from '@tauri-apps/api/tauri';
|
||||
import { listen, UnlistenFn } from '@tauri-apps/api/event';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { ConnectionStatsItem } from '../components/ConnectionStats';
|
||||
import { ServiceProvider, Services } from '../types/directory';
|
||||
|
||||
const TAURI_EVENT_STATUS_CHANGED = 'app:connection-status-changed';
|
||||
|
||||
@@ -14,11 +15,14 @@ type TClientContext = {
|
||||
connectionStatus: ConnectionStatusKind;
|
||||
connectionStats?: ConnectionStatsItem[];
|
||||
connectedSince?: DateTime;
|
||||
services?: Services;
|
||||
serviceProvider?: ServiceProvider;
|
||||
|
||||
setMode: (mode: ModeType) => void;
|
||||
setConnectionStatus: (connectionStatus: ConnectionStatusKind) => void;
|
||||
setConnectionStats: (connectionStats: ConnectionStatsItem[] | undefined) => void;
|
||||
setConnectedSince: (connectedSince: DateTime | undefined) => void;
|
||||
setServiceProvider: (serviceProvider: ServiceProvider) => void;
|
||||
|
||||
startConnecting: () => Promise<void>;
|
||||
startDisconnecting: () => Promise<void>;
|
||||
@@ -31,6 +35,14 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
const [connectionStatus, setConnectionStatus] = useState<ConnectionStatusKind>(ConnectionStatusKind.disconnected);
|
||||
const [connectionStats, setConnectionStats] = useState<ConnectionStatsItem[]>();
|
||||
const [connectedSince, setConnectedSince] = useState<DateTime>();
|
||||
const [services, setServices] = React.useState<Services>();
|
||||
const [serviceProvider, setRawServiceProvider] = React.useState<ServiceProvider>();
|
||||
|
||||
useEffect(() => {
|
||||
invoke('get_services').then((result) => {
|
||||
setServices(result as Services);
|
||||
});
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
let unlisten: UnlistenFn | undefined;
|
||||
@@ -59,6 +71,12 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
await invoke('start_disconnecting');
|
||||
}, []);
|
||||
|
||||
const setServiceProvider = useCallback(async (newServiceProvider: ServiceProvider) => {
|
||||
await invoke('set_gateway', { gateway: newServiceProvider.gateway });
|
||||
await invoke('set_service_provider', { serviceProvider: newServiceProvider.address });
|
||||
setRawServiceProvider(newServiceProvider);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<ClientContext.Provider
|
||||
value={{
|
||||
@@ -72,6 +90,9 @@ export const ClientContextProvider = ({ children }: { children: React.ReactNode
|
||||
setConnectedSince,
|
||||
startConnecting,
|
||||
startDisconnecting,
|
||||
services,
|
||||
serviceProvider,
|
||||
setServiceProvider,
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
|
||||
@@ -8,6 +8,7 @@ import { ConnectionStats, ConnectionStatsItem } from '../components/ConnectionSt
|
||||
import { NeedHelp } from '../components/NeedHelp';
|
||||
import { ConnectionButton } from '../components/ConnectionButton';
|
||||
import { IpAddressAndPort } from '../components/IpAddressAndPort';
|
||||
import { ServiceProvider } from '../types/directory';
|
||||
|
||||
export const ConnectedLayout: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
@@ -18,15 +19,16 @@ export const ConnectedLayout: React.FC<{
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onConnectClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, onConnectClick }) => (
|
||||
serviceProvider?: ServiceProvider;
|
||||
}> = ({ status, stats, ipAddress, port, connectedSince, busy, isError, serviceProvider, onConnectClick }) => (
|
||||
<AppWindowFrame>
|
||||
<Box pb={4}>
|
||||
<ConnectionStatus status={status} connectedSince={connectedSince} />
|
||||
<ConnectionStatus status={status} connectedSince={connectedSince} serviceProvider={serviceProvider} />
|
||||
</Box>
|
||||
<Box pb={4}>
|
||||
<IpAddressAndPort label="SOCKS5 Proxy" ipAddress={ipAddress} port={port} />
|
||||
</Box>
|
||||
<ConnectionStats stats={stats} />
|
||||
{/* <ConnectionStats stats={stats} /> */}
|
||||
<ConnectionButton status={status} busy={busy} onClick={onConnectClick} isError={isError} />
|
||||
<NeedHelp />
|
||||
</AppWindowFrame>
|
||||
|
||||
@@ -4,21 +4,39 @@ import { AppWindowFrame } from '../components/AppWindowFrame';
|
||||
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';
|
||||
|
||||
export const DefaultLayout: React.FC<{
|
||||
status: ConnectionStatusKind;
|
||||
services?: Services;
|
||||
busy?: boolean;
|
||||
isError?: boolean;
|
||||
onConnectClick?: (status: ConnectionStatusKind) => void;
|
||||
}> = ({ status, busy, isError, onConnectClick }) => (
|
||||
<AppWindowFrame>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center">
|
||||
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
|
||||
</Typography>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
|
||||
You are not protected now
|
||||
</Typography>
|
||||
<ConnectionButton status={status} busy={busy} isError={isError} onClick={onConnectClick} />
|
||||
<NeedHelp />
|
||||
</AppWindowFrame>
|
||||
);
|
||||
onServiceProviderChange?: (serviceProvider: ServiceProvider) => void;
|
||||
}> = ({ status, services, busy, isError, onConnectClick, onServiceProviderChange }) => {
|
||||
const [serviceProvider, setServiceProvider] = React.useState<ServiceProvider | undefined>();
|
||||
const handleServiceProviderChange = (newServiceProvider: ServiceProvider) => {
|
||||
setServiceProvider(newServiceProvider);
|
||||
onServiceProviderChange?.(newServiceProvider);
|
||||
};
|
||||
return (
|
||||
<AppWindowFrame>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center">
|
||||
Connect, your privacy will be 100% protected thanks to the Nym Mixnet
|
||||
</Typography>
|
||||
<Typography fontWeight="700" fontSize="14px" textAlign="center" color="#60D6EF" pt={2}>
|
||||
You are not protected now
|
||||
</Typography>
|
||||
<ServiceProviderSelector services={services} onChange={handleServiceProviderChange} />
|
||||
<ConnectionButton
|
||||
status={status}
|
||||
disabled={serviceProvider === undefined}
|
||||
busy={busy}
|
||||
isError={isError}
|
||||
onClick={onConnectClick}
|
||||
/>
|
||||
<NeedHelp />
|
||||
</AppWindowFrame>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -7,6 +7,7 @@ import { useClientContext } from '../context/main';
|
||||
import { ConnectionStatusKind } from '../types';
|
||||
import { DefaultLayout } from '../layouts/DefaultLayout';
|
||||
import { ConnectedLayout } from '../layouts/ConnectedLayout';
|
||||
import { Services } from '../types/directory';
|
||||
|
||||
export default {
|
||||
title: 'App/Flow',
|
||||
@@ -16,6 +17,19 @@ export default {
|
||||
export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
const context = useClientContext();
|
||||
const [busy, setBusy] = React.useState<boolean>();
|
||||
const services: Services = [
|
||||
{
|
||||
id: 'keybase',
|
||||
description: 'Keybase',
|
||||
items: [
|
||||
{
|
||||
id: 'nym-keybase',
|
||||
description: 'Nym Keybase Service Provider',
|
||||
address: '1234.5678',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
const handleConnectClick = React.useCallback(() => {
|
||||
const oldStatus = context.connectionStatus;
|
||||
if (oldStatus === ConnectionStatusKind.connected || oldStatus === ConnectionStatusKind.disconnected) {
|
||||
@@ -53,7 +67,12 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
) {
|
||||
return (
|
||||
<Box p={4} sx={{ background: 'white' }}>
|
||||
<DefaultLayout status={context.connectionStatus} busy={busy} onConnectClick={handleConnectClick} />
|
||||
<DefaultLayout
|
||||
status={context.connectionStatus}
|
||||
busy={busy}
|
||||
onConnectClick={handleConnectClick}
|
||||
services={services}
|
||||
/>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -67,6 +86,7 @@ export const Mock: ComponentStory<typeof AppWindowFrame> = () => {
|
||||
ipAddress="127.0.0.1"
|
||||
port={1080}
|
||||
connectedSince={context.connectedSince}
|
||||
serviceProvider={services[0].items[0]}
|
||||
stats={[
|
||||
{
|
||||
label: 'in:',
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
export interface ServiceProvider {
|
||||
id: string;
|
||||
description: string;
|
||||
address: string;
|
||||
gateway: string;
|
||||
}
|
||||
|
||||
export interface Service {
|
||||
id: string;
|
||||
description: string;
|
||||
items: ServiceProvider[];
|
||||
}
|
||||
|
||||
export type Services = Service[];
|
||||
Reference in New Issue
Block a user