To run this project, we need a few things
- clone repo
HTTPS : git clone https://github.com/Arcane-404/arcane-404-blog.git
SSH : git clone git@github.com:Arcane-404/arcane-404-blog.git
GH-CLI : gh repo clone Arcane-404/arcane-404-blog
cd ./arcane-404-blog
git switch -c develop
npm run update
- check & merge any updates
- install server-side packages
- install client-side packages
- create
.env
- copy & paste keys
- get your values from:
SKIP_PREFLIGHT_CHECK = true
JWT_TOKEN = secret
MONGODB_URI = [ db-link ]
# development: mongodb://localhost/[db-name]
# production: mongodb+srv://[db-user-name]:[db-password]@[server-cluster-name]:[port-number]/[db-name]
# e.g. mongodb+srv://the-arcane-404:the-password-404@foo123-shard-00-03-a1b2c.mongodb.net:27017/blog_app_db
- if Mac, enter
chmod +x .husky/pre-commit
- if Windows, skip this step
- start
npm run dev
View client structure
arcane-404-blog/client/
โโ public/
โ โโ icons/
โ โ โโ favicon.ico
โ โโ index.html
โโ src/
โ โโ assets/
โ โโ components/
โ โโ constants/
โ โโ containers/
โ โโ contexts/
โ โโ hooks/
โ โโ json/
โ โโ pages/
โ โโ services/
โ โโ theme/
โ โโ utils/
โ โโ App.jsx
โ โโ index.js
โโ .env
โ
View server structure
arcane-404-blog/
โโ client/
โโ config/
โโ controllers/
โโ middlewares/
โโ models/
โโ routes/
index.js
View general structure
arcane-404-blog/
โโ .github/
โโ .husky/
โโ .env
โโ .editorconfig
โโ .eslintignore
โโ .eslintrc.json
โโ .gitignore
โโ .lintstagedrc.json
โโ .prettierignore
โโ .prettierrc.json
โโ index.js
โโ package.json
โโ README.md
Our dev life cycle
- #. Starting the day: pull all updates
- Work on branch off of
develop
- Work on code
- Messageย theย reviewer
- #.ย Ending the day: commit/push updates
- Asana: create or update task 'To-Do'
- VS Code + Git: branch, commit message, push
- GitHub: manually create Pull Request
- Asana: move task 'In Review'
- Discord: message reviewer
- VS Code + Git: pull, branch (create || continue)
- Asana: update or move task 'In Progress'
- 1 Epic initiative contains a unique Sprint # to follow any Tasks or Bugs
- Epic: type branch name (descriptor)
- Task: sub-tasks of commit messages
- work on a support branch, PR to the develop branch, but never the main branch
- core branch: main \ develop \ release
- support branch: feature \ style \ fix \ chore \ test
- submit any PR to review in the
#๐-code-review
- post
@[username] [quick-message] [pull-request-link]
- Components are Connections to a Container that make a Page
- building blocks of Atomic Design + Folder Structure
Our file naming convention
React
- components/: _Component.styles.js + index.jsx
- connections/: _Connection.jsx + _Connection.styles.js + index.jsx
- containers/: _Container.jsx + _Container.styles.js + index.jsx
- pages/: _CurrentPage.jsx
- services/: _User.services.js
Node
- controllers/: _User.controllers.js
- model/: _User.model.js
- routes/: _User.route.js
General
- index.js: single source path for each folder
- export { default as Component } from './[path]'
Our code guideline
- white-space
- single quotes
- no semi-colon
- parent Component fn == arrow fn
- deconstruct props
- child helper fn == arrow fn
- if fn has one line, then make it inline
- else open to block scope, {}
- callback fn
- if single arg, none:
fn(item โ ())
- else, use ():
fn((item, index) => ())
- if single arg, none:
- open brackets, not condensed
// rafce (shortcut) || rfc (default)
import React, { useState, useEffect } from 'react'
import { Button } from './components' // PascalCase
const isEmpty, hasKey, getRandomNumber // camelCase
const DESKTOP_SIZE = 1080 // CAP_CASE + fixed value
const arr = [ 'a', 'b', 'c' ]
const obj = { num1: 1, num2: 2, num3: 3 }
const Component = ({ num1, num2, num3 }) => {
const [ count, setCount ] = useState('')
// const handleClick = (e) => setCount(count + 1)
const handleClick = (e) => {
setCount(count + 1)
}
useEffect(() => {
console.log('check state update:', count)
}, [ num1, num2, num3 ])
return (
<div>
<h2>Hooks Example: <code>{ count }</code></h2>
<a { ...obj } obj={{ num4: 4 }}>show me</a>
<button onClick={ handleClick }>increment</button>
<button onClick={ (e) => console.log('+') }>log</button>
{
arr.length && arr.map((item, index) => (
<Button key={ uuid() }>{ item }</Button>
))
}
{
Object.keys(obj).length && (
<>
<a href="#">Home Page</a>
<a href="#">About Page</a>
</>
)
}
</div>
)
}
export default Component
Our block setup
- components/
- connections/
- containers/
- pages/
example: Login Form
// create Button, Label, Input, ErrorText
// Button/ โ _Button.styles.js
import { chakra, Button } from '@chakra-ui/react'
export const ButtonBox = chakra(Button, {})
// Button/ โ _index.jsx
import React from 'react'
import { ButtonBox } from './_Button.styles'
export default function Button({ children, ...props }) {
return <ButtonBox {...props}>{children}</ButtonBox>
}
// index.js
export { default as Button } from './Button'
// create TextField โ Button + Label + Input + ErrorText
// TextField/ โ _TextField.styles.js
import { chakra, FormControl } from '@chakra-ui/react'
export const TextFieldBox = chakra(FormControl, {})
// TextField/ โ _TextField.jsx
import React from 'react'
import { TextFieldBox } from './_TextField.styles'
import { Label, Input, ErrorText } from '../components'
export default function TextField({ children, ...props }) {
return <TextFieldBox {...props}>{children}</TextFieldBox>
}
TextField.Input = function TextField(props) {
return <Input {...props} />
}
// ...
// TextField/ โ index.jsx
import React, { useState } from 'react'
import TextField from './_TextField'
const TextFieldConnection = ({ type, name, label, placeholder, error }) => {
const [value, setValue] = useState('')
const onChange = (e) => setValue(e.target.value)
const inputProps = {
type,
id: name,
name,
placeholder,
value,
onChange,
}
return (
<TextField isInvalid={error}>
<TextField.Label htmlFor={name} text={label} />
<TextField.Input {...inputProps} />
{error && <TextField.Error text={error.mesage} />}
</TextField>
)
}
// index.js
export { default as TextField } from './TextField'
// create LoginForm โ Form + Heading, Submit
// LoginForm/ โ _LoginForm.styles.js
import { chakra } from '@chakra-ui/react'
export const FormBox = chakra('form', {})
// LoginForm/ โ _LoginForm.jsx
import React from 'react'
import { FormBox } from './_FormBox.styles'
import { Button } from '../components'
export default function LoginForm ({ children, ...props }) {
return <FormBox { ...props }>{ children }</FormBox>
}
LoginForm.Submit = ({ children, ...props }) {
return <Button { ...props }>{ children }</Button>
}
// ...
// LoginForm/ โ index.jsx
import React, { useState } from 'react'
import LoginForm from './_LoginForm'
import { TextField } from '../connections'
const LoginFormContainer = () => {
const [ values, setValues ] = useState('')
const onSubmit = (e) => e.preventDefault()
return (
<LoginForm>
<LoginForm.Heading>Login</LoginForm.Heading>
<TextField name="email" label="email" />
<TextField name="password" label="Password" />
<LoginForm.Submit>Submit</LoginForm.Submit>
</LoginForm>
)
}
// index.js
export { default as LoginForm } from './LoginForm'
import React from 'react'
import { LoginForm } from '../containers'
const HomePage = () => {
return (
<>
<LoginForm />
</>
)
}
- Register & verify a new user account
- Login & verify a valid user account
- view all our blog post article at the home page
- select one blog post to view the whole content
- for users, upvote or downvote the blog post
- only for admins, submit a new blog post
Method | Path | Description |
---|---|---|
POST | /api/user/register |
enter new user account |
POST | /api/user/login |
enter existing user account |
POST | /api/user/validateToken |
verify if user is in a current session |
{
"username": { "STRING", "default": "anonymous" },
"email": { "STRING", "required", "unique", "index" },
"password": "STRING",
"avatar": "STRING",
"role": { "STRING", "default": "USER" },
"createdAt": "DATE"
}
Method | Path | Description |
---|---|---|
GET | /api/blog/all |
view all blog post |
GET | /api/blog/:id |
view one blog post from blog ID |
POST | /api/blog/create |
create one new blog post |
PUT | /api/blog/:id?vote=_ |
update up/down vote for specific blog post |
{
"author": {
"id": { "ref": "USER" },
"username": "STRING"
},
"title": "STRING",
"body": "STRING",
"upvote": [{ "ref": "USER" }],
"downvote": [{ "ref": "USER" }],
"createdOn": "DATE"
}
|
|
|
|
|
|
RESTful API - CRUD - MVC React Hooks - Compound-Components |
- readme.so/
- carbon.now.sh/
- openchakra.app/
- mockaroo.com/
- lucidchart.com/
- avatars.dicebear.com/
- ascii-tree-generator.com/
- codestream.com/
- thunderclient.io/
- app.uploadcare.com/