From 46b9df3279f51479cfc607cbce8fb8b73bef69f7 Mon Sep 17 00:00:00 2001 From: Ernst Widerberg Date: Wed, 6 Oct 2021 16:11:06 +0200 Subject: Initial commit --- src/components/App.js | 83 ++++++++++++++++++++++++ src/components/Error.js | 33 ++++++++++ src/components/Header.js | 32 +++++++++ src/components/List.js | 73 +++++++++++++++++++++ src/components/Login.js | 96 +++++++++++++++++++++++++++ src/components/ObjectComponent.js | 69 ++++++++++++++++++++ src/components/ObjectView.js | 46 +++++++++++++ src/components/SearchForm.js | 72 +++++++++++++++++++++ src/index.html | 17 +++++ src/index.js | 6 ++ src/styles/main.css | 133 ++++++++++++++++++++++++++++++++++++++ 11 files changed, 660 insertions(+) create mode 100644 src/components/App.js create mode 100644 src/components/Error.js create mode 100644 src/components/Header.js create mode 100644 src/components/List.js create mode 100644 src/components/Login.js create mode 100644 src/components/ObjectComponent.js create mode 100644 src/components/ObjectView.js create mode 100644 src/components/SearchForm.js create mode 100644 src/index.html create mode 100644 src/index.js create mode 100644 src/styles/main.css (limited to 'src') diff --git a/src/components/App.js b/src/components/App.js new file mode 100644 index 0000000..3a1d65d --- /dev/null +++ b/src/components/App.js @@ -0,0 +1,83 @@ +import React from "react"; +import { + BrowserRouter as Router, + Switch, + Route, + Link, + useParams +} from "react-router-dom"; +import { Button } from "semantic-ui-react"; + +import Error from "./Error"; +import Header from "./Header"; +import List from "./List"; +import Login from "./Login"; +import ObjectView from "./ObjectView"; + +import "../styles/main.css"; + +class App extends React.Component { + constructor(props) { + super(props); + this.state = { + token: localStorage.getItem("token"), + error: null + }; + + this.clearError = this.clearError.bind(this); + this.clearToken = this.clearToken.bind(this); + this.setError = this.setError.bind(this); + this.setToken = this.setToken.bind(this); + } + + setToken(token) { + this.setState({ token: token }); + localStorage.setItem("token", token); + } + + clearToken() { + this.setState({ token: null }); + localStorage.removeItem("token"); + } + + setError(msg) { + this.setState({ error: msg }); + } + + clearError() { + this.setState({ error: null }); + } + + render() { + if (this.state.error !== null) + return ( + + ); + // if (this.state.token === null) + // return ; + return ( + +
+ + + + + + + + + + ); + } +} + +function MakeObjectView() { + let { id } = useParams(); + return ; +} + +export default App; diff --git a/src/components/Error.js b/src/components/Error.js new file mode 100644 index 0000000..56cc09c --- /dev/null +++ b/src/components/Error.js @@ -0,0 +1,33 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Button, Message } from "semantic-ui-react"; + +class Error extends React.Component { + static propTypes = { + clearError: PropTypes.func.isRequired, + clearToken: PropTypes.func.isRequired, + error: PropTypes.string.isRequired + }; + + render() { + return ( +
+ + Internal server error +

{this.props.error}

+ +
+
+ ); + } +} + +export default Error; diff --git a/src/components/Header.js b/src/components/Header.js new file mode 100644 index 0000000..72234b6 --- /dev/null +++ b/src/components/Header.js @@ -0,0 +1,32 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Button } from "semantic-ui-react"; +import { Link } from "react-router-dom"; + +class Header extends React.Component { + static propTypes = { + clearToken: PropTypes.func.isRequired + }; + + render() { + return ( + + ); + } +} + +export default Header; diff --git a/src/components/List.js b/src/components/List.js new file mode 100644 index 0000000..5f12aa0 --- /dev/null +++ b/src/components/List.js @@ -0,0 +1,73 @@ +import React from "react"; +import { Button } from "semantic-ui-react"; + +import ObjectComponent from "./ObjectComponent"; +import SearchForm from "./SearchForm"; + +class List extends React.Component { + constructor(props) { + super(props); + this.state = { + objects: [], + filter: { + field: "default-field", + value: "" + } + }; + + this.getData = this.getData.bind(this); + } + + componentDidMount() { + this.getData(); + } + + // Fetch data from external source, update state + getData() { + fetch("http://localhost:8000/sc/v0/get", { + headers: { + Authorization: "Basic " + btoa("user1:pw1") + } + }) + .then(resp => resp.json()) + .then(data => this.setState({ objects: data })); + } + + filter(field, value) { + this.setState( + { + filter: { + field: field, + value: value + } + }, + this.getData + ); + } + + render() { + return ( +
+
+
+ + + + +
+ +
+
+ {this.state.objects.map(data => { + console.log(data); + return ; + })} +
+
+ ); + } +} + +export default List; diff --git a/src/components/Login.js b/src/components/Login.js new file mode 100644 index 0000000..7555dcf --- /dev/null +++ b/src/components/Login.js @@ -0,0 +1,96 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Button, Input } from "semantic-ui-react"; + +class Login extends React.Component { + static propTypes = { + setToken: PropTypes.func.isRequired, + setError: PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + + this.state = { + email: "", + password: "", + error: false + }; + + this.handleInput = this.handleInput.bind(this); + this.login = this.login.bind(this); + this.logout = this.logout.bind(this); + } + + // NOTE: btoa() limits email, password to ASCII + login(email, password) { + const url = process.env.JWT_URL + "/api/v1.0/auth"; + fetch(url, { + method: "POST", + headers: { Authorization: "Basic " + btoa(email + ":" + password) } + }) + .then(resp => { + if (resp.status !== 200) throw resp; + return resp; + }) + .then(resp => resp.json()) + .then(data => { + this.props.setToken(data.access_token); + }) + .catch(resp => { + if (resp.status === 401) this.setState({ error: true }); + else + this.props.setError( + `Unexpected response status: ${resp.status} ${resp.statusText}` + ); + }); + } + + logout() { + localStorage.removeItem("token"); + } + + handleInput(e) { + this.setState({ + [e.target.name]: e.target.value + }); + } + + render() { + return ( +
+
{ + e.preventDefault(); + this.login(this.state.email, this.state.password); + }} + > +

