PR-610983: Creating the example project of a List Task App.

This commit is contained in:
Alejandro Lembke Barrientos 2023-10-12 06:30:16 +00:00
parent f76a89eacb
commit dd3e108bca
20 changed files with 353 additions and 203 deletions

44
.github/workflows/npm-test.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: Testing package
on:
pull_request:
branches: ['*']
jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- uses: actions/checkout@v3
- name: Use Node.js 16
uses: actions/setup-node@v3
with:
node-version: 16
cache: 'npm'
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm test
cypress-run-component:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v5 # use the explicit version number
with:
component: true
test-build-package:
needs: [ test, cypress-run-component ]
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: 16
registry-url: https://registry.npmjs.org/
- run: npm ci
- run: npm run build:frontend

View File

@ -1,26 +1,15 @@
# Create React SSR
# Test List App
This project aims to have a starter kit for creating a new React app with Server Side Rendering with a backend in go and tools that generally go along with it.
It is not a project like create-react-app, create-react-app is used as a starter kit that handles all your scripts underneath, this is a project for developers who want more control over their application.
Tech(Library or Framework) | Version |
--- | --- |
React (Render Library) | 18.2.0
Redux (Global State Management) | 4.2.1
React Router DOM (Routing) | 6.16.0
Jest (Testing) | 29.7.0
Cypress (E2E Testing) | 13.3.0
Typescript | 5.2.2
This project is an Example of a List of Tasks App.
## Setup
To create a new project run in the terminal:
To start the project you need to clone the repo:
```
npx @aleleba/create-react-go-ssr app-name
git clone git@github.com:aleleba/test-list-app.git
```
Then run:
```
cd app-name
cd test-list-app
```
You will need to create a new .env file at the root of the project for global config.
This is an exaple of config.

95
package-lock.json generated
View File

