From a7bfcfc270f7ee6975f378ae81c9b69f83596432 Mon Sep 17 00:00:00 2001 From: Dwayne Harris Date: Mon, 14 Oct 2019 01:21:08 -0400 Subject: [PATCH] WIP --- src/actions/apps.ts | 32 +++++++++++ src/api/fetch.ts | 3 +- src/components/pages/self.tsx | 6 +- src/components/pages/view-app.tsx | 91 +++++++++++++++++++++---------- src/hooks/index.ts | 12 +++- src/types/entities.ts | 5 +- src/types/store.ts | 2 + src/utils/normalization.ts | 31 ++++++----- 8 files changed, 131 insertions(+), 51 deletions(-) diff --git a/src/actions/apps.ts b/src/actions/apps.ts index 174e3fe..0026ee3 100644 --- a/src/actions/apps.ts +++ b/src/actions/apps.ts @@ -131,3 +131,35 @@ export const fetchApp = (id: string): AppThunkAction => async dispatch => { throw err } } + +export const installApp = (id: string): AppThunkAction => async dispatch => { + dispatch(startRequest(RequestKey.InstallApp)) + + try { + await apiFetch({ + path: `/api/app/${id}/install`, + method: 'post' + }) + + dispatch(finishRequest(RequestKey.InstallApp, true)) + } catch (err) { + dispatch(finishRequest(RequestKey.InstallApp, false)) + throw err + } +} + +export const uninstallApp = (id: string): AppThunkAction => async dispatch => { + dispatch(startRequest(RequestKey.UninstallApp)) + + try { + await apiFetch({ + path: `/api/app/${id}/uninstall`, + method: 'post' + }) + + dispatch(finishRequest(RequestKey.UninstallApp, true)) + } catch (err) { + dispatch(finishRequest(RequestKey.UninstallApp, false)) + throw err + } +} diff --git a/src/api/fetch.ts b/src/api/fetch.ts index 806f856..3b9095e 100644 --- a/src/api/fetch.ts +++ b/src/api/fetch.ts @@ -76,10 +76,11 @@ export const apiFetch: APIFetch = async (options: FetchOptions) => { const doFetch = async () => { const headers = new Headers({ ...options.headers, - 'Content-Type': contentType, 'Accept': contentType, }) + if (body) headers.append('Content-Type', contentType) + const accessToken = localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY) if (accessToken) headers.append('Authorization', `Bearer ${accessToken}`) diff --git a/src/components/pages/self.tsx b/src/components/pages/self.tsx index de5f048..4c38875 100644 --- a/src/components/pages/self.tsx +++ b/src/components/pages/self.tsx @@ -7,7 +7,7 @@ import { faDoorOpen, faCheckCircle, faPlusCircle, faIdCard, faEnvelope, faUserSh import { unauthenticate, updateSelf } from 'src/actions/authentication' import { initForm, initField } from 'src/actions/forms' -import { getAuthenticated, getChecked, getAuthenticatedUser } from 'src/selectors/authentication' +import { getAuthenticatedUser } from 'src/selectors/authentication' import { getForm } from 'src/selectors/forms' import { handleApiError } from 'src/api/errors' @@ -38,12 +38,10 @@ const Self: FC = () => { const dispatch = useDispatch() const history = useHistory() - const checked = useSelector(getChecked) - const authenticated = useSelector(getAuthenticated) const user = useSelector(getAuthenticatedUser) const form = useSelector(getForm) - useAuthenticationCheck(checked, authenticated, history) + useAuthenticationCheck() const handleLogout = () => { localStorage.clear() diff --git a/src/components/pages/view-app.tsx b/src/components/pages/view-app.tsx index d085261..178dcc0 100644 --- a/src/components/pages/view-app.tsx +++ b/src/components/pages/view-app.tsx @@ -1,18 +1,21 @@ import React, { FC, useEffect } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useParams, useHistory } from 'react-router-dom' +import classNames from 'classnames' import moment from 'moment' import { handleApiError } from 'src/api/errors' -import { fetchApp } from 'src/actions/apps' +import { fetchApp, installApp, uninstallApp } from 'src/actions/apps' import { getAuthenticatedUserId } from 'src/selectors/authentication' import { getEntity } from 'src/selectors/entities' +import { getIsFetching } from 'src/selectors/requests' import { setTitle } from 'src/utils' -import { AppState, AppThunkDispatch, EntityType, App } from 'src/types' +import { AppState, AppThunkDispatch, EntityType, App, RequestKey } from 'src/types' import PageHeader from 'src/components/page-header' import Loading from 'src/components/pages/loading' +import { ClassDictionary } from 'src/types' interface Params { id: string @@ -22,6 +25,7 @@ const ViewApp: FC = () => { const { id } = useParams() const app = useSelector(state => getEntity(state, EntityType.App, id)) const selfId = useSelector(getAuthenticatedUserId) + const fetching = useSelector(state => getIsFetching(state, RequestKey.InstallApp) || getIsFetching(state, RequestKey.UninstallApp)) const dispatch = useDispatch() const history = useHistory() @@ -41,48 +45,79 @@ const ViewApp: FC = () => { const isCreator = app.user.id === selfId + const renderButton = () => { + if (app.installed) { + const handleClick = async () => { + await dispatch(uninstallApp(id)) + await dispatch(fetchApp(id)) + } + + const classes: ClassDictionary = { + 'button': true, + 'is-danger': true, + 'is-loading': fetching, + } + + return + } else { + const handleClick = async () => { + await dispatch(installApp(id)) + await dispatch(fetchApp(id)) + } + + const classes: ClassDictionary = { + 'button': true, + 'is-success': true, + 'is-loading': fetching, + } + + return + } + } + return (
- + -

{app.about}

-
+
- + {renderButton()} +
) diff --git a/src/hooks/index.ts b/src/hooks/index.ts index bffebc1..ca9da54 100644 --- a/src/hooks/index.ts +++ b/src/hooks/index.ts @@ -1,8 +1,16 @@ import { useEffect, useRef, EffectCallback } from 'react' -import { History } from 'history' +import { useSelector } from 'react-redux' +import { useHistory } from 'react-router-dom' import isEqual from 'lodash/isEqual' -export const useAuthenticationCheck = (checked: boolean, authenticated: boolean, history: History) => { +import { getAuthenticated, getChecked } from 'src/selectors/authentication' +import { AppState } from 'src/types' + +export const useAuthenticationCheck = () => { + const checked = useSelector(getChecked) + const authenticated = useSelector(getAuthenticated) + const history = useHistory() + useEffect(() => { if (checked && !authenticated) history.push('/login') }, [checked, authenticated]) diff --git a/src/types/entities.ts b/src/types/entities.ts index 0ae850d..71a728c 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -4,6 +4,7 @@ export enum EntityType { Log = 'logs', Invitation = 'invitations', App = 'apps', + Installation = 'installations', } export enum GroupMembershipType { @@ -24,10 +25,9 @@ export type Group = Entity & { about: string } -export type Installation = { +export type Installation = Entity & { app: App settings: object - created: number } export type User = Entity & { @@ -38,7 +38,6 @@ export type User = Entity & { coverImageUrl?: string requiresApproval: boolean privacy: string - installations: Installation[] } export type GroupLog = Entity & { diff --git a/src/types/store.ts b/src/types/store.ts index 3f5934c..6eeef36 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -24,6 +24,8 @@ export enum RequestKey { FetchInvitations = 'fetch_invitations', FetchSelfApps = 'fetch_self_apps', FetchUserAvailability = 'fetch_user_availability', + InstallApp = 'install_app', + UninstallApp = 'uninstall_app', Register = 'register', UpdateGroup = 'update_group', UpdateSelf = 'update_self', diff --git a/src/utils/normalization.ts b/src/utils/normalization.ts index 186a1d5..6baaa5f 100644 --- a/src/utils/normalization.ts +++ b/src/utils/normalization.ts @@ -6,6 +6,7 @@ import { Invitation, GroupLog, App, + Installation, } from '../types' import compact from 'lodash/compact' @@ -53,17 +54,10 @@ export function normalize(entities: Entity[], type: EntityType): NormalizeResult case EntityType.User: keys = entities.map(entity => { const user = entity as User - const { installations = [] } = user return set(type, newStore, { ...user, group: set(EntityType.Group, newStore, user.group), - installations: installations.map(installation => { - return { - ...installation, - app: set(EntityType.App, newStore, installation.app), - } - }) }) }) @@ -104,6 +98,15 @@ export function normalize(entities: Entity[], type: EntityType): NormalizeResult }) break + case EntityType.Installation: + keys = entities.map(entity => { + const installation = entity as Installation + + return set(type, newStore, { + ...installation, + app: set(EntityType.App, newStore, installation.app), + }) + }) } return { @@ -122,12 +125,6 @@ export function denormalize(keys: string[], type: EntityType, store: EntityStore return { ...user, group: get(EntityType.Group, store, user.group), - installations: user.installations.map(installation => { - return { - ...installation, - app: get(EntityType.App, store, installation.app), - } - }) } case EntityType.Group: return get(type, store, key) @@ -155,6 +152,14 @@ export function denormalize(keys: string[], type: EntityType, store: EntityStore ...app, user: get(EntityType.User, store, app.user), } + case EntityType.Installation: + const installation = get(type, store, key) + if (!installation) return + + return { + ...installation, + app: get(EntityType.App, store, installation.app), + } } })