Dwayne Harris 5 years ago
parent
commit
3fbcb3b8a7
  1. 30
      src/actions/authentication.ts
  2. 61
      src/actions/directory.ts
  3. 185
      src/actions/registration.ts
  4. 6
      src/api/errors.ts
  5. 6
      src/api/fetch.ts
  6. 42
      src/api/groups.ts
  7. 1
      src/api/index.ts
  8. 46
      src/api/registration.ts
  9. 0
      src/api/users.ts
  10. 2
      src/components/app/app.scss
  11. 17
      src/components/app/app.tsx
  12. 13
      src/components/app/index.ts
  13. 2
      src/components/create-group-form/index.ts
  14. 2
      src/components/forms/password-field/password-field.tsx
  15. 21
      src/components/forms/select-field/select-field.tsx
  16. 7
      src/components/forms/text-field/text-field.tsx
  17. 33
      src/components/pages/register/index.ts
  18. 23
      src/components/pages/self/index.ts
  19. 30
      src/components/pages/self/self.tsx
  20. 7
      src/constants/index.ts
  21. 1
      src/selectors/authentication.ts
  22. 2
      src/types/index.ts
  23. 2
      src/utils/index.ts

30
src/actions/authentication.ts

@ -1,4 +1,13 @@
import { Action } from 'redux'
import { normalize } from 'normalizr'
import { apiFetch } from 'src/api'
import { setEntities } from 'src/actions/entities'
import { startRequest, finishRequest } from 'src/actions/requests'
import { userSchema } from 'src/store/schemas'
import { REQUEST_KEYS } from 'src/constants'
import { AppThunkAction, Entity } from 'src/types'
export interface SetAuthenticatedAction extends Action {
type: 'AUTHENTICATION_SET_AUTHENTICATED'
@ -21,3 +30,24 @@ export const setUser = (userId: string): SetUserAction => ({
type: 'AUTHENTICATION_SET_USER',
payload: userId,
})
export const fetchSelf = (): AppThunkAction => async dispatch => {
dispatch(startRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY))
try {
const self = await apiFetch<Entity>({
path: '/api/self',
})
const result = normalize(self, userSchema)
dispatch(setEntities(result.entities))
dispatch(setUser(self.id))
dispatch(setAuthenticated(true))
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, true))
} catch (err) {
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, false))
throw err
}
}

61
src/actions/directory.ts

@ -1,14 +1,14 @@
import { Action } from 'redux'
import { normalize } from 'normalizr'
import { getGroup, getGroups } from '../api/groups'
import { setEntity, setEntities } from '../actions/entities'
import { startRequest, finishRequest } from '../actions/requests'
import { groupSchema } from '../store/schemas'
import { AppThunkAction } from '../types'
import { apiFetch } from 'src/api'
import { setEntity, setEntities } from 'src/actions/entities'
import { startRequest, finishRequest } from 'src/actions/requests'
import { groupSchema } from 'src/store/schemas'
const FETCH_GROUP_ID = 'FETCH_GROUP'
const FETCH_GROUPS_ID = 'FETCH_GROUPS'
import { REQUEST_KEYS } from 'src/constants'
import { objectToQuerystring } from 'src/utils'
import { AppThunkAction, Entity } from 'src/types'
export interface SetGroupsAction extends Action {
type: 'DIRECTORY_SET_GROUPS'
@ -44,38 +44,47 @@ export const setContinuation = (continuation: string): SetContinuationAction =>
export const fetchGroup = (id: string): AppThunkAction => {
return async dispatch => {
dispatch(startRequest(FETCH_GROUP_ID))
dispatch(startRequest(REQUEST_KEYS.FETCH_GROUP))
try {
const group = await getGroup(id)
const group = await apiFetch<Entity>({
path: `/api/group/${id}`
})
dispatch(setEntity('group', group))
dispatch(finishRequest(FETCH_GROUP_ID, true))
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP, true))
} catch (err) {
dispatch(finishRequest(FETCH_GROUP_ID, false))
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP, false))
throw err
}
}
}
export const fetchGroups = (sort?: string, continuation?: string): AppThunkAction => {
return async dispatch => {
dispatch(startRequest(FETCH_GROUPS_ID))
interface GroupsResponse {
groups: Entity[]
continuation?: string
}
try {
const response = await getGroups(sort, continuation)
const groups = normalize(response.groups, [groupSchema])
export const fetchGroups = (sort?: string, continuation?: string): AppThunkAction => async dispatch => {
dispatch(startRequest(REQUEST_KEYS.FETCH_GROUPS))
dispatch(setEntities(groups.entities))
dispatch(setGroups(groups.result))
try {
const response = await apiFetch<GroupsResponse>({
path: `/api/groups?${objectToQuerystring({ sort, continuation })}`
})
if (response.continuation) {
dispatch(setContinuation(response.continuation))
}
const groups = normalize(response.groups, [groupSchema])
dispatch(finishRequest(FETCH_GROUPS_ID, true))
} catch (err) {
dispatch(finishRequest(FETCH_GROUPS_ID, false))
throw err
dispatch(setEntities(groups.entities))
dispatch(setGroups(groups.result))
if (response.continuation) {
dispatch(setContinuation(response.continuation))
}
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUPS, true))
} catch (err) {
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUPS, false))
throw err
}
}