@ -1,15 +1,20 @@
{
"name": "@aleleba/create-react-go-ssr",
"version": "1.0.3",
"name": "test-list-app",
"version": "0.0.1",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@aleleba/create-react-go-ssr",
"version": "1.0.3",
"name": "test-list-app",
"version": "0.0.1",
"license": "MIT",
"dependencies": {
"@babel/register": "^7.22.15",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"helmet": "^7.0.0",
@ -17,6 +22,7 @@
"ignore-styles": "^5.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-list-ui-library": "^1.0.2",
"react-redux": "^8.1.3",
"react-router-dom": "^6.16.0",
"react-router-hash-link": "^2.4.3",
@ -36,9 +42,6 @@
"workbox-strategies": "^7.0.0",
"workbox-streams": "^7.0.0"
},
"bin": {
"create-react-go-ssr": "bin/cli.js"
},
"devDependencies": {
"@babel/core": "^7.23.0",
"@babel/preset-env": "^7.22.20",
@ -2027,6 +2030,75 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
}
},
"node_modules/@fortawesome/fontawesome-common-types": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.2.tgz",
"integrity": "sha512-1DgP7f+XQIJbLFCTX1V2QnxVmpLdKdzzo2k8EmvDOePfchaIGQ9eCHj2up3/jNEbZuBqel5OxiaOJf37TWauRA==",
"hasInstallScript": true,
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/fontawesome-svg-core": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.2.tgz",
"integrity": "sha512-gjYDSKv3TrM2sLTOKBc5rH9ckje8Wrwgx1CxAPbN5N3Fm4prfi7NsJVWd1jklp7i5uSCVwhZS5qlhMXqLrpAIg==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-brands-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.2.tgz",
"integrity": "sha512-LKOwJX0I7+mR/cvvf6qIiqcERbdnY+24zgpUSouySml+5w8B4BJOx8EhDR/FTKAu06W12fmUIcv6lzPSwYKGGg==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-regular-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-regular-svg-icons/-/free-regular-svg-icons-6.4.2.tgz",
"integrity": "sha512-0+sIUWnkgTVVXVAPQmW4vxb9ZTHv0WstOa3rBx9iPxrrrDH6bNLsDYuwXF9b6fGm+iR7DKQvQshUH/FJm3ed9Q==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/free-solid-svg-icons": {
"version": "6.4.2",
"resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.2.tgz",
"integrity": "sha512-sYwXurXUEQS32fZz9hVCUUv/xu49PEJEyUOsA51l6PU/qVgfbTb2glsTEaJngVVT8VqBATRIdh7XVgV1JF1LkA==",
"hasInstallScript": true,
"dependencies": {
"@fortawesome/fontawesome-common-types": "6.4.2"
},
"engines": {
"node": ">=6"
}
},
"node_modules/@fortawesome/react-fontawesome": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/@fortawesome/react-fontawesome/-/react-fontawesome-0.2.0.tgz",
"integrity": "sha512-uHg75Rb/XORTtVt7OS9WoK8uM276Ufi7gCzshVWkUJbHhh3svsUUeqXerrM96Wm7fRiDzfKRwSoahhMIkGAYHw==",
"dependencies": {
"prop-types": "^15.8.1"
},
"peerDependencies": {
"@fortawesome/fontawesome-svg-core": "~1 || ~6",
"react": ">=16.3"
}
},
"node_modules/@humanwhocodes/config-array": {
"version": "0.11.11",
"dev": true,
@ -13818,6 +13890,15 @@
"dev": true,
"license": "MIT"
},
"node_modules/react-list-ui-library": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/react-list-ui-library/-/react-list-ui-library-1.0.2.tgz",
"integrity": "sha512-5f4ET0toaBPrDKdtuSLCiJ88y7TmWmpgXlvK6BiRAcXwFLOaYL8VJgGIhSL+GBny5HG88Rg/PIYeNS6AJeMmYA==",
"peerDependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
}
},
"node_modules/react-redux": {
"version": "8.1.3",
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz",

View File

@ -1,8 +1,8 @@
{
"name": "test-list-app",
"version": "0.0.1",
"version": "1.0.0",
"description": "Starter Kit of server side render of react with backend in go",
"scripts": {
"scripts": {
"start": "cd build/server && ./react-server",
"start-frontend:dev": "webpack watch --config webpack.config.ts",
"start-server:dev": "cd src/server && go run main.go",
@ -38,6 +38,11 @@
"homepage": "https://github.com/aleleba/create-react-go-ssr#readme",
"dependencies": {
"@babel/register": "^7.22.15",
"@fortawesome/fontawesome-svg-core": "^6.4.2",
"@fortawesome/free-brands-svg-icons": "^6.4.2",
"@fortawesome/free-regular-svg-icons": "^6.4.2",
"@fortawesome/free-solid-svg-icons": "^6.4.2",
"@fortawesome/react-fontawesome": "^0.2.0",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"helmet": "^7.0.0",
@ -45,6 +50,7 @@
"ignore-styles": "^5.0.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-list-ui-library": "^1.0.2",
"react-redux": "^8.1.3",
"react-router-dom": "^6.16.0",
"react-router-hash-link": "^2.4.3",

View File

@ -0,0 +1,65 @@
/* List */
export type TList = {
list: TItem[]
}
export interface IListPayload {
index?: number
item?: TItem
}
export type TItem = {
name: string,
status: Status
}
export enum Status {
TODO = 'TODO',
DONE = 'DONE'
}
export interface IAddItemToList {
type: ActionTypesList.AddItem
payload: IListPayload
}
export interface IChangeStatus {
type: ActionTypesList.ChangeStatus
payload: IListPayload
}
export interface IDeleteItemToList {
type: ActionTypesList.DeleteItem
payload: IListPayload
}
export enum ActionTypesList {
AddItem = 'ADD_ITEM',
ChangeStatus = 'CHANGE_STATUS',
DeleteItem = 'DELETE_ITEM'
}
export type TListAction = {
type: ActionTypesList
payload: IListPayload
}
const addItem = (payload: IAddItemToList) => ({
type: ActionTypesList.AddItem,
payload
});
const changeStatus = (payload: IChangeStatus) => ({
type: ActionTypesList.ChangeStatus,
payload
});
const listActions = {
addItem,
changeStatus
};
/* List */
export default listActions;

View File

@ -1,9 +0,0 @@
import test, { TTest } from './testAction';
export type TAction = TTest
const actions = {
test
}
export default actions

View File

@ -1,25 +0,0 @@
export enum ActionTypesTest {
ChangeHello = 'CHANGE_HELLO'
}
export interface IChangeHello {
type: ActionTypesTest.ChangeHello
payload: IChangeHelloPayload
}
export interface IChangeHelloPayload {
hello: any | undefined
}
export type TTest = IChangeHello
const changeHello = (payload: string) => ({
type: ActionTypesTest.ChangeHello,
payload
})
const actions = {
changeHello
}
export default actions

View File

@ -1,38 +0,0 @@
.App {
text-align: center;
}
.App-logo {
height: 40vmin;
pointer-events: none;
}
@media (prefers-reduced-motion: no-preference) {
.App-logo {
animation: App-logo-spin infinite 20s linear;
}
}
.App-header {
min-height: 100vh;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
font-size: calc(10px + 2vmin);
color: white;
}
.App-link {
color: #61dafb;
}
@keyframes App-logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}

View File

@ -1,36 +0,0 @@
import React from 'react';
import './InitialComponent.scss';
import { connect } from 'react-redux';
import { Link } from 'react-router-dom';
const InitialComponent = ({ hello }: { hello: string }) => {
return(
<div className="App">
<header className="App-header">
<img src="assets/img/logo.svg" className="App-logo" alt="logo" />
<p>This is the text from the store of redux: <strong>{hello}</strong></p>
<p>
Edit <code>src/frontend/InitialComponent.jsx</code> and save to reload.
</p>
<a
className="App-link"
href="https://reactjs.org"
target="_blank"
rel="noopener noreferrer"
>
Learn React
</a>
<Link className="App-link" to="/other-component">Other Component</Link>
</header>
</div>
);
};
const mapStateToProps = (state) => {
return {
hello: state.testReducer.hello
};
};
export default connect(mapStateToProps)(InitialComponent);

View File

@ -0,0 +1,69 @@
import React, { useState } from 'react';
import { connect } from 'react-redux';
import { TItem, ActionTypesList, Status } from '../actions/ListAction';
import 'react-list-ui-library/dist/index.css';
import { ContainerList, List } from 'react-list-ui-library';
// eslint-disable-next-line @typescript-eslint/no-explicit-any
const ListComponent = ({ list, addItem, changeStatus, deleteItem }: {list: TItem[], addItem: (item: TItem) => any, changeStatus: (index: number) => any, deleteItem: (index: number) => any }) => {
const [itemName, setItemName] = useState<string>('');
const onClickAddItem = () => {
const item: TItem = {
name: itemName,
status: Status.TODO
};
if(itemName !== ''){
addItem(item);
setItemName('');
}else{
alert('Please, insert a name');
}
};
const onChangeInput = (e) => {
setItemName(e.target.value);
};
const handleChangeState = (index) => {
changeStatus(index);
};
const onClickRemoveItem = (index) => {
deleteItem(index);
};
return(
<div className="App">
<ContainerList title='List App'>
<List
list={list}
valueInput={itemName}
placeholderInput='Insert a name of a new item'
onChangeInput={onChangeInput}
onClickAddItem={onClickAddItem}
onClickRemoveItem={onClickRemoveItem}
handleChangeState={handleChangeState}
/>
</ContainerList>
</div>
);
};
const mapStateToProps = (state) => {
return {
list: state.listReducer.list
};
};
const mapDispatchToProps = (dispatch) => {
return {
addItem: (item: TItem) => dispatch({ type: ActionTypesList.AddItem, payload: { item: item } }),
changeStatus: (index: number) => dispatch({ type: ActionTypesList.ChangeStatus, payload: { index: index } }),
deleteItem: (index: number) => dispatch({ type: ActionTypesList.DeleteItem, payload: { index: index } })
};
};
export default connect(mapStateToProps, mapDispatchToProps)(ListComponent);

View File

@ -1,18 +0,0 @@
import React from 'react';
// import logo from '../logo.svg';
import './InitialComponent.scss';
import { Link } from 'react-router-dom';
const OtherComponent = () => (
<div className="App">
<header className="App-header">
<img src="assets/img/logo.svg" className="App-logo" alt="logo" />
<p>
Edit <code>src/frontend/OtherComponent.jsx</code> and save to reload.
</p>
<Link className="App-link" to="/">Initial Component</Link>
</header>
</div>
);
export default OtherComponent;

View File

@ -4,7 +4,7 @@ import { useRoutes } from 'react-router-dom';
import routes from '../../routes';
const PrincipalRoutes = () => {
let element = useRoutes(routes);
const element = useRoutes(routes);
return element;
};

View File

@ -11,6 +11,6 @@ describe('Testing Card Component', () => {
);
});
it('Show Text', () => {
cy.get('p').contains('Edit src/frontend/InitialComponent.jsx and save to reload.');
cy.get('div').contains('List App');
});
});

View File

@ -4,20 +4,20 @@ import { ProviderMock } from '@mocks';
import App from '@components/App';
describe('<App/> Component', () => {
beforeEach(() => {
fetchMock.resetMocks();
});
beforeEach(() => {
fetchMock.resetMocks();
});
it('Should render root <App /> Component', async () => {
fetchMock.mockResponseOnce(JSON.stringify({
//First Data Fetch
data: 'data'
}));
it('Should render root <App /> Component', async () => {
fetchMock.mockResponseOnce(JSON.stringify({
//First Data Fetch
data: 'data'
}));
render(
<ProviderMock>
<App />
</ProviderMock>
)
})
})
render(
<ProviderMock>
<App />
</ProviderMock>
);
});
});

