Skip to content

Instantly share code, notes, and snippets.

@danielsmykowski1
Created November 25, 2019 20:28
Show Gist options
  • Save danielsmykowski1/49dc08bf30285401a0312ffefd4ceb7d to your computer and use it in GitHub Desktop.
Save danielsmykowski1/49dc08bf30285401a0312ffefd4ceb7d to your computer and use it in GitHub Desktop.
A React main screen component written in Typescript
import * as React from 'react';
import { reaction } from 'mobx';
import { observer, inject } from 'mobx-react';
import * as usb from 'usb-detection'
import * as moment from 'moment'
import {
Table, Pagination, Header, Icon, Form,
Container, Modal, Button, Input,
} from 'semantic-ui-react';
import { BaseComponent } from '../components'
import { Notifications } from '../components/nofitications';
import { DomainStore, CheckUgrardKeyUniqueType } from '../../stores/domain.store';
import { AsyncState, AsyncError } from '../../models/async.models';
import { C } from '../../c';
import { User } from '../../models/domain.models';
interface Props {
domainStore: DomainStore
notifications: () => Notifications
}
interface State {
newUser: User
editUser: User
saveUserState: AsyncState<User>
loadUserListState: AsyncState<User[]>
checkUguardKeyUniqueState: AsyncState<CheckUgrardKeyUniqueType>
userListPage: number
usbDevice: usb.Device,
pickUserMode?: User,
}
@inject('domainStore')
@observer
export class MainScreen extends BaseComponent<Props, State> {
constructor(props: Props) {
super(props)
this.state = {
newUser: null,
editUser: null,
saveUserState: props.domainStore.saveUserState,
loadUserListState: props.domainStore.loadUserListState,
checkUguardKeyUniqueState: props.domainStore.checkUguardKeyUniqueState,
userListPage: 1,
usbDevice: null,
}
}
componentDidMount() {
usb.startMonitoring()
usb.on('add', (device) => {
console.log('usb device pluged in', device)
if (this.state.usbDevice) {
this.props.notifications().add({
level: 'warning',
title: 'Multiple USB',
message: 'Plugged-in USB device ignored',
})
return
}
if (!device.serialNumber || device.serialNumber.length < 5)
return this.props.notifications().add({
level: 'error',
title: 'Invalid serial number',
message: `Serial number "${device.serialNumber}" is invalid`,
delay: 9000,
})
usb.on(`remove:${device.vendorId}:${device.productId}`, () => {
console.log('usb device unplugged')
if (this.state.usbDevice) {
this.setState({ newUser: null, usbDevice: null })
this.props.notifications().add({
level: 'error',
title: 'Unplugged',
message: 'The usb device has been unplugged',
})
}
});
const newUser = new User('', '', device, moment().add('year', 1).toDate(), '')
this.setState({ newUser, usbDevice: device, },
() => this.props.domainStore.checkUguardKeyUnique(newUser.uguardKeys[0]))
})
this.disposables = [
reaction(
() => this.props.domainStore.saveUserState,
(saveUserState) => {
if (saveUserState.isFailed()) {
this.props.notifications()
.addFromAsyncError(saveUserState.error)
}
if (saveUserState.isSuccessful()) {
this.props.notifications().add({
level: 'success',
message: 'Saved successfully',
onDismiss: () => this.setState({ newUser: null, usbDevice: null })
})
}
this.setState({ saveUserState })
},
),
reaction(
() => this.props.domainStore.loadUserListState,
(loadUserListState) => {
this.setState({ loadUserListState })
},
),
reaction(
() => this.props.domainStore.checkUguardKeyUniqueState,
(checkUguardKeyUniqueState) => {
this.setState({ checkUguardKeyUniqueState })
},
),
]
if (this.state.loadUserListState.isUseless())
this.props.domainStore.loadUserList()
}
componentWillUnmount() {
usb.stopMonitoring()
}
render() {
return (
<Container>
{this.state.newUser != null && !this.state.pickUserMode
? this.renderNewUserModal() : null}
{this.state.editUser != null ? this.renderEditUserModal() : null}
{this.asyncStateWidget(this.state.loadUserListState, {
onFail: (error) => this.asyncErrorMessage(error, {
text: 'Try again',
onClick: () => this.props.domainStore.loadUserList(),
}),
onProgress: () => this.backLoading(),
onSuccess: (data) => this.renderUserListTable(data),
})}
</Container >
)
}
private saveUser(user: User, saveDownloadLinks: boolean) {
if (this.state.saveUserState.isInProgress())
return
if (!user.id && !user.name)
return this.props.notifications().add({
level: 'error',
message: 'Empty name',
})
if (!user.phone)
return this.props.notifications().add({
level: 'error',
message: 'Empty phone number',
})
this.props.domainStore.saveUser(user, saveDownloadLinks)
}
private renderUserListTable(users: User[]): JSX.Element {
const pageCount = Math.ceil(users.length / C.ITEMS_PER_PAGE)
const footer = pageCount > 1 ? (
<Table.Footer>
<Table.Row>
<Table.HeaderCell colSpan='8' className='right aligned'>
<Pagination
size='tiny'
activePage={this.state.userListPage}
totalPages={pageCount}
nextItem={false}
prevItem={false}
siblingRange={1}
boundaryRange={0}
ellipsisItem={false}
onPageChange={(_, data) => this.setState({
userListPage: data.activePage as number,
})} />
</Table.HeaderCell>
</Table.Row>
</Table.Footer>
) : null
const startIndex = (this.state.userListPage - 1) * C.ITEMS_PER_PAGE
return (
<Table celled selectable size='small'>
<Table.Header className='hideOnMobile'>
<Table.Row>
<Table.HeaderCell width='3'>ID</Table.HeaderCell>
<Table.HeaderCell>User name</Table.HeaderCell>
<Table.HeaderCell>User phone</Table.HeaderCell>
<Table.HeaderCell>Active Until</Table.HeaderCell>
<Table.HeaderCell>Description</Table.HeaderCell>
</Table.Row>
</Table.Header>
<Table.Body>
{
users
.slice(startIndex, startIndex + C.ITEMS_PER_PAGE)
.map(user => (
<Table.Row key={'userRow' + user.id}
className={this.state.pickUserMode ? 'pick-mode' : 'standard-mode'}
style={{ cursor: 'pointer' }}
onClick={() => {
if (this.state.pickUserMode) {
this.state.pickUserMode.id = user.id
this.state.pickUserMode.name = user.name
this.state.pickUserMode.phone = user.phone
this.state.pickUserMode.activeUntil = user.activeUntil
this.state.pickUserMode.description = user.description
this.state.pickUserMode.uguardKeys.push(...user.uguardKeys)
this.setState({ pickUserMode: undefined })
}
}}
onDoubleClick={() => this.setState({ editUser: user })} >
<Table.Cell>{user.id}</Table.Cell>
<Table.Cell>{user.name}</Table.Cell>
<Table.Cell>{user.phone}</Table.Cell>
<Table.Cell>{moment(user.activeUntil).format('DD-MM-YYYY')}</Table.Cell>
<Table.Cell>{user.description}</Table.Cell>
</Table.Row>
))
}
</Table.Body>
{footer}
</Table>
)
}
private renderNewUserModal() {
const user = this.state.newUser
const device = this.state.usbDevice
return (
<Modal size='small' centered={false}
open={this.state.newUser != null}
onClose={() => this.setState({ newUser: null, usbDevice: null })}
closeOnDimmerClick={false}
closeOnDocumentClick={false}
closeIcon>
<Header icon='key' content='New UGuard Key' />
<Modal.Content>
<Form size='tiny' >
<Header>USB Info</Header>
<Form.Group widths='equal'>
<Form.Field>
<label>Vendor ID</label>
<input readOnly
value={device.vendorId} />
</Form.Field>
<Form.Field>
<label>Product ID</label>
<input readOnly
value={device.productId} />
</Form.Field>
<Form.Field>
<label>Device Name</label>
<input readOnly
value={device.deviceName} />
</Form.Field>
</Form.Group>
<Form.Group widths='equal'>
<Form.Field>
<label>Manufacturer</label>
<input readOnly
value={device.manufacturer} />
</Form.Field>
<Form.Field>
<label>Serial Number</label>
<input readOnly
value={device.serialNumber} />
</Form.Field>
</Form.Group>
<Form.Field>
<label>Security Key</label>
<Input readOnly
value={user.uguardKeys[0]}
loading={this.state.checkUguardKeyUniqueState.isInProgress()}
icon={this.state.checkUguardKeyUniqueState.isSuccessful()
? this.state.checkUguardKeyUniqueState.value.unique
? { name: 'check circle', color: 'green' }
: {
name: 'cancel', color: 'red', link: true,
onClick: () => this.props.notifications().
addFromAsyncError(new AsyncError('API_ERROR', 'WARNING',
'UGUARD_KEY_ALREADY_EXISTS'))
}
: null} />
</Form.Field>
<Header className='user-info'>
User Info
{
user.id
? <Icon link
name='user cancel'
color='blue'
size='mini'
onClick={() => {
user.id = null
user.name = ''
user.phone = ''
user.activeUntil = moment().add('year', 1).toDate()
user.description = ''
user.uguardKeys.splice(1)
this.setState({})
}} />
: <Icon link
name='user'
color='blue'
size='mini'
onClick={() => {
this.setState({ pickUserMode: user })
this.props.notifications().add({
message: 'Pick the user',
level: 'info',
})
}} />
}
</Header>
<Form.Field>
<label>User name</label>
<Form.Input autoFocus
value={user.name || ''}
disabled={user.id != undefined}
onChange={(e, { value }) => this.setState(s => ({
newUser: {
...s.newUser,
name: value,
},
}))} />
</Form.Field>
<Form.Field>
<label>Phone number</label>
<Form.Input
value={user.phone || ''}
disabled={user.id != undefined}
onChange={(e, { value }) => this.setState(s => ({
newUser: {
...s.newUser,
phone: value,
},
}))} />
</Form.Field>
<Form.Field>
<label>Active Until</label>
<Form.Input type='date'
value={user.activeUntil ?
moment(user.activeUntil).format('YYYY-MM-DD') : ''}
disabled={user.id != undefined}
onChange={(e, { value }) => {
const activeUntil = moment(value).toDate()
this.setState(s => ({
newUser: {
...s.newUser,
activeUntil,
},
}))
}} />
</Form.Field>
<Form.Field>
<label>Description</label>
<Form.Input
value={user.description || ''}
disabled={user.id != undefined}
onChange={(e, { value }) => this.setState(s => ({
newUser: {
...s.newUser,
description: value,
},
}))} />
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button color='green'
loading={this.state.saveUserState.isInProgress()}
onClick={() => this.saveUser(user, true)}>
<Icon name='checkmark' />
Create
</Button>
</Modal.Actions>
</Modal>
)
}
private renderEditUserModal() {
const user = this.state.editUser
return (
<Modal size='small' centered={false}
open={this.state.editUser != null}
onClose={() => this.setState({ editUser: null })}
closeOnDimmerClick={false}
closeOnDocumentClick={false}
closeIcon>
<Header icon='key' content='Edit UGuard Key' />
<Modal.Content>
<Form size='small' >
<Form.Field>
<label>ID</label>
<input readOnly
value={user.id} />
</Form.Field>
<Form.Field>
<label>Phone</label>
<Form.Input required
value={user.phone}
onChange={(e, { value }) => this.setState(s => ({
editUser: {
...s.editUser,
phone: value,
},
}))} />
</Form.Field>
<Form.Field>
<label>Active Until</label>
<Form.Input type='date'
value={user.activeUntil ? moment(user.activeUntil).format('YYYY-MM-DD') : ''}
onChange={(e, { value }) => {
const activeUntil = moment(value).toDate()
this.setState(s => ({
editUser: {
...s.editUser,
activeUntil,
},
}))
}} />
</Form.Field>
<Form.Field>
<label>Description</label>
<Form.Input autoFocus
value={user.description || ''}
onChange={(e, { value }) => this.setState(s => ({
editUser: {
...s.editUser,
description: value,
},
}))} />
</Form.Field>
<Form.Field>
<label>Security Keys</label>
{user.uguardKeys.map((uguardKey, idx) => (
<Form.Input required
key={'uguardKey-' + idx}
value={uguardKey}
icon={
user.uguardKeys.length > 1
? <Icon name='trash' link
onClick={() => {
const editUser = { ...user }
editUser.uguardKeys = user.uguardKeys.slice()
editUser.uguardKeys.splice(idx, 1)
this.setState({ editUser })
}} />
: null
}
onChange={(e, { value }) => {
const editUser = { ...user }
editUser.uguardKeys = user.uguardKeys.slice()
editUser.uguardKeys[idx] = value
this.setState({ editUser })
}} />
))}
</Form.Field>
</Form>
</Modal.Content>
<Modal.Actions>
<Button color='green'
loading={this.state.saveUserState.isInProgress()}
onClick={() => this.saveUser(user, false)}>
<Icon name='checkmark' />
Save
</Button>
</Modal.Actions>
</Modal>
)
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment