From dff45c14de5c11aa0dd36126b69efc31c95f285a Mon Sep 17 00:00:00 2001 From: PraveenKumar Date: Thu, 2 Jul 2020 21:33:41 +0530 Subject: [PATCH 1/3] Will initialize the project. --- .gitignore | 8 ++ README.md | 22 +++ index.html | 14 ++ package.json | 51 +++++++ src/features/App.tsx | 42 ++++++ src/features/AppReducer.ts | 5 + src/features/Home.tsx | 9 ++ src/features/index.ts | 2 + src/index.tsx | 9 ++ src/services/ApiService.ts | 13 ++ src/services/ServiceApi.ts | 6 + src/services/ServiceFactory.ts | 32 +++++ src/services/StoreService.ts | 36 +++++ src/services/WebApi.ts | 122 +++++++++++++++++ src/services/index.ts | 2 + src/store/ServiceActions.ts | 77 +++++++++++ src/store/ServiceReducers.ts | 20 +++ src/store/StoreUtils.ts | 161 ++++++++++++++++++++++ src/store/index.ts | 2 + src/utils/Utils.ts | 27 ++++ src/utils/index.ts | 1 + tsconfig.json | 55 ++++++++ tslint.json | 238 +++++++++++++++++++++++++++++++++ webpack.config.js | 74 ++++++++++ 24 files changed, 1028 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 index.html create mode 100644 package.json create mode 100644 src/features/App.tsx create mode 100644 src/features/AppReducer.ts create mode 100644 src/features/Home.tsx create mode 100644 src/features/index.ts create mode 100644 src/index.tsx create mode 100644 src/services/ApiService.ts create mode 100644 src/services/ServiceApi.ts create mode 100644 src/services/ServiceFactory.ts create mode 100644 src/services/StoreService.ts create mode 100644 src/services/WebApi.ts create mode 100644 src/services/index.ts create mode 100644 src/store/ServiceActions.ts create mode 100644 src/store/ServiceReducers.ts create mode 100644 src/store/StoreUtils.ts create mode 100644 src/store/index.ts create mode 100644 src/utils/Utils.ts create mode 100644 src/utils/index.ts create mode 100644 tsconfig.json create mode 100644 tslint.json create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4262cf36 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +node_modules +dist +dist/bundle.js.map +dist/bundle.js +package-lock.json +dist/bundle.js.map +dist/bundle.js +npm-debug.log diff --git a/README.md b/README.md new file mode 100644 index 00000000..275da318 --- /dev/null +++ b/README.md @@ -0,0 +1,22 @@ +# react-typescript-redux-boilerplate + +This project is react with typescript and redux and redux thunk as middleware boilerplace. +Using Webpack for build tool. + +Initialize the project , We have to initialize the folder with node by using following command + +npm init + +Series of prompts will be ask and you can set the defaults. We can change whenever we need later in the package.json file which is auto generated file for us. + +Lets install the dependencies which is required to run the project. We will install the webpack globally, as it might use for various projects. + +npm install -g webpack + +Webpack is a tool that will bundle your code and optionally all of its dependencies into a single .js file. Add React and React-DOM, along with their declaration files, as dependencies to your package.json file. + +npm install // for local dependencies. + +to run the project , + +npm run dev \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..91e377ab --- /dev/null +++ b/index.html @@ -0,0 +1,14 @@ + + + + + + Hello React! + + + +
+ + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..da187922 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "react.pbpraveen.me", + "version": "1.0.0", + "description": "React Tutorial with Typescript", + "main": "index.js", + "scripts": { + "clean": "rm -rf /dist", + "build": "webpack --mode=development", + "dev": "set NODE_ENV=dev && npm run clean && npm run build && webpack-dev-server --mode=development --content-base dist/", + "start": "" + }, + "author": "Praveen Kumar", + "license": "ISC", + "dependencies": { + "@blueprintjs/core": "^3.29.0", + "@types/lodash": "^4.14.157", + "@types/pure-render-decorator": "^0.2.28", + "@types/react": "^16.0.36", + "@types/react-addons-css-transition-group": "^15.0.3", + "@types/react-dom": "^16.0.3", + "@types/react-redux": "^7.1.9", + "babel-loader": "^8.1.0", + "babel-preset-es2015": "^6.24.1", + "babel-preset-react": "^6.24.1", + "css-loader": "^3.6.0", + "es6-promise": "^4.2.8", + "extract-text-webpack-plugin": "^3.0.2", + "firebase": "^4.11.0", + "html-webpack-plugin": "^4.3.0", + "lodash": "^4.17.15", + "react": "^16.2.0", + "react-addons-css-transition-group": "^15.6.2", + "react-dom": "^16.2.0", + "react-redux": "^7.2.0", + "redux": "^4.0.5", + "redux-thunk": "^2.3.0", + "runtypes": "^4.3.0", + "sass-loader": "^8.0.2", + "style-loader": "^1.2.1", + "url-loader": "^4.1.0", + "webpack": "^4.43.0", + "whatwg-fetch": "^3.1.0" + }, + "devDependencies": { + "awesome-typescript-loader": "^5.2.1", + "source-map-loader": "^1.0.1", + "typescript": "^3.9.6", + "webpack-cli": "^3.3.12", + "webpack-dev-server": "^3.11.0" + } +} diff --git a/src/features/App.tsx b/src/features/App.tsx new file mode 100644 index 00000000..56b9234f --- /dev/null +++ b/src/features/App.tsx @@ -0,0 +1,42 @@ +import * as React from 'react'; +import { Provider } from 'react-redux'; +import { Home } from './Home'; +import { Sf } from '../services'; +import { AppReducer } from './AppReducer'; +export interface AppProps { + +} + +export interface AppState { + +} + + + + +const rootStore = Sf.store.initStore(AppReducer, {}); +export class App extends React.Component +{ + + public componentDidMount() + { + Sf.store.dispatch({ + type: 'Service_GetNewRecord_Success', + object: 'addedstore', + record: "praveen", + }); + + window.store = Sf.store.getState(); + } + + public render() { + console.log(Sf.store.getState()); + return ( + + + + ) + + + } +} \ No newline at end of file diff --git a/src/features/AppReducer.ts b/src/features/AppReducer.ts new file mode 100644 index 00000000..1abc5eca --- /dev/null +++ b/src/features/AppReducer.ts @@ -0,0 +1,5 @@ +import { serviceReducer } from '../store'; + +export function AppReducer(state: any, action: any) { + return serviceReducer(state, action); +} diff --git a/src/features/Home.tsx b/src/features/Home.tsx new file mode 100644 index 00000000..fcbb4da2 --- /dev/null +++ b/src/features/Home.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; + + +export class Home extends React.Component<{}, {}> +{ + public render() { + return 'Hello Application'; + } +} \ No newline at end of file diff --git a/src/features/index.ts b/src/features/index.ts new file mode 100644 index 00000000..81192987 --- /dev/null +++ b/src/features/index.ts @@ -0,0 +1,2 @@ +export * from './App'; +export * from './Home'; \ No newline at end of file diff --git a/src/index.tsx b/src/index.tsx new file mode 100644 index 00000000..2a4b8c7f --- /dev/null +++ b/src/index.tsx @@ -0,0 +1,9 @@ +import * as React from "react"; +import * as ReactDOM from "react-dom"; +import { App } from './features'; + + +ReactDOM.render( + , + document.getElementById("example") +); \ No newline at end of file diff --git a/src/services/ApiService.ts b/src/services/ApiService.ts new file mode 100644 index 00000000..ad836e2f --- /dev/null +++ b/src/services/ApiService.ts @@ -0,0 +1,13 @@ +import { ServiceApi } from "./ServiceApi"; + +export class ApiService { + + private api: ServiceApi = new ServiceApi(); + constructor() { + console.log(this.api); + } + + + + +} \ No newline at end of file diff --git a/src/services/ServiceApi.ts b/src/services/ServiceApi.ts new file mode 100644 index 00000000..d87747b4 --- /dev/null +++ b/src/services/ServiceApi.ts @@ -0,0 +1,6 @@ +import { WebApi } from './WebApi'; +export class ServiceApi extends WebApi { + constructor() { + super(undefined, undefined, undefined); + } +} \ No newline at end of file diff --git a/src/services/ServiceFactory.ts b/src/services/ServiceFactory.ts new file mode 100644 index 00000000..01fb6cc2 --- /dev/null +++ b/src/services/ServiceFactory.ts @@ -0,0 +1,32 @@ + +import { ApiService } from './ApiService'; +import { StoreService } from './StoreService'; +class Builder { + private instance: T; + + public build(creator: () => T) { + if (!this.instance) { + this.instance = creator(); + } + + return this.instance; + } +} + +// tslint:disable:typedef +export class ServiceFactory { + private _metadata = new Builder(); + get metadata(): ApiService { + return this._metadata.build(() => new ApiService()); + } + + private _store = new Builder(); + get store(): StoreService { + return this._store.build(() => new StoreService()); + } + + +} + +export const Sf: ServiceFactory = new ServiceFactory(); + diff --git a/src/services/StoreService.ts b/src/services/StoreService.ts new file mode 100644 index 00000000..17947f34 --- /dev/null +++ b/src/services/StoreService.ts @@ -0,0 +1,36 @@ +import { createStore, applyMiddleware, Store } from 'redux'; + +// tslint:disable-next-line:import-name +//import reduxImmutableStateInvariant from 'redux-immutable-state-invariant'; +import thunk from 'redux-thunk'; + +export class StoreService { + private store: any; + + public initStore = (reducer: any, initalState: any): Store => { + this.store = createStore( + reducer, + initalState || {}, + //applyMiddleware(thunk, reduxImmutableStateInvariant()), + applyMiddleware(thunk), + ); + const store: any = this.store; + return store; + } + + public getStore(): Store { + if (!this.store) { + throw new Error('Store is not initialized yet!'); + } + + return this.store; + } + + public dispatch(action: any): any { + return this.getStore().dispatch(action); + } + + public getState(): any { + return this.getStore().getState() as any; + } +} diff --git a/src/services/WebApi.ts b/src/services/WebApi.ts new file mode 100644 index 00000000..efd0c581 --- /dev/null +++ b/src/services/WebApi.ts @@ -0,0 +1,122 @@ + +import 'whatwg-fetch'; + +export class WebApi { + private apiHost: string; + private apiPort: number | undefined; + private apiPath: string | undefined; + + + constructor(host: string | undefined, port: number | undefined, path: string | undefined) { + + if (!Promise) { + // tslint:disable:no-string-literal + // tslint:disable:no-require-imports + window['Promise'] = require('es6-promise'); + } + + if (host) { + this.apiHost = host; + } else { + // tslint:disable-next-line:no-http-string + //http method for local testing. uncomment http and comment https for testing in local + + this.apiHost = 'https://webapi.' + window.location.hostname; + //this.apiHost = 'http://webapi.' + window.location.hostname; + + } + + //remove port definition for apis + //port for local testing. uncomment port for testing in local + + //this.apiPort = port; + this.apiPath = path; + + + } + + public httpPost(path: string, body?: string | Object): Promise { + return this.send('POST', path, body); + } + + public httpPatch(path: string, body: string | Object): Promise { + return this.send('PATCH', path, body); + } + + public httpGet(path: string): Promise { + return this.send('GET', path); + } + + public httpDelete(path: string): Promise { + return this.send('DELETE', path); + } + + public list(path: string): Promise { + return this.httpGet(path).then((body) => body); + } + + + + private getUrl(path: string): string { + return this.apiHost + (this.apiPort ? ':' + this.apiPort : '') + (this.apiPath ? this.apiPath : '') + path; + } + + private checkStatus(resp: any): Promise { + + if (resp.status_code && resp.status_code >= 200 && resp.status_code < 300) { + return new Promise((resolve, reject) => { + resolve(resp); + }); + } else { + //This is to make sure we do not show unnecesary errors + //while login page is loading. + return new Promise((resolve, reject) => { + setTimeout(() => resolve(true), 5000); + }); + } + + + } + + + private readBody(response: any): any { + return new Promise((resolve, reject) => { + response.text().then((body: string) => { + const apiResp = new Object(); + apiResp.url = response.url; + apiResp.status_code = response.status; + + if (body) { + apiResp.body = JSON.parse(body); + } + + resolve(apiResp); + }).catch(reject); + }); + } + + + + + + private send(httpMethod: string, path: string, body?: Object): Promise { + const myHeaders = new Headers(); + myHeaders.append('Content-Type', 'application/json'); + + return fetch(this.getUrl(path), { + body: body ? JSON.stringify(body) : undefined, + credentials: 'include', + headers: myHeaders, + method: httpMethod, + }) + .then(this.readBody) + .then(this.checkStatus) + .then((apiResp: any) => { + return new Promise((resolve, reject) => { + resolve(apiResp.body); + }); + }); + } + + +} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 00000000..4004cc3f --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,2 @@ +export * from './StoreService'; +export * from './ServiceFactory'; \ No newline at end of file diff --git a/src/store/ServiceActions.ts b/src/store/ServiceActions.ts new file mode 100644 index 00000000..2c34c99d --- /dev/null +++ b/src/store/ServiceActions.ts @@ -0,0 +1,77 @@ + + +// tslint:disable-next-line:export-name +export const GetRecord_Success: string = 'Service_GetRecord_Success'; +export const getRecordSuccess = (object: string, record: Object) => { + return { + type: GetRecord_Success, + object, + record, + }; +}; + +export const ChangeDataview: string = 'Service_ChangeDataview'; +export const changeDataview = (object: string, dataviewId: string) => { + return { + type: ChangeDataview, + object, + dataviewId, + }; +}; + +export const GetNewRecord_Success: string = 'Service_GetNewRecord_Success'; +export const getNewRecordSuccess = (object: string, record: Object) => { + return { + type: GetNewRecord_Success, + object, + record, + }; +}; + +export const AppendToList: string = 'Service_AppendToList'; +export const appendToList = (listType: string, listKey: string, listItemId: string) => { + return { + type: AppendToList, + listType, + listKey, + listItemId, + }; +}; + +export const GetRecords_Success: string = 'Service_GetRecord_Success'; +export const getRecordsSuccess = (object: string, records: Object[]) => { + return { + type: GetRecords_Success, + object, + records, + }; +}; + +export const GetList_Success: string = 'Service_GetList_Success'; +export const getListSuccess = (listCategory: string, listType: string, listKey: string, listObject: string, listResponse: any) => { + return { + type: GetList_Success, + listType, + listKey, + listObject, + listResponse, + }; +}; + + + + + + + +export const DeleteRecord: string = 'Service_DeleteRecord'; +export const deleteRecord = (object: string, recordId: string) => { + return { + type: DeleteRecord, + object, + recordId, + }; +}; + + + diff --git a/src/store/ServiceReducers.ts b/src/store/ServiceReducers.ts new file mode 100644 index 00000000..371d1fc2 --- /dev/null +++ b/src/store/ServiceReducers.ts @@ -0,0 +1,20 @@ +import * as ServiceActions from './ServiceActions'; +import { StoreUtils } from './StoreUtils'; + +// tslint:disable:export-name +// tslint:disable:max-func-body-length +// tslint:disable:cyclomatic-complexity +export function serviceReducer(state: any, action: any) { + let newState = state; + + if (action.type === ServiceActions.GetRecord_Success) { + newState = StoreUtils.setRecord(state, action.object, action.record); + return newState; + } + + if (action.type === ServiceActions.GetNewRecord_Success) { + newState = StoreUtils.setNewRecord(state, action.object, action.record); + return newState; + } + return state; +} diff --git a/src/store/StoreUtils.ts b/src/store/StoreUtils.ts new file mode 100644 index 00000000..2b902b97 --- /dev/null +++ b/src/store/StoreUtils.ts @@ -0,0 +1,161 @@ +//@ts-ignore +import { Utils } from '../utils'; + +const category_records: string = 'records'; +const multiple_category_record_ids: string = 'mcr_ids'; +const category_key2id: string = 'key2id_mappings'; +const category_lists: string = 'lists'; + + +// tslint:disable:no-stateless-class +// tslint:disable:no-unnecessary-local-variable +// tslint:disable:no-string-literal +export class StoreUtils { + + public static deleteRecord(state: any, object: string, recordId: string) { + let newState = state; + + const recordList: any = this.getRecords(newState, object) as Object; + if (!recordList) { + return; + } + const updatedList: Object = {}; + Object.keys(recordList).forEach((record: any) => { + const obj: any = recordList[record] as any; + if (record !== recordId) { + Object.assign(updatedList, { [record as string]: obj }); + newState = this.setNewRecord(newState, object, obj); + } + }); + + //FIX_ME : Store needs to be updated - remove deleted record from category_records + //newState = this.setRecord(newState, object, updatedList); + + //Delete record we need to make sure it is cleaned in various places. + //1. Delete the record from category_records + + + return newState; + } + + + + + + public static getRecord(state: any, object: string, idOrKey: string): any | undefined { + let record = this.getValue(state, category_records, object, idOrKey) as any; + if (!record) { + const idByKey = this.getValue(state, category_key2id, object, idOrKey) as string; + if (idByKey) { + record = this.getValue(state, category_records, object, idByKey) as any; + } + } + + return record; + } + + public static getNewRecord(state: any, object: string): any | undefined { + const record = this.getValue(state, category_records, object, 'new') as any; + return record; + } + + public static setRecord(state: any, object: string, value: any): any { + const recordId = value.id; + const recordKey = value.key; + + let newState = this.setValue(state, category_records, object, recordId, value); + + if (recordKey) { + newState = this.setValue(newState, category_key2id, object, recordKey, recordId); + } + + return newState; + } + + + public static setNewRecord(state: any, object: string, value: any): any { + const newState = this.setValue(state, category_records, object, 'new', value); + return newState; + } + + + public static getRecords(state: any, object: string): Object | undefined { + return this.getValue(state, category_records, object, null); + } + + public static getList(state: any, listType: string, listKey: string): any | undefined { + return this.getValue(state, category_lists, listType, listKey) as any; + } + + public static setList(state: any, listType: string, listKey: string, value: any): any { + return this.setValue(state, category_lists, listType, listKey, value); + } + public static setLocalStorage(objectKey: string, storeObj: any) { + let storeKey = multiple_category_record_ids; + if (objectKey) { + storeKey = storeKey + ':' + objectKey; + } + localStorage.setItem(storeKey, JSON.stringify(storeObj)); + } + + public static getLocalStorage(objectKey: string): string { + let storeKey = multiple_category_record_ids; + if (objectKey) { + storeKey = storeKey + ':' + objectKey; + } + return localStorage.getItem(storeKey) || ''; + } + + public static removeLocalStorage(objectKey: string) { + let storeKey = multiple_category_record_ids; + if (objectKey) { + storeKey = storeKey + ':' + objectKey; + } + localStorage.removeItem(storeKey); + } + + public static clearLocalStogareByKey(key: string) { + Object.entries(localStorage) + .map(x => x[0]) + .filter(x => x.substring(0, key.length) == key) + .map(x => localStorage.removeItem(x)); + } + + public static clearLocalStogare() { + localStorage.clear(); + } + + private static getValue(state: any, category: string, nodeType: string | undefined | null, key: string | undefined | null): Object | undefined { + return Utils.get(state, this.getValueKey(category, nodeType, key)); + } + + public static updateValue(state: any, category: string, nodeType: string, key: string, value: Object | undefined): any { + return this.setValue(state, category, nodeType, key, Object.assign({}, this.getValue(state, category, nodeType, key), value)); + } + + private static setValue(state: any, category: string, nodeType: string | undefined | null, key: string | undefined | null, value: Object | undefined): any { + const oldParentObject = this.getValue(state, category, nodeType, null); + let newParentObject; + if (key == "") { + newParentObject = Object.assign({}, oldParentObject, value); + } else { + newParentObject = Object.assign({}, oldParentObject, { [key as string]: value }); + } + const categoryNodeKey = this.getValueKey(category, nodeType, null); + const newState = Object.assign({}, state, { [categoryNodeKey]: oldParentObject }, { [categoryNodeKey]: newParentObject }); + return newState; + } + + private static getValueKey(category: string, nodeType: string | undefined | null, key: string | undefined | null) { + let storeKey = category; + if (nodeType) { + storeKey = storeKey + ':' + nodeType; + } + if (key) { + storeKey = storeKey + '.' + key; + } + //return category + ':' + nodeType + (Utils.isNotBlank(key) ? '.' + key : ''); + return storeKey; + } + +} diff --git a/src/store/index.ts b/src/store/index.ts new file mode 100644 index 00000000..c3aed906 --- /dev/null +++ b/src/store/index.ts @@ -0,0 +1,2 @@ +export * from './StoreUtils'; +export * from './ServiceReducers'; \ No newline at end of file diff --git a/src/utils/Utils.ts b/src/utils/Utils.ts new file mode 100644 index 00000000..903feb29 --- /dev/null +++ b/src/utils/Utils.ts @@ -0,0 +1,27 @@ + +//@ts-ignore +import * as ld_get from 'lodash/get'; +//@ts-ignore +import * as ld_set from 'lodash/set'; + +export class Utils { + public static INSTANCE: Utils = new Utils(); + + + public static get(obj: Object | undefined, key: string): any { + return ld_get(obj, key); + } + + public static set(obj: Object, key: string, value: any): any { + return ld_set(obj, key, value); + } + + public static unique(input?: T[]): T[] | undefined { + if (!input || input.length === 0) { + return input; + } + + return Array.from(new Set(input)); + } + +} diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..8606274d --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1 @@ +export * from './Utils'; \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 00000000..62cff093 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,55 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "allowSyntheticDefaultImports": true, + + "declaration": true, + "baseUrl": "./src", + "outDir": "./dist", + "sourceMap": true, + "noImplicitAny": true, + "strictNullChecks": true, + "module": "esnext", + "target": "es5", + "jsx": "react", + "moduleResolution": "Node", + "allowJs": false, + "stripInternal": true, + "noUnusedLocals": true, + "importHelpers": true, + "skipLibCheck": true, + "lib": [ + "dom", + "es5", + "es2016", + "es2015", + "es2015.core", + "es2015.collection", + "es2015.iterable", + "es2015.promise" + ], + "typeRoots": [ + "./node_modules/@types", + "./src/typings" + ] + }, + "awesomeTypescriptLoaderOptions": { + "silent": true, + "useTranspileModule": true, + "usePrecompiledFiles": true, + "ignoreDiagnostics": [ + 2339, + 2688 + ] + }, + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules", + "dist", + "test", + "src/**/*Test.ts", + "src/**/*Test.tsx" + ] +} \ No newline at end of file diff --git a/tslint.json b/tslint.json new file mode 100644 index 00000000..46dd6668 --- /dev/null +++ b/tslint.json @@ -0,0 +1,238 @@ +{ + "extends": "tslint:recommended", + "rules": { + + /** + * Security Rules. The following rules should be turned on because they find security issues + * or are recommended in the Microsoft Secure Development Lifecycle (SDL) + */ + "insecure-random": true, + "no-banned-terms": true, + "no-cookies": true, + "no-delete-expression": true, + "no-disable-auto-sanitization": true, + "no-document-domain": true, + "no-document-write": true, + "no-eval": true, + "no-exec-script": true, + "no-function-constructor-with-string-args": true, + "no-http-string": [true, "http://www.example.com/?.*", "http://www.examples.com/?.*"], + "no-inner-html": true, + "no-octal-literal": true, + "no-reserved-keywords": true, + "no-string-based-set-immediate": true, + "no-string-based-set-interval": true, + "no-string-based-set-timeout": true, + "non-literal-require": true, + "possible-timing-attack": true, + "react-anchor-blank-noopener": true, + "react-iframe-missing-sandbox": true, + "react-no-dangerous-html": true, + + /** + * Common Bugs and Correctness. The following rules should be turned on because they find + * common bug patterns in the code or enforce type safety. + */ + "forin": true, + "jquery-deferred-must-complete": true, + "label-position": true, + "mocha-avoid-only": true, + "mocha-no-side-effect-code": true, + "no-any": false, + "no-arg": true, + "no-backbone-get-set-outside-model": true, + "no-bitwise": true, + "no-conditional-assignment": true, + "no-console": [true, "debug", "info", "log", "time", "timeEnd", "trace"], + "no-constant-condition": true, + "no-control-regex": true, + "no-debugger": true, + "no-duplicate-case": true, + "no-duplicate-variable": true, + "no-empty": true, + "no-for-in-array": false, + "no-increment-decrement": true, + "no-invalid-regexp": true, + "no-invalid-this": true, + "no-jquery-raw-elements": true, + "no-regex-spaces": true, + "no-sparse-arrays": true, + "no-stateless-class": true, + "no-string-literal": true, + "no-unnecessary-bind": true, + "no-unnecessary-override": true, + "no-unsafe-finally": true, + "no-unused-expression": true, + "no-unused-new": true, + "no-use-before-declare": true, + "no-with-statement": true, + "promise-must-complete": true, + "radix": true, + "react-this-binding-issue": false, + "react-unused-props-and-state": true, + "restrict-plus-operands": false, // the plus operand should really only be used for strings and numbers + "switch-default": true, + "triple-equals": [true, "allow-null-check"], + "use-isnan": true, + "use-named-parameter": true, + "valid-typeof": true, + + /** + * Code Clarity. The following rules should be turned on because they make the code + * generally more clear to the reader. + */ + "adjacent-overload-signatures": true, + "array-type": [true, "array"], + "arrow-parens": false, // for simple functions the parens on arrow functions are not needed + "chai-prefer-contains-to-index-of": true, + "chai-vague-errors": true, + "class-name": true, + "comment-format": [true], + "completed-docs": [false, "classes"], + "export-name": true, + "function-name": [ true, { + "method-regex": "^[a-z][\\w\\d]+$", + "private-method-regex": "^[a-z][\\w\\d]+$", + "protected-method-regex": "^[a-z][\\w\\d]+$", + "static-method-regex": "^[a-z][\\w\\d]+$", + "function-regex": "^[a-z][\\w\\d]+$" + }], + "import-name": true, + "interface-name": [true, "never-prefix"], + "jsdoc-format": true, + "max-classes-per-file": [true, 3], // we generally recommend making one public class per file + "max-file-line-count": [true, 500], + "max-func-body-length": [true, 100, {"ignore-parameters-to-function-regex": "describe"}], + "max-line-length": [true, 200], + "member-access": true, + "member-ordering": [true, { "order": "fields-first" }], + "missing-jsdoc": false, + "mocha-unneeded-done": true, + "new-parens": true, + "no-construct": true, + "no-default-export": true, + "no-empty-interfaces": true, + "no-for-in": true, + "no-function-expression": true, + "no-inferrable-types": [false], // turn no-inferrable-types off in order to make the code consistent in its use of type decorations + "no-multiline-string": true, // multiline-strings often introduce unnecessary whitespace into the string literals + "no-null-keyword": false, // turn no-null-keyword off and use undefined to mean not initialized and null to mean without a value + "no-parameter-properties": true, + "no-relative-imports": false, + "no-require-imports": true, + "no-shadowed-variable": true, + "no-suspicious-comment": true, + "no-typeof-undefined": true, + "no-unnecessary-field-initialization": true, + "no-unnecessary-local-variable": true, + "no-unsupported-browser-code": true, + "no-var-keyword": true, + "no-var-requires": true, + "no-var-self": true, + "object-literal-sort-keys": false, // turn object-literal-sort-keys off and sort keys in a meaningful manner + "one-variable-per-declaration": [true, "ignore-for-loop"], + "only-arrow-functions": [true, "allow-declarations"], // there are many valid reasons to declare a function + "ordered-imports": [false], + "prefer-array-literal": true, + "prefer-const": true, + "prefer-for-of": true, + //"typedef": [true, "call-signature", "parameter", "arrow-parameter", "property-declaration", "variable-declaration", "member-variable-declaration"], + "typedef": [true, "parameter", "property-declaration", "member-variable-declaration"], + "underscore-consistent-invocation": true, + "variable-name": false, + + /** + * Accessibility. The following rules should be turned on to guarantee the best user + * experience for keyboard and screen reader users. + */ + "react-a11y-anchors": true, + "react-a11y-aria-unsupported-elements": true, + "react-a11y-event-has-role": true, + "react-a11y-image-button-has-alt": true, + "react-a11y-img-has-alt": true, + "react-a11y-lang": true, + "react-a11y-meta": true, + "react-a11y-props": true, + "react-a11y-proptypes": true, + "react-a11y-role": true, + "react-a11y-role-has-required-aria-props": true, + "react-a11y-role-supports-aria-props": true, + "react-a11y-tabindex-no-positive": true, + "react-a11y-titles": true, + + /** + * Whitespace related rules. The only recommended whitespace strategy is to pick a single format and + * be consistent. + */ + "align": [true, "parameters", "arguments", "statements"], + "curly": true, + "eofline": true, + "indent": [true, "spaces"], + "linebreak-style": [true, "LF"], + "no-consecutive-blank-lines": [true, 2], + "no-empty-line-after-opening-brace": false, + "no-single-line-block-comment": true, + "no-trailing-whitespace": true, + "no-unnecessary-semicolons": true, + "object-literal-key-quotes": [true, "as-needed"], + "one-line": [true, "check-open-brace", "check-catch", "check-else", "check-whitespace"], + "quotemark": [true, "single", "avoid-escape"], + "react-tsx-curly-spacing": false, + "semicolon": [true, "always"], + "trailing-comma": [true, {"singleline": "never", "multiline": "always"}], // forcing trailing commas for multi-line + // lists results in lists that are easier to reorder and version control diffs that are more clear. + // Many teams like to have multiline be 'always'. There is no clear consensus on this rule but the + // internal MS JavaScript coding standard does discourage it. + "typedef-whitespace": +[ + true, + { + "call-signature": "nospace", + "index-signature": "nospace", + "parameter": "nospace", + "property-declaration": "nospace", + "variable-declaration": "nospace" + }, + { + "call-signature": "onespace", + "index-signature": "onespace", + "parameter": "onespace", + "property-declaration": "onespace", + "variable-declaration": "onespace" + } +], + "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"], + + /** + * Controversial/Configurable rules. + */ + "ban": false, // only enable this if you have some code pattern that you want to ban + "cyclomatic-complexity": [true, 20], + "file-header": [false], // enable this rule only if you are legally required to add a file header + "no-angle-bracket-type-assertion": true, // pick either type-cast format and use it consistently + "no-internal-module": false, // only enable this if you are not using internal modules + "no-mergeable-namespace": false, // your project may require mergeable namespaces + "no-namespace": false, // only enable this if you are not using modules/namespaces + "no-reference": true, // in general you should use a module system and not /// reference imports + "no-unexternalized-strings": false, // the VS Code team has a specific localization process that this rule enforces + "object-literal-shorthand": false, // object-literal-shorthand offers an abbreviation not an abstraction + "prefer-type-cast": false, // pick either type-cast format and use it consistently + + /** + * Deprecated rules. The following rules are deprecated for various reasons. + */ + "missing-optional-annotation": false, // now supported by TypeScript compiler + "no-duplicate-parameter-names": false, // now supported by TypeScript compiler + "no-missing-visibility-modifiers": false, // use tslint member-access rule instead + "no-multiple-var-decl": false, // use tslint one-variable-per-declaration rule instead + "no-switch-case-fall-through": false, // now supported by TypeScript compiler + "no-unused-imports": false // use tslint no-unused-variable rule instead + }, + "linterOptions": { + "typeCheck": true + }, + "rulesDirectory": [ + "node_modules/tslint-eslint-rules/dist/rules", + "node_modules/tslint-microsoft-contrib" + ] +} diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..30f7f88e --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,74 @@ +const ExtractTextPlugin = require("extract-text-webpack-plugin"); +const HtmlWebpackPlugin = require('html-webpack-plugin') + +const HtmlWebpackPluginConfig = new HtmlWebpackPlugin({ + filename: 'index.html', + template: __dirname + '/index.html', + inject: 'body', + alwaysWriteToDisk: true +}); + + +module.exports = { + entry: { + 'bundle': "./src/index.tsx", + 'blueprint': "@blueprintjs/core/lib/css/blueprint.css", + 'normalize': "normalize.css/normalize.css", + }, + output: { + filename: '[name].js', + path: __dirname + "/dist", + publicPath: '/', + }, + + plugins: [ + HtmlWebpackPluginConfig + ], + + devServer: { + open: 'http://localhost:9000', + port: 9000, + publicPath: "/", + }, + node: { + __filename: true, + }, + + + // Enable sourcemaps for debugging webpack's output. + devtool: "source-map", + + resolve: { + // Add '.ts' and '.tsx' as resolvable extensions. + extensions: [".webpack.js", ".web.js", ".ts", ".tsx", ".js", ".css", ".scss"], + }, + + module: { + rules: [ + // All files with a '.ts' or '.tsx' extension will be handled by 'awesome-typescript-loader'. + { test: /\.tsx?$/, loader: "awesome-typescript-loader" }, + + // All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'. + { enforce: "pre", test: /\.js$/, loader: "source-map-loader" }, + + { + test: /\.css$/, + use: ['style-loader', 'css-loader'] + }, + { + test: /\.js$/, + exclude: /node_modules/, + loader: 'babel-loader', + query: { + presets: ['react', 'env'] + } + }, + { + test: /.(ico|png|jpg|gif|svg|eot|ttf|woff|woff2)(\?.+)?$/, + loader: "url-loader", + } + + + ] + }, +}; \ No newline at end of file From 73a27ebbbe7a6b5c3262a3fa7a84ab64eba9e5a9 Mon Sep 17 00:00:00 2001 From: PraveenKumar Date: Thu, 9 Jul 2020 11:34:55 +0530 Subject: [PATCH 2/3] Initialize the test project. --- package.json | 14 +- src/components/index.ts | 1 + src/components/tasks/AddTaskForm.tsx | 202 ++++++++++++++++++ src/components/tasks/Task.scss | 53 +++++ src/components/tasks/TaskGrid.tsx | 296 +++++++++++++++++++++++++++ src/components/tasks/TaskList.tsx | 71 +++++++ src/components/tasks/index.ts | 2 + src/features/App.tsx | 11 +- src/features/Home.tsx | 16 +- src/models/Task.ts | 19 ++ src/models/index.ts | 1 + tsconfig.json | 2 +- webpack.config.js | 19 +- 13 files changed, 680 insertions(+), 27 deletions(-) create mode 100644 src/components/index.ts create mode 100644 src/components/tasks/AddTaskForm.tsx create mode 100644 src/components/tasks/Task.scss create mode 100644 src/components/tasks/TaskGrid.tsx create mode 100644 src/components/tasks/TaskList.tsx create mode 100644 src/components/tasks/index.ts create mode 100644 src/models/Task.ts create mode 100644 src/models/index.ts diff --git a/package.json b/package.json index da187922..6f3e0d07 100644 --- a/package.json +++ b/package.json @@ -1,5 +1,5 @@ { - "name": "react.pbpraveen.me", + "name": "sureify.pbpraveen.me", "version": "1.0.0", "description": "React Tutorial with Typescript", "main": "index.js", @@ -13,6 +13,9 @@ "license": "ISC", "dependencies": { "@blueprintjs/core": "^3.29.0", + "@blueprintjs/datetime": "^3.18.3", + "@blueprintjs/select": "^3.13.4", + "@blueprintjs/table": "^3.8.10", "@types/lodash": "^4.14.157", "@types/pure-render-decorator": "^0.2.28", "@types/react": "^16.0.36", @@ -22,9 +25,7 @@ "babel-loader": "^8.1.0", "babel-preset-es2015": "^6.24.1", "babel-preset-react": "^6.24.1", - "css-loader": "^3.6.0", "es6-promise": "^4.2.8", - "extract-text-webpack-plugin": "^3.0.2", "firebase": "^4.11.0", "html-webpack-plugin": "^4.3.0", "lodash": "^4.17.15", @@ -35,7 +36,6 @@ "redux": "^4.0.5", "redux-thunk": "^2.3.0", "runtypes": "^4.3.0", - "sass-loader": "^8.0.2", "style-loader": "^1.2.1", "url-loader": "^4.1.0", "webpack": "^4.43.0", @@ -43,6 +43,12 @@ }, "devDependencies": { "awesome-typescript-loader": "^5.2.1", + "css-loader": "^3.6.0", + "extract-text-webpack-plugin": "^2.1.2", + "mini-css-extract-plugin": "^0.9.0", + "node-sass": "^4.14.1", + "postcss-loader": "^3.0.0", + "sass-loader": "^8.0.2", "source-map-loader": "^1.0.1", "typescript": "^3.9.6", "webpack-cli": "^3.3.12", diff --git a/src/components/index.ts b/src/components/index.ts new file mode 100644 index 00000000..727a91d7 --- /dev/null +++ b/src/components/index.ts @@ -0,0 +1 @@ +export * from './tasks'; \ No newline at end of file diff --git a/src/components/tasks/AddTaskForm.tsx b/src/components/tasks/AddTaskForm.tsx new file mode 100644 index 00000000..f2fa294a --- /dev/null +++ b/src/components/tasks/AddTaskForm.tsx @@ -0,0 +1,202 @@ +import * as React from 'react'; +import { Dialog, Classes, Button, Intent } from '@blueprintjs/core'; +import { FormGroup, InputGroup, TextArea } from '@blueprintjs/core'; +import { DateInput, IDateFormatProps } from "@blueprintjs/datetime"; +import { Task } from '../../models'; +import { StoreUtils } from '../../store'; +import { Sf } from '../../services'; +export interface AddTaskFormProps { + openModal?: boolean; + onClose?: (value?: any) => any; + taskRecord?: Task; + mode?: 'add' | 'edit' | 'view'; +} + +export interface AddTaskFormState { + openModal?: boolean; + taskRecord?: Task; + mode?: 'add' | 'edit' | 'view'; +} + +export class AddTaskForm extends React.Component +{ + + + constructor(props: AddTaskFormProps) { + super(props); + this.state = { + openModal: props.openModal, + taskRecord: props.taskRecord || new Task(), + mode: props.taskRecord ? 'edit' : 'add' + } + } + + public componentWillReceiveProps(nextProps: AddTaskFormProps) { + if (nextProps.taskRecord) { + const taskRecord = JSON.parse(JSON.stringify(nextProps.taskRecord)); + taskRecord.dueDate = new Date(taskRecord.dueDate); + this.setState({ openModal: nextProps.openModal, taskRecord: nextProps.taskRecord, mode: nextProps.mode ? nextProps.mode : 'edit' }) + } else { + this.setState({ openModal: nextProps.openModal, taskRecord: undefined, mode: 'add' }) + } + } + + + public render() { + const state = this.state; + const jsDateFormatter: IDateFormatProps = { + // note that the native implementation of Date functions differs between browsers + formatDate: date => date.toLocaleDateString(), + parseDate: str => new Date(str), + placeholder: "MM/DD/YYYY", + }; + const taskRecord = state.taskRecord ? JSON.parse(JSON.stringify(state.taskRecord)) : state.taskRecord; + const dueDate = taskRecord && taskRecord.dueDate && new Date(taskRecord.dueDate); + return ( + +
+ + this.handleOnChange('title', e.target.value)} type='text' id="text-summary" /> + + +