View File

@ -1,14 +1,14 @@
import { combineReducers } from 'redux';
import testReducer from './testReducer';
import { IChangeHelloPayload } from '../actions/testAction';
import listReducer from './listReducer';
import { TList } from '../actions/ListAction';
export interface IInitialState {
testReducer?: IChangeHelloPayload | undefined
listReducer?: TList | undefined
}
const rootReducer = combineReducers({
// Here comes the reducers
testReducer
listReducer
});
export default rootReducer;

View File

@ -0,0 +1,48 @@
import { ActionTypesList, TListAction, TItem, Status } from '../actions/ListAction';
const initialState = {
list: []
};
const listReducer = (state = initialState, action: TListAction) => {
switch (action.type){
case ActionTypesList.AddItem: {
const newList = [
...state.list,
action.payload.item
];
return {
list: newList
};
}
case ActionTypesList.ChangeStatus: {
const itemList = state.list[action.payload.index as number] as TItem;
const actualStatus = itemList.status;
const newItemList = {
...itemList,
status: actualStatus === Status.TODO ? Status.DONE : Status.TODO
};
const newList = [
...state.list.slice(0, action.payload.index as number),
newItemList,
...state.list.slice(action.payload.index as number + 1)
];
return {
list: newList
};
}
case ActionTypesList.DeleteItem: {
const newList = [
...state.list.slice(0, action.payload.index as number),
...state.list.slice(action.payload.index as number + 1)
];
return {
list: newList
};
}
default:
return state;
}
};
export default listReducer;

