{}}
9 | className='box' />)
10 | expect(wrapper).toMatchSnapshot()
11 | })
12 |
--------------------------------------------------------------------------------
/src/store/actions/trackAnalyticsEvent.js:
--------------------------------------------------------------------------------
1 | import { TRACK_ANALYTICS_EVENT } from 'store/constants'
2 |
3 | export default function trackAnalyticsEvent (eventName, data) {
4 | return {
5 | type: TRACK_ANALYTICS_EVENT,
6 | meta: {
7 | analytics: {
8 | eventName,
9 | ...data
10 | }
11 | }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/PostCard/Feature/Feature.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactPlayer from 'react-player'
3 | import './Feature.scss'
4 |
5 | export default function Feature ({ url }) {
6 | return (
7 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/store/selectors/getMySkills.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 |
4 | const getMySkills = createSelector(
5 | orm,
6 | (session) => {
7 | const me = session.Me.first()
8 | if (!me) return []
9 | return me.skills.toRefArray()
10 | }
11 | )
12 |
13 | export default getMySkills
14 |
--------------------------------------------------------------------------------
/src/components/UploadAttachmentButton/UploadAttachmentButton.scss:
--------------------------------------------------------------------------------
1 | .button {
2 | cursor: pointer;
3 | &:hover {
4 | .icon {
5 | opacity: 0.85;
6 | }
7 | }
8 | }
9 |
10 | .icon {
11 | cursor: pointer;
12 | opacity: 0.5;
13 | font-size: 30px;
14 | color: $color-white;
15 | text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.5);
16 | }
17 |
--------------------------------------------------------------------------------
/src/store/selectors/getPlatformAgreements.js:
--------------------------------------------------------------------------------
1 | import orm from '../models'
2 | import { createSelector as ormCreateSelector } from 'redux-orm'
3 |
4 | const getPlatformAgreements = ormCreateSelector(
5 | orm,
6 | session => {
7 | return session.PlatformAgreement.all().toRefArray()
8 | }
9 | )
10 |
11 | export default getPlatformAgreements
12 |
--------------------------------------------------------------------------------
/src/store/selectors/getPostById.js:
--------------------------------------------------------------------------------
1 | import { createSelector as ormCreateSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 |
4 | // protest selector
5 | const getPostById = ormCreateSelector(
6 | orm,
7 | (state, id) => id,
8 | ({ Post }, id) => {
9 | return Post.withId(id)
10 | }
11 | )
12 |
13 | export default getPostById
14 |
--------------------------------------------------------------------------------
/src/routes/WelcomeWizardRouter/AddLocation/AddLocation.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import AddLocation from './AddLocation'
4 |
5 | describe('AddLocation', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow()
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/routes/WelcomeWizardRouter/UploadPhoto/UploadPhoto.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import UploadPhoto from './UploadPhoto'
4 |
5 | describe('UploadPhoto', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow()
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/components/HyloHTML/HyloHTML.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | export default function HyloHTML ({
4 | html,
5 | element = 'div',
6 | ...props
7 | }) {
8 | return (
9 | React.createElement(
10 | element,
11 | {
12 | ...props,
13 | dangerouslySetInnerHTML: { __html: html }
14 | }
15 | )
16 | )
17 | }
18 |
--------------------------------------------------------------------------------
/src/store/selectors/getPerson.js:
--------------------------------------------------------------------------------
1 | import { createSelector as ormCreateSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 |
4 | const getPerson = ormCreateSelector(
5 | orm,
6 | (state, props) => props.personId,
7 | ({ Person }, personId) => Person.idExists(personId) ? Person.withId(personId) : null
8 | )
9 |
10 | export default getPerson
11 |
--------------------------------------------------------------------------------
/scripts/templates/Component.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 | import './ReplaceComponent.scss'
4 |
5 | const { string } = PropTypes
6 |
7 | export default function ReplaceComponent ({ example }) {
8 | return {example}
9 | }
10 | ReplaceComponent.propTypes = {
11 | example: string
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/CreateGroup/__snapshots__/CreateGroup.connector.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`CreateGroup.connector mapDispatchToProps maps the action generators 1`] = `
4 | Object {
5 | "createGroup": [Function],
6 | "fetchGroupExists": [Function],
7 | "goBack": [Function],
8 | "goToGroup": [Function],
9 | }
10 | `;
11 |
--------------------------------------------------------------------------------
/src/components/Map/Map.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | position: absolute;
3 | z-index: 0;
4 | pointerEvents: none;
5 | border-radius: 4px;
6 | background-color: white;
7 | padding: 0 15px;
8 | color: rgba(87, 102, 123, 1.000);
9 | box-shadow: $box-shadow-default;
10 | line-height: 28px;
11 |
12 | p {
13 | margin: 0;
14 | padding: 0;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/scripts/generate.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const program = require('commander')
3 |
4 | program
5 | .version('0.0.1')
6 | .command('component [ComponentName]', 'create a new component')
7 | .command('store [ComponentName]', 'add a store file for an existing component')
8 | .command('model [ModelName]', 'coming soon to a cli near you!')
9 | .parse(process.argv)
10 |
--------------------------------------------------------------------------------
/src/components/RoundImageRow/RoundImageRow.test.js:
--------------------------------------------------------------------------------
1 | import RoundImageRow from './RoundImageRow'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | it('displays a RoundImage for every url', () => {
6 | const wrapper = shallow()
7 | expect(wrapper.find('RoundImage').length).toEqual(4)
8 | })
9 |
--------------------------------------------------------------------------------
/src/routes/NonAuthLayoutRouter/Login/Login.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from 'util/testing/reactTestingLibraryExtended'
3 | import Login from './Login'
4 |
5 | it('renders correctly', () => {
6 | render(
7 |
8 | )
9 |
10 | expect(screen.getByText('Sign in to Hylo')).toBeInTheDocument()
11 | })
12 |
--------------------------------------------------------------------------------
/src/components/Badge/Badge.test.js:
--------------------------------------------------------------------------------
1 | import Badge from './component'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | it('matches the last snapshot', () => {
6 | const props = {
7 | number: 7,
8 | expanded: true,
9 | onClick: () => {}
10 | }
11 | const wrapper = shallow()
12 | expect(wrapper).toMatchSnapshot()
13 | })
14 |
--------------------------------------------------------------------------------
/src/util/generateTempId.js:
--------------------------------------------------------------------------------
1 | export default function generateTempID (length = 12) {
2 | const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
3 | let result = ''
4 | for (let i = 0; i < length; i++) {
5 | const randomIndex = Math.floor(Math.random() * characters.length)
6 | result += characters[randomIndex]
7 | }
8 | return result
9 | }
10 |
--------------------------------------------------------------------------------
/src/util/testing/extractModelsForTest.js:
--------------------------------------------------------------------------------
1 | import extractModelsFromAction from 'store/reducers/ModelExtractor/extractModelsFromAction'
2 |
3 | export default function extractModelsForTest (data, extractModel = {}, ormSession) {
4 | extractModelsFromAction({
5 | payload: {
6 | data
7 | },
8 | meta: {
9 | extractModel
10 | }
11 | }, ormSession)
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/PostCard/PostCard.connector.test.js:
--------------------------------------------------------------------------------
1 | import { mapDispatchToProps } from './PostCard.connector'
2 |
3 | describe('mapDispatchToProps', () => {
4 | it('', () => {
5 | const props = {
6 | post: {
7 | id: 1
8 | }
9 | }
10 | const dispatchProps = mapDispatchToProps({}, props)
11 | expect(dispatchProps).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/store/actions/fetchPerson.js:
--------------------------------------------------------------------------------
1 | import { FETCH_PERSON } from 'store/constants'
2 | import personQuery from 'graphql/queries/personQuery'
3 |
4 | export default function fetchPerson (id, query = personQuery) {
5 | return {
6 | type: FETCH_PERSON,
7 | graphql: {
8 | query,
9 | variables: { id }
10 | },
11 | meta: { extractModel: 'Person' }
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/components/CreateTopic/CreateTopic.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 |
3 | import { MODULE_NAME, fetchGroupTopic, createTopic } from './CreateTopic.store'
4 |
5 | const mapStateToProps = (state, props) => {
6 | return {
7 | groupTopicExists: state[MODULE_NAME]
8 | }
9 | }
10 |
11 | export default connect(mapStateToProps, { createTopic, fetchGroupTopic })
12 |
--------------------------------------------------------------------------------
/src/components/Widget/PrivacyWidget/PrivacyWidget.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTranslation } from 'react-i18next'
3 |
4 | import './Privacy.scss'
5 |
6 | export default function PrivacyWidget (props) {
7 | const { t } = useTranslation()
8 |
9 | return (
10 |
11 | {t('group privacy settings')}
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/components/Widget/StewardsWidget/StewardsWidget.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 |
3 | import { useTranslation } from 'react-i18next'
4 | import './StewardsWidget.scss'
5 |
6 | export default function StewardsWidget (props) {
7 | const { t } = useTranslation()
8 |
9 | return (
10 |
11 | {t('The stewards go here')}
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/store/presenters/presentComment.js:
--------------------------------------------------------------------------------
1 | export default function presentComment (comment, groupSlug = undefined) {
2 | if (!comment || !comment.post) return
3 | return {
4 | ...comment.ref,
5 | creator: comment.creator.ref,
6 | post: comment.post.ref,
7 | attachments: comment.attachments
8 | .orderBy('position').toModelArray(),
9 | slug: groupSlug
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/TextInput/TextInput.test.js:
--------------------------------------------------------------------------------
1 | import TextInput from './TextInput'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | describe('TextInput', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow( {}}
9 | value={'value'}
10 | />)
11 | expect(wrapper).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/routes/NonAuthLayoutRouter/PasswordReset/PasswordReset.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import PasswordReset from './PasswordReset'
4 |
5 | describe('Signup', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow(, { disableLifecycleMethods: true })
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/routes/NonAuthLayoutRouter/Signup/Signup.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from 'util/testing/reactTestingLibraryExtended'
3 | import Signup from './Signup'
4 |
5 | it('renders correctly', () => {
6 | render(
7 |
8 | )
9 |
10 | expect(screen.getByText('Enter your email to get started:')).toBeInTheDocument()
11 | })
12 |
--------------------------------------------------------------------------------
/src/routes/OAuth/Login/Login.test.js:
--------------------------------------------------------------------------------
1 | import Login from './Login'
2 | import { render, screen } from 'util/testing/reactTestingLibraryExtended'
3 | import React from 'react'
4 |
5 | it('renders correctly', () => {
6 | render(
7 |
8 | )
9 |
10 | expect(screen.getByText('Sign in to Hylo')).toBeInTheDocument()
11 | })
12 |
--------------------------------------------------------------------------------
/src/store/middleware/pendingMiddleware.js:
--------------------------------------------------------------------------------
1 | import { isPromise } from 'util/index'
2 |
3 | export default function pendingMiddleware (store) {
4 | return next => action => {
5 | const { type, payload } = action
6 |
7 | if (isPromise(payload)) {
8 | store.dispatch({ ...action, type: type + '_PENDING', payload: null })
9 | }
10 |
11 | return next(action)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/client/util.js:
--------------------------------------------------------------------------------
1 | // This is the id of the root element in the index.html file
2 | export const rootDomId = 'root'
3 |
4 | export const loadScript = url => {
5 | var script = document.createElement('script')
6 | script.src = url
7 | const promise = new Promise((resolve, reject) => {
8 | script.onload = resolve
9 | })
10 | document.head.appendChild(script)
11 | return promise
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/CheckBox/CheckBox.scss:
--------------------------------------------------------------------------------
1 | .checkbox {
2 | visibility: hidden;
3 | width: 0;
4 | }
5 |
6 | .label {
7 | margin-bottom: 0;
8 | cursor: pointer;
9 | }
10 |
11 | .label-left {
12 | padding-left: 8px;
13 | }
14 |
15 | .icon {
16 | color: $color-caribbean-green;
17 | &:hover {
18 | color: $color-gossamer;
19 | }
20 | vertical-align: sub;
21 | padding-right: 8px;
22 | }
23 |
--------------------------------------------------------------------------------
/src/routes/GroupSidebar/__snapshots__/GroupSidebar.connector.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`mapStateToProps returns the right keys 1`] = `
4 | Object {
5 | "group": Object {
6 | "id": "88",
7 | "slug": "bar",
8 | },
9 | "members": Array [],
10 | "myResponsibilities": Array [],
11 | "slug": "bar",
12 | "stewards": Array [],
13 | }
14 | `;
15 |
--------------------------------------------------------------------------------
/src/store/selectors/getMyJoinRequests.js:
--------------------------------------------------------------------------------
1 | import { createSelector as ormCreateSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 |
4 | const getMyJoinRequests = ormCreateSelector(
5 | orm,
6 | ({ Me, JoinRequest }) => {
7 | const me = Me.first()
8 | if (!me) return []
9 | return me.joinRequests.toModelArray()
10 | }
11 | )
12 |
13 | export default getMyJoinRequests
14 |
--------------------------------------------------------------------------------
/scripts/templates/Component.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | // import getMe from 'store/selectors/getMe'
3 |
4 | export function mapStateToProps (state, props) {
5 | return {
6 | example: 'example'
7 | // currentUser: getMe(state, props)
8 | }
9 | }
10 |
11 | export const mapDispatchToProps = {}
12 |
13 | export default connect(mapStateToProps, mapDispatchToProps)
14 |
--------------------------------------------------------------------------------
/src/components/Icon/Icon.scss:
--------------------------------------------------------------------------------
1 | .icon {
2 | color: $color-rhino-60;
3 | font-size: 24px;
4 | position: relative;
5 | }
6 |
7 | .green {
8 | color: $color-caribbean-green;
9 | }
10 |
11 | .blue {
12 | color: $color-picton-blue;
13 | }
14 |
15 | :global {
16 | .icon-More {
17 | position: relative;
18 | top: 3px;
19 | }
20 | .icon-Replies {
21 | font-size: 17px;
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/Member/__snapshots__/Member.connector.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`mapDispatchToProps goToPerson is correct for "groups" context 1`] = `
4 | Object {
5 | "payload": Object {
6 | "args": Array [
7 | "/groups/anything/members/1",
8 | ],
9 | "method": "push",
10 | },
11 | "type": "@@router/CALL_HISTORY_METHOD",
12 | }
13 | `;
14 |
--------------------------------------------------------------------------------
/src/components/ModalDialog/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ModalDialog from './ModalDialog'
3 | import ReactDOM from 'react-dom'
4 | import { rootDomId } from 'client/util'
5 |
6 | export default function ModalDialogPortal (props) {
7 | return ReactDOM.createPortal(
8 | {props.children},
9 | document.getElementById(rootDomId)
10 | )
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/PostCard/PostTitle/__snapshots__/PostTitle.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`matches last snapshot 1`] = `
4 |
9 |
13 | hello there
14 |
15 |
16 | `;
17 |
--------------------------------------------------------------------------------
/src/routes/WelcomeWizardRouter/WelcomeExplore/WelcomeExplore.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import WelcomeExplore from './WelcomeExplore'
4 |
5 | describe('WelcomeExplore', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow()
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/components/Badge/__snapshots__/Badge.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`matches the last snapshot 1`] = `
4 |
8 |
11 |
14 | 7
15 |
16 |
17 |
18 | `;
19 |
--------------------------------------------------------------------------------
/src/components/Pillbox/Pillbox.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { shallow } from 'enzyme'
3 | import Pillbox from './Pillbox'
4 |
5 | describe('Pillbox', () => {
6 | it('renders', () => {
7 | const wrapper = shallow()
11 | expect(wrapper).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/css/global/index.scss:
--------------------------------------------------------------------------------
1 | // NOTE: font-face declarations need to be outside
2 | // of CSS Modules global calls.
3 | // ref: https://github.com/css-modules/css-modules/issues/95
4 | @import './fonts';
5 |
6 | :global {
7 | @import './bootstrap';
8 | @import './icons';
9 | @import '../typography';
10 | @import '../datepicker';
11 |
12 | body {
13 | overflow-x: hidden;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/routes/AuthLayoutRouter/components/TopNav/LoadingItems/LoadingItems.scss:
--------------------------------------------------------------------------------
1 | .loader {
2 | height: $messages-body-height;
3 |
4 | .loader-image {
5 | background: no-repeat center;
6 | height: 352px;
7 | background-size: 250px;
8 | }
9 | .loader-animation {
10 | width: 39px;
11 | height: 20px;
12 | position: absolute;
13 | top: 112px;
14 | left: 177px;
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/store/selectors/getGroupForCurrentRoute.js:
--------------------------------------------------------------------------------
1 | import orm from 'store/models'
2 | import { createSelector as ormCreateSelector } from 'redux-orm'
3 | import { getSlugFromLocation } from './isGroupRoute'
4 |
5 | const getGroupForCurrentRoute = ormCreateSelector(
6 | orm,
7 | getSlugFromLocation,
8 | (session, slug) => session.Group.safeGet({ slug })
9 | )
10 |
11 | export default getGroupForCurrentRoute
12 |
--------------------------------------------------------------------------------
/src/components/FlagContent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FlagContent from './FlagContent'
3 | import ReactDOM from 'react-dom'
4 | import { rootDomId } from 'client/util'
5 |
6 | const FlagContentPortal = function (props) {
7 | return ReactDOM.createPortal(
8 | ,
9 | document.getElementById(rootDomId)
10 | )
11 | }
12 |
13 | export default FlagContentPortal
14 |
--------------------------------------------------------------------------------
/src/routes/AuthLayoutRouter/components/TopNav/NoItems/NoItems.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { bgImageStyle } from 'util/index'
3 | import './NoItems.scss'
4 |
5 | export default function NoItems ({ message }) {
6 | return (
7 |
11 | )
12 | }
13 |
--------------------------------------------------------------------------------
/src/routes/GroupSettings/ExportDataTab/ExportDataTab.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | // empty
3 | }
4 |
5 | .title {
6 | composes: hdr-display from 'css/typography.scss';
7 | margin-bottom: 10px;
8 | padding: 0
9 | }
10 |
11 | .subtitle {
12 | composes: hdr-headline from 'css/typography.scss';
13 | color: $color-rhino-80;
14 | }
15 |
16 | .help {
17 | color: $color-rhino-60;
18 | margin-bottom: 16px;
19 | }
20 |
--------------------------------------------------------------------------------
/src/routes/MemberProfile/RecentActivity/RecentActivity.scss:
--------------------------------------------------------------------------------
1 | .subhead {
2 | composes: hdr-headline from 'css/typography.scss';
3 | margin: $space-8x 0 $space-6x 0;
4 | }
5 |
6 | .activity-item {
7 | margin: 20px 0;
8 | background-color: rgba(255,255,255,1);
9 | border-radius: 5px;
10 | box-shadow: 0 4px 10px rgb(35 65 90 / 20%);
11 |
12 | a:first-child {
13 | text-decoration: none;
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/store/models/Widget.js:
--------------------------------------------------------------------------------
1 | import { attr, Model } from 'redux-orm'
2 |
3 | class Widget extends Model {
4 | toString () {
5 | return `Widget: ${this.name}`
6 | }
7 | }
8 |
9 | export default Widget
10 |
11 | Widget.modelName = 'Widget'
12 |
13 | Widget.fields = {
14 | id: attr(),
15 | name: attr(),
16 | isVisible: attr(),
17 | order: attr(),
18 | context: attr(),
19 | settings: attr()
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/selectors/getMyMemberships.js:
--------------------------------------------------------------------------------
1 | import { createSelector as ormCreateSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 |
4 | const getMyMemberships = ormCreateSelector(
5 | orm,
6 | ({ Me, Membership }) => {
7 | const me = Me.first()
8 | if (!me) return []
9 | return Membership.filter({ person: me.id }).toModelArray()
10 | }
11 | )
12 |
13 | export default getMyMemberships
14 |
--------------------------------------------------------------------------------
/src/routes/PostDetail/Comments/ShowMore.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './ShowMore.scss'
3 |
4 | export default function ShowMore ({ commentsLength, total, hasMore, fetchComments }) {
5 | if (!hasMore) return null
6 |
7 | const extra = total - commentsLength
8 |
9 | return
10 | View {extra} previous comment{extra > 1 ? 's' : ''}
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/store/actions/fetchNotificationSettings.js:
--------------------------------------------------------------------------------
1 | import { FETCH_NOTIFICATION_SETTINGS } from 'store/constants'
2 |
3 | export default function fetchNoticationSettings (token) {
4 | return {
5 | type: FETCH_NOTIFICATION_SETTINGS,
6 | payload: {
7 | api: {
8 | path: '/noo/user/notification-settings',
9 | method: 'GET',
10 | params: { token }
11 | }
12 | }
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/store/models/PostMembership.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class PostMembership extends Model {
4 | toString () {
5 | return `PostMembership: ${this.id}`
6 | }
7 | }
8 |
9 | export default PostMembership
10 |
11 | PostMembership.modelName = 'PostMembership'
12 | PostMembership.fields = {
13 | id: attr(),
14 | pinned: attr(),
15 | group: fk('Group', 'postMemberships')
16 | }
17 |
--------------------------------------------------------------------------------
/src/store/reducers/ormReducer/clearCacheFor.test.js:
--------------------------------------------------------------------------------
1 | import clearCacheFor from './clearCacheFor'
2 |
3 | describe('clearCacheFor', () => {
4 | it('runs expected Redux ORM cache clear hack', () => {
5 | const ModelClass = {
6 | withId: jest.fn(() => ModelClass),
7 | update: jest.fn()
8 | }
9 | clearCacheFor(ModelClass, '10')
10 | expect(ModelClass.update).toHaveBeenCalled()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/util/filterDeletedUsers.test.js:
--------------------------------------------------------------------------------
1 | import filterDeletedUsers from './filterDeletedUsers'
2 |
3 | it('Returns true for regular user', () => {
4 | const testUser = { name: 'Flowers' }
5 | expect(filterDeletedUsers(testUser)).toBeTruthy()
6 | })
7 |
8 | it('Returns false for deleted user', () => {
9 | const deletedUser = { name: 'Deleted User' }
10 | expect(filterDeletedUsers(deletedUser)).toBeFalsy()
11 | })
12 |
--------------------------------------------------------------------------------
/src/util/webView.js:
--------------------------------------------------------------------------------
1 | export function sendMessageToWebView (type, data) {
2 | if (!type) {
3 | throw new Error('Must provide a message `type` when sending a message to the WebView')
4 | }
5 |
6 | isWebView() && window.ReactNativeWebView.postMessage(JSON.stringify({ type, data }))
7 | }
8 |
9 | export default function isWebView () {
10 | return typeof window !== 'undefined' && window.ReactNativeWebView
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/PeopleTyping/PeopleTyping.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { clearUserTyping, getPeopleTyping } from './PeopleTyping.store'
3 |
4 | export function mapStateToProps (state, props) {
5 | return {
6 | peopleTyping: getPeopleTyping(state)
7 | }
8 | }
9 |
10 | export const mapDispatchToProps = { clearUserTyping }
11 |
12 | export default connect(mapStateToProps, mapDispatchToProps)
13 |
--------------------------------------------------------------------------------
/src/routes/OAuth/Consent/Consent.test.js:
--------------------------------------------------------------------------------
1 | import Consent from './Consent'
2 | import { render, screen } from 'util/testing/reactTestingLibraryExtended'
3 | import React from 'react'
4 |
5 | it('renders correctly', () => {
6 | render(
7 |
8 | )
9 |
10 | expect(screen.getByText('{{appName}} wants access to your Hylo account')).toBeInTheDocument()
11 | })
12 |
--------------------------------------------------------------------------------
/src/store/actions/joinProject.js:
--------------------------------------------------------------------------------
1 | import { JOIN_PROJECT } from 'store/constants'
2 |
3 | export default function (id) {
4 | return {
5 | type: JOIN_PROJECT,
6 | graphql: {
7 | query: `mutation ($id: ID) {
8 | joinProject (id: $id) {
9 | success
10 | }
11 | }`,
12 | variables: {
13 | id
14 | }
15 | },
16 | meta: {
17 | id
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/models/Agreement.js:
--------------------------------------------------------------------------------
1 | import { attr, Model } from 'redux-orm'
2 |
3 | class Agreement extends Model {
4 | toString () {
5 | return `Agreement (${this.id}): ${this.title}`
6 | }
7 | }
8 |
9 | export default Agreement
10 |
11 | Agreement.modelName = 'Agreement'
12 |
13 | Agreement.fields = {
14 | id: attr(),
15 | accepted: attr(),
16 | description: attr(),
17 | order: attr(),
18 | title: attr()
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/models/PlatformAgreement.js:
--------------------------------------------------------------------------------
1 | import { attr, Model } from 'redux-orm'
2 |
3 | class PlatformAgreement extends Model {
4 | toString () {
5 | return `PlatformAgreement (${this.id}): ${this.text}`
6 | }
7 | }
8 |
9 | export default PlatformAgreement
10 |
11 | PlatformAgreement.modelName = 'PlatformAgreement'
12 |
13 | PlatformAgreement.fields = {
14 | id: attr(),
15 | type: attr(),
16 | text: attr()
17 | }
18 |
--------------------------------------------------------------------------------
/tsconfig.json:
--------------------------------------------------------------------------------
1 | {
2 | "compilerOptions": {
3 | "target": "es6",
4 | "jsx": "react",
5 | "checkJs": false,
6 | "allowJs": true,
7 | "allowSyntheticDefaultImports": true,
8 | "module": "commonjs",
9 | "baseUrl": ".",
10 | "paths": {
11 | "*": ["src/*"]
12 | },
13 | "outDir": "build"
14 | },
15 | "exclude": [
16 | "node_modules",
17 | "es5",
18 | "build"
19 | ]
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/Button/__snapshots__/Button.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Button renders correctly 1`] = `
4 |
16 | Log in
17 |
18 | `;
19 |
--------------------------------------------------------------------------------
/src/store/actions/leaveProject.js:
--------------------------------------------------------------------------------
1 | import { LEAVE_PROJECT } from 'store/constants'
2 |
3 | export default function (id) {
4 | return {
5 | type: LEAVE_PROJECT,
6 | graphql: {
7 | query: `mutation ($id: ID) {
8 | leaveProject (id: $id) {
9 | success
10 | }
11 | }`,
12 | variables: {
13 | id
14 | }
15 | },
16 | meta: {
17 | id
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/public/apple-app-site-association:
--------------------------------------------------------------------------------
1 | {
2 | "applinks": {
3 | "apps": [],
4 | "details": [
5 | {
6 | "appID": "L4KZBPS2F3.com.hylo.HyloA",
7 | "paths": [
8 | "/noo/login/token",
9 | "/noo/login/jwt",
10 | "NOT /noo/*",
11 | "*"
12 | ]
13 | }
14 | ]
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/src/store/models/Message.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class Message extends Model {
4 | toString () {
5 | return `Message: ${this.id}`
6 | }
7 | }
8 |
9 | export default Message
10 |
11 | Message.modelName = 'Message'
12 |
13 | Message.fields = {
14 | id: attr(),
15 | text: attr(),
16 | creator: fk('Person'),
17 | createdAt: attr(),
18 | messageThread: fk('MessageThread', 'messages')
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/selectors/getQuerystringParam.js:
--------------------------------------------------------------------------------
1 | import { pick } from 'lodash/fp'
2 | import qs from 'querystring'
3 |
4 | const getQuerystringParam = (key, props) => {
5 | if (!props.location) throw new Error(`getQuerystringParam('${key}') missing props.location`)
6 | const query = qs.parse(props.location.search.substring(1))
7 | return Array.isArray(key) ? pick(key, query) : query[key]
8 | }
9 |
10 | export default getQuerystringParam
11 |
--------------------------------------------------------------------------------
/src/components/FlagGroupContent/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import FlagGroupContent from './FlagGroupContent'
3 | import ReactDOM from 'react-dom'
4 | import { rootDomId } from 'client/util'
5 |
6 | const FlagGroupContentPortal = function (props) {
7 | return ReactDOM.createPortal(
8 | ,
9 | document.getElementById(rootDomId)
10 | )
11 | }
12 |
13 | export default FlagGroupContentPortal
14 |
--------------------------------------------------------------------------------
/src/components/Switch/Switch.test.js:
--------------------------------------------------------------------------------
1 | import Switch from './Switch'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | describe('Switch', () => {
6 | it('renders correctly', () => {
7 | const props = {
8 | value: true,
9 | onClick: () => {},
10 | className: 'switch-class'
11 | }
12 | const wrapper = shallow()
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/src/routes/Messages/CloseMessages/CloseMessages.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { Link } from 'react-router-dom'
3 | import Icon from 'components/Icon'
4 | import './CloseMessages.scss'
5 |
6 | function CloseMessages ({ onCloseLocation }) {
7 | return
8 |
9 |
10 | }
11 |
12 | export default CloseMessages
13 |
--------------------------------------------------------------------------------
/Dockerfile:
--------------------------------------------------------------------------------
1 | # base image
2 | FROM node:8.9.2
3 |
4 | # set working directory
5 | RUN mkdir /usr/src/app
6 | WORKDIR /usr/src/app
7 |
8 | # add `/usr/src/app/node_modules/.bin` to $PATH
9 | ENV PATH /usr/src/app/node_modules/.bin:$PATH
10 |
11 | # install and cache app dependencies
12 | COPY package.json /usr/src/app/package.json
13 | COPY yarn.lock /usr/src/app/yarn.lock
14 |
15 | RUN yarn install
16 |
17 | # start app
18 | CMD ["yarn", "start"]
19 |
--------------------------------------------------------------------------------
/scripts/templates/Component.class.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React, { Component } from 'react'
3 | import './ReplaceComponent.scss'
4 | const { string } = PropTypes
5 |
6 | export default class ReplaceComponent extends Component {
7 | static propTypes = {
8 | example: string
9 | }
10 |
11 | render () {
12 | const { example } = this.props
13 | return {example}
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/PostCard/PostCompletion/PostCompletion.scss:
--------------------------------------------------------------------------------
1 | .postCompletion {
2 | background-color: #F2F2F2;
3 | color: $color-regent;
4 | font-size: 14px;
5 | display: flex;
6 | align-items: center;
7 | justify-content: space-between;
8 | margin: 8px;
9 | padding: 15px;
10 | background: rgba(213, 236, 250, .3);
11 | border: 1px dashed rgba(213, 236, 250, 1);
12 | border-radius: 5px;
13 |
14 | div { line-height: 29px; }
15 | }
16 |
--------------------------------------------------------------------------------
/src/graphql/queries/SubCommentsQuery.graphql:
--------------------------------------------------------------------------------
1 | #import "../fragments/CommentFieldsFragment.graphql"
2 |
3 | query SubCommentsQuery (
4 | $id: ID,
5 | $cursor: ID
6 | ) {
7 | comment(id: $id) {
8 | id
9 | childComments(first: 10, cursor: $cursor, order: "desc") {
10 | items {
11 | ...CommentFields
12 | post {
13 | id
14 | }
15 | }
16 | total
17 | hasMore
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/routes/Messages/CloseMessages/CloseMessages.scss:
--------------------------------------------------------------------------------
1 | .close-messages {
2 | cursor: pointer;
3 | padding: $space-2x 0;
4 |
5 | &:hover {
6 | text-decoration: none;
7 |
8 | .close-messages-icon {
9 | color: $color-rhino-60;
10 | }
11 | }
12 | }
13 |
14 | @media screen and (max-width: 650px) {
15 | .close-messages {
16 | position: absolute;
17 | left: 7px;
18 | z-index: 5;
19 | top: 22px;
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/store/models/PersonConnection.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class PersonConnection extends Model {
4 | toString () {
5 | return `PersonConnection: ${this.type}`
6 | }
7 | }
8 |
9 | export default PersonConnection
10 |
11 | PersonConnection.modelName = 'PersonConnection'
12 | PersonConnection.fields = {
13 | person: fk('Person'),
14 | type: attr(),
15 | createdAt: attr(),
16 | updatedAt: attr()
17 | }
18 |
--------------------------------------------------------------------------------
/src/components/Affiliation/__snapshots__/Affiliation.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Affiliation matches last snapshot 1`] = `
4 |
7 |
10 | Cheesemonger
11 |
12 |
13 | at
14 |
15 |
18 | La Fromagerie
19 |
20 |
21 | `;
22 |
--------------------------------------------------------------------------------
/src/store/selectors/isGroupRoute.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import getRouteParam from './getRouteParam'
3 |
4 | const isGroupRoute = createSelector(
5 | getSlugFromLocation,
6 | slug => !!slug
7 | )
8 |
9 | export default isGroupRoute
10 |
11 | export function getSlugFromLocation (state, props) {
12 | return getRouteParam('detailGroupSlug', props, false) || getRouteParam('groupSlug', props, false) || props.groupSlug
13 | }
14 |
--------------------------------------------------------------------------------
/src/graphql/fragments/groupsQueryFragment.js:
--------------------------------------------------------------------------------
1 | import groupFieldsFragment from 'graphql/fragments/groupFieldsFragment'
2 |
3 | const groupsQueryFragment = `
4 | groups(
5 | boundingBox: $boundingBox,
6 | context: $context,
7 | parentSlugs: $parentSlugs,
8 | search: $search,
9 | sortBy: $sortBy,
10 | visibility: $visibility
11 | ) {
12 | items {
13 | ${groupFieldsFragment(false)}
14 | }
15 | }`
16 |
17 | export default groupsQueryFragment
18 |
--------------------------------------------------------------------------------
/src/routes/UserSettings/AccountSettingsTab/AccountSettingsTab.test.js:
--------------------------------------------------------------------------------
1 | import AccountSettingsTab from './AccountSettingsTab'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | describe('AccountSettingsTab', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow( {}} />)
10 | expect(wrapper).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # See http://help.github.com/ignore-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | node_modules
5 |
6 | # testing
7 | coverage
8 |
9 | # production
10 | build
11 | build-hc
12 | es5
13 |
14 | # local config
15 | config/ssl
16 |
17 | # misc
18 | .DS_Store
19 | .env
20 | .env.old
21 | .env.production
22 | npm-debug.log
23 |
24 | .floo
25 | .flooignore
26 |
27 | yarn-error.log
28 | .idea
29 | yarn.lock
30 | .yarn
31 | .yarnrc.yml
32 |
--------------------------------------------------------------------------------
/src/components/SendAnnouncementModal/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import ReactDOM from 'react-dom'
3 | import SendAnnouncementModal from './SendAnnouncementModal'
4 | import { rootDomId } from 'client/util'
5 |
6 | const SendAnnouncementPortal = function (props) {
7 | return ReactDOM.createPortal(
8 | ,
9 | document.getElementById(rootDomId)
10 | )
11 | }
12 |
13 | export default SendAnnouncementPortal
14 |
--------------------------------------------------------------------------------
/src/graphql/queries/MessageThreadMessagesQuery.graphql:
--------------------------------------------------------------------------------
1 | query MessageThreadMessagesQuery ($id: ID, $cursor: ID) {
2 | messageThread (id: $id) {
3 | id
4 | messages(first: 80, cursor: $cursor, order: "desc") {
5 | items {
6 | id
7 | text
8 | createdAt
9 | creator {
10 | id
11 | name
12 | avatarUrl
13 | }
14 | }
15 | total
16 | hasMore
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/graphql/queries/PeopleQuery.graphql:
--------------------------------------------------------------------------------
1 | query PeopleQuery (
2 | $first: Int,
3 | $autocomplete: String,
4 | $groupIds: [ID],
5 | $offset: Int
6 | ) {
7 | groups(groupIds: $groupIds) {
8 | items {
9 | id
10 | members(first: $first, offset: $offset, search: $autocomplete, sortBy: "name", order: "asc") {
11 | items {
12 | id
13 | name
14 | avatarUrl
15 | }
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/routes/GroupSettings/DeleteSettingsTab/DeleteSettingsTab.test.js:
--------------------------------------------------------------------------------
1 | import DeleteSettingsTab from './DeleteSettingsTab'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | it('renders correctly', () => {
6 | const group = {
7 | id: 1,
8 | name: 'Hylo'
9 | }
10 |
11 | const wrapper = shallow( {}}
14 | />)
15 | expect(wrapper).toMatchSnapshot()
16 | })
17 |
--------------------------------------------------------------------------------
/src/routes/NonAuthLayoutRouter/Signup/VerifyEmail/VerifyEmail.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from 'util/testing/reactTestingLibraryExtended'
3 | import VerifyEmail from './VerifyEmail'
4 |
5 | it('renders correctly', async () => {
6 | render(
7 |
8 | )
9 |
10 | expect(screen.getByText("We've sent a 6 digit code", { exact: false })).toBeInTheDocument()
11 | })
12 |
--------------------------------------------------------------------------------
/src/store/actions/checkIsPostPublic.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag'
2 |
3 | export default function checkIsPostPublic (postId) {
4 | return {
5 | type: 'IS_POST_PUBLIC',
6 | graphql: {
7 | query: gql`
8 | query CheckIsPostPublic ($id: ID) {
9 | post (id: $id) {
10 | id
11 | }
12 | }
13 | `,
14 | variables: { id: postId }
15 | },
16 | meta: { extractModel: 'Post' }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/store/presenters/presentCollection.js:
--------------------------------------------------------------------------------
1 | import presentPost from 'store/presenters/presentPost'
2 |
3 | export default function presentCollection (collection) {
4 | if (!collection) return null
5 |
6 | const linkedPosts = collection.linkedPosts.toModelArray()
7 | return {
8 | ...collection.ref,
9 | posts: linkedPosts.length > 0 ? linkedPosts.map(lp => presentPost(lp.post)) : collection.posts.toModelArray().map(p => presentPost(p))
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/components/NotFound/NotFound.connector.js:
--------------------------------------------------------------------------------
1 | import { goBack, push } from 'connected-react-router'
2 | import { withRouter } from 'react-router-dom'
3 | import { connect } from 'react-redux'
4 |
5 | function mapDispatchToProps (dispatch, props) {
6 | return {
7 | goBack: () =>
8 | dispatch(props.history.length > 2 ? goBack() : push('/'))
9 | }
10 | }
11 |
12 | export default component =>
13 | withRouter(connect(null, mapDispatchToProps)(component))
14 |
--------------------------------------------------------------------------------
/src/routes/WelcomeWizardRouter/AddLocation/AddLocation.connector.test.js:
--------------------------------------------------------------------------------
1 | import { mapDispatchToProps } from './AddLocation.connector'
2 | describe('AddLocation', () => {
3 | it('should call updateUserSettings', () => {
4 | const dispatch = jest.fn(x => x)
5 | const props = {}
6 | const name = 'My Name'
7 | const dispatchProps = mapDispatchToProps(dispatch, props)
8 | expect(dispatchProps.updateUserSettings(name)).toMatchSnapshot()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/routes/WelcomeWizardRouter/UploadPhoto/UploadPhoto.connector.test.js:
--------------------------------------------------------------------------------
1 | import { mapDispatchToProps } from './UploadPhoto.connector'
2 | describe('Upload Photo', () => {
3 | it('should call updateUserSettings', () => {
4 | const dispatch = jest.fn(x => x)
5 | const props = {}
6 | const name = 'My Name'
7 | const dispatchProps = mapDispatchToProps(dispatch, props)
8 | expect(dispatchProps.updateUserSettings(name)).toMatchSnapshot()
9 | })
10 | })
11 |
--------------------------------------------------------------------------------
/src/components/Member/Member.connector.test.js:
--------------------------------------------------------------------------------
1 | import { mapDispatchToProps } from './Member.connector'
2 |
3 | describe('mapDispatchToProps', () => {
4 | it('goToPerson is correct for "groups" context', () => {
5 | const dispatch = jest.fn(x => x)
6 | const props = {
7 | context: 'groups'
8 | }
9 | const dispatchProps = mapDispatchToProps(dispatch, props)
10 | expect(dispatchProps.goToPerson(1, 'anything')()).toMatchSnapshot()
11 | })
12 | })
13 |
--------------------------------------------------------------------------------
/src/components/SkillsToLearnSection/SkillsToLearnSection.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTranslation } from 'react-i18next'
3 | import SkillsSection from '../SkillsSection/SkillsSection'
4 |
5 | const SkillsToLearnSection = (props) => {
6 | const { t } = useTranslation()
7 | return
8 | }
9 |
10 | export default SkillsToLearnSection
11 |
--------------------------------------------------------------------------------
/src/store/models/GroupTopic.js:
--------------------------------------------------------------------------------
1 | import { attr, Model, fk } from 'redux-orm'
2 |
3 | class GroupTopic extends Model {
4 | toString () {
5 | return `GroupTopic: ${this.topic}`
6 | }
7 | }
8 |
9 | export default GroupTopic
10 |
11 | GroupTopic.modelName = 'GroupTopic'
12 |
13 | GroupTopic.fields = {
14 | id: attr(),
15 | topic: fk('Topic', 'groupTopics'),
16 | group: fk('Group', 'groupTopics'),
17 | postsTotal: attr(),
18 | followersTotal: attr()
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/models/ModerationAction.js:
--------------------------------------------------------------------------------
1 | import { attr, Model } from 'redux-orm'
2 |
3 | class ModerationAction extends Model {
4 | toString () {
5 | return `ModerationAction (${this.id}): ${this.title}`
6 | }
7 | }
8 |
9 | export default ModerationAction
10 |
11 | ModerationAction.modelName = 'ModerationAction'
12 |
13 | ModerationAction.fields = {
14 | id: attr(),
15 | groupId: attr(),
16 | postId: attr(),
17 | status: attr(),
18 | text: attr()
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/CheckBox/__snapshots__/CheckBox.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`renders correctly 1`] = `
4 |
20 | `;
21 |
--------------------------------------------------------------------------------
/src/components/Switch/Switch.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './Switch.scss'
3 |
4 | export default function Switch ({
5 | value,
6 | onClick,
7 | className
8 | }) {
9 | return
15 | }
16 |
--------------------------------------------------------------------------------
/src/server/newrelic.js:
--------------------------------------------------------------------------------
1 | const newrelic = process.env.NEW_RELIC_LICENSE_KEY && process.env.NODE_ENV !== 'test'
2 | ? require('newrelic')
3 | : null
4 |
5 | export function getBrowserSnippet () {
6 | if (!newrelic) return ''
7 | // FIXME: this should be changed to represent the actual page being loaded.
8 | // it's here for now only because it's required for the browser snippet
9 | newrelic.setTransactionName('/')
10 | return newrelic.getBrowserTimingHeader()
11 | }
12 |
--------------------------------------------------------------------------------
/src/store/models/Topic.js:
--------------------------------------------------------------------------------
1 | import { attr, Model } from 'redux-orm'
2 |
3 | export const TOPIC_VISIBILITY = {
4 | 0: 'Hidden',
5 | 1: 'Visible',
6 | 2: 'Pinned'
7 | }
8 |
9 | class Topic extends Model {
10 | toString () {
11 | return `Topic: ${this.name}`
12 | }
13 | }
14 |
15 | export default Topic
16 |
17 | Topic.modelName = 'Topic'
18 |
19 | Topic.fields = {
20 | id: attr(),
21 | name: attr(),
22 | postsTotal: attr(),
23 | followersTotal: attr()
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/selectors/getGroupForDetails.js:
--------------------------------------------------------------------------------
1 | import { createSelector as ormCreateSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 | import getRouteParam from 'store/selectors/getRouteParam'
4 |
5 | const getGroupForDetail = ormCreateSelector(
6 | orm,
7 | (state, props) => props.slug || getRouteParam('detailGroupSlug', props) || getRouteParam('groupSlug', props),
8 | ({ Group }, slug) => Group.safeGet({ slug })
9 | )
10 |
11 | export default getGroupForDetail
12 |
--------------------------------------------------------------------------------
/src/components/PostCard/EventDate/EventDate.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Moment from 'moment-timezone'
3 | import './EventDate.scss'
4 |
5 | export default function EventDate ({ startTime }) {
6 | if (!startTime) return null
7 | const startTimeMoment = Moment(startTime)
8 | return
9 | {startTimeMoment.format('MMM')}
10 | {startTimeMoment.format('D')}
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/scripts/build-es5.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | if ! test -e package.json; then
3 | echo "Run this script from the app's root directory."
4 | exit 1
5 | fi
6 |
7 | export NODE_ENV=production
8 | for dir in src scripts; do
9 | ./node_modules/.bin/babel --config-file ./babel.config.js --env-name server $dir --out-dir es5/$dir
10 | done
11 |
12 | for file in `find src -name "*.scss"`; do
13 | dest=`echo $file | sed 's/^/es5\//'`
14 | mkdir -p `dirname $dest`
15 | cp $file $dest
16 | done
17 |
--------------------------------------------------------------------------------
/src/routes/GroupSettings/ImportExportSettingsTab/ImportExportSettingsTab.test.js:
--------------------------------------------------------------------------------
1 | import ImportExportSettingsTab from './ImportExportSettingsTab'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | it('renders correctly', () => {
6 | const group = {
7 | id: 1,
8 | name: 'Hylo'
9 | }
10 |
11 | const wrapper = shallow( {}}
14 | />)
15 | expect(wrapper).toMatchSnapshot()
16 | })
17 |
--------------------------------------------------------------------------------
/src/store/models/Activity.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class Activity extends Model {
4 | toString () {
5 | return `Message: ${this.id}`
6 | }
7 | }
8 |
9 | export default Activity
10 |
11 | Activity.modelName = 'Activity'
12 |
13 | Activity.fields = {
14 | id: attr(),
15 | actor: fk('Person'),
16 | post: fk('Post'),
17 | comment: fk('Comment'),
18 | group: fk('Group'),
19 | unread: attr(),
20 | action: attr(),
21 | meta: attr()
22 | }
23 |
--------------------------------------------------------------------------------
/src/store/models/Comment.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class Comment extends Model {
4 | toString () {
5 | return `Comment: ${this.name}`
6 | }
7 | }
8 |
9 | export default Comment
10 |
11 | Comment.modelName = 'Comment'
12 |
13 | Comment.fields = {
14 | id: attr(),
15 | text: attr(),
16 | creator: fk('Person', 'comments'),
17 | post: fk('Post', 'comments'),
18 | parentComment: fk('Comment', 'childComments'),
19 | createdAt: attr()
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/selectors/getLastViewedGroup.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import getMe from './getMe'
3 |
4 | export const getLastViewedGroup = createSelector(
5 | getMe,
6 | currentUser => {
7 | if (currentUser?.memberships.count() > 0) {
8 | return currentUser
9 | .memberships
10 | .orderBy(m => new Date(m.lastViewedAt), 'desc')
11 | .first()
12 | .group
13 | }
14 | }
15 | )
16 |
17 | export default getLastViewedGroup
18 |
--------------------------------------------------------------------------------
/src/components/SettingsControl/SettingsControl.test.js:
--------------------------------------------------------------------------------
1 | import SettingsControl from './SettingsControl'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | describe('SettingsControl', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow()
8 | expect(wrapper.find('label').text()).toEqual('A Control')
9 | expect(wrapper.find('input').prop('value')).toEqual('the value')
10 | })
11 | })
12 |
--------------------------------------------------------------------------------
/src/store/actions/deleteComment.js:
--------------------------------------------------------------------------------
1 | import { DELETE_COMMENT } from 'store/constants'
2 |
3 | export default function deleteComment (id) {
4 | return {
5 | type: DELETE_COMMENT,
6 | graphql: {
7 | query: `mutation DeleteComment ($id: ID) {
8 | deleteComment(id: $id) {
9 | success
10 | }
11 | }`,
12 | variables: {
13 | id
14 | }
15 | },
16 | meta: {
17 | optimistic: true,
18 | id
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/store/actions/deletePost.js:
--------------------------------------------------------------------------------
1 | import { DELETE_POST } from 'store/constants'
2 |
3 | export default function deletePost (id, groupId) {
4 | return {
5 | type: DELETE_POST,
6 | graphql: {
7 | query: `mutation ($id: ID) {
8 | deletePost(id: $id) {
9 | success
10 | }
11 | }`,
12 | variables: {
13 | id
14 | }
15 | },
16 | meta: {
17 | optimistic: true,
18 | id,
19 | groupId
20 | }
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/src/components/PostListRow/PostListRow.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { push } from 'connected-react-router'
3 | import { postUrl } from 'util/navigation'
4 |
5 | export function mapDispatchToProps (dispatch, props) {
6 | const { post, routeParams, querystringParams } = props
7 |
8 | return {
9 | showDetails: () => dispatch(push(postUrl(post.id, routeParams, querystringParams)))
10 | }
11 | }
12 |
13 | export default connect(() => ({}), mapDispatchToProps)
14 |
--------------------------------------------------------------------------------
/src/store/actions/checkIsPublicGroup.js:
--------------------------------------------------------------------------------
1 | import gql from 'graphql-tag'
2 |
3 | export default function checkIsPublicGroup (groupSlug) {
4 | return {
5 | type: 'IS_GROUP_PUBLIC',
6 | graphql: {
7 | query: gql`
8 | query CheckIsGroupPublic ($slug: String) {
9 | group (slug: $slug) {
10 | visibility
11 | }
12 | }
13 | `,
14 | variables: { slug: groupSlug }
15 | },
16 | meta: { extractModel: 'Group' }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/store/actions/sendPasswordReset.js:
--------------------------------------------------------------------------------
1 | import { SEND_PASSWORD_RESET } from 'store/constants'
2 |
3 | export default function sendPasswordReset (email) {
4 | return {
5 | type: SEND_PASSWORD_RESET,
6 | graphql: {
7 | query: `
8 | mutation SendPasswordReset ($email: String!) {
9 | sendPasswordReset(email: $email) {
10 | success
11 | }
12 | }
13 | `,
14 | variables: {
15 | email
16 | }
17 | }
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/util/asyncDebounce.js:
--------------------------------------------------------------------------------
1 | import { debounce } from 'lodash/fp'
2 |
3 | // From: https://github.com/lodash/lodash/issues/4815#issuecomment-815866904
4 | export const asyncDebounce = (wait, func) => {
5 | const debounced = debounce(wait, (resolve, reject, args) => {
6 | func(...args).then(resolve).catch(reject)
7 | })
8 |
9 | return (...args) =>
10 | new Promise((resolve, reject) => {
11 | debounced(resolve, reject, args)
12 | })
13 | }
14 |
15 | export default asyncDebounce
16 |
--------------------------------------------------------------------------------
/src/components/PostCard/PeopleInfo/PeopleInfo.scss:
--------------------------------------------------------------------------------
1 | .people-container {
2 | display: flex;
3 | align-items: center;
4 | }
5 |
6 | .people {
7 | margin-right: 4px;
8 | white-space: nowrap;
9 | display: inline-block;
10 | vertical-align: middle;
11 | }
12 |
13 | .caption {
14 | composes: caption-lt-lg from 'css/typography.scss';
15 | }
16 |
17 | .constrained .caption {
18 | font-size: 10px;
19 | line-height: 12px;
20 | }
21 |
22 | .constrained .people {
23 | margin-right: 5px;
24 | }
25 |
--------------------------------------------------------------------------------
/src/routes/Messages/PeopleSelector/PeopleListItem/PeopleListItem.test.js:
--------------------------------------------------------------------------------
1 | import PeopleListItem from './PeopleListItem'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | it('matches the last snapshot', () => {
6 | const person = {
7 | active: true,
8 | id: '1',
9 | name: 'Wombat',
10 | avatarUrl: 'https://wombat.life'
11 | }
12 | const wrapper = shallow( {}} person={person} />)
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/store/reducers/locationHistory.js:
--------------------------------------------------------------------------------
1 | import { LOCATION_CHANGE } from 'connected-react-router'
2 |
3 | const initialState = {
4 | previousLocation: null,
5 | currentLocation: null
6 | }
7 |
8 | export default (state = initialState, action) => {
9 | switch (action.type) {
10 | case LOCATION_CHANGE:
11 | return {
12 | previousLocation: state.currentLocation,
13 | currentLocation: action.payload.location
14 | }
15 | default:
16 | return state
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/store/selectors/getMyGroups.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 | import getMyMemberships from 'store/selectors/getMyMemberships'
4 |
5 | export const getMyGroups = createSelector(
6 | orm,
7 | getMyMemberships,
8 | (_, memberships) => {
9 | return memberships
10 | .map(m => ({ ...m.group.ref, newPostCount: m.newPostCount }))
11 | .sort((a, b) => a.name.localeCompare(b.name))
12 | }
13 | )
14 |
15 | export default getMyGroups
16 |
--------------------------------------------------------------------------------
/src/components/SimpleTabBar/SimpleTabBar.js:
--------------------------------------------------------------------------------
1 | import { capitalize } from 'lodash/fp'
2 | import React from 'react'
3 |
4 | import './SimpleTabBar.scss'
5 |
6 | export default function SimpleTabBar ({ currentTab, tabNames, selectTab }) {
7 | return
8 | {tabNames.map(name =>
9 | - selectTab(name)}>
12 | {capitalize(name)}
13 |
)}
14 |
15 | }
16 |
--------------------------------------------------------------------------------
/src/routes/UserSettings/PaymentSettingsTab/PaymentSettingsTab.test.js:
--------------------------------------------------------------------------------
1 | import PaymentSettingsTab from './PaymentSettingsTab'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | describe('PaymentSettingsTab', () => {
6 | it('renders correctly', () => {
7 | const wrapper = shallow( true }}
9 | updateUserSettings={() => {}}
10 | queryParams={{}} />)
11 | expect(wrapper).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/routes/UserSettings/UserSettings.store.test.js:
--------------------------------------------------------------------------------
1 | import { updateAllMemberships, registerStripeAccount } from './UserSettings.store'
2 |
3 | describe('updateAllMemberships', () => {
4 | it('matches snapshot', () => {
5 | expect(updateAllMemberships([1, 3, 5], { sendEmail: true })).toMatchSnapshot()
6 | })
7 | })
8 |
9 | describe('registerStripeAccount', () => {
10 | it('matches snapshot', () => {
11 | expect(registerStripeAccount('anauthorizationcodexyz')).toMatchSnapshot()
12 | })
13 | })
14 |
--------------------------------------------------------------------------------
/src/store/actions/fetchPosts.test.js:
--------------------------------------------------------------------------------
1 | import fetchPosts from './fetchPosts'
2 |
3 | it('works for a group', () => {
4 | expect(fetchPosts({
5 | context: 'groups',
6 | id: 'foo',
7 | offset: 20,
8 | search: 'gardening',
9 | filter: 'offer'
10 | })).toMatchSnapshot()
11 | })
12 |
13 | it('works for all groups', () => {
14 | expect(fetchPosts({
15 | context: 'all',
16 | offset: 20,
17 | search: 'graphic design',
18 | filter: 'request'
19 | })).toMatchSnapshot()
20 | })
21 |
--------------------------------------------------------------------------------
/src/components/Switch/__snapshots__/Switch.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`Switch renders correctly 1`] = `
4 |
22 | `;
23 |
--------------------------------------------------------------------------------
/src/components/GroupButton/__snapshots__/GroupButton.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`GroupButton matches last snapshot 1`] = `
4 |
20 | `;
21 |
--------------------------------------------------------------------------------
/src/graphql/fragments/CommentFieldsFragment.graphql:
--------------------------------------------------------------------------------
1 | fragment CommentFields on Comment {
2 | id
3 | text
4 | creator {
5 | id
6 | name
7 | avatarUrl
8 | }
9 | myReactions {
10 | emojiFull
11 | id
12 | }
13 | commentReactions {
14 | emojiFull
15 | id
16 | user {
17 | id
18 | name
19 | }
20 | }
21 | attachments {
22 | id
23 | position
24 | type
25 | url
26 | }
27 | parentComment {
28 | id
29 | }
30 | createdAt
31 | editedAt
32 | }
33 |
--------------------------------------------------------------------------------
/src/routes/AuthLayoutRouter/components/TopNav/NoItems/NoItems.scss:
--------------------------------------------------------------------------------
1 | .no-items {
2 | text-align: center;
3 | height: $messages-body-height;
4 |
5 | h3 {
6 | color: $color-dove-gray-50;
7 | font-size: 25px;
8 | margin-top: 25px;
9 | padding: 10px 30px;
10 | position: absolute;
11 | text-align: center;
12 | width: 100%;
13 | }
14 | .image {
15 | background: no-repeat center;
16 | height: $messages-body-height;
17 | background-size: calc($messages-body-height/2);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/src/store/selectors/getPost.js:
--------------------------------------------------------------------------------
1 | import { createSelector as ormCreateSelector } from 'redux-orm'
2 | import orm from 'store/models'
3 | import getRouteParam from 'store/selectors/getRouteParam'
4 | import getQuerystringParam from 'store/selectors/getQuerystringParam'
5 |
6 | const getPost = ormCreateSelector(
7 | orm,
8 | (state, props) => getRouteParam('postId', props) || getQuerystringParam('fromPostId', props),
9 | ({ Post }, id) => {
10 | return Post.withId(id)
11 | }
12 | )
13 |
14 | export default getPost
15 |
--------------------------------------------------------------------------------
/src/components/Tooltip/Tooltip.scss:
--------------------------------------------------------------------------------
1 | .tooltip {
2 | font-size: 12px;
3 | padding: 5px 12px;
4 | border-radius: 24px !important;
5 | font-weight: bold;
6 | // @see Tooltip.js - styles are applied as props
7 | // background: $color-white !important;
8 | // color: $color-member !important;
9 | // border: 1px solid $color-picton-blue !important;
10 | box-shadow: $box-shadow-tooltip;
11 | }
12 |
13 |
14 | @media screen and (max-width: 500px) {
15 | .tooltip {
16 | display: none !important;
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/src/components/TopicSupportComingSoon/TopicSupportComingSoon.scss:
--------------------------------------------------------------------------------
1 | .container {
2 | margin-top: 50px;
3 | text-align: center;
4 | }
5 |
6 | .back-button {
7 | text-align: center;
8 | cursor: pointer;
9 | background-color: $color-caribbean-green;
10 | }
11 |
12 | .axolotl-digging-image {
13 | width: 300px;
14 | height: 300px;
15 | display: block;
16 | margin: 0 auto;
17 | }
18 |
19 | .gray-text {
20 | color: $color-rhino-60;
21 | margin-bottom: 30px;
22 | font-size: 80%;
23 | line-height: 180%;
24 | }
25 |
--------------------------------------------------------------------------------
/src/routes/Messages/PeopleSelector/MatchingPeopleListItem/__snapshots__/MatchingPeopleListItem.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`matches last snapshot 1`] = `
4 |
7 |
11 |
14 |
15 |
19 |
20 |
21 | `;
22 |
--------------------------------------------------------------------------------
/src/store/actions/fetchForCurrentUser.js:
--------------------------------------------------------------------------------
1 | import { get } from 'lodash/fp'
2 | import { FETCH_FOR_CURRENT_USER } from 'store/constants'
3 | import MeQuery from 'graphql/queries/MeQuery'
4 |
5 | export default function fetchForCurrentUser () {
6 | return {
7 | type: FETCH_FOR_CURRENT_USER,
8 | graphql: {
9 | query: MeQuery
10 | },
11 | meta: {
12 | extractModel: [
13 | {
14 | getRoot: get('me'),
15 | modelName: 'Me'
16 | }
17 | ]
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/components/CommentCard/CommentCard.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import { push } from 'connected-react-router'
3 | import { postUrl } from 'util/navigation'
4 |
5 | export function mapStateToProps (state, props) {
6 | return { }
7 | }
8 |
9 | export function mapDispatchToProps (dispatch, props) {
10 | return {
11 | showDetails: () =>
12 | dispatch(push(postUrl(props.comment.post.id, props.routeParams)))
13 | }
14 | }
15 |
16 | export default connect(mapStateToProps, mapDispatchToProps)
17 |
--------------------------------------------------------------------------------
/src/routes/GroupSettings/InviteSettingsTab/InviteSettingsTab.test.js:
--------------------------------------------------------------------------------
1 | import InviteSettingsTab from './InviteSettingsTab'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | it('renders correctly', () => {
6 | const group = {
7 | id: 1,
8 | name: 'Hylo'
9 | }
10 |
11 | const wrapper = shallow( {}}
14 | inviteLink='http://www.hylo.com/c/hylo/join/lalala'
15 | />)
16 | expect(wrapper).toMatchSnapshot()
17 | })
18 |
--------------------------------------------------------------------------------
/scripts/templates/Component.connectorWithStore.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import {
3 | fetchExample,
4 | getExample
5 | } from './Component.store'
6 | // import getMe from 'store/selectors/getMe'
7 |
8 | export function mapStateToProps (state, props) {
9 | return {
10 | example: getExample(state, props)
11 | // currentUser: getMe(state, props)
12 | }
13 | }
14 |
15 | export const mapDispatchToProps = {
16 | fetchExample
17 | }
18 |
19 | export default connect(mapStateToProps, mapDispatchToProps)
20 |
--------------------------------------------------------------------------------
/src/components/BadgedIcon/component.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import Icon from '../Icon'
3 | import cx from 'classnames'
4 | import './component.scss'
5 |
6 | export default function BadgedIcon (props) {
7 | const { className, showBadge, green, ...rest } = props
8 | const styleNames = cx({ green }, showBadge ? 'badge' : 'badge-hidden')
9 | return (
10 |
11 |
12 |
13 | )
14 | }
15 |
--------------------------------------------------------------------------------
/src/routes/OAuth/Consent/Consent.store.js:
--------------------------------------------------------------------------------
1 | export const OAUTH_CONFIRM = 'OAuth/CONSENT_CONFIRM'
2 | export const OAUTH_CANCEL = 'OAuth/CANCEL'
3 |
4 | export function confirm (uid) {
5 | return {
6 | type: OAUTH_CONFIRM,
7 | payload: {
8 | api: { method: 'post', path: `/noo/oidc/${uid}/confirm` }
9 | }
10 | }
11 | }
12 |
13 | export function cancel (uid) {
14 | return {
15 | type: OAUTH_CANCEL,
16 | payload: {
17 | api: { method: 'post', path: `/noo/oidc/${uid}/abort` }
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/actions/fetchPeople.test.js:
--------------------------------------------------------------------------------
1 | import fetchPeople from 'store/actions/fetchPeople'
2 |
3 | it('matches the last snapshot', () => {
4 | const graphql = {
5 | query: 'All the lonely people / Where do they all come from?',
6 | variables: {
7 | autocomplete: 'Tchaikovs',
8 | first: 100
9 | }
10 | }
11 | const { query, variables } = graphql
12 | const actual = fetchPeople({ autocomplete: variables.autocomplete, groupIds: [], first: variables.first, query })
13 | expect(actual).toMatchSnapshot()
14 | })
15 |
--------------------------------------------------------------------------------
/src/store/middleware/userBlockingMiddleware.js:
--------------------------------------------------------------------------------
1 | import { BLOCK_USER, UNBLOCK_USER } from '../constants'
2 | import fetchForCurrentUser from 'store/actions/fetchForCurrentUser'
3 | import resetStore from '../actions/resetStore'
4 |
5 | export default function userBlockingMiddleware (store) {
6 | return next => action => {
7 | if (action.type === BLOCK_USER || action.type === UNBLOCK_USER) {
8 | store.dispatch(resetStore())
9 | store.dispatch(fetchForCurrentUser())
10 | }
11 | return next(action)
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/src/store/models/Invitation.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class Invitation extends Model {
4 | toString () {
5 | return `Invitation: ${this.id}`
6 | }
7 | }
8 |
9 | export default Invitation
10 |
11 | Invitation.modelName = 'Invitation'
12 | Invitation.fields = {
13 | id: attr(),
14 | email: attr(),
15 | createdAt: attr(),
16 | creator: fk('Person', 'createdInvites'),
17 | group: fk('Group', 'pendingInvitations'),
18 | lastSentAt: attr(),
19 | resent: attr(),
20 | token: attr()
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Badge/component.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import './component.scss'
3 | import cx from 'classnames'
4 |
5 | export default function Badge ({ number, expanded, className, border, onClick }) {
6 | if (!number) return null
7 | return
8 |
9 | {number}
10 |
11 |
12 | }
13 |
--------------------------------------------------------------------------------
/src/components/RemovableListItem/RemovableListItem.scss:
--------------------------------------------------------------------------------
1 | .item {
2 | display: flex;
3 | align-items: center;
4 | min-height: 52px;
5 | border-bottom: 1px solid $color-ghost;
6 | }
7 |
8 | .avatar {
9 | margin-right: $space-4x;
10 | }
11 |
12 | .name {
13 | composes: bdy-drk-sm from 'css/typography.scss';
14 | font-size: 16px;
15 | &:hover {
16 | color: $color-rhino;
17 | text-decoration: none;
18 | }
19 | }
20 |
21 | .remove-button {
22 | margin-left: auto;
23 | composes: text-button from 'css/typography.scss'
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/actions/fetchPlatformAgreements.js:
--------------------------------------------------------------------------------
1 | export const FETCH_PLATFORM_AGREEMENTS = 'FETCH_PLATFORM_AGREEMENTS'
2 |
3 | export default function fetchPlatformAgreements () {
4 | return {
5 | type: FETCH_PLATFORM_AGREEMENTS,
6 | graphql: {
7 | query: `query FetchPlatformAgreements {
8 | platformAgreements {
9 | id
10 | text
11 | type
12 | }
13 | }`,
14 | variables: { }
15 | },
16 | meta: {
17 | extractModel: 'PlatformAgreement'
18 | }
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/src/store/models/Attachment.js:
--------------------------------------------------------------------------------
1 | import { attr, fk, Model } from 'redux-orm'
2 |
3 | class Attachment extends Model {
4 | toString () {
5 | return `Attachment (${this.type}): ${this.name}`
6 | }
7 | }
8 |
9 | export default Attachment
10 |
11 | Attachment.modelName = 'Attachment'
12 |
13 | Attachment.fields = {
14 | id: attr(),
15 | type: attr(),
16 | position: attr(),
17 | url: attr(),
18 | thumbnailUrl: attr(),
19 | post: fk('Post', 'attachments'),
20 | comment: fk('Comment', 'attachments'),
21 | createdAt: attr()
22 | }
23 |
--------------------------------------------------------------------------------
/src/store/models/SearchResult.js:
--------------------------------------------------------------------------------
1 | import { attr, Model } from 'redux-orm'
2 |
3 | class SearchResult extends Model {
4 | toString () {
5 | return `SearchResult: ${this.id}`
6 | }
7 |
8 | getContent (session) {
9 | const [ type, id ] = this.content.split('-')
10 | return session[type].withId(id)
11 | }
12 | }
13 |
14 | export default SearchResult
15 |
16 | SearchResult.modelName = 'SearchResult'
17 | SearchResult.fields = {
18 | id: attr(),
19 | // this is a polymorphicId, see getContent above
20 | content: attr()
21 | }
22 |
--------------------------------------------------------------------------------
/src/components/Widget/MapWidget/MapWidget.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React, { Component } from 'react'
3 | import { withTranslation } from 'react-i18next'
4 |
5 | import './MapWidget.scss'
6 |
7 | const { array } = PropTypes
8 |
9 | class MapWidget extends Component {
10 | static propTypes = {
11 | map: array
12 | }
13 |
14 | render () {
15 | return (
16 |
17 | {this.props.t('Community map')}
18 |
19 | )
20 | }
21 | }
22 |
23 | export default withTranslation()(MapWidget)
24 |
--------------------------------------------------------------------------------
/src/routes/Messages/PeopleSelector/PeopleListItem/__snapshots__/PeopleListItem.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`matches the last snapshot 1`] = `
4 |
7 |
12 |
13 |
16 | Wombat
17 |
18 |
21 |
22 |
23 | `;
24 |
--------------------------------------------------------------------------------
/src/store/selectors/getChildComments.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import { get } from 'lodash/fp'
3 | import { FETCH_CHILD_COMMENTS } from 'store/constants'
4 | import { makeGetQueryResults } from 'store/reducers/queryResults'
5 |
6 | const getCommentResults = makeGetQueryResults(FETCH_CHILD_COMMENTS)
7 |
8 | export const getHasMoreChildComments = createSelector(
9 | getCommentResults,
10 | get('hasMore')
11 | )
12 |
13 | export const getTotalChildComments = createSelector(
14 | getCommentResults,
15 | get('total')
16 | )
17 |
--------------------------------------------------------------------------------
/src/store/selectors/getMyGroupMembership.js:
--------------------------------------------------------------------------------
1 | import { createSelector } from 'reselect'
2 | import getMyMemberships from 'store/selectors/getMyMemberships'
3 | import getGroupForCurrentRoute from './getGroupForCurrentRoute'
4 |
5 | export const getMyGroupMembership = createSelector(
6 | getGroupForCurrentRoute,
7 | getMyMemberships,
8 | (group, memberships) => {
9 | if (group && memberships.length > 0) {
10 | return memberships.find(m => m.group.id === group.id)
11 | }
12 | }
13 | )
14 |
15 | export default getMyGroupMembership
16 |
--------------------------------------------------------------------------------
/src/components/PostCard/EventDate/EventDate.scss:
--------------------------------------------------------------------------------
1 | .eventDate {
2 | border-radius: 4px;
3 | background-color: rgba(254, 72, 80, 1);
4 | width: 45px;
5 | height: 45px;
6 | display: flex;
7 | flex-direction: column;
8 | align-items: center;
9 | padding: 8px;
10 | }
11 |
12 | .month {
13 | text-transform: uppercase;
14 | color: white;
15 | font-size: 12px !important;
16 | line-height: 0.6;
17 | font-size: 20px;
18 | }
19 |
20 | .day {
21 | color: white;
22 | font-size: 24px;
23 | line-height: 24px;
24 | color: white;
25 | }
26 |
--------------------------------------------------------------------------------
/src/routes/MemberProfile/MemberPosts/MemberPosts.test.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme'
2 | import React from 'react'
3 |
4 | import MemberPosts from './MemberPosts'
5 | import denormalized from '../MemberProfile.test.json'
6 |
7 | describe.only('MemberPosts', () => {
8 | const { person } = denormalized.data
9 |
10 | it('renders the same as the last snapshot', () => {
11 | const wrapper = shallow(
12 |
13 | )
14 | expect(wrapper).toMatchSnapshot()
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/store/actions/removePost.js:
--------------------------------------------------------------------------------
1 | import { REMOVE_POST } from 'store/constants'
2 |
3 | export default function removePost (postId, slug) {
4 | return {
5 | type: REMOVE_POST,
6 | graphql: {
7 | query: `mutation ($postId: ID, $slug: String) {
8 | removePost(postId: $postId, slug: $slug) {
9 | success
10 | }
11 | }`,
12 | variables: {
13 | postId,
14 | slug
15 | }
16 | },
17 | meta: {
18 | optimistic: true,
19 | postId,
20 | slug
21 | }
22 | }
23 | }
24 |
--------------------------------------------------------------------------------
/src/routes/AllTopics/__snapshots__/AllTopics.store.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`deleteGroupTopic should match latest snapshot 1`] = `
4 | Object {
5 | "graphql": Object {
6 | "query": "mutation ($id: ID) {
7 | deleteGroupTopic(id: $id) {
8 | success
9 | }
10 | }",
11 | "variables": Object {
12 | "id": 135,
13 | },
14 | },
15 | "meta": Object {
16 | "id": 135,
17 | "optimistic": true,
18 | },
19 | "type": "AllTopics/DELETE_GROUP_TOPIC",
20 | }
21 | `;
22 |
--------------------------------------------------------------------------------
/src/routes/NonAuthLayoutRouter/ManageNotifications/ManageNotifications.scss:
--------------------------------------------------------------------------------
1 | .wrapper {
2 | padding: 30px;
3 | }
4 |
5 | .setting-explanation {
6 | composes: caption-lt-lg from 'css/typography.scss';
7 | font-size: 13px;
8 | color: black;
9 | margin-bottom: 0;
10 | }
11 |
12 | .form-wrapper {
13 | padding: 0px;
14 | }
15 |
16 | .setting-wrapper {
17 | padding-bottom: 0px;
18 | }
19 |
20 | .unsubscribeAllLabel {
21 | color: #2C4059;
22 | }
23 |
24 | .submit {
25 | composes: submit from '../Login/Login.scss';
26 | margin-top: 20px;
27 | }
28 |
--------------------------------------------------------------------------------
/src/store/presenters/presentGroupRelationshipInvite.js:
--------------------------------------------------------------------------------
1 | export default function presentGroupRelationshipInvite (invite) {
2 | if (!invite) return null
3 | return {
4 | ...invite.ref,
5 | fromGroup: invite.fromGroup ? invite.fromGroup.ref : null,
6 | questionAnswers: invite.questionAnswers ? invite.questionAnswers.toModelArray().map(qa => {
7 | return {
8 | ...qa.ref,
9 | question: qa.question ? qa.question.ref : null
10 | }
11 | }) : [],
12 | toGroup: invite.toGroup ? invite.toGroup.ref : null
13 | }
14 | }
15 |
--------------------------------------------------------------------------------
/src/graphql/fragments/groupTopicsQueryFragment.js:
--------------------------------------------------------------------------------
1 | export default
2 | `groupTopics(
3 | first: $first,
4 | offset: $offset,
5 | sortBy: $sortBy,
6 | order: $order,
7 | subscribed: $subscribed,
8 | autocomplete: $autocomplete
9 | ) {
10 | hasMore
11 | total
12 | items {
13 | id
14 | followersTotal
15 | isDefault
16 | isSubscribed
17 | lastReadPostId
18 | newPostCount
19 | postsTotal
20 | visibility
21 | group {
22 | id
23 | }
24 | topic {
25 | id
26 | name
27 | }
28 | }
29 | }
30 | `
31 |
--------------------------------------------------------------------------------
/src/store/actions/blockUser.js:
--------------------------------------------------------------------------------
1 | import { AnalyticsEvents } from 'hylo-shared'
2 | import { BLOCK_USER } from '../constants'
3 |
4 | export default function blockUser (blockedUserId) {
5 | return {
6 | type: BLOCK_USER,
7 | graphql: {
8 | query: `mutation ($blockedUserId: ID) {
9 | blockUser (blockedUserId: $blockedUserId) {
10 | success
11 | }
12 | }`,
13 | variables: {
14 | blockedUserId
15 | }
16 | },
17 | meta: {
18 | analytics: AnalyticsEvents.BLOCK_USER
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/store/actions/fetchTopic.js:
--------------------------------------------------------------------------------
1 | import { FETCH_TOPIC } from 'store/constants'
2 |
3 | export default function fetchTopic (name, id) {
4 | return {
5 | type: FETCH_TOPIC,
6 | graphql: {
7 | query: `query ($name: String, $id: ID) {
8 | topic(name: $name, id: $id) {
9 | id
10 | name
11 | postsTotal
12 | followersTotal
13 | }
14 | }`,
15 | variables: {
16 | name,
17 | id
18 | }
19 | },
20 | meta: {
21 | extractModel: 'Topic'
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/reducers/pending.js:
--------------------------------------------------------------------------------
1 | export const initialState = {}
2 |
3 | export default function pending (state = initialState, action) {
4 | const { type, meta, error } = action
5 |
6 | if (error) return state
7 |
8 | const originalType = type.replace(/_PENDING/, '')
9 |
10 | if (type.endsWith('_PENDING')) {
11 | return {
12 | ...state,
13 | [originalType]: meta || true
14 | }
15 | } else if (state[originalType]) {
16 | return {
17 | ...state,
18 | [originalType]: null
19 | }
20 | }
21 |
22 | return state
23 | }
24 |
--------------------------------------------------------------------------------
/src/components/NoPosts/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { useTranslation } from 'react-i18next'
3 | import './NoPosts.scss'
4 |
5 | import { jollyAxolotl } from 'util/assets'
6 |
7 | const NoPosts = ({ message, className }) => {
8 | const { t } = useTranslation()
9 | const tMessage = message || t('Nothing to see here')
10 | return (
11 |
12 |

13 |
14 |
{tMessage}
15 |
16 | )
17 | }
18 |
19 | export default NoPosts
20 |
--------------------------------------------------------------------------------
/src/components/CreateGroup/CreateGroup.test.js:
--------------------------------------------------------------------------------
1 | import CreateGroup from './CreateGroup'
2 | import { shallow } from 'enzyme'
3 | import React from 'react'
4 |
5 | describe('CreateGroup', () => {
6 | it('matches snapshot', () => {
7 | const wrapper = shallow()
8 | expect(wrapper).toMatchSnapshot()
9 | })
10 |
11 | it('Allows for passing in initial name and slug via query parameters', () => {
12 | const wrapper = shallow()
13 | expect(wrapper).toMatchSnapshot()
14 | })
15 | })
16 |
--------------------------------------------------------------------------------
/src/graphql/queries/MessageThreadQuery.graphql:
--------------------------------------------------------------------------------
1 | query MessageThreadQuery ($id: ID) {
2 | messageThread (id: $id) {
3 | id
4 | unreadCount
5 | lastReadAt
6 | createdAt
7 | updatedAt
8 | participants {
9 | id
10 | name
11 | avatarUrl
12 | }
13 | messages(first: 80, order: "desc") {
14 | items {
15 | id
16 | text
17 | creator {
18 | id
19 | name
20 | avatarUrl
21 | }
22 | createdAt
23 | }
24 | total
25 | hasMore
26 | }
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/src/routes/MemberProfile/MemberPosts/MemberPosts.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import {
3 | getMemberPosts,
4 | fetchMemberPosts
5 | } from './MemberPosts.store'
6 |
7 | export function mapStateToProps (state, props) {
8 | return {
9 | posts: getMemberPosts(state, props)
10 | }
11 | }
12 |
13 | export function mapDispatchToProps (dispatch, props) {
14 | return {
15 | fetchMemberPosts: () => dispatch(fetchMemberPosts(props.routeParams.personId))
16 | }
17 | }
18 |
19 | export default connect(mapStateToProps, mapDispatchToProps)
20 |
--------------------------------------------------------------------------------
/src/store/actions/unBlockUser.js:
--------------------------------------------------------------------------------
1 | import { AnalyticsEvents } from 'hylo-shared'
2 | import { UNBLOCK_USER } from '../constants'
3 |
4 | export default function unBlockUser (blockedUserId) {
5 | return {
6 | type: UNBLOCK_USER,
7 | graphql: {
8 | query: `mutation ($blockedUserId: ID) {
9 | unblockUser (blockedUserId: $blockedUserId) {
10 | success
11 | }
12 | }`,
13 | variables: {
14 | blockedUserId
15 | }
16 | },
17 | meta: {
18 | analytics: AnalyticsEvents.UNBLOCK_USER
19 | }
20 | }
21 | }
22 |
--------------------------------------------------------------------------------
/src/routes/MemberProfile/MemberComments/MemberComments.test.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme'
2 | import React from 'react'
3 |
4 | import MemberComments from './MemberComments'
5 | import denormalized from '../MemberProfile.test.json'
6 |
7 | describe.only('MemberComments', () => {
8 | const { person } = denormalized.data
9 |
10 | it('renders the same as the last snapshot', () => {
11 | const wrapper = shallow(
12 |
13 | )
14 | expect(wrapper).toMatchSnapshot()
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/store/actions/fetchMySkills.js:
--------------------------------------------------------------------------------
1 | export const FETCH_MY_SKILLS = `FETCH_MY_SKILLS`
2 |
3 | export default function fetchMySkills (type, limit = 20) {
4 | return {
5 | type: FETCH_MY_SKILLS,
6 | graphql: {
7 | query: `query ($limit: Int) {
8 | me {
9 | id
10 | skills (first: $limit) {
11 | items {
12 | id
13 | name
14 | }
15 | }
16 | }
17 | }`,
18 | variables: { limit }
19 | },
20 | meta: {
21 | extractModel: 'Me'
22 | }
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/src/util/geo.js:
--------------------------------------------------------------------------------
1 | import WebMercatorViewport from '@math.gl/web-mercator'
2 |
3 | export function locationObjectToViewport (priorViewport, locationObject) {
4 | const bbox = locationObject.bbox
5 | if (bbox) {
6 | const bounds = [[parseFloat(bbox[0].lng), parseFloat(bbox[0].lat)], [parseFloat(bbox[1].lng), parseFloat(bbox[1].lat)]]
7 | return new WebMercatorViewport(priorViewport).fitBounds(bounds)
8 | } else {
9 | return { ...priorViewport, longitude: parseFloat(locationObject.center.lng), latitude: parseFloat(locationObject.center.lat), zoom: 12 }
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/src/routes/UserSettings/__snapshots__/UserSettings.connector.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`mapStateToProps returns the right keys 1`] = `
4 | Object {
5 | "allGroupsSettings": Object {
6 | "sendEmail": true,
7 | "sendPushNotifications": true,
8 | },
9 | "blockedUsers": Array [],
10 | "confirm": undefined,
11 | "currentUser": undefined,
12 | "fetchPending": undefined,
13 | "memberships": Array [],
14 | "messageSettings": undefined,
15 | "queryParams": Object {
16 | "registered": undefined,
17 | },
18 | }
19 | `;
20 |
--------------------------------------------------------------------------------
/src/components/SimpleTabBar/SimpleTabBar.scss:
--------------------------------------------------------------------------------
1 | .tab-bar {
2 | display: flex;
3 | flex-direction: row;
4 | justify-content: flex-start;
5 | padding: 0;
6 | }
7 |
8 | .tab {
9 | composes: caption-lt-lg from 'css/typography.scss';
10 | padding: 0 $space-4x;
11 | height: $space-7x;
12 | line-height: $space-7x;
13 | list-style: none;
14 | cursor: pointer;
15 | }
16 |
17 | .tab-active {
18 | composes: tab;
19 | @include font-bold;
20 | color: $color-rhino;
21 | font-size: $space-4x;
22 | background-color: $color-cape-cod-05;
23 | border-radius: $space-1x;
24 | }
25 |
--------------------------------------------------------------------------------
/src/store/actions/fetchPerson.test.js:
--------------------------------------------------------------------------------
1 | import fetchPerson from './fetchPerson'
2 |
3 | describe('fetchPerson', () => {
4 | it('returns the correct action', () => {
5 | const expected = {
6 | type: 'FETCH_PERSON',
7 | graphql: {
8 | query: 'A very wombaty query.',
9 | variables: {
10 | id: '12345'
11 | }
12 | },
13 | meta: { extractModel: 'Person' }
14 | }
15 | const { query, variables } = expected.graphql
16 | const actual = fetchPerson(variables.id, query)
17 | expect(actual).toEqual(expected)
18 | })
19 | })
20 |
--------------------------------------------------------------------------------
/src/css/bootstrap.scss:
--------------------------------------------------------------------------------
1 | // Core CSS
2 | // @import "node_modules/bootstrap/scss/type";
3 | @import "node_modules/bootstrap/scss/images";
4 | // @import "node_modules/bootstrap/scss/code";
5 | // @import "node_modules/bootstrap/scss/grid";
6 | // @import "node_modules/bootstrap/scss/tables";
7 | // @import "node_modules/bootstrap/scss/forms";
8 | // @import "node_modules/bootstrap/scss/buttons";
9 |
10 | // Components
11 | // @import "node_modules/bootstrap/scss/card";
12 |
13 | // Utility classes
14 | @import "node_modules/bootstrap/scss/utilities";
15 | @import "node_modules/bootstrap/scss/nav";
16 |
--------------------------------------------------------------------------------
/src/routes/NonAuthLayoutRouter/NonAuthLayoutRouter.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { render, screen } from 'util/testing/reactTestingLibraryExtended'
3 | import NonAuthLayoutRouter from './NonAuthLayoutRouter'
4 |
5 | // Currently the test below is going to default route to `/login`
6 | // so until more tests are added this test is identical to the `Login`
7 | // component test
8 |
9 | it('renders correctly', () => {
10 | render(
11 |
12 | )
13 |
14 | expect(screen.getByText('Sign in to Hylo')).toBeInTheDocument()
15 | })
16 |
--------------------------------------------------------------------------------
/src/store/selectors/getTopicForCurrentRoute.js:
--------------------------------------------------------------------------------
1 | import orm from 'store/models'
2 | import { createSelector as ormCreateSelector } from 'redux-orm'
3 | import getRouteParam from './getRouteParam'
4 | import presentTopic from 'store/presenters/presentTopic'
5 |
6 | const getTopicForCurrentRoute = ormCreateSelector(
7 | orm,
8 | (state, props) => getRouteParam('topicName', props),
9 | (session, topicName) => {
10 | const topic = session.Topic.safeGet({ name: topicName })
11 | return topic ? presentTopic(topic, {}) : null
12 | }
13 | )
14 |
15 | export default getTopicForCurrentRoute
16 |
--------------------------------------------------------------------------------
/src/components/LocationInput/LocationInput.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import {
3 | fetchLocation,
4 | pollingFetchLocation
5 | } from './LocationInput.store.js'
6 |
7 | export function mapStateToProps (state, props) {
8 | return {}
9 | }
10 |
11 | export const mapDispatchToProps = (dispatch, props) => {
12 | return {
13 | fetchLocation,
14 | pollingFetchLocation: (locationData, callback) => pollingFetchLocation(dispatch, locationData, callback)
15 | }
16 | }
17 |
18 | export default connect(mapStateToProps, mapDispatchToProps, null, { forwardRef: true })
19 |
--------------------------------------------------------------------------------
/src/components/SkillLabel/component.js:
--------------------------------------------------------------------------------
1 | import PropTypes from 'prop-types'
2 | import React from 'react'
3 | import cx from 'classnames'
4 | import './component.scss'
5 |
6 | const { string, bool } = PropTypes
7 |
8 | export default function SkillLabel ({ children, label, color = 'dark', active, className }) {
9 | let styleName = cx('label', color, { active })
10 | return
11 | {label || children}
12 |
13 | }
14 | SkillLabel.propTypes = {
15 | label: string,
16 | color: string,
17 | active: bool,
18 | className: string
19 | }
20 |
--------------------------------------------------------------------------------
/src/components/TextInput/__snapshots__/TextInput.test.js.snap:
--------------------------------------------------------------------------------
1 | // Jest Snapshot v1, https://goo.gl/fbAQLP
2 |
3 | exports[`TextInput renders correctly 1`] = `
4 |
25 | `;
26 |
--------------------------------------------------------------------------------
/src/routes/Events/Events.store.js:
--------------------------------------------------------------------------------
1 | export const MODULE_NAME = 'Events'
2 | export const UPDATE_TIMEFRAME = `${MODULE_NAME}/UPDATE_TIMEFRAME`
3 |
4 | export function updateTimeframe (timeframe) {
5 | return {
6 | type: UPDATE_TIMEFRAME,
7 | payload: timeframe
8 | }
9 | }
10 |
11 | // reducer
12 | const DEFAULT_STATE = {
13 | timeframe: 'future'
14 | }
15 |
16 | export default function (state = DEFAULT_STATE, action) {
17 | if (action.type === UPDATE_TIMEFRAME) {
18 | return {
19 | ...state,
20 | timeframe: action.payload
21 | }
22 | }
23 | return state
24 | }
25 |
--------------------------------------------------------------------------------
/src/routes/MemberProfile/MemberComments/MemberComments.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 | import {
3 | getMemberComments,
4 | fetchMemberComments
5 | } from './MemberComments.store'
6 |
7 | export function mapStateToProps (state, props) {
8 | return {
9 | comments: getMemberComments(state, props)
10 | }
11 | }
12 |
13 | export function mapDispatchToProps (dispatch, props) {
14 | return {
15 | fetchMemberComments: () => dispatch(fetchMemberComments(props.routeParams.personId))
16 | }
17 | }
18 |
19 | export default connect(mapStateToProps, mapDispatchToProps)
20 |
--------------------------------------------------------------------------------
/src/routes/MemberProfile/MemberVotes/MemberVotes.test.js:
--------------------------------------------------------------------------------
1 | import { shallow } from 'enzyme'
2 | import React from 'react'
3 |
4 | import MemberVotes from './MemberVotes'
5 | import denormalized from '../MemberProfile.test.json'
6 |
7 | describe.only('MemberVotes', () => { // TODO REACTIONS: switch this to reactions
8 | const { person } = denormalized.data
9 |
10 | it('renders the same as the last snapshot', () => {
11 | const wrapper = shallow(
12 |
13 | )
14 | expect(wrapper).toMatchSnapshot()
15 | })
16 | })
17 |
--------------------------------------------------------------------------------
/src/components/GroupButton/GroupButton.js:
--------------------------------------------------------------------------------
1 | import React from 'react'
2 | import { groupUrl } from 'util/navigation'
3 | import { DEFAULT_AVATAR } from 'store/models/Group'
4 | import { Link } from 'react-router-dom'
5 | import Button from 'components/Button'
6 | import RoundImage from 'components/RoundImage'
7 | import './GroupButton.scss'
8 |
9 | export default ({ group }) => (
10 |
15 | )
16 |
--------------------------------------------------------------------------------
/src/components/GroupsList/GroupsList.scss:
--------------------------------------------------------------------------------
1 | .groupRow {
2 | display: flex;
3 | flex-grow: 1;
4 | flex-direction: column;
5 | margin-bottom: 5px;
6 | padding-left: 8px;
7 | }
8 |
9 | .groupCell {
10 | flex: 1;
11 | &:hover {
12 | text-decoration: none;
13 | }
14 | }
15 |
16 | .groupCellName {
17 | composes: text-button from 'css/typography.scss';
18 | font-size: 15px;
19 | vertical-align: middle;
20 | }
21 |
22 | .groupCellAvatar {
23 | @include image(20px);
24 | display: inline-block;
25 | vertical-align: middle;
26 | border-radius: 4px;
27 | margin-right: 10px;
28 | }
29 |
--------------------------------------------------------------------------------
/src/components/PostCard/PostGroups/PostGroups.connector.js:
--------------------------------------------------------------------------------
1 | import { connect } from 'react-redux'
2 |
3 | export function mapStateToProps (state, props) {
4 | let isPublic = props.isPublic
5 | let groupsPlusPublic = props.groups
6 |
7 | if (isPublic) {
8 | groupsPlusPublic.unshift({ name: 'Public', id: 'public', avatarUrl: '/public-icon.svg', slug: 'public' })
9 | }
10 |
11 | return {
12 | groups: groupsPlusPublic
13 | }
14 | }
15 |
16 | export function mapDispatchToProps (dispatch, props) {
17 | return {}
18 | }
19 |
20 | export default connect(mapStateToProps, mapDispatchToProps)
21 |
--------------------------------------------------------------------------------