Migration to TypeScript

This commit is contained in:
Paulo Gustavo Veiga 2020-12-05 21:28:00 -08:00
parent c7f0c3e02e
commit cd72287481
17 changed files with 518 additions and 5763 deletions

View File

@ -44,5 +44,8 @@
] ]
}, },
"homepage": "http://localhost:8080/react", "homepage": "http://localhost:8080/react",
"license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL" "license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL",
"dependencies": {
"@types/react-google-recaptcha": "^2.1.0"
}
} }

View File

@ -1,58 +1,48 @@
{ {
"name": "@wisemapping/login", "name": "@wisemapping/login",
"version": "0.1.3", "version": "0.1.3",
"main": "src/app.jsx", "main": "app.jsx",
"scripts": { "scripts": {
"start": "react-scripts start", "start": "webpack serve",
"build": "react-scripts build", "build": "webpack --mode production",
"test": "react-scripts test", "lint": "eslint src"
"eject": "react-scripts eject", },
"extract": "formatjs extract", "repository": "http://www.wisemapping.com",
"compile": "formatjs compile" "author": "Paulo Veiga <pveiga@gmail.com>, Ezequiel Bergamaschi <ezequielbergamaschi@gmail.com>",
"license": "MIT",
"private": false,
"devDependencies": {
"@babel/preset-env": "^7.12.7",
"@babel/preset-react": "^7.12.7",
"@types/react": "^17.0.0",
"@types/react-dom": "^17.0.0",
"@types/react-router-dom": "^5.1.6",
"@typescript-eslint/eslint-plugin": "^4.8.1",
"@typescript-eslint/parser": "^4.8.1",
"babel-loader": "^8.2.2",
"clean-webpack-plugin": "^3.0.0",
"css-loader": "^5.0.1",
"eslint": "^7.14.0",
"eslint-plugin-react": "^7.21.5",
"eslint-plugin-react-hooks": "^4.2.0",
"html-webpack-plugin": "^4.5.0",
"sass-loader": "^10.1.0",
"style-loader": "^2.0.0",
"svg-url-loader": "^7.1.1",
"ts-loader": "^8.0.11",
"ts-node": "^9.0.0",
"typescript": "^4.1.2",
"url-loader": "^4.1.1",
"webpack": "^5.6.0",
"webpack-cli": "^4.2.0",
"webpack-dev-server": "^3.11.0"
}, },
"dependencies": { "dependencies": {
"axios": "^0.21.0", "axios": "^0.21.0",
"cors": "^2.8.5",
"joi": "^17.3.0",
"react": "^17.0.1", "react": "^17.0.1",
"react-dom": "^17.0.1", "react-dom": "^17.0.1",
"react-google-recaptcha": "^2.1.0", "react-intl": "^5.10.6",
"react-intl": "^5.10.5", "react-recaptcha-v3": "^2.0.1",
"web-vitals": "^0.2.4", "react-router-dom": "^5.2.0"
"chokidar": "^3.4.3" }
},
"devDependencies": {
"@formatjs/cli": "^2.13.14",
"@testing-library/jest-dom": "^5.11.6",
"@testing-library/react": "^11.2.2",
"@testing-library/user-event": "^12.2.2",
"react-scripts": "4.0.1"
},
"author": {
"name": "Paulo Veiga",
"login.email": "pveiga@wisemapping.com"
},
"contributors": [
"Ezequiel Bergamaschi"
],
"eslintConfig": {
"extends": [
"react-app",
"react-app/jest"
]
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"homepage": "http://localhost:8080/react",
"license": "https://wisemapping.atlassian.net/wiki/spaces/WS/pages/524357/WiseMapping+Public+License+Version+1.0+WPL"
} }

View File

@ -1,28 +0,0 @@
import React from 'react';
import { FormattedMessage } from 'react-intl'
import { ReactComponent as SvgLogo } from './images/logo-text.svg'
class Footer extends React.Component {
render() {
return (
<footer className="footer" >
{/* FIXME: we have to unify the way we load SVGs <Logo />*/}
<div >
<div><a href="termsofuse.html"> <FormattedMessage id="footer.termsandconditions" defaultMessage="Term And Conditions" /> </a></div>
<div><a href="faq.html"> <FormattedMessage id="footer.faq" defaultMessage="F.A.Q."/> </a></div >
<div><a href="aboutus.html"> <FormattedMessage id="footer.aboutus" defaultMessage="About Us" /></a></div >
</div>
<div >
<div><a href="http://www.wisemapping.org/"> <FormattedMessage id="footer.opensource" defaultMessage="Open Source" /> </a></div>
<div><a href="mailto:team@wisemapping.com"> <FormattedMessage id="footer.contactus" defaultMessage="Contact Us" /> </a></div>
<div>< a href="mailto:feedback@wisemapping.com" > <FormattedMessage id="footer.feedback" defaultMessage="Feedback" /> </a></div>
</div>
<div>
<div><span className="button-style2" >< a href="https://www.paypal.com/webapps/shoppingcart?flowlogging_id=c7ac923b53025&mfid=1606520600355_c7ac923b53025#/checkout/openButton">< FormattedMessage id="footer.donations" defaultMessage="PayPal Donations" /> </a></span ></div>
</div >
</footer>
)
};
}
export default Footer;

View File

@ -0,0 +1,26 @@
import React from 'react';
import { FormattedMessage } from 'react-intl'
// import { ReactComponent as SvgLogo } from './images/logo-text.svg'
const Footer = () => {
return (
<footer className="footer" >
{/* FIXME: we have to unify the way we load SVGs <Logo />*/}
<div >
<div><a href="termsofuse.html"> <FormattedMessage id="footer.termsandconditions" defaultMessage="Term And Conditions" /> </a></div>
<div><a href="faq.html"> <FormattedMessage id="footer.faq" defaultMessage="F.A.Q." /> </a></div >
<div><a href="aboutus.html"> <FormattedMessage id="footer.aboutus" defaultMessage="About Us" /></a></div >
</div>
<div >
<div><a href="http://www.wisemapping.org/"> <FormattedMessage id="footer.opensource" defaultMessage="Open Source" /> </a></div>
<div><a href="mailto:team@wisemapping.com"> <FormattedMessage id="footer.contactus" defaultMessage="Contact Us" /> </a></div>
<div>< a href="mailto:feedback@wisemapping.com" > <FormattedMessage id="footer.feedback" defaultMessage="Feedback" /> </a></div>
</div>
<div>
<div><span className="button-style2" >< a href="https://www.paypal.com/webapps/shoppingcart?flowlogging_id=c7ac923b53025&mfid=1606520600355_c7ac923b53025#/checkout/openButton">< FormattedMessage id="footer.donations" defaultMessage="PayPal Donations" /> </a></span ></div>
</div >
</footer>
)
}
export default Footer;

View File

@ -1,13 +1,16 @@
import React from 'react'; import React from 'react';
import { FormattedMessage } from 'react-intl' import { FormattedMessage } from 'react-intl'
import logo from './images/header-logo.png'
class Header extends React.Component { //const logo = require('./images/header-logo.png')
constructor(props) {
interface HeaderProps {
type: string;
}
class Header extends React.Component<HeaderProps, HeaderProps> {
constructor(props: HeaderProps) {
super(props); super(props);
this.state = { this.state = props;
type: props.type
};
} }
render() { render() {
@ -23,14 +26,14 @@ class Header extends React.Component {
text = <span className="header-area-content-span"><span><FormattedMessage id="header.haveaccount" defaultMessage="Already have an account?" /></span></span>; text = <span className="header-area-content-span"><span><FormattedMessage id="header.haveaccount" defaultMessage="Already have an account?" /></span></span>;
signUpButton = <SignInButton className="header-area-right2" />; signUpButton = <SignInButton className="header-area-right2" />;
} else { } else {
signUpButton = <SignUpButton /> signUpButton = <SignUpButton className="header-area-right2" />
signInButton = <SignInButton />; signInButton = <SignInButton className="header-area-right2" />;
} }
return ( return (
<nav> <nav>
<div className="header"> <div className="header">
<span className="header-logo"><a href="/"><img src={logo} alt="logo" /></a></span> <span className="header-logo"><a href="/"><img src="" alt="logo" /></a></span>
{text} {text}
{signUpButton} {signUpButton}
{signInButton} {signInButton}
@ -40,14 +43,18 @@ class Header extends React.Component {
}; };
} }
const SignInButton = (props) => { interface ButtonProps {
className: string;
}
const SignInButton = (props: ButtonProps) => {
return ( return (
<span className={`button-style1 ${props.className}`}> <span className={`button-style1 ${props.className}`}>
<a href="/c/login"><FormattedMessage id="login.signin" defaultMessage="Sign In" /></a> <a href="/c/login"><FormattedMessage id="login.signin" defaultMessage="Sign In" /></a>
</span>); </span>);
} }
const SignUpButton = (props) => { const SignUpButton = (props: ButtonProps) => {
return ( return (
<span className={`button-style1 ${props.className}`}> <span className={`button-style1 ${props.className}`}>
<a href="/c/user/registration"><FormattedMessage id="login.signup" defaultMessage="Sign Up" /></a> <a href="/c/user/registration"><FormattedMessage id="login.signup" defaultMessage="Sign Up" /></a>

View File

@ -1,90 +0,0 @@
import './css/login.css';
import React from 'react';
import { FormattedMessage, injectIntl } from 'react-intl'
import Header from './Header';
import Footer from './Footer';
const ConfigStatusMessage = (props) => {
const enabled = props.enabled
let result;
if (enabled === true) {
result = (<div className="db-warn-msg">
<p>
<FormattedMessage id="login.hsqldbcofig" defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL" description="Missing production database configured" /><a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration"> here</a>
</p>
</div>);
} else {
result = <span></span>;
}
return result;
}
const LoginError = (props) => {
// @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2
const errorCode = new URLSearchParams(window.location.search).get('login_error');
let result;
if (errorCode) {
if (errorCode === 3) {
result = (
<div className='form-error-dialog'>
<FormattedMessage id="login.userinactive" defaultMessage="Sorry, your account has not been activated yet. You'll receive a notification login.email when it becomes active. Stay tuned!." />
</div>)
} else {
result = (
<div className='form-error-dialog'>
<FormattedMessage id="login.error" defaultMessage="The login.email address or login.password you entered is not valid." />
</div>)
}
}
return (<span>{result}</span>);
}
class LoginForm extends React.Component {
render() {
const intl = this.props.intl;
return (
<div className="wrapper">
<div className="content">
<h1><FormattedMessage id="login.welcome" defaultMessage="Welcome" /></h1>
<p><FormattedMessage id="login.loginto" defaultMessage="Log Into Your Account" /></p>
<LoginError />
<form action="/c/perform-login" method="POST">
<input type="email" name="username" placeholder={intl.formatMessage({ id: "login.email", defaultMessage: "Email" })} required={true} autoComplete="email" />
<input type="password" name="password" placeholder={intl.formatMessage({ id: "login.password", defaultMessage: "Password" })} required={true} autoComplete="current-password" />
<div>
<input name="_spring_security_login.remberme" id="staySignIn" type="checkbox" />
<label htmlFor="staySignIn"><FormattedMessage id="login.remberme" defaultMessage="Remember me" /></label>
</div>
<input type="submit" value={intl.formatMessage({ id: "login.signin", defaultMessage: "Sign In" })} />
</form>
<a href="/c/user/resetPassword"><FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" /></a>
</div>
</div>
);
}
}
const LoginPage = (props) => {
return (
<div>
<Header type='only-signup' />
<LoginForm />
<ConfigStatusMessage enabled='false' />
<Footer />
</div>
);
}
LoginForm = injectIntl(LoginForm)
export default LoginPage;

View File

@ -0,0 +1,89 @@
import React from 'react'
import { FormattedMessage, useIntl } from 'react-intl'
import Header from './Header'
import Footer from './Footer'
const css = require('./css/login.css')
const ConfigStatusMessage = (props: any) => {
const enabled = props.enabled
let result;
if (enabled === true) {
result = (<div className="db-warn-msg">
<p>
<FormattedMessage id="login.hsqldbcofig" defaultMessage="Although HSQLDB is bundled with WiseMapping by default during the installation, we do not recommend this database for production use. Please consider using MySQL 5.7 instead. You can find more information how to configure MySQL" description="Missing production database configured" /><a href="https://wisemapping.atlassian.net/wiki/display/WS/Database+Configuration"> here</a>
</p>
</div>);
} else {
result = <span></span>;
}
return result;
}
const LoginError = (props: any) => {
// @Todo: This must be reviewed to be based on navigation state.
// Login error example: http://localhost:8080/c/login?login.error=2
const errorCode: string = new URLSearchParams(window.location.search).get('login_error');
let result;
if (errorCode) {
if (errorCode === "3") {
result = (
<div className='form-error-dialog'>
<FormattedMessage id="login.userinactive" defaultMessage="Sorry, your account has not been activated yet. You'll receive a notification login.email when it becomes active. Stay tuned!." />
</div>)
} else {
result = (
<div className='form-error-dialog'>
<FormattedMessage id="login.error" defaultMessage="The login.email address or login.password you entered is not valid." />
</div>)
}
}
return (<span>{result}</span>);
}
const LoginForm = () => {
const intl = useIntl();
return (
<div className="wrapper">
<div className="content">
<h1><FormattedMessage id="login.welcome" defaultMessage="Welcome" /></h1>
<p><FormattedMessage id="login.loginto" defaultMessage="Log Into Your Account" /></p>
<LoginError />
<form action="/c/perform-login" method="POST">
<input type="email" name="username" placeholder={intl.formatMessage({ id: "login.email", defaultMessage: "Email" })} required={true} autoComplete="email" />
<input type="password" name="password" placeholder={intl.formatMessage({ id: "login.password", defaultMessage: "Password" })} required={true} autoComplete="current-password" />
<div>
<input name="_spring_security_login.remberme" id="staySignIn" type="checkbox" />
<label htmlFor="staySignIn"><FormattedMessage id="login.remberme" defaultMessage="Remember me" /></label>
</div>
<input type="submit" value={intl.formatMessage({ id: "login.signin", defaultMessage: "Sign In" })} />
</form>
<a href="/c/user/resetPassword"><FormattedMessage id="login.forgotpwd" defaultMessage="Forgot Password ?" /></a>
</div>
</div>
);
}
const LoginPage = (props: any) => {
return (
<div>
<Header type='only-signup' />
<LoginForm />
<ConfigStatusMessage enabled='false' />
<Footer />
</div>
);
}
export default LoginPage;

View File

@ -1,17 +1,21 @@
import './css/registration.css'; import React from 'react'
import axios from 'axios'
import { FormattedMessage, useIntl } from 'react-intl'
import useHistory from 'react-router-dom'
import { ReCaptcha } from 'react-recaptcha-v3'
import React from 'react';
import axios from 'axios';
import { FormattedMessage, injectIntl } from 'react-intl'
import { useHistory } from "react-router-dom";
import ReCAPTCHA from "react-google-recaptcha";
import Header from './Header'; import Header from './Header';
import Footer from './Footer'; import Footer from './Footer';
const css = require('./css/registration.css');
const ErrorMessageDialog = (props) => {
interface ErrorMessageDialogProps {
message: string
}
const ErrorMessageDialog = (props: ErrorMessageDialogProps) => {
let result; let result;
const message = props.message; const message = props.message;
@ -23,28 +27,37 @@ const ErrorMessageDialog = (props) => {
return result; return result;
} }
class RegistrationForm extends React.Component { interface RegistrationFormState {
email: string;
firstname: string;
lastname: string;
password: string;
recaptcha: string;
constructor(props) { errorMsg: string;
super(props) intl: string;
this.state = {
errorMsg: ""
} }
class RegistrationForm extends React.Component<{}, RegistrationFormState> {
constructor(props: {}) {
super(props)
this.handleChange = this.handleChange.bind(this); this.handleChange = this.handleChange.bind(this);
this.handleRecaptchaChange = this.handleRecaptchaChange.bind(this); this.handleRecaptchaChange = this.handleRecaptchaChange.bind(this);
this.handleSubmit = this.handleSubmit.bind(this); this.handleSubmit = this.handleSubmit.bind(this);
} }
handleChange(event) { handleChange(event: React.ChangeEvent<HTMLInputElement>) {
this.setState({ [event.target.name]: event.target.value }); const { name, value }: any = event.target;
this.setState({ [name]: value } as Pick<RegistrationFormState, keyof RegistrationFormState>);
} }
handleRecaptchaChange(value) { handleRecaptchaChange(value: string) {
this.setState({ "recaptcha": value }); this.setState({ recaptcha: value });
} }
async handleSubmit(event) { async handleSubmit(event: React.FormEvent) {
event.preventDefault(); event.preventDefault();
const { errorMsg, ...rest } = this.state; const { errorMsg, ...rest } = this.state;
@ -53,20 +66,20 @@ class RegistrationForm extends React.Component {
rest, rest,
{ headers: { 'Content-Type': 'application/json' } } { headers: { 'Content-Type': 'application/json' } }
).then(response => { ).then(response => {
const history = useHistory(); // const history = useHistory();
history.push("/c/user/registrationSuccess"); // history.push("/c/user/registrationSuccess");
}).catch(error => { }).catch(error => {
// Handle error ... // Handle error ...
const data = error.response.data; const data = error.response.data;
// const status = error.response.status; // const status = error.response.status;
const errorMsg = Object.values(data.fieldErrors)[0]; const errorMsg = Object.values(data.fieldErrors)[0] as string;
this.setState({ "errorMsg": errorMsg }); this.setState({ errorMsg: errorMsg });
}); });
} }
render() { render() {
const intl = this.props.intl; const intl = useIntl();
const errrMsg = this.state.errorMsg; const errrMsg = this.state.errorMsg;
return ( return (
@ -84,9 +97,9 @@ class RegistrationForm extends React.Component {
<input type="password" name="password" onChange={this.handleChange} placeholder={intl.formatMessage({ id: "registration.password", defaultMessage: "Password" })} required={true} autoComplete="new-password" /> <input type="password" name="password" onChange={this.handleChange} placeholder={intl.formatMessage({ id: "registration.password", defaultMessage: "Password" })} required={true} autoComplete="new-password" />
<div> <div>
<ReCAPTCHA <ReCaptcha
sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI" sitekey="6LeIxAcTAAAAAJcZVRqyHh71UMIEGNQ_MXjiZKhI"
onChange={this.handleRecaptchaChange} verifyCallback={this.handleRecaptchaChange}
/> />
</div> </div>
<p> <p>
@ -100,9 +113,9 @@ class RegistrationForm extends React.Component {
); );
} }
} }
RegistrationForm = injectIntl(RegistrationForm);
const RegistationFormPage = props => {
const RegistationFormPage = (props: any) => {
return ( return (
<div> <div>
<Header type='only-signin' /> <Header type='only-signin' />
@ -112,7 +125,7 @@ const RegistationFormPage = props => {
); );
} }
const RegistrationSuccessPage = (props) => { const RegistrationSuccessPage = (props: any) => {
return ( return (
<div> <div>
<Header type='only-signup' /> <Header type='only-signup' />

View File

@ -1,21 +1,20 @@
import React, { useEffect, useState } from 'react'; import React, { useEffect, useState } from 'react';
import LoginPage from './LoginPage.jsx'; import LoginPage from './LoginPage';
import { RegistrationSuccessPage, RegistationFormPage } from './RegistrationPage.jsx'; import { RegistrationSuccessPage, RegistationFormPage } from './RegistrationPage';
import { IntlProvider } from 'react-intl' import { IntlProvider } from 'react-intl'
import { import {
Route, Route,
Switch, Switch,
Redirect, Redirect
useRouteMatch,
} from 'react-router-dom'; } from 'react-router-dom';
function loadLocaleData(language) { function loadLocaleData(language: string) {
switch (language) { switch (language) {
case 'es': case 'es':
return import('./compiled-lang/es.json') return require('./compiled-lang/es.json')
default: default:
return import('./compiled-lang/en.json') return require('./compiled-lang/en.json')
} }
} }
@ -25,7 +24,6 @@ const App = () => {
// Boostrap i18n ... // Boostrap i18n ...
const locale = (navigator.languages && navigator.languages[0]) const locale = (navigator.languages && navigator.languages[0])
|| navigator.language || navigator.language
|| navigator.userLanguage
|| 'en-US'; || 'en-US';
@ -34,7 +32,6 @@ const App = () => {
const fetchData = async () => { const fetchData = async () => {
const messages = await loadLocaleData(language); const messages = await loadLocaleData(language);
setMessages(messages); setMessages(messages);
} }
fetchData(); fetchData();

9
packages/login/src/css/login.css.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'content': string;
'db-warn-msg': string;
'wrapper': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -0,0 +1,9 @@
// This file is automatically generated.
// Please do not change this file!
interface CssExports {
'content': string;
'db-warn-msg': string;
'wrapper': string;
}
export const cssExports: CssExports;
export default cssExports;

View File

@ -4,8 +4,11 @@ import App from './app';
import { BrowserRouter as Router } from 'react-router-dom'; import { BrowserRouter as Router } from 'react-router-dom';
function bootstrapApplication() { function bootstrapApplication() {
ReactDOM.render(<Router><App /></Router>, ReactDOM.render(
document.getElementById('root') <Router>
<App />
</Router>,
document.getElementById('root') as HTMLElement
) )
} }

View File

@ -0,0 +1 @@
declare module 'react-recaptcha-v3';

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"allowJs": true,
"esModuleInterop": true
}
}

View File

@ -0,0 +1,12 @@
{
"compilerOptions": {
"outDir": "./dist/",
"sourceMap": true,
"noImplicitAny": true,
"module": "commonjs",
"target": "es5",
"jsx": "react",
"allowJs": true,
"esModuleInterop": true
}
}

View File

@ -0,0 +1,50 @@
const path = require('path');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const webpack = require('webpack');
const { CleanWebpackPlugin } = require('clean-webpack-plugin');
module.exports = {
mode: 'development',
devtool: 'eval-source-map',
entry: {
app: path.join(__dirname, 'src', 'index.tsx')
},
target: 'web',
resolve: {
extensions: ['.ts', '.tsx', '.js', '.jsx']
},
module: {
rules: [
{
test: /\.tsx?$/,
use: 'ts-loader',
exclude: '/node_modules/'
},
{
test: /\.css$/,
use: ["style-loader", "css-loader"]
}
]
},
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist')
},
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin({
template: path.join(__dirname, 'public/index.html')
}),
new webpack.DefinePlugin({
'process.env.NODE_ENV': JSON.stringify('development')
})
],
devServer: {
contentBase: path.join(__dirname, 'dist'),
compress: true,
port: 3000,
hot: true,
}
}

5736
yarn.lock

File diff suppressed because it is too large Load Diff