View File

@ -1,20 +0,0 @@
import { TAction } from '../actions';
const initialState = {
hello: 'world'
};
const testReducer = (state = initialState, action: TAction) => {
switch (action.type){
case 'CHANGE_HELLO': {
const newHello = action.payload.hello;
return {
hello: newHello
};
}
default:
return state;
}
};
export default testReducer;

View File

@ -1,4 +1,4 @@
$base-color: #282c34;
$base-color: #FFFFFF;
body {
background-color: $base-color;

View File

@ -1,15 +1,9 @@
import React from 'react';
import InitialComponent from '../frontend/components/InitialComponent';
import OtherComponent from '../frontend/components/OtherComponent';
const OTHER_COMPONENT = {
path: '/other-component',
element: <OtherComponent />
};
import List from '../frontend/components/List';
const INITIAL_COMPONENT = {
path: '/',
element: <InitialComponent />,
element: <List />,
};
export default [ INITIAL_COMPONENT, OTHER_COMPONENT ];
export default [ INITIAL_COMPONENT ];

View File

@ -43,7 +43,7 @@ func RegisterHandlers(e *echo.Echo, paths []string) {
<meta name="theme-color" content="#000000">
<!-- ${manifestJson} -->
<link href="assets/frontend.css" rel="stylesheet" type="text/css"></link>
<link href="assets/vendors.css" rel="stylesheet" type="text/css"></link>
<title>App</title>
</head>
<body>