From 3527bd6e7e2d90a74526f52821ed812ae813c21f Mon Sep 17 00:00:00 2001 From: Dwayne Harris Date: Wed, 30 Oct 2019 01:13:04 -0400 Subject: [PATCH] WIP --- src/actions/posts.ts | 4 +- src/actions/users.ts | 36 ++++++ src/components/app.tsx | 2 +- src/components/composer.tsx | 3 +- src/components/pages/create-app.tsx | 6 + src/components/pages/edit-app.tsx | 33 +++++- src/components/pages/home.tsx | 8 +- src/components/pages/self.tsx | 173 ++++++++++------------------ src/components/pages/view-app.tsx | 2 +- src/components/pages/view-group.tsx | 2 +- src/components/timeline.tsx | 39 +++++++ src/selectors/authentication.ts | 2 +- src/selectors/posts.ts | 9 +- src/types/entities.ts | 2 + src/types/store.ts | 10 +- 15 files changed, 205 insertions(+), 126 deletions(-) create mode 100644 src/components/timeline.tsx diff --git a/src/actions/posts.ts b/src/actions/posts.ts index a6dd7ea..94d757a 100644 --- a/src/actions/posts.ts +++ b/src/actions/posts.ts @@ -69,7 +69,7 @@ export const fetchPost = (id: string): AppThunkAction => { } interface TimelineResponse { - posts: Entity[] + posts: Post[] continuation?: string } @@ -81,7 +81,7 @@ export const fetchTimeline = (continuation?: string): AppThunkAction => async di path: `/api/timeline?${objectToQuerystring({ continuation })}`, }) - const posts = normalize(response.posts, EntityType.Group) + const posts = normalize(response.posts, EntityType.Post) dispatch(setEntities(posts.entities)) diff --git a/src/actions/users.ts b/src/actions/users.ts index 7a6f017..cba7494 100644 --- a/src/actions/users.ts +++ b/src/actions/users.ts @@ -23,3 +23,39 @@ export const fetchUser = (id: string): AppThunkAction => { } } } + +export const subscribe = (id: string): AppThunkAction => { + return async dispatch => { + dispatch(startRequest(RequestKey.Subscribe)) + + try { + await apiFetch({ + path: `/api/user/${id}/subscribe`, + method: 'post', + }) + + dispatch(finishRequest(RequestKey.Subscribe, true)) + } catch (err) { + dispatch(finishRequest(RequestKey.Subscribe, false)) + throw err + } + } +} + +export const unsubscribe = (id: string): AppThunkAction => { + return async dispatch => { + dispatch(startRequest(RequestKey.Unsubscribe)) + + try { + await apiFetch({ + path: `/api/user/${id}/unsubscribe`, + method: 'post', + }) + + dispatch(finishRequest(RequestKey.Unsubscribe, true)) + } catch (err) { + dispatch(finishRequest(RequestKey.Unsubscribe, false)) + throw err + } + } +} diff --git a/src/components/app.tsx b/src/components/app.tsx index 37cbfa1..ad188d0 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -110,7 +110,7 @@ const App: FC = () => { - + diff --git a/src/components/composer.tsx b/src/components/composer.tsx index 1847892..f7d5111 100644 --- a/src/components/composer.tsx +++ b/src/components/composer.tsx @@ -6,7 +6,7 @@ import { getOrigin } from 'src/utils' import { useConfig, useDeepCompareEffect } from 'src/hooks' import { fetchInstallations, setSelectedInstallation, setHeight as setComposerHeight, setError as setComposerError } from 'src/actions/composer' import { showNotification } from 'src/actions/notifications' -import { createPost } from 'src/actions/posts' +import { createPost, fetchTimeline } from 'src/actions/posts' import { getInstallations, getSelectedInstallation, getError, getHeight as getComposerHeight } from 'src/selectors/composer' import { AppState, Installation, ClassDictionary, AppThunkDispatch, NotificationType } from 'src/types' import { IncomingMessageData, OutgoingMessageData } from 'src/types/communicator' @@ -127,6 +127,7 @@ const Composer: FC = () => { }) dispatch(showNotification(NotificationType.Success, `Posted!`)) + dispatch(fetchTimeline()) } catch (err) { postMessage({ name, diff --git a/src/components/pages/create-app.tsx b/src/components/pages/create-app.tsx index 5a656de..8a381bc 100644 --- a/src/components/pages/create-app.tsx +++ b/src/components/pages/create-app.tsx @@ -40,6 +40,9 @@ const CreateApp: FC = () => { const version = valueFromForm(form, 'version') const composerUrl = valueFromForm(form, 'composerUrl') const rendererUrl = valueFromForm(form, 'rendererUrl') + const imageUrl = valueFromForm(form, 'image') + const coverImageUrl = valueFromForm(form, 'coverImage') + const iconImageUrl = valueFromForm(form, 'iconImage') const agree = valueFromForm(form, 'agree') if (!name || name === '') { @@ -68,6 +71,9 @@ const CreateApp: FC = () => { companyName, composerUrl, rendererUrl, + imageUrl, + coverImageUrl, + iconImageUrl, })) history.push(`/a/${id}`) diff --git a/src/components/pages/edit-app.tsx b/src/components/pages/edit-app.tsx index b571607..e5eac49 100644 --- a/src/components/pages/edit-app.tsx +++ b/src/components/pages/edit-app.tsx @@ -1,8 +1,8 @@ -import React, { FC, useEffect } from 'react' +import React, { FC, useEffect, useState } from 'react' import { useSelector, useDispatch } from 'react-redux' import { useParams, useHistory } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faIdCard, faCheckCircle } from '@fortawesome/free-solid-svg-icons' +import { faIdCard, faCheckCircle, faKey, faShieldAlt } from '@fortawesome/free-solid-svg-icons' import { handleApiError } from 'src/api/errors' import { fetchApp, updateApp } from 'src/actions/apps' @@ -38,6 +38,7 @@ const EditApp: FC = () => { const selfId = useSelector(getAuthenticatedUserId) const dispatch = useDispatch() const history = useHistory() + const [showPrivateKey, setShowPrivateKey] = useState(false) useEffect(() => { try { @@ -110,17 +111,45 @@ const EditApp: FC = () => { })) dispatch(showNotification(NotificationType.Success, 'Updated')) + history.push(`/a/${app.id}`) } catch (err) { handleApiError(err, dispatch, history) } } + const privateKeyDisplay = showPrivateKey ? app.privateKey : '' + return (
+
+ +
+ + + + +
+
+
+ +
+

+ +

+

+ +

+
+

+
diff --git a/src/components/pages/home.tsx b/src/components/pages/home.tsx index e31df95..4acffb9 100644 --- a/src/components/pages/home.tsx +++ b/src/components/pages/home.tsx @@ -7,6 +7,7 @@ import { AppState } from 'src/types' import PageHeader from 'src/components/page-header' import Composer from 'src/components/composer' +import Timeline from 'src/components/timeline' const Home: FC = () => { const authenticated = useSelector(getAuthenticated) @@ -20,7 +21,12 @@ const Home: FC = () => {
- {authenticated && } + {authenticated && +
+ + +
+ }
) diff --git a/src/components/pages/self.tsx b/src/components/pages/self.tsx index 135ec09..661a76d 100644 --- a/src/components/pages/self.tsx +++ b/src/components/pages/self.tsx @@ -1,9 +1,8 @@ import React, { FC } from 'react' import { useSelector, useDispatch } from 'react-redux' -import { Link, useParams, useHistory } from 'react-router-dom' -import moment from 'moment' +import { Link, useHistory } from 'react-router-dom' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' -import { faDoorOpen, faCheckCircle, faPlusCircle, faIdCard, faEnvelope, faUserShield } from '@fortawesome/free-solid-svg-icons' +import { faDoorOpen, faCheckCircle, faIdCard, faEnvelope, faUserShield } from '@fortawesome/free-solid-svg-icons' import { unauthenticate, updateSelf } from 'src/actions/authentication' import { initForm, initField } from 'src/actions/forms' @@ -26,18 +25,7 @@ import CheckboxField from 'src/components/forms/checkbox-field' import ImageField from 'src/components/forms/image-field' import CoverImageField from 'src/components/forms/cover-image-field' -interface Params { - tab: string -} - -const tabs: Tab[] = [ - { id: '', label: 'Posts' }, - { id: 'settings', label: 'Settings' }, - { id: 'apps', label: 'Apps' }, -] - const Self: FC = () => { - const { tab = '' } = useParams() const dispatch = useDispatch() const history = useHistory() @@ -98,106 +86,69 @@ const Self: FC = () => {
-
-
    - {tabs.map(t => ( -
  • - - {t.label} - -
  • - ))} -
+ View Your Page +

+ +
+ +
+ + + + +
- -
- {tab === '' && -

No Posts.

- } - - {tab === 'settings' && -
- View Your Page -

- -
- -
- - - - -
-
-
- -
- -
- - - - -
-
-
- - -
- -
- -
- -
- -
- - You must approve each Subscription request from other users. - -

- - -
- } - - {tab === 'apps' && -
-

No Apps.

- -
- - +
+ +
+ +
+ + + + +
+
+
+ + +
+ +
+ +
+ +
+ +
+ + You must approve each Subscription request from other users. + +

+ +
+ Save + +

+
+ +
+

+ +

+
+
diff --git a/src/components/pages/view-app.tsx b/src/components/pages/view-app.tsx index 6b791b9..087bc1f 100644 --- a/src/components/pages/view-app.tsx +++ b/src/components/pages/view-app.tsx @@ -126,7 +126,7 @@ const ViewApp: FC = () => {
{renderButton()} - {isCreator && Edit App} + {isCreator && View/Edit App}
diff --git a/src/components/pages/view-group.tsx b/src/components/pages/view-group.tsx index b181337..e909524 100644 --- a/src/components/pages/view-group.tsx +++ b/src/components/pages/view-group.tsx @@ -94,7 +94,7 @@ const ViewGroup: FC = () => { } {isAdmin && - + diff --git a/src/components/timeline.tsx b/src/components/timeline.tsx new file mode 100644 index 0000000..7257398 --- /dev/null +++ b/src/components/timeline.tsx @@ -0,0 +1,39 @@ +import React, { FC, useEffect } from 'react' +import { useSelector, useDispatch } from 'react-redux' +import { useHistory } from 'react-router-dom' + +import { handleApiError } from 'src/api/errors' +import { fetchTimeline } from 'src/actions/posts' +import { getTimeline } from 'src/selectors/posts' +import { getAuthenticated } from 'src/selectors/authentication' + +import { AppState, Post, AppThunkDispatch } from 'src/types' + +import PostList from 'src/components/post-list' + +const Timeline: FC = () => { + const authenticated = useSelector(getAuthenticated) + const posts = useSelector(getTimeline) + const dispatch = useDispatch() + const history = useHistory() + + useEffect(() => { + const init = async () => { + try { + await dispatch(fetchTimeline()) + } catch (err) { + handleApiError(err, dispatch, history) + } + } + + if (authenticated) init() + }, [authenticated]) + + return ( +
+ +
+ ) +} + +export default Timeline diff --git a/src/selectors/authentication.ts b/src/selectors/authentication.ts index ef57c93..01fd281 100644 --- a/src/selectors/authentication.ts +++ b/src/selectors/authentication.ts @@ -5,7 +5,7 @@ import { denormalize } from 'src/utils/normalization' import { AppState, User, EntityType } from 'src/types' export const getChecked = (state: AppState) => state.authentication.checked -export const getAuthenticated = (state: AppState) => state.authentication.authenticated +export const getAuthenticated = (state: AppState) => state.authentication.checked && state.authentication.authenticated export const getAuthenticatedUserId = (state: AppState) => state.authentication.userId export const getAuthenticatedUser = createSelector( diff --git a/src/selectors/posts.ts b/src/selectors/posts.ts index de810b0..d059572 100644 --- a/src/selectors/posts.ts +++ b/src/selectors/posts.ts @@ -1,5 +1,12 @@ import { denormalize } from 'src/utils/normalization' -import { AppState, Post, EntityType } from 'src/types' +import { AppState, Post, EntityType, EntityListKey } from 'src/types' + +export const getTimeline = (state: AppState) => { + const entityList = state.lists[EntityListKey.Timeline] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.Post, state.entities) as Post[] +} export const getUserPosts = (state: AppState, id: string) => { const entityList = state.lists[`posts:${id}`] diff --git a/src/types/entities.ts b/src/types/entities.ts index a2498a4..585ca6b 100644 --- a/src/types/entities.ts +++ b/src/types/entities.ts @@ -50,6 +50,8 @@ type BaseUser = Entity & { coverImageUrl?: string requiresApproval: boolean privacy: string + subscribed: boolean + subscribedToYou: boolean } export type User = BaseUser & { diff --git a/src/types/store.ts b/src/types/store.ts index f06f8a5..18093c8 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -15,8 +15,8 @@ export enum RequestKey { CreateGroup = 'create-group', CreateInvitation = 'create-invitation', CreatePost = 'create-post', - FetchAppAvailability = 'fetch-app-availability', FetchApp = 'fetch-app', + FetchAppAvailability = 'fetch-app-availability', FetchApps = 'fetch-apps', FetchGroup = 'fetch-group', FetchGroupAvailability = 'fetch-group-availability', @@ -26,14 +26,16 @@ export enum RequestKey { FetchInstallations = 'fetch-installations', FetchInvitations = 'fetch-invitations', FetchPost = 'fetch-post', - FetchTimeline = 'fetch-timeline', - FetchUserPosts = 'fetch-user-posts', FetchSelfApps = 'fetch-self-apps', + FetchTimeline = 'fetch-timeline', FetchUser = 'fetch-user', FetchUserAvailability = 'fetch-user-availability', + FetchUserPosts = 'fetch-user-posts', InstallApp = 'install-app', - UninstallApp = 'uninstall-app', Register = 'register', + Subscribe = 'subscribe', + UninstallApp = 'uninstall-app', + Unsubscribe = 'unsubscribe', UpdateGroup = 'update-group', UpdateSelf = 'update-self', }