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 { createSelector } from 'reselect' |
|||
import filter from 'lodash/filter' |
|||
|
|||
import { groupSchema } from '../store/schemas' |
|||
import { groupSchema, userSchema } from '../store/schemas' |
|||
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 getGroups = createSelector( |
|||
[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