Skip to content

Commit

Permalink
feat: better home page management
Browse files Browse the repository at this point in the history
  • Loading branch information
Kodylow committed Sep 13, 2024
1 parent 80ddaf9 commit bf221c8
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 102 deletions.
1 change: 0 additions & 1 deletion apps/router/public/config.json
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
{}
67 changes: 66 additions & 1 deletion apps/router/src/context/AppContext.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
import React, { createContext, Dispatch, ReactNode, useReducer } from 'react';
import React, {
createContext,
Dispatch,
ReactNode,
useEffect,
useReducer,
} from 'react';
import { GuardianConfig } from '../guardian-ui/GuardianApi';

import { GatewayConfig } from '../gateway-ui/types';

type Service = GuardianConfig | GatewayConfig;

export interface Guardian {
config: GuardianConfig;
}
Expand Down Expand Up @@ -139,6 +147,63 @@ export const AppContextProvider: React.FC<AppContextProviderProps> = ({
}: AppContextProviderProps) => {
const [state, dispatch] = useReducer(reducer, makeInitialState());

useEffect(() => {
const addService = (service: Service) => {
const kind = isGuardian(service) ? 'guardian' : 'gateway';
const stateKey = `${kind}s` as 'guardians' | 'gateways';
const id = (Object.keys(state[stateKey]).length + 1).toString();

if (kind === 'guardian') {
dispatch({
type: APP_ACTION_TYPE.ADD_GUARDIAN,
payload: { id, guardian: { config: service as GuardianConfig } },
});
} else {
dispatch({
type: APP_ACTION_TYPE.ADD_GATEWAY,
payload: { id, gateway: { config: service as GatewayConfig } },
});
}
};

const isGuardian = (service: Service): service is GuardianConfig =>
'fm_config_api' in service;
const isGateway = (service: Service): service is GatewayConfig =>
'baseUrl' in service;

const handleConfig = (data: Service | Service[]) => {
const services = Array.isArray(data) ? data : [data];
services.forEach((service) => {
if (isGuardian(service) || isGateway(service)) {
addService(service);
}
});
};
fetch('/config.json')
.then((response) => {
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.text();
})
.then((text) => {
if (!text.trim()) {
console.warn('Config file is empty');
return;
}
try {
const data = JSON.parse(text);
handleConfig(data);
} catch (error) {
console.error('Error parsing config JSON:', error);
console.log('Raw config content:', text);
}
})
.catch((error) => {
console.error('Error fetching or processing config:', error);
});
}, [state]);

return (
<AppContext.Provider value={{ ...state, dispatch }}>
{children}
Expand Down
88 changes: 88 additions & 0 deletions apps/router/src/home/ConnectServiceModal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
import React, { useState } from 'react';
import {
Button,
FormControl,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
} from '@chakra-ui/react';
import { useTranslation } from '@fedimint/utils';
import { GuardianConfig } from '../guardian-ui/GuardianApi';
import { useAppContext } from '../context/hooks';
import { APP_ACTION_TYPE } from '../context/AppContext';
import { GatewayConfig } from '../gateway-ui/types';

interface ConnectServiceModalProps {
isOpen: boolean;
onClose: () => void;
}

export const ConnectServiceModal: React.FC<ConnectServiceModalProps> = ({
isOpen,
onClose,
}) => {
const { t } = useTranslation();
const [configUrl, setConfigUrl] = useState('');
const { dispatch } = useAppContext();

const handleSubmit = () => {
const isWebSocket =
configUrl.startsWith('ws://') || configUrl.startsWith('wss://');
const isHttp =
configUrl.startsWith('http://') || configUrl.startsWith('https://');

if (isWebSocket) {
const guardianConfig: GuardianConfig = { fm_config_api: configUrl };
dispatch({
type: APP_ACTION_TYPE.ADD_GUARDIAN,
payload: {
id: configUrl,
guardian: { config: guardianConfig },
},
});
} else if (isHttp) {
const gatewayConfig: GatewayConfig = { baseUrl: configUrl };
dispatch({
type: APP_ACTION_TYPE.ADD_GATEWAY,
payload: {
id: configUrl,
gateway: { config: gatewayConfig },
},
});
} else {
console.error('Invalid URL format');
// You might want to show an error message to the user here
return;
}

onClose();
};

return (
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>{t('home.addService', 'Add Service')}</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<FormLabel>{t('notConfigured.urlLabel')}</FormLabel>
<Input
placeholder='wss://fedimintd.my-awesome-domain.com:6000'
value={configUrl}
onChange={(e) => setConfigUrl(e.target.value)}
/>
</FormControl>
<Button mt={4} colorScheme='blue' onClick={handleSubmit}>
{t('common.submit')}
</Button>
</ModalBody>
</ModalContent>
</Modal>
);
};
197 changes: 97 additions & 100 deletions apps/router/src/home/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,126 +1,123 @@
import React, { useContext, useMemo, useState } from 'react';
import React, { useContext, useMemo } from 'react';
import { Link } from 'react-router-dom';
import {
Box,
Button,
Center,
Card,
CardBody,
CardHeader,
Flex,
FormControl,
FormLabel,
Input,
Modal,
ModalBody,
ModalCloseButton,
ModalContent,
ModalHeader,
ModalOverlay,
Heading,
Table,
Tbody,
Td,
Text,
Th,
Thead,
Tr,
useDisclosure,
} from '@chakra-ui/react';
import { Wrapper } from '@fedimint/ui';
import { useTranslation } from '@fedimint/utils';
import { AppContext } from '../context/AppContext';
import { ConnectServiceModal } from './ConnectServiceModal';

export const HomePage: React.FC = () => {
const { t } = useTranslation();
const { guardians, gateways } = useContext(AppContext);
const { isOpen, onOpen, onClose } = useDisclosure();
const [configUrl, setConfigUrl] = useState('');

const content = useMemo(() => {
return (
<Flex
direction='column'
align='center'
width='100%'
paddingTop='10vh'
paddingX='4'
>
{Object.keys(guardians).length + Object.keys(gateways).length === 0 ? (
<>
<Button
onClick={onOpen}
colorScheme='green'
mb={4}
width='100%'
maxWidth='400px'
>
{t('home.connectGuardian', 'Connect a Guardian')}
</Button>
<Button
onClick={onOpen}
colorScheme='purple'
mb={8}
width='100%'
maxWidth='400px'
>
{t('home.connectGateway', 'Connect a Gateway')}
</Button>
</>
) : (
<>
<Text fontSize='xl' fontWeight='bold' mb={4}>
{t('home.connectedServices', 'Connected Services')}
</Text>
<Flex
direction='column'
gap={4}
align='stretch'
width='100%'
maxWidth='400px'
>
{Object.keys(guardians).map((guardianIndex) => (
<Link
key={`guardian-${guardianIndex}`}
to={`/guardian/${guardianIndex}`}
>
<Button width='100%' colorScheme='green'>
{t(`home.guardian`, 'Guardian')} {guardianIndex}
</Button>
</Link>
))}
{Object.keys(gateways).map((gatewayIndex) => (
<Link
key={`gateway-${gatewayIndex}`}
to={`/gateway/${gatewayIndex}`}
>
<Button width='100%' colorScheme='purple'>
{t(`home.gateway`, 'Gateway')} {gatewayIndex}
</Button>
</Link>
))}
</Flex>
</>
<Box width='100%' maxWidth='1200px' margin='auto' paddingY='8'>
<Flex
justifyContent='space-between'
alignItems='center'
marginBottom='6'
>
<Heading as='h1' size='xl'>
{t('home.services', 'Services')}
</Heading>
<Button onClick={onOpen} colorScheme='blue'>
{t('home.addService', 'Add a Service')}
</Button>
</Flex>

{Object.keys(guardians).length > 0 && (
<Card marginBottom='6'>
<CardHeader>
<Heading size='md'>{t('home.guardians', 'Guardians')}</Heading>
</CardHeader>
<CardBody>
<Table variant='simple'>
<Thead>
<Tr>
<Th>{t('home.guardianId', 'Guardian ID')}</Th>
<Th>{t('home.actions', 'Actions')}</Th>
</Tr>
</Thead>
<Tbody>
{Object.keys(guardians).map((guardianIndex) => (
<Tr key={`guardian-${guardianIndex}`}>
<Td>{guardianIndex}</Td>
<Td>
<Link to={`/guardian/${guardianIndex}`}>
<Button size='sm' colorScheme='green'>
{t('home.view', 'View')}
</Button>
</Link>
</Td>
</Tr>
))}
</Tbody>
</Table>
</CardBody>
</Card>
)}

{Object.keys(gateways).length > 0 && (
<Card>
<CardHeader>
<Heading size='md'>{t('home.gateways', 'Gateways')}</Heading>
</CardHeader>
<CardBody>
<Table variant='simple'>
<Thead>
<Tr>
<Th>{t('home.gatewayId', 'Gateway ID')}</Th>
<Th>{t('home.actions', 'Actions')}</Th>
</Tr>
</Thead>
<Tbody>
{Object.keys(gateways).map((gatewayIndex) => (
<Tr key={`gateway-${gatewayIndex}`}>
<Td>{gatewayIndex}</Td>
<Td>
<Link to={`/gateway/${gatewayIndex}`}>
<Button size='sm' colorScheme='purple'>
{t('home.view', 'View')}
</Button>
</Link>
</Td>
</Tr>
))}
</Tbody>
</Table>
</CardBody>
</Card>
)}
</Flex>

{Object.keys(guardians).length + Object.keys(gateways).length === 0 && (
<Text>{t('home.noServices', 'No services connected yet.')}</Text>
)}
</Box>
);
}, [guardians, gateways, t, onOpen]);

return (
<Center>
<Box width='100%'>
<Wrapper>{content}</Wrapper>
</Box>
<Modal isOpen={isOpen} onClose={onClose}>
<ModalOverlay />
<ModalContent>
<ModalHeader>{t('home.addGuardian', 'Add Guardian')}</ModalHeader>
<ModalCloseButton />
<ModalBody pb={6}>
<FormControl>
<FormLabel>{t('notConfigured.urlLabel')}</FormLabel>
<Input
placeholder='wss://fedimintd.my-awesome-domain.com:6000'
value={configUrl}
onChange={(e) => setConfigUrl(e.target.value)}
/>
</FormControl>
<Button mt={4} colorScheme='blue'>
{t('common.submit')}
</Button>
</ModalBody>
</ModalContent>
</Modal>
</Center>
<Wrapper>
{content}
<ConnectServiceModal isOpen={isOpen} onClose={onClose} />
</Wrapper>
);
};

0 comments on commit bf221c8

Please sign in to comment.