Dwayne Harris
5 years ago
20 changed files with 372 additions and 363 deletions
-
64src/actions/apps.ts
-
107src/actions/groups.ts
-
40src/actions/lists.ts
-
47src/actions/posts.ts
-
3src/components/pages/self.tsx
-
9src/components/pages/view-user.tsx
-
17src/components/post-list.tsx
-
40src/components/post.tsx
-
35src/components/user.tsx
-
50src/reducers/apps.ts
-
93src/reducers/groups.ts
-
42src/reducers/lists.ts
-
17src/selectors/apps.ts
-
2src/selectors/composer.ts
-
24src/selectors/groups.ts
-
15src/selectors/lists.ts
-
9src/selectors/posts.ts
-
6src/store/index.ts
-
33src/styles/app.scss
-
82src/types/store.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, |
||||
|
}, |
||||
|
}) |
@ -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<Props> = ({ posts }) => ( |
||||
|
<div className="post-list"> |
||||
|
{posts.map(post => <PostComponent post={post} />)} |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
export default PostList |
@ -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<Props> = ({ post }) => ( |
||||
|
<div className="post"> |
||||
|
<p>{post.text}</p> |
||||
|
|
||||
|
<div className="post-info"> |
||||
|
<div> |
||||
|
<User user={post.user} /> |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
Awards |
||||
|
</div> |
||||
|
|
||||
|
<div> |
||||
|
<span className="icon"> |
||||
|
<FontAwesomeIcon icon={faClock} /> |
||||
|
</span> |
||||
|
<Link to={`/p/${post.id}`} className="has-text-primary"> |
||||
|
{moment(post.created).format('MMMM Do, h:mm A')} |
||||
|
</Link> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
export default PostComponent |
@ -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<Props> = ({ user }) => { |
||||
|
const config = useSelector<AppState, Config>(getConfig) |
||||
|
const imageUrl = user && user.imageUrl ? urlForBlob(config, user.imageUrl) : undefined |
||||
|
|
||||
|
return ( |
||||
|
<div className="user"> |
||||
|
{imageUrl && |
||||
|
<div className="avatar"> |
||||
|
<img src={imageUrl} style={{ width: 32 }} /> |
||||
|
</div> |
||||
|
} |
||||
|
<div> |
||||
|
<Link to={`/u/${user.id}`}> |
||||
|
<span className="is-size-5">{user.name}</span> <span className="is-size-6 has-text-weight-bold">@{user.id}</span> |
||||
|
</Link> |
||||
|
<br /> |
||||
|
{user.group && <Link className="is-size-6 has-text-success" to={`/c/${user.group.id}`}>{user.group.name}</Link>} |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default UserComponent |
@ -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<AppsState, AppsActions> = (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 |
|
@ -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<GroupsState, GroupsActions> = (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 |
|
@ -0,0 +1,42 @@ |
|||||
|
import { Reducer } from 'redux' |
||||
|
|
||||
|
import { ListsActions } from '../actions/lists' |
||||
|
import { EntityListsState, EntityList } from '../types' |
||||
|
|
||||
|
const initialState: EntityListsState = {} |
||||
|
|
||||
|
const reducer: Reducer<EntityListsState, ListsActions> = (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 |
@ -1,5 +1,16 @@ |
|||||
import { denormalize } from 'src/utils/normalization' |
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[] |
||||
|
} |
@ -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 |
||||
|
} |
@ -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[] |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue