diff --git a/backend/Dockerfile b/backend/Dockerfile index 57761e6..9696264 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -7,7 +7,7 @@ RUN go install ./... FROM alpine:latest -RUN apk --no-cache add ca-certificates +RUN apk --no-cache add ca-certificates tzdata WORKDIR /app COPY --from=builder /go/bin/app . COPY config.yaml config.yaml diff --git a/backend/README.md b/backend/README.md index e655e6f..cf345a0 100644 --- a/backend/README.md +++ b/backend/README.md @@ -13,3 +13,5 @@ SpaceAPI extensions =================== * Key `ext_ccc` describes Chaos Computer Club relation status (Example values: `"chaostreff"` or `"erfa"`) + + diff --git a/backend/calendar.go b/backend/calendar.go index 0653e02..06882f0 100644 --- a/backend/calendar.go +++ b/backend/calendar.go @@ -15,7 +15,7 @@ func getCalendars() { outputChan := parser.GetOutputChan() calendar := Calendar{} calendar.Space = spaceData.Space - events := []Event{} + var events []Event go func() { for event := range outputChan { events = append(events, mapEventObject(event)) diff --git a/backend/database.go b/backend/database.go index d23f53f..1f7ade9 100644 --- a/backend/database.go +++ b/backend/database.go @@ -98,7 +98,7 @@ func readSpaceurl() []SpaceUrl { return result } -func deleteSpaceurl(id string) { +func deleteSpaceurl(url string) { session, err := mgo.Dial(config.MongoDbServer) if err != nil { panic(err) @@ -108,7 +108,7 @@ func deleteSpaceurl(id string) { session.SetMode(mgo.Monotonic, true) c := session.DB(config.MongoDbDatabase).C("spaceurl") - c.Remove(bson.M{"_id": id}) + c.Remove(bson.M{"url": url}) } func readCalendar() []Calendar { diff --git a/backend/eva-backend.go b/backend/eva-backend.go index d095864..f050426 100644 --- a/backend/eva-backend.go +++ b/backend/eva-backend.go @@ -15,7 +15,10 @@ var config = ConfigFile{} func main() { data, _ := ioutil.ReadFile("config.yaml") - yaml.Unmarshal(data, &config) + err := yaml.Unmarshal(data, &config) + if err != nil { + panic("Can't load config") + } config.SharedSecret = os.Getenv("SHARED_SECRET") router := NewRouter() @@ -55,7 +58,7 @@ func SpaceUrlAdd(w http.ResponseWriter, r *http.Request) { func SpaceUrlUpdate(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) SharedSecret := vars["SharedSecret"] - if(SharedSecret == config.SharedSecret) { + if SharedSecret == config.SharedSecret { spaceUrl := SpaceUrl{} createEntry(&spaceUrl, w, r) updateSpaceurl(spaceUrl) @@ -65,9 +68,9 @@ func SpaceUrlUpdate(w http.ResponseWriter, r *http.Request) { func SpaceUrlDelete(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) SharedSecret := vars["SharedSecret"] - Id := vars["id"] + Url := vars["url"] if SharedSecret == config.SharedSecret { - deleteSpaceurl(Id) + deleteSpaceurl(Url) } } @@ -77,11 +80,10 @@ func loadSpaceData() { timestamp := time.Now().Unix() for _, spaceUrl := range spaceUrls { - if(spaceUrl.Validated && int64(spaceUrl.LastUpdated + 60) < timestamp) { + if spaceUrl.Validated && int64(spaceUrl.LastUpdated + 60) < timestamp { spaceData := SpaceData{} err := getJson(spaceUrl.Url, &spaceData) - if(err != nil) - { + if err != nil { log.Println(spaceUrl.Url) log.Println(err) } else { diff --git a/backend/event.go b/backend/event.go index b29c262..5af2209 100644 --- a/backend/event.go +++ b/backend/event.go @@ -18,6 +18,5 @@ type Event struct { Class string `json:"class"` Url string `json:"url"` Sequence int `json:"sequence"` - WholeDayEvent bool `json:"whileDayEvent"` - + WholeDayEvent bool `json:"wholeDayEvent"` } \ No newline at end of file diff --git a/backend/routes.go b/backend/routes.go index 7fa3972..763aed1 100644 --- a/backend/routes.go +++ b/backend/routes.go @@ -52,7 +52,7 @@ var IndexRoutes = Routes{ Route{ "SpaceUrlDelete", "DELETE", - "/urls/{id}/{SharedSecret}", + "/urls/{url}/{SharedSecret}", SpaceUrlDelete, }, Route{ diff --git a/docker-compose.yml b/docker-compose.yml index ca53cc8..055d709 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,12 +1,20 @@ version: "3" services: frontend: + build: ./frontend + networks: + spaceapi-network: + ipv4_address: 172.16.238.10 image: gidsi/spaceapi-ccc-frontend:latest restart: always + expose: + - "80" depends_on: - backend backend: build: ./backend + networks: + - spaceapi-network image: gidsi/spaceapi-ccc-backend:latest restart: always environment: @@ -15,6 +23,15 @@ services: - database database: image: mongo:latest + networks: + - spaceapi-network restart: always volumes: - /opt/eva:/data/db + +networks: + spaceapi-network: + ipam: + driver: default + config: + - subnet: 172.16.238.0/24 \ No newline at end of file diff --git a/frontend/README.md b/frontend/README.md index 2285286..8715d27 100644 --- a/frontend/README.md +++ b/frontend/README.md @@ -8,4 +8,6 @@ npm install -g yarn yarn install yarn start -``` \ No newline at end of file +``` + + diff --git a/frontend/nginx.conf b/frontend/nginx.conf index 7884af6..bd2f221 100644 --- a/frontend/nginx.conf +++ b/frontend/nginx.conf @@ -9,7 +9,7 @@ http { default_type application/octet-stream; sendfile on; - + keepalive_timeout 65; gzip on; @@ -44,16 +44,20 @@ http { proxy_set_header Connection 'upgrade'; proxy_set_header Host $host; proxy_cache_bypass $http_upgrade; + proxy_connect_timeout 600; + proxy_send_timeout 600; + proxy_read_timeout 600; + send_timeout 600; } - location /map/tiles/ { - proxy_set_header Host b.basemaps.cartocdn.com; - proxy_http_version 1.1; - proxy_set_header Connection ""; - proxy_pass http://osm/dark_all/; - proxy_cache osm; - proxy_cache_valid 7d; - expires 7d; - } + location /map/tiles/ { + proxy_set_header Host b.basemaps.cartocdn.com; + proxy_http_version 1.1; + proxy_set_header Connection ""; + proxy_pass http://osm/dark_all/; + proxy_cache osm; + proxy_cache_valid 7d; + expires 7d; + } } } diff --git a/frontend/package.json b/frontend/package.json index 9bfc04b..37e0e0f 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -3,28 +3,37 @@ "version": "1.0.0", "private": true, "dependencies": { - "leaflet": "^1.0.3", - "material-ui": "^0.17.4", - "moment": "^2.18.1", - "react": "^15.5.4", - "react-dom": "^15.5.4", - "react-leaflet": "^1.1.6", - "react-redux": "^5.0.4", - "react-router-dom": "^4.1.1", - "react-tap-event-plugin": "^2.0.1", - "redux": "^3.6.0", - "redux-actions": "^2.0.2", - "redux-thunk": "^2.2.0", - "rrule": "^2.2.0", - "superagent": "^3.5.2" + "@material-ui/core": "^3.9.3", + "@material-ui/icons": "^3.0.2", + "leaflet": "^1.4.0", + "moment": "^2.24.0", + "prop-types": "^15.7.2", + "react": "^16.8.6", + "react-dom": "^16.8.6", + "react-leaflet": "^2.2.1", + "react-redux": "^6.0.1", + "react-router-dom": "^5.0.0", + "react-tap-event-plugin": "^3.0.3", + "react-virtualized": "^9.21.0", + "redux": "^4.0.1", + "redux-actions": "^2.6.5", + "redux-thunk": "^2.3.0", + "rrule": "^2.6.0", + "superagent": "^5.0.2" }, "devDependencies": { - "react-scripts": "0.9.5" + "react-scripts": "2.1.8" }, "scripts": { "start": "react-scripts start", "build": "react-scripts build", "test": "react-scripts test --env=jsdom", "eject": "react-scripts eject" - } + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] } diff --git a/frontend/src/App.js b/frontend/src/App.js index 5c0c757..0dcb1aa 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -3,10 +3,8 @@ import { BrowserRouter as Router, Route } from 'react-router-dom'; -import injectTapEventPlugin from 'react-tap-event-plugin'; import { Provider } from 'react-redux'; -import MuiThemeProvider from 'material-ui/styles/MuiThemeProvider'; -import getMuiTheme from 'material-ui/styles/getMuiTheme'; +import { MuiThemeProvider } from '@material-ui/core/styles'; import theme from './style/theme'; import store from './redux/store'; import IndexContainer from './views/Index'; @@ -14,10 +12,9 @@ import SpaceList from './views/SpaceList'; import UrlListView from './views/UrlListView'; import layout from './layout'; -injectTapEventPlugin(); const App = () => ( - +
diff --git a/frontend/src/components/EventList.jsx b/frontend/src/components/EventList.jsx index 2b27079..3ab9050 100644 --- a/frontend/src/components/EventList.jsx +++ b/frontend/src/components/EventList.jsx @@ -1,26 +1,27 @@ -import React from 'react'; +/* import React from 'react'; import { connect } from 'react-redux'; -import { Table, TableBody, TableRow, TableRowColumn } - from 'material-ui/Table'; -import InfoIcon from 'material-ui/svg-icons/action/info-outline'; +// import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableRow from '@material-ui/core/TableRow'; +import TableCell from '@material-ui/core/TableCell'; +import PropTypes from 'prop-types'; +import { AutoSizer, Column, SortDirection, Table } from 'react-virtualized'; +import InfoIcon from '@material-ui/icons/InfoOutlined'; import { actions as calendarActions, eventStruct } from '../redux/modules/calendar'; -import { spacedataStruct } from '../redux/modules/spacedata'; +import {actions as spaceDataActions, spacedataStruct} from '../redux/modules/spacedata'; + -const mapStateToProps = state => ({ - events: state.calendars.items, - spacedata: state.spacedata, -}); class EventList extends React.Component { static propTypes = { - events: React.PropTypes.arrayOf( - React.PropTypes.shape(eventStruct), + events: PropTypes.arrayOf( + PropTypes.shape(eventStruct), ), - fetchCalendars: React.PropTypes.func, + fetchCalendars: PropTypes.func, spacedata: spacedataStruct, }; - defaultProps = { + static defaultProps = { events: [], }; @@ -33,15 +34,8 @@ class EventList extends React.Component { render() { return ( - - +
{}}> + {this.props.events .filter(event => ( @@ -53,23 +47,23 @@ class EventList extends React.Component { - + {this.formatDate(event.start)} - - + + {event.wholeDayEvent ? null : this.formatTime(event.start)} - - + + {event.summary || event.description} - - + + {event.space} - - + + {event.url && } - + ))} @@ -78,6 +72,223 @@ class EventList extends React.Component { } } -export default connect(mapStateToProps, { +export default connect(mapStateToProps, mapDispatchToProps)(EventList); +*/ + +import React from 'react'; +import { connect } from 'react-redux'; +import TableCell from '@material-ui/core/TableCell'; +import PropTypes from 'prop-types'; +import InfoIcon from '@material-ui/icons/InfoOutlined'; +import { actions as calendarActions, eventStruct } from '../redux/modules/calendar'; +import {actions as spaceDataActions, spacedataStruct} from '../redux/modules/spacedata'; +import classNames from 'classnames'; +import { withStyles } from '@material-ui/core/styles'; +import { AutoSizer, Column, Table } from 'react-virtualized'; + +const styles = theme => ({ + table: { + fontFamily: theme.typography.fontFamily, + }, + flexContainer: { + display: 'flex', + alignItems: 'center', + boxSizing: 'border-box', + }, + tableRow: { + border: 0, + }, + tableRowHover: { + '&:hover': { + backgroundColor: theme.palette.grey[600], + }, + }, + tableCell: { + flex: 1, + color: '#fff', + }, + noClick: { + cursor: 'initial', + }, +}); + +class MuiVirtualizedTable extends React.PureComponent { + getRowClassName = ({ index }) => { + const { classes, rowClassName, onRowClick } = this.props; + + return classNames(classes.tableRow, classes.flexContainer, rowClassName, { + [classes.tableRowHover]: index !== -1 && onRowClick != null, + }); + }; + + cellRenderer = ({ cellData, columnIndex = null }) => { + const { columns, classes, rowHeight, onRowClick } = this.props; + return ( + + {cellData} + + ); + }; + + render() { + const { classes, columns, ...tableProps } = this.props; + return ( + + {({ height, width }) => ( +
+ {columns.map(({ cellContentRenderer = null, className, dataKey, ...other }, index) => { + let renderer; + if (cellContentRenderer != null) { + renderer = cellRendererProps => + this.cellRenderer({ + cellData: cellContentRenderer(cellRendererProps), + columnIndex: index, + }); + } else { + renderer = this.cellRenderer; + } + + return ( + {}} + className={classNames(classes.flexContainer, className)} + cellRenderer={renderer} + dataKey={dataKey} + {...other} + /> + ); + })} +
+ )} + + ); + } +} + +MuiVirtualizedTable.propTypes = { + classes: PropTypes.object.isRequired, + columns: PropTypes.arrayOf( + PropTypes.shape({ + cellContentRenderer: PropTypes.func, + dataKey: PropTypes.string.isRequired, + width: PropTypes.number.isRequired, + }), + ).isRequired, + headerHeight: PropTypes.number, + onRowClick: PropTypes.func, + rowClassName: PropTypes.string, + rowHeight: PropTypes.oneOfType([PropTypes.number, PropTypes.func]), + sort: PropTypes.func, +}; + +MuiVirtualizedTable.defaultProps = { + rowHeight: 40, + headerHeight: 0, +}; + +const WrappedVirtualizedTable = withStyles(styles)(MuiVirtualizedTable); + +const mapStateToProps = state => ({ + events: state.calendars.items, + spacedata: state.spacedata, +}); + +const mapDispatchToProps = { ...calendarActions, -})(EventList); + ...spaceDataActions, +}; + + +class EventList extends React.Component { + static propTypes = { + events: PropTypes.arrayOf( + PropTypes.shape(eventStruct), + ), + fetchCalendars: PropTypes.func, + spacedata: spacedataStruct, + }; + + static defaultProps = { + events: [], + }; + + componentWillMount() { + this.props.fetchCalendars(); + } + + formatDate = date => (date.format('DD.MM.YYYY')); + formatTime = date => (date.format('HH:mm')); + + render() { + + const rows = this.props.events.filter(event => + ( + this.props.spacedata.filter.indexOf(event.space) !== -1 + || this.props.spacedata.filter.length === 0 + ) + ).map(event => { + return { + ...event, + start_date: this.formatDate(event.start), + start_time: this.formatTime(event.start), + summary: event.summary || event.description, + link: event.url && + + , + } + }); + + return ( + rows[index]} + columns={[ + { + width: 120, + label: 'Date', + dataKey: 'start_date', + }, + { + width: 120, + label: 'Time', + dataKey: 'start_time', + }, + { + width: 120, + flexGrow: 1, + label: 'Summary', + dataKey: 'summary', + }, + { + width: 120, + flexGrow: 1, + label: 'Carbs (g)', + dataKey: 'space', + }, + { + width: 120, + label: 'Protein (g)', + dataKey: 'link', + }, + ]} + /> + ); + } +} + +export default connect(mapStateToProps, mapDispatchToProps)(EventList); diff --git a/frontend/src/components/Map.jsx b/frontend/src/components/Map.jsx index 841d816..c0a8b2a 100644 --- a/frontend/src/components/Map.jsx +++ b/frontend/src/components/Map.jsx @@ -1,4 +1,5 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { Map as LeafletMap, TileLayer } from 'react-leaflet'; import { connect } from 'react-redux'; import Marker from './Marker'; @@ -15,8 +16,8 @@ const mapDispatchToProps = { class Map extends React.Component { static propTypes = { spacedata: spacedataStruct.isRequired, - fetchSpacedata: React.PropTypes.func.isRequired, - toggleFilterSpacedata: React.PropTypes.func.isRequired, + fetchSpacedata: PropTypes.func.isRequired, + toggleFilterSpacedata: PropTypes.func.isRequired, }; componentWillMount() { @@ -29,7 +30,7 @@ class Map extends React.Component { ) diff --git a/frontend/src/components/Marker.jsx b/frontend/src/components/Marker.jsx index 9fb89ae..3633038 100644 --- a/frontend/src/components/Marker.jsx +++ b/frontend/src/components/Marker.jsx @@ -1,10 +1,11 @@ import React from 'react'; +import PropTypes from 'prop-types'; import { CircleMarker, Popup } from 'react-leaflet'; import theme from '../style/theme'; import { spacedataElementStruct } from '../redux/modules/spacedata'; const Marker = (props) => { - const color = props.highlight ? theme.palette.accent2Color : theme.palette.primary1Color; + const color = props.highlight ? theme.palette.secondary.light : theme.palette.primary.main; const style = { container: { @@ -23,7 +24,10 @@ const Marker = (props) => { radius={5} center={[props.spacedata.location.lat, props.spacedata.location.lon]} > - + props.toggleFilterSpacedata(props.spacedata.space)} + onClose={() => props.toggleFilterSpacedata(props.spacedata.space)} + >
{props.spacedata.space} @@ -40,7 +44,8 @@ const Marker = (props) => { Marker.propTypes = { spacedata: spacedataElementStruct.isRequired, - highlight: React.PropTypes.bool.isRequired, + highlight: PropTypes.bool.isRequired, + toggleFilterSpacedata: PropTypes.func.isRequired, }; export default Marker; diff --git a/frontend/src/components/SpaceApiInput.jsx b/frontend/src/components/SpaceApiInput.jsx index 788c6a5..4001453 100644 --- a/frontend/src/components/SpaceApiInput.jsx +++ b/frontend/src/components/SpaceApiInput.jsx @@ -1,14 +1,15 @@ import React from 'react'; import request from 'superagent'; -import TextField from 'material-ui/TextField'; -import FloatingActionButton from 'material-ui/FloatingActionButton'; -import ContentAdd from 'material-ui/svg-icons/content/add'; -import Snackbar from 'material-ui/Snackbar'; +import TextField from '@material-ui/core/TextField'; +import FloatingActionButton from '@material-ui/core/Fab'; +import ContentAdd from '@material-ui/icons/AddOutlined'; +import Snackbar from '@material-ui/core/Snackbar'; +import PropTypes from 'prop-types'; import config from '../api/config'; class SpaceApiInput extends React.Component { static propTypes = { - style: React.PropTypes.shape({}), + style: PropTypes.shape({}), }; static defaultProps = { diff --git a/frontend/src/components/SpaceList.jsx b/frontend/src/components/SpaceList.jsx index e3d610d..2a90bdd 100644 --- a/frontend/src/components/SpaceList.jsx +++ b/frontend/src/components/SpaceList.jsx @@ -1,15 +1,14 @@ import React from 'react'; -import { - Table, - TableBody, - TableRow, - TableRowColumn, -} from 'material-ui/Table'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableRow from '@material-ui/core/TableRow'; +import TableCell from '@material-ui/core/TableCell'; +import PropTypes from 'prop-types'; import { spacedataStruct } from '../redux/modules/spacedata'; export class SpaceList extends React.Component { static propTypes = { - fetchSpacedata: React.PropTypes.func.isRequired, + fetchSpacedata: PropTypes.func.isRequired, spacedata: spacedataStruct, }; @@ -28,34 +27,29 @@ export class SpaceList extends React.Component { (a, b) => a.space.toUpperCase().localeCompare(b.space.toUpperCase()) ); return ( - - - {items - .map(space => ( - - - {space.space} - - - - {space.url} - - - - ) - )} - -
+
+ + + {items + .map(space => ( + + + {space.space} + + + + {space.url} + + + + ) + )} + +
+
); } } diff --git a/frontend/src/components/Toolbar.jsx b/frontend/src/components/Toolbar.jsx index a627ddd..15cc22a 100644 --- a/frontend/src/components/Toolbar.jsx +++ b/frontend/src/components/Toolbar.jsx @@ -1,43 +1,93 @@ import React from 'react'; -import { - Toolbar as MuiToolbar, - ToolbarGroup, - ToolbarTitle, -} from 'material-ui/Toolbar'; -import IconMenu from 'material-ui/IconMenu'; -import IconButton from 'material-ui/IconButton'; -import NavigationExpandMoreIcon from 'material-ui/svg-icons/navigation/expand-more'; -import MenuItem from 'material-ui/MenuItem'; +import Toolbar from '@material-ui/core/Toolbar'; +import AppBar from '@material-ui/core/AppBar'; +import Typography from '@material-ui/core/Typography'; +import IconButton from '@material-ui/core/IconButton'; +import ClickAwayListener from '@material-ui/core/ClickAwayListener'; +import Grow from '@material-ui/core/Grow'; +import Paper from '@material-ui/core/Paper'; +import Popper from '@material-ui/core/Popper'; +import MenuItem from '@material-ui/core/MenuItem'; +import MenuList from '@material-ui/core/MenuList'; +import MenuIcon from '@material-ui/icons/Menu'; import { Link } from 'react-router-dom'; -const Toolbar = () => ( - - - - - - - } - > - } - /> - } - /> - - - - -); -export default Toolbar; +class MyToolbar extends React.Component { + state = { + open: false, + }; + + handleToggle = () => { + this.setState(state => ({ open: !state.open })); + }; + + handleClose = event => { + if (this.anchorEl.contains(event.target)) { + return; + } + + this.setState({ open: false }); + }; + + render() { + return ( + + +
+ + CCC Spaces + +
+ { + this.anchorEl = node; + }} + aria-owns={this.state.open ? 'menu-list-grow' : undefined} + aria-haspopup="true" + onClick={this.handleToggle} + > + + + + {({ TransitionProps, placement }) => ( + + + + + window.location.href = '/'} + containerElement={} + > + Events + + window.location.href = '/list'} + containerElement={} + > + Spaces + + window.location.href = 'http://ccc.de/de/imprint'} + > + Impressum + + + + + + )} + +
+
+
+
+ ); + } +} + +export default MyToolbar; diff --git a/frontend/src/components/UrlList.jsx b/frontend/src/components/UrlList.jsx index 2af23a7..bc0673f 100644 --- a/frontend/src/components/UrlList.jsx +++ b/frontend/src/components/UrlList.jsx @@ -1,19 +1,19 @@ import React from 'react'; -import { - Table, - TableBody, - TableRow, - TableRowColumn, -} from 'material-ui/Table'; -import TextField from 'material-ui/TextField'; -import FlatButton from 'material-ui/FlatButton'; +import Table from '@material-ui/core/Table'; +import TableBody from '@material-ui/core/TableBody'; +import TableRow from '@material-ui/core/TableRow'; +import TableCell from '@material-ui/core/TableCell'; +import TextField from '@material-ui/core/TextField'; +import PropTypes from 'prop-types'; +import Button from '@material-ui/core/Button'; import moment from 'moment'; import { spaceUrlStruct } from '../redux/modules/spaceurl'; export class UrlList extends React.Component { static propTypes = { - fetchSpaceUrl: React.PropTypes.func.isRequired, - validateSpaceUrl: React.PropTypes.func.isRequired, + fetchSpaceUrl: PropTypes.func.isRequired, + validateSpaceUrl: PropTypes.func.isRequired, + deleteSpaceUrl: PropTypes.func.isRequired, spaceurls: spaceUrlStruct, }; @@ -38,42 +38,46 @@ export class UrlList extends React.Component { url: spaceUrl.url, validated: true, }; - this.props.validateSpaceUrl(validatedSpaceUrl, this.secretInput.input.value); + this.props.validateSpaceUrl(validatedSpaceUrl, this.state.secret); + }; + + deleteSpaceUrl = (spaceUrl) => { + this.props.deleteSpaceUrl(spaceUrl.url, this.state.secret); }; render() { return (
- - +
+ {this.props.spaceurls.items .map(spaceurl => ( - + {spaceurl.url} - - + + {this.getFormatedDateTime(spaceurl.lastUpdated)} - - - {!spaceurl.validated ? this.validateSpaceUrl(spaceurl)} + + + {!spaceurl.validated ? : null} + + + + ) )} @@ -81,6 +85,7 @@ export class UrlList extends React.Component {
this.setState({ secret: event.target.value })} ref={ref => (this.secretInput = ref)} />
diff --git a/frontend/src/redux/modules/calendar.js b/frontend/src/redux/modules/calendar.js index ab8766f..37637cf 100644 --- a/frontend/src/redux/modules/calendar.js +++ b/frontend/src/redux/modules/calendar.js @@ -1,4 +1,4 @@ -import { PropTypes } from 'react'; +import PropTypes from 'prop-types'; import request from 'superagent'; import flatten from 'lodash/flatten'; import moment from 'moment'; @@ -35,6 +35,9 @@ export const actions = { fetchCalendars, }; +const formatDate = date => (date.format('DD.MM.YYYY')); +const formatTime = date => (date.format('HH:mm')); + export default handleActions({ [CALENDARS_FETCHED]: (state, { payload }) => { const items = flatten(flatten( @@ -60,7 +63,7 @@ export default handleActions({ )); } catch (ex) { - console.log(ex); + console.log(ex, event); return []; } } diff --git a/frontend/src/redux/modules/spacedata.js b/frontend/src/redux/modules/spacedata.js index 9c66379..bf3c8ab 100644 --- a/frontend/src/redux/modules/spacedata.js +++ b/frontend/src/redux/modules/spacedata.js @@ -1,4 +1,4 @@ -import { PropTypes } from 'react'; +import PropTypes from 'prop-types'; import request from 'superagent'; import { createAction, handleActions } from 'redux-actions'; import config from '../../api/config'; diff --git a/frontend/src/redux/modules/spaceurl.js b/frontend/src/redux/modules/spaceurl.js index ea9350a..209524a 100644 --- a/frontend/src/redux/modules/spaceurl.js +++ b/frontend/src/redux/modules/spaceurl.js @@ -1,4 +1,4 @@ -import { PropTypes } from 'react'; +import PropTypes from 'prop-types'; import request from 'superagent'; import { createAction, handleActions } from 'redux-actions'; import config from '../../api/config'; @@ -14,9 +14,11 @@ export const spaceUrlStruct = PropTypes.shape({ const SPACEURL_FETCHED = 'SPACEURL_FETCHED'; const SPACEURL_VALIDATE = 'SPACEURL_VALIDATE'; +const SPACEURL_DELETE = 'SPACEURL_DELETE'; export const fetched = createAction(SPACEURL_FETCHED, result => result); export const validate = createAction(SPACEURL_VALIDATE, result => result); +export const deleteSpace = createAction(SPACEURL_DELETE, result => result); export const fetchSpaceUrl = () => (dispatch) => { request @@ -45,9 +47,23 @@ export const validateSpaceUrl = (spaceUrl, secret) => (dispatch) => { ); }; +export const deleteSpaceUrl = (spaceUrl, secret) => (dispatch) => { + request + .delete(`${config.api.url}/urls/spaceUrl/${secret}`) + .set('Content-Type', 'application/json') + .end( + (err) => { + if (!err) { + dispatch(deleteSpace(spaceUrl)); + } + } + ); +}; + export const actions = { fetchSpaceUrl, validateSpaceUrl, + deleteSpaceUrl, }; export default handleActions({ @@ -67,4 +83,5 @@ export default handleActions({ return newState; }, + [SPACEURL_DELETE]: (state, { payload }) => state.items.filter(ele => ele.url != payload.url), }, { items: [] }); diff --git a/frontend/src/style/theme.js b/frontend/src/style/theme.js index f0ad75c..b7177ee 100644 --- a/frontend/src/style/theme.js +++ b/frontend/src/style/theme.js @@ -1,23 +1,23 @@ -import { fade } from 'material-ui/utils/colorManipulator'; -import { spacing, colors } from 'material-ui/styles'; +import grey from '@material-ui/core/colors/grey'; +import green from '@material-ui/core/colors/green'; +import red from '@material-ui/core/colors/red'; +import { createMuiTheme } from '@material-ui/core/styles'; -export default { - spacing, - fontFamily: 'Roboto, sans-serif', - palette: { - primary1Color: colors.grey900, - primary2Color: colors.grey900, - primary3Color: colors.grey600, - accent1Color: colors.grey500, - accent2Color: colors.grey500, - accent3Color: colors.grey100, - textColor: colors.fullWhite, - secondaryTextColor: fade(colors.fullWhite, 0.7), - alternateTextColor: '#303030', - canvasColor: '#303030', - borderColor: fade(colors.fullWhite, 0.3), - disabledColor: fade(colors.fullWhite, 0.3), - pickerHeaderColor: fade(colors.fullWhite, 0.12), - clockCircleColor: fade(colors.fullWhite, 0.12), +export default createMuiTheme({ + typography: { + fontFamily: 'Roboto, sans-serif', + useNextVariants: true, }, -}; + palette: { + primary: grey, + secondary: green, + error: red, + // Used by `getContrastText()` to maximize the contrast between the background and + // the text. + contrastThreshold: 3, + // Used to shift a color's luminance by approximately + // two indexes within its tonal palette. + // E.g., shift from Red 500 to Red 300 or Red 700. + tonalOffset: 0.2, + }, +}); diff --git a/frontend/src/views/Index.jsx b/frontend/src/views/Index.jsx index f83040b..f5274a6 100644 --- a/frontend/src/views/Index.jsx +++ b/frontend/src/views/Index.jsx @@ -6,14 +6,14 @@ const style = { container: { display: 'flex', flexDirection: 'column', - alignItems: 'center', - justifyContent: 'center', }, }; const IndexContainer = () => (
- +
+ +
);