From 30a6ebb5e40a5a29e2be8325afaf66c55a2e892e Mon Sep 17 00:00:00 2001 From: Dwayne Harris Date: Mon, 28 Oct 2019 19:48:19 -0400 Subject: [PATCH] WIP --- src/actions/apps.ts | 64 +++-------------- src/actions/groups.ts | 107 ++++++++--------------------- src/actions/lists.ts | 40 +++++++++++ src/actions/posts.ts | 47 +++++-------- src/components/pages/self.tsx | 3 + src/components/pages/view-user.tsx | 9 ++- src/components/post-list.tsx | 17 +++++ src/components/post.tsx | 40 +++++++++++ src/components/user.tsx | 35 ++++++++++ src/reducers/apps.ts | 50 -------------- src/reducers/groups.ts | 93 ------------------------- src/reducers/lists.ts | 42 +++++++++++ src/selectors/apps.ts | 17 ++++- src/selectors/composer.ts | 2 - src/selectors/groups.ts | 24 +++++-- src/selectors/lists.ts | 15 ++++ src/selectors/posts.ts | 9 +++ src/store/index.ts | 6 +- src/styles/app.scss | 33 +++++++++ src/types/store.ts | 82 +++++++++++----------- 20 files changed, 372 insertions(+), 363 deletions(-) create mode 100644 src/actions/lists.ts create mode 100644 src/components/post-list.tsx create mode 100644 src/components/post.tsx create mode 100644 src/components/user.tsx delete mode 100644 src/reducers/apps.ts delete mode 100644 src/reducers/groups.ts create mode 100644 src/reducers/lists.ts create mode 100644 src/selectors/lists.ts create mode 100644 src/selectors/posts.ts diff --git a/src/actions/apps.ts b/src/actions/apps.ts index 7565a7e..b9f0ed4 100644 --- a/src/actions/apps.ts +++ b/src/actions/apps.ts @@ -1,64 +1,14 @@ -import { Action } from 'redux' - import { apiFetch } from 'src/api' import { setEntities } from 'src/actions/entities' import { startRequest, finishRequest } from 'src/actions/requests' import { setFieldNotification } from 'src/actions/forms' +import { listSet, listAppend } from 'src/actions/lists' import { objectToQuerystring } from 'src/utils' import { normalize } from 'src/utils/normalization' +import { EntityListKey } from 'src/types' import { AppThunkAction, RequestKey, EntityType, App, AvailabilityResponse, NotificationType }from 'src/types' -export interface AppendAppsAction extends Action { - type: 'APPS_APPEND_APPS' - payload: { - items: string[] - continuation?: string - } -} - -export interface ClearAppsAction extends Action { - type: 'APPS_CLEAR_APPS' -} - -export interface AppendCreatedAppsAction extends Action { - type: 'APPS_APPEND_CREATED_APPS' - payload: { - items: string[] - continuation?: string - } -} - -export interface ClearCreatedAppsAction extends Action { - type: 'APPS_CLEAR_CREATED_APPS' -} - -export type AppsActions = AppendAppsAction | ClearAppsAction | AppendCreatedAppsAction | ClearCreatedAppsAction - -export const appendApps = (apps: string[], continuation?: string): AppendAppsAction => ({ - type: 'APPS_APPEND_APPS', - payload: { - items: apps, - continuation, - } -}) - -export const clearApps = (): ClearAppsAction => ({ - type: 'APPS_CLEAR_APPS', -}) - -export const appendCreatedApps = (apps: string[], continuation?: string): AppendCreatedAppsAction => ({ - type: 'APPS_APPEND_CREATED_APPS', - payload: { - items: apps, - continuation, - } -}) - -export const clearCreatedApps = (): ClearCreatedAppsAction => ({ - type: 'APPS_CLEAR_CREATED_APPS', -}) - interface AppsResponse { apps: App[] continuation?: string @@ -75,7 +25,13 @@ export const fetchApps = (sort?: string, continuation?: string): AppThunkAction const apps = normalize(response.apps, EntityType.App) dispatch(setEntities(apps.entities)) - dispatch(appendApps(apps.keys, response.continuation)) + + if (continuation) { + dispatch(listAppend(EntityListKey.Apps, apps.keys, response.continuation)) + } else { + dispatch(listSet(EntityListKey.Apps, apps.keys, response.continuation)) + } + dispatch(finishRequest(RequestKey.FetchApps, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchApps, false)) @@ -94,7 +50,7 @@ export const fetchCreatedApps = (sort?: string): AppThunkAction => async dispatc const apps = normalize(response.apps, EntityType.App) dispatch(setEntities(apps.entities)) - dispatch(appendCreatedApps(apps.keys, response.continuation)) + dispatch(listSet(EntityListKey.CreatedApps, apps.keys)) dispatch(finishRequest(RequestKey.FetchSelfApps, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchSelfApps, false)) diff --git a/src/actions/groups.ts b/src/actions/groups.ts index 103b474..ccbd743 100644 --- a/src/actions/groups.ts +++ b/src/actions/groups.ts @@ -2,85 +2,12 @@ import { Action } from 'redux' import { apiFetch } from 'src/api' import { setEntities } from 'src/actions/entities' +import { listSet, listAppend } from 'src/actions/lists' import { startRequest, finishRequest } from 'src/actions/requests' import { objectToQuerystring } from 'src/utils' import { normalize } from 'src/utils/normalization' -import { AppThunkAction, Entity, RequestKey, EntityType, User } from 'src/types' - -export interface AppendGroupsAction extends Action { - type: 'GROUPS_APPEND_GROUPS' - payload: { - items: string[] - continuation?: string - } -} - -export interface ClearGroupsAction extends Action { - type: 'GROUPS_CLEAR_GROUPS' -} - -export interface AppendLogsAction extends Action { - type: 'GROUPS_APPEND_LOGS' - payload: { - items: string[] - continuation?: string - } -} - -export interface ClearLogsAction extends Action { - type: 'GROUPS_CLEAR_LOGS' -} - -export interface AppendInvitationsAction extends Action { - type: 'GROUPS_APPEND_INVITATIONS' - payload: { - items: string[] - continuation?: string - } -} - -export interface ClearInvitationsAction extends Action { - type: 'GROUPS_CLEAR_INVITATIONS' -} - -export type GroupsActions = AppendGroupsAction | ClearGroupsAction | AppendLogsAction | ClearLogsAction | AppendInvitationsAction | ClearInvitationsAction - -export const appendGroups = (groups: string[], continuation?: string): AppendGroupsAction => ({ - type: 'GROUPS_APPEND_GROUPS', - payload: { - items: groups, - continuation, - }, -}) - -export const clearGroups = (): ClearGroupsAction => ({ - type: 'GROUPS_CLEAR_GROUPS', -}) - -export const appendLogs = (logs: string[], continuation?: string): AppendLogsAction => ({ - type: 'GROUPS_APPEND_LOGS', - payload: { - items: logs, - continuation, - }, -}) - -export const clearLogs = (): ClearLogsAction => ({ - type: 'GROUPS_CLEAR_LOGS', -}) - -export const appendInvitations = (invitations: string[], continuation?: string): AppendInvitationsAction => ({ - type: 'GROUPS_APPEND_INVITATIONS', - payload: { - items: invitations, - continuation, - }, -}) - -export const clearInvitations = (): ClearInvitationsAction => ({ - type: 'GROUPS_CLEAR_INVITATIONS', -}) +import { AppThunkAction, Entity, RequestKey, EntityType, User, EntityListKey } from 'src/types' export const fetchGroup = (id: string): AppThunkAction => { return async dispatch => { @@ -118,7 +45,13 @@ export const fetchGroups = (sort?: string, continuation?: string): AppThunkActio const groups = normalize(response.groups, EntityType.Group) dispatch(setEntities(groups.entities)) - dispatch(appendGroups(groups.keys, response.continuation)) + + if (continuation) { + dispatch(listAppend(EntityListKey.Groups, groups.keys, response.continuation)) + } else { + dispatch(listSet(EntityListKey.Groups, groups.keys, response.continuation)) + } + dispatch(finishRequest(RequestKey.FetchGroups, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchGroups, false)) @@ -142,6 +75,13 @@ export const fetchGroupMembers = (id: string, type?: string, continuation?: stri const users = normalize(response.members, EntityType.User) dispatch(setEntities(users.entities)) + + if (continuation) { + dispatch(listAppend(EntityListKey.GroupMembers, users.keys, response.continuation)) + } else { + dispatch(listSet(EntityListKey.GroupMembers, users.keys, response.continuation)) + } + dispatch(finishRequest(RequestKey.FetchGroupMembers, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchGroupMembers, false)) @@ -162,10 +102,16 @@ export const fetchLogs = (id: string, continuation?: string): AppThunkAction => path: `/api/group/${id}/logs?${objectToQuerystring({ continuation })}`, }) - const users = normalize(response.logs, EntityType.Log) + const logs = normalize(response.logs, EntityType.Log) + + dispatch(setEntities(logs.entities)) + + if (continuation) { + dispatch(listAppend(EntityListKey.Logs, logs.keys, response.continuation)) + } else { + dispatch(listSet(EntityListKey.Logs, logs.keys, response.continuation)) + } - dispatch(setEntities(users.entities)) - dispatch(appendLogs(users.keys, response.continuation)) dispatch(finishRequest(RequestKey.FetchGroupLogs, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchGroupLogs, false)) @@ -212,8 +158,9 @@ export const fetchInvitations = (id: string): AppThunkAction => async dispatch = }) const invitations = normalize(response.invitations, EntityType.Invitation) + dispatch(setEntities(invitations.entities)) - dispatch(appendInvitations(invitations.keys, response.continuation)) + dispatch(listSet(EntityListKey.Invitations, invitations.keys, response.continuation)) dispatch(finishRequest(RequestKey.FetchInvitations, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchInvitations, false)) diff --git a/src/actions/lists.ts b/src/actions/lists.ts new file mode 100644 index 0000000..469c2f3 --- /dev/null +++ b/src/actions/lists.ts @@ -0,0 +1,40 @@ +import { Action } from 'redux' +import { EntityListKey } from 'src/types' + +export interface ListAppendAction extends Action { + type: 'LISTS_APPEND' + payload: { + key: string + entities: string[] + continuation?: string + } +} + +export interface ListSetAction extends Action { + type: 'LISTS_SET' + payload: { + key: string + entities: string[] + continuation?: string + } +} + +export type ListsActions = ListAppendAction | ListSetAction + +export const listAppend = (key: string, entities: string[], continuation?: string): ListAppendAction => ({ + type: 'LISTS_APPEND', + payload: { + key, + entities, + continuation, + }, +}) + +export const listSet = (key: string, entities: string[], continuation?: string): ListSetAction => ({ + type: 'LISTS_SET', + payload: { + key, + entities, + continuation, + }, +}) diff --git a/src/actions/posts.ts b/src/actions/posts.ts index 374f1fe..a6dd7ea 100644 --- a/src/actions/posts.ts +++ b/src/actions/posts.ts @@ -2,37 +2,12 @@ import { Action } from 'redux' import { apiFetch } from 'src/api' import { setEntities } from 'src/actions/entities' +import { listSet, listAppend } from 'src/actions/lists' import { startRequest, finishRequest } from 'src/actions/requests' import { objectToQuerystring } from 'src/utils' import { normalize } from 'src/utils/normalization' -import { AppThunkAction, Entity, RequestKey, EntityType, User, Post } from 'src/types' - -export interface AppendPostsAction extends Action { - type: 'POSTS_APPEND_POSTS' - payload: { - items: string[] - continuation?: string - } -} - -export interface ClearPostsAction extends Action { - type: 'POSTS_CLEAR_POSTS' -} - -export type PostsActions = AppendPostsAction | ClearPostsAction - -export const appendPosts = (posts: string[], continuation?: string): AppendPostsAction => ({ - type: 'POSTS_APPEND_POSTS', - payload: { - items: posts, - continuation, - }, -}) - -export const clearPosts = (): ClearPostsAction => ({ - type: 'POSTS_CLEAR_POSTS', -}) +import { AppThunkAction, Entity, RequestKey, EntityType, User, Post, EntityListKey } from 'src/types' interface CreatePostResponse { id: string @@ -109,7 +84,13 @@ export const fetchTimeline = (continuation?: string): AppThunkAction => async di const posts = normalize(response.posts, EntityType.Group) dispatch(setEntities(posts.entities)) - dispatch(appendPosts(posts.keys, response.continuation)) + + if (continuation) { + dispatch(listAppend(EntityListKey.Timeline, posts.keys, response.continuation)) + } else { + dispatch(listSet(EntityListKey.Timeline, posts.keys, response.continuation)) + } + dispatch(finishRequest(RequestKey.FetchTimeline, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchTimeline, false)) @@ -128,7 +109,7 @@ export const fetchUserPosts = (id: string, continuation?: string): AppThunkActio try { const response = await apiFetch({ - path: `/api/user/${id}/posts?${objectToQuerystring({ continuation })}`, + path: `/api/user/${id}/posts`, }) const posts = normalize(response.posts.map(p => ({ @@ -137,7 +118,13 @@ export const fetchUserPosts = (id: string, continuation?: string): AppThunkActio })), EntityType.Post) dispatch(setEntities(posts.entities)) - dispatch(appendPosts(posts.keys, response.continuation)) + + if (continuation) { + dispatch(listAppend(`posts:${id}`, posts.keys, response.continuation)) + } else { + dispatch(listSet(`posts:${id}`, posts.keys, response.continuation)) + } + dispatch(finishRequest(RequestKey.FetchUserPosts, true)) } catch (err) { dispatch(finishRequest(RequestKey.FetchUserPosts, false)) diff --git a/src/components/pages/self.tsx b/src/components/pages/self.tsx index cd1f6fb..135ec09 100644 --- a/src/components/pages/self.tsx +++ b/src/components/pages/self.tsx @@ -117,6 +117,9 @@ const Self: FC = () => { {tab === 'settings' &&
+ View Your Page +

+
diff --git a/src/components/pages/view-user.tsx b/src/components/pages/view-user.tsx index 0715b54..a2b479e 100644 --- a/src/components/pages/view-user.tsx +++ b/src/components/pages/view-user.tsx @@ -9,14 +9,16 @@ import { fetchUser } from 'src/actions/users' import { fetchUserPosts } from 'src/actions/posts' import { getEntity } from 'src/selectors/entities' import { getAuthenticatedUser } from 'src/selectors/authentication' +import { getUserPosts } from 'src/selectors/posts' import { useDeepCompareEffect, useConfig } from 'src/hooks' import { setTitle, urlForBlob } from 'src/utils' -import { AppState, EntityType, User, AppThunkDispatch } from 'src/types' +import { AppState, EntityType, User, Post, AppThunkDispatch } from 'src/types' import PageHeader from 'src/components/page-header' import UserInfo from 'src/components/user-info' import Loading from 'src/components/pages/loading' +import PostList from 'src/components/post-list' interface Params { id: string @@ -26,6 +28,7 @@ const ViewUser: FC = () => { const { id } = useParams() const self = useSelector(getAuthenticatedUser) const user = useSelector(state => getEntity(state, EntityType.User, id)) + const posts = useSelector(state => getUserPosts(state, id)) const dispatch = useDispatch() const config = useConfig() const history = useHistory() @@ -110,9 +113,9 @@ const ViewUser: FC = () => { }
- -
+ + ) } diff --git a/src/components/post-list.tsx b/src/components/post-list.tsx new file mode 100644 index 0000000..8cd951a --- /dev/null +++ b/src/components/post-list.tsx @@ -0,0 +1,17 @@ +import React, { FC } from 'react' + +import { Post } from 'src/types' + +import PostComponent from 'src/components/post' + +interface Props { + posts: Post[] +} + +const PostList: FC = ({ posts }) => ( +
+ {posts.map(post => )} +
+) + +export default PostList diff --git a/src/components/post.tsx b/src/components/post.tsx new file mode 100644 index 0000000..8bcf181 --- /dev/null +++ b/src/components/post.tsx @@ -0,0 +1,40 @@ +import React, { FC } from 'react' +import { Link } from 'react-router-dom' +import moment from 'moment' +import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' +import { faClock } from '@fortawesome/free-solid-svg-icons' + +import { Post } from 'src/types' + +import User from 'src/components/user' + +interface Props { + post: Post +} + +const PostComponent: FC = ({ post }) => ( +
+

{post.text}

+ +
+
+ +
+ +
+ Awards +
+ +
+ + + + + {moment(post.created).format('MMMM Do, h:mm A')} + +
+
+
+) + +export default PostComponent diff --git a/src/components/user.tsx b/src/components/user.tsx new file mode 100644 index 0000000..e7bbcf9 --- /dev/null +++ b/src/components/user.tsx @@ -0,0 +1,35 @@ +import React, { FC } from 'react' +import { useSelector } from 'react-redux' +import { Link } from 'react-router-dom' + +import { getConfig } from 'src/selectors' +import { urlForBlob } from 'src/utils' +import { AppState, User, Config } from 'src/types' + +interface Props { + user: User +} + +const UserComponent: FC = ({ user }) => { + const config = useSelector(getConfig) + const imageUrl = user && user.imageUrl ? urlForBlob(config, user.imageUrl) : undefined + + return ( +
+ {imageUrl && +
+ +
+ } +
+ + {user.name} @{user.id} + +
+ {user.group && {user.group.name}} +
+
+ ) +} + +export default UserComponent diff --git a/src/reducers/apps.ts b/src/reducers/apps.ts deleted file mode 100644 index 1a3e3e6..0000000 --- a/src/reducers/apps.ts +++ /dev/null @@ -1,50 +0,0 @@ -import { Reducer } from 'redux' - -import { AppsActions } from '../actions/apps' -import { AppsState } from '../types' - -const initialState: AppsState = { - items: [], - continuation: undefined, - created: { - items: [], - continuation: undefined - } -} - -const reducer: Reducer = (state = initialState, action) => { - switch (action.type) { - case 'APPS_APPEND_APPS': - return { - ...state, - items: action.payload.items, - continuation: action.payload.continuation, - } - case 'APPS_CLEAR_APPS': - return { - ...state, - items: [], - continuation: undefined, - } - case 'APPS_APPEND_CREATED_APPS': - return { - ...state, - created: { - items: action.payload.items, - continuation: action.payload.continuation, - }, - } - case 'APPS_CLEAR_CREATED_APPS': - return { - ...state, - created: { - items: [], - continuation: undefined, - }, - } - default: - return state - } -} - -export default reducer diff --git a/src/reducers/groups.ts b/src/reducers/groups.ts deleted file mode 100644 index a934b81..0000000 --- a/src/reducers/groups.ts +++ /dev/null @@ -1,93 +0,0 @@ -import { Reducer } from 'redux' - -import { GroupsActions } from '../actions/groups' -import { GroupsState } from '../types' - -const initialState: GroupsState = { - items: [], - continuation: undefined, - admin: { - invitations: { - items: [], - continuation: undefined, - }, - logs: { - items: [], - continuation: undefined, - } - } -} - -const reducer: Reducer = (state = initialState, action) => { - switch (action.type) { - case 'GROUPS_APPEND_GROUPS': - return { - ...state, - items: [ - ...state.items, - ...action.payload.items, - ], - continuation: action.payload.continuation, - } - case 'GROUPS_CLEAR_GROUPS': - return { - ...state, - items: [], - continuation: undefined, - } - case 'GROUPS_APPEND_LOGS': - return { - ...state, - admin: { - ...state.admin, - logs: { - items: [ - ...state.admin.logs.items, - ...action.payload.items, - ], - continuation: action.payload.continuation, - }, - }, - } - case 'GROUPS_CLEAR_LOGS': - return { - ...state, - admin: { - ...state.admin, - logs: { - items: [], - continuation: undefined, - }, - }, - } - case 'GROUPS_APPEND_INVITATIONS': - return { - ...state, - admin: { - ...state.admin, - invitations: { - items: [ - ...state.admin.logs.items, - ...action.payload.items, - ], - continuation: action.payload.continuation, - }, - }, - } - case 'GROUPS_CLEAR_INVITATIONS': - return { - ...state, - admin: { - ...state.admin, - invitations: { - items: [], - continuation: undefined, - }, - }, - } - default: - return state - } -} - -export default reducer diff --git a/src/reducers/lists.ts b/src/reducers/lists.ts new file mode 100644 index 0000000..74930df --- /dev/null +++ b/src/reducers/lists.ts @@ -0,0 +1,42 @@ +import { Reducer } from 'redux' + +import { ListsActions } from '../actions/lists' +import { EntityListsState, EntityList } from '../types' + +const initialState: EntityListsState = {} + +const reducer: Reducer = (state = initialState, action) => { + switch (action.type) { + case 'LISTS_APPEND': + const list = state[action.payload.key] || { + entities: [], + continuation: undefined, + checked: 0, + } + + return { + ...state, + [action.payload.key]: { + entities: [ + ...list.entities, + ...action.payload.entities, + ], + continuation: action.payload.continuation, + checked: Date.now(), + }, + } + case 'LISTS_SET': + return { + ...state, + [action.payload.key]: { + entities: action.payload.entities, + continuation: action.payload.continuation, + checked: Date.now(), + } + } + default: + return state + } +} + +export default reducer diff --git a/src/selectors/apps.ts b/src/selectors/apps.ts index 487b278..ace94b5 100644 --- a/src/selectors/apps.ts +++ b/src/selectors/apps.ts @@ -1,5 +1,16 @@ import { denormalize } from 'src/utils/normalization' -import { AppState, EntityType, App, Installation } from 'src/types' +import { AppState, EntityType, App, EntityListKey } from 'src/types' -export const getApps = (state: AppState) => denormalize(state.apps.items, EntityType.App, state.entities) as App[] -export const getCreatedApps = (state: AppState) => denormalize(state.apps.created.items, EntityType.App, state.entities) as App[] +export const getApps = (state: AppState) => { + const entityList = state.lists[EntityListKey.Apps] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.App, state.entities) as App[] +} + +export const getCreatedApps = (state: AppState) => { + const entityList = state.lists[EntityListKey.CreatedApps] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.App, state.entities) as App[] +} diff --git a/src/selectors/composer.ts b/src/selectors/composer.ts index 2001eb5..8233570 100644 --- a/src/selectors/composer.ts +++ b/src/selectors/composer.ts @@ -1,8 +1,6 @@ import { denormalize } from 'src/utils/normalization' import { AppState, EntityType, App, Installation } from 'src/types' -export const getApps = (state: AppState) => denormalize(state.apps.items, EntityType.App, state.entities) as App[] -export const getCreatedApps = (state: AppState) => denormalize(state.apps.created.items, EntityType.App, state.entities) as App[] export const getInstallations = (state: AppState) => denormalize(state.composer.installations, EntityType.Installation, state.entities) as Installation[] export const getError = (state: AppState) => state.composer.error export const getHeight = (state: AppState) => state.composer.height diff --git a/src/selectors/groups.ts b/src/selectors/groups.ts index ba6d917..469412b 100644 --- a/src/selectors/groups.ts +++ b/src/selectors/groups.ts @@ -1,9 +1,25 @@ import { denormalize } from 'src/utils/normalization' -import { AppState, Group, User, EntityType, GroupLog, Invitation } from 'src/types' +import { AppState, Group, User, EntityType, GroupLog, Invitation, EntityListKey } from 'src/types' -export const getGroups = (state: AppState) => denormalize(state.groups.items, EntityType.Group, state.entities) as Group[] -export const getLogs = (state: AppState) => denormalize(state.groups.admin.logs.items, EntityType.Log, state.entities) as GroupLog[] -export const getInvitations = (state: AppState) => denormalize(state.groups.admin.invitations.items, EntityType.Invitation, state.entities) as Invitation[] +export const getGroups = (state: AppState) => { + const entityList = state.lists[EntityListKey.Groups] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.Group, state.entities) as Group[] +} + +export const getLogs = (state: AppState) => { + const entityList = state.lists[EntityListKey.Logs] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.Log, state.entities) as GroupLog[] +} +export const getInvitations = (state: AppState) => { + const entityList = state.lists[EntityListKey.Invitations] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.Invitation, state.entities) as Invitation[] +} export const getGroupMembers = (state: AppState, group: string) => { const users = state.entities[EntityType.User] diff --git a/src/selectors/lists.ts b/src/selectors/lists.ts new file mode 100644 index 0000000..0cef4f0 --- /dev/null +++ b/src/selectors/lists.ts @@ -0,0 +1,15 @@ +import { AppState } from 'src/types' + +export const getEntities = (state: AppState, name: string) => { + const entityList = state.lists[name] + if (!entityList) return [] + + return entityList.entities +} + +export const getLastChecked = (state: AppState, name: string) => { + const entityList = state.lists[name] + if (!entityList) return 0 + + return entityList.checked +} diff --git a/src/selectors/posts.ts b/src/selectors/posts.ts new file mode 100644 index 0000000..de810b0 --- /dev/null +++ b/src/selectors/posts.ts @@ -0,0 +1,9 @@ +import { denormalize } from 'src/utils/normalization' +import { AppState, Post, EntityType } from 'src/types' + +export const getUserPosts = (state: AppState, id: string) => { + const entityList = state.lists[`posts:${id}`] + if (!entityList) return [] + + return denormalize(entityList.entities, EntityType.Post, state.entities) as Post[] +} diff --git a/src/store/index.ts b/src/store/index.ts index a78911f..262d49d 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,13 +1,12 @@ import { createStore, combineReducers, applyMiddleware } from 'redux' import { AppState } from '../types' -import apps from '../reducers/apps' import authentication from '../reducers/authentication' import composer from '../reducers/composer' import config from '../reducers/config' import entities from '../reducers/entities' import forms from '../reducers/forms' -import groups from '../reducers/groups' +import lists from '../reducers/lists' import menu from '../reducers/menu' import notifications from '../reducers/notifications' import registration from '../reducers/registration' @@ -18,13 +17,12 @@ import thunk from 'redux-thunk' const store = createStore( combineReducers({ - apps, authentication, composer, config, entities, forms, - groups, + lists, menu, notifications, registration, diff --git a/src/styles/app.scss b/src/styles/app.scss index 01a1ca3..736a39d 100644 --- a/src/styles/app.scss +++ b/src/styles/app.scss @@ -153,3 +153,36 @@ div.composer-container { } } } + +div.user { + display: flex; + + div.avatar { + margin-top: 6px; + margin-right: 10px; + } +} + +div.posts-list { + margin: 10px; +} + +div.post { + background-color: white; + margin: 10px 0; +} + +div.post p { + padding: 20px; +} + +div.post-info { + border-top: solid 1px $grey-lighter; + display: flex; + justify-content: space-between; + padding: 0 20px; +} + +div.post-info > div { + padding: 10px; +} diff --git a/src/types/store.ts b/src/types/store.ts index 146b5fa..f06f8a5 100644 --- a/src/types/store.ts +++ b/src/types/store.ts @@ -11,31 +11,41 @@ export enum NotificationType { export enum RequestKey { Authenticate = 'authenticate', - CreateApp = 'create_app', - CreateGroup = 'create_group', - CreateInvitation = 'create_invitation', - CreatePost = 'create_post', - FetchAppAvailability = 'fetch_app_availability', - FetchApp = 'fetch_app', - FetchApps = 'fetch_apps', - FetchGroup = 'fetch_group', - FetchGroupAvailability = 'fetch_group_availability', - FetchGroupLogs = 'fetch_group_logs', - FetchGroupMembers = 'fetch_group_members', - FetchGroups = 'fetch_groups', - FetchInstallations = 'fetch_installations', - FetchInvitations = 'fetch_invitations', - FetchPost = 'fetch_post', - FetchTimeline = 'fetch_timeline', - FetchUserPosts = 'fetch_user_posts', - FetchSelfApps = 'fetch_self_apps', - FetchUser = 'fetch_user', - FetchUserAvailability = 'fetch_user_availability', - InstallApp = 'install_app', - UninstallApp = 'uninstall_app', + CreateApp = 'create-app', + CreateGroup = 'create-group', + CreateInvitation = 'create-invitation', + CreatePost = 'create-post', + FetchAppAvailability = 'fetch-app-availability', + FetchApp = 'fetch-app', + FetchApps = 'fetch-apps', + FetchGroup = 'fetch-group', + FetchGroupAvailability = 'fetch-group-availability', + FetchGroupLogs = 'fetch-group-logs', + FetchGroupMembers = 'fetch-group-members', + FetchGroups = 'fetch-groups', + FetchInstallations = 'fetch-installations', + FetchInvitations = 'fetch-invitations', + FetchPost = 'fetch-post', + FetchTimeline = 'fetch-timeline', + FetchUserPosts = 'fetch-user-posts', + FetchSelfApps = 'fetch-self-apps', + FetchUser = 'fetch-user', + FetchUserAvailability = 'fetch-user-availability', + InstallApp = 'install-app', + UninstallApp = 'uninstall-app', Register = 'register', - UpdateGroup = 'update_group', - UpdateSelf = 'update_self', + UpdateGroup = 'update-group', + UpdateSelf = 'update-self', +} + +export enum EntityListKey { + Apps = 'apps', + CreatedApps = 'created-apps', + Groups = 'groups', + GroupMembers = 'group-members', + Logs = 'logs', + Invitations = 'invitations', + Timeline = 'timeline', } export type FormValue = string | number | boolean @@ -86,8 +96,9 @@ export interface Form { } export interface EntityList { - items: string[] + entities: string[] continuation?: string + checked: number } export interface FormsState { @@ -95,23 +106,10 @@ export interface FormsState { notification?: FormNotification } -export interface GroupsAdminState { - invitations: EntityList - logs: EntityList -} - -export type GroupsState = EntityList & { - admin: GroupsAdminState -} - export interface RegistrationState { step: number } -export type AppsState = EntityList & { - created: EntityList -} - export type ComposerState = { installations: string[] selected?: string @@ -119,19 +117,23 @@ export type ComposerState = { height: number } +export interface EntityListCollection { + [key: string]: EntityList +} + export type ConfigState = Config export type RequestsState = APIRequestCollection export type NotificationsState = Notification[] export type EntitiesState = EntityStore +export type EntityListsState = EntityListCollection export interface AppState { authentication: AuthenticationState - apps: AppsState composer: ComposerState config: ConfigState entities: EntitiesState forms: FormsState - groups: GroupsState + lists: EntityListsState menu: MenuState notifications: NotificationsState registration: RegistrationState