185
src/actions/registration.ts

@ -1,22 +1,16 @@
import { Action, AnyAction } from 'redux'
import { ThunkAction } from 'redux-thunk'
import { Action } from 'redux'
import { apiFetch } from 'src/api'
import { setFieldNotification } from 'src/actions/forms'
import { startRequest, finishRequest } from 'src/actions/requests'
import { createGroup as fetchCreateGroup } from 'src/api/groups'
import { fetchGroupAvailability, register as fetchRegister } from 'src/api/registration'
import {
LOCAL_STORAGE_ACCESS_TOKEN_KEY,
LOCAL_STORAGE_ACCESS_TOKEN_AT_KEY,
LOCAL_STORAGE_REFRESH_TOKEN_KEY,
REQUEST_KEYS,
} from 'src/constants'
import { AppState, AppThunkAction } from 'src/types'
const FETCH_GROUP_AVAILABILITY_ID = 'FETCH_GROUP_AVAILABILITY'
const FETCH_USER_AVAILABILITY_ID = 'FETCH_USER_AVAILABILITY'
const CREATE_GROUP_ID = 'CREATE_GROUP'
const REGISTER_ID = 'REGISTER'
import { AppThunkAction } from 'src/types'
export interface SetStepAction extends Action {
type: 'REGISTRATION_SET_STEP'
@ -25,84 +19,141 @@ export interface SetStepAction extends Action {
export type RegistrationActions = SetStepAction
interface AvailabilityResponse {
id: string
available: boolean
}
export const setStep = (step: number): SetStepAction => ({
type: 'REGISTRATION_SET_STEP',
payload: step,
})
export const checkGroupAvailability = (name: string): AppThunkAction => {
return async dispatch => {
dispatch(startRequest(FETCH_GROUP_AVAILABILITY_ID))
try {
const response = await fetchGroupAvailability(name)
export const checkGroupAvailability = (name: string): AppThunkAction => async dispatch => {
dispatch(startRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY))
try {
const { id, available } = await apiFetch<AvailabilityResponse>({
path: '/api/group/available',
method: 'post',
body: {
name,
},
})
if (available) {
dispatch(setFieldNotification('group-name', 'success', `${id} is available`))
} else {
dispatch(setFieldNotification('group-name', 'error', `${id} isn't available`))
}
if (response.available) {
dispatch(setFieldNotification('group-name', 'success', `${response.id} is available`))
} else {
dispatch(setFieldNotification('group-name', 'error', `${response.id} isn't available`))
}
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, true))
} catch (err) {
dispatch(finishRequest(REQUEST_KEYS.FETCH_GROUP_AVAILABILITY, false))
throw err
}
}
dispatch(finishRequest(FETCH_GROUP_AVAILABILITY_ID, true))
} catch (err) {
dispatch(finishRequest(FETCH_GROUP_AVAILABILITY_ID, false))
throw err
export const checkUserAvailability = (name: string): AppThunkAction => async dispatch => {
dispatch(startRequest(REQUEST_KEYS.FETCH_USER_AVAILABILITY))
try {
const { id, available } = await apiFetch<AvailabilityResponse>({
path: '/api/user/available',
method: 'post',
body: {
name,
},
})
if (available) {
dispatch(setFieldNotification('user-id', 'success', `${id} is available`))
} else {
dispatch(setFieldNotification('user-id', 'error', `${id} isn't available`))
}
dispatch(finishRequest(REQUEST_KEYS.FETCH_USER_AVAILABILITY, true))
} catch (err) {
dispatch(finishRequest(REQUEST_KEYS.FETCH_USER_AVAILABILITY, false))
throw err
}
}
export const checkUserAvailability = (name: string): AppThunkAction => {
return async dispatch => {
dispatch(startRequest(FETCH_USER_AVAILABILITY_ID))
interface CreateGroupOptions {
name: string
registration: string
about?: string
}
try {
const { id, available } = await fetchGroupAvailability(name)
interface CreateGroupResponse {
id: string
}
if (available) {
dispatch(setFieldNotification('user-id', 'success', `${id} is available`))
} else {
dispatch(setFieldNotification('user-id', 'error', `${id} isn't available`))
}
export const createGroup = (options: CreateGroupOptions): AppThunkAction<string> => async dispatch => {
const { name, registration, about } = options
dispatch(finishRequest(FETCH_USER_AVAILABILITY_ID, true))
} catch (err) {
dispatch(finishRequest(FETCH_USER_AVAILABILITY_ID, false))
throw err
}
}
}
dispatch(startRequest(REQUEST_KEYS.CREATE_GROUP))
export const createGroup = (name: string, registration: string, about?: string): ThunkAction<Promise<string>, AppState, void, AnyAction> => {
return async dispatch => {
dispatch(startRequest(CREATE_GROUP_ID))
try {
const { id } = await apiFetch<CreateGroupResponse>({
path: '/api/group',
method: 'post',
body: {
name,
registration,
about,
},
})
try {
const { id } = await fetchCreateGroup(name, registration, about)
dispatch(finishRequest(CREATE_GROUP_ID, true))
dispatch(finishRequest(REQUEST_KEYS.CREATE_GROUP, true))
return id
} catch (err) {
dispatch(finishRequest(CREATE_GROUP_ID, false))
throw err
}
return id
} catch (err) {
dispatch(finishRequest(REQUEST_KEYS.CREATE_GROUP, false))
throw err
}
}
export const register = (id: string, email: string, password: string, name?: string, group?: string): ThunkAction<Promise<string>, AppState, void, AnyAction> => {
return async dispatch => {
dispatch(startRequest(REGISTER_ID))
try {
const response = await fetchRegister(id, email, password, name, group)
dispatch(finishRequest(REGISTER_ID, true))
interface RegisterOptions {
id: string
email: string
password: string
name?: string
group?: string
}
localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, response.access)
localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, response.refresh)
interface RegisterResponse {
id: string
access: string
refresh: string
}
return response.id
} catch (err) {
dispatch(finishRequest(REGISTER_ID, false))
throw err
}
export const register = (options: RegisterOptions): AppThunkAction<string> => async dispatch => {
const { id, email, password, name, group } = options
dispatch(startRequest(REQUEST_KEYS.REGISTER))
try {
const response = await apiFetch<RegisterResponse>({
path: '/api/register',
method: 'post',
body: {
id,
email,
password,
name,
group,
},
})
dispatch(finishRequest(REQUEST_KEYS.REGISTER, true))
localStorage.setItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY, response.access)
localStorage.setItem(LOCAL_STORAGE_REFRESH_TOKEN_KEY, response.refresh)
return response.id
} catch (err) {
dispatch(finishRequest(REQUEST_KEYS.REGISTER, false))
throw err
}
}

6
src/api/errors.ts

@ -1,5 +1,6 @@
import { History } from 'history'
import { setFieldNotification } from 'src/actions/forms'
import { showNotification } from 'src/actions/notifications'
import { AppThunkDispatch, FormNotification } from 'src/types'
@ -10,6 +11,11 @@ export function handleApiError(err: HttpError, dispatch: AppThunkDispatch, histo
if (err instanceof BadRequestError) {
dispatch(showNotification('error', `Error: ${err.message}`))
for (const error of err.errors) {
const { field, type, message } = error
if (field) dispatch(setFieldNotification(field, type, message))
}
}
if (err instanceof UnauthorizedError) {

6
src/api/fetch.ts

@ -68,7 +68,7 @@ const getResponseData = async (response: Response) => {
}
}
const apiFetch: APIFetch = async (options: FetchOptions) => {
export const apiFetch: APIFetch = async (options: FetchOptions) => {
const { path, method = 'get', body } = options
const contentType = 'application/json'
const config = await getConfig()
@ -137,7 +137,3 @@ const apiFetch: APIFetch = async (options: FetchOptions) => {
return await checkResponse(response, doRefresh)
}
export {
apiFetch as fetch,
}

42
src/api/groups.ts

@ -1,42 +0,0 @@
import { fetch } from './fetch'
import { Entity } from '../types'
interface GroupsResponse {
groups: Entity[]
continuation?: string
}
interface NewGroupResponse {
id: string
}
export async function getGroup(id: string) {
return await fetch<Entity>({
path: `/api/group/${id}`
})
}
export async function getGroups(sort: string = 'members', continuation?: string) {
const params = {
sort,
continuation,
}
const querystring = Object.entries(params).filter(([name, value]) => value !== undefined).map(([name, value]) => `${name}=${value}`).join('&')
return await fetch<GroupsResponse>({
path: `/api/groups?${querystring}`
})
}
export async function createGroup(name: string, registration: string, about?: string) {
return await fetch<NewGroupResponse>({
path: '/api/group',
method: 'post',
body: {
name,
registration,
about,
},
})
}

1
src/api/index.ts

@ -0,0 +1 @@
export { apiFetch } from './fetch'

46
src/api/registration.ts

@ -1,46 +0,0 @@
import { fetch } from './fetch'
interface AvailabilityResponse {
id: string
available: boolean
}
interface RegisterResponse {
id: string
access: string
refresh: string
}
export async function fetchGroupAvailability(name: string) {
return await fetch<AvailabilityResponse>({
path: '/api/group/available',
method: 'post',
body: {
name,
},
})
}
export async function fetchUserAvailability(name: string) {
return await fetch<AvailabilityResponse>({
path: '/api/user/available',
method: 'post',
body: {
name,
},
})
}
export async function register(id: string, email: string, password: string, name?: string, group?: string) {
return await fetch<RegisterResponse>({
path: '/api/register',
method: 'post',
body: {
id,
email,
password,
name,
group,
},
})
}

0
src/api/users.ts

2
src/components/app/app.scss

@ -5,7 +5,7 @@
// Colors
$orange: hsl(14, 100%, 53%);
$yellow: hsl(48, 100%, 67%);
$green: hsl(141, 71%, 48%);
$green: hsl(141, 65%, 31%);
$turquoise: hsl(171, 100%, 41%);
$cyan: hsl(204, 86%, 53%);
$blue: hsl(217, 72%, 30%);

17
src/components/app/app.tsx

@ -1,6 +1,8 @@
import React, { FC } from 'react'
import React, { FC, useEffect } from 'react'
import { BrowserRouter as Router, Route, Link } from 'react-router-dom'
import { LOCAL_STORAGE_ACCESS_TOKEN_KEY } from 'src/constants'
import Footer from '../footer'
import NavigationMenu from '../navigation-menu'
import NotificationContainer from '../notification-container'
@ -9,11 +11,12 @@ import UserInfo from '../user-info'
import About from '../pages/about'
import Developers from '../pages/developers'
import Directory from '../pages/directory'
import Home from '../pages/home'
import Login from '../pages/login'
import Register from '../pages/register'
import RegisterGroup from '../pages/register-group'
import Directory from '../pages/directory'
import Self from '../pages/self'
import Test from '../pages/test'
import './app.scss'
@ -21,12 +24,19 @@ import './app.scss'
interface Props {
collapsed: boolean
fetching: boolean
fetchSelf: () => void
}
const App: FC<Props> = ({ collapsed, fetching }) => {
const App: FC<Props> = ({ collapsed, fetching, fetchSelf }) => {
const mainMenuWidth = 250
const mainColumnLeftMargin = collapsed ? 0 : mainMenuWidth
useEffect(() => {
if (localStorage.getItem(LOCAL_STORAGE_ACCESS_TOKEN_KEY)) {
fetchSelf()
}
}, [])
return (
<Router>
<div>
@ -49,6 +59,7 @@ const App: FC<Props> = ({ collapsed, fetching }) => {
<Route path="/register" component={Register} />
<Route path="/c/:id/register" component={RegisterGroup} />
<Route path="/communities" component={Directory} />
<Route path="/self" component={Self} />
<Route path="/developers" component={Developers} />
<Route path="/about" component={About} />
<Route path="/test" component={Test} />

13
src/components/app/index.ts

@ -1,8 +1,10 @@
import { connect } from 'react-redux'
import { fetchSelf } from 'src/actions/authentication'
import { getFetching } from 'src/selectors'
import { getCollapsed } from 'src/selectors/menu'
import { AppState } from 'src/types'
import { AppState, AppThunkDispatch } from 'src/types'
import App from './app'
@ -11,6 +13,13 @@ const mapStateToProps = (state: AppState) => ({
fetching: getFetching(state),
})
const mapDispatchToProps = (dispatch: AppThunkDispatch) => ({
fetchSelf: () => {
dispatch(fetchSelf())
}
})
export default connect(
mapStateToProps
mapStateToProps,
mapDispatchToProps
)(App)

2
src/components/create-group-form/index.ts

@ -2,7 +2,7 @@ import { FocusEventHandler } from 'react'
import { connect } from 'react-redux'
import { checkGroupAvailability } from 'src/actions/registration'
import { AppState, AppThunkDispatch } from 'src/types'
import { AppThunkDispatch } from 'src/types'
import CreateGroupForm from './create-group-form'

2
src/components/forms/password-field/password-field.tsx

@ -56,7 +56,7 @@ const PasswordField: FC<Props> = ({
inputClassDictionary['is-success'] = true
controlClassDictionary['has-icons-right'] = true
icon = faCheckCircle
passwordMessage = <span>Strength: <span className="has-text-success">🔥</span></span>
passwordMessage = <span>Strength: <span className="has-text-success">LIT</span></span>
break
}
}

21
src/components/forms/select-field/select-field.tsx

@ -1,7 +1,9 @@
import React, { FC } from 'react'
import classNames from 'classnames'
import noop from 'lodash/noop'
import { FormNotification } from 'src/types'
import { notificationTypeToClassName } from 'src/utils'
import { FormNotification, ClassDictionary } from 'src/types'
interface SelectOptions {
[value: string]: string
@ -20,18 +22,33 @@ const SelectField: FC<Props> = ({
label,
options = {},
value = '',
notification,
setValue = noop,
}) => {
const opts = Object.entries(options)
const controlClassDictionary: ClassDictionary = { select: true, 'is-small': true }
const helpClassDictionary: ClassDictionary = { help: true }
console.log('notification', notification)
if (notification) {
const ncn = notificationTypeToClassName(notification.type)
controlClassDictionary[ncn] = true
helpClassDictionary[ncn] = true
}
return (
<div className="field">
<label className="label">{label}</label>
<div className="select is-small">
<div className={classNames(controlClassDictionary)}>
<select value={value} onChange={(e) => setValue(e.target.value)}>
{opts.map(([key, value]) => <option key={key} value={key}>{value}</option>)}
</select>
</div>
{notification &&
<p className={classNames(helpClassDictionary)}>{notification.message}</p>
}
</div>
)
}

7
src/components/forms/text-field/text-field.tsx

@ -29,14 +29,13 @@ const TextField: FC<Props> = ({
setValue = noop,
onBlur = noop,
}) => {
const controlClassNames = classNames({ control: true, 'has-icons-left': !!icon })
const helpClassDictionary: ClassDictionary = {}
const controlClassDictionary = { control: true, 'has-icons-left': !!icon }
const helpClassDictionary: ClassDictionary = { help: true }
const inputClassDictionary: ClassDictionary = { input: true }
if (notification) {
const ncn = notificationTypeToClassName(notification.type)
helpClassDictionary['help'] = true
helpClassDictionary[ncn] = true
inputClassDictionary[ncn] = true
}
@ -44,7 +43,7 @@ const TextField: FC<Props> = ({
return (
<div className="field">
<label className="label">{label}</label>
<div className={controlClassNames}>
<div className={classNames(controlClassDictionary)}>
<input className={classNames(inputClassDictionary)} type={type} placeholder={placeholder} value={value} onChange={(e) => setValue(e.target.value)} onBlur={onBlur} />
{icon &&
<span className="icon is-small is-left">

33
src/components/pages/register/index.ts

@ -1,5 +1,6 @@
import { connect } from 'react-redux'
import { handleApiError } from 'src/api/errors'
import { getForm } from 'src/selectors/forms'
import { getStep } from 'src/selectors/registration'
import { initForm, initField } from 'src/actions/forms'
@ -37,20 +38,24 @@ const mapDispatchToProps = (dispatch: AppThunkDispatch, ownProps: Props) => ({
return
}
const groupId = await dispatch(createGroup(
valueFromForm<string>(form, 'group-name', ''),
valueFromForm<string>(form, 'group-registration', '')
))
const userId = await dispatch(register(
valueFromForm<string>(form, 'user-id', ''),
valueFromForm<string>(form, 'user-email', ''),
valueFromForm<string>(form, 'password', ''),
valueFromForm<string>(form, 'user-name', ''),
groupId
))
ownProps.history.push('/self')
try {
const group = await dispatch(createGroup({
name: valueFromForm<string>(form, 'group-name', ''),
registration: valueFromForm<string>(form, 'group-registration', ''),
}))
await dispatch(register({
id: valueFromForm<string>(form, 'user-id', ''),
email: valueFromForm<string>(form, 'user-email', ''),
password: valueFromForm<string>(form, 'password', ''),
name: valueFromForm<string>(form, 'user-name', ''),
group,
}))
ownProps.history.push('/self')
} catch (err) {
handleApiError(err, dispatch, ownProps.history)
}
}
})

23
src/components/pages/self/index.ts

@ -0,0 +1,23 @@
import { connect } from 'react-redux'
import { getAuthenticatedUserId } from 'src/selectors/authentication'
import { getEntity } from 'src/selectors/entities'
import { AppState, AppThunkDispatch } from 'src/types'
import Self from './self'
const mapStateToProps = (state: AppState) => {
const userId = getAuthenticatedUserId(state)
const user = userId ? getEntity(state, 'user', userId) : undefined
return {
user,
}
}
const mapDispatchToProps = (dispatch: AppThunkDispatch) => ({})
export default connect(
mapStateToProps,
mapDispatchToProps
)(Self)

30
src/components/pages/self/self.tsx

@ -0,0 +1,30 @@
import React, { FC, useEffect } from 'react'
import { setTitle } from 'src/utils'
import { Entity } from 'src/types'
import PageHeader from 'src/components/page-header'
interface Props {
user?: Entity
}
const Self: FC<Props> = ({ user }) => {
useEffect(() => {
if (user) setTitle(user.name as string)
}, [user])
return (
<div>
<PageHeader title={user ? user.name as string : '?'} />
<div className="main-content">
<p>
Hello.
</p>
</div>
</div>
)
}
export default Self

7
src/constants/index.ts

@ -6,5 +6,10 @@ export const LOCAL_STORAGE_ACCESS_TOKEN_AT_KEY = 'FLEXOR_ACCESS_TOKEN_AT'
export const LOCAL_STORAGE_REFRESH_TOKEN_KEY = 'FLEXOR_REFRESH_TOKEN'
export const REQUEST_KEYS = {
FETCH_GROUP: 'FETCH_GROUP',
FETCH_GROUPS: 'FETCH_GROUPS',
FETCH_GROUP_AVAILABILITY: 'FETCH_GROUP_AVAILABILITY',
FETCH_USER_AVAILABILITY: 'FETCH_USER_AVAILABILITY',
CREATE_GROUP: 'CREATE_GROUP',
REGISTER: 'REGISTER',
}

1
src/selectors/authentication.ts

@ -1,3 +1,4 @@
import { AppState } from '../types'
export const getAuthenticated = (state: AppState) => state.authentication.authenticated
export const getAuthenticatedUserId = (state: AppState) => state.authentication.userId

2
src/types/index.ts

@ -44,4 +44,4 @@ export {
} from './store'
export type AppThunkDispatch = ThunkDispatch<AppState, void, AnyAction>
export type AppThunkAction = ThunkAction<Promise<void>, AppState, void, AnyAction>
export type AppThunkAction<T = void> = ThunkAction<Promise<T>, AppState, void, AnyAction>

2
src/utils/index.ts

@ -8,6 +8,8 @@ export function notificationTypeToClassName(type: NotificationType): string {
}
}
export const objectToQuerystring = (obj: object) => Object.entries(obj).filter(([name, value]) => value !== undefined).map(([name, value]) => `${name}=${value}`).join('&')
export function setTitle(title: string, decorate: boolean = true) {
if (decorate) {
document.title = `${title} | Flexor`

Loading…
Cancel
Save