From 7ab7e4918e3f15927e976b1de3dfcdb111468dbc Mon Sep 17 00:00:00 2001 From: Paul Bienkowski Date: Fri, 26 Feb 2021 21:57:36 +0100 Subject: [PATCH] frontend: Track editor and redirects on errors --- frontend/src/App.js | 12 +- frontend/src/api.js | 2 + frontend/src/pages/HomePage.js | 15 +-- frontend/src/pages/SettingsPage.tsx | 4 +- frontend/src/pages/TrackEditor.tsx | 154 +++++++++++++++++++++++++ frontend/src/pages/TrackPage/index.tsx | 61 +++++++--- frontend/src/pages/TracksPage.tsx | 34 +++--- frontend/src/pages/index.js | 1 + frontend/src/utils.js | 9 ++ 9 files changed, 248 insertions(+), 44 deletions(-) create mode 100644 frontend/src/pages/TrackEditor.tsx create mode 100644 frontend/src/utils.js diff --git a/frontend/src/App.js b/frontend/src/App.js index ca2e07d..5b6fc5f 100644 --- a/frontend/src/App.js +++ b/frontend/src/App.js @@ -11,6 +11,7 @@ import { LogoutPage, NotFoundPage, SettingsPage, + TrackEditor, TrackPage, TracksPage, UploadPage, @@ -40,7 +41,7 @@ const App = connect((state) => ({login: state.login}))(function App({login}) { Home
  • - Tracks + Tracks
  • @@ -76,15 +77,18 @@ const App = connect((state) => ({login: state.login}))(function App({login}) { - + - - + + + + + diff --git a/frontend/src/api.js b/frontend/src/api.js index e8f6f1e..e28fa3b 100644 --- a/frontend/src/api.js +++ b/frontend/src/api.js @@ -198,6 +198,8 @@ class API { if (response.status === 200) { return json + } else if (response.status === 204) { + return null } else { throw new RequestError('Error code ' + response.status, json?.errors) } diff --git a/frontend/src/pages/HomePage.js b/frontend/src/pages/HomePage.js index bc88bd2..e83c9cb 100644 --- a/frontend/src/pages/HomePage.js +++ b/frontend/src/pages/HomePage.js @@ -1,9 +1,9 @@ -import _ from 'lodash' import React from 'react' +import {Link} from 'react-router-dom' import {Message, Grid, Loader, Statistic, Segment, Header, Item} from 'semantic-ui-react' import {useObservable} from 'rxjs-hooks' -import {of, pipe, from} from 'rxjs' -import {map, switchMap, distinctUntilChanged} from 'rxjs/operators' +import {of, from} from 'rxjs' +import {map, switchMap} from 'rxjs/operators' import {fromLonLat} from 'ol/proj' import {Duration} from 'luxon' @@ -30,10 +30,9 @@ function WelcomeMap() { function Stats() { const stats = useObservable( - pipe( - distinctUntilChanged(_.isEqual), + () => of(null).pipe( switchMap(() => api.fetch('/stats')) - ) + ), ) return ( @@ -82,7 +81,9 @@ function MostRecentTrack() {

    Most recent track

    {track === undefined ? ( - No track uploaded yet. Be the first! + + No track uploaded yet. Be the first! + ) : track ? ( diff --git a/frontend/src/pages/SettingsPage.tsx b/frontend/src/pages/SettingsPage.tsx index a0ded29..d43024c 100644 --- a/frontend/src/pages/SettingsPage.tsx +++ b/frontend/src/pages/SettingsPage.tsx @@ -6,10 +6,8 @@ import {useForm} from 'react-hook-form' import {setLogin} from 'reducers/login' import {Page} from 'components' import api from 'api' +import {findInput} from 'utils' -function findInput(register) { - return (element) => register(element ? element.querySelector('input, textarea, select') : null) -} const SettingsPage = connect((state) => ({login: state.login}), {setLogin})(function SettingsPage({login, setLogin}) { const {register, handleSubmit} = useForm() diff --git a/frontend/src/pages/TrackEditor.tsx b/frontend/src/pages/TrackEditor.tsx new file mode 100644 index 0000000..e361a69 --- /dev/null +++ b/frontend/src/pages/TrackEditor.tsx @@ -0,0 +1,154 @@ +import React from 'react' +import _ from 'lodash' +import {connect} from 'react-redux' +import {Confirm, Grid, Button, Icon, Popup, Form, Ref, TextArea, Checkbox} from 'semantic-ui-react' +import {useHistory, useParams} from 'react-router-dom' +import {concat, of, from} from 'rxjs' +import {pluck, distinctUntilChanged, map, switchMap} from 'rxjs/operators' +import {useObservable} from 'rxjs-hooks' +import {findInput} from 'utils' +import {useForm, Controller} from 'react-hook-form' + +import api from 'api' +import {Page} from 'components' +import type {Track} from 'types' + +const TrackEditor = connect((state) => ({login: state.login}))(function TrackEditor({login}) { + const [busy, setBusy] = React.useState(false) + const {register, control, handleSubmit} = useForm() + const {slug} = useParams() + const history = useHistory() + + const track: null | Track = useObservable( + (_$, args$) => { + const slug$ = args$.pipe(pluck(0), distinctUntilChanged()) + return slug$.pipe( + map((slug) => `/tracks/${slug}`), + switchMap((url) => concat(of(null), from(api.get(url)))), + pluck('track') + ) + }, + null, + [slug] + ) + + const loading = busy || track == null + const isAuthor = login?.username === track?.author?.username + + // Navigate to track detials if we are not the author + React.useEffect(() => { + if (!login || (track && !isAuthor)) { + history.replace(`/tracks/${slug}`) + } + }, [slug, login, track, isAuthor, history]) + + const onSubmit = React.useMemo( + () => + handleSubmit(async (values) => { + setBusy(true) + + try { + await api.put(`/tracks/${slug}`, {body: {track: _.pickBy(values, (v) => typeof v !== 'undefined')}}) + history.push(`/tracks/${slug}`) + } finally { + setBusy(false) + } + }), + [slug, handleSubmit, history] + ) + + const [confirmDelete, setConfirmDelete] = React.useState(false) + const onDelete = React.useCallback(async () => { + setBusy(true) + + try { + await api.delete(`/tracks/${slug}`) + history.push('/tracks') + } finally { + setConfirmDelete(false) + setBusy(false) + } + }, [setBusy, setConfirmDelete, slug, history]) + + return ( + + + + +

    Edit track

    +
    + + + + + + + +