+ + + + {this.state.error && ( +

Wrong username or password

+ )} +
+
+ ); + } +} + +export default Login; diff --git a/src/components/ObjectComponent.js b/src/components/ObjectComponent.js new file mode 100644 index 0000000..56bdc7c --- /dev/null +++ b/src/components/ObjectComponent.js @@ -0,0 +1,69 @@ +import React from "react"; + +class ObjectComponent extends React.Component { + render() { + console.log(this.props); + let { user_presentation, ...rest } = this.props; + return ( +
+

+ Scan {this.props._id} +

+ + +
+ ); + } +} + +function GenericTable(props) { + return ( + + + {Object.entries(props.data).map(([key, value]) => { + return ( + + + + + ); + })} + +
{key}{value}
+ ); +} + +function UserPresentation(props) { + return ( +
+
Scanner-unique data
+ {props.description && ( +
{props.description}
+ )} + + + {Object.entries(props.data).map( + ([key, { data, display_name, description }]) => { + return ( + + + {description && ( + + )} + + + ); + } + )} + +
{display_name} + {description} + {data.toString()}
+
+ ); +} + +export default ObjectComponent; diff --git a/src/components/ObjectView.js b/src/components/ObjectView.js new file mode 100644 index 0000000..4a04c93 --- /dev/null +++ b/src/components/ObjectView.js @@ -0,0 +1,46 @@ +import React from "react"; + +import ObjectComponent from "./ObjectComponent"; + +class ObjectView extends React.Component { + constructor(props) { + super(props); + this.state = { + object: null + }; + + this.getData = this.getData.bind(this); + } + + componentDidMount() { + this.getData(); + } + + getData() { + fetch("http://localhost:8000/sc/v0/get", { + headers: { + Authorization: "Basic " + btoa("user1:pw1") + } + }) + .then(resp => resp.json()) + // TODO: Proper API call to get single object + .then(data => data.filter(x => x._id == this.props.id)[0]) + // .then(data => { + // console.log(data); + // return data; + // }) + .then(object => this.setState({ object: object })); + } + + render() { + return ( +
+ {this.state.object === null ? null : ( + + )} +
+ ); + } +} + +export default ObjectView; diff --git a/src/components/SearchForm.js b/src/components/SearchForm.js new file mode 100644 index 0000000..0dc288c --- /dev/null +++ b/src/components/SearchForm.js @@ -0,0 +1,72 @@ +import React from "react"; +import PropTypes from "prop-types"; +import { Button, Select, Input, Icon } from "semantic-ui-react"; + +class SearchForm extends React.Component { + static propTypes = { + filter: PropTypes.func.isRequired + }; + + constructor(props) { + super(props); + this.state = { + field: "default-field", + value: "" + }; + + this.clearSearch = this.clearSearch.bind(this); + this.handleInput = this.handleInput.bind(this); + this.submitSearch = this.submitSearch.bind(this); + } + + handleInput(e) { + this.setState({ + [e.target.name]: e.target.value + }); + } + + clearSearch(_) { + this.setState({ value: "" }); + this.props.filter(null, null); + } + + submitSearch(e) { + e.preventDefault(); + this.props.filter(this.state.field, this.state.value); + } + + render() { + const searchOptions = [ + { + key: "default-field", + value: "default-field", + text: "Default field" + } + ]; + return ( +
+ + + +