Dwayne Harris
5 years ago
16 changed files with 315 additions and 31 deletions
-
29src/actions/directory.ts
-
2src/api/fetch.ts
-
6src/components/app/app.scss
-
4src/components/app/app.tsx
-
17src/components/member-list/index.tsx
-
41src/components/member-list/member-list-item/index.tsx
-
9src/components/pages/directory/directory.tsx
-
130src/components/pages/group-admin/group-admin.tsx
-
29src/components/pages/group-admin/index.ts
-
31src/components/pages/group/group.tsx
-
7src/components/pages/login/login.tsx
-
15src/components/pages/self/self.tsx
-
4src/components/user-info/user-info.tsx
-
14src/selectors/directory.ts
-
7src/types/entities.ts
-
1src/types/store.ts
@ -0,0 +1,17 @@ |
|||||
|
import React, { FC } from 'react' |
||||
|
|
||||
|
import { User } from 'src/types' |
||||
|
|
||||
|
import MemberListItem from './member-list-item' |
||||
|
|
||||
|
interface Props { |
||||
|
members: User[] |
||||
|
} |
||||
|
|
||||
|
const MemberList: FC<Props> = ({ members }) => ( |
||||
|
<div className="is-flex"> |
||||
|
{members.map(member => <MemberListItem key={member.id} member={member} />)} |
||||
|
</div> |
||||
|
) |
||||
|
|
||||
|
export default MemberList |
@ -0,0 +1,41 @@ |
|||||
|
import React, { FC } from 'react' |
||||
|
import { Link } from 'react-router-dom' |
||||
|
import classNames from 'classnames' |
||||
|
import capitalize from 'lodash/capitalize' |
||||
|
|
||||
|
import { User, GroupMembershipType, ClassDictionary } from 'src/types' |
||||
|
|
||||
|
interface Props { |
||||
|
member: User |
||||
|
} |
||||
|
|
||||
|
const MemberListItem: FC<Props> = ({ member }) => { |
||||
|
const tagClass = () => { |
||||
|
switch (member.membership as GroupMembershipType) { |
||||
|
case GroupMembershipType.Admin: return 'is-success' |
||||
|
case GroupMembershipType.Moderator: return 'is-warning' |
||||
|
case GroupMembershipType.Member: return 'is-info' |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
const tagClassDictionary: ClassDictionary = { |
||||
|
tag: true, |
||||
|
[tagClass()]: true, |
||||
|
} |
||||
|
|
||||
|
return ( |
||||
|
<article className="media"> |
||||
|
<div className="media-content"> |
||||
|
<div className="content"> |
||||
|
<Link to={`/u/${member.id}`} className="is-size-5">{member.name}</Link> |
||||
|
<br /> |
||||
|
<Link to={`/u/${member.id}`} className="is-size-6 has-text-red">@{member.id}</Link> |
||||
|
<br /> |
||||
|
<span className={classNames(tagClassDictionary)}>{capitalize(member.membership as string)}</span> |
||||
|
</div> |
||||
|
</div> |
||||
|
</article> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default MemberListItem |
@ -0,0 +1,130 @@ |
|||||
|
import React, { FC, useEffect } from 'react' |
||||
|
import { RouteComponentProps, Link } from 'react-router-dom' |
||||
|
|
||||
|
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' |
||||
|
import { faCheckCircle } from '@fortawesome/free-solid-svg-icons' |
||||
|
|
||||
|
import { setTitle } from 'src/utils' |
||||
|
import { Group, GroupMembershipType, User } from 'src/types' |
||||
|
|
||||
|
import PageHeader from 'src/components/page-header' |
||||
|
import MemberList from 'src/components/member-list' |
||||
|
|
||||
|
interface Tab { |
||||
|
id: string |
||||
|
label: string |
||||
|
} |
||||
|
|
||||
|
interface Params { |
||||
|
id: string |
||||
|
tab: string |
||||
|
} |
||||
|
|
||||
|
export interface Props extends RouteComponentProps<Params> { |
||||
|
group?: Group |
||||
|
members?: User[] |
||||
|
fetchGroup: () => void |
||||
|
} |
||||
|
|
||||
|
const GroupAdmin: FC<Props> = ({ group, members = [], fetchGroup, match, history }) => { |
||||
|
useEffect(() => { |
||||
|
fetchGroup() |
||||
|
}, []) |
||||
|
|
||||
|
useEffect(() => { |
||||
|
if (group && group.membership) { |
||||
|
if (group.membership !== GroupMembershipType.Admin) { |
||||
|
history.push(`/c/${group.id}`) |
||||
|
return |
||||
|
} |
||||
|
|
||||
|
setTitle(`${group.name} Administration`) |
||||
|
} |
||||
|
}, [group]) |
||||
|
|
||||
|
if (!group) { |
||||
|
return ( |
||||
|
<div> |
||||
|
<PageHeader title="Group" /> |
||||
|
<div className="main-content"></div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
const selectedTab = match.params.tab ? match.params.tab : '' |
||||
|
const tabs: Tab[] = [ |
||||
|
{ |
||||
|
id: '', |
||||
|
label: 'General', |
||||
|
}, |
||||
|
{ |
||||
|
id: 'members', |
||||
|
label: 'Members', |
||||
|
} |
||||
|
] |
||||
|
|
||||
|
return ( |
||||
|
<div> |
||||
|
<PageHeader title={group.name} subtitle="Administration" /> |
||||
|
|
||||
|
<div className="main-content"> |
||||
|
<div className="centered-content"> |
||||
|
<div className="tabs is-large"> |
||||
|
<ul> |
||||
|
{tabs.map(t => ( |
||||
|
<li key={t.id} className={selectedTab === t.id ? 'is-active': ''}> |
||||
|
<Link to={`/c/${group.id}/admin/${t.id}`}> |
||||
|
{t.label} |
||||
|
</Link> |
||||
|
</li> |
||||
|
))} |
||||
|
</ul> |
||||
|
</div> |
||||
|
|
||||
|
<div className="container"> |
||||
|
{selectedTab === '' && |
||||
|
<div> |
||||
|
<div className="field"> |
||||
|
<label className="label">ID</label> |
||||
|
<div className="control"> |
||||
|
<input className="input" type="text" value={group.id} readOnly /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br /> |
||||
|
|
||||
|
<div className="field"> |
||||
|
<label className="label">Name</label> |
||||
|
<div className="control"> |
||||
|
<input className="input" type="text" value={group.name} readOnly /> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br /> |
||||
|
|
||||
|
<div className="field"> |
||||
|
<label className="label">About</label> |
||||
|
<div className="control"> |
||||
|
<textarea className="textarea" placeholder="About This Community" value={group.about as string}></textarea> |
||||
|
</div> |
||||
|
</div> |
||||
|
<br /> |
||||
|
|
||||
|
<button className="button is-primary"> |
||||
|
<span className="icon is-small"> |
||||
|
<FontAwesomeIcon icon={faCheckCircle} /> |
||||
|
</span> |
||||
|
<span>Save</span> |
||||
|
</button> |
||||
|
</div> |
||||
|
} |
||||
|
|
||||
|
{match.params.tab === 'members' && |
||||
|
<MemberList members={members} /> |
||||
|
} |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
</div> |
||||
|
) |
||||
|
} |
||||
|
|
||||
|
export default GroupAdmin |
@ -0,0 +1,29 @@ |
|||||
|
import { connect } from 'react-redux' |
||||
|
import { handleApiError } from 'src/api/errors' |
||||
|
import { fetchGroup, fetchGroupMembers } from 'src/actions/directory' |
||||
|
import { getGroupMembers } from 'src/selectors/directory' |
||||
|
import { getEntity } from 'src/selectors/entities' |
||||
|
import { AppState, EntityType, Group, AppThunkDispatch } from 'src/types' |
||||
|
|
||||
|
import GroupAdmin, { Props } from './group-admin' |
||||
|
|
||||
|
const mapStateToProps = (state: AppState, ownProps: Props) => ({ |
||||
|
group: getEntity<Group>(state, EntityType.Group, ownProps.match.params.id), |
||||
|
members: getGroupMembers(state, ownProps.match.params.id), |
||||
|
}) |
||||
|
|
||||
|
const mapDispatchToProps = (dispatch: AppThunkDispatch, ownProps: Props) => ({ |
||||
|
fetchGroup: () => { |
||||
|
try { |
||||
|
dispatch(fetchGroup(ownProps.match.params.id)) |
||||
|
dispatch(fetchGroupMembers(ownProps.match.params.id)) |
||||
|
} catch (err) { |
||||
|
handleApiError(err, dispatch, ownProps.history) |
||||
|
} |
||||
|
} |
||||
|
}) |
||||
|
|
||||
|
export default connect( |
||||
|
mapStateToProps, |
||||
|
mapDispatchToProps |
||||
|
)(GroupAdmin) |
@ -1,15 +1,19 @@ |
|||||
import { denormalize } from 'normalizr' |
import { denormalize } from 'normalizr' |
||||
import { createSelector } from 'reselect' |
import { createSelector } from 'reselect' |
||||
|
import filter from 'lodash/filter' |
||||
|
|
||||
import { groupSchema } from '../store/schemas' |
|
||||
|
import { groupSchema, userSchema } from '../store/schemas' |
||||
import { getEntityStore } from './entities' |
import { getEntityStore } from './entities' |
||||
import { AppState, Group } from 'src/types' |
|
||||
|
import { AppState, Group, User, EntityType } from 'src/types' |
||||
|
|
||||
export const getGroupIds = (state: AppState) => state.directory.groups |
export const getGroupIds = (state: AppState) => state.directory.groups |
||||
|
|
||||
export const getGroups = createSelector( |
export const getGroups = createSelector( |
||||
[getEntityStore, getGroupIds], |
[getEntityStore, getGroupIds], |
||||
(entities, groups) => { |
|
||||
return denormalize(groups, [groupSchema], entities) as Group[] |
|
||||
} |
|
||||
|
(entities, groups) => denormalize(groups, [groupSchema], entities) as Group[] |
||||
) |
) |
||||
|
|
||||
|
export const getGroupMembers = (state: AppState, group: string) => { |
||||
|
const users = state.entities[EntityType.User] |
||||
|
return denormalize(filter(users, user => user.group === group), [userSchema], state.entities) as User[] |
||||
|
} |
Write
Preview
Loading…
Cancel
Save
Reference in new issue