add account overview, cleanup
Change-Id: I176af7a7688f38cb30eb7d65fa3e00e55f10da6f
diff --git a/client/src/App.js b/client/src/App.js
index cc1b5bf..bce1842 100644
--- a/client/src/App.js
+++ b/client/src/App.js
@@ -16,6 +16,7 @@
import AccountSelection from "./pages/accountSelection.jsx"
import ServerSetup from "./pages/serverSetup.jsx"
import NotFoundPage from "./pages/404.jsx"
+import LoadingPage from './components/loading'
const App = (props) => {
const history = useHistory()
@@ -36,7 +37,7 @@
console.log(location)
if (!state.loaded) {
- return <Container><CircularProgress /></Container>
+ return <LoadingPage />
} else if (!state.auth.setupComplete) {
return <Switch>
<Route path="/setup" component={ServerSetup} />
@@ -44,7 +45,6 @@
</Switch>
}
return <React.Fragment>
- <CssBaseline />
<Switch>
<Route exact path="/"><Redirect to="/account" /></Route>
<Route path="/account/:accountId/settings" component={AccountSettings} />
diff --git a/client/src/AuthManager.js b/client/src/AuthManager.js
index 4f504db..4b7b6b4 100644
--- a/client/src/AuthManager.js
+++ b/client/src/AuthManager.js
@@ -140,7 +140,7 @@
}
fetch(url, init) {
- console.log(`get ${url}`)
+ console.log(`fetch ${url}`)
if (!this.state.authenticated) {
return new Promise((resolve, reject) => this.tasks.push({url, init, resolve, reject}))
}
diff --git a/client/src/components/AccountList.js b/client/src/components/AccountList.js
index 38f1291..594c979 100644
--- a/client/src/components/AccountList.js
+++ b/client/src/components/AccountList.js
@@ -1,6 +1,6 @@
import React from 'react'
-import { Avatar, List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
-import { PersonRounded } from '@material-ui/icons';
+import { List, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
+import ConversationAvatar from './ConversationAvatar'
export default function AccountList(props) {
return <List>
@@ -9,7 +9,7 @@
const displayName = account.getDisplayNameNoFallback()
return <ListItem button key={account.getId()} onClick={() => props.onClick(account)}>
<ListItemAvatar>
- <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
+ <ConversationAvatar displayName={displayName} />
</ListItemAvatar>
<ListItemText primary={account.getDisplayName()} secondary={account.getDisplayUri()} />
</ListItem>
diff --git a/client/src/components/AccountPreferences.js b/client/src/components/AccountPreferences.js
index 5b6ae28..5629ef0 100644
--- a/client/src/components/AccountPreferences.js
+++ b/client/src/components/AccountPreferences.js
@@ -1,63 +1,142 @@
import React from 'react';
-
-import Typography from '@material-ui/core/Typography';
+import { makeStyles } from '@material-ui/core/styles';
+import { List, ListItem, ListItemIcon, ListItemSecondaryAction, ListItemText, ListSubheader, Switch, Typography, Grid, Paper, CardContent, Card } from '@material-ui/core';
+import { PhoneCallbackRounded, GroupRounded } from '@material-ui/icons';
import JamiIdCard from './JamiIdCard';
-import List from '@material-ui/core/List';
-import ListItem from '@material-ui/core/ListItem';
-import ListItemIcon from '@material-ui/core/ListItemIcon';
-import ListItemSecondaryAction from '@material-ui/core/ListItemSecondaryAction';
-import ListItemText from '@material-ui/core/ListItemText';
-import ListSubheader from '@material-ui/core/ListSubheader';
-import Switch from '@material-ui/core/Switch';
-import PhoneCallbackIcon from '@material-ui/icons/PhoneCallback';
-import GroupRoundedIcon from '@material-ui/icons/GroupRounded';
import Account from '../../../model/Account';
+import ConversationsOverviewCard from './ConversationsOverviewCard';
-class AccountPreferences extends React.Component {
+const useStyles = makeStyles({
+ root: {
+ minWidth: 275,
+ },
+ bullet: {
+ display: 'inline-block',
+ margin: '0 2px',
+ transform: 'scale(0.8)',
+ },
+ title: {
+ fontSize: 14,
+ },
+ pos: {
+ marginBottom: 12,
+ },
+});
- render() {
- const account = this.props.account
- const isJamiAccount = account.getType() === Account.TYPE_JAMI
- return (
- <React.Fragment>
- <Typography variant="h2" component="h2">{isJamiAccount ? "Jami account" : "SIP account"}</Typography>
+const columns = [
+ { id: 'name', label: 'Name', minWidth: 170 },
+ { id: 'code', label: 'ISO\u00a0Code', minWidth: 100 },
+ {
+ id: 'population',
+ label: 'Population',
+ minWidth: 170,
+ align: 'right',
+ format: (value) => value.toLocaleString('en-US'),
+ },
+ {
+ id: 'size',
+ label: 'Size\u00a0(km\u00b2)',
+ minWidth: 170,
+ align: 'right',
+ format: (value) => value.toLocaleString('en-US'),
+ },
+ {
+ id: 'density',
+ label: 'Density',
+ minWidth: 170,
+ align: 'right',
+ format: (value) => value.toFixed(2),
+ },
+];
- {isJamiAccount &&
- <JamiIdCard account={account} />}
-
- <List subheader={<ListSubheader>Settings</ListSubheader>}>
- <ListItem>
- <ListItemIcon>
- <GroupRoundedIcon />
- </ListItemIcon>
- <ListItemText id="switch-list-label-rendezvous" primary="Rendez-Vous point" />
- <ListItemSecondaryAction>
- <Switch
- edge="end"
- /*onChange={handleToggle('wifi')}*/
- checked={account.isRendezVous()}
- inputProps={{ 'aria-labelledby': 'switch-list-label-wifi' }}
- />
- </ListItemSecondaryAction>
- </ListItem>
- <ListItem>
- <ListItemIcon>
- <PhoneCallbackIcon />
- </ListItemIcon>
- <ListItemText id="switch-list-label-publicin" primary="Allow connection from unkown peers" />
- <ListItemSecondaryAction>
- <Switch
- edge="end"
- /*onChange={handleToggle('bluetooth')}*/
- checked={account.isPublicIn()}
- inputProps={{ 'aria-labelledby': 'switch-list-label-bluetooth' }}
- />
- </ListItemSecondaryAction>
- </ListItem>
- </List>
- </React.Fragment>)
- }
+function createData(name, code, population, size) {
+ const density = population / size;
+ return { name, code, population, size, density };
}
-export default AccountPreferences;
\ No newline at end of file
+const rows = [
+ createData('India', 'IN', 1324171354, 3287263),
+ createData('China', 'CN', 1403500365, 9596961),
+ createData('Italy', 'IT', 60483973, 301340),
+ createData('United States', 'US', 327167434, 9833520),
+ createData('Canada', 'CA', 37602103, 9984670),
+ createData('Australia', 'AU', 25475400, 7692024),
+ createData('Germany', 'DE', 83019200, 357578),
+ createData('Ireland', 'IE', 4857000, 70273),
+ createData('Mexico', 'MX', 126577691, 1972550),
+ createData('Japan', 'JP', 126317000, 377973),
+ createData('France', 'FR', 67022000, 640679),
+ createData('United Kingdom', 'GB', 67545757, 242495),
+ createData('Russia', 'RU', 146793744, 17098246),
+ createData('Nigeria', 'NG', 200962417, 923768),
+ createData('Brazil', 'BR', 210147125, 8515767),
+];
+
+export default function AccountPreferences(props) {
+ const classes = useStyles()
+ const account = props.account
+ const isJamiAccount = account.getType() === Account.TYPE_JAMI
+ const alias = isJamiAccount ? "Jami account" : "SIP account"
+ return (
+ <React.Fragment>
+ <Typography variant="h2" component="h2" gutterBottom>{alias}</Typography>
+ <Grid container spacing={3} style={{marginBottom: 16}}>
+ {isJamiAccount &&
+ <Grid item xs={12}><JamiIdCard account={account} /></Grid>}
+
+ <Grid item xs={12} sm={6}>
+ <ConversationsOverviewCard accountId={account.getId()} />
+ </Grid>
+
+ <Grid item xs={12} sm={6}>
+ <Card>
+ <CardContent>
+ <Typography className={classes.title} color="textSecondary" gutterBottom>
+ Current calls
+ </Typography>
+ <Typography gutterBottom variant="h5" component="h2">
+ 0
+ </Typography>
+ </CardContent>
+ </Card>
+ </Grid>
+ </Grid>
+
+ <List subheader={<ListSubheader>Settings</ListSubheader>}>
+ <ListItem>
+ <ListItemIcon>
+ <GroupRounded />
+ </ListItemIcon>
+ <ListItemText id="switch-list-label-rendezvous" primary="Rendez-Vous point" />
+ <ListItemSecondaryAction>
+ <Switch
+ edge="end"
+ /*onChange={handleToggle('wifi')}*/
+ checked={account.isRendezVous()}
+ inputProps={{ 'aria-labelledby': 'switch-list-label-wifi' }}
+ />
+ </ListItemSecondaryAction>
+ </ListItem>
+ <ListItem>
+ <ListItemIcon>
+ <PhoneCallbackRounded />
+ </ListItemIcon>
+ <ListItemText id="switch-list-label-publicin" primary="Allow connection from unkown peers" />
+ <ListItemSecondaryAction>
+ <Switch
+ edge="end"
+ /*onChange={handleToggle('bluetooth')}*/
+ checked={account.isPublicIn()}
+ inputProps={{ 'aria-labelledby': 'switch-list-label-bluetooth' }}
+ />
+ </ListItemSecondaryAction>
+ </ListItem>
+ </List>
+
+
+
+
+
+ </React.Fragment>)
+}
diff --git a/client/src/components/ConversationAvatar.js b/client/src/components/ConversationAvatar.js
new file mode 100644
index 0000000..b9160a1
--- /dev/null
+++ b/client/src/components/ConversationAvatar.js
@@ -0,0 +1,7 @@
+import React from 'react';
+import { Avatar } from '@material-ui/core';
+import { PersonRounded } from '@material-ui/icons'
+
+export default function ConversationAvatar(props) {
+ return <Avatar>{props.displayName ? props.displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
+}
diff --git a/client/src/components/ConversationList.js b/client/src/components/ConversationList.js
index b28a6d6..9fa4650 100644
--- a/client/src/components/ConversationList.js
+++ b/client/src/components/ConversationList.js
@@ -3,7 +3,7 @@
import ConversationListItem from './ConversationListItem'
import ListSubheader from '@material-ui/core/ListSubheader';
import Conversation from '../../../model/Conversation';
-import GroupRoundedIcon from '@material-ui/icons/GroupRounded';
+import { GroupRounded as GroupIcon } from '@material-ui/icons';
import Typography from '@material-ui/core/Typography';
export default function ConversationList(props) {
@@ -21,7 +21,7 @@
)}
{props.conversations.length === 0 && (
<div className="list-placeholder">
- <GroupRoundedIcon color="disabled" fontSize="large" />
+ <GroupIcon color="disabled" fontSize="large" />
<Typography className="subtitle" variant="subtitle2">No conversation yet</Typography>
</div>
)}
diff --git a/client/src/components/ConversationListItem.js b/client/src/components/ConversationListItem.js
index a769168..a0295d5 100644
--- a/client/src/components/ConversationListItem.js
+++ b/client/src/components/ConversationListItem.js
@@ -1,15 +1,14 @@
-import { Avatar, ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
+import { ListItem, ListItemAvatar, ListItemText } from '@material-ui/core'
import React from 'react'
import Conversation from '../../../model/Conversation'
import { useHistory, useParams } from "react-router-dom"
-import { PersonRounded } from '@material-ui/icons'
+import ConversationAvatar from './ConversationAvatar'
export default function ConversationListItem(props) {
const { conversationId, contactId } = useParams()
const conversation = props.conversation
const pathId = conversationId || contactId
const isSelected = conversation.getDisplayUri() === pathId
- const displayName = conversation.getDisplayName()
const history = useHistory()
const uri = conversation.getId() ? `conversation/${conversation.getId()}` : `addContact/${conversation.getFirstMember().contact.getUri()}`
@@ -20,10 +19,8 @@
alignItems="flex-start"
selected={isSelected}
style={{overflow:'hidden'}}
- onClick={() => history.push(`/account/${conversation.getAccountId()}/${uri}`)}>
- <ListItemAvatar>
- <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
- </ListItemAvatar>
+ onClick={() => history.replace(`/account/${conversation.getAccountId()}/${uri}`)}>
+ <ListItemAvatar><ConversationAvatar displayName={conversation.getDisplayNameNoFallback()} /></ListItemAvatar>
<ListItemText
style={{overflow:'hidden', textOverflow:'ellipsis'}}
primary={conversation.getDisplayName()} secondary={conversation.getDisplayUri()} />
diff --git a/client/src/components/ConversationView.js b/client/src/components/ConversationView.js
index 678aafe..e818ba0 100644
--- a/client/src/components/ConversationView.js
+++ b/client/src/components/ConversationView.js
@@ -4,6 +4,7 @@
import SendMessageForm from './SendMessageForm';
import authManager from '../AuthManager'
import Conversation from '../../../model/Conversation';
+import LoadingPage from './loading';
class ConversationView extends React.Component {
constructor(props) {
@@ -69,7 +70,7 @@
render() {
if (this.state.loaded === false) {
- return <CircularProgress />
+ return <LoadingPage />
} else if (this.state.error === true) {
return <div>Error loding {this.props.conversationId}</div>
} else {
diff --git a/client/src/components/ConversationsOverviewCard.js b/client/src/components/ConversationsOverviewCard.js
new file mode 100644
index 0000000..01b2d7b
--- /dev/null
+++ b/client/src/components/ConversationsOverviewCard.js
@@ -0,0 +1,47 @@
+import React, { useEffect, useState } from 'react';
+import { Box, Card, CardActionArea, CardContent, CircularProgress, Typography } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
+import { useHistory, useParams } from 'react-router';
+import authManager from '../AuthManager'
+import Conversation from '../../../model/Conversation';
+
+const useStyles = makeStyles({
+ title: {
+ fontSize: 14,
+ }, pos: {
+ fontSize: 14,
+ }
+});
+
+export default function ConversationsOverviewCard(props) {
+ const classes = useStyles()
+ const history = useHistory()
+ const accountId = props.accountId || useParams().accountId
+ const [state, setState] = useState({ loaded: false })
+
+ useEffect(() => {
+ const controller = new AbortController()
+ authManager.fetch(`/api/accounts/${accountId}/conversations`, { signal: controller.signal })
+ .then(res => res.json())
+ .then(result => {
+ console.log(result)
+ setState({ loaded: true, conversations: Object.values(result).map(c => Conversation.from(accountId, c)) })
+ })
+ return () => controller.abort()
+ }, [])
+
+ return (
+ <Card onClick={() => history.push(`/account/${accountId}`)} >
+ <CardActionArea>
+ <CardContent>
+ <Typography className={classes.title} color="textSecondary" gutterBottom>
+ Conversations
+ </Typography>
+ <Typography gutterBottom variant="h5" component="h2">
+ {state.loaded ? state.conversations.length : <CircularProgress size={24} />}
+ </Typography>
+ </CardContent>
+ </CardActionArea>
+ </Card>
+ )
+}
diff --git a/client/src/components/Header.js b/client/src/components/Header.js
index c0408a1..c621b56 100644
--- a/client/src/components/Header.js
+++ b/client/src/components/Header.js
@@ -1,45 +1,33 @@
-import React from 'react'
-import Button from '@material-ui/core/Button';
-import Menu from '@material-ui/core/Menu';
-import MenuItem from '@material-ui/core/MenuItem';
-//import { useHistory } from "react-router-dom";
+import React, { useState } from 'react'
+import { Box, Button, Menu, MenuItem } from '@material-ui/core'
+import { useHistory, useParams } from "react-router-dom"
import authManager from '../AuthManager'
export default function Header() {
- //const history = useHistory();
+ const history = useHistory()
+ const [anchorEl, setAnchorEl] = useState(null)
+ const handleClick = (event) => setAnchorEl(event.currentTarget)
+ const handleClose = () => setAnchorEl(null)
+ const params = useParams()
- const [anchorEl, setAnchorEl] = React.useState(null);
+ const goToAccountSelection = () => history.push(`/account`)
+ const goToAccountSettings = () => history.push(`/account/${params.accountId}/settings`)
- const handleClick = (event) => {
- setAnchorEl(event.currentTarget);
- };
-
- const handleClose = () => {
- setAnchorEl(null);
- };
-
- const disconnect = () => {
- authManager.disconnect()
- //let path = `/`;
- //history.push(path);
- }
-
- return (
- <div>
- <Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
- Menu
- </Button>
- <Menu
- id="simple-menu"
- anchorEl={anchorEl}
-
- open={Boolean(anchorEl)}
- onClose={handleClose}
- >
- <MenuItem onClick={handleClose}>Profile</MenuItem>
- <MenuItem onClick={handleClose}>Mon compte</MenuItem>
- <MenuItem onClick={disconnect}>Déconnexion</MenuItem>
- </Menu>
- </div>
- );
+ return (
+ <Box>
+ <Button aria-controls="simple-menu" aria-haspopup="true" onClick={handleClick}>
+ Menu
+ </Button>
+ <Menu
+ id="simple-menu"
+ anchorEl={anchorEl}
+ open={Boolean(anchorEl)}
+ onClose={handleClose}
+ >
+ <MenuItem onClick={goToAccountSelection}>Change account</MenuItem>
+ {params.accountId && <MenuItem onClick={goToAccountSettings}>Account settings</MenuItem>}
+ <MenuItem onClick={() => authManager.disconnect()}>Log out</MenuItem>
+ </Menu>
+ </Box>
+ )
}
\ No newline at end of file
diff --git a/client/src/components/JamiIdCard.js b/client/src/components/JamiIdCard.js
index eb31a26..1b75bc5 100644
--- a/client/src/components/JamiIdCard.js
+++ b/client/src/components/JamiIdCard.js
@@ -1,25 +1,35 @@
import React from 'react';
+import { Box, Card, CardContent, Typography } from '@material-ui/core';
+import { makeStyles } from '@material-ui/core/styles';
-import Card from '@material-ui/core/Card';
-import CardContent from '@material-ui/core/CardContent';
-import Typography from '@material-ui/core/Typography';
+const useStyles = makeStyles({
+ title: {
+ fontSize: 14,
+ }, pos: {
+ fontSize: 14,
-class JamiIdCard extends React.Component {
- render() {
- const account = this.props.account
- const registeredName = account.getRegisteredName()
- return (
- <Card style={{marginBottom:16}}>
- <CardContent>
- <Typography variant="h6">Jami key ID</Typography>
- <Typography variant="body1">{account.getUri()}</Typography>
- {registeredName && <div>
- <Typography variant="h6">Jami username</Typography>
- <Typography variant="body1">{registeredName}</Typography></div>
- }
- </CardContent>
- </Card>)
}
-}
+});
-export default JamiIdCard;
\ No newline at end of file
+export default function JamiIdCard(props) {
+ const classes = useStyles()
+ const account = props.account
+ const registeredName = account.getRegisteredName()
+ return <Card style={{marginBottom:16}}>
+ <CardContent>
+ <Box>
+ <Typography className={classes.title} color="textSecondary">
+ Jami ID
+ </Typography>
+ <Typography variant="h5" component="h2" gutterBottom noWrap>{account.getUri()}</Typography>
+ </Box>
+ {registeredName && <Box>
+ <Typography className={classes.title} color="textSecondary" >
+ Jami username
+ </Typography>
+ <Typography variant="h5" component="h2" noWrap>{registeredName}</Typography>
+ </Box>
+ }
+ </CardContent>
+ </Card>
+}
diff --git a/client/src/components/MessageList.js b/client/src/components/MessageList.js
index 4841ea1..1673a38 100644
--- a/client/src/components/MessageList.js
+++ b/client/src/components/MessageList.js
@@ -1,26 +1,26 @@
import Message from './Message'
import React from 'react'
-import { Avatar, Box, Divider, Typography } from '@material-ui/core'
-import { PersonRounded } from '@material-ui/icons'
+import { Box, Divider, Typography } from '@material-ui/core'
+import ConversationAvatar from './ConversationAvatar'
export default function MessageList(props) {
- const displayName = props.conversation.getDisplayName()
+ const displayName = props.conversation.getDisplayName()
- return (
- <div className="message-list">
- <Box>
- <Box style={{ display: 'inline-block', margin: 16, verticalAlign: 'middle' }}>
- <Avatar>{displayName ? displayName[0].toUpperCase() : <PersonRounded />}</Avatar>
- </Box>
- <Box style={{ display: 'inline-block', verticalAlign: 'middle' }}>
- <Typography variant="h5">{displayName}</Typography>
- <Typography variant="subtitle1">{props.conversation.getId()}</Typography>
- </Box>
- </Box>
- <Divider orientation="horizontal" />
- {props.messages.map((message, index) =>
- <Message key={index} username={message.senderId} text={message.text} />
- )}
- </div>
- )
+ return (
+ <div className="message-list">
+ <Box>
+ <Box style={{ display: 'inline-block', margin: 16, verticalAlign: 'middle' }}>
+ <ConversationAvatar displayName={props.conversation.getDisplayNameNoFallback()} />
+ </Box>
+ <Box style={{ display: 'inline-block', verticalAlign: 'middle' }}>
+ <Typography variant="h6">{displayName}</Typography>
+ <Typography variant="subtitle1">{props.conversation.getId()}</Typography>
+ </Box>
+ </Box>
+ <Divider orientation="horizontal" />
+ {props.messages.map((message, index) =>
+ <Message key={index} username={message.senderId} text={message.text} />
+ )}
+ </div>
+ )
}
\ No newline at end of file
diff --git a/client/src/components/NewContactForm.js b/client/src/components/NewContactForm.js
index 5636be2..3543d6d 100644
--- a/client/src/components/NewContactForm.js
+++ b/client/src/components/NewContactForm.js
@@ -1,49 +1,32 @@
-import React from 'react'
+import React, { useState } from 'react'
import { InputBase, InputAdornment } from '@material-ui/core';
import { SearchRounded } from '@material-ui/icons';
-class NewContactForm extends React.Component {
- constructor(props) {
- super(props)
- this.state = {value: ''}
- this.controller = new AbortController()
- this.handleChange = this.handleChange.bind(this)
- this.handleSubmit = this.handleSubmit.bind(this)
+export default function NewContactForm(props) {
+ const [value, setValue] = useState('')
+
+ const handleChange = event => {
+ setValue(event.target.value)
+ if (props.onChange)
+ props.onChange(event.target.value)
}
- componentDidMount() {
- this.controller = new AbortController()
- }
-
- componentWillUnmount() {
- this.controller.abort()
- this.req = undefined
- }
-
- handleChange(event) {
- this.setState({value: event.target.value})
- this.props.onChange(event.target.value)
- }
-
- handleSubmit(event) {
+ const handleSubmit = event => {
event.preventDefault()
- if (this.props.onSubmit)
- this.props.onSubmit(this.state.value)
+ if (value && props.onSubmit)
+ props.onSubmit(value)
}
- render() {
- return (
- <form className="main-search" onSubmit={this.handleSubmit} noValidate autoComplete="off">
- <InputBase
- className="main-search-input"
- type="search"
- placeholder="Ajouter un contact"
- onChange={this.handleChange}
- startAdornment={<InputAdornment position="start"><SearchRounded /></InputAdornment>}
- />
- </form>
- )
- }
+ return (
+ <form className="main-search" onSubmit={handleSubmit} noValidate autoComplete="off">
+ <InputBase
+ className="main-search-input"
+ type="search"
+ placeholder="Ajouter un contact"
+ value={value}
+ onChange={handleChange}
+ startAdornment={<InputAdornment position="start"><SearchRounded /></InputAdornment>}
+ />
+ </form>
+ )
}
-
-export default NewContactForm
\ No newline at end of file
diff --git a/client/src/components/loading.js b/client/src/components/loading.js
new file mode 100644
index 0000000..198b993
--- /dev/null
+++ b/client/src/components/loading.js
@@ -0,0 +1,8 @@
+import { CircularProgress, Container } from '@material-ui/core';
+import React from 'react';
+
+export default function LoadingPage() {
+ return <Container style={{ textAlign: "center" }}>
+ <CircularProgress style={{ margin: 32 }} />
+ </Container>
+}
\ No newline at end of file
diff --git a/client/src/index.ejs b/client/src/index.ejs
index aee5126..162d470 100644
--- a/client/src/index.ejs
+++ b/client/src/index.ejs
@@ -3,13 +3,12 @@
<head>
<meta charset="utf-8" />
- <link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
+ <link rel="icon" href="/favicon.ico" />
+ <link rel="apple-touch-icon" href="/logo192.png" />
<meta name="viewport" content="minimum-scale=1, initial-scale=1, width=device-width" />
- <meta name="theme-color" content="#000000" />
<meta name="description" content="Web site created using create-react-app" />
- <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700&display=swap" />
- <title>Jaas Web Client App</title>
+ <title>Jami Web Node</title>
</head>
<body>
diff --git a/client/src/index.js b/client/src/index.js
index 054afae..182e2cb 100644
--- a/client/src/index.js
+++ b/client/src/index.js
@@ -4,6 +4,7 @@
import { BrowserRouter as Router } from 'react-router-dom'
import App from './App.js'
import './index.scss'
+import { CssBaseline } from '@material-ui/core'
//import * as serviceWorker from './serviceWorker'
const rootEl = document.getElementById('root')
@@ -11,6 +12,7 @@
const render = Component =>
ReactDOM.render(
<React.StrictMode>
+ <CssBaseline />
<Router>
<Component />
</Router>
diff --git a/client/src/index.scss b/client/src/index.scss
index 73ba105..9ddcfc8 100644
--- a/client/src/index.scss
+++ b/client/src/index.scss
@@ -23,16 +23,18 @@
.app {
display: grid;
height: 100%;
- grid-template-columns: repeat(6, 1fr);
- grid-template-rows: 40px 40px 1fr 1fr 1fr 1fr 92px;
+ grid-template-columns: 320px 1fr;
+ grid-template-rows: 40px 50px 1fr 1fr 92px;
grid-template-areas:
- "h h h h h h"
- "n m m m m m"
- "r m m m m m"
- "r m m m m m"
- "r m m m m m"
- "r m m m m m"
- "r s s s s s";
+ "h m"
+ "n m"
+ "r m"
+ "r m"
+ "r s";
+}
+
+.MuiContainer-root {
+ padding-bottom: 32px;
}
.login {
diff --git a/client/src/pages/accountSelection.jsx b/client/src/pages/accountSelection.jsx
index 2a4c99b..40c0750 100644
--- a/client/src/pages/accountSelection.jsx
+++ b/client/src/pages/accountSelection.jsx
@@ -1,64 +1,52 @@
-import React from 'react';
-import Header from '../components/Header'
-import authManager from '../AuthManager'
-import AccountList from '../components/AccountList';
-import CircularProgress from '@material-ui/core/CircularProgress';
+import React, { useEffect, useState } from 'react';
import { withRouter } from 'react-router-dom';
-import Card from '@material-ui/core/Card';
-import CardHeader from '@material-ui/core/CardHeader';
-import Container from '@material-ui/core/Container';
+import { Card, CardHeader, Container, CircularProgress } from '@material-ui/core';
+import Header from '../components/Header'
+import AccountList from '../components/AccountList';
+import authManager from '../AuthManager'
import Account from '../../../model/Account';
+import LoadingPage from '../components/loading';
-class AccountSelection extends React.Component {
+const AccountSelection = (props) => {
+ const [state, setState] = useState({
+ loaded: false,
+ error: false,
+ accounts: []
+ })
- constructor(props) {
- super(props)
- this.controller = new AbortController()
- this.state = {
- loaded: false,
- error: false,
- accounts: []
- }
- }
-
- componentDidMount() {
- if (!this.state.loaded) {
- authManager.fetch('/api/accounts', {signal: this.controller.signal})
- .then(res => res.json())
- .then(result => {
- console.log(result)
- this.setState({
- loaded: true,
- accounts: result.map(account => Account.from(account)),
- })
- }, error => {
- console.log(`get error ${error}`)
- this.setState({
- loaded: true,
- error: true
- })
+ useEffect(() => {
+ const controller = new AbortController()
+ authManager.fetch(`/api/accounts`, {signal: controller.signal})
+ .then(res => res.json())
+ .then(result => {
+ console.log(result)
+ setState({
+ loaded: true,
+ accounts: result.map(account => Account.from(account)),
})
- }
- }
- componentWillUnmount() {
- this.controller.abort()
- }
+ }, error => {
+ console.log(`get error ${error}`)
+ setState({
+ loaded: true,
+ error: true
+ })
+ })
+ return () => controller.abort()
+ }, [])
- render() {
- if (!this.state.loaded)
- return <Container><CircularProgress /></Container>
- return (
- <div className="app" style={{marginBottom:32}} >
- <Header />
- <Container>
- <Card style={{marginTop:32, marginBottom:32}}>
- <CardHeader title="Choose an account" />
- <AccountList accounts={this.state.accounts} onClick={account => this.props.history.push(`/account/${account.getId()}`)} />
- </Card>
- </Container>
- </div>
- )
- }
+ if (!state.loaded)
+ return <LoadingPage />
+ return (
+ <React.Fragment>
+ <Header />
+ <Container maxWidth="sm" style={{paddingBottom:32}}>
+ <Card style={{marginTop:32, marginBottom:32}}>
+ <CardHeader title="Choose an account" />
+ <AccountList accounts={state.accounts} onClick={account => props.history.push(`/account/${account.getId()}/settings`)} />
+ </Card>
+ </Container>
+ </React.Fragment>
+ )
}
export default withRouter(AccountSelection);
\ No newline at end of file
diff --git a/client/src/pages/accountSettings.jsx b/client/src/pages/accountSettings.jsx
index 12fa43a..79ab388 100644
--- a/client/src/pages/accountSettings.jsx
+++ b/client/src/pages/accountSettings.jsx
@@ -1,45 +1,32 @@
-import React from 'react';
+import React, {useEffect, useState} from 'react';
+import { useParams } from 'react-router';
+import { Container, CircularProgress } from '@material-ui/core';
import Header from '../components/Header'
import AccountPreferences from '../components/AccountPreferences'
-import Container from '@material-ui/core/Container';
-import CircularProgress from '@material-ui/core/CircularProgress';
import authManager from '../AuthManager'
import Account from '../../../model/Account'
-class AccountSettings extends React.Component {
+const AccountSettings = (props) => {
+ const accountId = props.accountId || useParams().accountId
+ const [state, setState] = useState({ loaded: false })
- constructor(props) {
- super(props)
- this.accountId = props.accountId || props.match.params.accountId
- this.state = { loaded: false, account: props.account }
- this.req = undefined
- }
+ useEffect(() => {
+ const controller = new AbortController()
+ authManager.fetch(`/api/accounts/${accountId}`, {signal: controller.signal})
+ .then(res => res.json())
+ .then(result => {
+ console.log(result)
+ setState({loaded: true, account: Account.from(result)})
+ })
+ return () => controller.abort()
+ }, [])
- componentDidMount() {
- this.controller = new AbortController()
- if (this.req === undefined) {
- this.req = authManager.fetch(`/api/accounts/${this.accountId}`, {signal: this.controller.signal})
- .then(res => res.json())
- .then(result => {
- console.log(result)
- this.setState({loaded: true, account: Account.from(result)})
- })
- }
- }
-
- componentWillUnmount() {
- this.controller.abort()
- this.req = undefined
- }
-
- render() {
- return (
- <Container maxWidth="sm" className="app" >
- <Header />
- {this.state.loaded ? <AccountPreferences account={this.state.account} /> : <CircularProgress />}
- </Container>
- )
- }
+ return (
+ <Container maxWidth="sm">
+ <Header />
+ {state.loaded ? <AccountPreferences account={state.account} /> : <CircularProgress />}
+ </Container>
+ )
}
export default AccountSettings;
\ No newline at end of file
diff --git a/client/src/pages/messenger.jsx b/client/src/pages/messenger.jsx
index f5e5104..3fef47b 100644
--- a/client/src/pages/messenger.jsx
+++ b/client/src/pages/messenger.jsx
@@ -15,6 +15,7 @@
import Contact from '../../../model/Contact'
import ConversationView from '../components/ConversationView';
import AddContactPage from './addContactPage.jsx';
+import LoadingPage from '../components/loading';
class JamiMessenger extends React.Component {
@@ -120,7 +121,7 @@
<Header />
{this.state.conversations ?
<ConversationList search={this.state.searchResult} conversations={this.state.conversations} accountId={accountId} /> :
- <CircularProgress />}
+ <div className="rooms-list"><LoadingPage /></div>}
<NewContactForm onChange={query => this.handleSearch(query)} />
{conversationId && <ConversationView accountId={accountId} conversationId={conversationId} />}
{contactId && <AddContactPage accountId={accountId} contactId={contactId} />}
@@ -137,4 +138,4 @@
}
}
-export default JamiMessenger;
\ No newline at end of file
+export default JamiMessenger
\ No newline at end of file
diff --git a/client/src/pages/serverSetup.jsx b/client/src/pages/serverSetup.jsx
index 70f9342..29e4ea5 100644
--- a/client/src/pages/serverSetup.jsx
+++ b/client/src/pages/serverSetup.jsx
@@ -29,11 +29,13 @@
const history = useHistory();
const [password, setPassword] = useState('');
const [passwordRepeat, setPasswordRepeat] = useState('');
+ const [loading, setLoading] = useState(false);
const isValid = () => password && password === passwordRepeat
const handleSubmit = async e => {
e.preventDefault()
+ setLoading(true)
if (!isValid())
return
if (await authManager.setup(password)) {
@@ -43,7 +45,7 @@
return (
<Container className='message-list'>
- <Card className={classes.wizardCard}>
+ <Card className={classes.wizardCard} disabled={loading}>
<CardContent component="form" onSubmit={handleSubmit}>
<Typography gutterBottom variant="h5" component="h2">
Jami Web Node setup
@@ -53,7 +55,7 @@
Let's start by creating a new administrator account to control access to the server configuration.
</Typography>
- <Typography variant='body1'></Typography>
+ <Box style={{ textAlign: 'center', marginTop: 8, marginBottom: 16 }}>
<div><Input className={classes.textField} value="admin" name="username" autoComplete="username" disabled /></div>
<div><Input
className={classes.textField}
@@ -73,7 +75,8 @@
type='password'
placeholder="Repeat password"
autoComplete="new-password" /></div>
- <Box style={{ textAlign: 'center', marginTop: 16 }}>
+ </Box>
+ <Box style={{ textAlign: 'center', marginTop: 24 }}>
<Fab variant='extended' color='primary' type='submit' disabled={!isValid()}>
<GroupAddRounded className={classes.extendedIcon} />
Create admin account