Skip to content

Instantly share code, notes, and snippets.

@jerome-diver
Created August 5, 2021 00:14
Show Gist options
  • Save jerome-diver/aa4432216636e8ce464061563945a10b to your computer and use it in GitHub Desktop.
Save jerome-diver/aa4432216636e8ce464061563945a10b to your computer and use it in GitHub Desktop.
Container UI design part
/* CRUD for container collection to call from server API
at /api/containers address */
import { TAG, HOST, SERVER_PORT } from '../../Views/helpers/config'
const host = TAG + HOST + ":" + SERVER_PORT
const giveMe = (url, successCBK, failedCBK, finalCBK, isMounted) => {
fetch(url)
.then( response => response.json() )
.then( response => { (isMounted) ? successCBK(response) : null } )
.catch( error => { (isMounted) ? failedCBK( { state: true, content: error } ) : null } )
.finally( () => { (isMounted) ? finalCBK(false) : null } )
}
const getContainer = (id, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/' + id
giveMe(url, successCBK, failedCBK, finalCBK, isMounted)
}
const getChildrenContainersOf = (id, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/children_of/' + id
giveMe(url, successCBK, failedCBK, finalCBK, isMounted)
}
const getChildrenIDof = (id, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/children_ids_of/' + id
giveMe(url, successCBK, failedCBK, finalCBK, isMounted)
}
const getContainersOfType = (type_name, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/type/' + type_name
giveMe(url, successCBK, failedCBK, finalCBK, isMounted)
}
const getContainersIDofType = (type_name, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/type_id/' + type_name
giveMe(url, successCBK, failedCBK, finalCBK, isMounted)
}
const createContainer = (data, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/'
fetch(url, {
method: 'POST',
credentials: 'include',
headers: { 'Accept': 'application/json',
'Content-Type': 'application/json' },
body: JSON.stringify( { data } ) })
.then( response => response.json() )
.then( response => { (isMounted) ? successCBK(response) : null } )
.catch( error => { (isMounted) ? failedCBK( { state: true, content: error } ) : null } )
.finally( () => { (isMounted) ? finalCBK(false) : null } )
}
const updateContainer = (data, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/' + data.id + '/update'
console.log("Go to update a container for url:", url)
fetch(url, {
method: 'PUT',
credentials: 'include',
headers: { 'Accept': 'application/json',
'Content-Type': 'application/json' },
body: JSON.stringify( data.body ) })
.then( response => response.json() )
.then( response => { (isMounted) ? successCBK(response) : null } )
.catch( error => { (isMounted) ? failedCBK( { state: true, content: error } ) : null } )
.finally( () => { (isMounted) ? finalCBK(false) : null } )
}
const deleteContainer = (id, successCBK, failedCBK, finalCBK, isMounted) => {
const url = host + '/api/containers/' + id
fetch(url, {
method: 'DELETE',
credentials: 'include',
headers: { 'Accept': 'application/json',
'Content-Type': 'application/json' } })
.then( response => response.json() )
.then( response => { (isMounted) ? successCBK(response) : null } )
.catch( error => { (isMounted) ? failedCBK( { state: true, content: error } ) : null } )
.finally( () => { (isMounted) ? finalCBK(false) : null } )
}
const crud_caller = {
$getContainer: getContainer,
$getContainersOfType: getContainersOfType,
$getContainersIDofType: getContainersIDofType,
$getChildrenIDof: getChildrenIDof,
$getChildrenContainersOf: getChildrenContainersOf,
$createContainer: createContainer,
$updateContainer: updateContainer,
$deleteContainer: deleteContainer
}
const crud_list = ['getContainer', 'getChildrenContainersOf', 'getContainersOfType','getContainersIDofType' ,
'getChildrenIDof', 'updateContainer', 'createContainer', 'deleteContainer']
export { crud_caller, crud_list, getContainer, getContainersOfType, getChildrenContainersOf,
createContainer, updateContainer, deleteContainer }
/* HOC to Compose with Containers.component for ACTIONS */
import React, { useState, useRef, useEffect, useReducer } from 'react'
import { crud_caller, crud_list } from '../../../../Controllers/container/action-CRUD'
import { useTranslation } from 'react-i18next'
/* Component to use with ACTIONS (Private) */
const useFetch = (crud_name, data, triggers) => {
const [ loading, setLoading ] = useState(true)
const [ error, setError ] = useState({state: false, content: ""})
const [ response, setResponse ] = useState({})
const isMounted = useRef(null)
useEffect(() => {
isMounted.current = true
console.log("==> useFetch for CRUD's container function name and content:", {crud_name, data})
if (crud_list.includes(crud_name)) {
crud_caller['$' + crud_name](data, setResponse, setError, setLoading, isMounted.current)
}
return () => (isMounted.current = false)
}, triggers )
return { loading, error, response }
}
const dataReducer = (state, action) => {
console.log("dispatch for", action)
switch (action.type) {
case 'update':
return { crud: 'updateContainer', data: action.reference }
case 'delete':
return { crud: 'deleteContainer', data: action.reference }
case 'get':
return { crud: 'getContainer', data: action.reference }
}
}
/* Public ACTIONS HOC */
const actionsContainerLinks = UI => {
const actions = (props) => {
const edit = () => { props.callback('edit') }
const remove = (content, type) => { console.log("DELETE") }
const cancel = () => { props.callback('normal') }
props = { ...props, edit, remove, cancel }
return <UI {...props} />
}
return actions
}
const actionsContainer = UI => {
const actions = (props) => {
const { i18n } = useTranslation()
const [ validated, setValidated ] = useState(false)
const [ mode, setMode ] = useState('normal')
const [ state, dispatch ] = useReducer(dataReducer, {crud: 'getContainer', data: props.id})
const { loading, error, response } = useFetch(state.crud, state.data, [i18n.language, state])
const [ data, setData ] = useState({})
const [ form, setForm ] = useState({})
useEffect(()=>{
setForm({ title: { fr: response.title,
en: response.title_en },
content: { fr: response.content,
en: response.content_en } })
setData (response)
}, [response])
const change = target => value => {
if (target == 'title') setForm({...form, title: { [i18n.language]: value} })
else setForm({...form, content: { [i18n.language]: value } })
setData({title: data.title, title_en: data.title_en, content: data.content, content_en: data.content_en,
type_name: data.type_name, parent_id: data.parent_id, enable: data.enable, [target]: value})
}
const update = (container) => e => {
e.preventDefault();
const form_to_submit = e.currentTarget;
if (form_to_submit.checkValidity() === false) {
e.stopPropagation();
} else {
dispatch({ type: 'update', reference: { id: container.id, body: data } })
setValidated(true)
setMode('normal')
}
}
props = {...props, i18n, form, mode, setMode,
update, change, validated, loading, error, response, dispatch}
return <UI {...props} />
}
return actions
}
export { useFetch, actionsContainerLinks, actionsContainer }
/* it is all about a MongoDB Container linked with a Type
Containers component there is the main Component to use
to adapt return back for each situation depending on route and options
*/
import React from 'react'
import { string, bool, object, func, exact, number } from 'prop-types'
import { Loading, Error } from './Printers.component'
import { useTranslation } from "react-i18next"
import parse from 'html-react-parser'
import { useParams } from 'react-router-dom'
import { trContainer } from '../../helpers/config'
import { Card, CardGroup, Jumbotron, Badge, Button, Form, InputGroup, Image, Figure } from 'react-bootstrap'
import loadable from '@loadable/component'
import { actionsContainerLinks, actionsContainer, useFetch } from './compositions/containers.actions'
import { statesContainerLinks, statesHeadContainer, statesContainer } from './compositions/containers.states'
const Editor = loadable(() => import('for-editor'))
/* Pure UI components with HOC to compose with states and actions components.
-> ContainerLinks (with both edit and normal mode) show buttons for action on own Container
-> HeadContainer (and both normal and edit mode) show Top head container (root of tree called)
-> Container (and both normal and edit mode) to show a container of the children list content
-> Containers UI design to organize the full page to show containers content from specific call scenari
*/
/* Buttons actions links UI design for normal and edit mode */
const ContainerLinksUInormal = ( { t, data, type, edit, remove, cancel } ) => (
<>
<Button onClick={edit} variant="warning">
{ t('containers.content.edit', { content: data.title }) }
</Button>
<Button onClick={() => remove(content, type)} variant="danger">
{ t('containers.content.delete', { content: data.content }) }
</Button>
<Button onClick={cancel}>{ t('containers.content.cancel') }</Button>
</>
)
const ContainerLinksUIedit = ( { t, cancel } ) => (
<>
<Button type='submit' variant='warning'>
{t('containers.button_submit')}
</Button>
<Button onClick={cancel}>{ t('containers.content.cancel') }</Button>
</>
)
/* Head Containers UI design for normal and edit mode */
const HeadContainerUInormal = ({t, i18n, type_to_translate,
container, setMode, mode}) => (
<>
<Jumbotron id='head-container'>
<h1 id='head-container-title'>
{trContainer(i18n.language, container).title}&nbsp;
<Badge variant='info'>{t(type_to_translate)}</Badge>
</h1>
<Figure>
<Figure.Image rounded fluid src={`/uploads/${container.image_link}`} />
<Figure.Caption>
{parse(trContainer(i18n.language, container).content)}
</Figure.Caption>
</Figure>
<br/>
<ContainerLinks data={container} type={container.type_name} callback={setMode} mode={mode}/>
</Jumbotron>
</>
)
const HeadContainerUIedit = ( {t, i18n, type_to_translate,
validated, update, change, form,
container, setMode, mode}) => (
<>
<Jumbotron id='edit-container'>
<Badge variant='warning'>{t('containers.edit', {type: container.type_name})}</Badge>
<Form onSubmit={update(container)} noValidate validated={validated}>
<Form.Group controlId="formBasicText">
<Form.Label>Title</Form.Label>
<InputGroup>
<Form.Control type='text'
name="formContainerTitle"
onChange={change('title')}
value={form.title[i18n.language]}/>
<Badge variant='info'>{t(type_to_translate)}</Badge>
<Form.Control.Feedback type="invalid">Please update the title.</Form.Control.Feedback>
</InputGroup>
<Form.Text className="text-muted">{t('containers.helper.title')}</Form.Text>
</Form.Group>
<Form.Group controlId="formBasicText">
<Form.Label>Content this:</Form.Label>
<InputGroup>
<Editor value={form.content[i18n.language]}
onChange={change('content')}
language='en'
preview={true} />
<Form.Control.Feedback type="invalid">Please update the content.</Form.Control.Feedback>
</InputGroup>
<Form.Text className="text-muted">{t('containers.helper.content')}</Form.Text>
</Form.Group>
<ContainerLinks data={container} type={container.type_name} callback={setMode} mode={mode}/>
</Form>
</Jumbotron>
</>
)
/* Container UI design for mode edit and normal */
const ContainerUInormal = ( { t, i18n, type, container_type,
index, container, setMode, mode } ) => (
<>
<Card id={type+'_'+index}>
<Card.Img variant="top" src={`/uploads/${container.image_link}`} />
<Card.Body>
<Card.Title>
{trContainer(i18n.language, container).title}&nbsp;
<Badge variant='primary'>{t(container_type)}</Badge>
</Card.Title>
<Card.Text as="div">
{parse(trContainer(i18n.language, container).content)}
</Card.Text>
<Card.Link href={`/${container.type_name}/${container.id}`}>
{t('containers.link', { type, title: trContainer(i18n.language, container).title})}
</Card.Link>
<br/>
<ContainerLinks data={container} type={container_type} callback={setMode} mode={mode}/>
</Card.Body>
</Card>
</>
)
const ContainerUIedit = ( { t, i18n, type, container_type,
validated, update, change, form,
index, container, setMode, mode } ) => (
<>
<Card id={type+'_'+index}>
<Form onSubmit={update(container)} noValidate validated={validated}>
<Card.Img variant="top" src={`/uploads/${container.image_link}`} />
<Card.Body>
<Card.Title>
<Form.Group controlId="formBasicText">
<Form.Label>Title</Form.Label>
<InputGroup>
<Form.Control type='text'
name="formContainerTitle"
onChange={change('title')}
value={form.title[i18n.language]}/>
<Badge variant='primary'>{t(container_type)}</Badge>
<Form.Control.Feedback type="invalid">Please update the title.</Form.Control.Feedback>
</InputGroup>
<Form.Text className="text-muted">{t('containers.helper.title')}</Form.Text>
</Form.Group>
</Card.Title>
<Card.Text as="div">
<Form.Group controlId="formBasicText">
<Form.Label>Content this:</Form.Label>
<InputGroup>
<Editor value={form.content[i18n.language]}
onChange={change('content')}
language='en'
preview={true} />
<Form.Control.Feedback type="invalid">Please update the content.</Form.Control.Feedback>
</InputGroup>
<Form.Text className="text-muted">{t('containers.helper.content')}</Form.Text>
</Form.Group>
<ContainerLinks container={container} type={container.type_name} callback={setMode} mode={mode}/>
</Card.Text>
<Card.Link href={`/${container.type_name}/${container.id}`}>{t('containers.link',
{ type, title: trContainer(i18n.language, container).title})}</Card.Link>
<br/>
</Card.Body>
</Form>
</Card>
</>
)
/* Group of Containers */
const GroupContainer = ( props ) => {
const { i18n, t } = useTranslation()
const crud_mode = (props.children) ? 'getChildrenIDof' : 'getContainersIDofType'
const reference = (props.children) ? props.id : props.type
const { loading, error, response } = useFetch(crud_mode, reference, [])
if (loading) return <><Loading /></>
else if (error.state) return <><Error title={t('error:home.title')}
name={error.content.name}
message={error.content.message}
open={true} /></>
else {
return <CardGroup>
{ response.map( (data, index) => {
if (data.enabled) return <Container id={data.id} key={index} index={index} />
}) }
</CardGroup>
}
}
const CardTree = ({ data, link, text }) => (
<>
</>
)
/* type is a string for type name for containers list to print for,
children is an Object with two keys: {other, same} with boolean values
to show children for same and other types.
head is a boolean value for show params :id container content on head first. */
const Containers = ({ type, children }) => {
const { id } = useParams()
const { i18n, t } = useTranslation()
if (children == undefined) { // print containers list only
return (
<>
<h1>{t('containers.list', {type})}</h1>
<hr/>
<GroupContainer type={type} children={false} />
</>
)
} else if (!children.same && children.other) {
return (
<>
<HeadContainer id={id} />
<GroupContainer id={id} children={true} />
</>
)
} else if (children.same && !children.other) {
} else if (!children.same && !children.other) { }
}
/* Compose UI with actions and states to provide each Component */
const ContainerLinks = actionsContainerLinks(statesContainerLinks(ContainerLinksUInormal, ContainerLinksUIedit))
const HeadContainer = actionsContainer(statesHeadContainer(HeadContainerUInormal, HeadContainerUIedit))
const Container = actionsContainer(statesContainer(ContainerUInormal, ContainerUIedit))
/* Props Types checking part... */
Containers.propTypes = {
type: string.isRequired,
children: exact({
same: bool.isRequired,
other: bool.isRequired }),
}
Container.propTypes = {
id: string.isRequired,
index: number.isRequired
}
GroupContainer.propTypes = {
type: string,
children: bool
}
GroupContainer.defaultProps = {
children: false
}
ContainerLinks.propTypes = {
type: string.isRequired,
callback: func.isRequired
}
HeadContainer.propTypes = {
id: string.isRequired,
}
export default Containers
/* HOC to Compose with Containers.components STATES */
import React, { useState } from 'react'
import { useTranslation } from 'react-i18next'
import { colorType } from '../../../helpers/config'
import { useAuthenticate, itsMine, canModify } from '../../../../Controllers/context/authenticate'
import { Error, Loading } from '../Printers.component'
const statesContainerLinks = (UInormal, UIedit) => {
const states = (props) => {
const { t } = useTranslation()
const { getUser, getRole } = useAuthenticate()
const user = getUser()
const role = getRole()
props = { ...props, t}
if (canModify(role, props.type) || itsMine(user, props.data)) {
switch(props.mode) {
case 'normal':
return <UInormal {...props} />
case 'edit':
return <UIedit {...props} />
}
} else return null
}
return states
}
const statesHeadContainer = (UInormal, UIedit) => {
const states = (props) => {
const { t } = useTranslation()
const type_to_translate = "containers." + props.response.type_name
props = {...props, t, type_to_translate}
if (props.loading) return <><Loading /></>
if(props.error.state) return <><Error title={t('error:home.title')}
name={props.error.content.name}
message={props.error.content.message}
open={true} /></>
else {
switch (props.mode) {
case 'edit':
return <>
<style type='text/css'>{`
#edit-container-title h1 { display: inline-block; }
#edit-container-text { font-family: 'Santana'; }
.badge {
vertical-align: middle;
font-family: 'Source Code Pro'; }
#edit-container {
background-image: linear-gradient(to bottom left, rgb(199,19,99), rgb(44,32,22));
background-color: rgba(199,2,2,0.75) }
`}</style>
<UIedit {...props} container={props.response} />
</>
case 'normal':
return <>
<style type='text/css'>{`
#head-container-title h1 { display: inline-block; }
#head-container-text { font-family: 'Santana'; }
.badge {
vertical-align: middle;
font-family: 'Source Code Pro'; }
#head-container {
background-image: linear-gradient(to bottom left, rgb(99,99,99), rgb(44,32,22));
background-color: rgba(99,99,99,0.75) }
`}</style>
<UInormal {...props} container={props.response} />
</>
}
}
}
return states
}
const statesContainer = (UInormal, UIedit) => {
const states = (props) => {
const { t } = useTranslation()
const type = props.response.type_name
const container_type = "containers." + type
props = {...props, t, type, container_type}
if (props.loading) return <><Loading /></>
if(props.error.state) return <><Error title={t('error:home.title')}
name={props.error.content.name}
message={props.error.content.message}
open={true} /></>
else {
switch (props.mode) {
case 'edit':
return <>
<style type='text/css'>{`
#${type+'_'+props.index} {
margin: 5px;
min-width: 520px;
max-width: 600px;
border: 1px solid ${colorType(type)}; }
.badge {
vertical-align: middle;
font-family: 'Source Code Pro';}
#${type+'_'+props.index} .card-body {
background-color: rgba(55, 44, 44, 0.85);
background-image: linear-gradient(to bottom left, rgb(199,19,99), rgb(44,32,22)); }
#${type+'_'+props.index} .card-title .h5 { display: inline; }
`}</style>
<UIedit {...props} container={props.response} />
</>
case 'normal':
return <>
<style type='text/css'>{`
#${type+'_'+props.index} {
margin: 5px;
min-width: 520px;
max-width: 600px;
border: 1px solid ${colorType(type)}; }
.badge {
vertical-align: middle;
font-family: 'Source Code Pro';}
#${type+'_'+props.index} .card-body {
background-color: rgba(55, 44, 44, 0.85);
background-image: linear-gradient(to bottom left, rgb(99,99,99), rgb(44,32,22)); }
#${type+'_'+props.index} .card-title .h5 { display: inline; }
`}</style>
<UInormal {...props} container={props.response} />
</>
}
}
}
return states
}
export { statesContainerLinks, statesHeadContainer, statesContainer }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment