Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 42 additions & 39 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,48 +1,51 @@
<h1 align="center">
<img
alt="dogstack on a post-it note"
src="http://i.imgur.com/vjfouxn.jpg"
height="250"
/>
<br />
dogstack
</h1>

<h4 align="center">
:dog: :dog: :dog: a popular-choice grab-bag framework for teams working on production web apps
</h4>

<h6 align="center">
:cat: see also <a href='https://github.com/enspiral-root-systems/cat-stack'>catstack</a>, dogstack's smarter, slimmer, more cunning partner in crime
</h4>
# dogstack-example

-> make topic called agent
-> agent is an id that links to other foreign objects
-> agent might have a type person group or bot
agent has many accounts
account is a way of logging in
agent has one profile
agent has and belongs to many relationships
an example app using the [dogstack framework](https://dogstack.js.org). :dog: :dog: :dog:

TODO see it live at [dogstack.herokuapp.com](https://dogstack.herokuapp.com/)

## how to use

if you want to use this as a starting ground for your new app, simply fork this repository!

## features
if you want to develop this example further,

- provides generators for scaffolding apps made of popular libraries
- abstracts away the app plumbing that you don't want to write again, and let's you focus on features
```
git clone [email protected]:dogstack/dogstack-example
cd dogstack-example
yarn install
yarn dev
```

## demos
### use forked version of `fela`

- [dogstack.herokuapp.com](https://dogstack.herokuapp.com/): this repo's [./example](example) deployed to heroku
at the moment this example relies on [fela#260](https://github.com/rofrischmann/fela/pull/260)

## modules
```shell
cd ../
git clone git@github:ahdinosaur/fela
cd fela
git checkout build
cd packages/fela
npm link
cd ../..
cd packages/react-fela
npm link

- React
- Redux
- React Router
- Reselect
cd ../dogstack-example
npm link fela
npm link react-fela
```

## prior art
## user stories

TODO

-> make topic called agent
-> agent is an id that links to other foreign objects
-> agent might have a type person group or bot
agent has many accounts
account is a way of logging in
agent has one profile
agent has and belongs to many relationships

- architecture by [@ahdinosaur](https://github.com/ahdinosaur) on previous private projects
- https://github.com/jlongster/react-redux-universal-hot-example
- https://github.com/react-boilerplate/react-boilerplate
- general discussions with teammates
8 changes: 4 additions & 4 deletions layout/components/layout.js → app/components/home.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
import React from 'react'
import { createComponent } from 'react-fela'
import { Switch } from 'react-router'

import styles from '../styles/layout'
import styles from '../styles/home'

const Container = createComponent(styles.container, 'div')

export default function Layout (props) {
export default function Home (props) {
const { routes } = props

return <Container>
<Switch children={routes} />
dogstack!
</Container>
}
36 changes: 36 additions & 0 deletions app/components/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import React from 'react'
import { createComponent } from 'react-fela'
import { Route, Switch } from 'react-router-dom'
import { pipe, map, values } from 'ramda'

import styles from '../styles/layout'

import Nav from './nav'

const Container = createComponent(styles.container, 'div')

export default function Layout (props) {
const { routes, navigationRoutes } = props
const pages = mapRoutePages(routes)

return <Container>
<Nav navigationRoutes={navigationRoutes} />
<Switch>
{pages}
</Switch>
</Container>
}

const mapRoutePages = map(route => {
const {
path,
exact,
Component
} = route

const key = path || '404'

return (
<Route path={path} key={key} exact={exact} component={Component} />
)
})
34 changes: 34 additions & 0 deletions app/components/nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import React from 'react'
import { createComponent } from 'react-fela'
import { NavLink } from 'react-router-dom'
import { pipe, map, values } from 'ramda'

import styles from '../styles/nav'

const Container = createComponent(styles.container, 'nav')

export default function Nav (props) {
const { navigationRoutes } = props

return <Container>
{mapRouteLinks(navigationRoutes)}
</Container>
}

const mapRouteLinks = pipe(
map(route => {
const {
path,
name = path,
title = name,
Component
} = route

return (
<NavLink to={path} key={name}>
{title}
</NavLink>
)
}),
values
)
9 changes: 9 additions & 0 deletions app/containers/home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { connect } from 'react-redux'

import Home from '../components/home'

import { getHomeProps } from '../getters'

export default connect(
getHomeProps
)(Home)
9 changes: 9 additions & 0 deletions app/containers/layout.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { connect } from 'react-redux'

import Layout from '../components/layout'

import { getLayoutProps } from '../getters'

export default connect(
getLayoutProps
)(Layout)
33 changes: 33 additions & 0 deletions app/getters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { createSelector, createStructuredSelector } from 'reselect'
import { pipe, not, isNil, map, filter, prop, propOr, indexBy, nthArg, uncurryN } from 'ramda'

export const getState = state => state

export const getHomeProps = (state) => ({})

export const getRoutes = pipe(
nthArg(1),
propOr(null, 'routes')
)

const indexByName = indexBy(prop('name'))
const filterNil = filter(pipe(isNil, not))
export const getNavigationRoutes = createSelector(
getState,
getRoutes,
uncurryN(2, state => {
const mapRoutes = map(route => {
const { name, navigation } = route
if (isNil(navigation)) return
if (navigation === true) return route
const { selector } = navigation
if (isNil(selector) || selector(state)) return route
})
return pipe(mapRoutes, filterNil, indexByName)
})
)

export const getLayoutProps = createStructuredSelector({
routes: getRoutes,
navigationRoutes: getNavigationRoutes
})
9 changes: 9 additions & 0 deletions app/styles/home.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
export default {
container: (props) => {
return {
fontSize: '12rem',
display: 'flex',
justifyContent: 'center'
}
}
}
File renamed without changes.
8 changes: 8 additions & 0 deletions app/styles/nav.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export default {
container: (props) => {
return {
display: 'flex',
justifyContent: 'space-around'
}
}
}
24 changes: 20 additions & 4 deletions authentication/getters.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,36 @@
import { createSelector, createStructuredSelector } from 'reselect'
import { prop, propOr, not } from 'ramda'

export const getAuthentication = (state) => state.authentication
export const getAuthentication = prop('authentication')

export const getSigningIn = createSelector(
getAuthentication,
(authentication) => authentication.signingIn
propOr(false, 'signingIn')
)

export const getAccount = createSelector(
getAuthentication,
(authentication) => authentication.account
propOr(null, 'account')
)

export const getAccessToken = createSelector(
getAccount,
propOr(null, 'accessToken')
)

export const getIsAuthenticated = createSelector(
getAccount,
Boolean
)

export const getIsNotAuthenticated = createSelector(
getAccount,
not
)

export const getError = createSelector(
getAuthentication,
(authentication) => authentication.error
propOr(null, 'error')
)

export const getSignInProps = createStructuredSelector({
Expand Down
3 changes: 2 additions & 1 deletion authentication/hoc/userIsAuthenticated.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
import { connectedRouterRedirect } from 'redux-auth-wrapper/lib/history4/redirect'
import { propOr } from 'ramda'
import { getAccount } from '../getters'

export default connectedRouterRedirect({
wrapperDisplayName: 'UserIsAuthenticated',
authSelector: getAccount,
predicate: data => !!data.accessToken,
predicate: propOr(null, 'accessToken'),
redirectPath: '/sign-in',
allowRedirectBack: true
})
3 changes: 2 additions & 1 deletion authentication/hoc/userIsAuthenticatedOrHome.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import locationHelperBuilder from 'redux-auth-wrapper/lib/history4/locationHelper'
import { propOr } from 'ramda'
import { connectedRouterRedirect } from 'redux-auth-wrapper/lib/history4/redirect'

import { getAccount } from '../getters'
Expand All @@ -8,7 +9,7 @@ const locationHelper = locationHelperBuilder({})
export default connectedRouterRedirect({
wrapperDisplayName: 'UserIsAuthenticatedOrHome',
authSelector: getAccount,
predicate: data => !!data.accessToken,
predicate: propOr(null, 'accessToken'),
redirectPath: '/',
allowRedirectBack: false
})
3 changes: 2 additions & 1 deletion authentication/hoc/userIsNotAuthenticated.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import locationHelperBuilder from 'redux-auth-wrapper/lib/history4/locationHelper'
import { connectedRouterRedirect } from 'redux-auth-wrapper/lib/history4/redirect'
import { pipe, propOr, not } from 'ramda'

import { getAccount } from '../getters'

Expand All @@ -8,7 +9,7 @@ const locationHelper = locationHelperBuilder({})
export default connectedRouterRedirect({
wrapperDisplayName: 'UserIsNotAuthenticated',
authSelector: getAccount,
predicate: data => !data.accessToken,
predicate: pipe(propOr(null, 'accessToken'), not),
redirectPath: (state, ownProps) => locationHelper.getRedirectQuery(ownProps) || '/',
allowRedirectBack: false
})
4 changes: 2 additions & 2 deletions authentication/updater.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { updateStateAt, combine, withDefaultState, decorate } from 'redux-fp'

const account = action => (state = {}) => {
const account = action => (state = null) => {
switch (action.type) {
case 'REGISTER_SUCCESS':
case 'SIGN_IN_SUCCESS':
return action.payload
case 'SIGN_OUT_SUCCESS':
return {}
return null
default:
return state
}
Expand Down
2 changes: 1 addition & 1 deletion browser.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import createBrowserHistory from 'history/createBrowserHistory'
import configureStore from './store'
import createRenderer from './renderer'
import routes from './routes'
import Layout from './layout/components/layout'
import Layout from './app/containers/layout'

document.addEventListener('DOMContentLoaded', () => {
const history = createBrowserHistory()
Expand Down
20 changes: 15 additions & 5 deletions dogs/components/dog.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,21 @@
import React from 'react'
import { connect as connectStyles } from 'react-fela'
import { Link } from 'react-router-dom'
import { isNil } from 'ramda'

export default function Dog (props) {
return <div>
<span>
import styles from '../styles/dog'

export default connectStyles(styles)(Dog)

function Dog (props) {
const { dog, styles } = props
if (isNil(dog)) return null

return <div className={styles.container}>
<Link className={styles.name} to={`/dogs/${dog.id}`}>
<i className='em em-dog' />
{props.name}
{dog.name}
<i className='em em-dog' />
</span>
</Link>
</div>
}
Loading